1use indexmap::{IndexMap, IndexSet};
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::fmt::{self, Debug, Display};
10use std::str::FromStr;
11use thiserror::Error;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct QName {
16 pub local_name: String,
18 pub namespace_uri: Option<String>,
20 pub prefix: Option<String>,
22}
23
24impl QName {
25 pub fn new(local_name: impl Into<String>) -> Self {
27 Self {
28 local_name: local_name.into(),
29 namespace_uri: None,
30 prefix: None,
31 }
32 }
33
34 pub fn with_namespace(local_name: impl Into<String>, namespace_uri: impl Into<String>) -> Self {
36 Self {
37 local_name: local_name.into(),
38 namespace_uri: Some(namespace_uri.into()),
39 prefix: None,
40 }
41 }
42
43 pub fn with_prefix_and_namespace(
45 local_name: impl Into<String>,
46 prefix: impl Into<String>,
47 namespace_uri: impl Into<String>,
48 ) -> Self {
49 Self {
50 local_name: local_name.into(),
51 namespace_uri: Some(namespace_uri.into()),
52 prefix: Some(prefix.into()),
53 }
54 }
55
56 pub fn to_xml_name(&self) -> String {
58 match &self.prefix {
59 Some(prefix) if !prefix.is_empty() => format!("{}:{}", prefix, self.local_name),
60 _ => self.local_name.clone(),
61 }
62 }
63
64 pub fn is_namespace_declaration(&self) -> bool {
66 self.local_name == "xmlns" || (self.prefix.as_deref() == Some("xmlns"))
67 }
68
69 pub fn is_ddex_standard(&self) -> bool {
71 if self.is_namespace_declaration() {
73 return true;
74 }
75
76 match &self.namespace_uri {
77 Some(uri) => uri.contains("ddex.net") || uri.contains("w3.org/2001/XMLSchema"),
78 None => {
79 matches!(
81 self.local_name.as_str(),
82 "LanguageAndScriptCode"
83 | "ApplicableTerritoryCode"
84 | "IsDefault"
85 | "SequenceNumber"
86 | "Namespace"
87 )
88 }
89 }
90 }
91
92 pub fn canonical_sort_key(&self) -> String {
94 if self.is_namespace_declaration() {
96 if self.local_name == "xmlns" {
97 "0:xmlns".to_string()
98 } else {
99 format!("0:xmlns:{}", self.local_name)
100 }
101 } else {
102 format!("1:{}", self.to_xml_name())
103 }
104 }
105}
106
107impl Display for QName {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "{}", self.to_xml_name())
110 }
111}
112
113impl FromStr for QName {
114 type Err = AttributeError;
115
116 fn from_str(s: &str) -> Result<Self, Self::Err> {
117 if let Some((prefix, local_name)) = s.split_once(':') {
118 Ok(QName {
119 local_name: local_name.to_string(),
120 namespace_uri: None, prefix: Some(prefix.to_string()),
122 })
123 } else {
124 Ok(QName::new(s))
125 }
126 }
127}
128
129impl PartialOrd for QName {
130 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
131 Some(self.cmp(other))
132 }
133}
134
135impl Ord for QName {
136 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
137 self.canonical_sort_key().cmp(&other.canonical_sort_key())
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143pub enum AttributeValue {
144 String(String),
146 Boolean(bool),
148 Integer(i64),
150 Decimal(f64),
152 Date(chrono::NaiveDate),
154 DateTime(chrono::DateTime<chrono::Utc>),
156 Duration(chrono::Duration),
158 Uri(String),
160 Language(String),
162 Token(String),
164 Enum(String, Vec<String>), Raw(String),
168}
169
170impl AttributeValue {
171 pub fn string(value: impl Into<String>) -> Self {
173 Self::String(value.into())
174 }
175
176 pub fn boolean(value: bool) -> Self {
178 Self::Boolean(value)
179 }
180
181 pub fn integer(value: i64) -> Self {
183 Self::Integer(value)
184 }
185
186 pub fn decimal(value: f64) -> Self {
188 Self::Decimal(value)
189 }
190
191 pub fn uri(value: impl Into<String>) -> Self {
193 Self::Uri(value.into())
194 }
195
196 pub fn raw(value: impl Into<String>) -> Self {
198 Self::Raw(value.into())
199 }
200
201 pub fn to_xml_value(&self) -> String {
203 match self {
204 AttributeValue::String(s) => s.clone(),
205 AttributeValue::Boolean(b) => b.to_string(),
206 AttributeValue::Integer(i) => i.to_string(),
207 AttributeValue::Decimal(d) => d.to_string(),
208 AttributeValue::Date(d) => d.format("%Y-%m-%d").to_string(),
209 AttributeValue::DateTime(dt) => dt.to_rfc3339(),
210 AttributeValue::Duration(dur) => {
211 let secs = dur.num_seconds();
213 format!("PT{}S", secs)
214 }
215 AttributeValue::Uri(uri) => uri.clone(),
216 AttributeValue::Language(lang) => lang.clone(),
217 AttributeValue::Token(token) => token.clone(),
218 AttributeValue::Enum(value, _) => value.clone(),
219 AttributeValue::Raw(raw) => raw.clone(),
220 }
221 }
222
223 pub fn parse_with_type(value: &str, type_hint: AttributeType) -> Result<Self, AttributeError> {
225 match type_hint {
226 AttributeType::String => Ok(AttributeValue::String(value.to_string())),
227 AttributeType::Boolean => match value.to_lowercase().as_str() {
228 "true" | "1" => Ok(AttributeValue::Boolean(true)),
229 "false" | "0" => Ok(AttributeValue::Boolean(false)),
230 _ => Err(AttributeError::InvalidBoolean(value.to_string())),
231 },
232 AttributeType::Integer => value
233 .parse::<i64>()
234 .map(AttributeValue::Integer)
235 .map_err(|_| AttributeError::InvalidInteger(value.to_string())),
236 AttributeType::Decimal => value
237 .parse::<f64>()
238 .map(AttributeValue::Decimal)
239 .map_err(|_| AttributeError::InvalidDecimal(value.to_string())),
240 AttributeType::Date => chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d")
241 .map(AttributeValue::Date)
242 .map_err(|_| AttributeError::InvalidDate(value.to_string())),
243 AttributeType::DateTime => chrono::DateTime::parse_from_rfc3339(value)
244 .map(|dt| AttributeValue::DateTime(dt.with_timezone(&chrono::Utc)))
245 .map_err(|_| AttributeError::InvalidDateTime(value.to_string())),
246 AttributeType::Uri => Ok(AttributeValue::Uri(value.to_string())),
247 AttributeType::Language => Ok(AttributeValue::Language(value.to_string())),
248 AttributeType::Token => Ok(AttributeValue::Token(value.trim().to_string())),
249 AttributeType::Raw => Ok(AttributeValue::Raw(value.to_string())),
250 }
251 }
252
253 pub fn validate(&self) -> Result<(), AttributeError> {
255 match self {
256 AttributeValue::Enum(value, allowed_values) => {
257 if allowed_values.contains(value) {
258 Ok(())
259 } else {
260 Err(AttributeError::InvalidEnumValue {
261 value: value.clone(),
262 allowed: allowed_values.clone(),
263 })
264 }
265 }
266 AttributeValue::Uri(uri) => {
267 if uri.contains(' ') || uri.is_empty() {
269 Err(AttributeError::InvalidUri(uri.clone()))
270 } else {
271 Ok(())
272 }
273 }
274 AttributeValue::Language(lang) => {
275 if lang.len() < 2 || lang.len() > 8 {
277 Err(AttributeError::InvalidLanguage(lang.clone()))
278 } else {
279 Ok(())
280 }
281 }
282 _ => Ok(()),
283 }
284 }
285}
286
287impl Display for AttributeValue {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 write!(f, "{}", self.to_xml_value())
290 }
291}
292
293impl From<String> for AttributeValue {
294 fn from(value: String) -> Self {
295 AttributeValue::String(value)
296 }
297}
298
299impl From<&str> for AttributeValue {
300 fn from(value: &str) -> Self {
301 AttributeValue::String(value.to_string())
302 }
303}
304
305impl From<bool> for AttributeValue {
306 fn from(value: bool) -> Self {
307 AttributeValue::Boolean(value)
308 }
309}
310
311impl From<i64> for AttributeValue {
312 fn from(value: i64) -> Self {
313 AttributeValue::Integer(value)
314 }
315}
316
317impl From<f64> for AttributeValue {
318 fn from(value: f64) -> Self {
319 AttributeValue::Decimal(value)
320 }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
325pub enum AttributeType {
326 String,
327 Boolean,
328 Integer,
329 Decimal,
330 Date,
331 DateTime,
332 Uri,
333 Language,
334 Token,
335 Raw,
336}
337
338impl std::fmt::Display for AttributeType {
339 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340 match self {
341 AttributeType::String => write!(f, "string"),
342 AttributeType::Boolean => write!(f, "boolean"),
343 AttributeType::Integer => write!(f, "integer"),
344 AttributeType::Decimal => write!(f, "decimal"),
345 AttributeType::Date => write!(f, "date"),
346 AttributeType::DateTime => write!(f, "dateTime"),
347 AttributeType::Uri => write!(f, "anyURI"),
348 AttributeType::Language => write!(f, "language"),
349 AttributeType::Token => write!(f, "token"),
350 AttributeType::Raw => write!(f, "raw"),
351 }
352 }
353}
354
355#[derive(Debug, Clone, PartialEq)]
357pub struct AttributeMap {
358 attributes: IndexMap<QName, AttributeValue>,
360}
361
362impl AttributeMap {
363 pub fn new() -> Self {
365 Self {
366 attributes: IndexMap::new(),
367 }
368 }
369
370 pub fn insert(&mut self, name: QName, value: AttributeValue) -> Option<AttributeValue> {
372 self.attributes.insert(name, value)
373 }
374
375 pub fn insert_str(
377 &mut self,
378 name: &str,
379 value: impl Into<AttributeValue>,
380 ) -> Option<AttributeValue> {
381 let qname = QName::from_str(name).unwrap_or_else(|_| QName::new(name));
382 self.insert(qname, value.into())
383 }
384
385 pub fn get(&self, name: &QName) -> Option<&AttributeValue> {
387 self.attributes.get(name)
388 }
389
390 pub fn get_str(&self, name: &str) -> Option<&AttributeValue> {
392 let qname = QName::from_str(name).unwrap_or_else(|_| QName::new(name));
393 self.get(&qname)
394 }
395
396 pub fn remove(&mut self, name: &QName) -> Option<AttributeValue> {
398 self.attributes.shift_remove(name)
399 }
400
401 pub fn contains_key(&self, name: &QName) -> bool {
403 self.attributes.contains_key(name)
404 }
405
406 pub fn iter_canonical(&self) -> impl Iterator<Item = (&QName, &AttributeValue)> {
408 let mut sorted: Vec<_> = self.attributes.iter().collect();
409 sorted.sort_by(|(a, _), (b, _)| a.cmp(b));
410 sorted.into_iter()
411 }
412
413 pub fn iter(&self) -> impl Iterator<Item = (&QName, &AttributeValue)> {
415 self.attributes.iter()
416 }
417
418 pub fn iter_mut(&mut self) -> impl Iterator<Item = (&QName, &mut AttributeValue)> {
420 self.attributes.iter_mut()
421 }
422
423 pub fn len(&self) -> usize {
425 self.attributes.len()
426 }
427
428 pub fn is_empty(&self) -> bool {
430 self.attributes.is_empty()
431 }
432
433 pub fn clear(&mut self) {
435 self.attributes.clear();
436 }
437
438 pub fn standard_attributes(&self) -> IndexMap<QName, AttributeValue> {
440 self.attributes
441 .iter()
442 .filter(|(qname, _)| qname.is_ddex_standard())
443 .map(|(qname, value)| (qname.clone(), value.clone()))
444 .collect()
445 }
446
447 pub fn extension_attributes(&self) -> IndexMap<QName, AttributeValue> {
449 self.attributes
450 .iter()
451 .filter(|(qname, _)| !qname.is_ddex_standard())
452 .map(|(qname, value)| (qname.clone(), value.clone()))
453 .collect()
454 }
455
456 pub fn namespace_declarations(&self) -> IndexMap<QName, AttributeValue> {
458 self.attributes
459 .iter()
460 .filter(|(qname, _)| qname.is_namespace_declaration())
461 .map(|(qname, value)| (qname.clone(), value.clone()))
462 .collect()
463 }
464
465 pub fn merge(&mut self, other: &AttributeMap, strategy: AttributeMergeStrategy) {
467 for (qname, value) in &other.attributes {
468 if let Some(_existing) = self.attributes.get(qname) {
469 match strategy {
470 AttributeMergeStrategy::PreferThis => continue,
471 AttributeMergeStrategy::PreferOther => {
472 self.attributes.insert(qname.clone(), value.clone());
473 }
474 AttributeMergeStrategy::Error => {
475 eprintln!("Attribute conflict: {}", qname);
477 }
478 }
479 } else {
480 self.attributes.insert(qname.clone(), value.clone());
481 }
482 }
483 }
484
485 pub fn validate(&self) -> Vec<AttributeError> {
487 let mut errors = Vec::new();
488 for (_qname, value) in &self.attributes {
489 if let Err(error) = value.validate() {
490 errors.push(error);
491 }
492 }
493 errors
494 }
495
496 pub fn to_string_map(&self) -> IndexMap<String, String> {
498 self.attributes
499 .iter()
500 .map(|(qname, value)| (qname.to_xml_name(), value.to_xml_value()))
501 .collect()
502 }
503
504 pub fn from_string_map(map: IndexMap<String, String>) -> Self {
506 let mut attributes = IndexMap::new();
507 for (name, value) in map {
508 let qname = QName::from_str(&name).unwrap_or_else(|_| QName::new(name));
509 attributes.insert(qname, AttributeValue::String(value));
510 }
511 Self { attributes }
512 }
513
514 pub fn keys(&self) -> indexmap::map::Keys<'_, QName, AttributeValue> {
516 self.attributes.keys()
517 }
518
519 pub fn to_canonical_ordered(&self) -> IndexMap<QName, AttributeValue> {
521 let mut namespace_attrs = IndexMap::new();
522 let mut regular_attrs = IndexMap::new();
523
524 for (qname, value) in &self.attributes {
526 if qname.is_namespace_declaration() {
527 namespace_attrs.insert(qname.clone(), value.clone());
528 } else {
529 regular_attrs.insert(qname.clone(), value.clone());
530 }
531 }
532
533 namespace_attrs.sort_by(|a, _, b, _| a.canonical_sort_key().cmp(&b.canonical_sort_key()));
535 regular_attrs.sort_by(|a, _, b, _| a.canonical_sort_key().cmp(&b.canonical_sort_key()));
536
537 let mut result = IndexMap::new();
539 result.extend(namespace_attrs);
540 result.extend(regular_attrs);
541
542 result
543 }
544}
545
546impl Default for AttributeMap {
547 fn default() -> Self {
548 Self::new()
549 }
550}
551
552impl Serialize for AttributeMap {
553 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
554 where
555 S: Serializer,
556 {
557 let string_map = self.to_string_map();
559 string_map.serialize(serializer)
560 }
561}
562
563impl<'de> Deserialize<'de> for AttributeMap {
564 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
565 where
566 D: Deserializer<'de>,
567 {
568 let string_map = IndexMap::<String, String>::deserialize(deserializer)?;
569 Ok(Self::from_string_map(string_map))
570 }
571}
572
573impl<'a> IntoIterator for &'a AttributeMap {
574 type Item = (&'a QName, &'a AttributeValue);
575 type IntoIter = indexmap::map::Iter<'a, QName, AttributeValue>;
576
577 fn into_iter(self) -> Self::IntoIter {
578 self.attributes.iter()
579 }
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584pub enum AttributeMergeStrategy {
585 PreferThis,
587 PreferOther,
589 Error,
591}
592
593#[derive(Debug, Clone)]
595pub struct AttributeInheritance {
596 inheritable_attributes: IndexSet<QName>,
598 non_inheritable_attributes: IndexSet<QName>,
600}
601
602impl AttributeInheritance {
603 pub fn new() -> Self {
605 let mut inheritable = IndexSet::new();
606 let mut non_inheritable = IndexSet::new();
607
608 inheritable.insert(QName::new("LanguageAndScriptCode"));
610 inheritable.insert(QName::new("ApplicableTerritoryCode"));
611 inheritable.insert(QName::with_namespace(
612 "lang",
613 "http://www.w3.org/XML/1998/namespace",
614 ));
615
616 non_inheritable.insert(QName::new("SequenceNumber"));
618 non_inheritable.insert(QName::with_prefix_and_namespace(
619 "xsi",
620 "type",
621 "http://www.w3.org/2001/XMLSchema-instance",
622 ));
623
624 Self {
625 inheritable_attributes: inheritable,
626 non_inheritable_attributes: non_inheritable,
627 }
628 }
629
630 pub fn should_inherit(&self, qname: &QName) -> bool {
632 if self.non_inheritable_attributes.contains(qname) {
633 false
634 } else if self.inheritable_attributes.contains(qname) {
635 true
636 } else {
637 false
639 }
640 }
641
642 pub fn apply_inheritance(&self, parent: &AttributeMap, child: &mut AttributeMap) {
644 for (qname, value) in parent.iter() {
645 if self.should_inherit(qname) && !child.contains_key(qname) {
646 child.insert(qname.clone(), value.clone());
647 }
648 }
649 }
650}
651
652impl Default for AttributeInheritance {
653 fn default() -> Self {
654 Self::new()
655 }
656}
657
658#[derive(Debug, Clone, Error, PartialEq)]
660pub enum AttributeError {
661 #[error("Invalid boolean value: {0}")]
662 InvalidBoolean(String),
663
664 #[error("Invalid integer value: {0}")]
665 InvalidInteger(String),
666
667 #[error("Invalid decimal value: {0}")]
668 InvalidDecimal(String),
669
670 #[error("Invalid date value: {0}")]
671 InvalidDate(String),
672
673 #[error("Invalid datetime value: {0}")]
674 InvalidDateTime(String),
675
676 #[error("Invalid URI value: {0}")]
677 InvalidUri(String),
678
679 #[error("Invalid language code: {0}")]
680 InvalidLanguage(String),
681
682 #[error("Invalid enum value '{value}', allowed values: {}", allowed.join(", "))]
683 InvalidEnumValue { value: String, allowed: Vec<String> },
684
685 #[error("Missing required attribute: {0}")]
686 MissingRequired(String),
687
688 #[error("Conflicting attribute values for: {0}")]
689 ConflictingValues(String),
690
691 #[error("Invalid QName format: {0}")]
692 InvalidQName(String),
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 #[test]
700 fn test_qname_creation() {
701 let qname = QName::new("title");
702 assert_eq!(qname.local_name, "title");
703 assert_eq!(qname.namespace_uri, None);
704 assert_eq!(qname.prefix, None);
705
706 let qname_ns = QName::with_namespace("title", "http://ddex.net/xml/ern/43");
707 assert_eq!(
708 qname_ns.namespace_uri,
709 Some("http://ddex.net/xml/ern/43".to_string())
710 );
711
712 let qname_prefix =
713 QName::with_prefix_and_namespace("title", "ern", "http://ddex.net/xml/ern/43");
714 assert_eq!(qname_prefix.prefix, Some("ern".to_string()));
715 assert_eq!(qname_prefix.to_xml_name(), "ern:title");
716 }
717
718 #[test]
719 fn test_qname_parsing() {
720 let qname: QName = "ern:title".parse().unwrap();
721 assert_eq!(qname.local_name, "title");
722 assert_eq!(qname.prefix, Some("ern".to_string()));
723
724 let simple_qname: QName = "title".parse().unwrap();
725 assert_eq!(simple_qname.local_name, "title");
726 assert_eq!(simple_qname.prefix, None);
727 }
728
729 #[test]
730 fn test_qname_canonical_ordering() {
731 let xmlns = QName::new("xmlns");
732 let xmlns_ern = QName::from_str("xmlns:ern").unwrap();
733 let regular = QName::new("title");
734 let prefixed = QName::from_str("ern:title").unwrap();
735
736 let mut qnames = [®ular, &prefixed, &xmlns_ern, &xmlns].to_vec();
737 qnames.sort();
738
739 assert_eq!(qnames[0], &xmlns);
741 assert_eq!(qnames[1], &xmlns_ern);
742 }
743
744 #[test]
745 fn test_attribute_value_types() {
746 let string_val = AttributeValue::string("test");
747 assert_eq!(string_val.to_xml_value(), "test");
748
749 let bool_val = AttributeValue::boolean(true);
750 assert_eq!(bool_val.to_xml_value(), "true");
751
752 let int_val = AttributeValue::integer(42);
753 assert_eq!(int_val.to_xml_value(), "42");
754
755 let parsed = AttributeValue::parse_with_type("true", AttributeType::Boolean).unwrap();
757 assert_eq!(parsed, AttributeValue::Boolean(true));
758
759 let parsed_int = AttributeValue::parse_with_type("123", AttributeType::Integer).unwrap();
760 assert_eq!(parsed_int, AttributeValue::Integer(123));
761 }
762
763 #[test]
764 fn test_attribute_map() {
765 let mut map = AttributeMap::new();
766
767 map.insert_str("title", "Test Title");
768 map.insert_str("ern:version", "4.3");
769 map.insert_str("xmlns:ern", "http://ddex.net/xml/ern/43");
770
771 assert_eq!(map.len(), 3);
772 assert_eq!(map.get_str("title").unwrap().to_xml_value(), "Test Title");
773
774 let canonical: Vec<_> = map.iter_canonical().collect();
776 assert_eq!(canonical.len(), 3);
777
778 let first_attr = &canonical[0];
780 assert!(first_attr.0.is_namespace_declaration());
781 }
782
783 #[test]
784 fn test_attribute_inheritance() {
785 let inheritance = AttributeInheritance::new();
786
787 let lang_attr = QName::new("LanguageAndScriptCode");
788 let seq_attr = QName::new("SequenceNumber");
789
790 assert!(inheritance.should_inherit(&lang_attr));
791 assert!(!inheritance.should_inherit(&seq_attr));
792 }
793
794 #[test]
795 fn test_attribute_validation() {
796 let mut enum_val = AttributeValue::Enum(
797 "invalid".to_string(),
798 vec!["valid1".to_string(), "valid2".to_string()],
799 );
800 assert!(enum_val.validate().is_err());
801
802 enum_val = AttributeValue::Enum(
803 "valid1".to_string(),
804 vec!["valid1".to_string(), "valid2".to_string()],
805 );
806 assert!(enum_val.validate().is_ok());
807 }
808
809 #[test]
810 fn test_ddex_standard_detection() {
811 let ddex_attr = QName::with_namespace("title", "http://ddex.net/xml/ern/43");
812 assert!(ddex_attr.is_ddex_standard());
813
814 let xmlns_attr = QName::new("xmlns");
815 assert!(xmlns_attr.is_ddex_standard());
816
817 let custom_attr = QName::with_namespace("custom", "http://example.com/custom");
818 assert!(!custom_attr.is_ddex_standard());
819
820 let lang_attr = QName::new("LanguageAndScriptCode");
821 assert!(lang_attr.is_ddex_standard());
822 }
823
824 #[test]
825 fn test_attribute_map_serialization() {
826 let mut map = AttributeMap::new();
827 map.insert_str("title", "Test Title");
828 map.insert_str("version", "4.3");
829
830 let string_map = map.to_string_map();
832 assert_eq!(string_map.len(), 2);
833 assert_eq!(string_map.get("title"), Some(&"Test Title".to_string()));
834
835 let restored = AttributeMap::from_string_map(string_map);
837 assert_eq!(restored.len(), 2);
838 assert_eq!(
839 restored.get_str("title").unwrap().to_xml_value(),
840 "Test Title"
841 );
842 }
843}