1use crate::models::versions::ERNVersion;
8use indexmap::{IndexMap, IndexSet};
9
10#[derive(Debug, Clone)]
12pub struct NamespaceRegistry {
13 namespaces: IndexMap<String, NamespaceInfo>,
15 default_prefixes: IndexMap<String, String>,
17 custom_namespaces: IndexMap<String, NamespaceInfo>,
19 reserved_prefixes: IndexSet<String>,
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct NamespaceInfo {
26 pub uri: String,
28 pub preferred_prefix: String,
30 pub alternative_prefixes: Vec<String>,
32 pub standard: DDEXStandard,
34 pub version: Option<String>,
36 pub required: bool,
38}
39
40#[derive(Debug, Clone, PartialEq)]
42pub enum DDEXStandard {
43 ERN,
45 AVS,
47 MEAD,
49 PIE,
51 RIN,
53 RRI,
55 DSRF,
57 XMLSchema,
59 Custom(String),
61}
62
63#[derive(Debug, Clone)]
65pub struct NamespaceScope {
66 pub declarations: IndexMap<String, String>, pub parent: Option<Box<NamespaceScope>>,
70 pub depth: usize,
72}
73
74#[derive(Debug, Clone, PartialEq)]
76pub enum ConflictResolution {
77 PreferFirst,
79 PreferLatest,
81 GenerateUnique,
83 Error,
85}
86
87impl NamespaceRegistry {
88 pub fn new() -> Self {
90 let mut registry = Self {
91 namespaces: IndexMap::new(),
92 default_prefixes: IndexMap::new(),
93 custom_namespaces: IndexMap::new(),
94 reserved_prefixes: IndexSet::new(),
95 };
96
97 registry.initialize_ddex_namespaces();
98 registry
99 }
100
101 fn initialize_ddex_namespaces(&mut self) {
103 self.register_namespace(NamespaceInfo {
105 uri: "http://ddex.net/xml/ern/382".to_string(),
106 preferred_prefix: "ern".to_string(),
107 alternative_prefixes: vec!["ern382".to_string()],
108 standard: DDEXStandard::ERN,
109 version: Some("3.8.2".to_string()),
110 required: true,
111 });
112
113 self.register_namespace(NamespaceInfo {
114 uri: "http://ddex.net/xml/ern/42".to_string(),
115 preferred_prefix: "ern".to_string(),
116 alternative_prefixes: vec!["ern42".to_string()],
117 standard: DDEXStandard::ERN,
118 version: Some("4.2".to_string()),
119 required: true,
120 });
121
122 self.register_namespace(NamespaceInfo {
123 uri: "http://ddex.net/xml/ern/43".to_string(),
124 preferred_prefix: "ern".to_string(),
125 alternative_prefixes: vec!["ern43".to_string()],
126 standard: DDEXStandard::ERN,
127 version: Some("4.3".to_string()),
128 required: true,
129 });
130
131 self.register_namespace(NamespaceInfo {
133 uri: "http://ddex.net/xml/avs".to_string(),
134 preferred_prefix: "avs".to_string(),
135 alternative_prefixes: vec!["ddexavs".to_string()],
136 standard: DDEXStandard::AVS,
137 version: None,
138 required: false,
139 });
140
141 self.register_namespace(NamespaceInfo {
142 uri: "http://ddex.net/xml/avs/avs".to_string(),
143 preferred_prefix: "avs".to_string(),
144 alternative_prefixes: vec!["ddexavs".to_string()],
145 standard: DDEXStandard::AVS,
146 version: None,
147 required: false,
148 });
149
150 self.register_namespace(NamespaceInfo {
152 uri: "http://ddex.net/xml/mead/mead".to_string(),
153 preferred_prefix: "mead".to_string(),
154 alternative_prefixes: vec!["ddexmead".to_string()],
155 standard: DDEXStandard::MEAD,
156 version: None,
157 required: false,
158 });
159
160 self.register_namespace(NamespaceInfo {
162 uri: "http://ddex.net/xml/pie/pie".to_string(),
163 preferred_prefix: "pie".to_string(),
164 alternative_prefixes: vec!["ddexpie".to_string()],
165 standard: DDEXStandard::PIE,
166 version: None,
167 required: false,
168 });
169
170 self.register_namespace(NamespaceInfo {
172 uri: "http://ddex.net/xml/rin/rin".to_string(),
173 preferred_prefix: "rin".to_string(),
174 alternative_prefixes: vec!["ddexrin".to_string()],
175 standard: DDEXStandard::RIN,
176 version: None,
177 required: false,
178 });
179
180 self.register_namespace(NamespaceInfo {
182 uri: "http://www.w3.org/2001/XMLSchema-instance".to_string(),
183 preferred_prefix: "xsi".to_string(),
184 alternative_prefixes: vec!["xmlschema".to_string()],
185 standard: DDEXStandard::XMLSchema,
186 version: None,
187 required: false,
188 });
189
190 self.register_namespace(NamespaceInfo {
192 uri: "http://www.w3.org/2001/XMLSchema".to_string(),
193 preferred_prefix: "xs".to_string(),
194 alternative_prefixes: vec!["xsd".to_string(), "schema".to_string()],
195 standard: DDEXStandard::XMLSchema,
196 version: None,
197 required: false,
198 });
199
200 self.register_namespace(NamespaceInfo {
202 uri: "http://ddex.net/xml/gc".to_string(),
203 preferred_prefix: "gc".to_string(),
204 alternative_prefixes: vec!["ddexgc".to_string()],
205 standard: DDEXStandard::Custom("GC".to_string()),
206 version: None,
207 required: false,
208 });
209 }
210
211 pub fn register_namespace(&mut self, info: NamespaceInfo) {
213 self.default_prefixes
214 .insert(info.uri.clone(), info.preferred_prefix.clone());
215 self.reserved_prefixes.insert(info.preferred_prefix.clone());
216
217 for alt_prefix in &info.alternative_prefixes {
218 self.reserved_prefixes.insert(alt_prefix.clone());
219 }
220
221 self.namespaces.insert(info.uri.clone(), info);
222 }
223
224 pub fn register_custom_namespace(&mut self, info: NamespaceInfo) -> Result<(), NamespaceError> {
226 if self.namespaces.contains_key(&info.uri) || self.custom_namespaces.contains_key(&info.uri)
228 {
229 return Err(NamespaceError::UriConflict(info.uri));
230 }
231
232 if self.is_prefix_reserved(&info.preferred_prefix) {
234 return Err(NamespaceError::PrefixConflict(info.preferred_prefix));
235 }
236
237 self.reserved_prefixes.insert(info.preferred_prefix.clone());
238 self.custom_namespaces.insert(info.uri.clone(), info);
239 Ok(())
240 }
241
242 pub fn detect_version(&self, namespace_uri: &str) -> Option<ERNVersion> {
244 match namespace_uri {
245 "http://ddex.net/xml/ern/382" => Some(ERNVersion::V3_8_2),
246 "http://ddex.net/xml/ern/42" => Some(ERNVersion::V4_2),
247 "http://ddex.net/xml/ern/43" => Some(ERNVersion::V4_3),
248 _ => None,
249 }
250 }
251
252 pub fn get_version_namespaces(&self, version: &ERNVersion) -> Vec<String> {
254 let mut namespaces = vec![];
255
256 match version {
258 ERNVersion::V3_8_2 => namespaces.push("http://ddex.net/xml/ern/382".to_string()),
259 ERNVersion::V4_2 => namespaces.push("http://ddex.net/xml/ern/42".to_string()),
260 ERNVersion::V4_3 => namespaces.push("http://ddex.net/xml/ern/43".to_string()),
261 }
262
263 namespaces.push("http://ddex.net/xml/avs".to_string());
265 namespaces.push("http://www.w3.org/2001/XMLSchema-instance".to_string());
266
267 namespaces
268 }
269
270 pub fn get_preferred_prefix(&self, uri: &str) -> Option<&str> {
272 self.default_prefixes
273 .get(uri)
274 .map(|s| s.as_str())
275 .or_else(|| {
276 self.custom_namespaces
277 .get(uri)
278 .map(|info| info.preferred_prefix.as_str())
279 })
280 }
281
282 pub fn get_namespace_info(&self, uri: &str) -> Option<&NamespaceInfo> {
284 self.namespaces
285 .get(uri)
286 .or_else(|| self.custom_namespaces.get(uri))
287 }
288
289 pub fn is_prefix_reserved(&self, prefix: &str) -> bool {
291 self.reserved_prefixes.contains(prefix)
292 }
293
294 pub fn generate_unique_prefix(&self, base_prefix: &str) -> String {
296 if !self.is_prefix_reserved(base_prefix) {
297 return base_prefix.to_string();
298 }
299
300 let mut counter = 1;
301 loop {
302 let candidate = format!("{}{}", base_prefix, counter);
303 if !self.is_prefix_reserved(&candidate) {
304 return candidate;
305 }
306 counter += 1;
307 }
308 }
309
310 pub fn resolve_prefix_conflict(
312 &self,
313 _uri: &str,
314 existing_prefix: &str,
315 new_prefix: &str,
316 strategy: ConflictResolution,
317 ) -> Result<String, NamespaceError> {
318 match strategy {
319 ConflictResolution::PreferFirst => Ok(existing_prefix.to_string()),
320 ConflictResolution::PreferLatest => Ok(new_prefix.to_string()),
321 ConflictResolution::GenerateUnique => Ok(self.generate_unique_prefix(new_prefix)),
322 ConflictResolution::Error => {
323 Err(NamespaceError::PrefixConflict(new_prefix.to_string()))
324 }
325 }
326 }
327
328 pub fn create_minimal_declarations(
330 &self,
331 used_namespaces: &[String],
332 ) -> IndexMap<String, String> {
333 let mut declarations = IndexMap::new();
334
335 for uri in used_namespaces {
336 if let Some(prefix) = self.get_preferred_prefix(uri) {
337 declarations.insert(prefix.to_string(), uri.clone());
338 }
339 }
340
341 declarations
342 }
343
344 pub fn validate_declarations(
346 &self,
347 declarations: &IndexMap<String, String>,
348 ) -> Vec<NamespaceWarning> {
349 let mut warnings = Vec::new();
350
351 for (prefix, uri) in declarations {
352 if let Some(info) = self.get_namespace_info(uri) {
353 if prefix != &info.preferred_prefix && !info.alternative_prefixes.contains(prefix) {
355 warnings.push(NamespaceWarning::NonStandardPrefix {
356 uri: uri.clone(),
357 used_prefix: prefix.clone(),
358 preferred_prefix: info.preferred_prefix.clone(),
359 });
360 }
361 } else {
362 warnings.push(NamespaceWarning::UnknownNamespace {
364 uri: uri.clone(),
365 prefix: prefix.clone(),
366 });
367 }
368 }
369
370 warnings
371 }
372
373 pub fn get_namespaces_by_standard(&self, standard: &DDEXStandard) -> Vec<&NamespaceInfo> {
375 self.namespaces
376 .values()
377 .chain(self.custom_namespaces.values())
378 .filter(|info| &info.standard == standard)
379 .collect()
380 }
381}
382
383impl NamespaceScope {
384 pub fn new() -> Self {
386 Self {
387 declarations: IndexMap::new(),
388 parent: None,
389 depth: 0,
390 }
391 }
392
393 pub fn new_child(&self) -> Self {
395 Self {
396 declarations: IndexMap::new(),
397 parent: Some(Box::new(self.clone())),
398 depth: self.depth + 1,
399 }
400 }
401
402 pub fn declare_namespace(&mut self, prefix: String, uri: String) {
404 self.declarations.insert(prefix, uri);
405 }
406
407 pub fn resolve_prefix(&self, prefix: &str) -> Option<String> {
409 if let Some(uri) = self.declarations.get(prefix) {
410 Some(uri.clone())
411 } else if let Some(parent) = &self.parent {
412 parent.resolve_prefix(prefix)
413 } else {
414 None
415 }
416 }
417
418 pub fn get_all_declarations(&self) -> IndexMap<String, String> {
420 let mut all_declarations = IndexMap::new();
421
422 if let Some(parent) = &self.parent {
424 all_declarations = parent.get_all_declarations();
425 }
426
427 for (prefix, uri) in &self.declarations {
429 all_declarations.insert(prefix.clone(), uri.clone());
430 }
431
432 all_declarations
433 }
434
435 pub fn is_namespace_declared(&self, uri: &str) -> bool {
437 self.declarations
438 .values()
439 .any(|declared_uri| declared_uri == uri)
440 || self
441 .parent
442 .as_ref()
443 .is_some_and(|parent| parent.is_namespace_declared(uri))
444 }
445
446 pub fn find_prefix_for_uri(&self, uri: &str) -> Option<String> {
448 for (prefix, declared_uri) in &self.declarations {
449 if declared_uri == uri {
450 return Some(prefix.clone());
451 }
452 }
453
454 if let Some(parent) = &self.parent {
455 parent.find_prefix_for_uri(uri)
456 } else {
457 None
458 }
459 }
460}
461
462#[derive(Debug, Clone, PartialEq)]
464pub enum NamespaceError {
465 UriConflict(String),
467 PrefixConflict(String),
469 InvalidUri(String),
471 CircularDependency(Vec<String>),
473}
474
475impl std::fmt::Display for NamespaceError {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 match self {
478 NamespaceError::UriConflict(uri) => write!(f, "Namespace URI conflict: {}", uri),
479 NamespaceError::PrefixConflict(prefix) => write!(f, "Prefix conflict: {}", prefix),
480 NamespaceError::InvalidUri(uri) => write!(f, "Invalid namespace URI: {}", uri),
481 NamespaceError::CircularDependency(chain) => {
482 write!(f, "Circular namespace dependency: {}", chain.join(" -> "))
483 }
484 }
485 }
486}
487
488impl std::error::Error for NamespaceError {}
489
490#[derive(Debug, Clone, PartialEq)]
492pub enum NamespaceWarning {
493 NonStandardPrefix {
495 uri: String,
496 used_prefix: String,
497 preferred_prefix: String,
498 },
499 UnknownNamespace { uri: String, prefix: String },
501 RedundantDeclaration { uri: String, prefix: String },
503}
504
505impl std::fmt::Display for NamespaceWarning {
506 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507 match self {
508 NamespaceWarning::NonStandardPrefix {
509 uri,
510 used_prefix,
511 preferred_prefix,
512 } => {
513 write!(
514 f,
515 "Non-standard prefix '{}' for namespace '{}', prefer '{}'",
516 used_prefix, uri, preferred_prefix
517 )
518 }
519 NamespaceWarning::UnknownNamespace { uri, prefix } => {
520 write!(f, "Unknown namespace '{}' with prefix '{}'", uri, prefix)
521 }
522 NamespaceWarning::RedundantDeclaration { uri, prefix } => {
523 write!(
524 f,
525 "Redundant declaration of namespace '{}' with prefix '{}'",
526 uri, prefix
527 )
528 }
529 }
530 }
531}
532
533impl Default for NamespaceRegistry {
534 fn default() -> Self {
535 Self::new()
536 }
537}
538
539impl Default for NamespaceScope {
540 fn default() -> Self {
541 Self::new()
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use super::*;
548
549 #[test]
550 fn test_namespace_registry_creation() {
551 let registry = NamespaceRegistry::new();
552 assert!(registry
553 .get_preferred_prefix("http://ddex.net/xml/ern/43")
554 .is_some());
555 assert_eq!(
556 registry.get_preferred_prefix("http://ddex.net/xml/ern/43"),
557 Some("ern")
558 );
559 }
560
561 #[test]
562 fn test_version_detection() {
563 let registry = NamespaceRegistry::new();
564 assert_eq!(
565 registry.detect_version("http://ddex.net/xml/ern/382"),
566 Some(ERNVersion::V3_8_2)
567 );
568 assert_eq!(
569 registry.detect_version("http://ddex.net/xml/ern/42"),
570 Some(ERNVersion::V4_2)
571 );
572 assert_eq!(
573 registry.detect_version("http://ddex.net/xml/ern/43"),
574 Some(ERNVersion::V4_3)
575 );
576 assert_eq!(
577 registry.detect_version("http://unknown.com/namespace"),
578 None
579 );
580 }
581
582 #[test]
583 fn test_custom_namespace_registration() {
584 let mut registry = NamespaceRegistry::new();
585
586 let custom_ns = NamespaceInfo {
587 uri: "http://example.com/custom".to_string(),
588 preferred_prefix: "ex".to_string(),
589 alternative_prefixes: vec!["example".to_string()],
590 standard: DDEXStandard::Custom("Example".to_string()),
591 version: None,
592 required: false,
593 };
594
595 assert!(registry.register_custom_namespace(custom_ns).is_ok());
596 assert_eq!(
597 registry.get_preferred_prefix("http://example.com/custom"),
598 Some("ex")
599 );
600 }
601
602 #[test]
603 fn test_prefix_conflict_detection() {
604 let mut registry = NamespaceRegistry::new();
605
606 let conflicting_ns = NamespaceInfo {
607 uri: "http://example.com/conflict".to_string(),
608 preferred_prefix: "ern".to_string(), alternative_prefixes: vec![],
610 standard: DDEXStandard::Custom("Conflict".to_string()),
611 version: None,
612 required: false,
613 };
614
615 assert!(matches!(
616 registry.register_custom_namespace(conflicting_ns),
617 Err(NamespaceError::PrefixConflict(_))
618 ));
619 }
620
621 #[test]
622 fn test_namespace_scope() {
623 let mut root_scope = NamespaceScope::new();
624 root_scope.declare_namespace("ern".to_string(), "http://ddex.net/xml/ern/43".to_string());
625
626 let mut child_scope = root_scope.new_child();
627 child_scope.declare_namespace("avs".to_string(), "http://ddex.net/xml/avs".to_string());
628
629 assert_eq!(
631 child_scope.resolve_prefix("ern"),
632 Some("http://ddex.net/xml/ern/43".to_string())
633 );
634 assert_eq!(
635 child_scope.resolve_prefix("avs"),
636 Some("http://ddex.net/xml/avs".to_string())
637 );
638
639 assert_eq!(root_scope.resolve_prefix("avs"), None);
641 }
642
643 #[test]
644 fn test_unique_prefix_generation() {
645 let mut registry = NamespaceRegistry::new();
646
647 registry.reserved_prefixes.insert("test".to_string());
649 registry.reserved_prefixes.insert("test1".to_string());
650
651 let unique = registry.generate_unique_prefix("test");
652 assert_eq!(unique, "test2");
653 }
654
655 #[test]
656 fn test_minimal_declarations() {
657 let registry = NamespaceRegistry::new();
658 let used_namespaces = vec![
659 "http://ddex.net/xml/ern/43".to_string(),
660 "http://ddex.net/xml/avs".to_string(),
661 ];
662
663 let declarations = registry.create_minimal_declarations(&used_namespaces);
664 assert_eq!(
665 declarations.get("ern"),
666 Some(&"http://ddex.net/xml/ern/43".to_string())
667 );
668 assert_eq!(
669 declarations.get("avs"),
670 Some(&"http://ddex.net/xml/avs".to_string())
671 );
672 }
673}