deser_plist/format.rs
1/// The encodings of property lists.
2///
3/// Deserialization detects the format automatically, the serializer
4/// writes the format of its [`SerializerConfig`](crate::SerializerConfig).
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
6pub enum Format {
7 /// The XML format (`<plist version="1.0">`).
8 ///
9 /// This is the default when serializing.
10 #[default]
11 Xml,
12 /// The binary format (`bplist00`).
13 Binary,
14 /// The OpenStep format, also known as ASCII or old-style property lists
15 /// (`{ key = value; }`).
16 ///
17 /// Xcode project files (`.pbxproj`) and `.strings` files use it. The
18 /// format has no types besides strings, dictionaries, arrays and data.
19 Ascii,
20}
21
22impl Format {
23 /// Detects the format of a property list.
24 ///
25 /// Binary property lists start with `bplist`, XML documents with a
26 /// markup declaration or an element. Everything else is treated as
27 /// OpenStep. Byte order marks are skipped.
28 ///
29 /// ```
30 /// use deser_plist::Format;
31 ///
32 /// assert_eq!(Format::detect(b"bplist00..."), Format::Binary);
33 /// assert_eq!(Format::detect(b"<?xml version=\"1.0\"?>"), Format::Xml);
34 /// assert_eq!(Format::detect(b"<plist><true/></plist>"), Format::Xml);
35 /// assert_eq!(Format::detect(b"{ a = b; }"), Format::Ascii);
36 /// assert_eq!(Format::detect(b"<0fbd7a>"), Format::Ascii);
37 /// ```
38 pub fn detect(input: &[u8]) -> Format {
39 if input.starts_with(b"bplist") {
40 return Format::Binary;
41 }
42 // for UTF-16 only ASCII characters matter, every other byte is
43 // skipped.
44 let (input, step) = if input.starts_with(b"\xfe\xff") {
45 (&input[3.min(input.len())..], 2)
46 } else if input.starts_with(b"\xff\xfe") {
47 (&input[2..], 2)
48 } else if let Some(rest) = input.strip_prefix(b"\xef\xbb\xbf") {
49 (rest, 1)
50 } else {
51 (input, 1)
52 };
53 let mut chars = input.iter().step_by(step).copied();
54 let Some(first) = chars.by_ref().find(|c| !c.is_ascii_whitespace()) else {
55 return Format::Ascii;
56 };
57 if first != b'<' {
58 return Format::Ascii;
59 }
60 // `<` starts data in OpenStep, which only holds hex digits and
61 // whitespace. All tags of XML plists have other letters.
62 for c in chars {
63 match c {
64 b'>' => return Format::Ascii,
65 c if c.is_ascii_hexdigit() || c.is_ascii_whitespace() => {}
66 _ => return Format::Xml,
67 }
68 }
69 Format::Xml
70 }
71
72 /// Returns `true` for the text formats (XML and OpenStep).
73 pub fn is_text(self) -> bool {
74 !matches!(self, Format::Binary)
75 }
76}
77
78#[test]
79fn test_detect() {
80 assert_eq!(Format::detect(b""), Format::Ascii);
81 assert_eq!(Format::detect(b" \n"), Format::Ascii);
82 assert_eq!(Format::detect(b"\xef\xbb\xbf<plist/>"), Format::Xml);
83 assert_eq!(Format::detect(b"\xff\xfe<\0?\0"), Format::Xml);
84 assert_eq!(Format::detect(b"\xfe\xff\0<\0?"), Format::Xml);
85 assert_eq!(Format::detect(b"\xff\xfe{\0}\0"), Format::Ascii);
86 assert_eq!(Format::detect(b"<dict/>"), Format::Xml);
87 assert_eq!(Format::detect(b"<data>"), Format::Xml);
88 assert_eq!(Format::detect(b"< 0f bd >"), Format::Ascii);
89 assert_eq!(Format::detect(b"// !$*UTF8*$!\n{}"), Format::Ascii);
90 assert_eq!(Format::detect(b"\"a\" = \"b\";"), Format::Ascii);
91}