ddex_core/models/
attributes.rs

1//! # XML Attribute Preservation System
2//!
3//! This module provides comprehensive support for preserving all XML attributes,
4//! including unknown/proprietary ones, with proper namespace handling and
5//! deterministic ordering for canonical XML generation.
6
7use indexmap::{IndexMap, IndexSet};
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::fmt::{self, Debug, Display};
10use std::str::FromStr;
11use thiserror::Error;
12
13/// Qualified Name (QName) representing a namespace-qualified XML name
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct QName {
16    /// Local name part
17    pub local_name: String,
18    /// Namespace URI (empty string for default namespace, None for no namespace)
19    pub namespace_uri: Option<String>,
20    /// Namespace prefix (empty string for default namespace, None for no prefix)
21    pub prefix: Option<String>,
22}
23
24impl QName {
25    /// Create a new QName with no namespace
26    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    /// Create a new QName with namespace URI
35    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    /// Create a new QName with prefix and namespace URI
44    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    /// Get the qualified name as it would appear in XML
57    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    /// Check if this is a namespace declaration attribute
65    pub fn is_namespace_declaration(&self) -> bool {
66        self.local_name == "xmlns" || (self.prefix.as_deref() == Some("xmlns"))
67    }
68
69    /// Check if this is a standard DDEX attribute
70    pub fn is_ddex_standard(&self) -> bool {
71        // Always check namespace declarations first
72        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                // Common DDEX attributes without namespace
80                matches!(
81                    self.local_name.as_str(),
82                    "LanguageAndScriptCode"
83                        | "ApplicableTerritoryCode"
84                        | "IsDefault"
85                        | "SequenceNumber"
86                        | "Namespace"
87                )
88            }
89        }
90    }
91
92    /// Get the sorting key for canonical ordering
93    pub fn canonical_sort_key(&self) -> String {
94        // Namespace declarations come first, then alphabetical by QName
95        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, // Will be resolved later with namespace context
121                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/// Typed attribute value supporting various XML Schema types
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143pub enum AttributeValue {
144    /// String value (most common)
145    String(String),
146    /// Boolean value (true/false)
147    Boolean(bool),
148    /// Integer value
149    Integer(i64),
150    /// Decimal value
151    Decimal(f64),
152    /// Date value (ISO 8601)
153    Date(chrono::NaiveDate),
154    /// DateTime value (ISO 8601)
155    DateTime(chrono::DateTime<chrono::Utc>),
156    /// Duration value (ISO 8601)
157    Duration(chrono::Duration),
158    /// URI/URL value
159    Uri(String),
160    /// Language code (RFC 5646)
161    Language(String),
162    /// Token value (normalized string)
163    Token(String),
164    /// Enumerated value (for known enums)
165    Enum(String, Vec<String>), // (value, allowed_values)
166    /// Raw string value for unknown types
167    Raw(String),
168}
169
170impl AttributeValue {
171    /// Create a new string attribute value
172    pub fn string(value: impl Into<String>) -> Self {
173        Self::String(value.into())
174    }
175
176    /// Create a new boolean attribute value
177    pub fn boolean(value: bool) -> Self {
178        Self::Boolean(value)
179    }
180
181    /// Create a new integer attribute value
182    pub fn integer(value: i64) -> Self {
183        Self::Integer(value)
184    }
185
186    /// Create a new decimal attribute value
187    pub fn decimal(value: f64) -> Self {
188        Self::Decimal(value)
189    }
190
191    /// Create a new URI attribute value
192    pub fn uri(value: impl Into<String>) -> Self {
193        Self::Uri(value.into())
194    }
195
196    /// Create a new raw attribute value
197    pub fn raw(value: impl Into<String>) -> Self {
198        Self::Raw(value.into())
199    }
200
201    /// Get the attribute value as a string for XML serialization
202    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                // Convert to ISO 8601 duration format
212                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    /// Parse an attribute value from string with type hint
224    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    /// Validate the attribute value
254    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                // Basic URI validation
268                if uri.contains(' ') || uri.is_empty() {
269                    Err(AttributeError::InvalidUri(uri.clone()))
270                } else {
271                    Ok(())
272                }
273            }
274            AttributeValue::Language(lang) => {
275                // Basic language code validation (simplified)
276                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/// Attribute type hints for parsing
324#[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/// Comprehensive attribute map with deterministic ordering
356#[derive(Debug, Clone, PartialEq)]
357pub struct AttributeMap {
358    /// Attributes stored with deterministic ordering
359    attributes: IndexMap<QName, AttributeValue>,
360}
361
362impl AttributeMap {
363    /// Create a new empty attribute map
364    pub fn new() -> Self {
365        Self {
366            attributes: IndexMap::new(),
367        }
368    }
369
370    /// Insert an attribute
371    pub fn insert(&mut self, name: QName, value: AttributeValue) -> Option<AttributeValue> {
372        self.attributes.insert(name, value)
373    }
374
375    /// Insert an attribute by string name
376    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    /// Get an attribute value
386    pub fn get(&self, name: &QName) -> Option<&AttributeValue> {
387        self.attributes.get(name)
388    }
389
390    /// Get an attribute value by string name
391    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    /// Remove an attribute
397    pub fn remove(&mut self, name: &QName) -> Option<AttributeValue> {
398        self.attributes.shift_remove(name)
399    }
400
401    /// Check if an attribute exists
402    pub fn contains_key(&self, name: &QName) -> bool {
403        self.attributes.contains_key(name)
404    }
405
406    /// Get all attributes in canonical order
407    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    /// Get all attributes (in insertion order)
414    pub fn iter(&self) -> impl Iterator<Item = (&QName, &AttributeValue)> {
415        self.attributes.iter()
416    }
417
418    /// Get mutable iterator over all attributes
419    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&QName, &mut AttributeValue)> {
420        self.attributes.iter_mut()
421    }
422
423    /// Get the number of attributes
424    pub fn len(&self) -> usize {
425        self.attributes.len()
426    }
427
428    /// Check if the map is empty
429    pub fn is_empty(&self) -> bool {
430        self.attributes.is_empty()
431    }
432
433    /// Clear all attributes
434    pub fn clear(&mut self) {
435        self.attributes.clear();
436    }
437
438    /// Get all DDEX standard attributes
439    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    /// Get all extension/custom attributes
448    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    /// Get all namespace declaration attributes
457    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    /// Merge attributes from another map, with conflict resolution
466    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                        // In a real implementation, this would return a Result
476                        eprintln!("Attribute conflict: {}", qname);
477                    }
478                }
479            } else {
480                self.attributes.insert(qname.clone(), value.clone());
481            }
482        }
483    }
484
485    /// Validate all attributes
486    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    /// Convert to a simple string map for backwards compatibility
497    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    /// Create from a simple string map
505    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    /// Get iterator over attribute keys
515    pub fn keys(&self) -> indexmap::map::Keys<'_, QName, AttributeValue> {
516        self.attributes.keys()
517    }
518
519    /// Get all attributes in canonical order (namespace declarations first, then alphabetical)
520    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        // Separate namespace declarations from regular attributes
525        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        // Sort both collections by canonical sort key
534        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        // Combine namespace declarations first, then regular attributes
538        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        // Serialize as a map of string -> string for JSON compatibility
558        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/// Strategy for merging attribute maps
583#[derive(Debug, Clone, Copy, PartialEq, Eq)]
584pub enum AttributeMergeStrategy {
585    /// Keep the existing attribute (prefer this map)
586    PreferThis,
587    /// Use the new attribute (prefer other map)
588    PreferOther,
589    /// Raise an error on conflicts
590    Error,
591}
592
593/// Attribute inheritance model for nested elements
594#[derive(Debug, Clone)]
595pub struct AttributeInheritance {
596    /// Attributes that should be inherited from parent elements
597    inheritable_attributes: IndexSet<QName>,
598    /// Attributes that should never be inherited
599    non_inheritable_attributes: IndexSet<QName>,
600}
601
602impl AttributeInheritance {
603    /// Create new attribute inheritance rules
604    pub fn new() -> Self {
605        let mut inheritable = IndexSet::new();
606        let mut non_inheritable = IndexSet::new();
607
608        // Common inheritable attributes
609        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 attributes (element-specific)
617        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    /// Check if an attribute should be inherited
631    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            // Default: don't inherit unknown attributes
638            false
639        }
640    }
641
642    /// Apply inheritance from parent to child attributes
643    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/// Attribute-related errors
659#[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 = [&regular, &prefixed, &xmlns_ern, &xmlns].to_vec();
737        qnames.sort();
738
739        // Namespace declarations should come first
740        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        // Test parsing with type hints
756        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        // Test canonical ordering
775        let canonical: Vec<_> = map.iter_canonical().collect();
776        assert_eq!(canonical.len(), 3);
777
778        // xmlns attributes should come first
779        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        // Test conversion to string map
831        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        // Test round-trip through string map
836        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}