use serde::{
de::{Deserialize, DeserializeOwned},
ser::Serialize,
};
use std::{collections::BTreeMap, fmt::Debug, fs::File, io::Cursor, path::Path};
use crate ::{
stream::{private::Sealed, Event, OwnedEvent, Writer},
Date, Deserializer, Dictionary, Error, Integer, Serializer, Uid, Value,
};
struct VecWriter {
events: Vec<OwnedEvent>,
}
impl VecWriter {
pub fn new() -> VecWriter {
VecWriter { events: Vec::new() }
}
pub fn into_inner(
self ) -> Vec<OwnedEvent> {
self .events
}
}
impl Writer
for VecWriter {
fn write_start_array(&
mut self , len: Option<u64>) -> Result<(), Error>
{
self .events.push(Event::StartArray(len));
Ok(())
}
fn write_start_dictionary(&mut self , len: Option<u64>) -> Result<(), Error> {
self .events.push(Event::StartDictionary(len));
Ok(())
}
fn write_end_collection(&mut self ) -> Result<(), Error> {
self .events.push(Event::EndCollection);
Ok(())
}
fn write_boolean(&mut self , value: bool) -> Result<(), Error> {
self .events.push(Event::Boolean(value));
Ok(())
}
fn write_data(&mut self , value: &[u8]) -> Result<(), Error> {
self .events.push(Event::Data(value.to_owned().into()));
Ok(())
}
fn write_date(&mut self , value: Date) -> Result<(), Error> {
self .events.push(Event::Date(value));
Ok(())
}
fn write_integer(&mut self , value: Integer) -> Result<(), Error> {
self .events.push(Event::Integer(value));
Ok(())
}
fn write_real(&mut self , value: f64) -> Result<(), Error> {
self .events.push(Event::Real(value));
Ok(())
}
fn write_string(&mut self , value: &str) -> Result<(), Error> {
self .events.push(Event::String(value.to_owned().into()));
Ok(())
}
fn write_uid(&mut self , value: Uid) -> Result<(), Error> {
self .events.push(Event::Uid(value));
Ok(())
}
}
impl Sealed for VecWriter {}
fn new_serializer() -> Serializer<VecWriter> {
Serializer::new(VecWriter::new())
}
fn new_deserializer(events: Vec<OwnedEvent>) -> Deserializer<Vec<Result<OwnedEvent, Error>>> {
let result_events = events.into_iter().map(Ok).collect();
Deserializer::new(result_events)
}
fn assert_roundtrip<T>(obj: T, comparison: Option<&[Event]>)
where
T: Debug + DeserializeOwned + PartialEq + Serialize,
{
let mut se = new_serializer();
obj.serialize(&mut se).unwrap();
let events = se.into_inner().into_inner();
if let Some(comparison) = comparison {
assert_eq!(&events[..], comparison);
}
let mut de = new_deserializer(events);
let new_obj = T::deserialize(&mut de).unwrap();
assert_eq!(new_obj, obj);
}
#[ derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
enum Animal {
Cow,
Dog(DogOuter),
Frog(Result<String, bool>, Option<Vec<f64>>),
Cat {
age: Integer,
name: String,
firmware: Option<Vec<u8>>,
},
}
#[ derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct DogOuter {
inner: Vec<DogInner>,
}
#[ derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct DogInner {
a: (),
b: usize,
c: Vec<String>,
d: Option<Uid>,
}
#[ test]
fn cow() {
let cow = Animal::Cow;
let comparison = &[Event::String("Cow" .into())];
assert_roundtrip(cow, Some(comparison));
}
#[ test]
fn dog() {
let dog = Animal::Dog(DogOuter {
inner: vec![DogInner {
a: (),
b: 12 ,
c: vec!["a" .to_string(), "b" .to_string()],
d: Some(Uid::new(42 )),
}],
});
let comparison = &[
Event::StartDictionary(Some(1 )),
Event::String("Dog" .into()),
Event::StartDictionary(None),
Event::String("inner" .into()),
Event::StartArray(Some(1 )),
Event::StartDictionary(None),
Event::String("a" .into()),
Event::String("" .into()),
Event::String("b" .into()),
Event::Integer(12 .into()),
Event::String("c" .into()),
Event::StartArray(Some(2 )),
Event::String("a" .into()),
Event::String("b" .into()),
Event::EndCollection,
Event::String("d" .into()),
Event::Uid(Uid::new(42 )),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(dog, Some(comparison));
}
#[ test]
fn frog() {
let frog = Animal::Frog(
Ok("hello" .to_owned()),
Some(vec![1 .0 , 2 .0 , 3 .14159 , 0 .000000001 , 1 .27 e31]),
);
let comparison = &[
Event::StartDictionary(Some(1 )),
Event::String("Frog" .into()),
Event::StartArray(Some(2 )),
Event::StartDictionary(Some(1 )),
Event::String("Ok" .into()),
Event::String("hello" .into()),
Event::EndCollection,
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::StartArray(Some(5 )),
Event::Real(1 .0 ),
Event::Real(2 .0 ),
Event::Real(3 .14159 ),
Event::Real(0 .000000001 ),
Event::Real(1 .27 e31),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(frog, Some(comparison));
}
#[ test]
fn cat_with_firmware() {
let cat = Animal::Cat {
age: 12 .into(),
name: "Paws" .to_owned(),
firmware: Some(vec![0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]),
};
let comparison = &[
Event::StartDictionary(Some(1 )),
Event::String("Cat" .into()),
Event::StartDictionary(None),
Event::String("age" .into()),
Event::Integer(12 .into()),
Event::String("name" .into()),
Event::String("Paws" .into()),
Event::String("firmware" .into()),
Event::StartArray(Some(9 )),
Event::Integer(0 .into()),
Event::Integer(1 .into()),
Event::Integer(2 .into()),
Event::Integer(3 .into()),
Event::Integer(4 .into()),
Event::Integer(5 .into()),
Event::Integer(6 .into()),
Event::Integer(7 .into()),
Event::Integer(8 .into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(cat, Some(comparison));
}
#[ test]
fn cat_without_firmware() {
let cat = Animal::Cat {
age: Integer::from(-12 ),
name: "Paws" .to_owned(),
firmware: None,
};
let comparison = &[
Event::StartDictionary(Some(1 )),
Event::String("Cat" .into()),
Event::StartDictionary(None),
Event::String("age" .into()),
Event::Integer(Integer::from(-12 )),
Event::String("name" .into()),
Event::String("Paws" .into()),
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(cat, Some(comparison));
}
#[ derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct NewtypeStruct(NewtypeInner);
#[ derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct NewtypeInner(u8, u8, u8);
#[ test]
fn newtype_struct() {
let newtype = NewtypeStruct(NewtypeInner(34 , 32 , 13 ));
let comparison = &[
Event::StartArray(Some(3 )),
Event::Integer(34 .into()),
Event::Integer(32 .into()),
Event::Integer(13 .into()),
Event::EndCollection,
];
assert_roundtrip(newtype, Some(comparison));
}
#[ derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct TypeWithOptions {
a: Option<String>,
b: Option<Option<u32>>,
c: Option<Box <TypeWithOptions>>,
}
#[ test]
fn type_with_options() {
let inner = TypeWithOptions {
a: None,
b: Some(Some(12 )),
c: None,
};
let obj = TypeWithOptions {
a: Some("hello" .to_owned()),
b: Some(None),
c: Some(Box ::new(inner)),
};
let comparison = &[
Event::StartDictionary(None),
Event::String("a" .into()),
Event::String("hello" .into()),
Event::String("b" .into()),
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
Event::String("c" .into()),
Event::StartDictionary(None),
Event::String("b" .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::Integer(12 .into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[ derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct TypeWithDate {
a: Option<i32>,
b: Option<Date>,
}
#[ test]
fn type_with_date() {
let date = Date::from_rfc3339("1920-01-01T00:10:00Z" ).unwrap();
let obj = TypeWithDate {
a: Some(28 ),
b: Some(date.clone()),
};
let comparison = &[
Event::StartDictionary(None),
Event::String("a" .into()),
Event::Integer(28 .into()),
Event::String("b" .into()),
Event::Date(date),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn option_some() {
let obj = Some(12 );
let comparison = &[Event::Integer(12 .into())];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn option_none() {
let obj: Option<u32> = None;
let comparison = &[];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn option_some_some() {
let obj = Some(Some(12 ));
let comparison = &[
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::Integer(12 .into()),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn option_some_none() {
let obj: Option<Option<u32>> = Some(None);
let comparison = &[
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn option_dictionary_values() {
let mut obj = BTreeMap::new();
obj.insert("a" .to_owned(), None);
obj.insert("b" .to_owned(), Some(None));
obj.insert("c" .to_owned(), Some(Some(144 )));
let comparison = &[
Event::StartDictionary(Some(3 )),
Event::String("a" .into()),
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
Event::String("b" .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
Event::EndCollection,
Event::String("c" .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::Integer(144 .into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn option_dictionary_keys() {
let mut obj = BTreeMap::new();
obj.insert(None, 1 );
obj.insert(Some(None), 2 );
obj.insert(Some(Some(144 )), 3 );
let comparison = &[
Event::StartDictionary(Some(3 )),
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
Event::Integer(1 .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
Event::EndCollection,
Event::Integer(2 .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::Integer(144 .into()),
Event::EndCollection,
Event::EndCollection,
Event::Integer(3 .into()),
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn option_array() {
let obj = vec![None, Some(None), Some(Some(144 ))];
let comparison = &[
Event::StartArray(Some(3 )),
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::StartDictionary(Some(1 )),
Event::String("None" .into()),
Event::String("" .into()),
Event::EndCollection,
Event::EndCollection,
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::StartDictionary(Some(1 )),
Event::String("Some" .into()),
Event::Integer(144 .into()),
Event::EndCollection,
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(obj, Some(comparison));
}
#[ test]
fn enum_variant_types() {
#[ derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
enum Foo {
Unit,
Newtype(u32),
Tuple(u32, String),
Struct { v: u32, s: String },
}
let expected = &[Event::String("Unit" .into())];
assert_roundtrip(Foo::Unit, Some(expected));
let expected = &[
Event::StartDictionary(Some(1 )),
Event::String("Newtype" .into()),
Event::Integer(42 .into()),
Event::EndCollection,
];
assert_roundtrip(Foo::Newtype(42 ), Some(expected));
let expected = &[
Event::StartDictionary(Some(1 )),
Event::String("Tuple" .into()),
Event::StartArray(Some(2 )),
Event::Integer(42 .into()),
Event::String("bar" .into()),
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(Foo::Tuple(42 , "bar" .into()), Some(expected));
let expected = &[
Event::StartDictionary(Some(1 )),
Event::String("Struct" .into()),
Event::StartDictionary(None),
Event::String("v" .into()),
Event::Integer(42 .into()),
Event::String("s" .into()),
Event::String("bar" .into()),
Event::EndCollection,
Event::EndCollection,
];
assert_roundtrip(
Foo::Struct {
v: 42 ,
s: "bar" .into(),
},
Some(expected),
);
}
#[ test]
fn deserialise_old_enum_unit_variant_encoding() {
#[ derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
enum Foo {
Bar,
Baz,
}
// `plist` before v1.1 serialised unit enum variants as if they were newtype variants
// containing an empty string.
let events = &[
Event::StartDictionary(Some(1 )),
Event::String("Baz" .into()),
Event::String("" .into()),
Event::EndCollection,
];
let mut de = new_deserializer(events.to_vec());
let obj = Foo::deserialize(&mut de).unwrap();
assert_eq!(obj, Foo::Baz);
}
#[ test]
fn deserialize_dictionary_xml() {
let reader = File::open(&Path::new("./tests/data/xml.plist" )).unwrap();
let dict: Dictionary = crate ::from_reader(reader).unwrap();
check_common_plist(&dict);
// xml.plist has this member, but binary.plist does not.
assert_eq!(
dict.get("HexademicalNumber" ) // sic
.unwrap()
.as_unsigned_integer()
.unwrap(),
0 xDEADBEEF
);
}
#[ test]
fn deserialize_dictionary_binary() {
let reader = File::open(&Path::new("./tests/data/binary.plist" )).unwrap();
let dict: Dictionary = crate ::from_reader(reader).unwrap();
check_common_plist(&dict);
}
// Shared checks used by the tests deserialize_dictionary_xml() and
// deserialize_dictionary_binary(), which load files with different formats
// but the same data elements.
fn check_common_plist(dict: &Dictionary) {
// Array elements
let lines = dict.get("Lines" ).unwrap().as_array().unwrap();
assert_eq!(lines.len(), 2 );
assert_eq!(
lines[0 ].as_string().unwrap(),
"It is a tale told by an idiot,"
);
assert_eq!(
lines[1 ].as_string().unwrap(),
"Full of sound and fury, signifying nothing."
);
// Dictionary
//
// There is no embedded dictionary in this plist. See
// deserialize_dictionary_binary_nskeyedarchiver() below for an example
// of that.
// Boolean elements
assert_eq!(dict.get("IsTrue" ).unwrap().as_boolean().unwrap(), true );
assert_eq!(dict.get("IsNotFalse" ).unwrap().as_boolean().unwrap(), false );
// Data
let data = dict.get("Data" ).unwrap().as_data().unwrap();
assert_eq!(data.len(), 15 );
assert_eq!(
data,
&[0 , 0 , 0 , 0 xbe, 0 , 0 , 0 , 0 x03, 0 , 0 , 0 , 0 x1e, 0 , 0 , 0 ]
);
// Date
assert_eq!(
dict.get("Birthdate" ).unwrap().as_date().unwrap(),
Date::from_rfc3339("1981-05-16T11:32:06Z" ).unwrap()
);
// Real
assert_eq!(dict.get("Height" ).unwrap().as_real().unwrap(), 1 .6 );
// Integer
assert_eq!(
dict.get("BiggestNumber" )
.unwrap()
.as_unsigned_integer()
.unwrap(),
18446744073709551615
);
assert_eq!(
dict.get("Death" ).unwrap().as_unsigned_integer().unwrap(),
1564
);
assert_eq!(
dict.get("SmallestNumber" )
.unwrap()
.as_signed_integer()
.unwrap(),
-9223372036854775808
);
// String
assert_eq!(
dict.get("Author" ).unwrap().as_string().unwrap(),
"William Shakespeare"
);
assert_eq!(dict.get("Blank" ).unwrap().as_string().unwrap(), "" );
// Uid
//
// No checks for Uid value type in this test. See
// deserialize_dictionary_binary_nskeyedarchiver() below for an example
// of that.
}
#[ test]
fn deserialize_dictionary_binary_nskeyedarchiver() {
let reader = File::open(&Path::new("./tests/data/binary_NSKeyedArchiver.plist" )).unwrap();
let dict: Dictionary = crate ::from_reader(reader).unwrap();
assert_eq!(
dict.get("$archiver" ).unwrap().as_string().unwrap(),
"NSKeyedArchiver"
);
let objects = dict.get("$objects" ).unwrap().as_array().unwrap();
assert_eq!(objects.len(), 5 );
assert_eq!(objects[0 ].as_string().unwrap(), "$null" );
let objects_1 = objects[1 ].as_dictionary().unwrap();
assert_eq!(
*objects_1.get("$class" ).unwrap().as_uid().unwrap(),
Uid::new(4 )
);
assert_eq!(
objects_1
.get("NSRangeCount" )
.unwrap()
.as_unsigned_integer()
.unwrap(),
42
);
assert_eq!(
*objects_1.get("NSRangeData" ).unwrap().as_uid().unwrap(),
Uid::new(2 )
);
let objects_2 = objects[2 ].as_dictionary().unwrap();
assert_eq!(
*objects_2.get("$class" ).unwrap().as_uid().unwrap(),
Uid::new(3 )
);
let objects_2_nsdata = objects_2.get("NS.data" ).unwrap().as_data().unwrap();
assert_eq!(objects_2_nsdata.len(), 103 );
assert_eq!(objects_2_nsdata[0 ], 0 x03);
assert_eq!(objects_2_nsdata[102 ], 0 x01);
let objects_3 = objects[3 ].as_dictionary().unwrap();
let objects_3_classes = objects_3.get("$classes" ).unwrap().as_array().unwrap();
assert_eq!(objects_3_classes.len(), 3 );
assert_eq!(objects_3_classes[0 ].as_string().unwrap(), "NSMutableData" );
assert_eq!(objects_3_classes[1 ].as_string().unwrap(), "NSData" );
assert_eq!(objects_3_classes[2 ].as_string().unwrap(), "NSObject" );
assert_eq!(
objects_3.get("$classname" ).unwrap().as_string().unwrap(),
"NSMutableData"
);
let objects_4 = objects[4 ].as_dictionary().unwrap();
let objects_4_classes = objects_4.get("$classes" ).unwrap().as_array().unwrap();
assert_eq!(objects_4_classes.len(), 3 );
assert_eq!(
objects_4_classes[0 ].as_string().unwrap(),
"NSMutableIndexSet"
);
assert_eq!(objects_4_classes[1 ].as_string().unwrap(), "NSIndexSet" );
assert_eq!(objects_4_classes[2 ].as_string().unwrap(), "NSObject" );
assert_eq!(
objects_4.get("$classname" ).unwrap().as_string().unwrap(),
"NSMutableIndexSet"
);
let top = dict.get("$top" ).unwrap().as_dictionary().unwrap();
assert_eq!(
*top.get("foundItems" ).unwrap().as_uid().unwrap(),
Uid::new(1 )
);
let version = dict.get("$version" ).unwrap().as_unsigned_integer().unwrap();
assert_eq!(version, 100000 );
}
#[ test]
fn dictionary_deserialize_dictionary_in_struct() {
// Example from <https://github.com/ebarnard/rust-plist/issues/54 >
#[ derive(Deserialize)]
struct LayerinfoData {
color: Option<String>,
lib: Option<Dictionary>,
}
let lib_dict: LayerinfoData = crate ::from_bytes(r#"
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd ">
<plist version="1.0" >
<dict>
<key>color</key>
<string>1 ,0 .75 ,0 ,0 .7 </string>
<key>lib</key>
<dict>
<key>com.typemytype.robofont.segmentType</key>
<string>curve</string>
</dict>
</dict>
</plist>
"#.as_bytes()).unwrap();
assert_eq!(lib_dict.color.unwrap(), "1,0.75,0,0.7" );
assert_eq!(
lib_dict
.lib
.unwrap()
.get("com.typemytype.robofont.segmentType" )
.unwrap()
.as_string()
.unwrap(),
"curve"
);
}
#[ test]
fn dictionary_serialize_xml() {
// Dictionary to be embedded in dict, below.
let mut inner_dict = Dictionary::new();
inner_dict.insert(
"FirstKey" .to_owned(),
Value::String("FirstValue" .to_owned()),
);
inner_dict.insert("SecondKey" .to_owned(), Value::Data(vec![10 , 20 , 30 , 40 ]));
inner_dict.insert("ThirdKey" .to_owned(), Value::Real(1 .234 ));
inner_dict.insert(
"FourthKey" .to_owned(),
Value::Date(Date::from_rfc3339("1981-05-16T11:32:06Z" ).unwrap()),
);
// Top-level dictionary.
let mut dict = Dictionary::new();
dict.insert(
"AnArray" .to_owned(),
Value::Array(vec![
Value::String("Hello, world!" .to_owned()),
Value::Integer(Integer::from(345 )),
]),
);
dict.insert("ADictionary" .to_owned(), Value::Dictionary(inner_dict));
dict.insert("AnInteger" .to_owned(), Value::Integer(Integer::from(123 )));
dict.insert("ATrueBoolean" .to_owned(), Value::Boolean(true ));
dict.insert("AFalseBoolean" .to_owned(), Value::Boolean(false ));
// Serialize dictionary as an XML plist.
let mut buf = Cursor::new(Vec::new());
crate ::to_writer_xml(&mut buf, &dict).unwrap();
let buf = buf.into_inner();
let xml = std::str::from_utf8(&buf).unwrap();
let comparison = "<?xml version=\" 1 .0 \" encoding=\" UTF-8 \"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\ ">
<plist version=\"1.0\" >
<dict>
\t<key>AnArray</key>
\t<array>
\t\t<string>Hello, world!</string>
\t\t<integer>345 </integer>
\t</array>
\t<key>ADictionary</key>
\t<dict>
\t\t<key>FirstKey</key>
\t\t<string>FirstValue</string>
\t\t<key>SecondKey</key>
\t\t<data>\n\t\tChQeKA==\n\t\t</data>
\t\t<key>ThirdKey</key>
\t\t<real>1 .234 </real>
\t\t<key>FourthKey</key>
\t\t<date>1981 -05 -16 T11:32 :06 Z</date>
\t</dict>
\t<key>AnInteger</key>
\t<integer>123 </integer>
\t<key>ATrueBoolean</key>
\t<true />
\t<key>AFalseBoolean</key>
\t<false />
</dict>
</plist>";
assert_eq!(xml, comparison);
}
#[ test]
fn serde_yaml_to_value() {
let value: Value = serde_yaml::from_str("true" ).unwrap();
assert_eq!(value, Value::Boolean(true ));
}
Messung V0.5 in Prozent C=97 H=100 G=98
¤ Dauer der Verarbeitung: 0.22 Sekunden
(vorverarbeitet am 2026-06-18)
¤
*© Formatika GbR, Deutschland