1use indexmap::{IndexMap, IndexSet};
8use crate::models::versions::ERNVersion;
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.insert(info.uri.clone(), info.preferred_prefix.clone());
214 self.reserved_prefixes.insert(info.preferred_prefix.clone());
215
216 for alt_prefix in &info.alternative_prefixes {
217 self.reserved_prefixes.insert(alt_prefix.clone());
218 }
219
220 self.namespaces.insert(info.uri.clone(), info);
221 }
222
223 pub fn register_custom_namespace(&mut self, info: NamespaceInfo) -> Result<(), NamespaceError> {
225 if self.namespaces.contains_key(&info.uri) || self.custom_namespaces.contains_key(&info.uri) {
227 return Err(NamespaceError::UriConflict(info.uri));
228 }
229
230 if self.is_prefix_reserved(&info.preferred_prefix) {
232 return Err(NamespaceError::PrefixConflict(info.preferred_prefix));
233 }
234
235 self.reserved_prefixes.insert(info.preferred_prefix.clone());
236 self.custom_namespaces.insert(info.uri.clone(), info);
237 Ok(())
238 }
239
240 pub fn detect_version(&self, namespace_uri: &str) -> Option<ERNVersion> {
242 match namespace_uri {
243 "http://ddex.net/xml/ern/382" => Some(ERNVersion::V3_8_2),
244 "http://ddex.net/xml/ern/42" => Some(ERNVersion::V4_2),
245 "http://ddex.net/xml/ern/43" => Some(ERNVersion::V4_3),
246 _ => None,
247 }
248 }
249
250 pub fn get_version_namespaces(&self, version: &ERNVersion) -> Vec<String> {
252 let mut namespaces = vec![];
253
254 match version {
256 ERNVersion::V3_8_2 => namespaces.push("http://ddex.net/xml/ern/382".to_string()),
257 ERNVersion::V4_2 => namespaces.push("http://ddex.net/xml/ern/42".to_string()),
258 ERNVersion::V4_3 => namespaces.push("http://ddex.net/xml/ern/43".to_string()),
259 }
260
261 namespaces.push("http://ddex.net/xml/avs".to_string());
263 namespaces.push("http://www.w3.org/2001/XMLSchema-instance".to_string());
264
265 namespaces
266 }
267
268 pub fn get_preferred_prefix(&self, uri: &str) -> Option<&str> {
270 self.default_prefixes.get(uri).map(|s| s.as_str())
271 }
272
273 pub fn get_namespace_info(&self, uri: &str) -> Option<&NamespaceInfo> {
275 self.namespaces.get(uri).or_else(|| self.custom_namespaces.get(uri))
276 }
277
278 pub fn is_prefix_reserved(&self, prefix: &str) -> bool {
280 self.reserved_prefixes.contains(prefix)
281 }
282
283 pub fn generate_unique_prefix(&self, base_prefix: &str) -> String {
285 if !self.is_prefix_reserved(base_prefix) {
286 return base_prefix.to_string();
287 }
288
289 let mut counter = 1;
290 loop {
291 let candidate = format!("{}{}", base_prefix, counter);
292 if !self.is_prefix_reserved(&candidate) {
293 return candidate;
294 }
295 counter += 1;
296 }
297 }
298
299 pub fn resolve_prefix_conflict(
301 &self,
302 _uri: &str,
303 existing_prefix: &str,
304 new_prefix: &str,
305 strategy: ConflictResolution,
306 ) -> Result<String, NamespaceError> {
307 match strategy {
308 ConflictResolution::PreferFirst => Ok(existing_prefix.to_string()),
309 ConflictResolution::PreferLatest => Ok(new_prefix.to_string()),
310 ConflictResolution::GenerateUnique => Ok(self.generate_unique_prefix(new_prefix)),
311 ConflictResolution::Error => Err(NamespaceError::PrefixConflict(new_prefix.to_string())),
312 }
313 }
314
315 pub fn create_minimal_declarations(&self, used_namespaces: &[String]) -> IndexMap<String, String> {
317 let mut declarations = IndexMap::new();
318
319 for uri in used_namespaces {
320 if let Some(prefix) = self.get_preferred_prefix(uri) {
321 declarations.insert(prefix.to_string(), uri.clone());
322 }
323 }
324
325 declarations
326 }
327
328 pub fn validate_declarations(&self, declarations: &IndexMap<String, String>) -> Vec<NamespaceWarning> {
330 let mut warnings = Vec::new();
331
332 for (prefix, uri) in declarations {
333 if let Some(info) = self.get_namespace_info(uri) {
334 if prefix != &info.preferred_prefix && !info.alternative_prefixes.contains(prefix) {
336 warnings.push(NamespaceWarning::NonStandardPrefix {
337 uri: uri.clone(),
338 used_prefix: prefix.clone(),
339 preferred_prefix: info.preferred_prefix.clone(),
340 });
341 }
342 } else {
343 warnings.push(NamespaceWarning::UnknownNamespace {
345 uri: uri.clone(),
346 prefix: prefix.clone(),
347 });
348 }
349 }
350
351 warnings
352 }
353
354 pub fn get_namespaces_by_standard(&self, standard: &DDEXStandard) -> Vec<&NamespaceInfo> {
356 self.namespaces
357 .values()
358 .chain(self.custom_namespaces.values())
359 .filter(|info| &info.standard == standard)
360 .collect()
361 }
362}
363
364impl NamespaceScope {
365 pub fn new() -> Self {
367 Self {
368 declarations: IndexMap::new(),
369 parent: None,
370 depth: 0,
371 }
372 }
373
374 pub fn new_child(&self) -> Self {
376 Self {
377 declarations: IndexMap::new(),
378 parent: Some(Box::new(self.clone())),
379 depth: self.depth + 1,
380 }
381 }
382
383 pub fn declare_namespace(&mut self, prefix: String, uri: String) {
385 self.declarations.insert(prefix, uri);
386 }
387
388 pub fn resolve_prefix(&self, prefix: &str) -> Option<String> {
390 if let Some(uri) = self.declarations.get(prefix) {
391 Some(uri.clone())
392 } else if let Some(parent) = &self.parent {
393 parent.resolve_prefix(prefix)
394 } else {
395 None
396 }
397 }
398
399 pub fn get_all_declarations(&self) -> IndexMap<String, String> {
401 let mut all_declarations = IndexMap::new();
402
403 if let Some(parent) = &self.parent {
405 all_declarations = parent.get_all_declarations();
406 }
407
408 for (prefix, uri) in &self.declarations {
410 all_declarations.insert(prefix.clone(), uri.clone());
411 }
412
413 all_declarations
414 }
415
416 pub fn is_namespace_declared(&self, uri: &str) -> bool {
418 self.declarations.values().any(|declared_uri| declared_uri == uri) ||
419 self.parent.as_ref().map_or(false, |parent| parent.is_namespace_declared(uri))
420 }
421
422 pub fn find_prefix_for_uri(&self, uri: &str) -> Option<String> {
424 for (prefix, declared_uri) in &self.declarations {
425 if declared_uri == uri {
426 return Some(prefix.clone());
427 }
428 }
429
430 if let Some(parent) = &self.parent {
431 parent.find_prefix_for_uri(uri)
432 } else {
433 None
434 }
435 }
436}
437
438#[derive(Debug, Clone, PartialEq)]
440pub enum NamespaceError {
441 UriConflict(String),
443 PrefixConflict(String),
445 InvalidUri(String),
447 CircularDependency(Vec<String>),
449}
450
451impl std::fmt::Display for NamespaceError {
452 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
453 match self {
454 NamespaceError::UriConflict(uri) => write!(f, "Namespace URI conflict: {}", uri),
455 NamespaceError::PrefixConflict(prefix) => write!(f, "Prefix conflict: {}", prefix),
456 NamespaceError::InvalidUri(uri) => write!(f, "Invalid namespace URI: {}", uri),
457 NamespaceError::CircularDependency(chain) => {
458 write!(f, "Circular namespace dependency: {}", chain.join(" -> "))
459 }
460 }
461 }
462}
463
464impl std::error::Error for NamespaceError {}
465
466#[derive(Debug, Clone, PartialEq)]
468pub enum NamespaceWarning {
469 NonStandardPrefix {
471 uri: String,
472 used_prefix: String,
473 preferred_prefix: String,
474 },
475 UnknownNamespace {
477 uri: String,
478 prefix: String,
479 },
480 RedundantDeclaration {
482 uri: String,
483 prefix: String,
484 },
485}
486
487impl std::fmt::Display for NamespaceWarning {
488 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
489 match self {
490 NamespaceWarning::NonStandardPrefix { uri, used_prefix, preferred_prefix } => {
491 write!(f, "Non-standard prefix '{}' for namespace '{}', prefer '{}'",
492 used_prefix, uri, preferred_prefix)
493 },
494 NamespaceWarning::UnknownNamespace { uri, prefix } => {
495 write!(f, "Unknown namespace '{}' with prefix '{}'", uri, prefix)
496 },
497 NamespaceWarning::RedundantDeclaration { uri, prefix } => {
498 write!(f, "Redundant declaration of namespace '{}' with prefix '{}'", uri, prefix)
499 },
500 }
501 }
502}
503
504impl Default for NamespaceRegistry {
505 fn default() -> Self {
506 Self::new()
507 }
508}
509
510impl Default for NamespaceScope {
511 fn default() -> Self {
512 Self::new()
513 }
514}
515
516#[cfg(test)]
517mod tests {
518 use super::*;
519
520 #[test]
521 fn test_namespace_registry_creation() {
522 let registry = NamespaceRegistry::new();
523 assert!(registry.get_preferred_prefix("http://ddex.net/xml/ern/43").is_some());
524 assert_eq!(registry.get_preferred_prefix("http://ddex.net/xml/ern/43"), Some("ern"));
525 }
526
527 #[test]
528 fn test_version_detection() {
529 let registry = NamespaceRegistry::new();
530 assert_eq!(registry.detect_version("http://ddex.net/xml/ern/382"), Some(ERNVersion::V3_8_2));
531 assert_eq!(registry.detect_version("http://ddex.net/xml/ern/42"), Some(ERNVersion::V4_2));
532 assert_eq!(registry.detect_version("http://ddex.net/xml/ern/43"), Some(ERNVersion::V4_3));
533 assert_eq!(registry.detect_version("http://unknown.com/namespace"), None);
534 }
535
536 #[test]
537 fn test_custom_namespace_registration() {
538 let mut registry = NamespaceRegistry::new();
539
540 let custom_ns = NamespaceInfo {
541 uri: "http://example.com/custom".to_string(),
542 preferred_prefix: "ex".to_string(),
543 alternative_prefixes: vec!["example".to_string()],
544 standard: DDEXStandard::Custom("Example".to_string()),
545 version: None,
546 required: false,
547 };
548
549 assert!(registry.register_custom_namespace(custom_ns).is_ok());
550 assert_eq!(registry.get_preferred_prefix("http://example.com/custom"), Some("ex"));
551 }
552
553 #[test]
554 fn test_prefix_conflict_detection() {
555 let mut registry = NamespaceRegistry::new();
556
557 let conflicting_ns = NamespaceInfo {
558 uri: "http://example.com/conflict".to_string(),
559 preferred_prefix: "ern".to_string(), alternative_prefixes: vec![],
561 standard: DDEXStandard::Custom("Conflict".to_string()),
562 version: None,
563 required: false,
564 };
565
566 assert!(matches!(registry.register_custom_namespace(conflicting_ns), Err(NamespaceError::PrefixConflict(_))));
567 }
568
569 #[test]
570 fn test_namespace_scope() {
571 let mut root_scope = NamespaceScope::new();
572 root_scope.declare_namespace("ern".to_string(), "http://ddex.net/xml/ern/43".to_string());
573
574 let mut child_scope = root_scope.new_child();
575 child_scope.declare_namespace("avs".to_string(), "http://ddex.net/xml/avs".to_string());
576
577 assert_eq!(child_scope.resolve_prefix("ern"), Some("http://ddex.net/xml/ern/43".to_string()));
579 assert_eq!(child_scope.resolve_prefix("avs"), Some("http://ddex.net/xml/avs".to_string()));
580
581 assert_eq!(root_scope.resolve_prefix("avs"), None);
583 }
584
585 #[test]
586 fn test_unique_prefix_generation() {
587 let mut registry = NamespaceRegistry::new();
588
589 registry.reserved_prefixes.insert("test".to_string());
591 registry.reserved_prefixes.insert("test1".to_string());
592
593 let unique = registry.generate_unique_prefix("test");
594 assert_eq!(unique, "test2");
595 }
596
597 #[test]
598 fn test_minimal_declarations() {
599 let registry = NamespaceRegistry::new();
600 let used_namespaces = vec![
601 "http://ddex.net/xml/ern/43".to_string(),
602 "http://ddex.net/xml/avs".to_string(),
603 ];
604
605 let declarations = registry.create_minimal_declarations(&used_namespaces);
606 assert_eq!(declarations.get("ern"), Some(&"http://ddex.net/xml/ern/43".to_string()));
607 assert_eq!(declarations.get("avs"), Some(&"http://ddex.net/xml/avs".to_string()));
608 }
609}