/// Ascii property lists are used in legacy settings and only support four /// datatypes: Array, Dictionary, String and Data. /// See [Apple /// Documentation](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/PropertyLists/OldStylePlists/OldStylePLists.html) /// for more info. /// However this reader also support Integers as first class datatype. /// This reader will accept certain ill-formed ascii plist without complaining. /// It does not check the integrity of the plist format. usecrate::{
error::{Error, ErrorKind},
stream::{Event, OwnedEvent},
Integer,
}; use std::io::Read;
/// Consume the reader and set [`Self::current_char`] and /// [`Self::peeked_char`]. Returns the current character. fn advance(&mutself) -> Result<Option<u8>, Error> { self.current_char = self.peeked_char; self.peeked_char = self.read_one()?;
// We need to read two chars to boot the process and fill the peeked // char. ifself.current_pos == 0 { self.current_char = self.peeked_char; self.peeked_char = self.read_one()?;
}
/// From Apple doc: /// /// > The quotation marks can be omitted if the string is composed strictly of alphanumeric /// > characters and contains no white space (numbers are handled as /// > strings in property lists). Though the property list format uses /// > ASCII for strings, note that Cocoa uses Unicode. Since string /// > encodings vary from region to region, this representation makes the /// > format fragile. You may see strings containing unreadable sequences of /// > ASCII characters; these are used to represent Unicode characters /// /// This function will naively try to convert the string to Integer. fn unquoted_string_literal(&mutself, first: u8) -> Result<Option<OwnedEvent>, Error> { letmut acc: Vec<u8> = Vec::new();
acc.push(first);
while { matchself.peeked_char {
Some(c) => {
c != b' ' && c != b')' && c != b'\r' && c != b'\t' && c != b';' && c != b','
}
None => false,
}
} { // consuming the string itself self.advance()?; matchself.current_char {
Some(c) => acc.push(c),
None => return Err(self.error(ErrorKind::UnclosedString)),
};
}
let string_literal =
String::from_utf8(acc).map_err(|_e| self.error(ErrorKind::InvalidUtf8AsciiStream))?;
// Not ideal but does the trick for now match Integer::from_str(&string_literal) {
Ok(i) => Ok(Some(Event::Integer(i))),
Err(_) => Ok(Some(Event::String(string_literal.into()))),
}
}
/// The process for decoding utf-16 escapes to utf-8 is: /// 1. Convert the 4 hex characters to utf-16 code units (u16s). /// '\u006d' becomes 0x6d. /// 2. Based on the first code unit, determine whether another code unit is /// required to form the complete code point. /// "\uD83D\uDCA9" becomes `[0xd73d, 0xdca9]` /// 3. Convert the 1 or 2 u16 code point to utf-8. /// `[0xd73d, 0xdca9]` becomes ''. /// /// The standard library has some useful functions behind unstable feature /// flags, we can simplify and optimize this a bit once they're stable. /// - str_from_utf16_endian /// - is_utf16_surrogate fn utf16_escape(&mutself) -> Result<String, Error> { letmut code_units: &mut [u16] = &mut [0u16; 2];
let Some(code_unit) = self.utf16_code_unit()? else { return Err(self.error(ErrorKind::InvalidUtf16String));
};
code_units[0] = code_unit;
// This is the utf-16 surrogate range, indicating another code unit is // necessary to form a complete code point. if !matches!(code_unit, 0xD800..=0xDFFF) {
code_units = &mut code_units[0..1];
} else { self.advance_quoted_string()?;
let utf8 = String::from_utf16(code_units)
.map_err(|_| self.error(ErrorKind::InvalidUtf16String))?;
Ok(utf8)
}
/// Expects the reader's next read to return the first hex character of the /// utf-16 hex string. fn utf16_code_unit(&mutself) -> Result<Option<u16>, Error> { let hex_chars = [ self.advance_quoted_string()?, self.advance_quoted_string()?, self.advance_quoted_string()?, self.advance_quoted_string()?,
];
let hex_str = std::str::from_utf8(&hex_chars)
.map_err(|_| self.error(ErrorKind::InvalidUtf16String))?;
let code_unit = u16::from_str_radix(hex_str, 16)
.map_err(|_| self.error(ErrorKind::InvalidUtf16String))?;
if c == quote { return Ok(Some(Event::String(acc.into())));
}
let replacement = if c == b'\\' { let c = self.advance_quoted_string()?;
match c {
b'\\' | b'"' => c as char,
b'a' => '\u{7}',
b'b' => '\u{8}',
b'f' => '\u{c}',
b'n' => '\n',
b'r' => '\r',
b't' => '\t',
b'U' => { let utf8 = self.utf16_escape()?;
acc.push_str(utf8.as_str()); continue;
}
b'v' => '\u{b}',
b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' => { let value = [
c, self.advance_quoted_string()?, self.advance_quoted_string()?,
];
let value = std::str::from_utf8(&value)
.map_err(|_| self.error(ErrorKind::InvalidOctalString))?;
let value = u16::from_str_radix(value, 8)
.map_err(|_| self.error(ErrorKind::InvalidOctalString))? as u32;
let value = char::from_u32(value)
.ok_or(self.error(ErrorKind::InvalidOctalString))?;
map_next_step_to_unicode(value)
}
_ => return Err(self.error(ErrorKind::InvalidUtf8AsciiStream)),
}
} else {
c as char
};
acc.push(replacement);
}
}
fn line_comment(&mutself) -> Result<(), Error> { // Consumes up to the end of the line. // There's no error in this a line comment can reach the EOF and there's // no forbidden chars in comments. while { matchself.peeked_char {
Some(c) => c != b'\n',
None => false,
}
} { let _ = self.advance()?;
}
/// Returns: /// - Some(string) if '/' was the first character of a string /// - None if '/' was the beginning of a comment. fn potential_comment(&mutself) -> Result<Option<OwnedEvent>, Error> { matchself.peeked_char {
Some(c) => match c {
b'/' => self.line_comment().map(|_| None),
b'*' => self.block_comment().map(|_| None),
_ => self.unquoted_string_literal(c),
}, // EOF
None => Err(self.error(ErrorKind::IncompleteComment)),
}
}
/// Consumes the reader until it finds a valid Event /// Possible events for Ascii plists: /// - `StartArray(Option<u64>)`, /// - `StartDictionary(Option<u64>)`, /// - `EndCollection`, /// - `Data(Vec<u8>)`, fn read_next(&mutself) -> Result<Option<OwnedEvent>, Error> { whilelet Some(c) = self.advance()? { match c { // Single char tokens
b'(' => return Ok(Some(Event::StartArray(None))),
b')' => return Ok(Some(Event::EndCollection)),
b'{' => return Ok(Some(Event::StartDictionary(None))),
b'}' => return Ok(Some(Event::EndCollection)),
b'\'' | b'"' => return self.quoted_string_literal(c),
b'/' => { matchself.potential_comment() {
Ok(Some(event)) => return Ok(Some(event)),
Ok(None) => { /* Comment has been consumed */ }
Err(e) => return Err(e),
}
}
b',' | b';' | b'=' => { /* consume these without emitting anything */ }
b' ' | b'\r' | b'\t' | b'\n' => { /* whitespace is not significant */ }
_ => returnself.unquoted_string_literal(c),
}
}
Ok(None)
}
}
impl<R: Read> Iterator for AsciiReader<R> { type Item = Result<OwnedEvent, Error>;
#[test] fn integers_and_strings() { let plist = "{ name = James, age = 42 }".to_owned(); let cursor = Cursor::new(plist.as_bytes()); let streaming_parser = AsciiReader::new(cursor); let events: Vec<Event> = streaming_parser.map(|e| e.unwrap()).collect();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.