ddex-builder 0.4.5

Deterministic DDEX XML builder with smart normalization
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
//! Perfect Fidelity Engine for DDEX Builder
//!
//! This module provides comprehensive fidelity preservation features for the DDEX Builder,
//! ensuring perfect round-trip compatibility with the DDEX Parser and maintaining
//! semantic XML reproduction while maintaining structure and content.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;

/// Fidelity configuration for perfect XML preservation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FidelityConfig {
    /// Preservation level for different XML components
    pub preservation_level: PreservationLevel,
    /// Comment preservation settings
    pub comment_preservation: CommentPreservationConfig,
    /// Processing instruction preservation
    pub processing_instruction_preservation: bool,
    /// Extension element preservation settings
    pub extension_preservation: ExtensionPreservationConfig,
    /// Attribute preservation settings
    pub attribute_preservation: AttributePreservationConfig,
    /// Namespace preservation settings
    pub namespace_preservation: NamespacePreservationConfig,
    /// Whitespace preservation settings
    pub whitespace_preservation: WhitespacePreservationConfig,
}

impl Default for FidelityConfig {
    fn default() -> Self {
        Self {
            preservation_level: PreservationLevel::Standard,
            comment_preservation: CommentPreservationConfig::default(),
            processing_instruction_preservation: false,
            extension_preservation: ExtensionPreservationConfig::default(),
            attribute_preservation: AttributePreservationConfig::default(),
            namespace_preservation: NamespacePreservationConfig::default(),
            whitespace_preservation: WhitespacePreservationConfig::default(),
        }
    }
}

/// Preservation levels for different use cases
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PreservationLevel {
    /// Basic preservation (DDEX elements only)
    Basic,
    /// Standard preservation (DDEX + known extensions)
    Standard,
    /// Perfect preservation (everything including comments, whitespace)
    Perfect,
    /// Custom preservation with fine-grained control
    Custom,
}

/// Comment preservation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommentPreservationConfig {
    /// Whether to preserve comments at all
    pub enabled: bool,
    /// Preserve document-level comments (outside root element)
    pub preserve_document_comments: bool,
    /// Preserve element-level comments (between elements)
    pub preserve_element_comments: bool,
    /// Preserve inline comments (within elements)
    pub preserve_inline_comments: bool,
    /// Preserve comment positioning relative to elements
    pub preserve_comment_positioning: bool,
    /// Maximum comment length to preserve (0 = unlimited)
    pub max_comment_length: usize,
}

impl Default for CommentPreservationConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            preserve_document_comments: true,
            preserve_element_comments: true,
            preserve_inline_comments: false,
            preserve_comment_positioning: true,
            max_comment_length: 0,
        }
    }
}

/// Extension preservation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionPreservationConfig {
    /// Whether to preserve extension elements
    pub enabled: bool,
    /// Known extension namespaces to always preserve
    pub known_extensions: Vec<String>,
    /// Unknown extension handling
    pub unknown_extension_handling: UnknownExtensionHandling,
    /// Preserve extension attributes
    pub preserve_extension_attributes: bool,
    /// Extension validation rules
    pub extension_validation: ExtensionValidationConfig,
}

impl Default for ExtensionPreservationConfig {
    fn default() -> Self {
        Self {
            enabled: true,
            known_extensions: vec![
                "http://spotify.com/ddex".to_string(),
                "http://apple.com/ddex".to_string(),
                "http://youtube.com/ddex".to_string(),
                "http://amazon.com/ddex".to_string(),
            ],
            unknown_extension_handling: UnknownExtensionHandling::Preserve,
            preserve_extension_attributes: true,
            extension_validation: ExtensionValidationConfig::default(),
        }
    }
}

/// How to handle unknown extensions
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnknownExtensionHandling {
    /// Preserve unknown extensions as-is
    Preserve,
    /// Drop unknown extensions
    Drop,
    /// Validate against schema and preserve if valid
    ValidateAndPreserve,
    /// Convert to generic extension format
    Generalize,
}

/// Extension validation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionValidationConfig {
    /// Validate extension URIs
    pub validate_uris: bool,
    /// Validate extension schema compliance
    pub validate_schema: bool,
    /// Maximum extension nesting depth
    pub max_nesting_depth: usize,
    /// Maximum extension element count per document
    pub max_extension_count: usize,
}

impl Default for ExtensionValidationConfig {
    fn default() -> Self {
        Self {
            validate_uris: true,
            validate_schema: false,
            max_nesting_depth: 10,
            max_extension_count: 1000,
        }
    }
}

/// Attribute preservation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributePreservationConfig {
    /// Preserve original attribute ordering
    pub preserve_ordering: bool,
    /// Preserve unknown attributes
    pub preserve_unknown_attributes: bool,
    /// Preserve empty attributes
    pub preserve_empty_attributes: bool,
    /// Attribute normalization rules
    pub normalization: AttributeNormalizationConfig,
    /// Attribute validation rules
    pub validation: AttributeValidationConfig,
}

impl Default for AttributePreservationConfig {
    fn default() -> Self {
        Self {
            preserve_ordering: false,
            preserve_unknown_attributes: true,
            preserve_empty_attributes: false,
            normalization: AttributeNormalizationConfig::default(),
            validation: AttributeValidationConfig::default(),
        }
    }
}

/// Attribute normalization configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeNormalizationConfig {
    /// Normalize boolean attributes (true/false vs 1/0)
    pub normalize_booleans: bool,
    /// Normalize numeric formats
    pub normalize_numbers: bool,
    /// Normalize date/time formats
    pub normalize_datetime: bool,
    /// Normalize whitespace in attribute values
    pub normalize_whitespace: bool,
}

impl Default for AttributeNormalizationConfig {
    fn default() -> Self {
        Self {
            normalize_booleans: true,
            normalize_numbers: true,
            normalize_datetime: true,
            normalize_whitespace: true,
        }
    }
}

/// Attribute validation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeValidationConfig {
    /// Validate attribute value formats
    pub validate_formats: bool,
    /// Validate attribute value constraints
    pub validate_constraints: bool,
    /// Custom validation rules per attribute
    pub custom_rules: HashMap<String, AttributeValidationRule>,
}

impl Default for AttributeValidationConfig {
    fn default() -> Self {
        Self {
            validate_formats: true,
            validate_constraints: true,
            custom_rules: HashMap::new(),
        }
    }
}

/// Custom attribute validation rule
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttributeValidationRule {
    /// Regular expression pattern for validation
    pub pattern: Option<String>,
    /// Minimum value (for numeric attributes)
    pub min_value: Option<f64>,
    /// Maximum value (for numeric attributes)
    pub max_value: Option<f64>,
    /// Allowed values (enum validation)
    pub allowed_values: Option<Vec<String>>,
    /// Custom validation message
    pub validation_message: Option<String>,
}

/// Namespace preservation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NamespacePreservationConfig {
    /// Preserve original namespace prefixes
    pub preserve_prefixes: bool,
    /// Preserve unused namespace declarations
    pub preserve_unused_declarations: bool,
    /// Namespace minimization strategy
    pub minimization_strategy: NamespaceMinimizationStrategy,
    /// Default namespace handling
    pub default_namespace_handling: DefaultNamespaceHandling,
}

impl Default for NamespacePreservationConfig {
    fn default() -> Self {
        Self {
            preserve_prefixes: false,
            preserve_unused_declarations: false,
            minimization_strategy: NamespaceMinimizationStrategy::Aggressive,
            default_namespace_handling: DefaultNamespaceHandling::Preserve,
        }
    }
}

/// Namespace minimization strategies
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum NamespaceMinimizationStrategy {
    /// No minimization (preserve all declarations)
    None,
    /// Conservative minimization (remove only clearly unused)
    Conservative,
    /// Aggressive minimization (minimize to essential declarations)
    Aggressive,
    /// Custom minimization based on rules
    Custom(NamespaceMinimizationRules),
}

/// Custom namespace minimization rules
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct NamespaceMinimizationRules {
    /// Namespaces to always preserve
    pub always_preserve: Vec<String>,
    /// Namespaces that can be safely removed if unused
    pub can_remove: Vec<String>,
    /// Preferred prefix mappings
    pub preferred_prefixes: HashMap<String, String>,
}

/// Default namespace handling
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DefaultNamespaceHandling {
    /// Preserve default namespace as-is
    Preserve,
    /// Remove default namespace if possible
    Remove,
    /// Convert to explicit prefix
    ConvertToPrefix,
}

/// Whitespace preservation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WhitespacePreservationConfig {
    /// Preserve significant whitespace
    pub preserve_significant: bool,
    /// Preserve formatting whitespace (indentation, etc.)
    pub preserve_formatting: bool,
    /// Normalize line endings
    pub normalize_line_endings: bool,
    /// Target line ending style
    pub line_ending_style: LineEndingStyle,
    /// Indentation style for formatted output
    pub indentation_style: IndentationStyle,
}

impl Default for WhitespacePreservationConfig {
    fn default() -> Self {
        Self {
            preserve_significant: true,
            preserve_formatting: false,
            normalize_line_endings: true,
            line_ending_style: LineEndingStyle::Unix,
            indentation_style: IndentationStyle::Spaces(2),
        }
    }
}

/// Line ending styles
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum LineEndingStyle {
    /// Unix style (\n)
    Unix,
    /// Windows style (\r\n)
    Windows,
    /// Classic Mac style (\r)
    Mac,
    /// Preserve original
    Preserve,
}

/// Indentation styles
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum IndentationStyle {
    /// Spaces with specified count
    Spaces(usize),
    /// Tabs
    Tabs,
    /// No indentation
    None,
}

/// Fidelity statistics collected during processing
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FidelityStatistics {
    /// Elements preserved by category
    pub elements_preserved: HashMap<String, usize>,
    /// Attributes preserved by category
    pub attributes_preserved: HashMap<String, usize>,
    /// Comments preserved
    pub comments_preserved: usize,
    /// Processing instructions preserved
    pub processing_instructions_preserved: usize,
    /// Extensions preserved by namespace
    pub extensions_preserved: HashMap<String, usize>,
    /// Namespaces processed
    pub namespaces_processed: usize,
    /// Fidelity processing time
    pub processing_time: Duration,
    /// Memory usage for fidelity features
    pub memory_usage: usize,
}

impl Default for FidelityStatistics {
    fn default() -> Self {
        Self {
            elements_preserved: HashMap::new(),
            attributes_preserved: HashMap::new(),
            comments_preserved: 0,
            processing_instructions_preserved: 0,
            extensions_preserved: HashMap::new(),
            namespaces_processed: 0,
            processing_time: Duration::ZERO,
            memory_usage: 0,
        }
    }
}

/// Fidelity processing result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FidelityResult {
    /// Whether fidelity processing was successful
    pub success: bool,
    /// Fidelity level achieved
    pub achieved_level: PreservationLevel,
    /// Processing statistics
    pub statistics: FidelityStatistics,
    /// Issues encountered during processing
    pub issues: Vec<FidelityIssue>,
    /// Recommendations for improving fidelity
    pub recommendations: Vec<String>,
}

/// Fidelity processing issue
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FidelityIssue {
    /// Issue severity
    pub severity: FidelitySeverity,
    /// Issue category
    pub category: String,
    /// Issue description
    pub message: String,
    /// XML path where issue occurred
    pub path: Option<String>,
    /// Suggested resolution
    pub suggestion: Option<String>,
}

/// Fidelity issue severity
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum FidelitySeverity {
    /// Critical issue that prevents fidelity
    Critical,
    /// Major issue that significantly impacts fidelity
    Major,
    /// Minor issue with minimal fidelity impact
    Minor,
    /// Informational message
    Info,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fidelity_config_default() {
        let config = FidelityConfig::default();
        assert_eq!(config.preservation_level, PreservationLevel::Standard);
        assert!(!config.comment_preservation.enabled);
        assert!(!config.processing_instruction_preservation);
        assert!(config.extension_preservation.enabled);
    }

    #[test]
    fn test_preservation_levels() {
        let basic = PreservationLevel::Basic;
        let standard = PreservationLevel::Standard;
        let perfect = PreservationLevel::Perfect;
        let custom = PreservationLevel::Custom;

        assert_ne!(basic, standard);
        assert_ne!(standard, perfect);
        assert_ne!(perfect, custom);
    }

    #[test]
    fn test_comment_preservation_config() {
        let mut config = CommentPreservationConfig::default();
        assert!(!config.enabled);

        config.enabled = true;
        assert!(config.enabled);
        assert!(config.preserve_document_comments);
        assert!(config.preserve_element_comments);
        assert!(!config.preserve_inline_comments);
    }

    #[test]
    fn test_extension_preservation() {
        let config = ExtensionPreservationConfig::default();
        assert!(config.enabled);
        assert!(!config.known_extensions.is_empty());
        assert_eq!(
            config.unknown_extension_handling,
            UnknownExtensionHandling::Preserve
        );
    }

    #[test]
    fn test_namespace_minimization() {
        let strategy = NamespaceMinimizationStrategy::Aggressive;
        match strategy {
            NamespaceMinimizationStrategy::Aggressive => {}
            _ => panic!("Expected Aggressive strategy"),
        }
    }
}