Skip to main content

xsd_schema/types/
validators.rs

1//! Type validators for XSD atomic types
2//!
3//! This module provides the `TypeValidator` trait and `ValidatorRegistry` for
4//! parsing, validating, and formatting XSD atomic type values.
5//!
6//! ## Design
7//!
8//! - `TypeValidator` trait defines the interface for type-specific validation
9//! - `ValidatorRegistry` provides lookup and registration of validators
10//! - Built-in validators cover all 19 primitive XSD types
11
12use std::collections::HashMap;
13use std::sync::Arc;
14
15use num_bigint::BigInt;
16use once_cell::sync::Lazy;
17use rust_decimal::Decimal;
18
19use super::facets::{normalize_whitespace, FacetSet, WhitespaceMode};
20use super::value::{
21    DateTimeValue, DateValue, DayTimeDurationValue, DurationValue, GDayValue, GMonthDayValue,
22    GMonthValue, GYearMonthValue, GYearValue, TimeValue, TimezoneOffset, XmlAtomicValue, XmlValue,
23    XmlValueKind, YearMonthDurationValue,
24};
25use super::{PrimitiveTypeCode, XmlTypeCode};
26use crate::error::FacetError;
27
28/// Error type for validation operations
29#[derive(Debug, Clone)]
30pub enum ValidationError {
31    /// Invalid lexical representation
32    InvalidLexical {
33        value: String,
34        type_name: &'static str,
35        message: String,
36    },
37    /// Facet constraint violation
38    FacetViolation(FacetError),
39    /// Type error (wrong type for operation)
40    TypeError {
41        expected: XmlTypeCode,
42        actual: XmlTypeCode,
43    },
44    /// Range error (value out of range for type)
45    RangeError {
46        value: String,
47        type_name: &'static str,
48    },
49}
50
51impl std::fmt::Display for ValidationError {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Self::InvalidLexical {
55                value,
56                type_name,
57                message,
58            } => {
59                write!(f, "Invalid {} value '{}': {}", type_name, value, message)
60            }
61            Self::FacetViolation(e) => write!(f, "{}", e),
62            Self::TypeError { expected, actual } => {
63                write!(f, "Type error: expected {:?}, got {:?}", expected, actual)
64            }
65            Self::RangeError { value, type_name } => {
66                write!(f, "Value '{}' out of range for type {}", value, type_name)
67            }
68        }
69    }
70}
71
72impl std::error::Error for ValidationError {}
73
74impl From<FacetError> for ValidationError {
75    fn from(e: FacetError) -> Self {
76        Self::FacetViolation(e)
77    }
78}
79
80/// Result type for validation operations
81pub type ValidationResult<T> = Result<T, ValidationError>;
82
83/// Build the `validate_enum_value_space` closure for a type whose instance
84/// value has been parsed. Each enumeration candidate lexical is normalized
85/// (whitespace=collapse), parsed with `$parse_fn` (returning `Result` or
86/// `Option` with `.ok()`), then compared against `$typed` using `$eq_fn`.
87///
88/// Used by Float / Double / Duration validators — §cvc-enumeration-valid
89/// compares in value space (not lexically) for these types.
90macro_rules! enum_matches_value_space {
91    ($typed:expr, $parse_fn:expr, $eq_fn:expr) => {
92        |s: &str| {
93            $parse_fn(&normalize_whitespace(s, WhitespaceMode::Collapse))
94                .ok()
95                .map(|e| $eq_fn($typed, e))
96                .unwrap_or(false)
97        }
98    };
99}
100
101/// Trait for XSD type validators
102///
103/// Validators are responsible for:
104/// - Parsing lexical values into typed values
105/// - Applying whitespace normalization
106/// - Validating against facets
107/// - Formatting typed values back to canonical lexical form
108pub trait TypeValidator: Send + Sync {
109    /// Get the type name (e.g., "string", "integer")
110    fn type_name(&self) -> &'static str;
111
112    /// Get the type code for this validator
113    fn type_code(&self) -> XmlTypeCode;
114
115    /// Get the primitive type from which this type derives
116    fn primitive_type(&self) -> PrimitiveTypeCode;
117
118    /// Get the whitespace normalization mode for this type
119    fn whitespace(&self) -> WhitespaceMode;
120
121    /// Parse and validate a lexical value
122    fn validate(&self, value: &str) -> ValidationResult<XmlValue>;
123
124    /// Parse a value and apply facets
125    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue>;
126
127    /// Check if a facet is applicable to this type
128    fn facet_applicable(&self, facet: &str) -> bool;
129}
130
131/// Registry of type validators
132pub struct ValidatorRegistry {
133    /// Validators by type name
134    validators: HashMap<&'static str, Arc<dyn TypeValidator>>,
135    /// Validators by type code
136    by_code: HashMap<XmlTypeCode, Arc<dyn TypeValidator>>,
137}
138
139impl ValidatorRegistry {
140    /// Create a new registry with all built-in validators
141    pub fn new() -> Self {
142        let mut registry = Self {
143            validators: HashMap::new(),
144            by_code: HashMap::new(),
145        };
146
147        // Register all primitive type validators
148        registry.register(Arc::new(StringValidator));
149        registry.register(Arc::new(BooleanValidator));
150        registry.register(Arc::new(DecimalValidator));
151        registry.register(Arc::new(FloatValidator));
152        registry.register(Arc::new(DoubleValidator));
153        registry.register(Arc::new(IntegerValidator));
154        registry.register(Arc::new(DurationValidator));
155        registry.register(Arc::new(DateTimeValidator));
156        registry.register(Arc::new(DateValidator));
157        registry.register(Arc::new(TimeValidator));
158        registry.register(Arc::new(GYearMonthValidator));
159        registry.register(Arc::new(GYearValidator));
160        registry.register(Arc::new(GMonthDayValidator));
161        registry.register(Arc::new(GDayValidator));
162        registry.register(Arc::new(GMonthValidator));
163        registry.register(Arc::new(HexBinaryValidator));
164        registry.register(Arc::new(Base64BinaryValidator));
165        registry.register(Arc::new(AnyUriValidator));
166
167        // Register derived string validators
168        registry.register(Arc::new(NormalizedStringValidator));
169        registry.register(Arc::new(TokenValidator));
170        registry.register(Arc::new(LanguageValidator));
171        registry.register(Arc::new(NmTokenValidator));
172        registry.register(Arc::new(NameValidator));
173        registry.register(Arc::new(NCNameValidator));
174        registry.register(Arc::new(IdValidator));
175        registry.register(Arc::new(IdRefValidator));
176        registry.register(Arc::new(EntityValidator));
177
178        // Register integer hierarchy validators
179        registry.register(Arc::new(LongValidator));
180        registry.register(Arc::new(IntValidator));
181        registry.register(Arc::new(ShortValidator));
182        registry.register(Arc::new(ByteValidator));
183        registry.register(Arc::new(NonNegativeIntegerValidator));
184        registry.register(Arc::new(PositiveIntegerValidator));
185        registry.register(Arc::new(NonPositiveIntegerValidator));
186        registry.register(Arc::new(NegativeIntegerValidator));
187        registry.register(Arc::new(UnsignedLongValidator));
188        registry.register(Arc::new(UnsignedIntValidator));
189        registry.register(Arc::new(UnsignedShortValidator));
190        registry.register(Arc::new(UnsignedByteValidator));
191
192        // Register QName and NOTATION validators
193        registry.register(Arc::new(QNameValidator));
194        registry.register(Arc::new(NotationValidator));
195
196        // Register list type validators
197        registry.register(Arc::new(NmTokensValidator));
198        registry.register(Arc::new(IdRefsValidator));
199        registry.register(Arc::new(EntitiesValidator));
200
201        // Register XSD 1.1 validators
202        registry.register(Arc::new(YearMonthDurationValidator));
203        registry.register(Arc::new(DayTimeDurationValidator));
204        registry.register(Arc::new(DateTimeStampValidator));
205
206        registry
207    }
208
209    /// Register a validator
210    pub fn register(&mut self, validator: Arc<dyn TypeValidator>) {
211        let name = validator.type_name();
212        let code = validator.type_code();
213        self.validators.insert(name, validator.clone());
214        self.by_code.insert(code, validator);
215    }
216
217    /// Get a validator by type name
218    pub fn get_by_name(&self, name: &str) -> Option<&dyn TypeValidator> {
219        self.validators.get(name).map(|v| v.as_ref())
220    }
221
222    /// Get a validator by type code
223    pub fn get_by_code(&self, code: XmlTypeCode) -> Option<&dyn TypeValidator> {
224        self.by_code.get(&code).map(|v| v.as_ref())
225    }
226
227    /// Validate a value using the appropriate validator
228    pub fn validate(&self, type_code: XmlTypeCode, value: &str) -> ValidationResult<XmlValue> {
229        match self.get_by_code(type_code) {
230            Some(validator) => validator.validate(value),
231            None => Err(ValidationError::InvalidLexical {
232                value: value.to_string(),
233                type_name: "unknown",
234                message: format!("No validator for type code {:?}", type_code),
235            }),
236        }
237    }
238}
239
240impl Default for ValidatorRegistry {
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246/// Global validator registry instance.
247///
248/// This is a lazily-initialized singleton containing all built-in type validators.
249/// Use this instead of creating new `ValidatorRegistry` instances to avoid
250/// the overhead of re-registering 50+ validators on each call.
251///
252/// # Example
253///
254/// ```ignore
255/// use crate::types::validators::VALIDATOR_REGISTRY;
256///
257/// if let Some(validator) = VALIDATOR_REGISTRY.get_by_code(XmlTypeCode::Integer) {
258///     let result = validator.validate("42")?;
259/// }
260/// ```
261pub static VALIDATOR_REGISTRY: Lazy<ValidatorRegistry> = Lazy::new(ValidatorRegistry::new);
262
263// ============================================================================
264// String Validators
265// ============================================================================
266
267/// Validator for xs:string
268pub struct StringValidator;
269
270impl TypeValidator for StringValidator {
271    fn type_name(&self) -> &'static str {
272        "string"
273    }
274    fn type_code(&self) -> XmlTypeCode {
275        XmlTypeCode::String
276    }
277    fn primitive_type(&self) -> PrimitiveTypeCode {
278        PrimitiveTypeCode::String
279    }
280    fn whitespace(&self) -> WhitespaceMode {
281        WhitespaceMode::Preserve
282    }
283
284    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
285        Ok(XmlValue::string(value))
286    }
287
288    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
289        let ws = facets
290            .whitespace
291            .as_ref()
292            .map(|w| w.value)
293            .unwrap_or(WhitespaceMode::Preserve);
294        let normalized = normalize_whitespace(value, ws);
295        facets.validate_string(&normalized)?;
296        Ok(XmlValue::string(normalized))
297    }
298
299    fn facet_applicable(&self, facet: &str) -> bool {
300        matches!(
301            facet,
302            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
303        )
304    }
305}
306
307/// Validator for xs:normalizedString
308pub struct NormalizedStringValidator;
309
310impl TypeValidator for NormalizedStringValidator {
311    fn type_name(&self) -> &'static str {
312        "normalizedString"
313    }
314    fn type_code(&self) -> XmlTypeCode {
315        XmlTypeCode::NormalizedString
316    }
317    fn primitive_type(&self) -> PrimitiveTypeCode {
318        PrimitiveTypeCode::String
319    }
320    fn whitespace(&self) -> WhitespaceMode {
321        WhitespaceMode::Replace
322    }
323
324    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
325        let normalized = normalize_whitespace(value, WhitespaceMode::Replace);
326        Ok(XmlValue::new(
327            XmlTypeCode::NormalizedString,
328            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
329        ))
330    }
331
332    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
333        // NormalizedString requires at least Replace mode
334        let base_ws = facets
335            .whitespace
336            .as_ref()
337            .map(|w| w.value)
338            .unwrap_or(WhitespaceMode::Replace);
339        let ws = if matches!(base_ws, WhitespaceMode::Collapse) {
340            WhitespaceMode::Collapse
341        } else {
342            WhitespaceMode::Replace
343        };
344        let normalized = normalize_whitespace(value, ws);
345        facets.validate_string(&normalized)?;
346        Ok(XmlValue::new(
347            XmlTypeCode::NormalizedString,
348            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
349        ))
350    }
351
352    fn facet_applicable(&self, facet: &str) -> bool {
353        matches!(
354            facet,
355            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
356        )
357    }
358}
359
360/// Validator for xs:token
361pub struct TokenValidator;
362
363impl TypeValidator for TokenValidator {
364    fn type_name(&self) -> &'static str {
365        "token"
366    }
367    fn type_code(&self) -> XmlTypeCode {
368        XmlTypeCode::Token
369    }
370    fn primitive_type(&self) -> PrimitiveTypeCode {
371        PrimitiveTypeCode::String
372    }
373    fn whitespace(&self) -> WhitespaceMode {
374        WhitespaceMode::Collapse
375    }
376
377    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
378        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
379        Ok(XmlValue::new(
380            XmlTypeCode::Token,
381            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
382        ))
383    }
384
385    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
386        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
387        facets.validate_string(&normalized)?;
388        Ok(XmlValue::new(
389            XmlTypeCode::Token,
390            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
391        ))
392    }
393
394    fn facet_applicable(&self, facet: &str) -> bool {
395        matches!(
396            facet,
397            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
398        )
399    }
400}
401
402/// Validator for xs:language
403pub struct LanguageValidator;
404
405impl TypeValidator for LanguageValidator {
406    fn type_name(&self) -> &'static str {
407        "language"
408    }
409    fn type_code(&self) -> XmlTypeCode {
410        XmlTypeCode::Language
411    }
412    fn primitive_type(&self) -> PrimitiveTypeCode {
413        PrimitiveTypeCode::String
414    }
415    fn whitespace(&self) -> WhitespaceMode {
416        WhitespaceMode::Collapse
417    }
418
419    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
420        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
421        // Language pattern: [a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*
422        if !is_valid_language(&normalized) {
423            return Err(ValidationError::InvalidLexical {
424                value: value.to_string(),
425                type_name: "language",
426                message: "Invalid language tag format".to_string(),
427            });
428        }
429        Ok(XmlValue::new(
430            XmlTypeCode::Language,
431            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
432        ))
433    }
434
435    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
436        let result = self.validate(value)?;
437        facets.validate_string(&result.to_string_value())?;
438        Ok(result)
439    }
440
441    fn facet_applicable(&self, facet: &str) -> bool {
442        matches!(
443            facet,
444            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
445        )
446    }
447}
448
449/// Validator for xs:NMTOKEN
450pub struct NmTokenValidator;
451
452impl TypeValidator for NmTokenValidator {
453    fn type_name(&self) -> &'static str {
454        "NMTOKEN"
455    }
456    fn type_code(&self) -> XmlTypeCode {
457        XmlTypeCode::NmToken
458    }
459    fn primitive_type(&self) -> PrimitiveTypeCode {
460        PrimitiveTypeCode::String
461    }
462    fn whitespace(&self) -> WhitespaceMode {
463        WhitespaceMode::Collapse
464    }
465
466    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
467        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
468        if normalized.is_empty() || !normalized.chars().all(is_name_char) {
469            return Err(ValidationError::InvalidLexical {
470                value: value.to_string(),
471                type_name: "NMTOKEN",
472                message: "Must contain only name characters".to_string(),
473            });
474        }
475        Ok(XmlValue::new(
476            XmlTypeCode::NmToken,
477            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
478        ))
479    }
480
481    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
482        let result = self.validate(value)?;
483        facets.validate_string(&result.to_string_value())?;
484        Ok(result)
485    }
486
487    fn facet_applicable(&self, facet: &str) -> bool {
488        matches!(
489            facet,
490            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
491        )
492    }
493}
494
495/// Validator for xs:Name
496pub struct NameValidator;
497
498impl TypeValidator for NameValidator {
499    fn type_name(&self) -> &'static str {
500        "Name"
501    }
502    fn type_code(&self) -> XmlTypeCode {
503        XmlTypeCode::Name
504    }
505    fn primitive_type(&self) -> PrimitiveTypeCode {
506        PrimitiveTypeCode::String
507    }
508    fn whitespace(&self) -> WhitespaceMode {
509        WhitespaceMode::Collapse
510    }
511
512    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
513        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
514        if !is_valid_name(&normalized) {
515            return Err(ValidationError::InvalidLexical {
516                value: value.to_string(),
517                type_name: "Name",
518                message: "Invalid XML Name".to_string(),
519            });
520        }
521        Ok(XmlValue::new(
522            XmlTypeCode::Name,
523            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
524        ))
525    }
526
527    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
528        let result = self.validate(value)?;
529        facets.validate_string(&result.to_string_value())?;
530        Ok(result)
531    }
532
533    fn facet_applicable(&self, facet: &str) -> bool {
534        matches!(
535            facet,
536            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
537        )
538    }
539}
540
541/// Validator for xs:NCName
542pub struct NCNameValidator;
543
544impl TypeValidator for NCNameValidator {
545    fn type_name(&self) -> &'static str {
546        "NCName"
547    }
548    fn type_code(&self) -> XmlTypeCode {
549        XmlTypeCode::NCName
550    }
551    fn primitive_type(&self) -> PrimitiveTypeCode {
552        PrimitiveTypeCode::String
553    }
554    fn whitespace(&self) -> WhitespaceMode {
555        WhitespaceMode::Collapse
556    }
557
558    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
559        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
560        if !is_valid_ncname(&normalized) {
561            return Err(ValidationError::InvalidLexical {
562                value: value.to_string(),
563                type_name: "NCName",
564                message: "Invalid NCName (Name without colons)".to_string(),
565            });
566        }
567        Ok(XmlValue::new(
568            XmlTypeCode::NCName,
569            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
570        ))
571    }
572
573    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
574        let result = self.validate(value)?;
575        facets.validate_string(&result.to_string_value())?;
576        Ok(result)
577    }
578
579    fn facet_applicable(&self, facet: &str) -> bool {
580        matches!(
581            facet,
582            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
583        )
584    }
585}
586
587/// Validator for xs:ID
588pub struct IdValidator;
589
590impl TypeValidator for IdValidator {
591    fn type_name(&self) -> &'static str {
592        "ID"
593    }
594    fn type_code(&self) -> XmlTypeCode {
595        XmlTypeCode::Id
596    }
597    fn primitive_type(&self) -> PrimitiveTypeCode {
598        PrimitiveTypeCode::String
599    }
600    fn whitespace(&self) -> WhitespaceMode {
601        WhitespaceMode::Collapse
602    }
603
604    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
605        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
606        if !is_valid_ncname(&normalized) {
607            return Err(ValidationError::InvalidLexical {
608                value: value.to_string(),
609                type_name: "ID",
610                message: "Invalid ID (must be NCName)".to_string(),
611            });
612        }
613        Ok(XmlValue::new(
614            XmlTypeCode::Id,
615            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
616        ))
617    }
618
619    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
620        let result = self.validate(value)?;
621        facets.validate_string(&result.to_string_value())?;
622        Ok(result)
623    }
624
625    fn facet_applicable(&self, facet: &str) -> bool {
626        matches!(
627            facet,
628            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
629        )
630    }
631}
632
633/// Validator for xs:IDREF
634pub struct IdRefValidator;
635
636impl TypeValidator for IdRefValidator {
637    fn type_name(&self) -> &'static str {
638        "IDREF"
639    }
640    fn type_code(&self) -> XmlTypeCode {
641        XmlTypeCode::IdRef
642    }
643    fn primitive_type(&self) -> PrimitiveTypeCode {
644        PrimitiveTypeCode::String
645    }
646    fn whitespace(&self) -> WhitespaceMode {
647        WhitespaceMode::Collapse
648    }
649
650    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
651        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
652        if !is_valid_ncname(&normalized) {
653            return Err(ValidationError::InvalidLexical {
654                value: value.to_string(),
655                type_name: "IDREF",
656                message: "Invalid IDREF (must be NCName)".to_string(),
657            });
658        }
659        Ok(XmlValue::new(
660            XmlTypeCode::IdRef,
661            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
662        ))
663    }
664
665    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
666        let result = self.validate(value)?;
667        facets.validate_string(&result.to_string_value())?;
668        Ok(result)
669    }
670
671    fn facet_applicable(&self, facet: &str) -> bool {
672        matches!(
673            facet,
674            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
675        )
676    }
677}
678
679/// Validator for xs:ENTITY
680pub struct EntityValidator;
681
682impl TypeValidator for EntityValidator {
683    fn type_name(&self) -> &'static str {
684        "ENTITY"
685    }
686    fn type_code(&self) -> XmlTypeCode {
687        XmlTypeCode::Entity
688    }
689    fn primitive_type(&self) -> PrimitiveTypeCode {
690        PrimitiveTypeCode::String
691    }
692    fn whitespace(&self) -> WhitespaceMode {
693        WhitespaceMode::Collapse
694    }
695
696    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
697        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
698        if !is_valid_ncname(&normalized) {
699            return Err(ValidationError::InvalidLexical {
700                value: value.to_string(),
701                type_name: "ENTITY",
702                message: "Invalid ENTITY (must be NCName)".to_string(),
703            });
704        }
705        Ok(XmlValue::new(
706            XmlTypeCode::Entity,
707            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
708        ))
709    }
710
711    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
712        let result = self.validate(value)?;
713        facets.validate_string(&result.to_string_value())?;
714        Ok(result)
715    }
716
717    fn facet_applicable(&self, facet: &str) -> bool {
718        matches!(
719            facet,
720            "length" | "minLength" | "maxLength" | "pattern" | "enumeration" | "whitespace"
721        )
722    }
723}
724
725// ============================================================================
726// Boolean Validator
727// ============================================================================
728
729/// Validator for xs:boolean
730pub struct BooleanValidator;
731
732impl TypeValidator for BooleanValidator {
733    fn type_name(&self) -> &'static str {
734        "boolean"
735    }
736    fn type_code(&self) -> XmlTypeCode {
737        XmlTypeCode::Boolean
738    }
739    fn primitive_type(&self) -> PrimitiveTypeCode {
740        PrimitiveTypeCode::Boolean
741    }
742    fn whitespace(&self) -> WhitespaceMode {
743        WhitespaceMode::Collapse
744    }
745
746    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
747        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
748        match normalized.as_str() {
749            "true" | "1" => Ok(XmlValue::boolean(true)),
750            "false" | "0" => Ok(XmlValue::boolean(false)),
751            _ => Err(ValidationError::InvalidLexical {
752                value: value.to_string(),
753                type_name: "boolean",
754                message: "Expected 'true', 'false', '1', or '0'".to_string(),
755            }),
756        }
757    }
758
759    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
760        let result = self.validate(value)?;
761        // §cvc-pattern-valid: pattern applies to lexical representation, not canonical form.
762        // "1" must match pattern "[1]{1}" even though canonical form is "true".
763        facets.validate_patterns_only(value)?;
764        // §cvc-enumeration-valid: compare in value space ("1" == "true" == true)
765        if let XmlValueKind::Atomic(XmlAtomicValue::Boolean(b)) = result.value {
766            facets.validate_enum_value_space(
767                |s| match normalize_whitespace(s, WhitespaceMode::Collapse).as_str() {
768                    "true" | "1" => b,
769                    "false" | "0" => !b,
770                    _ => false,
771                },
772                value,
773            )?;
774        }
775        Ok(result)
776    }
777
778    fn facet_applicable(&self, facet: &str) -> bool {
779        matches!(facet, "pattern" | "enumeration")
780    }
781}
782
783// ============================================================================
784// Numeric Validators
785// ============================================================================
786
787/// Validator for xs:decimal
788pub struct DecimalValidator;
789
790impl TypeValidator for DecimalValidator {
791    fn type_name(&self) -> &'static str {
792        "decimal"
793    }
794    fn type_code(&self) -> XmlTypeCode {
795        XmlTypeCode::Decimal
796    }
797    fn primitive_type(&self) -> PrimitiveTypeCode {
798        PrimitiveTypeCode::Decimal
799    }
800    fn whitespace(&self) -> WhitespaceMode {
801        WhitespaceMode::Collapse
802    }
803
804    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
805        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
806        normalized
807            .parse::<Decimal>()
808            .map(XmlValue::decimal)
809            .map_err(|e| ValidationError::InvalidLexical {
810                value: value.to_string(),
811                type_name: "decimal",
812                message: e.to_string(),
813            })
814    }
815
816    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
817        let result = self.validate(value)?;
818        if let Some(d) = result.as_decimal() {
819            facets.validate_decimal(&d)?;
820        }
821        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
822        facets.validate_string_patterns_enums(&normalized)?;
823        Ok(result)
824    }
825
826    fn facet_applicable(&self, facet: &str) -> bool {
827        matches!(
828            facet,
829            "totalDigits"
830                | "fractionDigits"
831                | "minInclusive"
832                | "maxInclusive"
833                | "minExclusive"
834                | "maxExclusive"
835                | "pattern"
836                | "enumeration"
837        )
838    }
839}
840
841/// Validator for xs:integer
842pub struct IntegerValidator;
843
844impl TypeValidator for IntegerValidator {
845    fn type_name(&self) -> &'static str {
846        "integer"
847    }
848    fn type_code(&self) -> XmlTypeCode {
849        XmlTypeCode::Integer
850    }
851    fn primitive_type(&self) -> PrimitiveTypeCode {
852        PrimitiveTypeCode::Decimal
853    }
854    fn whitespace(&self) -> WhitespaceMode {
855        WhitespaceMode::Collapse
856    }
857
858    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
859        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
860        normalized
861            .parse::<BigInt>()
862            .map(XmlValue::integer)
863            .map_err(|e| ValidationError::InvalidLexical {
864                value: value.to_string(),
865                type_name: "integer",
866                message: e.to_string(),
867            })
868    }
869
870    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
871        let result = self.validate(value)?;
872        if let Some(d) = result.as_decimal() {
873            facets.validate_decimal(&d)?;
874        }
875        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
876        facets.validate_string_patterns_enums(&normalized)?;
877        Ok(result)
878    }
879
880    fn facet_applicable(&self, facet: &str) -> bool {
881        matches!(
882            facet,
883            "totalDigits"
884                | "fractionDigits"
885                | "minInclusive"
886                | "maxInclusive"
887                | "minExclusive"
888                | "maxExclusive"
889                | "pattern"
890                | "enumeration"
891        )
892    }
893}
894
895/// Validator for xs:float
896pub struct FloatValidator;
897
898impl TypeValidator for FloatValidator {
899    fn type_name(&self) -> &'static str {
900        "float"
901    }
902    fn type_code(&self) -> XmlTypeCode {
903        XmlTypeCode::Float
904    }
905    fn primitive_type(&self) -> PrimitiveTypeCode {
906        PrimitiveTypeCode::Float
907    }
908    fn whitespace(&self) -> WhitespaceMode {
909        WhitespaceMode::Collapse
910    }
911
912    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
913        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
914        parse_float(&normalized)
915            .map(XmlValue::float)
916            .map_err(|msg| ValidationError::InvalidLexical {
917                value: value.to_string(),
918                type_name: "float",
919                message: msg,
920            })
921    }
922
923    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
924        let result = self.validate(value)?;
925        if let XmlValueKind::Atomic(XmlAtomicValue::Float(f)) = result.value {
926            facets.validate_float(f)?;
927            // §cvc-enumeration-valid: compare in value space, not lexically
928            facets.validate_enum_value_space(
929                enum_matches_value_space!(f, parse_float, float_eq),
930                value,
931            )?;
932        }
933        facets.validate_patterns_only(value)?;
934        Ok(result)
935    }
936
937    fn facet_applicable(&self, facet: &str) -> bool {
938        matches!(
939            facet,
940            "minInclusive"
941                | "maxInclusive"
942                | "minExclusive"
943                | "maxExclusive"
944                | "pattern"
945                | "enumeration"
946        )
947    }
948}
949
950/// Validator for xs:double
951pub struct DoubleValidator;
952
953impl TypeValidator for DoubleValidator {
954    fn type_name(&self) -> &'static str {
955        "double"
956    }
957    fn type_code(&self) -> XmlTypeCode {
958        XmlTypeCode::Double
959    }
960    fn primitive_type(&self) -> PrimitiveTypeCode {
961        PrimitiveTypeCode::Double
962    }
963    fn whitespace(&self) -> WhitespaceMode {
964        WhitespaceMode::Collapse
965    }
966
967    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
968        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
969        parse_double(&normalized)
970            .map(XmlValue::double)
971            .map_err(|msg| ValidationError::InvalidLexical {
972                value: value.to_string(),
973                type_name: "double",
974                message: msg,
975            })
976    }
977
978    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
979        let result = self.validate(value)?;
980        if let XmlValueKind::Atomic(XmlAtomicValue::Double(d)) = result.value {
981            facets.validate_double(d)?;
982            // §cvc-enumeration-valid: compare in value space, not lexically
983            facets.validate_enum_value_space(
984                enum_matches_value_space!(d, parse_double, double_eq),
985                value,
986            )?;
987        }
988        facets.validate_patterns_only(value)?;
989        Ok(result)
990    }
991
992    fn facet_applicable(&self, facet: &str) -> bool {
993        matches!(
994            facet,
995            "minInclusive"
996                | "maxInclusive"
997                | "minExclusive"
998                | "maxExclusive"
999                | "pattern"
1000                | "enumeration"
1001        )
1002    }
1003}
1004
1005/// Validate strict XSD lexical form for `xs:float` / `xs:double`.
1006///
1007/// Datatypes 1.1 §3.3.16 / §3.3.17 enumerate the legal special values as
1008/// `INF`, `+INF`, `-INF`, and `NaN` (case-sensitive). XSD 1.0 omits the
1009/// `+INF` form (Datatypes 1.0 §3.2.4 / §3.2.5); the version-specific
1010/// rejection of `+INF` is handled post-validation in `validate_atomic_type`.
1011/// Anything else must match the numeric production
1012/// `('+'|'-')? (digits ('.' digits?)? | '.' digits) ([eE] ('+'|'-')? digits)?`.
1013/// Rust's `f32::from_str` / `f64::from_str` accept additional forms
1014/// (`inf`, `nan`, `infinity`, `+NaN`, etc.) — we pre-validate to reject them.
1015pub fn is_valid_xsd_float_lexical(s: &str) -> bool {
1016    if s == "INF" || s == "-INF" || s == "+INF" || s == "NaN" {
1017        return true;
1018    }
1019    let bytes = s.as_bytes();
1020    if bytes.is_empty() {
1021        return false;
1022    }
1023    let mut i = 0;
1024    if bytes[i] == b'+' || bytes[i] == b'-' {
1025        i += 1;
1026    }
1027    let mut has_int_digits = false;
1028    while i < bytes.len() && bytes[i].is_ascii_digit() {
1029        has_int_digits = true;
1030        i += 1;
1031    }
1032    let mut has_frac_digits = false;
1033    if i < bytes.len() && bytes[i] == b'.' {
1034        i += 1;
1035        while i < bytes.len() && bytes[i].is_ascii_digit() {
1036            has_frac_digits = true;
1037            i += 1;
1038        }
1039    }
1040    if !has_int_digits && !has_frac_digits {
1041        return false;
1042    }
1043    if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
1044        i += 1;
1045        if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
1046            i += 1;
1047        }
1048        let mut has_exp_digits = false;
1049        while i < bytes.len() && bytes[i].is_ascii_digit() {
1050            has_exp_digits = true;
1051            i += 1;
1052        }
1053        if !has_exp_digits {
1054            return false;
1055        }
1056    }
1057    i == bytes.len()
1058}
1059
1060/// Parse XSD float with special value handling
1061fn parse_float(s: &str) -> Result<f32, String> {
1062    if !is_valid_xsd_float_lexical(s) {
1063        return Err(format!("Invalid xs:float lexical form: '{}'", s));
1064    }
1065    match s {
1066        "INF" | "+INF" => Ok(f32::INFINITY),
1067        "-INF" => Ok(f32::NEG_INFINITY),
1068        "NaN" => Ok(f32::NAN),
1069        _ => s
1070            .parse()
1071            .map_err(|e: std::num::ParseFloatError| e.to_string()),
1072    }
1073}
1074
1075/// Parse XSD double with special value handling
1076fn parse_double(s: &str) -> Result<f64, String> {
1077    if !is_valid_xsd_float_lexical(s) {
1078        return Err(format!("Invalid xs:double lexical form: '{}'", s));
1079    }
1080    match s {
1081        "INF" | "+INF" => Ok(f64::INFINITY),
1082        "-INF" => Ok(f64::NEG_INFINITY),
1083        "NaN" => Ok(f64::NAN),
1084        _ => s
1085            .parse()
1086            .map_err(|e: std::num::ParseFloatError| e.to_string()),
1087    }
1088}
1089
1090use crate::types::equality::{double_eq, float_eq};
1091
1092/// Value-space equality for xs:duration per §3.3.6.
1093/// Two durations are equal iff their signed total-months components are equal
1094/// AND their signed total day-time seconds components are equal.
1095fn datetime_value_eq(a: &DateTimeValue, b: &DateTimeValue) -> bool {
1096    a.partial_cmp(b) == Some(std::cmp::Ordering::Equal)
1097}
1098
1099fn date_value_eq(a: &DateValue, b: &DateValue) -> bool {
1100    a.partial_cmp(b) == Some(std::cmp::Ordering::Equal)
1101}
1102
1103fn time_value_eq(a: &TimeValue, b: &TimeValue) -> bool {
1104    a.partial_cmp(b) == Some(std::cmp::Ordering::Equal)
1105}
1106
1107fn duration_eq(a: &DurationValue, b: &DurationValue) -> bool {
1108    let sign = |d: &DurationValue| if d.negative { -1i64 } else { 1 };
1109    let total_months = |d: &DurationValue| d.years as i64 * 12 + d.months as i64;
1110    if sign(a) * total_months(a) != sign(b) * total_months(b) {
1111        return false;
1112    }
1113    let day_secs = |d: &DurationValue| {
1114        let s = rust_decimal::Decimal::from(d.days as i64) * rust_decimal::Decimal::from(86400)
1115            + rust_decimal::Decimal::from(d.hours as i64) * rust_decimal::Decimal::from(3600)
1116            + rust_decimal::Decimal::from(d.minutes as i64) * rust_decimal::Decimal::from(60)
1117            + d.seconds;
1118        if d.negative {
1119            -s
1120        } else {
1121            s
1122        }
1123    };
1124    day_secs(a) == day_secs(b)
1125}
1126
1127fn year_month_duration_value_eq(a: &YearMonthDurationValue, b: &YearMonthDurationValue) -> bool {
1128    a.partial_cmp(b) == Some(std::cmp::Ordering::Equal)
1129}
1130
1131fn day_time_duration_value_eq(a: &DayTimeDurationValue, b: &DayTimeDurationValue) -> bool {
1132    a.partial_cmp(b) == Some(std::cmp::Ordering::Equal)
1133}
1134
1135// ============================================================================
1136// ---------------------------------------------------------------------------
1137// Date/time bound checking helper
1138// ---------------------------------------------------------------------------
1139
1140/// Validate min/max inclusive/exclusive bounds for types that implement PartialOrd.
1141/// The `parse_fn` parses the bound value string using the same parser as the main value.
1142fn validate_bounds<T: PartialOrd, F: Fn(&str) -> Option<T>>(
1143    value: &T,
1144    facets: &FacetSet,
1145    _type_name: &str,
1146    value_str: &str,
1147    parse_fn: F,
1148) -> ValidationResult<()> {
1149    if let Some(ref min) = facets.min_inclusive {
1150        if let Some(bound) = parse_fn(&min.value) {
1151            if value.partial_cmp(&bound) != Some(std::cmp::Ordering::Greater)
1152                && value.partial_cmp(&bound) != Some(std::cmp::Ordering::Equal)
1153            {
1154                return Err(ValidationError::FacetViolation(
1155                    FacetError::MinInclusiveViolation {
1156                        value: value_str.to_string(),
1157                        min: min.value.clone(),
1158                    },
1159                ));
1160            }
1161        }
1162    }
1163    if let Some(ref max) = facets.max_inclusive {
1164        if let Some(bound) = parse_fn(&max.value) {
1165            if value.partial_cmp(&bound) != Some(std::cmp::Ordering::Less)
1166                && value.partial_cmp(&bound) != Some(std::cmp::Ordering::Equal)
1167            {
1168                return Err(ValidationError::FacetViolation(
1169                    FacetError::MaxInclusiveViolation {
1170                        value: value_str.to_string(),
1171                        max: max.value.clone(),
1172                    },
1173                ));
1174            }
1175        }
1176    }
1177    if let Some(ref min) = facets.min_exclusive {
1178        if let Some(bound) = parse_fn(&min.value) {
1179            if value.partial_cmp(&bound) != Some(std::cmp::Ordering::Greater) {
1180                return Err(ValidationError::FacetViolation(
1181                    FacetError::MinExclusiveViolation {
1182                        value: value_str.to_string(),
1183                        min: min.value.clone(),
1184                    },
1185                ));
1186            }
1187        }
1188    }
1189    if let Some(ref max) = facets.max_exclusive {
1190        if let Some(bound) = parse_fn(&max.value) {
1191            if value.partial_cmp(&bound) != Some(std::cmp::Ordering::Less) {
1192                return Err(ValidationError::FacetViolation(
1193                    FacetError::MaxExclusiveViolation {
1194                        value: value_str.to_string(),
1195                        max: max.value.clone(),
1196                    },
1197                ));
1198            }
1199        }
1200    }
1201    Ok(())
1202}
1203
1204// Date/Time Validators
1205// ============================================================================
1206
1207/// Validator for xs:duration
1208pub struct DurationValidator;
1209
1210impl TypeValidator for DurationValidator {
1211    fn type_name(&self) -> &'static str {
1212        "duration"
1213    }
1214    fn type_code(&self) -> XmlTypeCode {
1215        XmlTypeCode::Duration
1216    }
1217    fn primitive_type(&self) -> PrimitiveTypeCode {
1218        PrimitiveTypeCode::Duration
1219    }
1220    fn whitespace(&self) -> WhitespaceMode {
1221        WhitespaceMode::Collapse
1222    }
1223
1224    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1225        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1226        parse_duration(&normalized).map(|d| {
1227            XmlValue::new(
1228                XmlTypeCode::Duration,
1229                XmlValueKind::Atomic(XmlAtomicValue::Duration(d)),
1230            )
1231        })
1232    }
1233
1234    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1235        let result = self.validate(value)?;
1236        if let XmlValueKind::Atomic(XmlAtomicValue::Duration(ref dur)) = result.value {
1237            validate_bounds(dur, facets, "duration", value, |s| {
1238                parse_duration(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1239            })?;
1240            // §cvc-enumeration-valid: compare in value space, not lexically
1241            facets.validate_enum_value_space(
1242                enum_matches_value_space!(dur, parse_duration, |a, b| duration_eq(a, &b)),
1243                value,
1244            )?;
1245        }
1246        facets.validate_patterns_only(value)?;
1247        Ok(result)
1248    }
1249
1250    fn facet_applicable(&self, facet: &str) -> bool {
1251        matches!(
1252            facet,
1253            "minInclusive"
1254                | "maxInclusive"
1255                | "minExclusive"
1256                | "maxExclusive"
1257                | "pattern"
1258                | "enumeration"
1259        )
1260    }
1261}
1262
1263/// Validator for xs:dateTime
1264pub struct DateTimeValidator;
1265
1266impl TypeValidator for DateTimeValidator {
1267    fn type_name(&self) -> &'static str {
1268        "dateTime"
1269    }
1270    fn type_code(&self) -> XmlTypeCode {
1271        XmlTypeCode::DateTime
1272    }
1273    fn primitive_type(&self) -> PrimitiveTypeCode {
1274        PrimitiveTypeCode::DateTime
1275    }
1276    fn whitespace(&self) -> WhitespaceMode {
1277        WhitespaceMode::Collapse
1278    }
1279
1280    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1281        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1282        parse_datetime(&normalized).map(|dt| {
1283            XmlValue::new(
1284                XmlTypeCode::DateTime,
1285                XmlValueKind::Atomic(XmlAtomicValue::DateTime(dt)),
1286            )
1287        })
1288    }
1289
1290    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1291        let result = self.validate(value)?;
1292        if let XmlValueKind::Atomic(XmlAtomicValue::DateTime(ref dt)) = result.value {
1293            facets.validate_explicit_timezone(dt.timezone.is_some())?;
1294            validate_bounds(dt, facets, "dateTime", value, |s| {
1295                parse_datetime(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1296            })?;
1297            facets.validate_enum_value_space(
1298                enum_matches_value_space!(dt, parse_datetime, |a, b| datetime_value_eq(a, &b)),
1299                value,
1300            )?;
1301        }
1302        facets.validate_patterns_only(value)?;
1303        Ok(result)
1304    }
1305
1306    fn facet_applicable(&self, facet: &str) -> bool {
1307        matches!(
1308            facet,
1309            "minInclusive"
1310                | "maxInclusive"
1311                | "minExclusive"
1312                | "maxExclusive"
1313                | "pattern"
1314                | "enumeration"
1315                | "explicitTimezone"
1316        )
1317    }
1318}
1319
1320/// Validator for xs:date
1321pub struct DateValidator;
1322
1323impl TypeValidator for DateValidator {
1324    fn type_name(&self) -> &'static str {
1325        "date"
1326    }
1327    fn type_code(&self) -> XmlTypeCode {
1328        XmlTypeCode::Date
1329    }
1330    fn primitive_type(&self) -> PrimitiveTypeCode {
1331        PrimitiveTypeCode::Date
1332    }
1333    fn whitespace(&self) -> WhitespaceMode {
1334        WhitespaceMode::Collapse
1335    }
1336
1337    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1338        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1339        parse_date(&normalized).map(|d| {
1340            XmlValue::new(
1341                XmlTypeCode::Date,
1342                XmlValueKind::Atomic(XmlAtomicValue::Date(d)),
1343            )
1344        })
1345    }
1346
1347    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1348        let result = self.validate(value)?;
1349        if let XmlValueKind::Atomic(XmlAtomicValue::Date(ref d)) = result.value {
1350            facets.validate_explicit_timezone(d.timezone.is_some())?;
1351            validate_bounds(d, facets, "date", value, |s| {
1352                parse_date(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1353            })?;
1354            facets.validate_enum_value_space(
1355                enum_matches_value_space!(d, parse_date, |a, b| date_value_eq(a, &b)),
1356                value,
1357            )?;
1358        }
1359        facets.validate_patterns_only(value)?;
1360        Ok(result)
1361    }
1362
1363    fn facet_applicable(&self, facet: &str) -> bool {
1364        matches!(
1365            facet,
1366            "minInclusive"
1367                | "maxInclusive"
1368                | "minExclusive"
1369                | "maxExclusive"
1370                | "pattern"
1371                | "enumeration"
1372                | "explicitTimezone"
1373        )
1374    }
1375}
1376
1377/// Validator for xs:time
1378pub struct TimeValidator;
1379
1380impl TypeValidator for TimeValidator {
1381    fn type_name(&self) -> &'static str {
1382        "time"
1383    }
1384    fn type_code(&self) -> XmlTypeCode {
1385        XmlTypeCode::Time
1386    }
1387    fn primitive_type(&self) -> PrimitiveTypeCode {
1388        PrimitiveTypeCode::Time
1389    }
1390    fn whitespace(&self) -> WhitespaceMode {
1391        WhitespaceMode::Collapse
1392    }
1393
1394    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1395        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1396        parse_time(&normalized).map(|t| {
1397            XmlValue::new(
1398                XmlTypeCode::Time,
1399                XmlValueKind::Atomic(XmlAtomicValue::Time(t)),
1400            )
1401        })
1402    }
1403
1404    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1405        let result = self.validate(value)?;
1406        if let XmlValueKind::Atomic(XmlAtomicValue::Time(ref t)) = result.value {
1407            facets.validate_explicit_timezone(t.timezone.is_some())?;
1408            validate_bounds(t, facets, "time", value, |s| {
1409                parse_time(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1410            })?;
1411            facets.validate_enum_value_space(
1412                enum_matches_value_space!(t, parse_time, |a, b| time_value_eq(a, &b)),
1413                value,
1414            )?;
1415        }
1416        facets.validate_patterns_only(value)?;
1417        Ok(result)
1418    }
1419
1420    fn facet_applicable(&self, facet: &str) -> bool {
1421        matches!(
1422            facet,
1423            "minInclusive"
1424                | "maxInclusive"
1425                | "minExclusive"
1426                | "maxExclusive"
1427                | "pattern"
1428                | "enumeration"
1429                | "explicitTimezone"
1430        )
1431    }
1432}
1433
1434/// Validator for xs:gYearMonth
1435pub struct GYearMonthValidator;
1436
1437impl TypeValidator for GYearMonthValidator {
1438    fn type_name(&self) -> &'static str {
1439        "gYearMonth"
1440    }
1441    fn type_code(&self) -> XmlTypeCode {
1442        XmlTypeCode::GYearMonth
1443    }
1444    fn primitive_type(&self) -> PrimitiveTypeCode {
1445        PrimitiveTypeCode::GYearMonth
1446    }
1447    fn whitespace(&self) -> WhitespaceMode {
1448        WhitespaceMode::Collapse
1449    }
1450
1451    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1452        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1453        parse_gyearmonth(&normalized).map(|v| {
1454            XmlValue::new(
1455                XmlTypeCode::GYearMonth,
1456                XmlValueKind::Atomic(XmlAtomicValue::GYearMonth(v)),
1457            )
1458        })
1459    }
1460
1461    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1462        let result = self.validate(value)?;
1463        if let XmlValueKind::Atomic(XmlAtomicValue::GYearMonth(ref v)) = result.value {
1464            facets.validate_explicit_timezone(v.timezone.is_some())?;
1465            validate_bounds(v, facets, "gYearMonth", value, |s| {
1466                parse_gyearmonth(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1467            })?;
1468        }
1469        facets.validate_string(&result.to_string_value())?;
1470        Ok(result)
1471    }
1472
1473    fn facet_applicable(&self, facet: &str) -> bool {
1474        matches!(
1475            facet,
1476            "minInclusive"
1477                | "maxInclusive"
1478                | "minExclusive"
1479                | "maxExclusive"
1480                | "pattern"
1481                | "enumeration"
1482                | "explicitTimezone"
1483        )
1484    }
1485}
1486
1487/// Validator for xs:gYear
1488pub struct GYearValidator;
1489
1490impl TypeValidator for GYearValidator {
1491    fn type_name(&self) -> &'static str {
1492        "gYear"
1493    }
1494    fn type_code(&self) -> XmlTypeCode {
1495        XmlTypeCode::GYear
1496    }
1497    fn primitive_type(&self) -> PrimitiveTypeCode {
1498        PrimitiveTypeCode::GYear
1499    }
1500    fn whitespace(&self) -> WhitespaceMode {
1501        WhitespaceMode::Collapse
1502    }
1503
1504    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1505        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1506        parse_gyear(&normalized).map(|v| {
1507            XmlValue::new(
1508                XmlTypeCode::GYear,
1509                XmlValueKind::Atomic(XmlAtomicValue::GYear(v)),
1510            )
1511        })
1512    }
1513
1514    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1515        let result = self.validate(value)?;
1516        if let XmlValueKind::Atomic(XmlAtomicValue::GYear(ref v)) = result.value {
1517            facets.validate_explicit_timezone(v.timezone.is_some())?;
1518            validate_bounds(v, facets, "gYear", value, |s| {
1519                parse_gyear(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1520            })?;
1521        }
1522        facets.validate_string(&result.to_string_value())?;
1523        Ok(result)
1524    }
1525
1526    fn facet_applicable(&self, facet: &str) -> bool {
1527        matches!(
1528            facet,
1529            "minInclusive"
1530                | "maxInclusive"
1531                | "minExclusive"
1532                | "maxExclusive"
1533                | "pattern"
1534                | "enumeration"
1535                | "explicitTimezone"
1536        )
1537    }
1538}
1539
1540/// Validator for xs:gMonthDay
1541pub struct GMonthDayValidator;
1542
1543impl TypeValidator for GMonthDayValidator {
1544    fn type_name(&self) -> &'static str {
1545        "gMonthDay"
1546    }
1547    fn type_code(&self) -> XmlTypeCode {
1548        XmlTypeCode::GMonthDay
1549    }
1550    fn primitive_type(&self) -> PrimitiveTypeCode {
1551        PrimitiveTypeCode::GMonthDay
1552    }
1553    fn whitespace(&self) -> WhitespaceMode {
1554        WhitespaceMode::Collapse
1555    }
1556
1557    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1558        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1559        parse_gmonthday(&normalized).map(|v| {
1560            XmlValue::new(
1561                XmlTypeCode::GMonthDay,
1562                XmlValueKind::Atomic(XmlAtomicValue::GMonthDay(v)),
1563            )
1564        })
1565    }
1566
1567    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1568        let result = self.validate(value)?;
1569        if let XmlValueKind::Atomic(XmlAtomicValue::GMonthDay(ref v)) = result.value {
1570            facets.validate_explicit_timezone(v.timezone.is_some())?;
1571            validate_bounds(v, facets, "gMonthDay", value, |s| {
1572                parse_gmonthday(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1573            })?;
1574        }
1575        facets.validate_string(&result.to_string_value())?;
1576        Ok(result)
1577    }
1578
1579    fn facet_applicable(&self, facet: &str) -> bool {
1580        matches!(
1581            facet,
1582            "minInclusive"
1583                | "maxInclusive"
1584                | "minExclusive"
1585                | "maxExclusive"
1586                | "pattern"
1587                | "enumeration"
1588                | "explicitTimezone"
1589        )
1590    }
1591}
1592
1593/// Validator for xs:gDay
1594pub struct GDayValidator;
1595
1596impl TypeValidator for GDayValidator {
1597    fn type_name(&self) -> &'static str {
1598        "gDay"
1599    }
1600    fn type_code(&self) -> XmlTypeCode {
1601        XmlTypeCode::GDay
1602    }
1603    fn primitive_type(&self) -> PrimitiveTypeCode {
1604        PrimitiveTypeCode::GDay
1605    }
1606    fn whitespace(&self) -> WhitespaceMode {
1607        WhitespaceMode::Collapse
1608    }
1609
1610    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1611        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1612        parse_gday(&normalized).map(|v| {
1613            XmlValue::new(
1614                XmlTypeCode::GDay,
1615                XmlValueKind::Atomic(XmlAtomicValue::GDay(v)),
1616            )
1617        })
1618    }
1619
1620    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1621        let result = self.validate(value)?;
1622        if let XmlValueKind::Atomic(XmlAtomicValue::GDay(ref v)) = result.value {
1623            facets.validate_explicit_timezone(v.timezone.is_some())?;
1624            validate_bounds(v, facets, "gDay", value, |s| {
1625                parse_gday(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1626            })?;
1627        }
1628        facets.validate_string(&result.to_string_value())?;
1629        Ok(result)
1630    }
1631
1632    fn facet_applicable(&self, facet: &str) -> bool {
1633        matches!(
1634            facet,
1635            "minInclusive"
1636                | "maxInclusive"
1637                | "minExclusive"
1638                | "maxExclusive"
1639                | "pattern"
1640                | "enumeration"
1641                | "explicitTimezone"
1642        )
1643    }
1644}
1645
1646/// Validator for xs:gMonth
1647pub struct GMonthValidator;
1648
1649impl TypeValidator for GMonthValidator {
1650    fn type_name(&self) -> &'static str {
1651        "gMonth"
1652    }
1653    fn type_code(&self) -> XmlTypeCode {
1654        XmlTypeCode::GMonth
1655    }
1656    fn primitive_type(&self) -> PrimitiveTypeCode {
1657        PrimitiveTypeCode::GMonth
1658    }
1659    fn whitespace(&self) -> WhitespaceMode {
1660        WhitespaceMode::Collapse
1661    }
1662
1663    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1664        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1665        parse_gmonth(&normalized).map(|v| {
1666            XmlValue::new(
1667                XmlTypeCode::GMonth,
1668                XmlValueKind::Atomic(XmlAtomicValue::GMonth(v)),
1669            )
1670        })
1671    }
1672
1673    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1674        let result = self.validate(value)?;
1675        if let XmlValueKind::Atomic(XmlAtomicValue::GMonth(ref v)) = result.value {
1676            facets.validate_explicit_timezone(v.timezone.is_some())?;
1677            validate_bounds(v, facets, "gMonth", value, |s| {
1678                parse_gmonth(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
1679            })?;
1680        }
1681        facets.validate_string(&result.to_string_value())?;
1682        Ok(result)
1683    }
1684
1685    fn facet_applicable(&self, facet: &str) -> bool {
1686        matches!(
1687            facet,
1688            "minInclusive"
1689                | "maxInclusive"
1690                | "minExclusive"
1691                | "maxExclusive"
1692                | "pattern"
1693                | "enumeration"
1694                | "explicitTimezone"
1695        )
1696    }
1697}
1698
1699// ============================================================================
1700// Binary Validators
1701// ============================================================================
1702
1703/// Validator for xs:hexBinary
1704pub struct HexBinaryValidator;
1705
1706impl TypeValidator for HexBinaryValidator {
1707    fn type_name(&self) -> &'static str {
1708        "hexBinary"
1709    }
1710    fn type_code(&self) -> XmlTypeCode {
1711        XmlTypeCode::HexBinary
1712    }
1713    fn primitive_type(&self) -> PrimitiveTypeCode {
1714        PrimitiveTypeCode::HexBinary
1715    }
1716    fn whitespace(&self) -> WhitespaceMode {
1717        WhitespaceMode::Collapse
1718    }
1719
1720    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1721        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1722        hex::decode(&normalized)
1723            .map(|bytes| {
1724                XmlValue::new(
1725                    XmlTypeCode::HexBinary,
1726                    XmlValueKind::Atomic(XmlAtomicValue::HexBinary(bytes)),
1727                )
1728            })
1729            .map_err(|e| ValidationError::InvalidLexical {
1730                value: value.to_string(),
1731                type_name: "hexBinary",
1732                message: e.to_string(),
1733            })
1734    }
1735
1736    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1737        let result = self.validate(value)?;
1738        if let XmlValueKind::Atomic(XmlAtomicValue::HexBinary(bytes)) = &result.value {
1739            facets.validate_binary_length(bytes.len() as u64)?;
1740        }
1741        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1742        facets.validate_string_patterns_enums(&normalized)?;
1743        Ok(result)
1744    }
1745
1746    fn facet_applicable(&self, facet: &str) -> bool {
1747        matches!(
1748            facet,
1749            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
1750        )
1751    }
1752}
1753
1754/// Validator for xs:base64Binary
1755pub struct Base64BinaryValidator;
1756
1757impl TypeValidator for Base64BinaryValidator {
1758    fn type_name(&self) -> &'static str {
1759        "base64Binary"
1760    }
1761    fn type_code(&self) -> XmlTypeCode {
1762        XmlTypeCode::Base64Binary
1763    }
1764    fn primitive_type(&self) -> PrimitiveTypeCode {
1765        PrimitiveTypeCode::Base64Binary
1766    }
1767    fn whitespace(&self) -> WhitespaceMode {
1768        WhitespaceMode::Collapse
1769    }
1770
1771    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1772        use base64::Engine;
1773        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1774        base64::engine::general_purpose::STANDARD
1775            .decode(&normalized)
1776            .map(|bytes| {
1777                XmlValue::new(
1778                    XmlTypeCode::Base64Binary,
1779                    XmlValueKind::Atomic(XmlAtomicValue::Base64Binary(bytes)),
1780                )
1781            })
1782            .map_err(|e| ValidationError::InvalidLexical {
1783                value: value.to_string(),
1784                type_name: "base64Binary",
1785                message: e.to_string(),
1786            })
1787    }
1788
1789    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1790        let result = self.validate(value)?;
1791        if let XmlValueKind::Atomic(XmlAtomicValue::Base64Binary(bytes)) = &result.value {
1792            facets.validate_binary_length(bytes.len() as u64)?;
1793        }
1794        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1795        facets.validate_string_patterns_enums(&normalized)?;
1796        Ok(result)
1797    }
1798
1799    fn facet_applicable(&self, facet: &str) -> bool {
1800        matches!(
1801            facet,
1802            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
1803        )
1804    }
1805}
1806
1807// ============================================================================
1808// URI Validator
1809// ============================================================================
1810
1811/// Validator for xs:anyURI
1812pub struct AnyUriValidator;
1813
1814impl TypeValidator for AnyUriValidator {
1815    fn type_name(&self) -> &'static str {
1816        "anyURI"
1817    }
1818    fn type_code(&self) -> XmlTypeCode {
1819        XmlTypeCode::AnyUri
1820    }
1821    fn primitive_type(&self) -> PrimitiveTypeCode {
1822        PrimitiveTypeCode::AnyUri
1823    }
1824    fn whitespace(&self) -> WhitespaceMode {
1825        WhitespaceMode::Collapse
1826    }
1827
1828    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
1829        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
1830        // XSD anyURI is very permissive - just check for illegal characters
1831        // (actual URI validation is application-specific)
1832        Ok(XmlValue::new(
1833            XmlTypeCode::AnyUri,
1834            XmlValueKind::Atomic(XmlAtomicValue::AnyUri(normalized)),
1835        ))
1836    }
1837
1838    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
1839        let result = self.validate(value)?;
1840        facets.validate_string(&result.to_string_value())?;
1841        Ok(result)
1842    }
1843
1844    fn facet_applicable(&self, facet: &str) -> bool {
1845        matches!(
1846            facet,
1847            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
1848        )
1849    }
1850}
1851
1852/// Check if a string is a valid URI scheme per RFC 2396 §3.1 / RFC 3986
1853/// (`alpha *( alpha | digit | "+" | "-" | "." )`). Empty is invalid.
1854pub fn is_valid_uri_scheme(s: &str) -> bool {
1855    let mut chars = s.chars();
1856    let Some(first) = chars.next() else {
1857        return false;
1858    };
1859    if !first.is_ascii_alphabetic() {
1860        return false;
1861    }
1862    chars.all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.')
1863}
1864
1865/// Strict XSD 1.0 lexical check for an `xs:anyURI` value.
1866///
1867/// XSD 1.0 §3.2.17 ties `xs:anyURI` to RFC 2396, which forbids a URI
1868/// reference whose first colon is preceded by anything other than a valid
1869/// scheme name. The W3C `anyURI_a001_1336` fixture relies on exactly this
1870/// check: the schema contains `appinfo/@source="9999...anyURI:"` and
1871/// `documentation/@source="1111...http://foo/bar"`, both of which place a
1872/// digit in the (would-be) scheme position. XSD 1.1 explicitly relaxed
1873/// the rule, so callers must gate this on `XsdVersion::V1_0`.
1874///
1875/// The check is intentionally narrow — it only catches the malformed-scheme
1876/// shape — to avoid false positives on the many sloppy-but-spec-legal URIs
1877/// in the existing conformance corpus (numeric relative segments like `"0"`
1878/// or `"123"` remain accepted as valid relative-path references).
1879///
1880/// `xs:anyURI` declares `whiteSpace = collapse`. The common case of an
1881/// annotation `source` value has no whitespace at all, so we skip the
1882/// allocation entirely unless the input actually contains whitespace.
1883pub fn is_strict_xsd10_anyuri(value: &str) -> bool {
1884    let collapsed: String;
1885    let value: &str = if value.chars().any(char::is_whitespace) {
1886        collapsed = normalize_whitespace(value, WhitespaceMode::Collapse);
1887        collapsed.as_str()
1888    } else {
1889        value
1890    };
1891
1892    if value.is_empty() {
1893        return true;
1894    }
1895
1896    // Find the first character among ':' / '/' / '?' / '#'. Anything before
1897    // that delimiter is the candidate scheme; if the delimiter is ':' the
1898    // scheme syntax must be valid.
1899    let mut scheme_end: Option<usize> = None;
1900    for (i, c) in value.char_indices() {
1901        match c {
1902            ':' => {
1903                scheme_end = Some(i);
1904                break;
1905            }
1906            // Pure relative reference — scheme rule does not apply.
1907            '/' | '?' | '#' => return true,
1908            _ => {}
1909        }
1910    }
1911    // No colon at all — relative reference, always lexically OK.
1912    let Some(end) = scheme_end else { return true };
1913    is_valid_uri_scheme(&value[..end])
1914}
1915
1916// ============================================================================
1917// Date/Time Parsing Helpers
1918// ============================================================================
1919
1920/// Parse XSD duration
1921fn parse_duration(s: &str) -> ValidationResult<DurationValue> {
1922    // Lexical form per §3.3.6 (Datatypes):
1923    //   -?P([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.[0-9]+)?S)?)?
1924    // At least one designator after `P` is required; after `T` at least one
1925    // time designator is required. Fraction is only legal on the seconds field.
1926    let bad = |message: String| ValidationError::InvalidLexical {
1927        value: s.to_string(),
1928        type_name: "duration",
1929        message,
1930    };
1931
1932    let bytes = s.as_bytes();
1933    let mut pos = 0usize;
1934    let negative = bytes.first() == Some(&b'-');
1935    if negative {
1936        pos += 1;
1937    }
1938    if bytes.get(pos) != Some(&b'P') {
1939        return Err(bad("Must start with 'P'".to_string()));
1940    }
1941    pos += 1;
1942
1943    let mut out = DurationValue {
1944        negative,
1945        years: 0,
1946        months: 0,
1947        days: 0,
1948        hours: 0,
1949        minutes: 0,
1950        seconds: Decimal::ZERO,
1951    };
1952    // Duplicate-designator detection. Index order matches `Field` below.
1953    let mut seen = [false; 6];
1954    let mut in_time = false;
1955
1956    while pos < bytes.len() {
1957        if bytes[pos] == b'T' {
1958            if in_time {
1959                return Err(bad("Duplicate 'T' separator".to_string()));
1960            }
1961            in_time = true;
1962            pos += 1;
1963            continue;
1964        }
1965
1966        let int_start = pos;
1967        while pos < bytes.len() && bytes[pos].is_ascii_digit() {
1968            pos += 1;
1969        }
1970        if pos == int_start {
1971            return Err(bad(format!(
1972                "Unexpected character '{}' at position {}",
1973                bytes[pos] as char, pos
1974            )));
1975        }
1976        let int_end = pos;
1977        if pos < bytes.len() && bytes[pos] == b'.' {
1978            pos += 1;
1979            let frac_start = pos;
1980            while pos < bytes.len() && bytes[pos].is_ascii_digit() {
1981                pos += 1;
1982            }
1983            if pos == frac_start {
1984                return Err(bad(
1985                    "Fractional part must have at least one digit".to_string()
1986                ));
1987            }
1988        }
1989        let has_fraction = pos != int_end;
1990        if pos >= bytes.len() {
1991            return Err(bad("Missing designator after number".to_string()));
1992        }
1993        let designator = bytes[pos];
1994        pos += 1;
1995        let num_str = &s[int_start..pos - 1];
1996
1997        // Resolve the designator to a slot; the letter 'M' is overloaded on `T`.
1998        let (field, name, allow_fraction): (Field, &str, bool) = match (designator, in_time) {
1999            (b'Y', false) => (Field::Years, "Y", false),
2000            (b'M', false) => (Field::Months, "M", false),
2001            (b'D', false) => (Field::Days, "D", false),
2002            (b'H', true) => (Field::Hours, "H", false),
2003            (b'M', true) => (Field::Minutes, "M", false),
2004            (b'S', true) => (Field::Seconds, "S", true),
2005            (other, _) => {
2006                return Err(bad(format!(
2007                    "Unexpected designator '{}' (in_time={})",
2008                    other as char, in_time
2009                )));
2010            }
2011        };
2012        if seen[field as usize] {
2013            return Err(bad(format!("Duplicate '{}' designator", name)));
2014        }
2015        seen[field as usize] = true;
2016        if has_fraction && !allow_fraction {
2017            return Err(bad(format!("'{}' must be an integer", name)));
2018        }
2019
2020        match field {
2021            Field::Seconds => {
2022                out.seconds = num_str
2023                    .parse()
2024                    .map_err(|_| bad(format!("Invalid seconds value '{}'", num_str)))?;
2025            }
2026            _ => {
2027                let v: u32 = num_str
2028                    .parse()
2029                    .map_err(|_| bad(format!("Invalid {} value '{}'", name, num_str)))?;
2030                match field {
2031                    Field::Years => out.years = v,
2032                    Field::Months => out.months = v,
2033                    Field::Days => out.days = v,
2034                    Field::Hours => out.hours = v,
2035                    Field::Minutes => out.minutes = v,
2036                    Field::Seconds => unreachable!(),
2037                }
2038            }
2039        }
2040    }
2041
2042    if !seen.iter().any(|&b| b) {
2043        return Err(bad(
2044            "Duration must contain at least one component".to_string()
2045        ));
2046    }
2047    let any_time = seen[Field::Hours as usize]
2048        || seen[Field::Minutes as usize]
2049        || seen[Field::Seconds as usize];
2050    if in_time && !any_time {
2051        return Err(bad(
2052            "Time designator 'T' requires at least one time component".to_string(),
2053        ));
2054    }
2055
2056    Ok(out)
2057}
2058
2059#[repr(u8)]
2060#[derive(Copy, Clone)]
2061enum Field {
2062    Years = 0,
2063    Months = 1,
2064    Days = 2,
2065    Hours = 3,
2066    Minutes = 4,
2067    Seconds = 5,
2068}
2069
2070/// Parse XSD dateTime
2071fn parse_datetime(s: &str) -> ValidationResult<DateTimeValue> {
2072    // Format: YYYY-MM-DDTHH:MM:SS(.sss)?(Z|[+-]HH:MM)?
2073    let (date_time, tz) = split_timezone(s);
2074    let parts: Vec<&str> = date_time.split('T').collect();
2075    if parts.len() != 2 {
2076        return Err(ValidationError::InvalidLexical {
2077            value: s.to_string(),
2078            type_name: "dateTime",
2079            message: "Missing 'T' separator".to_string(),
2080        });
2081    }
2082
2083    let date = parse_date_part(parts[0], "dateTime")?;
2084    let time = parse_time_part(parts[1], "dateTime")?;
2085
2086    // Normalize 24:00:00 → 00:00:00 of the next day (XSD Part 2 §3.3.8).
2087    // The literal `24:00:00` is only valid with minute=0 and second=0.
2088    let (year, month, day, hour) = if time.0 == 24 {
2089        if time.1 != 0 || time.2 != Decimal::from(0) {
2090            return Err(ValidationError::InvalidLexical {
2091                value: s.to_string(),
2092                type_name: "dateTime",
2093                message: "24:00:00 requires minute=0 and second=0".to_string(),
2094            });
2095        }
2096        let (y, m, d) = add_one_day(date.0, date.1, date.2);
2097        (y, m, d, 0u8)
2098    } else {
2099        (date.0, date.1, date.2, time.0)
2100    };
2101
2102    Ok(DateTimeValue {
2103        year,
2104        month,
2105        day,
2106        hour,
2107        minute: time.1,
2108        second: time.2,
2109        timezone: tz,
2110    })
2111}
2112
2113/// Increment a date by one day, handling month/year boundaries.
2114fn add_one_day(year: i32, month: u8, day: u8) -> (i32, u8, u8) {
2115    let dim = days_in_month(year, month);
2116    if (day as u32) < dim {
2117        (year, month, day + 1)
2118    } else if month < 12 {
2119        (year, month + 1, 1)
2120    } else {
2121        // Year 0 is excluded in XSD 1.0 but valid in XSD 1.1; either way,
2122        // the next day after 12/31 of year Y is 1/1 of year Y+1.
2123        (year + 1, 1, 1)
2124    }
2125}
2126
2127/// Days in a (Gregorian) month, accounting for leap years.
2128fn days_in_month(year: i32, month: u8) -> u32 {
2129    match month {
2130        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2131        4 | 6 | 9 | 11 => 30,
2132        2 => {
2133            let leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
2134            if leap {
2135                29
2136            } else {
2137                28
2138            }
2139        }
2140        _ => 0,
2141    }
2142}
2143
2144/// Parse XSD date
2145fn parse_date(s: &str) -> ValidationResult<DateValue> {
2146    let (date_str, tz) = split_timezone(s);
2147    let (year, month, day) = parse_date_part(date_str, "date")?;
2148    Ok(DateValue {
2149        year,
2150        month,
2151        day,
2152        timezone: tz,
2153    })
2154}
2155
2156/// Parse XSD time
2157fn parse_time(s: &str) -> ValidationResult<TimeValue> {
2158    let (time_str, tz) = split_timezone(s);
2159    let (mut hour, minute, second) = parse_time_part(time_str, "time")?;
2160    // Normalize 24:00:00 → 00:00:00 (XSD Part 2 §3.3.9).
2161    // The literal `24:00:00` is only valid with minute=0 and second=0.
2162    if hour == 24 {
2163        if minute != 0 || second != Decimal::from(0) {
2164            return Err(ValidationError::InvalidLexical {
2165                value: s.to_string(),
2166                type_name: "time",
2167                message: "24:00:00 requires minute=0 and second=0".to_string(),
2168            });
2169        }
2170        hour = 0;
2171    }
2172    Ok(TimeValue {
2173        hour,
2174        minute,
2175        second,
2176        timezone: tz,
2177    })
2178}
2179
2180/// Validate the lexical form of a yearFrag per XSD Part 2 §3.3.9:
2181/// `yearFrag ::= '-'? (([1-9] digit digit digitRep) | ('0' digit digit digit))`.
2182/// In particular: at least four digits, and five-or-more-digit forms must
2183/// start with a non-zero leading digit.
2184fn valid_year_lexical(year_str: &str) -> bool {
2185    let digits = year_str.strip_prefix('-').unwrap_or(year_str);
2186    if digits.is_empty() || !digits.chars().all(|c| c.is_ascii_digit()) {
2187        return false;
2188    }
2189    if digits.len() < 4 {
2190        return false;
2191    }
2192    if digits.len() > 4 && digits.starts_with('0') {
2193        return false;
2194    }
2195    true
2196}
2197
2198/// Parse date part (YYYY-MM-DD)
2199fn parse_date_part(s: &str, type_name: &'static str) -> ValidationResult<(i32, u8, u8)> {
2200    let parts: Vec<&str> = s.split('-').collect();
2201    let (year_str, year, month, day) = if s.starts_with('-') && parts.len() >= 4 {
2202        // Negative year
2203        let year_str = format!("-{}", parts[1]);
2204        let year: i32 = year_str
2205            .parse()
2206            .map_err(|_| ValidationError::InvalidLexical {
2207                value: s.to_string(),
2208                type_name,
2209                message: "Invalid year".to_string(),
2210            })?;
2211        let month: u8 = parts[2]
2212            .parse()
2213            .map_err(|_| ValidationError::InvalidLexical {
2214                value: s.to_string(),
2215                type_name,
2216                message: "Invalid month".to_string(),
2217            })?;
2218        let day: u8 = parts[3]
2219            .parse()
2220            .map_err(|_| ValidationError::InvalidLexical {
2221                value: s.to_string(),
2222                type_name,
2223                message: "Invalid day".to_string(),
2224            })?;
2225        (year_str, year, month, day)
2226    } else if parts.len() == 3 {
2227        let year_str = parts[0].to_string();
2228        let year: i32 = year_str
2229            .parse()
2230            .map_err(|_| ValidationError::InvalidLexical {
2231                value: s.to_string(),
2232                type_name,
2233                message: "Invalid year".to_string(),
2234            })?;
2235        let month: u8 = parts[1]
2236            .parse()
2237            .map_err(|_| ValidationError::InvalidLexical {
2238                value: s.to_string(),
2239                type_name,
2240                message: "Invalid month".to_string(),
2241            })?;
2242        let day: u8 = parts[2]
2243            .parse()
2244            .map_err(|_| ValidationError::InvalidLexical {
2245                value: s.to_string(),
2246                type_name,
2247                message: "Invalid day".to_string(),
2248            })?;
2249        (year_str, year, month, day)
2250    } else {
2251        return Err(ValidationError::InvalidLexical {
2252            value: s.to_string(),
2253            type_name,
2254            message: "Invalid date format".to_string(),
2255        });
2256    };
2257
2258    if !valid_year_lexical(&year_str) {
2259        return Err(ValidationError::InvalidLexical {
2260            value: s.to_string(),
2261            type_name,
2262            message: "Invalid year: must be 4+ digits, no leading zeros except single '0YYY' form"
2263                .to_string(),
2264        });
2265    }
2266
2267    if !(1..=12).contains(&month) || !(1..=31).contains(&day) {
2268        return Err(ValidationError::InvalidLexical {
2269            value: s.to_string(),
2270            type_name,
2271            message: "Date out of range".to_string(),
2272        });
2273    }
2274
2275    // Reject days that don't exist in the given month (e.g. Feb 29 of a
2276    // non-leap year, Apr 31). Spec §3.3.9 / §3.3.10 require lexical
2277    // representations to denote dates that exist in the proleptic
2278    // Gregorian calendar.
2279    if (day as u32) > days_in_month(year, month) {
2280        return Err(ValidationError::InvalidLexical {
2281            value: s.to_string(),
2282            type_name,
2283            message: "Day does not exist in the given month".to_string(),
2284        });
2285    }
2286
2287    Ok((year, month, day))
2288}
2289
2290/// Parse time part (HH:MM:SS(.sss)?)
2291///
2292/// Per Datatypes Part 2 §3.3.8 / §3.3.9 the hour, minute, and integer-second
2293/// fields must each be exactly two ASCII digits. No leading sign or other
2294/// non-digit characters are permitted (Rust's `from_str_radix` accepts a
2295/// leading `+` even for unsigned types — we reject it explicitly here).
2296fn parse_time_part(s: &str, type_name: &'static str) -> ValidationResult<(u8, u8, Decimal)> {
2297    let parts: Vec<&str> = s.split(':').collect();
2298    if parts.len() != 3 {
2299        return Err(ValidationError::InvalidLexical {
2300            value: s.to_string(),
2301            type_name,
2302            message: "Invalid time format".to_string(),
2303        });
2304    }
2305
2306    let is_two_ascii_digits = |t: &str| t.len() == 2 && t.bytes().all(|b| b.is_ascii_digit());
2307    if !is_two_ascii_digits(parts[0]) {
2308        return Err(ValidationError::InvalidLexical {
2309            value: s.to_string(),
2310            type_name,
2311            message: "Hour must be exactly two ASCII digits".to_string(),
2312        });
2313    }
2314    if !is_two_ascii_digits(parts[1]) {
2315        return Err(ValidationError::InvalidLexical {
2316            value: s.to_string(),
2317            type_name,
2318            message: "Minute must be exactly two ASCII digits".to_string(),
2319        });
2320    }
2321    let sec_str = parts[2];
2322    let (sec_int, sec_frac) = match sec_str.find('.') {
2323        Some(p) => (&sec_str[..p], Some(&sec_str[p + 1..])),
2324        None => (sec_str, None),
2325    };
2326    if !is_two_ascii_digits(sec_int) {
2327        return Err(ValidationError::InvalidLexical {
2328            value: s.to_string(),
2329            type_name,
2330            message: "Seconds integer part must be exactly two ASCII digits".to_string(),
2331        });
2332    }
2333    if let Some(frac) = sec_frac {
2334        if frac.is_empty() || !frac.bytes().all(|b| b.is_ascii_digit()) {
2335            return Err(ValidationError::InvalidLexical {
2336                value: s.to_string(),
2337                type_name,
2338                message: "Seconds fractional part must be one or more ASCII digits".to_string(),
2339            });
2340        }
2341    }
2342
2343    let hour: u8 = parts[0]
2344        .parse()
2345        .map_err(|_| ValidationError::InvalidLexical {
2346            value: s.to_string(),
2347            type_name,
2348            message: "Invalid hour".to_string(),
2349        })?;
2350    let minute: u8 = parts[1]
2351        .parse()
2352        .map_err(|_| ValidationError::InvalidLexical {
2353            value: s.to_string(),
2354            type_name,
2355            message: "Invalid minute".to_string(),
2356        })?;
2357    let second: Decimal = parts[2]
2358        .parse()
2359        .map_err(|_| ValidationError::InvalidLexical {
2360            value: s.to_string(),
2361            type_name,
2362            message: "Invalid second".to_string(),
2363        })?;
2364
2365    if hour > 24 || minute > 59 || second >= Decimal::from(60) {
2366        return Err(ValidationError::InvalidLexical {
2367            value: s.to_string(),
2368            type_name,
2369            message: "Time out of range".to_string(),
2370        });
2371    }
2372
2373    Ok((hour, minute, second))
2374}
2375
2376/// Split timezone from date/time string
2377fn split_timezone(s: &str) -> (&str, Option<TimezoneOffset>) {
2378    if let Some(stripped) = s.strip_suffix('Z') {
2379        (stripped, Some(TimezoneOffset::UTC))
2380    } else if let Some(pos) = s.rfind('+') {
2381        if pos > 0 && pos < s.len() - 1 {
2382            let tz_str = &s[pos + 1..];
2383            if let Some(tz) = parse_timezone_offset(tz_str, false) {
2384                return (&s[..pos], Some(tz));
2385            }
2386        }
2387        (s, None)
2388    } else if let Some(pos) = s.rfind('-') {
2389        // Make sure it's a timezone, not part of date
2390        if pos > 0 && pos < s.len() - 1 {
2391            let tz_str = &s[pos + 1..];
2392            if let Some(tz) = parse_timezone_offset(tz_str, true) {
2393                return (&s[..pos], Some(tz));
2394            }
2395        }
2396        (s, None)
2397    } else {
2398        (s, None)
2399    }
2400}
2401
2402/// Parse timezone offset (HH:MM)
2403fn parse_timezone_offset(s: &str, negative: bool) -> Option<TimezoneOffset> {
2404    let parts: Vec<&str> = s.split(':').collect();
2405    if parts.len() != 2 {
2406        return None;
2407    }
2408    let hours: i8 = parts[0].parse().ok()?;
2409    let minutes: i8 = parts[1].parse().ok()?;
2410    let offset = hours as i16 * 60 + minutes as i16;
2411    Some(TimezoneOffset(if negative { -offset } else { offset }))
2412}
2413
2414/// Parse gYearMonth
2415fn parse_gyearmonth(s: &str) -> ValidationResult<GYearMonthValue> {
2416    let (date_str, tz) = split_timezone(s);
2417    let parts: Vec<&str> = date_str.split('-').collect();
2418
2419    let (year_str, year, month) = if date_str.starts_with('-') && parts.len() >= 3 {
2420        let year_str = format!("-{}", parts[1]);
2421        let year: i32 = year_str
2422            .parse()
2423            .map_err(|_| ValidationError::InvalidLexical {
2424                value: s.to_string(),
2425                type_name: "gYearMonth",
2426                message: "Invalid year".to_string(),
2427            })?;
2428        let month: u8 = parts[2]
2429            .parse()
2430            .map_err(|_| ValidationError::InvalidLexical {
2431                value: s.to_string(),
2432                type_name: "gYearMonth",
2433                message: "Invalid month".to_string(),
2434            })?;
2435        (year_str, year, month)
2436    } else if parts.len() == 2 {
2437        let year_str = parts[0].to_string();
2438        let year: i32 = year_str
2439            .parse()
2440            .map_err(|_| ValidationError::InvalidLexical {
2441                value: s.to_string(),
2442                type_name: "gYearMonth",
2443                message: "Invalid year".to_string(),
2444            })?;
2445        let month: u8 = parts[1]
2446            .parse()
2447            .map_err(|_| ValidationError::InvalidLexical {
2448                value: s.to_string(),
2449                type_name: "gYearMonth",
2450                message: "Invalid month".to_string(),
2451            })?;
2452        (year_str, year, month)
2453    } else {
2454        return Err(ValidationError::InvalidLexical {
2455            value: s.to_string(),
2456            type_name: "gYearMonth",
2457            message: "Invalid format".to_string(),
2458        });
2459    };
2460
2461    if !valid_year_lexical(&year_str) {
2462        return Err(ValidationError::InvalidLexical {
2463            value: s.to_string(),
2464            type_name: "gYearMonth",
2465            message: "Invalid year lexical form".to_string(),
2466        });
2467    }
2468
2469    if !(1..=12).contains(&month) {
2470        return Err(ValidationError::InvalidLexical {
2471            value: s.to_string(),
2472            type_name: "gYearMonth",
2473            message: "Month out of range".to_string(),
2474        });
2475    }
2476
2477    Ok(GYearMonthValue {
2478        year,
2479        month,
2480        timezone: tz,
2481    })
2482}
2483
2484/// Parse gYear
2485fn parse_gyear(s: &str) -> ValidationResult<GYearValue> {
2486    let (year_str, tz) = split_timezone(s);
2487    let year: i32 = year_str
2488        .parse()
2489        .map_err(|_| ValidationError::InvalidLexical {
2490            value: s.to_string(),
2491            type_name: "gYear",
2492            message: "Invalid year".to_string(),
2493        })?;
2494    if !valid_year_lexical(year_str) {
2495        return Err(ValidationError::InvalidLexical {
2496            value: s.to_string(),
2497            type_name: "gYear",
2498            message: "Invalid year lexical form".to_string(),
2499        });
2500    }
2501    Ok(GYearValue { year, timezone: tz })
2502}
2503
2504/// Parse gMonthDay (--MM-DD)
2505fn parse_gmonthday(s: &str) -> ValidationResult<GMonthDayValue> {
2506    let (date_str, tz) = split_timezone(s);
2507    if !date_str.starts_with("--") {
2508        return Err(ValidationError::InvalidLexical {
2509            value: s.to_string(),
2510            type_name: "gMonthDay",
2511            message: "Must start with '--'".to_string(),
2512        });
2513    }
2514
2515    let rest = &date_str[2..];
2516    let parts: Vec<&str> = rest.split('-').collect();
2517    if parts.len() != 2 {
2518        return Err(ValidationError::InvalidLexical {
2519            value: s.to_string(),
2520            type_name: "gMonthDay",
2521            message: "Invalid format".to_string(),
2522        });
2523    }
2524
2525    let month: u8 = parts[0]
2526        .parse()
2527        .map_err(|_| ValidationError::InvalidLexical {
2528            value: s.to_string(),
2529            type_name: "gMonthDay",
2530            message: "Invalid month".to_string(),
2531        })?;
2532    let day: u8 = parts[1]
2533        .parse()
2534        .map_err(|_| ValidationError::InvalidLexical {
2535            value: s.to_string(),
2536            type_name: "gMonthDay",
2537            message: "Invalid day".to_string(),
2538        })?;
2539
2540    if !(1..=12).contains(&month) {
2541        return Err(ValidationError::InvalidLexical {
2542            value: s.to_string(),
2543            type_name: "gMonthDay",
2544            message: "Month out of range".to_string(),
2545        });
2546    }
2547    // Day must exist in the given month. Year is unspecified, so allow
2548    // Feb 29 (a leap year is possible) but still reject Feb 30/31, Apr 31, etc.
2549    let max_day = match month {
2550        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
2551        4 | 6 | 9 | 11 => 30,
2552        2 => 29,
2553        _ => unreachable!(),
2554    };
2555    if !(1..=max_day).contains(&day) {
2556        return Err(ValidationError::InvalidLexical {
2557            value: s.to_string(),
2558            type_name: "gMonthDay",
2559            message: "Day does not exist in the given month".to_string(),
2560        });
2561    }
2562
2563    Ok(GMonthDayValue {
2564        month,
2565        day,
2566        timezone: tz,
2567    })
2568}
2569
2570/// Parse gDay (---DD)
2571fn parse_gday(s: &str) -> ValidationResult<GDayValue> {
2572    let (day_str, tz) = split_timezone(s);
2573    if !day_str.starts_with("---") {
2574        return Err(ValidationError::InvalidLexical {
2575            value: s.to_string(),
2576            type_name: "gDay",
2577            message: "Must start with '---'".to_string(),
2578        });
2579    }
2580
2581    let day: u8 = day_str[3..]
2582        .parse()
2583        .map_err(|_| ValidationError::InvalidLexical {
2584            value: s.to_string(),
2585            type_name: "gDay",
2586            message: "Invalid day".to_string(),
2587        })?;
2588
2589    if !(1..=31).contains(&day) {
2590        return Err(ValidationError::InvalidLexical {
2591            value: s.to_string(),
2592            type_name: "gDay",
2593            message: "Day out of range".to_string(),
2594        });
2595    }
2596
2597    Ok(GDayValue { day, timezone: tz })
2598}
2599
2600/// Parse gMonth (--MM)
2601fn parse_gmonth(s: &str) -> ValidationResult<GMonthValue> {
2602    let (month_str, tz) = split_timezone(s);
2603    if !month_str.starts_with("--") {
2604        return Err(ValidationError::InvalidLexical {
2605            value: s.to_string(),
2606            type_name: "gMonth",
2607            message: "Must start with '--'".to_string(),
2608        });
2609    }
2610
2611    let month: u8 = month_str[2..]
2612        .parse()
2613        .map_err(|_| ValidationError::InvalidLexical {
2614            value: s.to_string(),
2615            type_name: "gMonth",
2616            message: "Invalid month".to_string(),
2617        })?;
2618
2619    if !(1..=12).contains(&month) {
2620        return Err(ValidationError::InvalidLexical {
2621            value: s.to_string(),
2622            type_name: "gMonth",
2623            message: "Month out of range".to_string(),
2624        });
2625    }
2626
2627    Ok(GMonthValue {
2628        month,
2629        timezone: tz,
2630    })
2631}
2632
2633/// Parse xs:yearMonthDuration (XSD 1.1)
2634/// Format: -?P(nY)?(nM)?
2635fn parse_year_month_duration(s: &str) -> ValidationResult<YearMonthDurationValue> {
2636    let (negative, rest) = if let Some(stripped) = s.strip_prefix('-') {
2637        (true, stripped)
2638    } else {
2639        (false, s)
2640    };
2641
2642    if !rest.starts_with('P') {
2643        return Err(ValidationError::InvalidLexical {
2644            value: s.to_string(),
2645            type_name: "yearMonthDuration",
2646            message: "Must start with 'P' (or '-P')".to_string(),
2647        });
2648    }
2649
2650    let content = &rest[1..];
2651    if content.is_empty() {
2652        return Err(ValidationError::InvalidLexical {
2653            value: s.to_string(),
2654            type_name: "yearMonthDuration",
2655            message: "Duration cannot be empty".to_string(),
2656        });
2657    }
2658
2659    // Must not contain day/time components
2660    if content.contains('D') || content.contains('T') {
2661        return Err(ValidationError::InvalidLexical {
2662            value: s.to_string(),
2663            type_name: "yearMonthDuration",
2664            message: "yearMonthDuration cannot contain day or time components".to_string(),
2665        });
2666    }
2667
2668    let mut years = 0u32;
2669    let mut months = 0u32;
2670    let mut current = content;
2671
2672    // Parse years
2673    if let Some(y_pos) = current.find('Y') {
2674        years = current[..y_pos]
2675            .parse()
2676            .map_err(|_| ValidationError::InvalidLexical {
2677                value: s.to_string(),
2678                type_name: "yearMonthDuration",
2679                message: "Invalid years value".to_string(),
2680            })?;
2681        current = &current[y_pos + 1..];
2682    }
2683
2684    // Parse months
2685    if let Some(m_pos) = current.find('M') {
2686        months = current[..m_pos]
2687            .parse()
2688            .map_err(|_| ValidationError::InvalidLexical {
2689                value: s.to_string(),
2690                type_name: "yearMonthDuration",
2691                message: "Invalid months value".to_string(),
2692            })?;
2693        current = &current[m_pos + 1..];
2694    }
2695
2696    // Check nothing remains
2697    if !current.is_empty() {
2698        return Err(ValidationError::InvalidLexical {
2699            value: s.to_string(),
2700            type_name: "yearMonthDuration",
2701            message: "Invalid duration format".to_string(),
2702        });
2703    }
2704
2705    Ok(YearMonthDurationValue {
2706        negative,
2707        years,
2708        months,
2709    })
2710}
2711
2712/// Parse xs:dayTimeDuration (XSD 1.1)
2713/// Format: -?P(nD)?(T(nH)?(nM)?(n(.n)?S)?)?
2714fn parse_day_time_duration(s: &str) -> ValidationResult<DayTimeDurationValue> {
2715    let (negative, rest) = if let Some(stripped) = s.strip_prefix('-') {
2716        (true, stripped)
2717    } else {
2718        (false, s)
2719    };
2720
2721    if !rest.starts_with('P') {
2722        return Err(ValidationError::InvalidLexical {
2723            value: s.to_string(),
2724            type_name: "dayTimeDuration",
2725            message: "Must start with 'P' (or '-P')".to_string(),
2726        });
2727    }
2728
2729    let content = &rest[1..];
2730    if content.is_empty() {
2731        return Err(ValidationError::InvalidLexical {
2732            value: s.to_string(),
2733            type_name: "dayTimeDuration",
2734            message: "Duration cannot be empty".to_string(),
2735        });
2736    }
2737
2738    // Must not contain year/month components (before T)
2739    let date_part = if let Some(t_pos) = content.find('T') {
2740        &content[..t_pos]
2741    } else {
2742        content
2743    };
2744    if date_part.contains('Y') || date_part.contains('M') {
2745        return Err(ValidationError::InvalidLexical {
2746            value: s.to_string(),
2747            type_name: "dayTimeDuration",
2748            message: "dayTimeDuration cannot contain year or month components".to_string(),
2749        });
2750    }
2751
2752    let mut days = 0u32;
2753    let mut hours = 0u32;
2754    let mut minutes = 0u32;
2755    let mut seconds = Decimal::ZERO;
2756    let mut current = content;
2757
2758    // Parse days
2759    if let Some(d_pos) = current.find('D') {
2760        days = current[..d_pos]
2761            .parse()
2762            .map_err(|_| ValidationError::InvalidLexical {
2763                value: s.to_string(),
2764                type_name: "dayTimeDuration",
2765                message: "Invalid days value".to_string(),
2766            })?;
2767        current = &current[d_pos + 1..];
2768    }
2769
2770    // Parse time part
2771    if let Some(stripped) = current.strip_prefix('T') {
2772        current = stripped;
2773
2774        // Parse hours
2775        if let Some(h_pos) = current.find('H') {
2776            hours = current[..h_pos]
2777                .parse()
2778                .map_err(|_| ValidationError::InvalidLexical {
2779                    value: s.to_string(),
2780                    type_name: "dayTimeDuration",
2781                    message: "Invalid hours value".to_string(),
2782                })?;
2783            current = &current[h_pos + 1..];
2784        }
2785
2786        // Parse minutes
2787        if let Some(m_pos) = current.find('M') {
2788            minutes = current[..m_pos]
2789                .parse()
2790                .map_err(|_| ValidationError::InvalidLexical {
2791                    value: s.to_string(),
2792                    type_name: "dayTimeDuration",
2793                    message: "Invalid minutes value".to_string(),
2794                })?;
2795            current = &current[m_pos + 1..];
2796        }
2797
2798        // Parse seconds
2799        if let Some(s_pos) = current.find('S') {
2800            seconds = current[..s_pos]
2801                .parse()
2802                .map_err(|_| ValidationError::InvalidLexical {
2803                    value: s.to_string(),
2804                    type_name: "dayTimeDuration",
2805                    message: "Invalid seconds value".to_string(),
2806                })?;
2807            current = &current[s_pos + 1..];
2808        }
2809    }
2810
2811    // Check nothing remains
2812    if !current.is_empty() {
2813        return Err(ValidationError::InvalidLexical {
2814            value: s.to_string(),
2815            type_name: "dayTimeDuration",
2816            message: "Invalid duration format".to_string(),
2817        });
2818    }
2819
2820    Ok(DayTimeDurationValue {
2821        negative,
2822        days,
2823        hours,
2824        minutes,
2825        seconds,
2826    })
2827}
2828
2829// ============================================================================
2830// XML Name Validation Helpers
2831// ============================================================================
2832
2833/// Check if a character is a valid XML name start character
2834fn is_name_start_char(c: char) -> bool {
2835    matches!(c,
2836        'A'..='Z' | 'a'..='z' | '_' | ':' |
2837        '\u{C0}'..='\u{D6}' | '\u{D8}'..='\u{F6}' | '\u{F8}'..='\u{2FF}' |
2838        '\u{370}'..='\u{37D}' | '\u{37F}'..='\u{1FFF}' | '\u{200C}'..='\u{200D}' |
2839        '\u{2070}'..='\u{218F}' | '\u{2C00}'..='\u{2FEF}' | '\u{3001}'..='\u{D7FF}' |
2840        '\u{F900}'..='\u{FDCF}' | '\u{FDF0}'..='\u{FFFD}' | '\u{10000}'..='\u{EFFFF}'
2841    )
2842}
2843
2844/// Check if a character is a valid XML name character
2845fn is_name_char(c: char) -> bool {
2846    is_name_start_char(c)
2847        || matches!(c,
2848            '-' | '.' | '0'..='9' | '\u{B7}' |
2849            '\u{0300}'..='\u{036F}' | '\u{203F}'..='\u{2040}'
2850        )
2851}
2852
2853/// Check if a character is a valid NCName start character (no colon)
2854fn is_ncname_start_char(c: char) -> bool {
2855    is_name_start_char(c) && c != ':'
2856}
2857
2858/// Check if a character is a valid NCName character (no colon)
2859fn is_ncname_char(c: char) -> bool {
2860    is_name_char(c) && c != ':'
2861}
2862
2863/// Validate an XML Name
2864fn is_valid_name(s: &str) -> bool {
2865    let mut chars = s.chars();
2866    match chars.next() {
2867        Some(c) if is_name_start_char(c) => chars.all(is_name_char),
2868        _ => false,
2869    }
2870}
2871
2872/// Validate an NCName (Name without colons)
2873fn is_valid_ncname(s: &str) -> bool {
2874    let mut chars = s.chars();
2875    match chars.next() {
2876        Some(c) if is_ncname_start_char(c) => chars.all(is_ncname_char),
2877        _ => false,
2878    }
2879}
2880
2881/// Validate a language tag (RFC 3066 / BCP 47 simplified)
2882pub fn is_valid_language(s: &str) -> bool {
2883    if s.is_empty() {
2884        return false;
2885    }
2886    let parts: Vec<&str> = s.split('-').collect();
2887    if parts.is_empty() {
2888        return false;
2889    }
2890    // First subtag: 1-8 letters
2891    let first = parts[0];
2892    if first.is_empty() || first.len() > 8 || !first.chars().all(|c| c.is_ascii_alphabetic()) {
2893        return false;
2894    }
2895    // Subsequent subtags: 1-8 alphanumeric characters
2896    for part in parts.iter().skip(1) {
2897        if part.is_empty() || part.len() > 8 || !part.chars().all(|c| c.is_ascii_alphanumeric()) {
2898            return false;
2899        }
2900    }
2901    true
2902}
2903
2904// ============================================================================
2905// Integer Hierarchy Validators
2906// ============================================================================
2907
2908/// Validator for xs:long
2909pub struct LongValidator;
2910
2911impl TypeValidator for LongValidator {
2912    fn type_name(&self) -> &'static str {
2913        "long"
2914    }
2915    fn type_code(&self) -> XmlTypeCode {
2916        XmlTypeCode::Long
2917    }
2918    fn primitive_type(&self) -> PrimitiveTypeCode {
2919        PrimitiveTypeCode::Decimal
2920    }
2921    fn whitespace(&self) -> WhitespaceMode {
2922        WhitespaceMode::Collapse
2923    }
2924
2925    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
2926        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
2927        normalized
2928            .parse::<i64>()
2929            .map(|v| {
2930                XmlValue::new(
2931                    XmlTypeCode::Long,
2932                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
2933                )
2934            })
2935            .map_err(|e| ValidationError::InvalidLexical {
2936                value: value.to_string(),
2937                type_name: "long",
2938                message: e.to_string(),
2939            })
2940    }
2941
2942    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
2943        let result = self.validate(value)?;
2944        if let Some(d) = result.as_decimal() {
2945            facets.validate_decimal(&d)?;
2946        }
2947        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
2948        facets.validate_string_patterns_enums(&normalized)?;
2949        Ok(result)
2950    }
2951
2952    fn facet_applicable(&self, facet: &str) -> bool {
2953        matches!(
2954            facet,
2955            "totalDigits"
2956                | "fractionDigits"
2957                | "minInclusive"
2958                | "maxInclusive"
2959                | "minExclusive"
2960                | "maxExclusive"
2961                | "pattern"
2962                | "enumeration"
2963        )
2964    }
2965}
2966
2967/// Validator for xs:int
2968pub struct IntValidator;
2969
2970impl TypeValidator for IntValidator {
2971    fn type_name(&self) -> &'static str {
2972        "int"
2973    }
2974    fn type_code(&self) -> XmlTypeCode {
2975        XmlTypeCode::Int
2976    }
2977    fn primitive_type(&self) -> PrimitiveTypeCode {
2978        PrimitiveTypeCode::Decimal
2979    }
2980    fn whitespace(&self) -> WhitespaceMode {
2981        WhitespaceMode::Collapse
2982    }
2983
2984    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
2985        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
2986        normalized
2987            .parse::<i32>()
2988            .map(|v| {
2989                XmlValue::new(
2990                    XmlTypeCode::Int,
2991                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
2992                )
2993            })
2994            .map_err(|e| ValidationError::InvalidLexical {
2995                value: value.to_string(),
2996                type_name: "int",
2997                message: e.to_string(),
2998            })
2999    }
3000
3001    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3002        let result = self.validate(value)?;
3003        if let Some(d) = result.as_decimal() {
3004            facets.validate_decimal(&d)?;
3005        }
3006        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3007        facets.validate_string_patterns_enums(&normalized)?;
3008        Ok(result)
3009    }
3010
3011    fn facet_applicable(&self, facet: &str) -> bool {
3012        matches!(
3013            facet,
3014            "totalDigits"
3015                | "fractionDigits"
3016                | "minInclusive"
3017                | "maxInclusive"
3018                | "minExclusive"
3019                | "maxExclusive"
3020                | "pattern"
3021                | "enumeration"
3022        )
3023    }
3024}
3025
3026/// Validator for xs:short
3027pub struct ShortValidator;
3028
3029impl TypeValidator for ShortValidator {
3030    fn type_name(&self) -> &'static str {
3031        "short"
3032    }
3033    fn type_code(&self) -> XmlTypeCode {
3034        XmlTypeCode::Short
3035    }
3036    fn primitive_type(&self) -> PrimitiveTypeCode {
3037        PrimitiveTypeCode::Decimal
3038    }
3039    fn whitespace(&self) -> WhitespaceMode {
3040        WhitespaceMode::Collapse
3041    }
3042
3043    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3044        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3045        normalized
3046            .parse::<i16>()
3047            .map(|v| {
3048                XmlValue::new(
3049                    XmlTypeCode::Short,
3050                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
3051                )
3052            })
3053            .map_err(|e| ValidationError::InvalidLexical {
3054                value: value.to_string(),
3055                type_name: "short",
3056                message: e.to_string(),
3057            })
3058    }
3059
3060    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3061        let result = self.validate(value)?;
3062        if let Some(d) = result.as_decimal() {
3063            facets.validate_decimal(&d)?;
3064        }
3065        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3066        facets.validate_string_patterns_enums(&normalized)?;
3067        Ok(result)
3068    }
3069
3070    fn facet_applicable(&self, facet: &str) -> bool {
3071        matches!(
3072            facet,
3073            "totalDigits"
3074                | "fractionDigits"
3075                | "minInclusive"
3076                | "maxInclusive"
3077                | "minExclusive"
3078                | "maxExclusive"
3079                | "pattern"
3080                | "enumeration"
3081        )
3082    }
3083}
3084
3085/// Validator for xs:byte
3086pub struct ByteValidator;
3087
3088impl TypeValidator for ByteValidator {
3089    fn type_name(&self) -> &'static str {
3090        "byte"
3091    }
3092    fn type_code(&self) -> XmlTypeCode {
3093        XmlTypeCode::Byte
3094    }
3095    fn primitive_type(&self) -> PrimitiveTypeCode {
3096        PrimitiveTypeCode::Decimal
3097    }
3098    fn whitespace(&self) -> WhitespaceMode {
3099        WhitespaceMode::Collapse
3100    }
3101
3102    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3103        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3104        normalized
3105            .parse::<i8>()
3106            .map(|v| {
3107                XmlValue::new(
3108                    XmlTypeCode::Byte,
3109                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
3110                )
3111            })
3112            .map_err(|e| ValidationError::InvalidLexical {
3113                value: value.to_string(),
3114                type_name: "byte",
3115                message: e.to_string(),
3116            })
3117    }
3118
3119    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3120        let result = self.validate(value)?;
3121        if let Some(d) = result.as_decimal() {
3122            facets.validate_decimal(&d)?;
3123        }
3124        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3125        facets.validate_string_patterns_enums(&normalized)?;
3126        Ok(result)
3127    }
3128
3129    fn facet_applicable(&self, facet: &str) -> bool {
3130        matches!(
3131            facet,
3132            "totalDigits"
3133                | "fractionDigits"
3134                | "minInclusive"
3135                | "maxInclusive"
3136                | "minExclusive"
3137                | "maxExclusive"
3138                | "pattern"
3139                | "enumeration"
3140        )
3141    }
3142}
3143
3144/// Validator for xs:nonNegativeInteger
3145pub struct NonNegativeIntegerValidator;
3146
3147impl TypeValidator for NonNegativeIntegerValidator {
3148    fn type_name(&self) -> &'static str {
3149        "nonNegativeInteger"
3150    }
3151    fn type_code(&self) -> XmlTypeCode {
3152        XmlTypeCode::NonNegativeInteger
3153    }
3154    fn primitive_type(&self) -> PrimitiveTypeCode {
3155        PrimitiveTypeCode::Decimal
3156    }
3157    fn whitespace(&self) -> WhitespaceMode {
3158        WhitespaceMode::Collapse
3159    }
3160
3161    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3162        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3163        let bigint = normalized
3164            .parse::<BigInt>()
3165            .map_err(|e| ValidationError::InvalidLexical {
3166                value: value.to_string(),
3167                type_name: "nonNegativeInteger",
3168                message: e.to_string(),
3169            })?;
3170        if bigint < BigInt::from(0) {
3171            return Err(ValidationError::RangeError {
3172                value: value.to_string(),
3173                type_name: "nonNegativeInteger",
3174            });
3175        }
3176        Ok(XmlValue::new(
3177            XmlTypeCode::NonNegativeInteger,
3178            XmlValueKind::Atomic(XmlAtomicValue::Integer(bigint)),
3179        ))
3180    }
3181
3182    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3183        let result = self.validate(value)?;
3184        if let Some(d) = result.as_decimal() {
3185            facets.validate_decimal(&d)?;
3186        }
3187        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3188        facets.validate_string_patterns_enums(&normalized)?;
3189        Ok(result)
3190    }
3191
3192    fn facet_applicable(&self, facet: &str) -> bool {
3193        matches!(
3194            facet,
3195            "totalDigits"
3196                | "fractionDigits"
3197                | "minInclusive"
3198                | "maxInclusive"
3199                | "minExclusive"
3200                | "maxExclusive"
3201                | "pattern"
3202                | "enumeration"
3203        )
3204    }
3205}
3206
3207/// Validator for xs:positiveInteger
3208pub struct PositiveIntegerValidator;
3209
3210impl TypeValidator for PositiveIntegerValidator {
3211    fn type_name(&self) -> &'static str {
3212        "positiveInteger"
3213    }
3214    fn type_code(&self) -> XmlTypeCode {
3215        XmlTypeCode::PositiveInteger
3216    }
3217    fn primitive_type(&self) -> PrimitiveTypeCode {
3218        PrimitiveTypeCode::Decimal
3219    }
3220    fn whitespace(&self) -> WhitespaceMode {
3221        WhitespaceMode::Collapse
3222    }
3223
3224    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3225        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3226        let bigint = normalized
3227            .parse::<BigInt>()
3228            .map_err(|e| ValidationError::InvalidLexical {
3229                value: value.to_string(),
3230                type_name: "positiveInteger",
3231                message: e.to_string(),
3232            })?;
3233        if bigint <= BigInt::from(0) {
3234            return Err(ValidationError::RangeError {
3235                value: value.to_string(),
3236                type_name: "positiveInteger",
3237            });
3238        }
3239        Ok(XmlValue::new(
3240            XmlTypeCode::PositiveInteger,
3241            XmlValueKind::Atomic(XmlAtomicValue::Integer(bigint)),
3242        ))
3243    }
3244
3245    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3246        let result = self.validate(value)?;
3247        if let Some(d) = result.as_decimal() {
3248            facets.validate_decimal(&d)?;
3249        }
3250        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3251        facets.validate_string_patterns_enums(&normalized)?;
3252        Ok(result)
3253    }
3254
3255    fn facet_applicable(&self, facet: &str) -> bool {
3256        matches!(
3257            facet,
3258            "totalDigits"
3259                | "fractionDigits"
3260                | "minInclusive"
3261                | "maxInclusive"
3262                | "minExclusive"
3263                | "maxExclusive"
3264                | "pattern"
3265                | "enumeration"
3266        )
3267    }
3268}
3269
3270/// Validator for xs:nonPositiveInteger
3271pub struct NonPositiveIntegerValidator;
3272
3273impl TypeValidator for NonPositiveIntegerValidator {
3274    fn type_name(&self) -> &'static str {
3275        "nonPositiveInteger"
3276    }
3277    fn type_code(&self) -> XmlTypeCode {
3278        XmlTypeCode::NonPositiveInteger
3279    }
3280    fn primitive_type(&self) -> PrimitiveTypeCode {
3281        PrimitiveTypeCode::Decimal
3282    }
3283    fn whitespace(&self) -> WhitespaceMode {
3284        WhitespaceMode::Collapse
3285    }
3286
3287    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3288        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3289        let bigint = normalized
3290            .parse::<BigInt>()
3291            .map_err(|e| ValidationError::InvalidLexical {
3292                value: value.to_string(),
3293                type_name: "nonPositiveInteger",
3294                message: e.to_string(),
3295            })?;
3296        if bigint > BigInt::from(0) {
3297            return Err(ValidationError::RangeError {
3298                value: value.to_string(),
3299                type_name: "nonPositiveInteger",
3300            });
3301        }
3302        Ok(XmlValue::new(
3303            XmlTypeCode::NonPositiveInteger,
3304            XmlValueKind::Atomic(XmlAtomicValue::Integer(bigint)),
3305        ))
3306    }
3307
3308    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3309        let result = self.validate(value)?;
3310        if let Some(d) = result.as_decimal() {
3311            facets.validate_decimal(&d)?;
3312        }
3313        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3314        facets.validate_string_patterns_enums(&normalized)?;
3315        Ok(result)
3316    }
3317
3318    fn facet_applicable(&self, facet: &str) -> bool {
3319        matches!(
3320            facet,
3321            "totalDigits"
3322                | "fractionDigits"
3323                | "minInclusive"
3324                | "maxInclusive"
3325                | "minExclusive"
3326                | "maxExclusive"
3327                | "pattern"
3328                | "enumeration"
3329        )
3330    }
3331}
3332
3333/// Validator for xs:negativeInteger
3334pub struct NegativeIntegerValidator;
3335
3336impl TypeValidator for NegativeIntegerValidator {
3337    fn type_name(&self) -> &'static str {
3338        "negativeInteger"
3339    }
3340    fn type_code(&self) -> XmlTypeCode {
3341        XmlTypeCode::NegativeInteger
3342    }
3343    fn primitive_type(&self) -> PrimitiveTypeCode {
3344        PrimitiveTypeCode::Decimal
3345    }
3346    fn whitespace(&self) -> WhitespaceMode {
3347        WhitespaceMode::Collapse
3348    }
3349
3350    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3351        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3352        let bigint = normalized
3353            .parse::<BigInt>()
3354            .map_err(|e| ValidationError::InvalidLexical {
3355                value: value.to_string(),
3356                type_name: "negativeInteger",
3357                message: e.to_string(),
3358            })?;
3359        if bigint >= BigInt::from(0) {
3360            return Err(ValidationError::RangeError {
3361                value: value.to_string(),
3362                type_name: "negativeInteger",
3363            });
3364        }
3365        Ok(XmlValue::new(
3366            XmlTypeCode::NegativeInteger,
3367            XmlValueKind::Atomic(XmlAtomicValue::Integer(bigint)),
3368        ))
3369    }
3370
3371    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3372        let result = self.validate(value)?;
3373        if let Some(d) = result.as_decimal() {
3374            facets.validate_decimal(&d)?;
3375        }
3376        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3377        facets.validate_string_patterns_enums(&normalized)?;
3378        Ok(result)
3379    }
3380
3381    fn facet_applicable(&self, facet: &str) -> bool {
3382        matches!(
3383            facet,
3384            "totalDigits"
3385                | "fractionDigits"
3386                | "minInclusive"
3387                | "maxInclusive"
3388                | "minExclusive"
3389                | "maxExclusive"
3390                | "pattern"
3391                | "enumeration"
3392        )
3393    }
3394}
3395
3396/// Validator for xs:unsignedLong
3397pub struct UnsignedLongValidator;
3398
3399impl TypeValidator for UnsignedLongValidator {
3400    fn type_name(&self) -> &'static str {
3401        "unsignedLong"
3402    }
3403    fn type_code(&self) -> XmlTypeCode {
3404        XmlTypeCode::UnsignedLong
3405    }
3406    fn primitive_type(&self) -> PrimitiveTypeCode {
3407        PrimitiveTypeCode::Decimal
3408    }
3409    fn whitespace(&self) -> WhitespaceMode {
3410        WhitespaceMode::Collapse
3411    }
3412
3413    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3414        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3415        normalized
3416            .parse::<u64>()
3417            .map(|v| {
3418                XmlValue::new(
3419                    XmlTypeCode::UnsignedLong,
3420                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
3421                )
3422            })
3423            .map_err(|e| ValidationError::InvalidLexical {
3424                value: value.to_string(),
3425                type_name: "unsignedLong",
3426                message: e.to_string(),
3427            })
3428    }
3429
3430    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3431        let result = self.validate(value)?;
3432        if let Some(d) = result.as_decimal() {
3433            facets.validate_decimal(&d)?;
3434        }
3435        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3436        facets.validate_string_patterns_enums(&normalized)?;
3437        Ok(result)
3438    }
3439
3440    fn facet_applicable(&self, facet: &str) -> bool {
3441        matches!(
3442            facet,
3443            "totalDigits"
3444                | "fractionDigits"
3445                | "minInclusive"
3446                | "maxInclusive"
3447                | "minExclusive"
3448                | "maxExclusive"
3449                | "pattern"
3450                | "enumeration"
3451        )
3452    }
3453}
3454
3455/// Validator for xs:unsignedInt
3456pub struct UnsignedIntValidator;
3457
3458impl TypeValidator for UnsignedIntValidator {
3459    fn type_name(&self) -> &'static str {
3460        "unsignedInt"
3461    }
3462    fn type_code(&self) -> XmlTypeCode {
3463        XmlTypeCode::UnsignedInt
3464    }
3465    fn primitive_type(&self) -> PrimitiveTypeCode {
3466        PrimitiveTypeCode::Decimal
3467    }
3468    fn whitespace(&self) -> WhitespaceMode {
3469        WhitespaceMode::Collapse
3470    }
3471
3472    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3473        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3474        normalized
3475            .parse::<u32>()
3476            .map(|v| {
3477                XmlValue::new(
3478                    XmlTypeCode::UnsignedInt,
3479                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
3480                )
3481            })
3482            .map_err(|e| ValidationError::InvalidLexical {
3483                value: value.to_string(),
3484                type_name: "unsignedInt",
3485                message: e.to_string(),
3486            })
3487    }
3488
3489    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3490        let result = self.validate(value)?;
3491        if let Some(d) = result.as_decimal() {
3492            facets.validate_decimal(&d)?;
3493        }
3494        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3495        facets.validate_string_patterns_enums(&normalized)?;
3496        Ok(result)
3497    }
3498
3499    fn facet_applicable(&self, facet: &str) -> bool {
3500        matches!(
3501            facet,
3502            "totalDigits"
3503                | "fractionDigits"
3504                | "minInclusive"
3505                | "maxInclusive"
3506                | "minExclusive"
3507                | "maxExclusive"
3508                | "pattern"
3509                | "enumeration"
3510        )
3511    }
3512}
3513
3514/// Validator for xs:unsignedShort
3515pub struct UnsignedShortValidator;
3516
3517impl TypeValidator for UnsignedShortValidator {
3518    fn type_name(&self) -> &'static str {
3519        "unsignedShort"
3520    }
3521    fn type_code(&self) -> XmlTypeCode {
3522        XmlTypeCode::UnsignedShort
3523    }
3524    fn primitive_type(&self) -> PrimitiveTypeCode {
3525        PrimitiveTypeCode::Decimal
3526    }
3527    fn whitespace(&self) -> WhitespaceMode {
3528        WhitespaceMode::Collapse
3529    }
3530
3531    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3532        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3533        normalized
3534            .parse::<u16>()
3535            .map(|v| {
3536                XmlValue::new(
3537                    XmlTypeCode::UnsignedShort,
3538                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
3539                )
3540            })
3541            .map_err(|e| ValidationError::InvalidLexical {
3542                value: value.to_string(),
3543                type_name: "unsignedShort",
3544                message: e.to_string(),
3545            })
3546    }
3547
3548    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3549        let result = self.validate(value)?;
3550        if let Some(d) = result.as_decimal() {
3551            facets.validate_decimal(&d)?;
3552        }
3553        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3554        facets.validate_string_patterns_enums(&normalized)?;
3555        Ok(result)
3556    }
3557
3558    fn facet_applicable(&self, facet: &str) -> bool {
3559        matches!(
3560            facet,
3561            "totalDigits"
3562                | "fractionDigits"
3563                | "minInclusive"
3564                | "maxInclusive"
3565                | "minExclusive"
3566                | "maxExclusive"
3567                | "pattern"
3568                | "enumeration"
3569        )
3570    }
3571}
3572
3573/// Validator for xs:unsignedByte
3574pub struct UnsignedByteValidator;
3575
3576impl TypeValidator for UnsignedByteValidator {
3577    fn type_name(&self) -> &'static str {
3578        "unsignedByte"
3579    }
3580    fn type_code(&self) -> XmlTypeCode {
3581        XmlTypeCode::UnsignedByte
3582    }
3583    fn primitive_type(&self) -> PrimitiveTypeCode {
3584        PrimitiveTypeCode::Decimal
3585    }
3586    fn whitespace(&self) -> WhitespaceMode {
3587        WhitespaceMode::Collapse
3588    }
3589
3590    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3591        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3592        normalized
3593            .parse::<u8>()
3594            .map(|v| {
3595                XmlValue::new(
3596                    XmlTypeCode::UnsignedByte,
3597                    XmlValueKind::Atomic(XmlAtomicValue::Integer(BigInt::from(v))),
3598                )
3599            })
3600            .map_err(|e| ValidationError::InvalidLexical {
3601                value: value.to_string(),
3602                type_name: "unsignedByte",
3603                message: e.to_string(),
3604            })
3605    }
3606
3607    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3608        let result = self.validate(value)?;
3609        if let Some(d) = result.as_decimal() {
3610            facets.validate_decimal(&d)?;
3611        }
3612        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3613        facets.validate_string_patterns_enums(&normalized)?;
3614        Ok(result)
3615    }
3616
3617    fn facet_applicable(&self, facet: &str) -> bool {
3618        matches!(
3619            facet,
3620            "totalDigits"
3621                | "fractionDigits"
3622                | "minInclusive"
3623                | "maxInclusive"
3624                | "minExclusive"
3625                | "maxExclusive"
3626                | "pattern"
3627                | "enumeration"
3628        )
3629    }
3630}
3631
3632// ============================================================================
3633// QName and NOTATION Validators
3634// ============================================================================
3635
3636/// Validator for xs:QName
3637/// Note: Full QName validation requires namespace context. This validator
3638/// validates the lexical form (prefix:localname or just localname) and stores
3639/// the validated string. Namespace resolution must be done separately.
3640pub struct QNameValidator;
3641
3642impl TypeValidator for QNameValidator {
3643    fn type_name(&self) -> &'static str {
3644        "QName"
3645    }
3646    fn type_code(&self) -> XmlTypeCode {
3647        XmlTypeCode::QName
3648    }
3649    fn primitive_type(&self) -> PrimitiveTypeCode {
3650        PrimitiveTypeCode::QName
3651    }
3652    fn whitespace(&self) -> WhitespaceMode {
3653        WhitespaceMode::Collapse
3654    }
3655
3656    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3657        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3658        // QName format: prefix:localname or just localname
3659        // Both parts must be NCNames
3660        let (prefix, local) = if let Some(colon_pos) = normalized.find(':') {
3661            let prefix = &normalized[..colon_pos];
3662            let local = &normalized[colon_pos + 1..];
3663            (Some(prefix), local)
3664        } else {
3665            (None, normalized.as_str())
3666        };
3667
3668        // Validate prefix is NCName (if present)
3669        if let Some(p) = prefix {
3670            if !is_valid_ncname(p) {
3671                return Err(ValidationError::InvalidLexical {
3672                    value: value.to_string(),
3673                    type_name: "QName",
3674                    message: format!("Invalid prefix '{}' (must be NCName)", p),
3675                });
3676            }
3677        }
3678
3679        // Validate localname is NCName
3680        if !is_valid_ncname(local) {
3681            return Err(ValidationError::InvalidLexical {
3682                value: value.to_string(),
3683                type_name: "QName",
3684                message: format!("Invalid local name '{}' (must be NCName)", local),
3685            });
3686        }
3687
3688        // Store as string for now - namespace resolution requires NamespaceContext
3689        // which is not available at basic validation time
3690        Ok(XmlValue::new(
3691            XmlTypeCode::QName,
3692            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
3693        ))
3694    }
3695
3696    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3697        let result = self.validate(value)?;
3698        // §cvc-length-valid §1.3 / §cvc-minLength-valid §1.3 / §cvc-maxLength-valid §1.3:
3699        // For QName any value is always facet-valid w.r.t. length/minLength/maxLength.
3700        // Only pattern and enumeration constrain the value.
3701        facets.validate_string_patterns_enums(&result.to_string_value())?;
3702        Ok(result)
3703    }
3704
3705    fn facet_applicable(&self, facet: &str) -> bool {
3706        matches!(
3707            facet,
3708            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
3709        )
3710    }
3711}
3712
3713/// Validator for xs:NOTATION
3714/// Note: NOTATION validation is similar to QName but the notation must be declared in the schema.
3715/// This validator validates the lexical form only; notation declaration checking must be done separately.
3716pub struct NotationValidator;
3717
3718impl TypeValidator for NotationValidator {
3719    fn type_name(&self) -> &'static str {
3720        "NOTATION"
3721    }
3722    fn type_code(&self) -> XmlTypeCode {
3723        XmlTypeCode::Notation
3724    }
3725    fn primitive_type(&self) -> PrimitiveTypeCode {
3726        PrimitiveTypeCode::Notation
3727    }
3728    fn whitespace(&self) -> WhitespaceMode {
3729        WhitespaceMode::Collapse
3730    }
3731
3732    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3733        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3734        // NOTATION format: same as QName
3735        let (prefix, local) = if let Some(colon_pos) = normalized.find(':') {
3736            let prefix = &normalized[..colon_pos];
3737            let local = &normalized[colon_pos + 1..];
3738            (Some(prefix), local)
3739        } else {
3740            (None, normalized.as_str())
3741        };
3742
3743        if let Some(p) = prefix {
3744            if !is_valid_ncname(p) {
3745                return Err(ValidationError::InvalidLexical {
3746                    value: value.to_string(),
3747                    type_name: "NOTATION",
3748                    message: format!("Invalid prefix '{}' (must be NCName)", p),
3749                });
3750            }
3751        }
3752
3753        if !is_valid_ncname(local) {
3754            return Err(ValidationError::InvalidLexical {
3755                value: value.to_string(),
3756                type_name: "NOTATION",
3757                message: format!("Invalid local name '{}' (must be NCName)", local),
3758            });
3759        }
3760
3761        // Store as string for now - notation declaration checking requires schema context
3762        Ok(XmlValue::new(
3763            XmlTypeCode::Notation,
3764            XmlValueKind::Atomic(XmlAtomicValue::String(normalized)),
3765        ))
3766    }
3767
3768    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3769        let result = self.validate(value)?;
3770        // §cvc-length-valid §1.3 / §cvc-minLength-valid §1.3 / §cvc-maxLength-valid §1.3:
3771        // For NOTATION any value is always facet-valid w.r.t. length/minLength/maxLength.
3772        // Only pattern and enumeration constrain the value.
3773        facets.validate_string_patterns_enums(&result.to_string_value())?;
3774        Ok(result)
3775    }
3776
3777    fn facet_applicable(&self, facet: &str) -> bool {
3778        matches!(
3779            facet,
3780            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
3781        )
3782    }
3783}
3784
3785// ============================================================================
3786// List Type Validators
3787// ============================================================================
3788
3789/// Validator for xs:NMTOKENS (list of NMTOKEN)
3790pub struct NmTokensValidator;
3791
3792impl TypeValidator for NmTokensValidator {
3793    fn type_name(&self) -> &'static str {
3794        "NMTOKENS"
3795    }
3796    fn type_code(&self) -> XmlTypeCode {
3797        XmlTypeCode::NmTokens
3798    }
3799    fn primitive_type(&self) -> PrimitiveTypeCode {
3800        PrimitiveTypeCode::String
3801    }
3802    fn whitespace(&self) -> WhitespaceMode {
3803        WhitespaceMode::Collapse
3804    }
3805
3806    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3807        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3808        if normalized.is_empty() {
3809            return Err(ValidationError::InvalidLexical {
3810                value: value.to_string(),
3811                type_name: "NMTOKENS",
3812                message: "NMTOKENS must contain at least one token".to_string(),
3813            });
3814        }
3815
3816        let mut items = Vec::new();
3817        for token in normalized.split_whitespace() {
3818            if !token.chars().all(is_name_char) {
3819                return Err(ValidationError::InvalidLexical {
3820                    value: value.to_string(),
3821                    type_name: "NMTOKENS",
3822                    message: format!("Invalid NMTOKEN: '{}'", token),
3823                });
3824            }
3825            items.push(XmlAtomicValue::String(token.to_string()));
3826        }
3827
3828        Ok(XmlValue::new(
3829            XmlTypeCode::NmTokens,
3830            XmlValueKind::List {
3831                item_type: XmlTypeCode::NmToken,
3832                items,
3833            },
3834        ))
3835    }
3836
3837    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3838        let result = self.validate(value)?;
3839        if let XmlValueKind::List { items, .. } = &result.value {
3840            facets.validate_list_length(items.len() as u64)?;
3841        }
3842        Ok(result)
3843    }
3844
3845    fn facet_applicable(&self, facet: &str) -> bool {
3846        matches!(
3847            facet,
3848            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
3849        )
3850    }
3851}
3852
3853/// Validator for xs:IDREFS (list of IDREF)
3854pub struct IdRefsValidator;
3855
3856impl TypeValidator for IdRefsValidator {
3857    fn type_name(&self) -> &'static str {
3858        "IDREFS"
3859    }
3860    fn type_code(&self) -> XmlTypeCode {
3861        XmlTypeCode::IdRefs
3862    }
3863    fn primitive_type(&self) -> PrimitiveTypeCode {
3864        PrimitiveTypeCode::String
3865    }
3866    fn whitespace(&self) -> WhitespaceMode {
3867        WhitespaceMode::Collapse
3868    }
3869
3870    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3871        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3872        if normalized.is_empty() {
3873            return Err(ValidationError::InvalidLexical {
3874                value: value.to_string(),
3875                type_name: "IDREFS",
3876                message: "IDREFS must contain at least one IDREF".to_string(),
3877            });
3878        }
3879
3880        let mut items = Vec::new();
3881        for token in normalized.split_whitespace() {
3882            if !is_valid_ncname(token) {
3883                return Err(ValidationError::InvalidLexical {
3884                    value: value.to_string(),
3885                    type_name: "IDREFS",
3886                    message: format!("Invalid IDREF: '{}' (must be NCName)", token),
3887                });
3888            }
3889            items.push(XmlAtomicValue::String(token.to_string()));
3890        }
3891
3892        Ok(XmlValue::new(
3893            XmlTypeCode::IdRefs,
3894            XmlValueKind::List {
3895                item_type: XmlTypeCode::IdRef,
3896                items,
3897            },
3898        ))
3899    }
3900
3901    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3902        let result = self.validate(value)?;
3903        if let XmlValueKind::List { items, .. } = &result.value {
3904            facets.validate_list_length(items.len() as u64)?;
3905        }
3906        Ok(result)
3907    }
3908
3909    fn facet_applicable(&self, facet: &str) -> bool {
3910        matches!(
3911            facet,
3912            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
3913        )
3914    }
3915}
3916
3917/// Validator for xs:ENTITIES (list of ENTITY)
3918pub struct EntitiesValidator;
3919
3920impl TypeValidator for EntitiesValidator {
3921    fn type_name(&self) -> &'static str {
3922        "ENTITIES"
3923    }
3924    fn type_code(&self) -> XmlTypeCode {
3925        XmlTypeCode::Entities
3926    }
3927    fn primitive_type(&self) -> PrimitiveTypeCode {
3928        PrimitiveTypeCode::String
3929    }
3930    fn whitespace(&self) -> WhitespaceMode {
3931        WhitespaceMode::Collapse
3932    }
3933
3934    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
3935        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
3936        if normalized.is_empty() {
3937            return Err(ValidationError::InvalidLexical {
3938                value: value.to_string(),
3939                type_name: "ENTITIES",
3940                message: "ENTITIES must contain at least one ENTITY".to_string(),
3941            });
3942        }
3943
3944        let mut items = Vec::new();
3945        for token in normalized.split_whitespace() {
3946            if !is_valid_ncname(token) {
3947                return Err(ValidationError::InvalidLexical {
3948                    value: value.to_string(),
3949                    type_name: "ENTITIES",
3950                    message: format!("Invalid ENTITY: '{}' (must be NCName)", token),
3951                });
3952            }
3953            items.push(XmlAtomicValue::String(token.to_string()));
3954        }
3955
3956        Ok(XmlValue::new(
3957            XmlTypeCode::Entities,
3958            XmlValueKind::List {
3959                item_type: XmlTypeCode::Entity,
3960                items,
3961            },
3962        ))
3963    }
3964
3965    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
3966        let result = self.validate(value)?;
3967        if let XmlValueKind::List { items, .. } = &result.value {
3968            facets.validate_list_length(items.len() as u64)?;
3969        }
3970        Ok(result)
3971    }
3972
3973    fn facet_applicable(&self, facet: &str) -> bool {
3974        matches!(
3975            facet,
3976            "length" | "minLength" | "maxLength" | "pattern" | "enumeration"
3977        )
3978    }
3979}
3980
3981// ============================================================================
3982// XSD 1.1 Duration Validators
3983// ============================================================================
3984
3985/// Validator for xs:yearMonthDuration (XSD 1.1)
3986pub struct YearMonthDurationValidator;
3987
3988impl TypeValidator for YearMonthDurationValidator {
3989    fn type_name(&self) -> &'static str {
3990        "yearMonthDuration"
3991    }
3992    fn type_code(&self) -> XmlTypeCode {
3993        XmlTypeCode::YearMonthDuration
3994    }
3995    fn primitive_type(&self) -> PrimitiveTypeCode {
3996        PrimitiveTypeCode::Duration
3997    }
3998    fn whitespace(&self) -> WhitespaceMode {
3999        WhitespaceMode::Collapse
4000    }
4001
4002    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
4003        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
4004        parse_year_month_duration(&normalized).map(|d| {
4005            XmlValue::new(
4006                XmlTypeCode::YearMonthDuration,
4007                XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(d)),
4008            )
4009        })
4010    }
4011
4012    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
4013        let result = self.validate(value)?;
4014        if let XmlValueKind::Atomic(XmlAtomicValue::YearMonthDuration(ref dur)) = result.value {
4015            validate_bounds(dur, facets, "yearMonthDuration", value, |s| {
4016                parse_year_month_duration(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
4017            })?;
4018            facets.validate_enum_value_space(
4019                enum_matches_value_space!(dur, parse_year_month_duration, |a, b| {
4020                    year_month_duration_value_eq(a, &b)
4021                }),
4022                value,
4023            )?;
4024        }
4025        facets.validate_patterns_only(value)?;
4026        Ok(result)
4027    }
4028
4029    fn facet_applicable(&self, facet: &str) -> bool {
4030        matches!(
4031            facet,
4032            "minInclusive"
4033                | "maxInclusive"
4034                | "minExclusive"
4035                | "maxExclusive"
4036                | "pattern"
4037                | "enumeration"
4038        )
4039    }
4040}
4041
4042/// Validator for xs:dayTimeDuration (XSD 1.1)
4043pub struct DayTimeDurationValidator;
4044
4045impl TypeValidator for DayTimeDurationValidator {
4046    fn type_name(&self) -> &'static str {
4047        "dayTimeDuration"
4048    }
4049    fn type_code(&self) -> XmlTypeCode {
4050        XmlTypeCode::DayTimeDuration
4051    }
4052    fn primitive_type(&self) -> PrimitiveTypeCode {
4053        PrimitiveTypeCode::Duration
4054    }
4055    fn whitespace(&self) -> WhitespaceMode {
4056        WhitespaceMode::Collapse
4057    }
4058
4059    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
4060        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
4061        parse_day_time_duration(&normalized).map(|d| {
4062            XmlValue::new(
4063                XmlTypeCode::DayTimeDuration,
4064                XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(d)),
4065            )
4066        })
4067    }
4068
4069    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
4070        let result = self.validate(value)?;
4071        if let XmlValueKind::Atomic(XmlAtomicValue::DayTimeDuration(ref dur)) = result.value {
4072            validate_bounds(dur, facets, "dayTimeDuration", value, |s| {
4073                parse_day_time_duration(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
4074            })?;
4075            facets.validate_enum_value_space(
4076                enum_matches_value_space!(dur, parse_day_time_duration, |a, b| {
4077                    day_time_duration_value_eq(a, &b)
4078                }),
4079                value,
4080            )?;
4081        }
4082        facets.validate_patterns_only(value)?;
4083        Ok(result)
4084    }
4085
4086    fn facet_applicable(&self, facet: &str) -> bool {
4087        matches!(
4088            facet,
4089            "minInclusive"
4090                | "maxInclusive"
4091                | "minExclusive"
4092                | "maxExclusive"
4093                | "pattern"
4094                | "enumeration"
4095        )
4096    }
4097}
4098
4099/// Validator for xs:dateTimeStamp (XSD 1.1)
4100/// dateTimeStamp is dateTime with required timezone
4101pub struct DateTimeStampValidator;
4102
4103impl TypeValidator for DateTimeStampValidator {
4104    fn type_name(&self) -> &'static str {
4105        "dateTimeStamp"
4106    }
4107    fn type_code(&self) -> XmlTypeCode {
4108        XmlTypeCode::DateTimeStamp
4109    }
4110    fn primitive_type(&self) -> PrimitiveTypeCode {
4111        PrimitiveTypeCode::DateTime
4112    }
4113    fn whitespace(&self) -> WhitespaceMode {
4114        WhitespaceMode::Collapse
4115    }
4116
4117    fn validate(&self, value: &str) -> ValidationResult<XmlValue> {
4118        let normalized = normalize_whitespace(value, WhitespaceMode::Collapse);
4119        let dt = parse_datetime(&normalized)?;
4120
4121        // dateTimeStamp requires timezone
4122        if dt.timezone.is_none() {
4123            return Err(ValidationError::InvalidLexical {
4124                value: value.to_string(),
4125                type_name: "dateTimeStamp",
4126                message: "dateTimeStamp requires a timezone".to_string(),
4127            });
4128        }
4129
4130        Ok(XmlValue::new(
4131            XmlTypeCode::DateTimeStamp,
4132            XmlValueKind::Atomic(XmlAtomicValue::DateTime(dt)),
4133        ))
4134    }
4135
4136    fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue> {
4137        let result = self.validate(value)?;
4138        if let XmlValueKind::Atomic(XmlAtomicValue::DateTime(ref dt)) = result.value {
4139            facets.validate_explicit_timezone(dt.timezone.is_some())?;
4140            validate_bounds(dt, facets, "dateTimeStamp", value, |s| {
4141                parse_datetime(&normalize_whitespace(s, WhitespaceMode::Collapse)).ok()
4142            })?;
4143            facets.validate_enum_value_space(
4144                enum_matches_value_space!(dt, parse_datetime, |a, b| datetime_value_eq(a, &b)),
4145                value,
4146            )?;
4147        }
4148        facets.validate_patterns_only(value)?;
4149        Ok(result)
4150    }
4151
4152    fn facet_applicable(&self, facet: &str) -> bool {
4153        matches!(
4154            facet,
4155            "minInclusive"
4156                | "maxInclusive"
4157                | "minExclusive"
4158                | "maxExclusive"
4159                | "pattern"
4160                | "enumeration"
4161                | "explicitTimezone"
4162        )
4163    }
4164}
4165
4166// ============================================================================
4167// Tests
4168// ============================================================================
4169
4170#[cfg(test)]
4171mod tests {
4172    use super::*;
4173
4174    #[test]
4175    fn test_string_validator() {
4176        let v = StringValidator;
4177        let result = v.validate("hello").unwrap();
4178        assert_eq!(result.type_code, XmlTypeCode::String);
4179        assert_eq!(result.to_string_value(), "hello");
4180    }
4181
4182    #[test]
4183    fn test_boolean_validator() {
4184        let v = BooleanValidator;
4185        assert_eq!(v.validate("true").unwrap().as_boolean(), Some(true));
4186        assert_eq!(v.validate("false").unwrap().as_boolean(), Some(false));
4187        assert_eq!(v.validate("1").unwrap().as_boolean(), Some(true));
4188        assert_eq!(v.validate("0").unwrap().as_boolean(), Some(false));
4189        assert!(v.validate("yes").is_err());
4190    }
4191
4192    #[test]
4193    fn test_decimal_validator() {
4194        let v = DecimalValidator;
4195        let result = v.validate("123.45").unwrap();
4196        assert_eq!(result.type_code, XmlTypeCode::Decimal);
4197        assert!(result.as_decimal().is_some());
4198    }
4199
4200    #[test]
4201    fn test_integer_validator() {
4202        let v = IntegerValidator;
4203        let result = v.validate("12345").unwrap();
4204        assert_eq!(result.type_code, XmlTypeCode::Integer);
4205        assert_eq!(result.as_integer(), Some(&BigInt::from(12345)));
4206    }
4207
4208    #[test]
4209    fn test_float_validator() {
4210        let v = FloatValidator;
4211        assert!(v.validate("2.5").is_ok());
4212        assert!(v.validate("INF").is_ok());
4213        assert!(v.validate("-INF").is_ok());
4214        assert!(v.validate("NaN").is_ok());
4215    }
4216
4217    #[test]
4218    fn test_double_validator() {
4219        let v = DoubleValidator;
4220        let result = v.validate("2.718281828").unwrap();
4221        assert_eq!(result.type_code, XmlTypeCode::Double);
4222    }
4223
4224    #[test]
4225    fn test_datetime_validator() {
4226        let v = DateTimeValidator;
4227        let result = v.validate("2024-03-15T10:30:00Z").unwrap();
4228        assert_eq!(result.type_code, XmlTypeCode::DateTime);
4229    }
4230
4231    #[test]
4232    fn test_date_validator() {
4233        let v = DateValidator;
4234        let result = v.validate("2024-03-15").unwrap();
4235        assert_eq!(result.type_code, XmlTypeCode::Date);
4236    }
4237
4238    #[test]
4239    fn test_time_validator() {
4240        let v = TimeValidator;
4241        let result = v.validate("10:30:00").unwrap();
4242        assert_eq!(result.type_code, XmlTypeCode::Time);
4243    }
4244
4245    #[test]
4246    fn test_duration_validator() {
4247        let v = DurationValidator;
4248        let result = v.validate("P1Y2M3DT4H5M6S").unwrap();
4249        assert_eq!(result.type_code, XmlTypeCode::Duration);
4250    }
4251
4252    #[test]
4253    fn test_hex_binary_validator() {
4254        let v = HexBinaryValidator;
4255        let result = v.validate("DEADBEEF").unwrap();
4256        assert_eq!(result.type_code, XmlTypeCode::HexBinary);
4257    }
4258
4259    #[test]
4260    fn test_base64_binary_validator() {
4261        let v = Base64BinaryValidator;
4262        let result = v.validate("SGVsbG8=").unwrap();
4263        assert_eq!(result.type_code, XmlTypeCode::Base64Binary);
4264    }
4265
4266    #[test]
4267    fn test_anyuri_validator() {
4268        let v = AnyUriValidator;
4269        let result = v.validate("http://example.com").unwrap();
4270        assert_eq!(result.type_code, XmlTypeCode::AnyUri);
4271    }
4272
4273    #[test]
4274    fn test_validator_registry() {
4275        let registry = ValidatorRegistry::new();
4276        assert!(registry.get_by_name("string").is_some());
4277        assert!(registry.get_by_name("integer").is_some());
4278        assert!(registry.get_by_code(XmlTypeCode::Boolean).is_some());
4279        assert!(registry.get_by_name("nonexistent").is_none());
4280    }
4281
4282    #[test]
4283    fn test_whitespace_normalization() {
4284        let v = TokenValidator;
4285        let result = v.validate("  hello   world  ").unwrap();
4286        assert_eq!(result.to_string_value(), "hello world");
4287    }
4288}