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::{Serialize, Deserialize, Serializer, Deserializer};
9use std::fmt::{self, Display, Debug};
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" || 
67        (self.prefix.as_deref() == Some("xmlns"))
68    }
69
70    /// Check if this is a standard DDEX attribute
71    pub fn is_ddex_standard(&self) -> bool {
72        match &self.namespace_uri {
73            Some(uri) => {
74                uri.contains("ddex.net") || 
75                uri.contains("w3.org/2001/XMLSchema") ||
76                self.is_namespace_declaration()
77            },
78            None => {
79                // Common DDEX attributes without namespace
80                matches!(self.local_name.as_str(), 
81                    "LanguageAndScriptCode" | "ApplicableTerritoryCode" | 
82                    "IsDefault" | "SequenceNumber" | "Namespace"
83                )
84            }
85        }
86    }
87
88    /// Get the sorting key for canonical ordering
89    pub fn canonical_sort_key(&self) -> String {
90        // Namespace declarations come first, then alphabetical by QName
91        if self.is_namespace_declaration() {
92            if self.local_name == "xmlns" {
93                "0:xmlns".to_string()
94            } else {
95                format!("0:xmlns:{}", self.local_name)
96            }
97        } else {
98            format!("1:{}", self.to_xml_name())
99        }
100    }
101}
102
103impl Display for QName {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        write!(f, "{}", self.to_xml_name())
106    }
107}
108
109impl FromStr for QName {
110    type Err = AttributeError;
111
112    fn from_str(s: &str) -> Result<Self, Self::Err> {
113        if let Some((prefix, local_name)) = s.split_once(':') {
114            Ok(QName {
115                local_name: local_name.to_string(),
116                namespace_uri: None, // Will be resolved later with namespace context
117                prefix: Some(prefix.to_string()),
118            })
119        } else {
120            Ok(QName::new(s))
121        }
122    }
123}
124
125impl PartialOrd for QName {
126    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
127        Some(self.cmp(other))
128    }
129}
130
131impl Ord for QName {
132    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
133        self.canonical_sort_key().cmp(&other.canonical_sort_key())
134    }
135}
136
137/// Typed attribute value supporting various XML Schema types
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub enum AttributeValue {
140    /// String value (most common)
141    String(String),
142    /// Boolean value (true/false)
143    Boolean(bool),
144    /// Integer value
145    Integer(i64),
146    /// Decimal value
147    Decimal(f64),
148    /// Date value (ISO 8601)
149    Date(chrono::NaiveDate),
150    /// DateTime value (ISO 8601)
151    DateTime(chrono::DateTime<chrono::Utc>),
152    /// Duration value (ISO 8601)
153    Duration(chrono::Duration),
154    /// URI/URL value
155    Uri(String),
156    /// Language code (RFC 5646)
157    Language(String),
158    /// Token value (normalized string)
159    Token(String),
160    /// Enumerated value (for known enums)
161    Enum(String, Vec<String>), // (value, allowed_values)
162    /// Raw string value for unknown types
163    Raw(String),
164}
165
166impl AttributeValue {
167    /// Create a new string attribute value
168    pub fn string(value: impl Into<String>) -> Self {
169        Self::String(value.into())
170    }
171
172    /// Create a new boolean attribute value
173    pub fn boolean(value: bool) -> Self {
174        Self::Boolean(value)
175    }
176
177    /// Create a new integer attribute value
178    pub fn integer(value: i64) -> Self {
179        Self::Integer(value)
180    }
181
182    /// Create a new decimal attribute value
183    pub fn decimal(value: f64) -> Self {
184        Self::Decimal(value)
185    }
186
187    /// Create a new URI attribute value
188    pub fn uri(value: impl Into<String>) -> Self {
189        Self::Uri(value.into())
190    }
191
192    /// Create a new raw attribute value
193    pub fn raw(value: impl Into<String>) -> Self {
194        Self::Raw(value.into())
195    }
196
197    /// Get the attribute value as a string for XML serialization
198    pub fn to_xml_value(&self) -> String {
199        match self {
200            AttributeValue::String(s) => s.clone(),
201            AttributeValue::Boolean(b) => b.to_string(),
202            AttributeValue::Integer(i) => i.to_string(),
203            AttributeValue::Decimal(d) => d.to_string(),
204            AttributeValue::Date(d) => d.format("%Y-%m-%d").to_string(),
205            AttributeValue::DateTime(dt) => dt.to_rfc3339(),
206            AttributeValue::Duration(dur) => {
207                // Convert to ISO 8601 duration format
208                let secs = dur.num_seconds();
209                format!("PT{}S", secs)
210            },
211            AttributeValue::Uri(uri) => uri.clone(),
212            AttributeValue::Language(lang) => lang.clone(),
213            AttributeValue::Token(token) => token.clone(),
214            AttributeValue::Enum(value, _) => value.clone(),
215            AttributeValue::Raw(raw) => raw.clone(),
216        }
217    }
218
219    /// Parse an attribute value from string with type hint
220    pub fn parse_with_type(value: &str, type_hint: AttributeType) -> Result<Self, AttributeError> {
221        match type_hint {
222            AttributeType::String => Ok(AttributeValue::String(value.to_string())),
223            AttributeType::Boolean => {
224                match value.to_lowercase().as_str() {
225                    "true" | "1" => Ok(AttributeValue::Boolean(true)),
226                    "false" | "0" => Ok(AttributeValue::Boolean(false)),
227                    _ => Err(AttributeError::InvalidBoolean(value.to_string())),
228                }
229            },
230            AttributeType::Integer => {
231                value.parse::<i64>()
232                    .map(AttributeValue::Integer)
233                    .map_err(|_| AttributeError::InvalidInteger(value.to_string()))
234            },
235            AttributeType::Decimal => {
236                value.parse::<f64>()
237                    .map(AttributeValue::Decimal)
238                    .map_err(|_| AttributeError::InvalidDecimal(value.to_string()))
239            },
240            AttributeType::Date => {
241                chrono::NaiveDate::parse_from_str(value, "%Y-%m-%d")
242                    .map(AttributeValue::Date)
243                    .map_err(|_| AttributeError::InvalidDate(value.to_string()))
244            },
245            AttributeType::DateTime => {
246                chrono::DateTime::parse_from_rfc3339(value)
247                    .map(|dt| AttributeValue::DateTime(dt.with_timezone(&chrono::Utc)))
248                    .map_err(|_| AttributeError::InvalidDateTime(value.to_string()))
249            },
250            AttributeType::Uri => Ok(AttributeValue::Uri(value.to_string())),
251            AttributeType::Language => Ok(AttributeValue::Language(value.to_string())),
252            AttributeType::Token => Ok(AttributeValue::Token(value.trim().to_string())),
253            AttributeType::Raw => Ok(AttributeValue::Raw(value.to_string())),
254        }
255    }
256
257    /// Validate the attribute value
258    pub fn validate(&self) -> Result<(), AttributeError> {
259        match self {
260            AttributeValue::Enum(value, allowed_values) => {
261                if allowed_values.contains(value) {
262                    Ok(())
263                } else {
264                    Err(AttributeError::InvalidEnumValue {
265                        value: value.clone(),
266                        allowed: allowed_values.clone(),
267                    })
268                }
269            },
270            AttributeValue::Uri(uri) => {
271                // Basic URI validation
272                if uri.contains(' ') || uri.is_empty() {
273                    Err(AttributeError::InvalidUri(uri.clone()))
274                } else {
275                    Ok(())
276                }
277            },
278            AttributeValue::Language(lang) => {
279                // Basic language code validation (simplified)
280                if lang.len() < 2 || lang.len() > 8 {
281                    Err(AttributeError::InvalidLanguage(lang.clone()))
282                } else {
283                    Ok(())
284                }
285            },
286            _ => Ok(()),
287        }
288    }
289}
290
291impl Display for AttributeValue {
292    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293        write!(f, "{}", self.to_xml_value())
294    }
295}
296
297impl From<String> for AttributeValue {
298    fn from(value: String) -> Self {
299        AttributeValue::String(value)
300    }
301}
302
303impl From<&str> for AttributeValue {
304    fn from(value: &str) -> Self {
305        AttributeValue::String(value.to_string())
306    }
307}
308
309impl From<bool> for AttributeValue {
310    fn from(value: bool) -> Self {
311        AttributeValue::Boolean(value)
312    }
313}
314
315impl From<i64> for AttributeValue {
316    fn from(value: i64) -> Self {
317        AttributeValue::Integer(value)
318    }
319}
320
321impl From<f64> for AttributeValue {
322    fn from(value: f64) -> Self {
323        AttributeValue::Decimal(value)
324    }
325}
326
327/// Attribute type hints for parsing
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
329pub enum AttributeType {
330    String,
331    Boolean,
332    Integer,
333    Decimal,
334    Date,
335    DateTime,
336    Uri,
337    Language,
338    Token,
339    Raw,
340}
341
342impl std::fmt::Display for AttributeType {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        match self {
345            AttributeType::String => write!(f, "string"),
346            AttributeType::Boolean => write!(f, "boolean"),
347            AttributeType::Integer => write!(f, "integer"),
348            AttributeType::Decimal => write!(f, "decimal"),
349            AttributeType::Date => write!(f, "date"),
350            AttributeType::DateTime => write!(f, "dateTime"),
351            AttributeType::Uri => write!(f, "anyURI"),
352            AttributeType::Language => write!(f, "language"),
353            AttributeType::Token => write!(f, "token"),
354            AttributeType::Raw => write!(f, "raw"),
355        }
356    }
357}
358
359/// Comprehensive attribute map with deterministic ordering
360#[derive(Debug, Clone, PartialEq)]
361pub struct AttributeMap {
362    /// Attributes stored with deterministic ordering
363    attributes: IndexMap<QName, AttributeValue>,
364}
365
366impl AttributeMap {
367    /// Create a new empty attribute map
368    pub fn new() -> Self {
369        Self {
370            attributes: IndexMap::new(),
371        }
372    }
373
374    /// Insert an attribute
375    pub fn insert(&mut self, name: QName, value: AttributeValue) -> Option<AttributeValue> {
376        self.attributes.insert(name, value)
377    }
378
379    /// Insert an attribute by string name
380    pub fn insert_str(&mut self, name: &str, value: impl Into<AttributeValue>) -> 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.iter()
441            .filter(|(qname, _)| qname.is_ddex_standard())
442            .map(|(qname, value)| (qname.clone(), value.clone()))
443            .collect()
444    }
445
446    /// Get all extension/custom attributes
447    pub fn extension_attributes(&self) -> IndexMap<QName, AttributeValue> {
448        self.attributes.iter()
449            .filter(|(qname, _)| !qname.is_ddex_standard())
450            .map(|(qname, value)| (qname.clone(), value.clone()))
451            .collect()
452    }
453
454    /// Get all namespace declaration attributes
455    pub fn namespace_declarations(&self) -> IndexMap<QName, AttributeValue> {
456        self.attributes.iter()
457            .filter(|(qname, _)| qname.is_namespace_declaration())
458            .map(|(qname, value)| (qname.clone(), value.clone()))
459            .collect()
460    }
461
462    /// Merge attributes from another map, with conflict resolution
463    pub fn merge(&mut self, other: &AttributeMap, strategy: AttributeMergeStrategy) {
464        for (qname, value) in &other.attributes {
465            if let Some(_existing) = self.attributes.get(qname) {
466                match strategy {
467                    AttributeMergeStrategy::PreferThis => continue,
468                    AttributeMergeStrategy::PreferOther => {
469                        self.attributes.insert(qname.clone(), value.clone());
470                    },
471                    AttributeMergeStrategy::Error => {
472                        // In a real implementation, this would return a Result
473                        eprintln!("Attribute conflict: {}", qname);
474                    },
475                }
476            } else {
477                self.attributes.insert(qname.clone(), value.clone());
478            }
479        }
480    }
481
482    /// Validate all attributes
483    pub fn validate(&self) -> Vec<AttributeError> {
484        let mut errors = Vec::new();
485        for (_qname, value) in &self.attributes {
486            if let Err(error) = value.validate() {
487                errors.push(error);
488            }
489        }
490        errors
491    }
492
493    /// Convert to a simple string map for backwards compatibility
494    pub fn to_string_map(&self) -> IndexMap<String, String> {
495        self.attributes.iter()
496            .map(|(qname, value)| (qname.to_xml_name(), value.to_xml_value()))
497            .collect()
498    }
499
500    /// Create from a simple string map
501    pub fn from_string_map(map: IndexMap<String, String>) -> Self {
502        let mut attributes = IndexMap::new();
503        for (name, value) in map {
504            let qname = QName::from_str(&name).unwrap_or_else(|_| QName::new(name));
505            attributes.insert(qname, AttributeValue::String(value));
506        }
507        Self { attributes }
508    }
509    
510    /// Get iterator over attribute keys
511    pub fn keys(&self) -> indexmap::map::Keys<'_, QName, AttributeValue> {
512        self.attributes.keys()
513    }
514    
515    /// Get all attributes in canonical order (namespace declarations first, then alphabetical)
516    pub fn to_canonical_ordered(&self) -> IndexMap<QName, AttributeValue> {
517        let mut namespace_attrs = IndexMap::new();
518        let mut regular_attrs = IndexMap::new();
519        
520        // Separate namespace declarations from regular attributes
521        for (qname, value) in &self.attributes {
522            if qname.is_namespace_declaration() {
523                namespace_attrs.insert(qname.clone(), value.clone());
524            } else {
525                regular_attrs.insert(qname.clone(), value.clone());
526            }
527        }
528        
529        // Sort both collections by canonical sort key
530        namespace_attrs.sort_by(|a, _, b, _| a.canonical_sort_key().cmp(&b.canonical_sort_key()));
531        regular_attrs.sort_by(|a, _, b, _| a.canonical_sort_key().cmp(&b.canonical_sort_key()));
532        
533        // Combine namespace declarations first, then regular attributes
534        let mut result = IndexMap::new();
535        result.extend(namespace_attrs);
536        result.extend(regular_attrs);
537        
538        result
539    }
540    
541}
542
543impl Default for AttributeMap {
544    fn default() -> Self {
545        Self::new()
546    }
547}
548
549impl Serialize for AttributeMap {
550    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
551    where
552        S: Serializer,
553    {
554        // Serialize as a map of string -> string for JSON compatibility
555        let string_map = self.to_string_map();
556        string_map.serialize(serializer)
557    }
558}
559
560impl<'de> Deserialize<'de> for AttributeMap {
561    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
562    where
563        D: Deserializer<'de>,
564    {
565        let string_map = IndexMap::<String, String>::deserialize(deserializer)?;
566        Ok(Self::from_string_map(string_map))
567    }
568}
569
570impl<'a> IntoIterator for &'a AttributeMap {
571    type Item = (&'a QName, &'a AttributeValue);
572    type IntoIter = indexmap::map::Iter<'a, QName, AttributeValue>;
573
574    fn into_iter(self) -> Self::IntoIter {
575        self.attributes.iter()
576    }
577}
578
579/// Strategy for merging attribute maps
580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
581pub enum AttributeMergeStrategy {
582    /// Keep the existing attribute (prefer this map)
583    PreferThis,
584    /// Use the new attribute (prefer other map)
585    PreferOther,
586    /// Raise an error on conflicts
587    Error,
588}
589
590/// Attribute inheritance model for nested elements
591#[derive(Debug, Clone)]
592pub struct AttributeInheritance {
593    /// Attributes that should be inherited from parent elements
594    inheritable_attributes: IndexSet<QName>,
595    /// Attributes that should never be inherited
596    non_inheritable_attributes: IndexSet<QName>,
597}
598
599impl AttributeInheritance {
600    /// Create new attribute inheritance rules
601    pub fn new() -> Self {
602        let mut inheritable = IndexSet::new();
603        let mut non_inheritable = IndexSet::new();
604
605        // Common inheritable attributes
606        inheritable.insert(QName::new("LanguageAndScriptCode"));
607        inheritable.insert(QName::new("ApplicableTerritoryCode"));
608        inheritable.insert(QName::with_namespace("lang", "http://www.w3.org/XML/1998/namespace"));
609
610        // Non-inheritable attributes (element-specific)
611        non_inheritable.insert(QName::new("SequenceNumber"));
612        non_inheritable.insert(QName::with_prefix_and_namespace("xsi", "type", "http://www.w3.org/2001/XMLSchema-instance"));
613
614        Self {
615            inheritable_attributes: inheritable,
616            non_inheritable_attributes: non_inheritable,
617        }
618    }
619
620    /// Check if an attribute should be inherited
621    pub fn should_inherit(&self, qname: &QName) -> bool {
622        if self.non_inheritable_attributes.contains(qname) {
623            false
624        } else if self.inheritable_attributes.contains(qname) {
625            true
626        } else {
627            // Default: don't inherit unknown attributes
628            false
629        }
630    }
631
632    /// Apply inheritance from parent to child attributes
633    pub fn apply_inheritance(&self, parent: &AttributeMap, child: &mut AttributeMap) {
634        for (qname, value) in parent.iter() {
635            if self.should_inherit(qname) && !child.contains_key(qname) {
636                child.insert(qname.clone(), value.clone());
637            }
638        }
639    }
640}
641
642impl Default for AttributeInheritance {
643    fn default() -> Self {
644        Self::new()
645    }
646}
647
648/// Attribute-related errors
649#[derive(Debug, Clone, Error, PartialEq)]
650pub enum AttributeError {
651    #[error("Invalid boolean value: {0}")]
652    InvalidBoolean(String),
653    
654    #[error("Invalid integer value: {0}")]
655    InvalidInteger(String),
656    
657    #[error("Invalid decimal value: {0}")]
658    InvalidDecimal(String),
659    
660    #[error("Invalid date value: {0}")]
661    InvalidDate(String),
662    
663    #[error("Invalid datetime value: {0}")]
664    InvalidDateTime(String),
665    
666    #[error("Invalid URI value: {0}")]
667    InvalidUri(String),
668    
669    #[error("Invalid language code: {0}")]
670    InvalidLanguage(String),
671    
672    #[error("Invalid enum value '{value}', allowed values: {}", allowed.join(", "))]
673    InvalidEnumValue {
674        value: String,
675        allowed: Vec<String>,
676    },
677    
678    #[error("Missing required attribute: {0}")]
679    MissingRequired(String),
680    
681    #[error("Conflicting attribute values for: {0}")]
682    ConflictingValues(String),
683    
684    #[error("Invalid QName format: {0}")]
685    InvalidQName(String),
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691
692    #[test]
693    fn test_qname_creation() {
694        let qname = QName::new("title");
695        assert_eq!(qname.local_name, "title");
696        assert_eq!(qname.namespace_uri, None);
697        assert_eq!(qname.prefix, None);
698
699        let qname_ns = QName::with_namespace("title", "http://ddex.net/xml/ern/43");
700        assert_eq!(qname_ns.namespace_uri, Some("http://ddex.net/xml/ern/43".to_string()));
701
702        let qname_prefix = QName::with_prefix_and_namespace("title", "ern", "http://ddex.net/xml/ern/43");
703        assert_eq!(qname_prefix.prefix, Some("ern".to_string()));
704        assert_eq!(qname_prefix.to_xml_name(), "ern:title");
705    }
706
707    #[test]
708    fn test_qname_parsing() {
709        let qname: QName = "ern:title".parse().unwrap();
710        assert_eq!(qname.local_name, "title");
711        assert_eq!(qname.prefix, Some("ern".to_string()));
712
713        let simple_qname: QName = "title".parse().unwrap();
714        assert_eq!(simple_qname.local_name, "title");
715        assert_eq!(simple_qname.prefix, None);
716    }
717
718    #[test]
719    fn test_qname_canonical_ordering() {
720        let xmlns = QName::new("xmlns");
721        let xmlns_ern = QName::from_str("xmlns:ern").unwrap();
722        let regular = QName::new("title");
723        let prefixed = QName::from_str("ern:title").unwrap();
724
725        let mut qnames = vec![&regular, &prefixed, &xmlns_ern, &xmlns];
726        qnames.sort();
727
728        // Namespace declarations should come first
729        assert_eq!(qnames[0], &xmlns);
730        assert_eq!(qnames[1], &xmlns_ern);
731    }
732
733    #[test]
734    fn test_attribute_value_types() {
735        let string_val = AttributeValue::string("test");
736        assert_eq!(string_val.to_xml_value(), "test");
737
738        let bool_val = AttributeValue::boolean(true);
739        assert_eq!(bool_val.to_xml_value(), "true");
740
741        let int_val = AttributeValue::integer(42);
742        assert_eq!(int_val.to_xml_value(), "42");
743
744        // Test parsing with type hints
745        let parsed = AttributeValue::parse_with_type("true", AttributeType::Boolean).unwrap();
746        assert_eq!(parsed, AttributeValue::Boolean(true));
747
748        let parsed_int = AttributeValue::parse_with_type("123", AttributeType::Integer).unwrap();
749        assert_eq!(parsed_int, AttributeValue::Integer(123));
750    }
751
752    #[test]
753    fn test_attribute_map() {
754        let mut map = AttributeMap::new();
755        
756        map.insert_str("title", "Test Title");
757        map.insert_str("ern:version", "4.3");
758        map.insert_str("xmlns:ern", "http://ddex.net/xml/ern/43");
759
760        assert_eq!(map.len(), 3);
761        assert_eq!(map.get_str("title").unwrap().to_xml_value(), "Test Title");
762
763        // Test canonical ordering
764        let canonical: Vec<_> = map.iter_canonical().collect();
765        assert_eq!(canonical.len(), 3);
766        
767        // xmlns attributes should come first
768        let first_attr = &canonical[0];
769        assert!(first_attr.0.is_namespace_declaration());
770    }
771
772    #[test]
773    fn test_attribute_inheritance() {
774        let inheritance = AttributeInheritance::new();
775        
776        let lang_attr = QName::new("LanguageAndScriptCode");
777        let seq_attr = QName::new("SequenceNumber");
778        
779        assert!(inheritance.should_inherit(&lang_attr));
780        assert!(!inheritance.should_inherit(&seq_attr));
781    }
782
783    #[test]
784    fn test_attribute_validation() {
785        let mut enum_val = AttributeValue::Enum(
786            "invalid".to_string(), 
787            vec!["valid1".to_string(), "valid2".to_string()]
788        );
789        assert!(enum_val.validate().is_err());
790
791        enum_val = AttributeValue::Enum(
792            "valid1".to_string(), 
793            vec!["valid1".to_string(), "valid2".to_string()]
794        );
795        assert!(enum_val.validate().is_ok());
796    }
797
798    #[test]
799    fn test_ddex_standard_detection() {
800        let ddex_attr = QName::with_namespace("title", "http://ddex.net/xml/ern/43");
801        assert!(ddex_attr.is_ddex_standard());
802
803        let xmlns_attr = QName::new("xmlns");
804        assert!(xmlns_attr.is_ddex_standard());
805
806        let custom_attr = QName::with_namespace("custom", "http://example.com/custom");
807        assert!(!custom_attr.is_ddex_standard());
808
809        let lang_attr = QName::new("LanguageAndScriptCode");
810        assert!(lang_attr.is_ddex_standard());
811    }
812
813    #[test]
814    fn test_attribute_map_serialization() {
815        let mut map = AttributeMap::new();
816        map.insert_str("title", "Test Title");
817        map.insert_str("version", "4.3");
818
819        // Test conversion to string map
820        let string_map = map.to_string_map();
821        assert_eq!(string_map.len(), 2);
822        assert_eq!(string_map.get("title"), Some(&"Test Title".to_string()));
823
824        // Test round-trip through string map
825        let restored = AttributeMap::from_string_map(string_map);
826        assert_eq!(restored.len(), 2);
827        assert_eq!(restored.get_str("title").unwrap().to_xml_value(), "Test Title");
828    }
829}