ddex_builder/canonical/
rules.rs

1//! # Canonical XML Rules for DB-C14N/1.0
2//!
3//! This module defines the canonical XML transformation rules for DDEX Builder,
4//! including comprehensive namespace prefix locking, element ordering, and
5//! support for all DDEX standards (ERN, AVS, MEAD, PIE, etc.).
6
7use ddex_core::namespace::NamespaceRegistry;
8use indexmap::IndexMap;
9use std::collections::HashSet;
10
11/// Fixed XML declaration for all canonical XML
12pub const XML_DECLARATION: &str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
13
14/// Comprehensive namespace prefix lock tables for all ERN versions
15pub struct NamespacePrefixLock {
16    /// Registry for namespace management
17    #[allow(dead_code)]
18    registry: NamespaceRegistry,
19    /// Locked prefixes for specific versions
20    version_locks: IndexMap<String, IndexMap<String, String>>, // version -> (uri -> prefix)
21    /// Reserved prefixes that cannot be used
22    reserved_prefixes: HashSet<String>,
23}
24
25impl NamespacePrefixLock {
26    /// Create new prefix lock manager
27    pub fn new() -> Self {
28        let mut lock = Self {
29            registry: NamespaceRegistry::new(),
30            version_locks: IndexMap::new(),
31            reserved_prefixes: HashSet::new(),
32        };
33
34        lock.initialize_version_locks();
35        lock
36    }
37
38    /// Initialize prefix locks for all ERN versions
39    fn initialize_version_locks(&mut self) {
40        // ERN 3.8.2 prefix locks
41        let mut ern_382_prefixes = IndexMap::new();
42        ern_382_prefixes.insert("http://ddex.net/xml/ern/382".to_string(), "ern".to_string());
43        ern_382_prefixes.insert("http://ddex.net/xml/avs".to_string(), "avs".to_string());
44        ern_382_prefixes.insert("http://ddex.net/xml/avs/avs".to_string(), "avs".to_string());
45        ern_382_prefixes.insert(
46            "http://www.w3.org/2001/XMLSchema-instance".to_string(),
47            "xsi".to_string(),
48        );
49        ern_382_prefixes.insert(
50            "http://www.w3.org/2001/XMLSchema".to_string(),
51            "xs".to_string(),
52        );
53        ern_382_prefixes.insert("http://ddex.net/xml/gc".to_string(), "gc".to_string());
54        self.version_locks
55            .insert("3.8.2".to_string(), ern_382_prefixes.clone());
56        self.version_locks
57            .insert("382".to_string(), ern_382_prefixes);
58
59        // ERN 4.2 prefix locks
60        let mut ern_42_prefixes = IndexMap::new();
61        ern_42_prefixes.insert("http://ddex.net/xml/ern/42".to_string(), "ern".to_string());
62        ern_42_prefixes.insert("http://ddex.net/xml/avs".to_string(), "avs".to_string());
63        ern_42_prefixes.insert("http://ddex.net/xml/avs/avs".to_string(), "avs".to_string());
64        ern_42_prefixes.insert(
65            "http://www.w3.org/2001/XMLSchema-instance".to_string(),
66            "xsi".to_string(),
67        );
68        ern_42_prefixes.insert(
69            "http://www.w3.org/2001/XMLSchema".to_string(),
70            "xs".to_string(),
71        );
72        ern_42_prefixes.insert("http://ddex.net/xml/gc".to_string(), "gc".to_string());
73        // Additional 4.2 namespaces
74        ern_42_prefixes.insert(
75            "http://ddex.net/xml/mead/mead".to_string(),
76            "mead".to_string(),
77        );
78        ern_42_prefixes.insert("http://ddex.net/xml/pie/pie".to_string(), "pie".to_string());
79        self.version_locks
80            .insert("4.2".to_string(), ern_42_prefixes.clone());
81        self.version_locks.insert("42".to_string(), ern_42_prefixes);
82
83        // ERN 4.3 prefix locks
84        let mut ern_43_prefixes = IndexMap::new();
85        ern_43_prefixes.insert("http://ddex.net/xml/ern/43".to_string(), "ern".to_string());
86        ern_43_prefixes.insert("http://ddex.net/xml/avs".to_string(), "avs".to_string());
87        ern_43_prefixes.insert("http://ddex.net/xml/avs/avs".to_string(), "avs".to_string());
88        ern_43_prefixes.insert(
89            "http://www.w3.org/2001/XMLSchema-instance".to_string(),
90            "xsi".to_string(),
91        );
92        ern_43_prefixes.insert(
93            "http://www.w3.org/2001/XMLSchema".to_string(),
94            "xs".to_string(),
95        );
96        ern_43_prefixes.insert("http://ddex.net/xml/gc".to_string(), "gc".to_string());
97        // Additional 4.3 namespaces
98        ern_43_prefixes.insert(
99            "http://ddex.net/xml/mead/mead".to_string(),
100            "mead".to_string(),
101        );
102        ern_43_prefixes.insert("http://ddex.net/xml/pie/pie".to_string(), "pie".to_string());
103        ern_43_prefixes.insert("http://ddex.net/xml/rin/rin".to_string(), "rin".to_string());
104        ern_43_prefixes.insert("http://ddex.net/xml/dsrf".to_string(), "dsrf".to_string());
105        self.version_locks
106            .insert("4.3".to_string(), ern_43_prefixes.clone());
107        self.version_locks.insert("43".to_string(), ern_43_prefixes);
108
109        // Mark all locked prefixes as reserved
110        for prefixes in self.version_locks.values() {
111            for prefix in prefixes.values() {
112                self.reserved_prefixes.insert(prefix.clone());
113            }
114        }
115    }
116
117    /// Get locked prefix for a namespace URI in a specific version
118    pub fn get_locked_prefix(&self, uri: &str, version: &str) -> Option<&str> {
119        self.version_locks
120            .get(version)
121            .and_then(|prefixes| prefixes.get(uri))
122            .map(|prefix| prefix.as_str())
123    }
124
125    /// Get all locked prefixes for a version
126    pub fn get_version_prefixes(&self, version: &str) -> Option<&IndexMap<String, String>> {
127        self.version_locks.get(version)
128    }
129
130    /// Apply prefix deduplication algorithm
131    pub fn deduplicate_prefixes(
132        &self,
133        declarations: &IndexMap<String, String>,
134        version: &str,
135    ) -> IndexMap<String, String> {
136        let mut deduplicated = IndexMap::new();
137        let locked_prefixes = self
138            .get_version_prefixes(version)
139            .cloned()
140            .unwrap_or_default();
141
142        // First pass: apply locked prefixes
143        for (original_prefix, uri) in declarations {
144            if let Some(locked_prefix) = locked_prefixes.get(uri) {
145                deduplicated.insert(locked_prefix.clone(), uri.clone());
146            } else {
147                // Use original prefix if not locked
148                deduplicated.insert(original_prefix.clone(), uri.clone());
149            }
150        }
151
152        // Second pass: resolve conflicts
153        let mut final_declarations = IndexMap::new();
154        let mut used_prefixes = HashSet::new();
155
156        for (prefix, uri) in deduplicated {
157            if used_prefixes.contains(&prefix) {
158                // Generate unique prefix
159                let unique_prefix = self.generate_unique_prefix(&prefix, &used_prefixes);
160                used_prefixes.insert(unique_prefix.clone());
161                final_declarations.insert(unique_prefix, uri);
162            } else {
163                used_prefixes.insert(prefix.clone());
164                final_declarations.insert(prefix, uri);
165            }
166        }
167
168        final_declarations
169    }
170
171    /// Generate a unique prefix to avoid conflicts
172    fn generate_unique_prefix(&self, base_prefix: &str, used_prefixes: &HashSet<String>) -> String {
173        let mut counter = 1;
174        loop {
175            let candidate = format!("{}{}", base_prefix, counter);
176            if !used_prefixes.contains(&candidate) && !self.reserved_prefixes.contains(&candidate) {
177                return candidate;
178            }
179            counter += 1;
180        }
181    }
182}
183
184/// Comprehensive element ordering for all ERN versions
185pub struct ElementOrder {
186    /// Element ordering rules by version
187    version_orders: IndexMap<String, IndexMap<String, Vec<String>>>, // version -> (parent -> children)
188}
189
190impl ElementOrder {
191    /// Create new element order manager
192    pub fn new() -> Self {
193        let mut order = Self {
194            version_orders: IndexMap::new(),
195        };
196
197        order.initialize_element_orders();
198        order
199    }
200
201    /// Initialize element orders for all ERN versions
202    fn initialize_element_orders(&mut self) {
203        // ERN 3.8.2 element orders
204        let mut ern_382_order = IndexMap::new();
205        self.add_common_orders(&mut ern_382_order);
206        self.add_ern_382_specific_orders(&mut ern_382_order);
207        self.version_orders
208            .insert("3.8.2".to_string(), ern_382_order.clone());
209        self.version_orders.insert("382".to_string(), ern_382_order);
210
211        // ERN 4.2 element orders
212        let mut ern_42_order = IndexMap::new();
213        self.add_common_orders(&mut ern_42_order);
214        self.add_ern_42_specific_orders(&mut ern_42_order);
215        self.version_orders
216            .insert("4.2".to_string(), ern_42_order.clone());
217        self.version_orders.insert("42".to_string(), ern_42_order);
218
219        // ERN 4.3 element orders
220        let mut ern_43_order = IndexMap::new();
221        self.add_common_orders(&mut ern_43_order);
222        self.add_ern_43_specific_orders(&mut ern_43_order);
223        self.version_orders
224            .insert("4.3".to_string(), ern_43_order.clone());
225        self.version_orders.insert("43".to_string(), ern_43_order);
226    }
227
228    /// Add common element orders across all versions
229    fn add_common_orders(&self, order: &mut IndexMap<String, Vec<String>>) {
230        // Message header - common to all versions
231        order.insert(
232            "MessageHeader".to_string(),
233            vec![
234                "MessageId".to_string(),
235                "MessageType".to_string(),
236                "MessageCreatedDateTime".to_string(),
237                "MessageSender".to_string(),
238                "MessageRecipient".to_string(),
239                "MessageControlType".to_string(),
240            ],
241        );
242
243        // Party - common structure
244        order.insert(
245            "Party".to_string(),
246            vec![
247                "PartyReference".to_string(),
248                "PartyId".to_string(),
249                "PartyName".to_string(),
250                "PartyType".to_string(),
251            ],
252        );
253
254        // Release - base structure
255        order.insert(
256            "Release".to_string(),
257            vec![
258                "ReleaseReference".to_string(),
259                "ReleaseId".to_string(),
260                "ReferenceTitle".to_string(),
261                "ReleaseType".to_string(),
262                "ReleaseResourceReferenceList".to_string(),
263                "ReleaseDetailsByTerritory".to_string(),
264            ],
265        );
266
267        // Deal - base structure
268        order.insert(
269            "Deal".to_string(),
270            vec![
271                "DealReference".to_string(),
272                "DealTerms".to_string(),
273                "DealReleaseReference".to_string(),
274            ],
275        );
276    }
277
278    /// Add ERN 3.8.2 specific orders
279    fn add_ern_382_specific_orders(&self, order: &mut IndexMap<String, Vec<String>>) {
280        // SoundRecording for 3.8.2
281        order.insert(
282            "SoundRecording".to_string(),
283            vec![
284                "SoundRecordingType".to_string(),
285                "SoundRecordingId".to_string(),
286                "ReferenceTitle".to_string(),
287                "Duration".to_string(),
288                "CreationDate".to_string(),
289                "SoundRecordingDetailsByTerritory".to_string(),
290            ],
291        );
292    }
293
294    /// Add ERN 4.2 specific orders
295    fn add_ern_42_specific_orders(&self, order: &mut IndexMap<String, Vec<String>>) {
296        // MessageHeader with audit trail for 4.2
297        order.insert(
298            "MessageHeader".to_string(),
299            vec![
300                "MessageId".to_string(),
301                "MessageType".to_string(),
302                "MessageCreatedDateTime".to_string(),
303                "MessageSender".to_string(),
304                "MessageRecipient".to_string(),
305                "MessageControlType".to_string(),
306                "MessageAuditTrail".to_string(),
307            ],
308        );
309
310        // Enhanced SoundRecording for 4.2
311        order.insert(
312            "SoundRecording".to_string(),
313            vec![
314                "SoundRecordingType".to_string(),
315                "SoundRecordingId".to_string(),
316                "ReferenceTitle".to_string(),
317                "DisplayTitle".to_string(),
318                "Duration".to_string(),
319                "CreationDate".to_string(),
320                "MasteredDate".to_string(),
321                "SoundRecordingDetailsByTerritory".to_string(),
322            ],
323        );
324    }
325
326    /// Add ERN 4.3 specific orders
327    fn add_ern_43_specific_orders(&self, order: &mut IndexMap<String, Vec<String>>) {
328        // MessageHeader with audit trail for 4.3
329        order.insert(
330            "MessageHeader".to_string(),
331            vec![
332                "MessageId".to_string(),
333                "MessageType".to_string(),
334                "MessageCreatedDateTime".to_string(),
335                "MessageSender".to_string(),
336                "MessageRecipient".to_string(),
337                "MessageControlType".to_string(),
338                "MessageAuditTrail".to_string(),
339            ],
340        );
341
342        // Enhanced SoundRecording for 4.3
343        order.insert(
344            "SoundRecording".to_string(),
345            vec![
346                "SoundRecordingType".to_string(),
347                "SoundRecordingId".to_string(),
348                "ReferenceTitle".to_string(),
349                "DisplayTitle".to_string(),
350                "DisplayTitleText".to_string(),
351                "Duration".to_string(),
352                "CreationDate".to_string(),
353                "MasteredDate".to_string(),
354                "OriginalResourceReleaseDate".to_string(),
355                "SoundRecordingDetailsByTerritory".to_string(),
356            ],
357        );
358
359        // Video for 4.3
360        order.insert(
361            "Video".to_string(),
362            vec![
363                "VideoType".to_string(),
364                "VideoId".to_string(),
365                "ReferenceTitle".to_string(),
366                "DisplayTitle".to_string(),
367                "Duration".to_string(),
368                "CreationDate".to_string(),
369                "VideoDetailsByTerritory".to_string(),
370            ],
371        );
372
373        // Image for 4.3
374        order.insert(
375            "Image".to_string(),
376            vec![
377                "ImageType".to_string(),
378                "ImageId".to_string(),
379                "ReferenceTitle".to_string(),
380                "DisplayTitle".to_string(),
381                "CreationDate".to_string(),
382                "ImageDetailsByTerritory".to_string(),
383            ],
384        );
385    }
386
387    /// Get element order for a parent element in a specific version
388    pub fn get_element_order(&self, parent_element: &str, version: &str) -> Option<&Vec<String>> {
389        self.version_orders
390            .get(version)
391            .and_then(|orders| orders.get(parent_element))
392    }
393
394    /// Get all element orders for a version
395    pub fn get_version_orders(&self, version: &str) -> Option<&IndexMap<String, Vec<String>>> {
396        self.version_orders.get(version)
397    }
398}
399
400/// AVS namespace handling
401pub struct AVSNamespaceHandler {
402    /// AVS-specific namespaces
403    avs_namespaces: IndexMap<String, String>,
404}
405
406impl AVSNamespaceHandler {
407    /// Create new AVS namespace handler
408    pub fn new() -> Self {
409        let mut handler = Self {
410            avs_namespaces: IndexMap::new(),
411        };
412
413        // Initialize AVS namespaces
414        handler
415            .avs_namespaces
416            .insert("http://ddex.net/xml/avs".to_string(), "avs".to_string());
417        handler
418            .avs_namespaces
419            .insert("http://ddex.net/xml/avs/avs".to_string(), "avs".to_string());
420
421        handler
422    }
423
424    /// Check if a namespace is AVS-related
425    pub fn is_avs_namespace(&self, uri: &str) -> bool {
426        self.avs_namespaces.contains_key(uri)
427    }
428
429    /// Get AVS prefix for a namespace
430    pub fn get_avs_prefix(&self, uri: &str) -> Option<&str> {
431        self.avs_namespaces.get(uri).map(|prefix| prefix.as_str())
432    }
433}
434
435/// Complete namespace management for canonical XML
436pub struct CanonicalNamespaceManager {
437    /// Namespace prefix lock
438    prefix_lock: NamespacePrefixLock,
439    /// Element order manager
440    element_order: ElementOrder,
441    /// AVS namespace handler
442    avs_handler: AVSNamespaceHandler,
443}
444
445impl CanonicalNamespaceManager {
446    /// Create new canonical namespace manager
447    pub fn new() -> Self {
448        Self {
449            prefix_lock: NamespacePrefixLock::new(),
450            element_order: ElementOrder::new(),
451            avs_handler: AVSNamespaceHandler::new(),
452        }
453    }
454
455    /// Apply complete canonical transformation
456    pub fn canonicalize_namespaces(
457        &self,
458        declarations: &IndexMap<String, String>,
459        version: &str,
460    ) -> IndexMap<String, String> {
461        // Apply prefix locking and deduplication
462        let locked_declarations = self.prefix_lock.deduplicate_prefixes(declarations, version);
463
464        // Sort declarations alphabetically by prefix
465        let mut sorted_declarations: Vec<_> = locked_declarations.into_iter().collect();
466        sorted_declarations.sort_by(|a, b| a.0.cmp(&b.0));
467
468        sorted_declarations.into_iter().collect()
469    }
470
471    /// Get element order for canonicalization
472    pub fn get_canonical_element_order(&self, parent: &str, version: &str) -> Option<&Vec<String>> {
473        self.element_order.get_element_order(parent, version)
474    }
475
476    /// Check if namespace requires special AVS handling
477    pub fn requires_avs_handling(&self, uri: &str) -> bool {
478        self.avs_handler.is_avs_namespace(uri)
479    }
480}
481
482impl Default for NamespacePrefixLock {
483    fn default() -> Self {
484        Self::new()
485    }
486}
487
488impl Default for ElementOrder {
489    fn default() -> Self {
490        Self::new()
491    }
492}
493
494impl Default for AVSNamespaceHandler {
495    fn default() -> Self {
496        Self::new()
497    }
498}
499
500impl Default for CanonicalNamespaceManager {
501    fn default() -> Self {
502        Self::new()
503    }
504}
505
506/// Convenience functions for backward compatibility
507
508/// Get namespace prefix table for a specific ERN version
509pub fn get_namespace_prefixes(version: &str) -> IndexMap<String, String> {
510    let lock = NamespacePrefixLock::new();
511    lock.get_version_prefixes(version)
512        .cloned()
513        .unwrap_or_default()
514}
515
516/// Get element order for a specific ERN version
517pub fn get_element_order(version: &str) -> IndexMap<String, Vec<String>> {
518    let order = ElementOrder::new();
519    order
520        .get_version_orders(version)
521        .cloned()
522        .unwrap_or_default()
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528
529    #[test]
530    fn test_namespace_prefix_lock() {
531        let lock = NamespacePrefixLock::new();
532
533        assert_eq!(
534            lock.get_locked_prefix("http://ddex.net/xml/ern/43", "4.3"),
535            Some("ern")
536        );
537        assert_eq!(
538            lock.get_locked_prefix("http://ddex.net/xml/avs", "4.3"),
539            Some("avs")
540        );
541    }
542
543    #[test]
544    fn test_prefix_deduplication() {
545        let lock = NamespacePrefixLock::new();
546
547        let mut declarations = IndexMap::new();
548        declarations.insert(
549            "custom_ern".to_string(),
550            "http://ddex.net/xml/ern/43".to_string(),
551        );
552        declarations.insert("avs".to_string(), "http://ddex.net/xml/avs".to_string());
553
554        let deduplicated = lock.deduplicate_prefixes(&declarations, "4.3");
555
556        // Should use locked prefix for ERN
557        assert_eq!(
558            deduplicated.get("ern"),
559            Some(&"http://ddex.net/xml/ern/43".to_string())
560        );
561        assert_eq!(
562            deduplicated.get("avs"),
563            Some(&"http://ddex.net/xml/avs".to_string())
564        );
565    }
566
567    #[test]
568    fn test_element_order() {
569        let order = ElementOrder::new();
570
571        let message_order = order.get_element_order("MessageHeader", "4.3");
572        assert!(message_order.is_some());
573
574        let order_vec = message_order.unwrap();
575        assert_eq!(order_vec[0], "MessageId");
576        assert_eq!(order_vec[1], "MessageType");
577    }
578
579    #[test]
580    fn test_avs_namespace_handler() {
581        let handler = AVSNamespaceHandler::new();
582
583        assert!(handler.is_avs_namespace("http://ddex.net/xml/avs"));
584        assert_eq!(
585            handler.get_avs_prefix("http://ddex.net/xml/avs"),
586            Some("avs")
587        );
588        assert!(!handler.is_avs_namespace("http://ddex.net/xml/ern/43"));
589    }
590
591    #[test]
592    fn test_canonical_namespace_manager() {
593        let manager = CanonicalNamespaceManager::new();
594
595        let mut declarations = IndexMap::new();
596        declarations.insert(
597            "z_ern".to_string(),
598            "http://ddex.net/xml/ern/43".to_string(),
599        );
600        declarations.insert("a_avs".to_string(), "http://ddex.net/xml/avs".to_string());
601
602        let canonical = manager.canonicalize_namespaces(&declarations, "4.3");
603
604        // Should be sorted alphabetically and use locked prefixes
605        let keys: Vec<_> = canonical.keys().collect();
606        assert!(keys.len() >= 2);
607        // Should contain locked prefixes
608        assert!(canonical.contains_key("ern"));
609        assert!(canonical.contains_key("avs"));
610    }
611}