1use 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#[derive(Debug, Clone)]
30pub enum ValidationError {
31 InvalidLexical {
33 value: String,
34 type_name: &'static str,
35 message: String,
36 },
37 FacetViolation(FacetError),
39 TypeError {
41 expected: XmlTypeCode,
42 actual: XmlTypeCode,
43 },
44 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
80pub type ValidationResult<T> = Result<T, ValidationError>;
82
83macro_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
101pub trait TypeValidator: Send + Sync {
109 fn type_name(&self) -> &'static str;
111
112 fn type_code(&self) -> XmlTypeCode;
114
115 fn primitive_type(&self) -> PrimitiveTypeCode;
117
118 fn whitespace(&self) -> WhitespaceMode;
120
121 fn validate(&self, value: &str) -> ValidationResult<XmlValue>;
123
124 fn validate_with_facets(&self, value: &str, facets: &FacetSet) -> ValidationResult<XmlValue>;
126
127 fn facet_applicable(&self, facet: &str) -> bool;
129}
130
131pub struct ValidatorRegistry {
133 validators: HashMap<&'static str, Arc<dyn TypeValidator>>,
135 by_code: HashMap<XmlTypeCode, Arc<dyn TypeValidator>>,
137}
138
139impl ValidatorRegistry {
140 pub fn new() -> Self {
142 let mut registry = Self {
143 validators: HashMap::new(),
144 by_code: HashMap::new(),
145 };
146
147 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 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 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 registry.register(Arc::new(QNameValidator));
194 registry.register(Arc::new(NotationValidator));
195
196 registry.register(Arc::new(NmTokensValidator));
198 registry.register(Arc::new(IdRefsValidator));
199 registry.register(Arc::new(EntitiesValidator));
200
201 registry.register(Arc::new(YearMonthDurationValidator));
203 registry.register(Arc::new(DayTimeDurationValidator));
204 registry.register(Arc::new(DateTimeStampValidator));
205
206 registry
207 }
208
209 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 pub fn get_by_name(&self, name: &str) -> Option<&dyn TypeValidator> {
219 self.validators.get(name).map(|v| v.as_ref())
220 }
221
222 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 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
246pub static VALIDATOR_REGISTRY: Lazy<ValidatorRegistry> = Lazy::new(ValidatorRegistry::new);
262
263pub 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
307pub 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 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
360pub 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
402pub 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 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
449pub 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
495pub 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
541pub 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
587pub 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
633pub 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
679pub 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
725pub 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 facets.validate_patterns_only(value)?;
764 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
783pub 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
841pub 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
895pub 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 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
950pub 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 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
1005pub 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
1060fn 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
1075fn 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
1092fn 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
1135fn 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
1204pub 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 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
1263pub 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
1320pub 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
1377pub 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
1434pub 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
1487pub 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
1540pub 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
1593pub 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
1646pub 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
1699pub 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
1754pub 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
1807pub 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 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
1852pub 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
1865pub 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 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 '/' | '?' | '#' => return true,
1908 _ => {}
1909 }
1910 }
1911 let Some(end) = scheme_end else { return true };
1913 is_valid_uri_scheme(&value[..end])
1914}
1915
1916fn parse_duration(s: &str) -> ValidationResult<DurationValue> {
1922 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 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 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
2070fn parse_datetime(s: &str) -> ValidationResult<DateTimeValue> {
2072 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 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
2113fn 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 + 1, 1, 1)
2124 }
2125}
2126
2127fn 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
2144fn 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
2156fn 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 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
2180fn 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
2198fn 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 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 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
2290fn 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
2376fn 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 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
2402fn 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
2414fn 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
2484fn 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
2504fn 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 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
2570fn 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
2600fn 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
2633fn 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 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 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 = ¤t[y_pos + 1..];
2682 }
2683
2684 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 = ¤t[m_pos + 1..];
2694 }
2695
2696 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
2712fn 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 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 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 = ¤t[d_pos + 1..];
2768 }
2769
2770 if let Some(stripped) = current.strip_prefix('T') {
2772 current = stripped;
2773
2774 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 = ¤t[h_pos + 1..];
2784 }
2785
2786 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 = ¤t[m_pos + 1..];
2796 }
2797
2798 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 = ¤t[s_pos + 1..];
2808 }
2809 }
2810
2811 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
2829fn 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
2844fn 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
2853fn is_ncname_start_char(c: char) -> bool {
2855 is_name_start_char(c) && c != ':'
2856}
2857
2858fn is_ncname_char(c: char) -> bool {
2860 is_name_char(c) && c != ':'
2861}
2862
2863fn 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
2872fn 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
2881pub 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 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 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
2904pub 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
2967pub 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
3026pub 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
3085pub 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
3144pub 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
3207pub 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
3270pub 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
3333pub 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
3396pub 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
3455pub 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
3514pub 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
3573pub 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
3632pub 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 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 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 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 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 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
3713pub 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 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 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 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
3785pub 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
3853pub 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
3917pub 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
3981pub 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
4042pub 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
4099pub 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 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#[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}