1use crate::error::{SchemaError, SchemaResult};
29use crate::ids::{
30 AttributeGroupKey, AttributeKey, ComplexTypeKey, ElementKey, NameId, SimpleTypeKey, TypeKey,
31};
32use crate::parser::frames::{
33 AttributeUseKind, ComplexContentResult, Compositor, DerivationMethod, ElementFrameResult,
34 ModelGroupDefResult, ParticleResult, ParticleTerm, ProcessContents, SimpleTypeVariety,
35 WildcardNamespace, WildcardResult,
36};
37#[cfg(feature = "xsd11")]
38use crate::parser::frames::{OpenContentMode, OpenContentResult};
39use crate::parser::location::{SourceLocation, SourceRef};
40use crate::schema::dependencies::DependencyGraph;
41use crate::schema::model::DerivationSet;
42use crate::schema::SchemaSet;
43use crate::types::facets::{FacetKind, FacetSet};
44
45#[derive(Debug, Default)]
47pub struct DerivationStats {
48 pub simple_types_validated: usize,
50 pub complex_types_validated: usize,
52 pub list_types_validated: usize,
54 pub union_types_validated: usize,
56 pub restrictions_validated: usize,
58 pub extensions_validated: usize,
60 pub errors: usize,
62}
63
64pub fn validate_all_derivations(
78 schema_set: &SchemaSet,
79 dep_graph: &DependencyGraph,
80) -> SchemaResult<DerivationStats> {
81 let mut stats = DerivationStats::default();
82 let mut errors: Vec<SchemaError> = Vec::new();
83
84 for &type_key in dep_graph.compilation_order() {
86 match type_key {
87 TypeKey::Simple(key) => {
88 if let Err(e) = validate_simple_type(schema_set, key, &mut stats) {
89 errors.push(e);
90 stats.errors += 1;
91 }
92 }
93 TypeKey::Complex(key) => {
94 if let Err(e) = validate_complex_type(schema_set, key, &mut stats) {
95 errors.push(e);
96 stats.errors += 1;
97 }
98 }
99 }
100 }
101
102 validate_all_redefine_group_restrictions(schema_set, &mut errors, &mut stats);
107 validate_all_redefine_attribute_group_restrictions(schema_set, &mut errors, &mut stats);
108
109 if schema_set.is_xsd10() {
115 validate_attribute_group_no_circular(schema_set, &mut errors);
116 } else {
117 validate_default_attribute_groups_no_circular(schema_set, &mut errors);
125 }
126
127 if let Some(first_error) = errors.into_iter().next() {
129 return Err(first_error);
130 }
131
132 Ok(stats)
133}
134
135fn validate_attribute_group_no_circular(schema_set: &SchemaSet, errors: &mut Vec<SchemaError>) {
143 use std::collections::HashSet;
144
145 let keys: Vec<_> = schema_set.arenas.attribute_groups.keys().collect();
147
148 for start in keys {
149 let mut path: Vec<AttributeGroupKey> = Vec::new();
150 let mut visited: HashSet<AttributeGroupKey> = HashSet::new();
151 if let Some(cycle_key) =
152 find_attribute_group_cycle(schema_set, start, &mut path, &mut visited)
153 {
154 let location = schema_set
155 .arenas
156 .attribute_groups
157 .get(cycle_key)
158 .and_then(|ag| ag.source.as_ref())
159 .and_then(|s| schema_set.source_maps.locate(s));
160 errors.push(SchemaError::structural(
161 "src-attribute_group",
162 "Circular attribute group reference detected",
163 location,
164 ));
165 }
166 }
167}
168
169fn find_attribute_group_cycle(
173 schema_set: &SchemaSet,
174 key: AttributeGroupKey,
175 path: &mut Vec<AttributeGroupKey>,
176 visited: &mut std::collections::HashSet<AttributeGroupKey>,
177) -> Option<AttributeGroupKey> {
178 if path.contains(&key) {
179 return Some(key);
180 }
181 if !visited.insert(key) {
182 return None;
183 }
184 path.push(key);
185
186 let result = if let Some(ag) = schema_set.arenas.attribute_groups.get(key) {
187 let mut found: Option<AttributeGroupKey> = None;
188 if let Some(ref_key) = ag.resolved_ref {
189 if let Some(c) = find_attribute_group_cycle(schema_set, ref_key, path, visited) {
190 found = Some(c);
191 }
192 }
193 if found.is_none() {
194 for &nested_key in &ag.resolved_attribute_groups {
195 if let Some(c) = find_attribute_group_cycle(schema_set, nested_key, path, visited) {
196 found = Some(c);
197 break;
198 }
199 }
200 }
201 found
202 } else {
203 None
204 };
205
206 path.pop();
207 result
208}
209
210fn validate_default_attribute_groups_no_circular(
216 schema_set: &SchemaSet,
217 errors: &mut Vec<SchemaError>,
218) {
219 use std::collections::HashSet;
220 let mut seen_starts: HashSet<AttributeGroupKey> = HashSet::new();
221 for doc in &schema_set.documents {
222 let Some(ref qname) = doc.default_attributes else {
223 continue;
224 };
225 let Some(start) = schema_set.lookup_attribute_group(qname.namespace_uri, qname.local_name)
226 else {
227 continue; };
229 if !seen_starts.insert(start) {
230 continue;
231 }
232 let mut path: Vec<AttributeGroupKey> = Vec::new();
233 let mut visited: HashSet<AttributeGroupKey> = HashSet::new();
234 if let Some(cycle_key) =
235 find_attribute_group_cycle(schema_set, start, &mut path, &mut visited)
236 {
237 let location = schema_set
238 .arenas
239 .attribute_groups
240 .get(cycle_key)
241 .and_then(|ag| ag.source.as_ref())
242 .and_then(|s| schema_set.source_maps.locate(s));
243 errors.push(SchemaError::structural(
244 "src-attribute_group",
245 "Circular attribute group reference detected via defaultAttributes",
246 location,
247 ));
248 }
249 }
250}
251
252fn validate_simple_type(
254 schema_set: &SchemaSet,
255 key: SimpleTypeKey,
256 stats: &mut DerivationStats,
257) -> SchemaResult<()> {
258 let type_def = schema_set
259 .arenas
260 .simple_types
261 .get(key)
262 .ok_or_else(|| SchemaError::internal("Simple type not found in arena"))?;
263
264 stats.simple_types_validated += 1;
265
266 validate_applicable_facets(schema_set, type_def)?;
268
269 match type_def.variety {
270 SimpleTypeVariety::Atomic => {
271 validate_simple_restriction(schema_set, type_def, stats)?;
273 }
274 SimpleTypeVariety::List => {
275 stats.list_types_validated += 1;
276 validate_simple_list(schema_set, type_def)?;
277 validate_facets_against_resolved_base(schema_set, type_def)?;
278 }
279 SimpleTypeVariety::Union => {
280 stats.union_types_validated += 1;
281 validate_simple_union(schema_set, type_def)?;
282 validate_facets_against_resolved_base(schema_set, type_def)?;
283 }
284 }
285
286 Ok(())
287}
288
289fn validate_facets_against_resolved_base(
297 schema_set: &SchemaSet,
298 type_def: &crate::arenas::SimpleTypeDefData,
299) -> SchemaResult<()> {
300 let Some(base_key) = type_def.resolved_base_type else {
301 return Ok(());
302 };
303 if let Some(base_facets) = get_type_facets(schema_set, base_key)? {
304 type_def.facets.merge_with_base(&base_facets).map_err(|e| {
305 let (location, type_name) = type_error_context(schema_set, type_def);
306 SchemaError::structural(
307 "cos-st-restricts",
308 format!("Simple type '{}' has invalid restriction: {}", type_name, e),
309 location,
310 )
311 })?;
312 }
313 validate_facet_values_against_base_type(schema_set, type_def, base_key)?;
314 Ok(())
315}
316
317fn validate_applicable_facets(
323 schema_set: &SchemaSet,
324 type_def: &crate::arenas::SimpleTypeDefData,
325) -> SchemaResult<()> {
326 let facets = &type_def.facets;
327
328 match type_def.variety {
329 SimpleTypeVariety::List => {
330 let has_inapplicable = facets.min_inclusive.is_some()
332 || facets.max_inclusive.is_some()
333 || facets.min_exclusive.is_some()
334 || facets.max_exclusive.is_some()
335 || facets.total_digits.is_some()
336 || facets.fraction_digits.is_some()
337 || facets.explicit_timezone.is_some();
338
339 if has_inapplicable {
340 let (location, type_name) = type_error_context(schema_set, type_def);
341 let inapplicable = list_inapplicable_facets_for_list(facets);
342 return Err(SchemaError::structural(
343 "cos-applicable-facets",
344 format!(
345 "List type '{}' has inapplicable facet(s): {}",
346 type_name, inapplicable
347 ),
348 location,
349 ));
350 }
351 }
352 SimpleTypeVariety::Union => {
353 let has_inapplicable = facets.length.is_some()
355 || facets.min_length.is_some()
356 || facets.max_length.is_some()
357 || facets.whitespace.is_some()
358 || facets.min_inclusive.is_some()
359 || facets.max_inclusive.is_some()
360 || facets.min_exclusive.is_some()
361 || facets.max_exclusive.is_some()
362 || facets.total_digits.is_some()
363 || facets.fraction_digits.is_some()
364 || facets.explicit_timezone.is_some();
365
366 if has_inapplicable {
367 let (location, type_name) = type_error_context(schema_set, type_def);
368 let inapplicable = list_inapplicable_facets_for_union(facets);
369 return Err(SchemaError::structural(
370 "cos-applicable-facets",
371 format!(
372 "Union type '{}' has inapplicable facet(s): {}",
373 type_name, inapplicable
374 ),
375 location,
376 ));
377 }
378 }
379 SimpleTypeVariety::Atomic => {
380 if let Some(primitive_code) = primitive_type_code(schema_set, type_def) {
384 use crate::types::facets::{
385 facet_applicable_for_type, ExplicitTimezone, FacetApplicability, FacetKind,
386 WhitespaceMode,
387 };
388 let facets = &type_def.facets;
389 let mut bad: Vec<&'static str> = Vec::new();
390 let mut check = |present: bool, kind: FacetKind| {
391 if present
392 && matches!(
393 facet_applicable_for_type(kind, primitive_code),
394 FacetApplicability::NotApplicable
395 )
396 {
397 bad.push(kind.name());
398 }
399 };
400 check(facets.length.is_some(), FacetKind::Length);
401 check(facets.min_length.is_some(), FacetKind::MinLength);
402 check(facets.max_length.is_some(), FacetKind::MaxLength);
403 check(facets.whitespace.is_some(), FacetKind::Whitespace);
404 check(facets.min_inclusive.is_some(), FacetKind::MinInclusive);
405 check(facets.max_inclusive.is_some(), FacetKind::MaxInclusive);
406 check(facets.min_exclusive.is_some(), FacetKind::MinExclusive);
407 check(facets.max_exclusive.is_some(), FacetKind::MaxExclusive);
408 check(facets.total_digits.is_some(), FacetKind::TotalDigits);
409 check(facets.fraction_digits.is_some(), FacetKind::FractionDigits);
410 check(
411 facets.explicit_timezone.is_some(),
412 FacetKind::ExplicitTimezone,
413 );
414 if let Some(ws) = &facets.whitespace {
415 if !matches!(
416 primitive_code,
417 crate::types::XmlTypeCode::String
418 | crate::types::XmlTypeCode::NormalizedString
419 | crate::types::XmlTypeCode::Token
420 | crate::types::XmlTypeCode::Language
421 | crate::types::XmlTypeCode::NmToken
422 | crate::types::XmlTypeCode::Name
423 | crate::types::XmlTypeCode::NCName
424 | crate::types::XmlTypeCode::Id
425 | crate::types::XmlTypeCode::IdRef
426 | crate::types::XmlTypeCode::Entity
427 ) && ws.value != WhitespaceMode::Collapse
428 {
429 bad.push(FacetKind::Whitespace.name());
430 }
431 }
432 if let Some(tz) = &facets.explicit_timezone {
433 if primitive_code == crate::types::XmlTypeCode::DateTimeStamp
434 && tz.value != ExplicitTimezone::Required
435 {
436 bad.push(FacetKind::ExplicitTimezone.name());
437 }
438 }
439 if !bad.is_empty() {
440 let (location, type_name) = type_error_context(schema_set, type_def);
441 return Err(SchemaError::structural(
442 "cos-applicable-facets",
443 format!(
444 "Atomic type '{}' has inapplicable facet(s) for primitive '{}': {}",
445 type_name,
446 primitive_code.local_name().unwrap_or("<unnamed>"),
447 bad.join(", ")
448 ),
449 location,
450 ));
451 }
452 }
453 }
454 }
455
456 Ok(())
457}
458
459fn primitive_type_code(
465 schema_set: &SchemaSet,
466 type_def: &crate::arenas::SimpleTypeDefData,
467) -> Option<crate::types::XmlTypeCode> {
468 let builtin = schema_set.builtin_types();
469 let mut current_base = type_def.resolved_base_type;
470 for _ in 0..64 {
471 let Some(TypeKey::Simple(k)) = current_base else {
472 return None;
473 };
474 if let Some(code) = builtin.get_type_code(k) {
475 return Some(code);
476 }
477 current_base = schema_set
478 .arenas
479 .simple_types
480 .get(k)
481 .and_then(|t| t.resolved_base_type);
482 }
483 None
484}
485
486fn reject_any_atomic_type(
490 schema_set: &SchemaSet,
491 type_def: &crate::arenas::SimpleTypeDefData,
492 simple_key: SimpleTypeKey,
493 constraint: &'static str,
494 role: &'static str,
495) -> SchemaResult<()> {
496 if !schema_set.builtin_types().is_any_atomic_type(simple_key) {
497 return Ok(());
498 }
499 let (location, type_name) = type_error_context(schema_set, type_def);
500 Err(SchemaError::structural(
501 constraint,
502 format!(
503 "Simple type '{}' cannot {} xs:anyAtomicType (abstract per XSD 1.1 bug 11103)",
504 type_name, role
505 ),
506 location,
507 ))
508}
509
510fn list_inapplicable_facets_for_list(facets: &FacetSet) -> String {
512 let mut names = Vec::new();
513 if facets.min_inclusive.is_some() {
514 names.push("minInclusive");
515 }
516 if facets.max_inclusive.is_some() {
517 names.push("maxInclusive");
518 }
519 if facets.min_exclusive.is_some() {
520 names.push("minExclusive");
521 }
522 if facets.max_exclusive.is_some() {
523 names.push("maxExclusive");
524 }
525 if facets.total_digits.is_some() {
526 names.push("totalDigits");
527 }
528 if facets.fraction_digits.is_some() {
529 names.push("fractionDigits");
530 }
531 if facets.explicit_timezone.is_some() {
532 names.push("explicitTimezone");
533 }
534 names.join(", ")
535}
536
537fn list_inapplicable_facets_for_union(facets: &FacetSet) -> String {
539 let mut names = Vec::new();
540 if facets.length.is_some() {
541 names.push("length");
542 }
543 if facets.min_length.is_some() {
544 names.push("minLength");
545 }
546 if facets.max_length.is_some() {
547 names.push("maxLength");
548 }
549 if facets.whitespace.is_some() {
550 names.push("whiteSpace");
551 }
552 if facets.min_inclusive.is_some() {
553 names.push("minInclusive");
554 }
555 if facets.max_inclusive.is_some() {
556 names.push("maxInclusive");
557 }
558 if facets.min_exclusive.is_some() {
559 names.push("minExclusive");
560 }
561 if facets.max_exclusive.is_some() {
562 names.push("maxExclusive");
563 }
564 if facets.total_digits.is_some() {
565 names.push("totalDigits");
566 }
567 if facets.fraction_digits.is_some() {
568 names.push("fractionDigits");
569 }
570 if facets.explicit_timezone.is_some() {
571 names.push("explicitTimezone");
572 }
573 names.join(", ")
574}
575
576fn validate_simple_restriction(
580 schema_set: &SchemaSet,
581 type_def: &crate::arenas::SimpleTypeDefData,
582 stats: &mut DerivationStats,
583) -> SchemaResult<()> {
584 let base_key = match type_def.resolved_base_type {
586 Some(key) => key,
587 None => return Ok(()), };
589
590 if let TypeKey::Complex(_) = base_key {
592 let (location, type_name) = type_error_context(schema_set, type_def);
593 return Err(SchemaError::structural(
594 "cos-st-restricts",
595 format!("Simple type '{}': base type must be a simple type definition (cos-st-restricts.1.1)", type_name),
596 location,
597 ));
598 }
599
600 if let TypeKey::Simple(base_simple_key) = base_key {
601 reject_any_atomic_type(
602 schema_set,
603 type_def,
604 base_simple_key,
605 "cos-st-restricts",
606 "restrict",
607 )?;
608 }
609
610 stats.restrictions_validated += 1;
611
612 if let TypeKey::Simple(base_simple_key) = base_key {
614 if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
615 if base_type.final_derivation.contains_restriction() {
616 let (location, type_name) = type_error_context(schema_set, type_def);
617 let base_name =
618 format_type_name(schema_set, base_type.name, base_type.target_namespace);
619 return Err(SchemaError::structural(
620 "cos-st-restricts",
621 format!(
622 "Simple type '{}' cannot restrict '{}' because base type is final for restriction",
623 type_name, base_name
624 ),
625 location,
626 ));
627 }
628 }
629 }
630
631 let base_facets = get_type_facets(schema_set, base_key)?;
633
634 if let Some(ref base_facets) = base_facets {
636 type_def.facets.merge_with_base(base_facets).map_err(|e| {
638 let (location, type_name) = type_error_context(schema_set, type_def);
639 SchemaError::structural(
640 "cos-st-restricts",
641 format!("Simple type '{}' has invalid restriction: {}", type_name, e),
642 location,
643 )
644 })?;
645 }
646
647 validate_facet_values_against_base_type(schema_set, type_def, base_key)?;
650
651 Ok(())
652}
653
654fn validate_simple_list(
658 schema_set: &SchemaSet,
659 type_def: &crate::arenas::SimpleTypeDefData,
660) -> SchemaResult<()> {
661 if let Some(TypeKey::Simple(item_simple_key)) = type_def.resolved_item_type {
663 if let Some(item_type) = schema_set.arenas.simple_types.get(item_simple_key) {
664 if item_type.final_derivation.contains_list() {
665 let (location, type_name) = type_error_context(schema_set, type_def);
666 let item_name =
667 format_type_name(schema_set, item_type.name, item_type.target_namespace);
668 return Err(SchemaError::structural(
669 "cos-st-restricts",
670 format!(
671 "List type '{}' cannot use '{}' as item type because it is final for list",
672 type_name, item_name
673 ),
674 location,
675 ));
676 }
677 }
678 }
679
680 if let Some(TypeKey::Simple(base_simple_key)) = type_def.resolved_base_type {
682 if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
683 if base_type.final_derivation.contains_list() {
684 let (location, type_name) = type_error_context(schema_set, type_def);
685 let base_name =
686 format_type_name(schema_set, base_type.name, base_type.target_namespace);
687 return Err(SchemaError::structural(
688 "cos-st-restricts",
689 format!(
690 "List type '{}' cannot derive from '{}' because it is final for list",
691 type_name, base_name
692 ),
693 location,
694 ));
695 }
696 }
697 }
698
699 let item_key = match type_def.resolved_item_type {
701 Some(key) => key,
702 None => {
703 return Ok(());
705 }
706 };
707
708 match item_key {
710 TypeKey::Simple(simple_key) => {
711 reject_any_atomic_type(
712 schema_set,
713 type_def,
714 simple_key,
715 "cos-list-of-atomic",
716 "use as list item type",
717 )?;
718 if let Some(item_type) = schema_set.arenas.simple_types.get(simple_key) {
719 match item_type.variety {
720 SimpleTypeVariety::Atomic => {
721 }
723 SimpleTypeVariety::List => {
724 let (location, type_name) = type_error_context(schema_set, type_def);
726 return Err(SchemaError::structural(
727 "cos-list-of-atomic",
728 format!(
729 "List type '{}' has list item type, which is not allowed",
730 type_name
731 ),
732 location,
733 ));
734 }
735 SimpleTypeVariety::Union => {
736 if union_contains_list(schema_set, item_type) {
738 let (location, type_name) = type_error_context(schema_set, type_def);
739 return Err(SchemaError::structural(
740 "cos-list-of-atomic",
741 format!(
742 "List type '{}' has union item type containing list member",
743 type_name
744 ),
745 location,
746 ));
747 }
748 }
749 }
750 }
751 }
752 TypeKey::Complex(_) => {
753 let (location, type_name) = type_error_context(schema_set, type_def);
755 return Err(SchemaError::structural(
756 "cos-list-of-atomic",
757 format!(
758 "List type '{}' has complex item type, which is not allowed",
759 type_name
760 ),
761 location,
762 ));
763 }
764 }
765
766 Ok(())
767}
768
769fn union_contains_list(
771 schema_set: &SchemaSet,
772 union_type: &crate::arenas::SimpleTypeDefData,
773) -> bool {
774 for member_key in &union_type.resolved_member_types {
775 if let TypeKey::Simple(simple_key) = member_key {
776 if let Some(member) = schema_set.arenas.simple_types.get(*simple_key) {
777 match member.variety {
778 SimpleTypeVariety::List => return true,
779 SimpleTypeVariety::Union => {
780 if union_contains_list(schema_set, member) {
781 return true;
782 }
783 }
784 SimpleTypeVariety::Atomic => {}
785 }
786 }
787 }
788 }
789 false
790}
791
792fn validate_simple_union(
796 schema_set: &SchemaSet,
797 type_def: &crate::arenas::SimpleTypeDefData,
798) -> SchemaResult<()> {
799 for member_key in &type_def.resolved_member_types {
801 if let TypeKey::Simple(simple_key) = member_key {
802 if let Some(member_type) = schema_set.arenas.simple_types.get(*simple_key) {
803 if member_type.final_derivation.contains_union() {
804 let (location, type_name) = type_error_context(schema_set, type_def);
805 let member_name = format_type_name(
806 schema_set,
807 member_type.name,
808 member_type.target_namespace,
809 );
810 return Err(SchemaError::structural(
811 "cos-st-restricts",
812 format!(
813 "Union type '{}' cannot use '{}' as member type because it is final for union",
814 type_name, member_name
815 ),
816 location,
817 ));
818 }
819 }
820 }
821 }
822
823 for member_key in &type_def.resolved_member_types {
825 match member_key {
826 TypeKey::Simple(simple_key) => {
827 reject_any_atomic_type(
828 schema_set,
829 type_def,
830 *simple_key,
831 "cos-union-memberTypes",
832 "use as union member type",
833 )?;
834 }
835 TypeKey::Complex(_) => {
836 let (location, type_name) = type_error_context(schema_set, type_def);
838 return Err(SchemaError::structural(
839 "cos-union-memberTypes",
840 format!(
841 "Union type '{}' has complex member type, which is not allowed",
842 type_name
843 ),
844 location,
845 ));
846 }
847 }
848 }
849
850 Ok(())
851}
852
853fn validate_complex_type(
855 schema_set: &SchemaSet,
856 key: ComplexTypeKey,
857 stats: &mut DerivationStats,
858) -> SchemaResult<()> {
859 let type_def = schema_set
860 .arenas
861 .complex_types
862 .get(key)
863 .ok_or_else(|| SchemaError::internal("Complex type not found in arena"))?;
864
865 stats.complex_types_validated += 1;
866
867 match type_def.derivation_method {
869 Some(DerivationMethod::Extension) => {
870 stats.extensions_validated += 1;
871 validate_complex_extension(schema_set, key, type_def)?;
872 }
873 Some(DerivationMethod::Restriction) => {
874 stats.restrictions_validated += 1;
875 validate_complex_restriction(schema_set, type_def)?;
876 }
877 None => {
878 }
881 }
882
883 Ok(())
884}
885
886fn validate_complex_extension(
890 schema_set: &SchemaSet,
891 #[cfg_attr(not(feature = "xsd11"), allow(unused_variables))] derived_key: ComplexTypeKey,
892 type_def: &crate::arenas::ComplexTypeDefData,
893) -> SchemaResult<()> {
894 let base_key = match type_def.resolved_base_type {
896 Some(key) => key,
897 None => return Ok(()), };
899
900 match base_key {
902 TypeKey::Simple(base_simple_key) => {
903 if matches!(type_def.content, ComplexContentResult::Complex(_)) {
906 let (location, type_name) = type_error_context(schema_set, type_def);
907 return Err(SchemaError::structural(
908 "cos-ct-extends",
909 format!(
910 "Complex type '{}' cannot use complexContent extension from a simple type",
911 type_name,
912 ),
913 location,
914 ));
915 }
916
917 if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
919 if base_type.final_derivation.contains_extension() {
920 let (location, type_name) = type_error_context(schema_set, type_def);
921 let base_name =
922 format_type_name(schema_set, base_type.name, base_type.target_namespace);
923 return Err(SchemaError::structural(
924 "cos-ct-extends",
925 format!(
926 "Complex type '{}' cannot extend simple type '{}' because it is final for extension",
927 type_name, base_name,
928 ),
929 location,
930 ));
931 }
932 }
933 }
934 TypeKey::Complex(base_complex_key) => {
935 if let Some(base_type) = schema_set.arenas.complex_types.get(base_complex_key) {
936 if base_type.final_derivation.contains_extension() {
938 let (location, type_name) = type_error_context(schema_set, type_def);
939 let base_name =
940 format_type_name(schema_set, base_type.name, base_type.target_namespace);
941 return Err(SchemaError::structural(
942 "cos-ct-extends",
943 format!(
944 "Complex type '{}' cannot extend '{}' because base type is final for extension",
945 type_name, base_name
946 ),
947 location,
948 ));
949 }
950
951 if matches!(type_def.content, ComplexContentResult::Simple(_))
958 && !matches!(base_type.content, ComplexContentResult::Simple(_))
959 {
960 let (location, type_name) = type_error_context(schema_set, type_def);
961 let base_name =
962 format_type_name(schema_set, base_type.name, base_type.target_namespace);
963 return Err(SchemaError::structural(
964 "src-ct",
965 format!(
966 "Complex type '{}' uses xs:simpleContent extension but base '{}' \
967 does not have a simple {{content type}} (src-ct.2.1.1)",
968 type_name, base_name,
969 ),
970 location,
971 ));
972 }
973
974 if matches!(base_type.content, ComplexContentResult::Simple(_)) {
980 if let ComplexContentResult::Complex(ref complex) = type_def.content {
981 if complex.particle.is_some() || schema_set.is_xsd11() {
982 let (location, type_name) = type_error_context(schema_set, type_def);
983 let base_name = format_type_name(
984 schema_set,
985 base_type.name,
986 base_type.target_namespace,
987 );
988 return Err(SchemaError::structural(
989 "cos-ct-extends",
990 format!(
991 "Complex type '{}' cannot use complexContent to extend '{}' which has simpleContent{}",
992 type_name, base_name,
993 if complex.particle.is_some() { " with element content" }
994 else { " (XSD 1.1 cos-ct-extends clause 1.4)" },
995 ),
996 location,
997 ));
998 }
999 }
1000 }
1001
1002 validate_extension_mixed_parity(schema_set, type_def, base_type)?;
1003
1004 if let ComplexContentResult::Complex(ref base_complex) = base_type.content {
1016 if let Some(ref base_particle) = base_complex.particle {
1017 if let ComplexContentResult::Complex(ref derived_complex) = type_def.content
1018 {
1019 if let Some(ref ext_particle) = derived_complex.particle {
1020 let ext_compositor = match &ext_particle.term {
1021 ParticleTerm::Group(mg) => mg.compositor,
1022 _ => None,
1023 };
1024 let base_is_all = matches!(
1025 base_particle.term,
1026 ParticleTerm::Group(ModelGroupDefResult {
1027 compositor: Some(Compositor::All),
1028 ..
1029 })
1030 );
1031
1032 match ext_compositor {
1038 Some(Compositor::Sequence) => {
1039 }
1041 Some(Compositor::All)
1042 if base_is_all
1043 && schema_set.xsd_version
1044 == crate::schema::model::XsdVersion::V1_1 =>
1045 {
1046 }
1048 Some(Compositor::Choice) if !base_is_all => {
1049 }
1058 Some(compositor @ (Compositor::All | Compositor::Choice)) => {
1059 let location = type_def
1060 .source
1061 .as_ref()
1062 .and_then(|s| schema_set.source_maps.locate(s));
1063 let type_name = format_type_name(
1064 schema_set,
1065 type_def.name,
1066 type_def.target_namespace,
1067 );
1068 let base_name = format_type_name(
1069 schema_set,
1070 base_type.name,
1071 base_type.target_namespace,
1072 );
1073 let (comp_name, reason) = match compositor {
1074 Compositor::All => (
1075 "all",
1076 "the resulting content model would \
1077 violate cos-all-limited placement \
1078 constraints",
1079 ),
1080 Compositor::Choice => (
1081 "choice",
1082 "the base type's xs:all particle would \
1083 be nested inside a sequence, \
1084 violating cos-all-limited.1",
1085 ),
1086 Compositor::Sequence => unreachable!(),
1087 };
1088 return Err(SchemaError::structural(
1089 "cos-ct-extends",
1090 format!(
1091 "Complex type '{}' cannot extend '{}' with \
1092 an xs:{} compositor because the base type \
1093 has non-empty content; {}",
1094 type_name, base_name, comp_name, reason,
1095 ),
1096 location,
1097 ));
1098 }
1099 None => {
1100 }
1105 }
1106 }
1107 }
1108 }
1109 }
1110
1111 #[cfg(feature = "xsd11")]
1113 validate_open_content_extension(
1114 schema_set,
1115 derived_key,
1116 type_def,
1117 base_complex_key,
1118 base_type,
1119 )?;
1120
1121 }
1126 }
1127 }
1128
1129 Ok(())
1130}
1131
1132fn validate_complex_restriction(
1136 schema_set: &SchemaSet,
1137 type_def: &crate::arenas::ComplexTypeDefData,
1138) -> SchemaResult<()> {
1139 let base_key = match type_def.resolved_base_type {
1141 Some(key) => key,
1142 None => return Ok(()), };
1144
1145 match base_key {
1146 TypeKey::Simple(base_simple_key) => {
1147 let (location, type_name) = type_error_context(schema_set, type_def);
1150 let base_name =
1151 if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
1152 format_type_name(schema_set, base_type.name, base_type.target_namespace)
1153 } else {
1154 "(unknown)".to_string()
1155 };
1156 return Err(SchemaError::structural(
1157 "ct-props-correct",
1158 format!(
1159 "Complex type '{}' cannot restrict simple type '{}'; \
1160 derivation from a simple type must use extension",
1161 type_name, base_name,
1162 ),
1163 location,
1164 ));
1165 }
1166 TypeKey::Complex(base_complex_key) => {
1167 if let Some(base_type) = schema_set.arenas.complex_types.get(base_complex_key) {
1168 if base_type.final_derivation.contains_restriction() {
1170 let (location, type_name) = type_error_context(schema_set, type_def);
1171 let base_name =
1172 format_type_name(schema_set, base_type.name, base_type.target_namespace);
1173 return Err(SchemaError::structural(
1174 "derivation-ok-restriction",
1175 format!(
1176 "Complex type '{}' cannot restrict '{}' because base type is final for restriction",
1177 type_name, base_name
1178 ),
1179 location,
1180 ));
1181 }
1182
1183 if let (
1192 ComplexContentResult::Complex(base_complex),
1193 ComplexContentResult::Complex(derived_complex),
1194 ) = (&base_type.content, &type_def.content)
1195 {
1196 let base_mixed = effective_mixed_of(base_type, base_complex);
1197 let derived_mixed = effective_mixed_of(type_def, derived_complex);
1198 if derived_mixed && !base_mixed {
1199 let (location, type_name) = type_error_context(schema_set, type_def);
1200 let base_name = format_type_name(
1201 schema_set,
1202 base_type.name,
1203 base_type.target_namespace,
1204 );
1205 return Err(SchemaError::structural(
1206 "derivation-ok-restriction",
1207 format!(
1208 "Complex type '{}' cannot restrict element-only base '{}' \
1209 to mixed content (§3.4.6.4 clause 5.4.1)",
1210 type_name, base_name,
1211 ),
1212 location,
1213 ));
1214 }
1215 }
1216
1217 if matches!(type_def.content, ComplexContentResult::Simple(_))
1224 && !is_valid_simple_content_restriction_base(schema_set, base_type)
1225 {
1226 let (location, type_name) = type_error_context(schema_set, type_def);
1227 let base_name =
1228 format_type_name(schema_set, base_type.name, base_type.target_namespace);
1229 return Err(SchemaError::structural(
1230 "src-ct",
1231 format!(
1232 "Complex type '{}' uses xs:simpleContent restriction but base '{}' \
1233 does not have a simple {{content type}} nor mixed+emptiable content \
1234 (src-ct.2.1.1 / 2.1.2)",
1235 type_name, base_name,
1236 ),
1237 location,
1238 ));
1239 }
1240
1241 validate_content_particle_restriction(schema_set, type_def, base_type)?;
1242
1243 #[cfg(feature = "xsd11")]
1254 validate_all_group_restriction_edc(schema_set, type_def, base_type)?;
1255
1256 #[cfg(feature = "xsd11")]
1258 validate_open_content_restriction(schema_set, type_def, base_type)?;
1259
1260 validate_attribute_restriction(schema_set, type_def, base_type)?;
1262
1263 validate_simple_content_restriction(schema_set, type_def, base_type)?;
1266 }
1267 }
1268 }
1269
1270 Ok(())
1271}
1272
1273fn effective_mixed_of(
1278 type_def: &crate::arenas::ComplexTypeDefData,
1279 complex: &crate::parser::frames::ComplexContentDefResult,
1280) -> bool {
1281 complex.mixed || type_def.mixed
1287}
1288
1289fn validate_extension_mixed_parity(
1300 schema_set: &SchemaSet,
1301 type_def: &crate::arenas::ComplexTypeDefData,
1302 base_type: &crate::arenas::ComplexTypeDefData,
1303) -> SchemaResult<()> {
1304 let ComplexContentResult::Complex(ref base_complex) = base_type.content else {
1305 return Ok(());
1306 };
1307 let ComplexContentResult::Complex(ref derived_complex) = type_def.content else {
1308 return Ok(());
1309 };
1310 let base_mixed = effective_mixed_of(base_type, base_complex);
1311 let derived_mixed = effective_mixed_of(type_def, derived_complex);
1312 if derived_mixed == base_mixed {
1313 return Ok(());
1314 }
1315 if derived_complex.particle.is_none() && !derived_complex.mixed && !type_def.mixed {
1318 return Ok(());
1319 }
1320 let (location, type_name) = type_error_context(schema_set, type_def);
1321 let base_name = format_type_name(schema_set, base_type.name, base_type.target_namespace);
1322 Err(SchemaError::structural(
1323 "cos-ct-extends",
1324 format!(
1325 "Complex type '{}' cannot extend '{}' — derived is {} but base is {} \
1326 (cos-ct-extends clause 1.4.3.2.2.4.1)",
1327 type_name,
1328 base_name,
1329 if derived_mixed {
1330 "mixed"
1331 } else {
1332 "element-only"
1333 },
1334 if base_mixed { "mixed" } else { "element-only" },
1335 ),
1336 location,
1337 ))
1338}
1339
1340fn is_valid_simple_content_restriction_base(
1346 schema_set: &SchemaSet,
1347 base: &crate::arenas::ComplexTypeDefData,
1348) -> bool {
1349 match &base.content {
1350 ComplexContentResult::Simple(_) => true,
1351 ComplexContentResult::Complex(complex) => {
1352 if !complex.mixed {
1357 return false;
1358 }
1359 match &complex.particle {
1360 None => true,
1362 Some(particle) => match normalize_type_particle(schema_set, base, particle) {
1363 Ok(normalized) => particle_is_emptiable(&normalized),
1364 Err(_) => false,
1365 },
1366 }
1367 }
1368 ComplexContentResult::Empty => base.mixed,
1376 }
1377}
1378
1379#[derive(Debug, Clone)]
1380struct NormalizedParticle {
1381 term: NormalizedParticleTerm,
1382 min_occurs: u32,
1383 max_occurs: Option<u32>,
1384 source: Option<SourceRef>,
1385}
1386
1387#[derive(Debug, Clone)]
1388enum NormalizedParticleTerm {
1389 Element(NormalizedElement),
1390 Wildcard(Box<NormalizedWildcard>),
1391 Group(NormalizedGroup),
1392}
1393
1394#[derive(Debug, Clone)]
1395struct NormalizedElement {
1396 name: NameId,
1397 namespace: Option<NameId>,
1398 type_key: TypeKey,
1399 element_key: Option<ElementKey>,
1400 block: DerivationSet,
1401 nillable: bool,
1402 fixed_value: Option<String>,
1403}
1404
1405#[derive(Debug, Clone)]
1406struct NormalizedWildcard {
1407 wildcard: WildcardResult,
1408 target_namespace: Option<NameId>,
1409}
1410
1411#[derive(Debug, Clone)]
1412struct NormalizedGroup {
1413 compositor: Compositor,
1414 particles: Vec<NormalizedParticle>,
1415}
1416
1417struct ParticleNormalizer<'a> {
1418 schema_set: &'a SchemaSet,
1419 target_namespace: Option<NameId>,
1420 resolved_types: &'a [Option<TypeKey>],
1421 flat_index: usize,
1422 depth: usize,
1423}
1424
1425const MAX_PARTICLE_RESTRICTION_DEPTH: usize = 100;
1426
1427impl<'a> ParticleNormalizer<'a> {
1428 fn new(
1429 schema_set: &'a SchemaSet,
1430 target_namespace: Option<NameId>,
1431 resolved_types: &'a [Option<TypeKey>],
1432 ) -> Self {
1433 Self {
1434 schema_set,
1435 target_namespace,
1436 resolved_types,
1437 flat_index: 0,
1438 depth: 0,
1439 }
1440 }
1441
1442 fn normalize_particle(
1443 &mut self,
1444 particle: &ParticleResult,
1445 ) -> SchemaResult<NormalizedParticle> {
1446 if self.depth >= MAX_PARTICLE_RESTRICTION_DEPTH {
1447 return Err(SchemaError::internal(
1448 "particle restriction normalization exceeded recursion limit",
1449 ));
1450 }
1451
1452 self.depth += 1;
1453 let term = match &particle.term {
1454 ParticleTerm::Element(elem) => {
1455 let source = particle.source.as_ref().or(elem.source.as_ref());
1456 NormalizedParticleTerm::Element(self.normalize_element(elem, source)?)
1457 }
1458 ParticleTerm::Any(wildcard) => {
1459 NormalizedParticleTerm::Wildcard(Box::new(NormalizedWildcard {
1460 wildcard: wildcard.clone(),
1461 target_namespace: self.target_namespace,
1462 }))
1463 }
1464 ParticleTerm::Group(group) => {
1465 NormalizedParticleTerm::Group(self.normalize_group(group)?)
1466 }
1467 };
1468 self.depth -= 1;
1469
1470 Ok(collapse_single_child_groups(NormalizedParticle {
1471 term,
1472 min_occurs: particle.min_occurs,
1473 max_occurs: particle.max_occurs,
1474 source: particle.source.clone(),
1475 }))
1476 }
1477
1478 fn normalize_element(
1479 &mut self,
1480 elem: &ElementFrameResult,
1481 source: Option<&SourceRef>,
1482 ) -> SchemaResult<NormalizedElement> {
1483 if let Some(ref_name) = &elem.ref_name {
1484 let elem_key = self
1485 .schema_set
1486 .lookup_element(ref_name.namespace, ref_name.local_name);
1487 let (type_key, block, nillable, fixed_value) = elem_key
1488 .and_then(|key| self.schema_set.arenas.elements.get(key))
1489 .map(|decl| {
1490 let (eff_block, _) =
1491 crate::compiler::substitution::effective_element_constraints(
1492 self.schema_set,
1493 decl,
1494 );
1495 let tk = decl
1496 .resolved_type
1497 .unwrap_or_else(|| TypeKey::Complex(self.schema_set.any_type_key()));
1498 (tk, eff_block, decl.nillable, decl.fixed_value.clone())
1499 })
1500 .unwrap_or_else(|| {
1501 (
1502 TypeKey::Complex(self.schema_set.any_type_key()),
1503 DerivationSet::empty(),
1504 false,
1505 None,
1506 )
1507 });
1508 return Ok(NormalizedElement {
1509 name: ref_name.local_name,
1510 namespace: ref_name.namespace,
1511 type_key,
1512 element_key: elem_key,
1513 block,
1514 nillable,
1515 fixed_value,
1516 });
1517 }
1518
1519 let name = elem
1520 .name
1521 .ok_or_else(|| SchemaError::internal("element particle missing name and ref"))?;
1522 let index = self.flat_index;
1523 self.flat_index += 1;
1524
1525 let namespace = self.schema_set.effective_local_element_namespace(
1526 elem.target_namespace,
1527 elem.form.as_deref(),
1528 source,
1529 self.target_namespace,
1530 );
1531 let type_key = self
1532 .resolved_types
1533 .get(index)
1534 .copied()
1535 .flatten()
1536 .or_else(|| resolve_element_type_ref(self.schema_set, elem))
1537 .unwrap_or_else(|| TypeKey::Complex(self.schema_set.any_type_key()));
1538
1539 let block = match elem.block {
1542 Some(b) => b,
1543 None => source
1544 .and_then(|s| {
1545 let doc_id = s.schema_defaults_doc.unwrap_or(s.doc_id);
1546 self.schema_set
1547 .documents
1548 .get(doc_id as usize)
1549 .map(|d| d.block_default)
1550 })
1551 .unwrap_or_default(),
1552 };
1553
1554 Ok(NormalizedElement {
1555 name,
1556 namespace,
1557 type_key,
1558 element_key: None,
1559 block,
1560 nillable: elem.nillable,
1561 fixed_value: elem.fixed_value.clone(),
1562 })
1563 }
1564
1565 fn normalize_group(&mut self, group: &ModelGroupDefResult) -> SchemaResult<NormalizedGroup> {
1566 if let Some(ref_name) = &group.ref_name {
1567 let group_key = self
1568 .schema_set
1569 .lookup_model_group(ref_name.namespace, ref_name.local_name)
1570 .ok_or_else(|| SchemaError::internal("model group reference was not resolved"))?;
1571 let group_data = self
1572 .schema_set
1573 .arenas
1574 .get_model_group(group_key)
1575 .ok_or_else(|| SchemaError::internal("resolved model group not found"))?;
1576 let compositor = group_data
1577 .compositor
1578 .ok_or_else(|| SchemaError::internal("resolved model group missing compositor"))?;
1579 let mut nested = ParticleNormalizer::new(
1580 self.schema_set,
1581 group_data.target_namespace,
1582 &group_data.resolved_particle_types,
1583 );
1584 nested.depth = self.depth;
1585 let particles = group_data
1586 .particles
1587 .iter()
1588 .map(|particle| nested.normalize_particle(particle))
1589 .collect::<SchemaResult<Vec<_>>>()?;
1590 return Ok(NormalizedGroup {
1591 compositor,
1592 particles,
1593 });
1594 }
1595
1596 let compositor = group
1597 .compositor
1598 .ok_or_else(|| SchemaError::internal("inline model group missing compositor"))?;
1599 let particles = group
1600 .particles
1601 .iter()
1602 .map(|particle| self.normalize_particle(particle))
1603 .collect::<SchemaResult<Vec<_>>>()?;
1604 Ok(NormalizedGroup {
1605 compositor,
1606 particles,
1607 })
1608 }
1609}
1610
1611fn resolve_element_type_ref(schema_set: &SchemaSet, elem: &ElementFrameResult) -> Option<TypeKey> {
1612 match &elem.type_ref {
1613 Some(crate::parser::frames::TypeRefResult::QName(qname)) => schema_set
1614 .lookup_type(qname.namespace, qname.local_name)
1615 .or_else(|| schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name)),
1616 _ => None,
1617 }
1618}
1619
1620fn validate_content_particle_restriction(
1621 schema_set: &SchemaSet,
1622 derived: &crate::arenas::ComplexTypeDefData,
1623 base: &crate::arenas::ComplexTypeDefData,
1624) -> SchemaResult<()> {
1625 let derived_particle = complex_content_particle(&derived.content);
1626 let (effective_base, base_particle) = effective_base_content_particle(schema_set, base);
1629
1630 let location = derived
1631 .source
1632 .as_ref()
1633 .and_then(|s| schema_set.source_maps.locate(s));
1634 let type_name = format_type_name(schema_set, derived.name, derived.target_namespace);
1635 let base_name = format_type_name(schema_set, base.name, base.target_namespace);
1636
1637 match (derived_particle, base_particle) {
1638 (None, None) => Ok(()),
1639 (Some(derived_particle), None) => {
1640 let derived_particle = normalize_type_particle(schema_set, derived, derived_particle)?;
1641 if !matches!(effective_base.content, ComplexContentResult::Simple(_))
1643 && is_effectively_empty(&derived_particle)
1644 {
1645 Ok(())
1646 } else {
1647 Err(SchemaError::structural(
1648 "derivation-ok-restriction",
1649 format!(
1650 "Complex type '{}' adds particle content while restricting '{}' which has empty content",
1651 type_name, base_name
1652 ),
1653 location,
1654 ))
1655 }
1656 }
1657 (None, Some(base_particle)) => {
1658 let base_particle = normalize_type_particle(schema_set, effective_base, base_particle)?;
1659 if particle_is_emptiable(&base_particle) {
1660 Ok(())
1661 } else {
1662 Err(SchemaError::structural(
1663 "derivation-ok-restriction",
1664 format!(
1665 "Complex type '{}' removes required particle content from base type '{}'",
1666 type_name, base_name
1667 ),
1668 location,
1669 ))
1670 }
1671 }
1672 (Some(derived_particle), Some(base_particle)) => {
1673 let derived_particle = normalize_type_particle(schema_set, derived, derived_particle)?;
1674 let base_particle = normalize_type_particle(schema_set, effective_base, base_particle)?;
1675
1676 if is_effectively_empty(&derived_particle) {
1677 if particle_is_emptiable(&base_particle) {
1678 return Ok(());
1679 } else {
1680 return Err(SchemaError::structural(
1681 "derivation-ok-restriction",
1682 format!(
1683 "Complex type '{}' removes required particle content from base type '{}'",
1684 type_name, base_name
1685 ),
1686 location,
1687 ));
1688 }
1689 }
1690
1691 if particle_restricts(schema_set, &derived_particle, &base_particle) {
1697 Ok(())
1698 } else {
1699 Err(SchemaError::structural(
1700 "derivation-ok-restriction",
1701 format!(
1702 "Content model of '{}' is not a valid restriction of base type '{}'",
1703 type_name, base_name
1704 ),
1705 location,
1706 ))
1707 }
1708 }
1709 }
1710}
1711
1712fn is_empty_group(particle: &NormalizedParticle) -> bool {
1714 matches!(&particle.term, NormalizedParticleTerm::Group(group) if group.particles.is_empty())
1715}
1716
1717fn is_effectively_empty(particle: &NormalizedParticle) -> bool {
1719 particle.max_occurs == Some(0) || is_empty_group(particle)
1720}
1721
1722fn complex_content_particle(content: &ComplexContentResult) -> Option<&ParticleResult> {
1723 match content {
1724 ComplexContentResult::Complex(def) => def.particle.as_ref(),
1725 ComplexContentResult::Empty | ComplexContentResult::Simple(_) => None,
1726 }
1727}
1728
1729fn effective_base_content_particle<'a>(
1734 schema_set: &'a SchemaSet,
1735 base: &'a crate::arenas::ComplexTypeDefData,
1736) -> (
1737 &'a crate::arenas::ComplexTypeDefData,
1738 Option<&'a ParticleResult>,
1739) {
1740 let mut current = base;
1741 let mut depth = 0;
1742 loop {
1743 if let Some(particle) = complex_content_particle(¤t.content) {
1744 return (current, Some(particle));
1745 }
1746 if current.derivation_method != Some(DerivationMethod::Extension) {
1748 return (current, None);
1749 }
1750 let Some(TypeKey::Complex(base_key)) = current.resolved_base_type else {
1751 return (current, None);
1752 };
1753 let Some(base_type) = schema_set.arenas.complex_types.get(base_key) else {
1754 return (current, None);
1755 };
1756 depth += 1;
1757 if depth > 50 {
1758 return (current, None); }
1760 current = base_type;
1761 }
1762}
1763
1764fn normalize_type_particle(
1765 schema_set: &SchemaSet,
1766 type_def: &crate::arenas::ComplexTypeDefData,
1767 particle: &ParticleResult,
1768) -> SchemaResult<NormalizedParticle> {
1769 let mut normalizer = ParticleNormalizer::new(
1770 schema_set,
1771 type_def.target_namespace,
1772 &type_def.resolved_content_particle_types,
1773 );
1774 let particle = normalizer.normalize_particle(particle)?;
1775 let particle = remove_pointless_particles(particle);
1776 if !schema_set.is_xsd11() {
1780 let particle = flatten_same_compositor_groups(particle);
1781 return Ok(particle);
1782 }
1783 Ok(particle)
1784}
1785
1786fn normalize_model_group_as_particle(
1798 schema_set: &SchemaSet,
1799 group_data: &crate::arenas::ModelGroupData,
1800) -> SchemaResult<NormalizedParticle> {
1801 let compositor = group_data
1802 .compositor
1803 .ok_or_else(|| SchemaError::internal("redefined named model group missing compositor"))?;
1804
1805 let mut normalizer = ParticleNormalizer::new(
1806 schema_set,
1807 group_data.target_namespace,
1808 &group_data.resolved_particle_types,
1809 );
1810 let particles = group_data
1811 .particles
1812 .iter()
1813 .map(|particle| normalizer.normalize_particle(particle))
1814 .collect::<SchemaResult<Vec<_>>>()?;
1815
1816 let wrapper = NormalizedParticle {
1817 term: NormalizedParticleTerm::Group(NormalizedGroup {
1818 compositor,
1819 particles,
1820 }),
1821 min_occurs: group_data.min_occurs,
1822 max_occurs: group_data.max_occurs,
1823 source: group_data.source.clone(),
1824 };
1825
1826 let particle = collapse_single_child_groups(wrapper);
1831 let particle = remove_pointless_particles(particle);
1832 if !schema_set.is_xsd11() {
1834 let particle = flatten_same_compositor_groups(particle);
1835 return Ok(particle);
1836 }
1837 Ok(particle)
1838}
1839
1840fn remove_pointless_particles(mut particle: NormalizedParticle) -> NormalizedParticle {
1844 if let NormalizedParticleTerm::Group(group) = &mut particle.term {
1845 group.particles = group
1846 .particles
1847 .drain(..)
1848 .map(remove_pointless_particles)
1849 .filter(|p| p.max_occurs != Some(0))
1850 .collect();
1851 }
1852 particle
1853}
1854
1855fn flatten_same_compositor_groups(mut particle: NormalizedParticle) -> NormalizedParticle {
1859 if let NormalizedParticleTerm::Group(group) = &mut particle.term {
1860 group.particles = group
1862 .particles
1863 .drain(..)
1864 .map(flatten_same_compositor_groups)
1865 .collect();
1866 let parent_compositor = group.compositor;
1868 let mut flattened = Vec::with_capacity(group.particles.len());
1869 for child in group.particles.drain(..) {
1870 if let NormalizedParticleTerm::Group(ref child_group) = child.term {
1871 if child_group.compositor == parent_compositor
1872 && occurs_is_unit(child.min_occurs, child.max_occurs)
1873 {
1874 flattened.extend(child_group.particles.iter().cloned());
1875 continue;
1876 }
1877 }
1878 flattened.push(child);
1879 }
1880 group.particles = flattened;
1881 }
1882 particle
1883}
1884
1885fn collapse_single_child_groups(mut particle: NormalizedParticle) -> NormalizedParticle {
1886 if let NormalizedParticleTerm::Group(group) = &mut particle.term {
1887 group.particles = group
1888 .particles
1889 .drain(..)
1890 .map(collapse_single_child_groups)
1891 .collect();
1892 }
1893
1894 loop {
1895 let child = match &particle.term {
1896 NormalizedParticleTerm::Group(group)
1897 if group.particles.len() == 1
1898 && can_collapse_single_child_group(
1899 group.compositor,
1900 particle.min_occurs,
1901 particle.max_occurs,
1902 &group.particles[0],
1903 ) =>
1904 {
1905 Some(group.particles[0].clone())
1906 }
1907 _ => None,
1908 };
1909 let Some(child) = child else {
1910 return particle;
1911 };
1912 let (min_occurs, max_occurs) = multiply_occurs(
1913 particle.min_occurs,
1914 particle.max_occurs,
1915 child.min_occurs,
1916 child.max_occurs,
1917 );
1918 particle = NormalizedParticle {
1919 term: child.term,
1920 min_occurs,
1921 max_occurs,
1922 source: particle.source.clone().or(child.source),
1923 };
1924 }
1925}
1926
1927fn can_collapse_single_child_group(
1928 compositor: Compositor,
1929 group_min_occurs: u32,
1930 group_max_occurs: Option<u32>,
1931 child: &NormalizedParticle,
1932) -> bool {
1933 if compositor == Compositor::Choice {
1934 return true;
1935 }
1936
1937 occurs_is_unit(group_min_occurs, group_max_occurs) || child.max_occurs == Some(1)
1938}
1939
1940fn occurs_is_unit(min_occurs: u32, max_occurs: Option<u32>) -> bool {
1941 min_occurs == 1 && max_occurs == Some(1)
1942}
1943
1944fn multiply_occurs(
1945 left_min: u32,
1946 left_max: Option<u32>,
1947 right_min: u32,
1948 right_max: Option<u32>,
1949) -> (u32, Option<u32>) {
1950 let min_occurs = left_min.saturating_mul(right_min);
1951 let max_occurs = match (left_max, right_max) {
1952 (Some(left), Some(right)) => Some(left.saturating_mul(right)),
1953 (Some(0), None) | (None, Some(0)) => Some(0),
1954 _ => None,
1955 };
1956 (min_occurs, max_occurs)
1957}
1958
1959fn fold_single_child_group(particle: &NormalizedParticle) -> Option<NormalizedParticle> {
1962 if let NormalizedParticleTerm::Group(group) = &particle.term {
1963 if group.particles.len() == 1
1964 && matches!(group.compositor, Compositor::Sequence | Compositor::All)
1965 {
1966 let child = &group.particles[0];
1967 let (min_occurs, max_occurs) = multiply_occurs(
1968 particle.min_occurs,
1969 particle.max_occurs,
1970 child.min_occurs,
1971 child.max_occurs,
1972 );
1973 return Some(NormalizedParticle {
1974 term: child.term.clone(),
1975 min_occurs,
1976 max_occurs,
1977 source: particle.source.clone().or(child.source.clone()),
1978 });
1979 }
1980 }
1981 None
1982}
1983
1984fn particle_restricts(
1985 schema_set: &SchemaSet,
1986 derived: &NormalizedParticle,
1987 base: &NormalizedParticle,
1988) -> bool {
1989 if schema_set.is_xsd11() {
1992 if let Some(folded) = fold_single_child_group(derived) {
1993 return particle_restricts(schema_set, &folded, base);
1994 }
1995 if let Some(folded_base) = fold_single_child_group(base) {
1996 return particle_restricts(schema_set, derived, &folded_base);
1997 }
1998 }
1999
2000 if schema_set.is_xsd10()
2005 && derived.min_occurs == 0
2006 && !matches!(
2007 &derived.term,
2008 NormalizedParticleTerm::Group(group) if group.compositor == Compositor::Choice
2009 )
2010 && matches!(
2011 &base.term,
2012 NormalizedParticleTerm::Group(group)
2013 if group.compositor == Compositor::Choice
2014 && base.min_occurs == 0
2015 && base.max_occurs == Some(1)
2016 && group.particles.len() > 1
2017 )
2018 {
2019 return false;
2020 }
2021
2022 if let Some(base_branches) = expand_choice_branches(base) {
2023 if let Some(derived_branches) = expand_choice_branches(derived) {
2024 if schema_set.is_xsd10() {
2027 return choice_branches_restrict_ordered(
2028 schema_set,
2029 &derived_branches,
2030 &base_branches,
2031 );
2032 }
2033 let base_has_emptiable_branch = base_branches.iter().any(particle_is_emptiable);
2042 return derived_branches.iter().all(|branch| {
2043 if base_branches
2044 .iter()
2045 .any(|candidate| particle_restricts(schema_set, branch, candidate))
2046 {
2047 return true;
2048 }
2049 if branch.min_occurs == 0 && base_has_emptiable_branch {
2050 let mut non_empty = branch.clone();
2051 non_empty.min_occurs = non_empty.min_occurs.max(1);
2052 if non_empty.max_occurs.is_some_and(|m| m == 0) {
2053 return true;
2056 }
2057 return base_branches
2058 .iter()
2059 .any(|candidate| particle_restricts(schema_set, &non_empty, candidate));
2060 }
2061 false
2062 });
2063 }
2064
2065 if let NormalizedParticleTerm::Group(derived_group) = &derived.term {
2067 if derived_group.compositor == Compositor::Sequence {
2068 if schema_set.is_xsd11() {
2072 let any_branch = base_branches
2073 .iter()
2074 .any(|candidate| particle_restricts(schema_set, derived, candidate));
2075 if any_branch {
2076 return true;
2077 }
2078 }
2079 let NormalizedParticleTerm::Group(base_group) = &base.term else {
2080 unreachable!()
2081 };
2082 return sequence_restricts_choice(
2083 schema_set,
2084 derived,
2085 derived_group,
2086 base,
2087 base_group,
2088 );
2089 }
2090 }
2091
2092 return base_branches
2093 .iter()
2094 .any(|candidate| particle_restricts(schema_set, derived, candidate));
2095 }
2096
2097 if let Some(derived_branches) = expand_choice_branches(derived) {
2098 return derived_branches
2099 .iter()
2100 .all(|branch| particle_restricts(schema_set, branch, base));
2101 }
2102
2103 match (&derived.term, &base.term) {
2104 (
2105 NormalizedParticleTerm::Element(derived_element),
2106 NormalizedParticleTerm::Element(base_element),
2107 ) => {
2108 let names_match = derived_element.name == base_element.name
2109 && derived_element.namespace == base_element.namespace;
2110 let subst_match = !names_match
2111 && match (derived_element.element_key, base_element.element_key) {
2112 (Some(d_key), Some(b_key)) => {
2113 crate::compiler::substitution::is_element_substitutable_for(
2114 schema_set, b_key, d_key,
2115 )
2116 }
2117 _ => false,
2118 };
2119 (names_match || subst_match)
2122 && occurs_range_is_subset(
2124 derived.min_occurs,
2125 derived.max_occurs,
2126 base.min_occurs,
2127 base.max_occurs,
2128 )
2129 && (base_element.nillable || !derived_element.nillable)
2131 && match (&base_element.fixed_value, &derived_element.fixed_value) {
2133 (None, _) => true,
2134 (Some(_), None) => false,
2135 (Some(base_fixed), Some(derived_fixed)) => {
2136 crate::validation::simple::fixed_values_equal(
2137 derived_fixed,
2138 base_fixed,
2139 Some(derived_element.type_key),
2140 schema_set,
2141 )
2142 }
2143 }
2144 && derived_element.block.element_block_mask()
2147 .contains(base_element.block.element_block_mask())
2148 && schema_set.is_type_derived_from(
2150 derived_element.type_key,
2151 base_element.type_key,
2152 DerivationSet::extension(),
2153 )
2154 }
2155 (
2156 NormalizedParticleTerm::Element(element),
2157 NormalizedParticleTerm::Wildcard(base_wildcard),
2158 ) => {
2159 occurs_range_is_subset(
2160 derived.min_occurs,
2161 derived.max_occurs,
2162 base.min_occurs,
2163 base.max_occurs,
2164 ) && wildcard_allows_element(element, base_wildcard)
2165 }
2166 (
2167 NormalizedParticleTerm::Wildcard(derived_wildcard),
2168 NormalizedParticleTerm::Wildcard(base_wildcard),
2169 ) => {
2170 occurs_range_is_subset(
2171 derived.min_occurs,
2172 derived.max_occurs,
2173 base.min_occurs,
2174 base.max_occurs,
2175 ) && wildcard_restricts(derived_wildcard, base_wildcard)
2176 }
2177 (
2178 NormalizedParticleTerm::Group(derived_group),
2179 NormalizedParticleTerm::Wildcard(base_wildcard),
2180 ) => group_particle_restricts_wildcard(derived, derived_group, base, base_wildcard),
2181 (
2182 NormalizedParticleTerm::Group(derived_group),
2183 NormalizedParticleTerm::Group(base_group),
2184 ) if derived_group.compositor == base_group.compositor => {
2185 if !occurs_range_is_subset(
2186 derived.min_occurs,
2187 derived.max_occurs,
2188 base.min_occurs,
2189 base.max_occurs,
2190 ) {
2191 return false;
2192 }
2193 match derived_group.compositor {
2194 Compositor::Sequence => sequence_particles_restrict(
2195 schema_set,
2196 &derived_group.particles,
2197 &base_group.particles,
2198 ),
2199 Compositor::All => all_particles_restrict(
2200 schema_set,
2201 &derived_group.particles,
2202 &base_group.particles,
2203 ),
2204 Compositor::Choice => unreachable!("choice particles are handled earlier"),
2205 }
2206 }
2207 (
2211 NormalizedParticleTerm::Group(derived_group),
2212 NormalizedParticleTerm::Group(base_group),
2213 ) if derived_group.compositor == Compositor::Sequence
2214 && base_group.compositor == Compositor::All =>
2215 {
2216 if !occurs_range_is_subset(
2217 derived.min_occurs,
2218 derived.max_occurs,
2219 base.min_occurs,
2220 base.max_occurs,
2221 ) {
2222 return false;
2223 }
2224 recurse_unordered(schema_set, &derived_group.particles, &base_group.particles)
2225 }
2226 (
2229 NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_),
2230 NormalizedParticleTerm::Group(base_group),
2231 ) if base_group.compositor == Compositor::Sequence => {
2232 occurs_range_is_subset(1, Some(1), base.min_occurs, base.max_occurs)
2233 && sequence_particles_restrict(
2234 schema_set,
2235 std::slice::from_ref(derived),
2236 &base_group.particles,
2237 )
2238 }
2239 (
2240 NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_),
2241 NormalizedParticleTerm::Group(base_group),
2242 ) if base_group.compositor == Compositor::All => {
2243 occurs_range_is_subset(1, Some(1), base.min_occurs, base.max_occurs)
2244 && all_particles_restrict(
2245 schema_set,
2246 std::slice::from_ref(derived),
2247 &base_group.particles,
2248 )
2249 }
2250 _ => false,
2251 }
2252}
2253
2254fn group_particle_restricts_wildcard(
2255 derived: &NormalizedParticle,
2256 group: &NormalizedGroup,
2257 base: &NormalizedParticle,
2258 wildcard: &NormalizedWildcard,
2259) -> bool {
2260 let (derived_min, derived_max) = particle_total_occurrence_range(derived);
2261 if !occurs_range_is_subset(derived_min, derived_max, base.min_occurs, base.max_occurs) {
2262 return false;
2263 }
2264
2265 group_particles_fit_wildcard(&group.particles, wildcard)
2266}
2267
2268fn occurs_range_is_subset(
2269 derived_min: u32,
2270 derived_max: Option<u32>,
2271 base_min: u32,
2272 base_max: Option<u32>,
2273) -> bool {
2274 if derived_min < base_min {
2275 return false;
2276 }
2277
2278 match (derived_max, base_max) {
2279 (_, None) => true,
2280 (Some(derived), Some(base)) => derived <= base,
2281 (None, Some(_)) => false,
2282 }
2283}
2284
2285fn expand_choice_branches(particle: &NormalizedParticle) -> Option<Vec<NormalizedParticle>> {
2286 let NormalizedParticleTerm::Group(group) = &particle.term else {
2287 return None;
2288 };
2289 if group.compositor != Compositor::Choice {
2290 return None;
2291 }
2292
2293 Some(
2294 group
2295 .particles
2296 .iter()
2297 .map(|child| {
2298 let (min_occurs, max_occurs) = multiply_occurs(
2299 particle.min_occurs,
2300 particle.max_occurs,
2301 child.min_occurs,
2302 child.max_occurs,
2303 );
2304 collapse_single_child_groups(NormalizedParticle {
2305 term: child.term.clone(),
2306 min_occurs,
2307 max_occurs,
2308 source: particle.source.clone().or(child.source.clone()),
2309 })
2310 })
2311 .collect(),
2312 )
2313}
2314
2315fn particle_total_occurrence_range(particle: &NormalizedParticle) -> (u32, Option<u32>) {
2316 let (term_min, term_max) = match &particle.term {
2317 NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_) => (1, Some(1)),
2318 NormalizedParticleTerm::Group(group) => match group.compositor {
2319 Compositor::Sequence | Compositor::All => {
2320 group
2321 .particles
2322 .iter()
2323 .fold((0u32, Some(0u32)), |(acc_min, acc_max), child| {
2324 let (child_min, child_max) = particle_total_occurrence_range(child);
2325 (
2326 acc_min.saturating_add(child_min),
2327 add_optional_occurs(acc_max, child_max),
2328 )
2329 })
2330 }
2331 Compositor::Choice => {
2332 let mut min_total: Option<u32> = None;
2333 let mut max_total: Option<Option<u32>> = None;
2334 for child in &group.particles {
2335 let (child_min, child_max) = particle_total_occurrence_range(child);
2336 min_total = Some(match min_total {
2337 Some(current) => current.min(child_min),
2338 None => child_min,
2339 });
2340 max_total = Some(match max_total {
2341 Some(current) => max_optional_occurs(current, child_max),
2342 None => child_max,
2343 });
2344 }
2345 (min_total.unwrap_or(0), max_total.unwrap_or(Some(0)))
2346 }
2347 },
2348 };
2349
2350 multiply_occurs(particle.min_occurs, particle.max_occurs, term_min, term_max)
2351}
2352
2353fn add_optional_occurs(left: Option<u32>, right: Option<u32>) -> Option<u32> {
2354 match (left, right) {
2355 (Some(left), Some(right)) => Some(left.saturating_add(right)),
2356 _ => None,
2357 }
2358}
2359
2360fn max_optional_occurs(left: Option<u32>, right: Option<u32>) -> Option<u32> {
2361 match (left, right) {
2362 (Some(left), Some(right)) => Some(left.max(right)),
2363 _ => None,
2364 }
2365}
2366
2367fn group_particles_fit_wildcard(
2368 particles: &[NormalizedParticle],
2369 wildcard: &NormalizedWildcard,
2370) -> bool {
2371 particles
2372 .iter()
2373 .all(|particle| particle_fits_wildcard(particle, wildcard))
2374}
2375
2376fn particle_fits_wildcard(particle: &NormalizedParticle, wildcard: &NormalizedWildcard) -> bool {
2377 if let Some(branches) = expand_choice_branches(particle) {
2378 return branches
2379 .iter()
2380 .all(|branch| particle_fits_wildcard(branch, wildcard));
2381 }
2382
2383 match &particle.term {
2384 NormalizedParticleTerm::Element(element) => wildcard_allows_element(element, wildcard),
2385 NormalizedParticleTerm::Wildcard(derived_wildcard) => {
2386 wildcard_restricts(derived_wildcard, wildcard)
2387 }
2388 NormalizedParticleTerm::Group(group) => {
2389 group_particles_fit_wildcard(&group.particles, wildcard)
2390 }
2391 }
2392}
2393
2394fn sequence_restricts_choice(
2406 schema_set: &SchemaSet,
2407 derived: &NormalizedParticle,
2408 derived_group: &NormalizedGroup,
2409 base: &NormalizedParticle,
2410 base_group: &NormalizedGroup,
2411) -> bool {
2412 let base_branches = &base_group.particles;
2413
2414 let mut required_per_iter: u32 = 0;
2415 let mut total_per_iter: u32 = 0;
2416
2417 for derived_child in &derived_group.particles {
2418 let found = base_branches
2420 .iter()
2421 .any(|branch| particle_restricts(schema_set, derived_child, branch));
2422 if !found {
2423 return false;
2424 }
2425
2426 if derived_child.min_occurs > 0 {
2428 required_per_iter += 1;
2429 }
2430 if derived_child.max_occurs != Some(0) {
2431 total_per_iter += 1;
2432 }
2433 }
2434
2435 let min_demand = derived.min_occurs.saturating_mul(required_per_iter);
2438 let max_demand = match derived.max_occurs {
2439 Some(m) => Some(m.saturating_mul(total_per_iter)),
2440 None => {
2441 if total_per_iter == 0 {
2442 Some(0)
2443 } else {
2444 None
2445 }
2446 }
2447 };
2448
2449 occurs_range_is_subset(min_demand, max_demand, base.min_occurs, base.max_occurs)
2450}
2451
2452fn choice_branches_restrict_ordered(
2456 schema_set: &SchemaSet,
2457 derived_branches: &[NormalizedParticle],
2458 base_branches: &[NormalizedParticle],
2459) -> bool {
2460 let mut base_index = 0;
2461 for derived in derived_branches {
2462 let mut found = false;
2463 while base_index < base_branches.len() {
2464 if particle_restricts(schema_set, derived, &base_branches[base_index]) {
2465 base_index += 1;
2466 found = true;
2467 break;
2468 }
2469 base_index += 1;
2470 }
2471 if !found {
2472 return false;
2473 }
2474 }
2475 true
2476}
2477
2478fn sequence_particles_restrict(
2479 schema_set: &SchemaSet,
2480 derived_particles: &[NormalizedParticle],
2481 base_particles: &[NormalizedParticle],
2482) -> bool {
2483 let mut base_index = 0;
2484 let mut derived_index = 0;
2485
2486 while derived_index < derived_particles.len() {
2487 let mut matched = false;
2488
2489 while let Some(base) = base_particles.get(base_index) {
2490 if particle_restricts(schema_set, &derived_particles[derived_index], base) {
2492 matched = true;
2493 base_index += 1;
2494 derived_index += 1;
2495 break;
2496 }
2497
2498 if let NormalizedParticleTerm::Group(base_group) = &base.term {
2504 if base_group.compositor == Compositor::Sequence && !base_group.particles.is_empty()
2505 {
2506 let unit_len = base_group.particles.len();
2507 let remaining = derived_particles.len() - derived_index;
2508 if remaining >= unit_len
2509 && sequence_particles_restrict(
2510 schema_set,
2511 &derived_particles[derived_index..derived_index + unit_len],
2512 &base_group.particles,
2513 )
2514 {
2515 matched = true;
2516 base_index += 1;
2517 derived_index += unit_len;
2518 break;
2519 }
2520 }
2521 }
2522
2523 if schema_set.is_xsd11() {
2527 if let Some(branches) = expand_choice_branches(&derived_particles[derived_index]) {
2528 let all_ok = branches.iter().all(|branch| {
2529 let mut remaining = vec![branch.clone()];
2530 remaining.extend_from_slice(&derived_particles[derived_index + 1..]);
2531 sequence_particles_restrict(
2532 schema_set,
2533 &remaining,
2534 &base_particles[base_index..],
2535 )
2536 });
2537 if all_ok {
2538 return true;
2539 }
2540 }
2541 }
2542
2543 if schema_set.is_xsd11() {
2548 if let NormalizedParticleTerm::Group(dg) = &derived_particles[derived_index].term {
2549 if dg.compositor == Compositor::Sequence
2550 && occurs_is_unit(
2551 derived_particles[derived_index].min_occurs,
2552 derived_particles[derived_index].max_occurs,
2553 )
2554 && !dg.particles.is_empty()
2555 {
2556 let mut inlined = dg.particles.clone();
2557 inlined.extend_from_slice(&derived_particles[derived_index + 1..]);
2558 if sequence_particles_restrict(
2559 schema_set,
2560 &inlined,
2561 &base_particles[base_index..],
2562 ) {
2563 return true;
2564 }
2565 }
2566 }
2567 }
2568
2569 if particle_is_emptiable(base) {
2571 base_index += 1;
2572 continue;
2573 }
2574
2575 return false;
2576 }
2577
2578 if !matched {
2579 return false;
2580 }
2581 }
2582
2583 base_particles[base_index..]
2584 .iter()
2585 .all(particle_is_emptiable)
2586}
2587
2588fn merge_duplicate_elements(particles: &[NormalizedParticle]) -> Vec<NormalizedParticle> {
2601 let mut merged: Vec<NormalizedParticle> = Vec::with_capacity(particles.len());
2602 for particle in particles {
2603 let NormalizedParticleTerm::Element(elem) = &particle.term else {
2604 merged.push(particle.clone());
2605 continue;
2606 };
2607 let existing = merged.iter_mut().find(|m| {
2608 matches!(
2609 &m.term,
2610 NormalizedParticleTerm::Element(other)
2611 if other.name == elem.name && other.namespace == elem.namespace
2612 )
2613 });
2614 if let Some(existing) = existing {
2615 existing.min_occurs = existing.min_occurs.saturating_add(particle.min_occurs);
2616 existing.max_occurs = match (existing.max_occurs, particle.max_occurs) {
2617 (None, _) | (_, None) => None,
2618 (Some(a), Some(b)) => Some(a.saturating_add(b)),
2619 };
2620 } else {
2621 merged.push(particle.clone());
2622 }
2623 }
2624 merged
2625}
2626
2627fn recurse_unordered(
2642 schema_set: &SchemaSet,
2643 derived_particles: &[NormalizedParticle],
2644 base_particles: &[NormalizedParticle],
2645) -> bool {
2646 if let Some(result) = try_count_based_subsumption(schema_set, derived_particles, base_particles)
2649 {
2650 if result {
2651 return true;
2652 }
2653 }
2658
2659 fn backtrack(
2661 schema_set: &SchemaSet,
2662 derived_particles: &[NormalizedParticle],
2663 base_particles: &[NormalizedParticle],
2664 used: &mut [bool],
2665 derived_index: usize,
2666 ) -> bool {
2667 if derived_index == derived_particles.len() {
2668 return base_particles
2669 .iter()
2670 .enumerate()
2671 .all(|(index, particle)| used[index] || particle_is_emptiable(particle));
2672 }
2673
2674 for (base_index, base_particle) in base_particles.iter().enumerate() {
2675 if used[base_index]
2676 || !particle_restricts(schema_set, &derived_particles[derived_index], base_particle)
2677 {
2678 continue;
2679 }
2680 used[base_index] = true;
2681 if backtrack(
2682 schema_set,
2683 derived_particles,
2684 base_particles,
2685 used,
2686 derived_index + 1,
2687 ) {
2688 return true;
2689 }
2690 used[base_index] = false;
2691 }
2692
2693 false
2694 }
2695
2696 let merged_owned;
2699 let derived_particles = if derived_particles
2700 .iter()
2701 .any(|p| matches!(&p.term, NormalizedParticleTerm::Element(_)))
2702 && has_duplicate_element_names(derived_particles)
2703 {
2704 merged_owned = merge_duplicate_elements(derived_particles);
2705 &merged_owned[..]
2706 } else {
2707 derived_particles
2708 };
2709
2710 let mut used = vec![false; base_particles.len()];
2711 backtrack(schema_set, derived_particles, base_particles, &mut used, 0)
2712}
2713
2714fn try_count_based_subsumption(
2729 schema_set: &SchemaSet,
2730 derived_particles: &[NormalizedParticle],
2731 base_particles: &[NormalizedParticle],
2732) -> Option<bool> {
2733 let expanded = expand_top_level_choices_for_unordered(derived_particles)?;
2735
2736 let mut buckets: Vec<Vec<(u32, Option<u32>)>> = vec![Vec::new(); base_particles.len()];
2738 for derived in &expanded {
2739 if matches!(&derived.term, NormalizedParticleTerm::Group(_)) {
2741 return None;
2742 }
2743
2744 match find_subsumption_bucket(schema_set, derived, base_particles)? {
2745 BucketAssignment::Single(idx) => {
2746 buckets[idx].push((derived.min_occurs, derived.max_occurs))
2747 }
2748 BucketAssignment::Partition(idxs) => {
2749 for &i in &idxs {
2754 let base = &base_particles[i];
2755 if base.min_occurs > 0 {
2756 return Some(false);
2757 }
2758 if !occurs_max_fits(derived.max_occurs, base.max_occurs) {
2759 return Some(false);
2760 }
2761 }
2762 for &i in &idxs {
2766 buckets[i].push((0, derived.max_occurs));
2767 }
2768 }
2769 BucketAssignment::None => return Some(false),
2770 }
2771 }
2772
2773 for (i, ranges) in buckets.iter().enumerate() {
2776 let base = &base_particles[i];
2777 if ranges.is_empty() {
2778 if !particle_is_emptiable(base) {
2779 return Some(false);
2780 }
2781 } else {
2782 let (sum_min, sum_max) =
2783 ranges
2784 .iter()
2785 .fold((0u32, Some(0u32)), |(amin, amax), &(min, max)| {
2786 (amin.saturating_add(min), add_optional_occurs(amax, max))
2787 });
2788 if !occurs_range_is_subset(sum_min, sum_max, base.min_occurs, base.max_occurs) {
2789 return Some(false);
2790 }
2791 }
2792 }
2793
2794 Some(true)
2795}
2796
2797fn expand_top_level_choices_for_unordered(
2806 particles: &[NormalizedParticle],
2807) -> Option<Vec<NormalizedParticle>> {
2808 let mut result = Vec::with_capacity(particles.len());
2809 for p in particles {
2810 match &p.term {
2811 NormalizedParticleTerm::Group(group) if group.compositor == Compositor::Choice => {
2812 let outer_max = p.max_occurs;
2815 for branch in &group.particles {
2816 if matches!(&branch.term, NormalizedParticleTerm::Group(_)) {
2817 return None;
2819 }
2820 let new_max = match (outer_max, branch.max_occurs) {
2821 (Some(om), Some(bm)) => Some(om.saturating_mul(bm)),
2822 _ => None,
2823 };
2824 result.push(NormalizedParticle {
2825 term: branch.term.clone(),
2826 min_occurs: 0,
2827 max_occurs: new_max,
2828 source: branch.source.clone(),
2829 });
2830 }
2831 }
2832 NormalizedParticleTerm::Group(_) => {
2833 return None;
2835 }
2836 _ => result.push(p.clone()),
2837 }
2838 }
2839 Some(result)
2840}
2841
2842#[derive(Debug, Clone)]
2844enum BucketAssignment {
2845 Single(usize),
2847 Partition(Vec<usize>),
2851 None,
2853}
2854
2855fn find_subsumption_bucket(
2873 schema_set: &SchemaSet,
2874 derived: &NormalizedParticle,
2875 base_particles: &[NormalizedParticle],
2876) -> Option<BucketAssignment> {
2877 match &derived.term {
2878 NormalizedParticleTerm::Element(d_elem) => {
2879 for (i, base) in base_particles.iter().enumerate() {
2881 if let NormalizedParticleTerm::Element(b_elem) = &base.term {
2882 if d_elem.name == b_elem.name
2883 && d_elem.namespace == b_elem.namespace
2884 && name_and_type_ok_no_occurs(schema_set, d_elem, b_elem)
2885 {
2886 return Some(BucketAssignment::Single(i));
2887 }
2888 }
2889 }
2890 for (i, base) in base_particles.iter().enumerate() {
2892 if let NormalizedParticleTerm::Element(b_elem) = &base.term {
2893 if d_elem.name == b_elem.name && d_elem.namespace == b_elem.namespace {
2894 continue; }
2896 if derived_element_substitutes_base(schema_set, d_elem, b_elem)
2897 && name_and_type_ok_no_occurs(schema_set, d_elem, b_elem)
2898 {
2899 return Some(BucketAssignment::Single(i));
2900 }
2901 }
2902 }
2903 for (i, base) in base_particles.iter().enumerate() {
2905 if let NormalizedParticleTerm::Wildcard(b_wc) = &base.term {
2906 if wildcard_allows_element(d_elem, b_wc) {
2907 return Some(BucketAssignment::Single(i));
2908 }
2909 }
2910 }
2911 Some(BucketAssignment::None)
2912 }
2913 NormalizedParticleTerm::Wildcard(d_wc) => {
2914 for (i, base) in base_particles.iter().enumerate() {
2916 if let NormalizedParticleTerm::Wildcard(b_wc) = &base.term {
2917 if wildcard_restricts(d_wc, b_wc) {
2918 return Some(BucketAssignment::Single(i));
2919 }
2920 }
2921 }
2922 let candidate_idxs: Vec<usize> = base_particles
2927 .iter()
2928 .enumerate()
2929 .filter_map(|(i, base)| {
2930 let NormalizedParticleTerm::Wildcard(b_wc) = &base.term else {
2931 return None;
2932 };
2933 if process_contents_strictness(d_wc.wildcard.process_contents)
2934 < process_contents_strictness(b_wc.wildcard.process_contents)
2935 {
2936 return None;
2937 }
2938 Some(i)
2939 })
2940 .collect();
2941 if candidate_idxs.len() >= 2 {
2942 let bases: Vec<&NormalizedWildcard> = candidate_idxs
2943 .iter()
2944 .map(|&i| match &base_particles[i].term {
2945 NormalizedParticleTerm::Wildcard(b_wc) => b_wc.as_ref(),
2946 _ => unreachable!(),
2947 })
2948 .collect();
2949 if let Some(spanned) = wildcard_subset_of_union(d_wc, &bases) {
2950 let idxs: Vec<usize> = spanned
2951 .into_iter()
2952 .map(|local_idx| candidate_idxs[local_idx])
2953 .collect();
2954 return Some(BucketAssignment::Partition(idxs));
2955 }
2956 }
2957 Some(BucketAssignment::None)
2958 }
2959 NormalizedParticleTerm::Group(_) => None,
2960 }
2961}
2962
2963fn occurs_max_fits(derived_max: Option<u32>, base_max: Option<u32>) -> bool {
2967 match (derived_max, base_max) {
2968 (_, None) => true,
2969 (None, Some(_)) => false,
2970 (Some(d), Some(b)) => d <= b,
2971 }
2972}
2973
2974fn name_and_type_ok_no_occurs(
2978 schema_set: &SchemaSet,
2979 derived: &NormalizedElement,
2980 base: &NormalizedElement,
2981) -> bool {
2982 if derived.nillable && !base.nillable {
2984 return false;
2985 }
2986 match (&base.fixed_value, &derived.fixed_value) {
2988 (None, _) => {}
2989 (Some(_), None) => return false,
2990 (Some(base_fixed), Some(derived_fixed)) => {
2991 if !crate::validation::simple::fixed_values_equal(
2992 derived_fixed,
2993 base_fixed,
2994 Some(derived.type_key),
2995 schema_set,
2996 ) {
2997 return false;
2998 }
2999 }
3000 }
3001 if !derived
3003 .block
3004 .element_block_mask()
3005 .contains(base.block.element_block_mask())
3006 {
3007 return false;
3008 }
3009 schema_set.is_type_derived_from(derived.type_key, base.type_key, DerivationSet::extension())
3011}
3012
3013fn derived_element_substitutes_base(
3018 schema_set: &SchemaSet,
3019 derived: &NormalizedElement,
3020 base: &NormalizedElement,
3021) -> bool {
3022 let d_key = derived
3023 .element_key
3024 .or_else(|| schema_set.lookup_element(derived.namespace, derived.name));
3025 let b_key = base
3026 .element_key
3027 .or_else(|| schema_set.lookup_element(base.namespace, base.name));
3028 match (d_key, b_key) {
3029 (Some(d), Some(b)) => {
3030 crate::compiler::substitution::is_element_substitutable_for(schema_set, b, d)
3031 }
3032 _ => false,
3033 }
3034}
3035
3036fn has_duplicate_element_names(particles: &[NormalizedParticle]) -> bool {
3041 for (i, a) in particles.iter().enumerate() {
3042 let NormalizedParticleTerm::Element(a_elem) = &a.term else {
3043 continue;
3044 };
3045 for b in &particles[i + 1..] {
3046 if let NormalizedParticleTerm::Element(b_elem) = &b.term {
3047 if a_elem.name == b_elem.name && a_elem.namespace == b_elem.namespace {
3048 return true;
3049 }
3050 }
3051 }
3052 }
3053 false
3054}
3055
3056fn all_particles_restrict(
3057 schema_set: &SchemaSet,
3058 derived_particles: &[NormalizedParticle],
3059 base_particles: &[NormalizedParticle],
3060) -> bool {
3061 if schema_set.is_xsd10() {
3064 return sequence_particles_restrict(schema_set, derived_particles, base_particles);
3065 }
3066 recurse_unordered(schema_set, derived_particles, base_particles)
3067}
3068
3069fn particle_is_emptiable(particle: &NormalizedParticle) -> bool {
3070 if particle.min_occurs == 0 {
3071 return true;
3072 }
3073
3074 match &particle.term {
3075 NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_) => false,
3076 NormalizedParticleTerm::Group(group) => match group.compositor {
3077 Compositor::Sequence | Compositor::All => {
3078 group.particles.iter().all(particle_is_emptiable)
3079 }
3080 Compositor::Choice => group.particles.iter().any(particle_is_emptiable),
3081 },
3082 }
3083}
3084
3085fn wildcard_restricts(derived: &NormalizedWildcard, base: &NormalizedWildcard) -> bool {
3086 is_wildcard_ns_subset(
3087 &derived.wildcard,
3088 derived.target_namespace,
3089 &base.wildcard,
3090 base.target_namespace,
3091 ) && process_contents_strictness(derived.wildcard.process_contents)
3092 >= process_contents_strictness(base.wildcard.process_contents)
3093}
3094
3095fn wildcard_subset_of_union(
3108 derived: &NormalizedWildcard,
3109 bases: &[&NormalizedWildcard],
3110) -> Option<Vec<usize>> {
3111 use crate::parser::frames::NotQNameItem;
3112
3113 let derived_target = derived.target_namespace;
3114
3115 let mut explicit_namespaces: Vec<Option<NameId>> = Vec::new();
3117 let push_ns = |ns: Option<NameId>, out: &mut Vec<Option<NameId>>| {
3118 if !out.contains(&ns) {
3119 out.push(ns);
3120 }
3121 };
3122
3123 let collect_namespaces = |wc: &NormalizedWildcard, out: &mut Vec<Option<NameId>>| {
3124 let target = wc.target_namespace;
3125 match &wc.wildcard.namespace {
3126 WildcardNamespace::TargetNamespace => {
3127 if !out.contains(&target) {
3128 out.push(target);
3129 }
3130 }
3131 WildcardNamespace::Local => {
3132 if !out.contains(&None) {
3133 out.push(None);
3134 }
3135 }
3136 WildcardNamespace::List(tokens) => {
3137 for t in tokens {
3138 let ns = t.resolve(target);
3139 if !out.contains(&ns) {
3140 out.push(ns);
3141 }
3142 }
3143 }
3144 _ => {}
3145 }
3146 for t in &wc.wildcard.not_namespace {
3147 let ns = t.resolve(target);
3148 if !out.contains(&ns) {
3149 out.push(ns);
3150 }
3151 }
3152 for item in &wc.wildcard.not_qname {
3153 if let NotQNameItem::QName { namespace, .. } = item {
3154 if !out.contains(namespace) {
3155 out.push(*namespace);
3156 }
3157 }
3158 }
3159 };
3160
3161 collect_namespaces(derived, &mut explicit_namespaces);
3162 for base in bases {
3163 collect_namespaces(base, &mut explicit_namespaces);
3164 }
3165 push_ns(derived_target, &mut explicit_namespaces);
3166 for base in bases {
3167 push_ns(base.target_namespace, &mut explicit_namespaces);
3168 }
3169 push_ns(None, &mut explicit_namespaces);
3170
3171 let mut spanned: Vec<usize> = Vec::new();
3172
3173 let check_namespace = |ns: Option<NameId>,
3174 is_explicit_ns: bool,
3175 bases: &[&NormalizedWildcard],
3176 spanned: &mut Vec<usize>|
3177 -> bool {
3178 if !wildcard_admits_ns(derived, ns) {
3179 return true;
3180 }
3181 let admitting: Vec<usize> = (0..bases.len())
3183 .filter(|&i| wildcard_admits_ns(bases[i], ns))
3184 .collect();
3185 if admitting.is_empty() {
3186 return false;
3187 }
3188 let mut name_witnesses: Vec<NameId> = Vec::new();
3190 let push_name = |n: NameId, out: &mut Vec<NameId>| {
3191 if !out.contains(&n) {
3192 out.push(n);
3193 }
3194 };
3195 for item in &derived.wildcard.not_qname {
3196 if let NotQNameItem::QName {
3197 namespace,
3198 local_name,
3199 } = item
3200 {
3201 if *namespace == ns {
3202 push_name(*local_name, &mut name_witnesses);
3203 }
3204 }
3205 }
3206 for &i in &admitting {
3207 for item in &bases[i].wildcard.not_qname {
3208 if let NotQNameItem::QName {
3209 namespace,
3210 local_name,
3211 } = item
3212 {
3213 if *namespace == ns {
3214 push_name(*local_name, &mut name_witnesses);
3215 }
3216 }
3217 }
3218 }
3219 for name in &name_witnesses {
3220 if !wildcard_admits_qname(derived, ns, *name) {
3221 continue;
3222 }
3223 let any_admits = admitting
3224 .iter()
3225 .any(|&i| wildcard_admits_qname(bases[i], ns, *name));
3226 if !any_admits {
3227 return false;
3228 }
3229 }
3230 let derived_admits_other = wildcard_admits_qname_symbolic_other(derived, ns);
3235 if derived_admits_other {
3236 let any_admits_other = admitting
3237 .iter()
3238 .any(|&i| wildcard_admits_qname_symbolic_other(bases[i], ns));
3239 if !any_admits_other {
3240 return false;
3241 }
3242 }
3243 if is_explicit_ns {
3248 for &i in &admitting {
3249 if !spanned.contains(&i) {
3250 spanned.push(i);
3251 }
3252 }
3253 }
3254 true
3255 };
3256
3257 for ns in &explicit_namespaces {
3259 if !check_namespace(*ns, true, bases, &mut spanned) {
3260 return None;
3261 }
3262 }
3263
3264 let fresh_ns = Some(NameId(u32::MAX));
3267 let derived_admits_fresh =
3268 wildcard_admits_ns(derived, fresh_ns) || wildcard_admits_ns(derived, None);
3269 if derived_admits_fresh {
3270 let mut fresh_bases: Vec<usize> = (0..bases.len())
3273 .filter(|&i| wildcard_admits_ns(bases[i], fresh_ns))
3274 .collect();
3275 if wildcard_admits_ns(derived, fresh_ns) && fresh_bases.is_empty() {
3276 return None;
3277 }
3278 for &i in &fresh_bases {
3279 if !spanned.contains(&i) {
3280 spanned.push(i);
3281 }
3282 }
3283 fresh_bases.clear();
3284 }
3285
3286 if spanned.is_empty() {
3287 return Some(spanned);
3289 }
3290 Some(spanned)
3291}
3292
3293fn wildcard_admits_ns(wc: &NormalizedWildcard, ns: Option<NameId>) -> bool {
3296 if !wildcard_namespace_matches(&wc.wildcard.namespace, ns, wc.target_namespace) {
3297 return false;
3298 }
3299 !wc.wildcard
3300 .not_namespace
3301 .iter()
3302 .any(|t| t.resolve(wc.target_namespace) == ns)
3303}
3304
3305fn wildcard_admits_qname(wc: &NormalizedWildcard, ns: Option<NameId>, name: NameId) -> bool {
3310 use crate::parser::frames::NotQNameItem;
3311 if !wildcard_admits_ns(wc, ns) {
3312 return false;
3313 }
3314 !wc.wildcard.not_qname.iter().any(|item| match item {
3315 NotQNameItem::QName {
3316 namespace,
3317 local_name,
3318 } => *namespace == ns && *local_name == name,
3319 NotQNameItem::Defined | NotQNameItem::DefinedSibling => true,
3320 })
3321}
3322
3323fn wildcard_admits_qname_symbolic_other(wc: &NormalizedWildcard, ns: Option<NameId>) -> bool {
3328 use crate::parser::frames::NotQNameItem;
3329 if !wildcard_admits_ns(wc, ns) {
3330 return false;
3331 }
3332 !wc.wildcard
3333 .not_qname
3334 .iter()
3335 .any(|item| matches!(item, NotQNameItem::Defined | NotQNameItem::DefinedSibling))
3336}
3337
3338fn wildcard_allows_element(element: &NormalizedElement, wildcard: &NormalizedWildcard) -> bool {
3339 if !wildcard_namespace_matches(
3340 &wildcard.wildcard.namespace,
3341 element.namespace,
3342 wildcard.target_namespace,
3343 ) {
3344 return false;
3345 }
3346
3347 let excluded_namespace = wildcard
3348 .wildcard
3349 .not_namespace
3350 .iter()
3351 .map(|token| token.resolve(wildcard.target_namespace))
3352 .any(|namespace| namespace == element.namespace);
3353 if excluded_namespace {
3354 return false;
3355 }
3356
3357 !wildcard_not_qname_excludes(
3358 &wildcard.wildcard.not_qname,
3359 element.namespace,
3360 element.name,
3361 )
3362}
3363
3364fn wildcard_namespace_matches(
3365 namespace: &WildcardNamespace,
3366 element_namespace: Option<NameId>,
3367 target_namespace: Option<NameId>,
3368) -> bool {
3369 match namespace {
3370 WildcardNamespace::Any => true,
3371 WildcardNamespace::Other => {
3372 !other_exclusion_set(target_namespace).contains(&element_namespace)
3373 }
3374 WildcardNamespace::TargetNamespace => element_namespace == target_namespace,
3375 WildcardNamespace::Local => element_namespace.is_none(),
3376 WildcardNamespace::List(tokens) => tokens
3377 .iter()
3378 .map(|token| token.resolve(target_namespace))
3379 .any(|resolved| resolved == element_namespace),
3380 }
3381}
3382
3383fn wildcard_not_qname_excludes(
3384 not_qname: &[crate::parser::frames::NotQNameItem],
3385 namespace: Option<NameId>,
3386 local_name: NameId,
3387) -> bool {
3388 not_qname.iter().any(|item| match item {
3389 crate::parser::frames::NotQNameItem::QName {
3390 namespace: excluded_ns,
3391 local_name: excluded_name,
3392 } => *excluded_ns == namespace && *excluded_name == local_name,
3393 crate::parser::frames::NotQNameItem::Defined => true,
3394 crate::parser::frames::NotQNameItem::DefinedSibling => false,
3395 })
3396}
3397
3398#[cfg(feature = "xsd11")]
3408fn effective_open_content(oc: Option<&OpenContentResult>) -> Option<&OpenContentResult> {
3409 oc.filter(|o| o.mode != OpenContentMode::None)
3410}
3411
3412fn process_contents_strictness(pc: ProcessContents) -> u8 {
3414 match pc {
3415 ProcessContents::Strict => 2,
3416 ProcessContents::Lax => 1,
3417 ProcessContents::Skip => 0,
3418 }
3419}
3420
3421fn other_exclusion_set(target_ns: Option<NameId>) -> Vec<Option<NameId>> {
3426 match target_ns {
3427 Some(ns) => vec![Some(ns), None],
3428 None => vec![None],
3429 }
3430}
3431
3432fn resolve_ns_set(
3439 wns: &WildcardNamespace,
3440 target_ns: Option<NameId>,
3441) -> Option<Vec<Option<NameId>>> {
3442 match wns {
3443 WildcardNamespace::Any | WildcardNamespace::Other => None,
3444 WildcardNamespace::TargetNamespace => Some(vec![target_ns]),
3445 WildcardNamespace::Local => Some(vec![None]),
3446 WildcardNamespace::List(tokens) => {
3447 Some(tokens.iter().map(|t| t.resolve(target_ns)).collect())
3448 }
3449 }
3450}
3451
3452fn is_namespace_subset(
3466 derived: &WildcardNamespace,
3467 derived_target_ns: Option<NameId>,
3468 base: &WildcardNamespace,
3469 base_target_ns: Option<NameId>,
3470) -> bool {
3471 match base {
3472 WildcardNamespace::Any => true,
3473
3474 WildcardNamespace::Other => {
3475 let base_excluded = other_exclusion_set(base_target_ns);
3479
3480 match derived {
3481 WildcardNamespace::Any => false,
3482
3483 WildcardNamespace::Other => {
3484 let derived_excluded = other_exclusion_set(derived_target_ns);
3488 base_excluded.iter().all(|ns| derived_excluded.contains(ns))
3489 }
3490
3491 _ => {
3492 match resolve_ns_set(derived, derived_target_ns) {
3495 Some(resolved) => resolved.iter().all(|ns| !base_excluded.contains(ns)),
3496 None => false,
3497 }
3498 }
3499 }
3500 }
3501
3502 WildcardNamespace::TargetNamespace
3503 | WildcardNamespace::Local
3504 | WildcardNamespace::List(_) => {
3505 let Some(base_set) = resolve_ns_set(base, base_target_ns) else {
3508 return false;
3509 };
3510 match derived {
3511 WildcardNamespace::Any | WildcardNamespace::Other => false,
3512 _ => {
3513 let Some(derived_set) = resolve_ns_set(derived, derived_target_ns) else {
3514 return false;
3515 };
3516 derived_set.iter().all(|ns| base_set.contains(ns))
3517 }
3518 }
3519 }
3520 }
3521}
3522
3523fn is_wildcard_ns_subset(
3533 derived: &WildcardResult,
3534 derived_target_ns: Option<NameId>,
3535 base: &WildcardResult,
3536 base_target_ns: Option<NameId>,
3537) -> bool {
3538 if !is_namespace_subset(
3540 &derived.namespace,
3541 derived_target_ns,
3542 &base.namespace,
3543 base_target_ns,
3544 ) {
3545 return false;
3546 }
3547
3548 for base_excl in &base.not_namespace {
3554 let base_ns = base_excl.resolve(base_target_ns);
3555 let derived_allows =
3556 wildcard_namespace_matches(&derived.namespace, base_ns, derived_target_ns)
3557 && !derived
3558 .not_namespace
3559 .iter()
3560 .any(|d| d.resolve(derived_target_ns) == base_ns);
3561 if derived_allows {
3562 return false;
3563 }
3564 }
3565
3566 for item in &base.not_qname {
3571 match item {
3572 crate::parser::frames::NotQNameItem::QName { namespace, .. } => {
3573 let derived_admits_ns =
3574 wildcard_namespace_matches(&derived.namespace, *namespace, derived_target_ns)
3575 && !derived
3576 .not_namespace
3577 .iter()
3578 .any(|t| t.resolve(derived_target_ns) == *namespace);
3579 if derived_admits_ns && !derived.not_qname.contains(item) {
3580 return false;
3581 }
3582 }
3583 crate::parser::frames::NotQNameItem::Defined
3584 | crate::parser::frames::NotQNameItem::DefinedSibling => {
3585 if !derived.not_qname.contains(item) {
3586 return false;
3587 }
3588 }
3589 }
3590 }
3591
3592 true
3593}
3594
3595#[cfg(feature = "xsd11")]
3608fn validate_open_content_extension(
3609 schema_set: &SchemaSet,
3610 derived_key: ComplexTypeKey,
3611 derived: &crate::arenas::ComplexTypeDefData,
3612 base_key: ComplexTypeKey,
3613 base: &crate::arenas::ComplexTypeDefData,
3614) -> SchemaResult<()> {
3615 let bot = compute_effective_open_content(schema_set, base_key);
3616 let eot = compute_effective_open_content(schema_set, derived_key);
3617
3618 let Some(bot) = bot else {
3620 return Ok(());
3621 };
3622
3623 let (location, type_name) = type_error_context(schema_set, derived);
3624 let base_name = format_type_name(schema_set, base.name, base.target_namespace);
3625
3626 let Some(eot) = eot else {
3633 return Err(SchemaError::structural(
3634 "cos-ct-extends",
3635 format!(
3636 "Complex type '{}' extends '{}' which has open content, \
3637 but derived type has no open content",
3638 type_name, base_name
3639 ),
3640 location,
3641 ));
3642 };
3643
3644 let mode_ok = eot.mode == OpenContentMode::Interleave
3646 || (bot.mode == OpenContentMode::Suffix && eot.mode == OpenContentMode::Suffix);
3647 if !mode_ok {
3648 return Err(SchemaError::structural(
3649 "cos-ct-extends",
3650 format!(
3651 "Complex type '{}' uses suffix open content mode but base type '{}' \
3652 uses interleave mode — suffix cannot extend interleave",
3653 type_name, base_name
3654 ),
3655 location,
3656 ));
3657 }
3658
3659 if let (Some(bot_wc), Some(eot_wc)) = (bot.wildcard.as_ref(), eot.wildcard.as_ref()) {
3661 if !is_wildcard_ns_subset(
3662 bot_wc,
3663 base.target_namespace,
3664 eot_wc,
3665 derived.target_namespace,
3666 ) {
3667 return Err(SchemaError::structural(
3668 "cos-ct-extends",
3669 format!(
3670 "Open content wildcard of '{}' is not a valid extension \
3671 of base type '{}' wildcard",
3672 type_name, base_name
3673 ),
3674 location,
3675 ));
3676 }
3677 }
3678
3679 Ok(())
3680}
3681
3682#[cfg(feature = "xsd11")]
3690#[derive(Debug, Clone)]
3691struct EffectiveOpenContent {
3692 mode: OpenContentMode,
3693 wildcard: Option<WildcardResult>,
3694 target_namespace: Option<NameId>,
3695}
3696
3697#[cfg(feature = "xsd11")]
3702fn compute_effective_open_content(
3703 schema_set: &SchemaSet,
3704 key: ComplexTypeKey,
3705) -> Option<EffectiveOpenContent> {
3706 compute_effective_open_content_bounded(schema_set, key, 0)
3707}
3708
3709#[cfg(feature = "xsd11")]
3710fn compute_effective_open_content_bounded(
3711 schema_set: &SchemaSet,
3712 key: ComplexTypeKey,
3713 depth: u32,
3714) -> Option<EffectiveOpenContent> {
3715 if depth > 100 {
3718 return None;
3719 }
3720 let type_data = schema_set.arenas.complex_types.get(key)?;
3721 let target_ns = type_data.target_namespace;
3722
3723 let own_oc: Option<EffectiveOpenContent> =
3729 type_data
3730 .open_content
3731 .as_ref()
3732 .map(|oc| EffectiveOpenContent {
3733 mode: oc.mode,
3734 wildcard: oc.wildcard.clone(),
3735 target_namespace: target_ns,
3736 });
3737
3738 let wildcard_element: Option<EffectiveOpenContent> = if own_oc.is_some() {
3739 own_oc
3741 } else if let Some(default) = type_data
3742 .source
3743 .as_ref()
3744 .and_then(|s| schema_set.documents.get(s.defaults_doc() as usize))
3745 .and_then(|d| d.default_open_content.as_ref())
3746 {
3747 if default.applies_to_empty || !explicit_content_is_empty(schema_set, type_data, 0) {
3750 default_open_content_to_effective(default, target_ns)
3751 } else {
3752 None
3753 }
3754 } else {
3755 None };
3757
3758 let base_oc: Option<EffectiveOpenContent> = if matches!(
3762 type_data.derivation_method,
3763 Some(DerivationMethod::Extension)
3764 ) {
3765 match type_data.resolved_base_type {
3766 Some(TypeKey::Complex(base_key)) => {
3767 compute_effective_open_content_bounded(schema_set, base_key, depth + 1)
3768 }
3769 _ => None,
3770 }
3771 } else {
3772 None
3773 };
3774
3775 let Some(we) = wildcard_element else {
3777 return base_oc;
3778 };
3779 if we.mode == OpenContentMode::None {
3780 return base_oc;
3781 }
3782
3783 let wildcard = match (base_oc.as_ref(), we.wildcard.as_ref()) {
3785 (Some(b), Some(w)) => match &b.wildcard {
3786 Some(bw) => Some(wildcard_result_union(bw, b.target_namespace, w, target_ns)),
3787 None => Some(w.clone()),
3788 },
3789 (None, Some(w)) => Some(w.clone()),
3790 (Some(b), None) => b.wildcard.clone(),
3791 (None, None) => None,
3792 };
3793
3794 Some(EffectiveOpenContent {
3795 mode: we.mode,
3796 wildcard,
3797 target_namespace: target_ns,
3798 })
3799}
3800
3801#[cfg(feature = "xsd11")]
3808fn explicit_content_is_empty(
3809 schema_set: &SchemaSet,
3810 type_data: &crate::arenas::ComplexTypeDefData,
3811 depth: u32,
3812) -> bool {
3813 if depth > 100 {
3814 return true;
3815 }
3816 if !type_data.content.explicit_content_type_is_empty() {
3819 return false;
3820 }
3821 if matches!(
3822 type_data.derivation_method,
3823 Some(DerivationMethod::Extension)
3824 ) {
3825 if let Some(TypeKey::Complex(base_key)) = type_data.resolved_base_type {
3826 if let Some(base_data) = schema_set.arenas.complex_types.get(base_key) {
3827 return explicit_content_is_empty(schema_set, base_data, depth + 1);
3828 }
3829 }
3830 }
3831 true
3832}
3833
3834#[cfg(feature = "xsd11")]
3838fn default_open_content_to_effective(
3839 default: &crate::schema::model::DefaultOpenContent,
3840 target_ns: Option<NameId>,
3841) -> Option<EffectiveOpenContent> {
3842 let mode = match default.mode {
3843 crate::schema::model::OpenContentMode::None => OpenContentMode::None,
3844 crate::schema::model::OpenContentMode::Interleave => OpenContentMode::Interleave,
3845 crate::schema::model::OpenContentMode::Suffix => OpenContentMode::Suffix,
3846 };
3847 if mode == OpenContentMode::None {
3848 return None;
3849 }
3850 let wildcard = default.wildcard.as_ref().map(element_wildcard_to_result);
3851 Some(EffectiveOpenContent {
3852 mode,
3853 wildcard,
3854 target_namespace: target_ns,
3855 })
3856}
3857
3858#[cfg(feature = "xsd11")]
3861fn element_wildcard_to_result(ew: &crate::schema::wildcard::ElementWildcard) -> WildcardResult {
3862 use crate::parser::frames::NotQNameItem;
3863 use crate::schema::wildcard::{NamespaceConstraint, QNameDisallowed};
3864
3865 let (namespace, not_namespace) = match &ew.namespace_constraint {
3866 NamespaceConstraint::Any => (WildcardNamespace::Any, Vec::new()),
3867 NamespaceConstraint::Other => (WildcardNamespace::Other, Vec::new()),
3868 NamespaceConstraint::Enumeration(nss) => (
3869 WildcardNamespace::List(nss.iter().copied().map(ns_token).collect()),
3870 Vec::new(),
3871 ),
3872 NamespaceConstraint::Not(nss) => (
3873 WildcardNamespace::Any,
3874 nss.iter().copied().map(ns_token).collect(),
3875 ),
3876 };
3877
3878 let process_contents = match ew.process_contents {
3879 crate::schema::wildcard::ProcessContents::Strict => ProcessContents::Strict,
3880 crate::schema::wildcard::ProcessContents::Lax => ProcessContents::Lax,
3881 crate::schema::wildcard::ProcessContents::Skip => ProcessContents::Skip,
3882 };
3883
3884 let not_qname = ew
3885 .not_qnames
3886 .iter()
3887 .map(|q| match q {
3888 QNameDisallowed::QName {
3889 namespace,
3890 local_name,
3891 } => NotQNameItem::QName {
3892 namespace: *namespace,
3893 local_name: *local_name,
3894 },
3895 QNameDisallowed::Defined => NotQNameItem::Defined,
3896 QNameDisallowed::DefinedSibling => NotQNameItem::DefinedSibling,
3897 })
3898 .collect();
3899
3900 WildcardResult {
3901 namespace,
3902 process_contents,
3903 not_namespace,
3904 not_qname,
3905 id: ew.id.clone(),
3906 annotation: None,
3907 source: ew.source.clone(),
3908 }
3909}
3910
3911#[cfg(feature = "xsd11")]
3914#[derive(Debug, Clone)]
3915enum NsForm {
3916 Pos(Vec<Option<NameId>>),
3917 Neg(Vec<Option<NameId>>),
3918}
3919
3920#[cfg(feature = "xsd11")]
3924fn wildcard_to_ns_form(
3925 ns: &WildcardNamespace,
3926 not_namespace: &[crate::parser::frames::NamespaceToken],
3927 target_ns: Option<NameId>,
3928) -> NsForm {
3929 let resolved_not: Vec<Option<NameId>> =
3930 not_namespace.iter().map(|t| t.resolve(target_ns)).collect();
3931 match ns {
3932 WildcardNamespace::Any => NsForm::Neg(resolved_not),
3933 WildcardNamespace::Other => {
3934 let mut excl = other_exclusion_set(target_ns);
3935 for r in resolved_not {
3936 if !excl.contains(&r) {
3937 excl.push(r);
3938 }
3939 }
3940 NsForm::Neg(excl)
3941 }
3942 WildcardNamespace::TargetNamespace => {
3943 let base = target_ns;
3944 if resolved_not.contains(&base) {
3945 NsForm::Pos(Vec::new())
3946 } else {
3947 NsForm::Pos(vec![base])
3948 }
3949 }
3950 WildcardNamespace::Local => {
3951 if resolved_not.contains(&None) {
3952 NsForm::Pos(Vec::new())
3953 } else {
3954 NsForm::Pos(vec![None])
3955 }
3956 }
3957 WildcardNamespace::List(tokens) => {
3958 let allowed: Vec<Option<NameId>> = tokens
3959 .iter()
3960 .map(|t| t.resolve(target_ns))
3961 .filter(|r| !resolved_not.contains(r))
3962 .collect();
3963 NsForm::Pos(allowed)
3964 }
3965 }
3966}
3967
3968#[cfg(feature = "xsd11")]
3973fn ns_form_to_wildcard(
3974 form: NsForm,
3975) -> (
3976 WildcardNamespace,
3977 Vec<crate::parser::frames::NamespaceToken>,
3978) {
3979 use crate::parser::frames::NamespaceToken;
3980 match form {
3981 NsForm::Pos(list) => {
3982 let tokens: Vec<NamespaceToken> = list.into_iter().map(ns_token).collect();
3983 (WildcardNamespace::List(tokens), Vec::new())
3984 }
3985 NsForm::Neg(list) if list.is_empty() => (WildcardNamespace::Any, Vec::new()),
3986 NsForm::Neg(list) => {
3987 let tokens: Vec<NamespaceToken> = list.into_iter().map(ns_token).collect();
3988 (WildcardNamespace::Any, tokens)
3989 }
3990 }
3991}
3992
3993#[cfg(feature = "xsd11")]
3997fn ns_token(ns: Option<NameId>) -> crate::parser::frames::NamespaceToken {
3998 match ns {
3999 Some(id) => crate::parser::frames::NamespaceToken::Uri(id),
4000 None => crate::parser::frames::NamespaceToken::Local,
4001 }
4002}
4003
4004#[cfg(feature = "xsd11")]
4012pub(crate) fn wildcard_result_union(
4013 a: &WildcardResult,
4014 a_tns: Option<NameId>,
4015 b: &WildcardResult,
4016 b_tns: Option<NameId>,
4017) -> WildcardResult {
4018 let form_a = wildcard_to_ns_form(&a.namespace, &a.not_namespace, a_tns);
4019 let form_b = wildcard_to_ns_form(&b.namespace, &b.not_namespace, b_tns);
4020
4021 let merged = match (form_a, form_b) {
4022 (NsForm::Pos(mut pa), NsForm::Pos(pb)) => {
4023 for item in pb {
4024 if !pa.contains(&item) {
4025 pa.push(item);
4026 }
4027 }
4028 NsForm::Pos(pa)
4029 }
4030 (NsForm::Pos(pa), NsForm::Neg(nb)) | (NsForm::Neg(nb), NsForm::Pos(pa)) => {
4031 NsForm::Neg(nb.into_iter().filter(|ns| !pa.contains(ns)).collect())
4032 }
4033 (NsForm::Neg(na), NsForm::Neg(nb)) => {
4034 NsForm::Neg(na.into_iter().filter(|ns| nb.contains(ns)).collect())
4035 }
4036 };
4037
4038 let (namespace, not_namespace) = ns_form_to_wildcard(merged);
4039
4040 let not_qname: Vec<crate::parser::frames::NotQNameItem> = a
4041 .not_qname
4042 .iter()
4043 .filter(|item| b.not_qname.contains(item))
4044 .cloned()
4045 .collect();
4046
4047 WildcardResult {
4048 namespace,
4049 process_contents: a.process_contents,
4050 not_namespace,
4051 not_qname,
4052 id: None,
4053 annotation: None,
4054 source: a.source.clone(),
4055 }
4056}
4057
4058#[cfg(feature = "xsd11")]
4067fn derived_particle_is_empty(
4068 schema_set: &SchemaSet,
4069 derived: &crate::arenas::ComplexTypeDefData,
4070) -> bool {
4071 let Some(particle) = complex_content_particle(&derived.content) else {
4072 return true;
4073 };
4074 let Ok(normalized) = normalize_type_particle(schema_set, derived, particle) else {
4075 return false;
4076 };
4077 is_effectively_empty(&normalized)
4078}
4079
4080#[cfg(feature = "xsd11")]
4090fn base_content_single_wildcard<'a>(
4091 schema_set: &'a SchemaSet,
4092 base: &'a crate::arenas::ComplexTypeDefData,
4093) -> Option<NormalizedWildcard> {
4094 let (particle_owner, particle) = effective_base_content_particle(schema_set, base);
4095 let particle = particle?;
4096 let normalized = normalize_type_particle(schema_set, particle_owner, particle).ok()?;
4097 match &normalized.term {
4098 NormalizedParticleTerm::Wildcard(wc) => Some((**wc).clone()),
4099 NormalizedParticleTerm::Group(group) => {
4100 if group.particles.len() == 1 {
4101 if let NormalizedParticleTerm::Wildcard(wc) = &group.particles[0].term {
4102 return Some((**wc).clone());
4103 }
4104 }
4105 None
4106 }
4107 _ => None,
4108 }
4109}
4110
4111#[cfg(feature = "xsd11")]
4125fn validate_all_group_restriction_edc(
4126 schema_set: &SchemaSet,
4127 derived: &crate::arenas::ComplexTypeDefData,
4128 base: &crate::arenas::ComplexTypeDefData,
4129) -> SchemaResult<()> {
4130 use crate::parser::frames::ProcessContents;
4131
4132 let derived_particle = complex_content_particle(&derived.content);
4133 let (effective_base, base_particle) = effective_base_content_particle(schema_set, base);
4134
4135 let (Some(derived_p), Some(base_p)) = (derived_particle, base_particle) else {
4136 return Ok(());
4137 };
4138
4139 let derived_norm = match normalize_type_particle(schema_set, derived, derived_p) {
4140 Ok(n) => n,
4141 Err(_) => return Ok(()),
4142 };
4143 let base_norm = match normalize_type_particle(schema_set, effective_base, base_p) {
4144 Ok(n) => n,
4145 Err(_) => return Ok(()),
4146 };
4147
4148 if !is_top_all_group(&derived_norm) || !is_top_all_group(&base_norm) {
4150 return Ok(());
4151 }
4152
4153 let mut derived_local_qnames: Vec<(Option<NameId>, NameId)> = Vec::new();
4156 let mut derived_wildcards: Vec<&NormalizedWildcard> = Vec::new();
4157 if let NormalizedParticleTerm::Group(group) = &derived_norm.term {
4158 for p in &group.particles {
4159 match &p.term {
4160 NormalizedParticleTerm::Element(elem) => {
4161 derived_local_qnames.push((elem.namespace, elem.name));
4162 }
4163 NormalizedParticleTerm::Wildcard(wc) => {
4164 derived_wildcards.push(wc.as_ref());
4165 }
4166 NormalizedParticleTerm::Group(_) => {}
4167 }
4168 }
4169 }
4170
4171 if derived_wildcards.is_empty() {
4172 return Ok(());
4173 }
4174
4175 let base_locals: Vec<(Option<NameId>, NameId, TypeKey)> =
4177 if let NormalizedParticleTerm::Group(group) = &base_norm.term {
4178 group
4179 .particles
4180 .iter()
4181 .filter_map(|p| match &p.term {
4182 NormalizedParticleTerm::Element(elem) => {
4183 Some((elem.namespace, elem.name, elem.type_key))
4184 }
4185 _ => None,
4186 })
4187 .collect()
4188 } else {
4189 Vec::new()
4190 };
4191
4192 let (location, type_name) = type_error_context(schema_set, derived);
4193 let base_name = format_type_name(schema_set, base.name, base.target_namespace);
4194
4195 for (l_ns, l_name, l_type) in &base_locals {
4196 if derived_local_qnames.contains(&(*l_ns, *l_name)) {
4198 continue;
4199 }
4200
4201 for wc in &derived_wildcards {
4202 if !wildcard_admits_qname(wc, *l_ns, *l_name) {
4203 continue;
4204 }
4205
4206 let pc = wc.wildcard.process_contents;
4207 if matches!(pc, ProcessContents::Skip) {
4208 continue;
4209 }
4210
4211 let global_key = schema_set.lookup_element(*l_ns, *l_name);
4212 let governing_type = global_key
4213 .and_then(|k| schema_set.arenas.elements.get(k))
4214 .and_then(|d| d.resolved_type);
4215
4216 match (pc, governing_type) {
4217 (ProcessContents::Strict, None) => {
4218 continue;
4221 }
4222 (ProcessContents::Lax, None) => {
4223 }
4227 (_, Some(gov_type)) => {
4228 if schema_set.is_type_derived_from(gov_type, *l_type, DerivationSet::empty()) {
4229 continue;
4230 }
4231 }
4232 _ => continue,
4233 }
4234
4235 return Err(SchemaError::structural(
4236 "cos-element-consistent",
4237 format!(
4238 "Complex type '{}' restricts '{}' (xs:all) by removing local element \
4239 while keeping a wildcard that admits the same QName; the wildcard's \
4240 governing type is not validly substitutable for the base local element's \
4241 type (cvc-complex-type rule 5 / tighter EDC for xs:all restriction)",
4242 type_name, base_name,
4243 ),
4244 location.clone(),
4245 ));
4246 }
4247 }
4248
4249 Ok(())
4250}
4251
4252#[cfg(feature = "xsd11")]
4255fn is_top_all_group(particle: &NormalizedParticle) -> bool {
4256 matches!(
4257 &particle.term,
4258 NormalizedParticleTerm::Group(group) if group.compositor == Compositor::All
4259 )
4260}
4261
4262#[cfg(feature = "xsd11")]
4275fn validate_open_content_restriction(
4276 schema_set: &SchemaSet,
4277 derived: &crate::arenas::ComplexTypeDefData,
4278 base: &crate::arenas::ComplexTypeDefData,
4279) -> SchemaResult<()> {
4280 let base_oc = effective_open_content(base.open_content.as_ref());
4281 let derived_oc = effective_open_content(derived.open_content.as_ref());
4282
4283 if base_oc.is_none() && derived_oc.is_some() {
4287 if derived_particle_is_empty(schema_set, derived) {
4288 let derived_oc_wc = derived_oc.as_ref().and_then(|o| o.wildcard.as_ref());
4289 if let Some(d_wc) = derived_oc_wc {
4290 if let Some(base_wc) = base_content_single_wildcard(schema_set, base) {
4291 if is_wildcard_ns_subset(
4292 d_wc,
4293 derived.target_namespace,
4294 &base_wc.wildcard,
4295 base_wc.target_namespace,
4296 ) {
4297 return Ok(());
4298 }
4299 }
4300 }
4301 }
4302 let (location, type_name) = type_error_context(schema_set, derived);
4303 let base_name = format_type_name(schema_set, base.name, base.target_namespace);
4304 return Err(SchemaError::structural(
4305 "derivation-ok-restriction",
4306 format!(
4307 "Complex type '{}' restricts '{}' which has no open content, \
4308 but adds open content — not allowed",
4309 type_name, base_name
4310 ),
4311 location,
4312 ));
4313 }
4314
4315 let (Some(base_oc), Some(derived_oc)) = (base_oc, derived_oc) else {
4317 return Ok(());
4318 };
4319
4320 let (location, type_name) = type_error_context(schema_set, derived);
4321 let base_name = format_type_name(schema_set, base.name, base.target_namespace);
4322
4323 if base_oc.mode == OpenContentMode::Suffix
4327 && derived_oc.mode == OpenContentMode::Interleave
4328 && !derived_particle_is_empty(schema_set, derived)
4329 {
4330 return Err(SchemaError::structural(
4331 "derivation-ok-restriction",
4332 format!(
4333 "Complex type '{}' uses interleave open content mode but base type '{}' \
4334 uses suffix mode — interleave cannot restrict suffix",
4335 type_name, base_name
4336 ),
4337 location,
4338 ));
4339 }
4340
4341 if let (Some(base_wc), Some(derived_wc)) =
4343 (base_oc.wildcard.as_ref(), derived_oc.wildcard.as_ref())
4344 {
4345 if !is_wildcard_ns_subset(
4346 derived_wc,
4347 derived.target_namespace,
4348 base_wc,
4349 base.target_namespace,
4350 ) {
4351 return Err(SchemaError::structural(
4352 "derivation-ok-restriction",
4353 format!(
4354 "Open content wildcard of '{}' is not a valid restriction \
4355 of base type '{}' wildcard",
4356 type_name, base_name
4357 ),
4358 location,
4359 ));
4360 }
4361
4362 if process_contents_strictness(derived_wc.process_contents)
4364 < process_contents_strictness(base_wc.process_contents)
4365 {
4366 return Err(SchemaError::structural(
4367 "derivation-ok-restriction",
4368 format!(
4369 "Open content wildcard of '{}' has weaker processContents \
4370 than base type '{}' wildcard",
4371 type_name, base_name
4372 ),
4373 location,
4374 ));
4375 }
4376 }
4377
4378 Ok(())
4379}
4380
4381fn is_notation_or_qname_base(schema_set: &SchemaSet, key: TypeKey) -> bool {
4383 let TypeKey::Simple(sk) = key else {
4384 return false;
4385 };
4386 let bt = schema_set.builtin_types();
4387 schema_set.derives_from(sk, bt.notation) || schema_set.derives_from(sk, bt.qname)
4388}
4389
4390fn base_without_enumeration(schema_set: &SchemaSet, key: TypeKey) -> TypeKey {
4399 let mut current = key;
4400 for _ in 0..100 {
4401 if let TypeKey::Simple(sk) = current {
4402 if let Some(st_data) = schema_set.arenas.simple_types.get(sk) {
4403 if st_data.facets.enumeration.is_none() {
4404 return current;
4405 }
4406 if let Some(base) = st_data.resolved_base_type {
4407 current = base;
4408 continue;
4409 }
4410 }
4411 }
4412 break;
4413 }
4414 current
4415}
4416
4417fn validate_facet_values_against_base_type(
4429 schema_set: &SchemaSet,
4430 type_def: &crate::arenas::SimpleTypeDefData,
4431 base_key: TypeKey,
4432) -> SchemaResult<()> {
4433 let (location, type_name) = type_error_context(schema_set, type_def);
4434
4435 if is_notation_or_qname_base(schema_set, base_key) {
4441 if let Some(ref enum_facet) = type_def.facets.enumeration {
4442 for value in &enum_facet.values {
4443 if value.trim().is_empty() {
4444 return Err(SchemaError::structural(
4445 "enumeration-valid-restriction",
4446 format!(
4447 "Enumeration value '' in type '{}' is not in the value space of the base type",
4448 type_name
4449 ),
4450 location.clone(),
4451 ));
4452 }
4453 }
4454 }
4455 return Ok(());
4456 }
4457
4458 if let Some(ref enum_facet) = type_def.facets.enumeration {
4462 let enum_base = base_without_enumeration(schema_set, base_key);
4463 for value in &enum_facet.values {
4464 if crate::validation::simple::validate_simple_type(value, enum_base, schema_set)
4465 .is_err()
4466 {
4467 return Err(SchemaError::structural(
4468 "enumeration-valid-restriction",
4469 format!(
4470 "Enumeration value '{}' in type '{}' is not in the value space of the base type",
4471 value, type_name
4472 ),
4473 location.clone(),
4474 ));
4475 }
4476 }
4477 }
4478
4479 let check_bound = |value: &str,
4483 constraint: &'static str,
4484 kind: FacetKind|
4485 -> SchemaResult<()> {
4486 match crate::validation::simple::validate_simple_type(value, base_key, schema_set) {
4487 Ok(_) => Ok(()),
4488 Err(err) if is_bound_self_violation(&err, kind, schema_set, base_key, value) => Ok(()),
4489 Err(_) => Err(SchemaError::structural(
4490 constraint,
4491 format!(
4492 "{} value '{}' in type '{}' is not in the value space of the base type",
4493 kind.name(),
4494 value,
4495 type_name
4496 ),
4497 location.clone(),
4498 )),
4499 }
4500 };
4501
4502 if let Some(ref f) = type_def.facets.min_inclusive {
4503 check_bound(
4504 &f.value,
4505 "minInclusive-valid-restriction",
4506 FacetKind::MinInclusive,
4507 )?;
4508 }
4509 if let Some(ref f) = type_def.facets.max_inclusive {
4510 check_bound(
4511 &f.value,
4512 "maxInclusive-valid-restriction",
4513 FacetKind::MaxInclusive,
4514 )?;
4515 }
4516 if let Some(ref f) = type_def.facets.min_exclusive {
4517 check_bound(
4518 &f.value,
4519 "minExclusive-valid-restriction",
4520 FacetKind::MinExclusive,
4521 )?;
4522 }
4523 if let Some(ref f) = type_def.facets.max_exclusive {
4524 check_bound(
4525 &f.value,
4526 "maxExclusive-valid-restriction",
4527 FacetKind::MaxExclusive,
4528 )?;
4529 }
4530
4531 validate_typed_bound_consistency(
4532 schema_set,
4533 &type_def.facets,
4534 base_key,
4535 &type_name,
4536 &location,
4537 )?;
4538
4539 Ok(())
4540}
4541
4542fn validate_typed_bound_consistency(
4543 schema_set: &SchemaSet,
4544 facets: &FacetSet,
4545 base_key: TypeKey,
4546 type_name: &str,
4547 location: &Option<SourceLocation>,
4548) -> SchemaResult<()> {
4549 let check_pair = |lower: Option<&str>,
4550 upper: Option<&str>,
4551 lower_name: &'static str,
4552 upper_name: &'static str,
4553 allow_equal: bool|
4554 -> SchemaResult<()> {
4555 let (Some(lower), Some(upper)) = (lower, upper) else {
4556 return Ok(());
4557 };
4558 let Some(cmp) = compare_bound_literals(schema_set, base_key, lower, upper) else {
4559 return Ok(());
4560 };
4561 let valid = if allow_equal {
4562 matches!(cmp, std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
4563 } else {
4564 cmp == std::cmp::Ordering::Less
4565 };
4566 if valid {
4567 return Ok(());
4568 }
4569 Err(SchemaError::structural(
4570 "cos-st-restricts",
4571 format!(
4572 "{} value '{}' is not below {} value '{}' in type '{}'",
4573 lower_name, lower, upper_name, upper, type_name
4574 ),
4575 location.clone(),
4576 ))
4577 };
4578
4579 check_pair(
4580 facets.min_inclusive.as_ref().map(|f| f.value.as_str()),
4581 facets.max_inclusive.as_ref().map(|f| f.value.as_str()),
4582 "minInclusive",
4583 "maxInclusive",
4584 true,
4585 )?;
4586 check_pair(
4587 facets.min_exclusive.as_ref().map(|f| f.value.as_str()),
4588 facets.max_exclusive.as_ref().map(|f| f.value.as_str()),
4589 "minExclusive",
4590 "maxExclusive",
4591 false,
4592 )?;
4593 check_pair(
4594 facets.min_inclusive.as_ref().map(|f| f.value.as_str()),
4595 facets.max_exclusive.as_ref().map(|f| f.value.as_str()),
4596 "minInclusive",
4597 "maxExclusive",
4598 false,
4599 )?;
4600 check_pair(
4601 facets.min_exclusive.as_ref().map(|f| f.value.as_str()),
4602 facets.max_inclusive.as_ref().map(|f| f.value.as_str()),
4603 "minExclusive",
4604 "maxInclusive",
4605 false,
4606 )
4607}
4608
4609fn compare_bound_literals(
4610 schema_set: &SchemaSet,
4611 base_key: TypeKey,
4612 lower: &str,
4613 upper: &str,
4614) -> Option<std::cmp::Ordering> {
4615 let parse_base = bound_comparison_base(schema_set, base_key);
4616 let lower =
4617 crate::validation::simple::validate_simple_type(lower, parse_base, schema_set).ok()?;
4618 let upper =
4619 crate::validation::simple::validate_simple_type(upper, parse_base, schema_set).ok()?;
4620 compare_xml_values(&lower.typed_value, &upper.typed_value)
4621}
4622
4623fn bound_comparison_base(schema_set: &SchemaSet, key: TypeKey) -> TypeKey {
4624 let mut current = key;
4625 for _ in 0..100 {
4626 let TypeKey::Simple(sk) = current else {
4627 return current;
4628 };
4629 let Some(st) = schema_set.arenas.simple_types.get(sk) else {
4630 return current;
4631 };
4632 let has_bounds_or_enum = st.facets.enumeration.is_some()
4633 || st.facets.min_inclusive.is_some()
4634 || st.facets.min_exclusive.is_some()
4635 || st.facets.max_inclusive.is_some()
4636 || st.facets.max_exclusive.is_some();
4637 if !has_bounds_or_enum {
4638 return current;
4639 }
4640 let Some(base) = st.resolved_base_type else {
4641 return current;
4642 };
4643 current = base;
4644 }
4645 current
4646}
4647
4648fn compare_xml_values(
4649 lower: &crate::types::value::XmlValue,
4650 upper: &crate::types::value::XmlValue,
4651) -> Option<std::cmp::Ordering> {
4652 use crate::types::value::XmlValueKind;
4653 match (&lower.value, &upper.value) {
4654 (XmlValueKind::Atomic(a), XmlValueKind::Atomic(b)) => compare_xml_atomic_values(a, b),
4655 (XmlValueKind::Union(a), _) => compare_xml_values(a, upper),
4656 (_, XmlValueKind::Union(b)) => compare_xml_values(lower, b),
4657 _ => None,
4658 }
4659}
4660
4661fn compare_xml_atomic_values(
4662 lower: &crate::types::value::XmlAtomicValue,
4663 upper: &crate::types::value::XmlAtomicValue,
4664) -> Option<std::cmp::Ordering> {
4665 use crate::types::value::XmlAtomicValue;
4666 match (lower, upper) {
4667 (XmlAtomicValue::DateTime(a), XmlAtomicValue::DateTime(b)) => a.partial_cmp(b),
4668 (XmlAtomicValue::Date(a), XmlAtomicValue::Date(b)) => a.partial_cmp(b),
4669 (XmlAtomicValue::Time(a), XmlAtomicValue::Time(b)) => a.partial_cmp(b),
4670 (XmlAtomicValue::Duration(a), XmlAtomicValue::Duration(b)) => a.partial_cmp(b),
4671 (XmlAtomicValue::YearMonthDuration(a), XmlAtomicValue::YearMonthDuration(b)) => {
4672 a.partial_cmp(b)
4673 }
4674 (XmlAtomicValue::DayTimeDuration(a), XmlAtomicValue::DayTimeDuration(b)) => {
4675 a.partial_cmp(b)
4676 }
4677 (XmlAtomicValue::GYearMonth(a), XmlAtomicValue::GYearMonth(b)) => a.partial_cmp(b),
4678 (XmlAtomicValue::GYear(a), XmlAtomicValue::GYear(b)) => a.partial_cmp(b),
4679 (XmlAtomicValue::GMonthDay(a), XmlAtomicValue::GMonthDay(b)) => a.partial_cmp(b),
4680 (XmlAtomicValue::GDay(a), XmlAtomicValue::GDay(b)) => a.partial_cmp(b),
4681 (XmlAtomicValue::GMonth(a), XmlAtomicValue::GMonth(b)) => a.partial_cmp(b),
4682 _ => None,
4683 }
4684}
4685
4686fn get_type_facets(schema_set: &SchemaSet, type_key: TypeKey) -> SchemaResult<Option<FacetSet>> {
4687 match type_key {
4688 TypeKey::Simple(key) => {
4689 if let Some(type_def) = schema_set.arenas.simple_types.get(key) {
4690 Ok(Some(type_def.facets.clone()))
4691 } else {
4692 Ok(None)
4693 }
4694 }
4695 TypeKey::Complex(_) => {
4696 Ok(None)
4699 }
4700 }
4701}
4702
4703pub(crate) fn format_type_name(
4705 schema_set: &SchemaSet,
4706 name: Option<NameId>,
4707 namespace: Option<NameId>,
4708) -> String {
4709 match name {
4710 Some(name_id) => {
4711 let local = schema_set.name_table.resolve(name_id);
4712 match namespace {
4713 Some(ns_id) => {
4714 let ns = schema_set.name_table.resolve(ns_id);
4715 if ns.is_empty() {
4716 local.to_string()
4717 } else {
4718 format!("{{{}}}{}", ns, local)
4719 }
4720 }
4721 None => local.to_string(),
4722 }
4723 }
4724 None => "(anonymous)".to_string(),
4725 }
4726}
4727
4728pub(crate) trait TypeDefForError {
4732 fn error_name(&self) -> Option<NameId>;
4733 fn error_target_namespace(&self) -> Option<NameId>;
4734 fn error_source(&self) -> Option<&SourceRef>;
4735}
4736
4737impl TypeDefForError for crate::arenas::SimpleTypeDefData {
4738 fn error_name(&self) -> Option<NameId> {
4739 self.name
4740 }
4741 fn error_target_namespace(&self) -> Option<NameId> {
4742 self.target_namespace
4743 }
4744 fn error_source(&self) -> Option<&SourceRef> {
4745 self.source.as_ref()
4746 }
4747}
4748
4749impl TypeDefForError for crate::arenas::ComplexTypeDefData {
4750 fn error_name(&self) -> Option<NameId> {
4751 self.name
4752 }
4753 fn error_target_namespace(&self) -> Option<NameId> {
4754 self.target_namespace
4755 }
4756 fn error_source(&self) -> Option<&SourceRef> {
4757 self.source.as_ref()
4758 }
4759}
4760
4761pub(crate) fn type_error_context<T: TypeDefForError>(
4765 schema_set: &SchemaSet,
4766 type_def: &T,
4767) -> (Option<SourceLocation>, String) {
4768 (
4769 schema_set.locate(type_def.error_source()),
4770 format_type_name(
4771 schema_set,
4772 type_def.error_name(),
4773 type_def.error_target_namespace(),
4774 ),
4775 )
4776}
4777
4778struct EffectiveAttributeUse {
4781 name: NameId,
4782 target_namespace: Option<NameId>,
4783 use_kind: AttributeUseKind,
4784 resolved_type: Option<TypeKey>,
4785 fixed_value: Option<String>,
4786 default_value: Option<String>,
4787 inheritable: bool,
4793}
4794
4795fn resolve_single_attribute_use(
4799 schema_set: &SchemaSet,
4800 attr_use: &crate::parser::frames::AttributeUseResult,
4801 resolved: Option<&crate::arenas::ResolvedAttributeUse>,
4802) -> Option<EffectiveAttributeUse> {
4803 let (name, target_namespace) = if let Some(ref_name) = &attr_use.attribute.ref_name {
4804 if let Some(resolved_attr) = resolved.and_then(|r| r.resolved_ref) {
4805 let decl = schema_set.arenas.attributes.get(resolved_attr);
4806 (
4807 decl.and_then(|d| d.name)?,
4808 decl.and_then(|d| d.target_namespace),
4809 )
4810 } else {
4811 (ref_name.local_name, ref_name.namespace)
4812 }
4813 } else {
4814 let n = attr_use.attribute.name?;
4815 let ns = schema_set.effective_local_attribute_namespace(
4818 attr_use.attribute.target_namespace,
4819 attr_use.attribute.form.as_deref(),
4820 attr_use.attribute.source.as_ref(),
4821 None,
4822 );
4823 (n, ns)
4824 };
4825
4826 let resolved_type = resolved.and_then(|r| r.resolved_type).or_else(|| {
4828 resolved
4829 .and_then(|r| r.resolved_ref)
4830 .and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
4831 .and_then(|decl| decl.resolved_type)
4832 });
4833
4834 let fixed_value = attr_use.attribute.fixed_value.clone().or_else(|| {
4836 resolved
4837 .and_then(|r| r.resolved_ref)
4838 .and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
4839 .and_then(|decl| decl.fixed_value.clone())
4840 });
4841 let default_value = attr_use.attribute.default_value.clone().or_else(|| {
4843 resolved
4844 .and_then(|r| r.resolved_ref)
4845 .and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
4846 .and_then(|decl| decl.default_value.clone())
4847 });
4848
4849 let inheritable = if attr_use.attribute.inheritable {
4857 true
4858 } else if attr_use.attribute.ref_name.is_some() {
4859 resolved
4860 .and_then(|r| r.resolved_ref)
4861 .and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
4862 .map(|decl| decl.inheritable)
4863 .unwrap_or(false)
4864 } else {
4865 false
4866 };
4867
4868 Some(EffectiveAttributeUse {
4869 name,
4870 target_namespace,
4871 use_kind: attr_use.use_kind,
4872 resolved_type,
4873 fixed_value,
4874 default_value,
4875 inheritable,
4876 })
4877}
4878
4879fn collect_effective_attribute_uses(
4884 schema_set: &SchemaSet,
4885 type_def: &crate::arenas::ComplexTypeDefData,
4886) -> Vec<EffectiveAttributeUse> {
4887 let mut result = Vec::new();
4888
4889 for (i, attr_use) in type_def.attributes.iter().enumerate() {
4890 let resolved = type_def.resolved_attributes.get(i);
4891 if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
4892 result.push(eau);
4893 }
4894 }
4895
4896 for &ag_key in &type_def.resolved_attribute_groups {
4897 collect_attribute_group_uses(schema_set, ag_key, &mut result, 0);
4898 }
4899
4900 result
4901}
4902
4903#[derive(Debug, Clone, PartialEq, Eq)]
4929pub(crate) enum CanonicalNs {
4930 Any,
4932 Enum(std::collections::HashSet<Option<NameId>>),
4935 Not(std::collections::HashSet<Option<NameId>>),
4939}
4940
4941#[derive(Debug, Clone)]
4946pub(crate) struct EffectiveAttributeWildcard {
4947 pub(crate) namespace: CanonicalNs,
4948 pub(crate) not_qname: Vec<crate::parser::frames::NotQNameItem>,
4949 pub(crate) process_contents: ProcessContents,
4950}
4951
4952fn normalize_attribute_wildcard(
4956 schema_set: &SchemaSet,
4957 wc: &WildcardResult,
4958 target_ns: Option<NameId>,
4959) -> EffectiveAttributeWildcard {
4960 use std::collections::HashSet;
4961
4962 let base: CanonicalNs = match &wc.namespace {
4964 WildcardNamespace::Any => CanonicalNs::Any,
4965 WildcardNamespace::Other => {
4966 let mut excl = HashSet::new();
4976 excl.insert(target_ns);
4977 if schema_set.is_xsd10() {
4978 excl.insert(None);
4979 }
4980 CanonicalNs::Not(excl)
4981 }
4982 WildcardNamespace::TargetNamespace => {
4983 let mut s = HashSet::new();
4984 s.insert(target_ns);
4985 CanonicalNs::Enum(s)
4986 }
4987 WildcardNamespace::Local => {
4988 let mut s = HashSet::new();
4989 s.insert(None);
4990 CanonicalNs::Enum(s)
4991 }
4992 WildcardNamespace::List(tokens) => {
4993 let mut s = HashSet::new();
4994 for tok in tokens {
4995 s.insert(tok.resolve(target_ns));
4996 }
4997 CanonicalNs::Enum(s)
4998 }
4999 };
5000
5001 let not_ns: HashSet<Option<NameId>> = wc
5003 .not_namespace
5004 .iter()
5005 .map(|t| t.resolve(target_ns))
5006 .collect();
5007
5008 let namespace = if not_ns.is_empty() {
5009 base
5010 } else {
5011 match base {
5012 CanonicalNs::Any => CanonicalNs::Not(not_ns),
5013 CanonicalNs::Enum(set) => {
5014 let filtered: HashSet<Option<NameId>> =
5015 set.into_iter().filter(|ns| !not_ns.contains(ns)).collect();
5016 CanonicalNs::Enum(filtered)
5017 }
5018 CanonicalNs::Not(set) => {
5019 let mut combined = set;
5020 combined.extend(not_ns);
5021 CanonicalNs::Not(combined)
5022 }
5023 }
5024 };
5025
5026 EffectiveAttributeWildcard {
5027 namespace,
5028 not_qname: wc.not_qname.clone(),
5029 process_contents: wc.process_contents,
5030 }
5031}
5032
5033fn intersect_canonical_ns(a: &CanonicalNs, b: &CanonicalNs) -> CanonicalNs {
5036 use std::collections::HashSet;
5037 match (a, b) {
5038 (CanonicalNs::Any, other) | (other, CanonicalNs::Any) => other.clone(),
5040
5041 (CanonicalNs::Enum(s1), CanonicalNs::Enum(s2)) => {
5043 let inter: HashSet<Option<NameId>> = s1.intersection(s2).copied().collect();
5044 CanonicalNs::Enum(inter)
5045 }
5046
5047 (CanonicalNs::Enum(s), CanonicalNs::Not(n))
5049 | (CanonicalNs::Not(n), CanonicalNs::Enum(s)) => {
5050 let filtered: HashSet<Option<NameId>> =
5051 s.iter().filter(|ns| !n.contains(ns)).copied().collect();
5052 CanonicalNs::Enum(filtered)
5053 }
5054
5055 (CanonicalNs::Not(n1), CanonicalNs::Not(n2)) => {
5057 let mut union = n1.clone();
5058 union.extend(n2.iter().copied());
5059 CanonicalNs::Not(union)
5060 }
5061 }
5062}
5063
5064fn union_canonical_ns(a: &CanonicalNs, b: &CanonicalNs) -> CanonicalNs {
5067 use std::collections::HashSet;
5068 match (a, b) {
5069 (CanonicalNs::Any, _) | (_, CanonicalNs::Any) => CanonicalNs::Any,
5071
5072 (CanonicalNs::Enum(s1), CanonicalNs::Enum(s2)) => {
5074 let mut union = s1.clone();
5075 union.extend(s2.iter().copied());
5076 CanonicalNs::Enum(union)
5077 }
5078
5079 (CanonicalNs::Enum(s), CanonicalNs::Not(n))
5084 | (CanonicalNs::Not(n), CanonicalNs::Enum(s)) => {
5085 let filtered: HashSet<Option<NameId>> =
5086 n.iter().filter(|ns| !s.contains(ns)).copied().collect();
5087 if filtered.is_empty() {
5088 CanonicalNs::Any
5089 } else {
5090 CanonicalNs::Not(filtered)
5091 }
5092 }
5093
5094 (CanonicalNs::Not(n1), CanonicalNs::Not(n2)) => {
5097 let inter: HashSet<Option<NameId>> = n1.intersection(n2).copied().collect();
5098 if inter.is_empty() {
5099 CanonicalNs::Any
5100 } else {
5101 CanonicalNs::Not(inter)
5102 }
5103 }
5104 }
5105}
5106
5107fn canonical_ns_subset(a: &CanonicalNs, b: &CanonicalNs) -> bool {
5110 match (a, b) {
5111 (_, CanonicalNs::Any) => true,
5113
5114 (CanonicalNs::Any, _) => false,
5118
5119 (CanonicalNs::Enum(s), CanonicalNs::Enum(t)) => s.iter().all(|ns| t.contains(ns)),
5121
5122 (CanonicalNs::Enum(s), CanonicalNs::Not(n)) => s.iter().all(|ns| !n.contains(ns)),
5124
5125 (CanonicalNs::Not(n1), CanonicalNs::Not(n2)) => n2.iter().all(|ns| n1.contains(ns)),
5129
5130 (CanonicalNs::Not(_), CanonicalNs::Enum(_)) => false,
5133 }
5134}
5135
5136fn intersect_effective_attribute_wildcards(
5146 a: &EffectiveAttributeWildcard,
5147 b: &EffectiveAttributeWildcard,
5148) -> EffectiveAttributeWildcard {
5149 let namespace = intersect_canonical_ns(&a.namespace, &b.namespace);
5150
5151 let mut not_qname = a.not_qname.clone();
5154 for item in &b.not_qname {
5155 if !not_qname.contains(item) {
5156 not_qname.push(item.clone());
5157 }
5158 }
5159
5160 EffectiveAttributeWildcard {
5161 namespace,
5162 not_qname,
5163 process_contents: a.process_contents,
5164 }
5165}
5166
5167fn attribute_group_cycle_error() -> SchemaError {
5171 SchemaError::structural(
5172 "derivation-ok-restriction",
5173 "attribute group reference cycle exceeded max depth while computing \
5174 effective attribute wildcard (§3.6.2.2)",
5175 None,
5176 )
5177}
5178
5179fn combine_effective_wildcards(
5186 local: Option<EffectiveAttributeWildcard>,
5187 w: Vec<EffectiveAttributeWildcard>,
5188) -> Option<EffectiveAttributeWildcard> {
5189 match (local, w.is_empty()) {
5190 (None, true) => None,
5191 (Some(l), true) => Some(l),
5192 (Some(l), false) => Some(w.into_iter().fold(l, |acc, wi| {
5193 intersect_effective_attribute_wildcards(&acc, &wi)
5194 })),
5195 (None, false) => {
5196 let mut it = w.into_iter();
5197 let first = it.next().expect("w is non-empty");
5198 Some(it.fold(first, |acc, wi| {
5199 intersect_effective_attribute_wildcards(&acc, &wi)
5200 }))
5201 }
5202 }
5203}
5204
5205pub(crate) fn effective_attribute_wildcard(
5216 schema_set: &SchemaSet,
5217 local_wc: Option<&WildcardResult>,
5218 local_target_ns: Option<NameId>,
5219 attribute_groups: &[AttributeGroupKey],
5220) -> SchemaResult<Option<EffectiveAttributeWildcard>> {
5221 let local = local_wc.map(|w| normalize_attribute_wildcard(schema_set, w, local_target_ns));
5222
5223 let mut w: Vec<EffectiveAttributeWildcard> = Vec::new();
5224 for &ag_key in attribute_groups {
5225 collect_effective_group_wildcards(schema_set, ag_key, &mut w, 0)?;
5226 }
5227
5228 Ok(combine_effective_wildcards(local, w))
5229}
5230
5231pub(crate) fn compute_runtime_attribute_wildcard(
5246 schema_set: &SchemaSet,
5247 ct_key: ComplexTypeKey,
5248) -> Option<EffectiveAttributeWildcard> {
5249 compute_runtime_attribute_wildcard_bounded(schema_set, ct_key, 0)
5250}
5251
5252fn compute_runtime_attribute_wildcard_bounded(
5253 schema_set: &SchemaSet,
5254 ct_key: ComplexTypeKey,
5255 depth: u32,
5256) -> Option<EffectiveAttributeWildcard> {
5257 if depth > 100 {
5258 return None;
5259 }
5260 let ct = schema_set.arenas.complex_types.get(ct_key)?;
5261
5262 let own_local = own_attribute_wildcard_ref(ct);
5267 let own = effective_attribute_wildcard(
5268 schema_set,
5269 own_local,
5270 ct.target_namespace,
5271 &ct.resolved_attribute_groups,
5272 )
5273 .ok()
5274 .flatten();
5275
5276 let Some(TypeKey::Complex(base_key)) = ct.resolved_base_type else {
5277 return own;
5278 };
5279 if base_key == schema_set.any_type_key() {
5280 return own;
5281 }
5282
5283 match ct.derivation_method {
5284 Some(DerivationMethod::Extension) => {
5285 let base = compute_runtime_attribute_wildcard_bounded(schema_set, base_key, depth + 1);
5286 match (own, base) {
5287 (Some(a), Some(b)) => Some(union_effective_attribute_wildcards(&a, &b)),
5288 (Some(a), None) => Some(a),
5289 (None, Some(b)) => Some(b),
5290 (None, None) => None,
5291 }
5292 }
5293 _ => own,
5304 }
5305}
5306
5307fn own_attribute_wildcard_ref(ct: &crate::arenas::ComplexTypeDefData) -> Option<&WildcardResult> {
5311 if let Some(wc) = ct.attribute_wildcard.as_ref() {
5312 return Some(wc);
5313 }
5314 match &ct.content {
5315 ComplexContentResult::Empty => None,
5316 ComplexContentResult::Simple(sc) => sc.attribute_wildcard.as_ref(),
5317 ComplexContentResult::Complex(cc) => cc.attribute_wildcard.as_ref(),
5318 }
5319}
5320
5321fn union_effective_attribute_wildcards(
5332 a: &EffectiveAttributeWildcard,
5333 b: &EffectiveAttributeWildcard,
5334) -> EffectiveAttributeWildcard {
5335 use crate::parser::frames::NotQNameItem;
5336
5337 let namespace = union_canonical_ns(&a.namespace, &b.namespace);
5338
5339 let mut not_qname: Vec<NotQNameItem> = Vec::new();
5344
5345 let mut consider = |item: &NotQNameItem, other: &EffectiveAttributeWildcard| match item {
5346 NotQNameItem::QName {
5347 namespace,
5348 local_name,
5349 } => {
5350 let admitted_by_other_ns = match &other.namespace {
5351 CanonicalNs::Any => true,
5352 CanonicalNs::Enum(set) => set.contains(namespace),
5353 CanonicalNs::Not(set) => !set.contains(namespace),
5354 };
5355 let excluded_by_other_qname = other.not_qname.iter().any(|o| match o {
5356 NotQNameItem::QName {
5357 namespace: ons,
5358 local_name: oln,
5359 } => ons == namespace && oln == local_name,
5360 NotQNameItem::Defined | NotQNameItem::DefinedSibling => false,
5361 });
5362 if (!admitted_by_other_ns || excluded_by_other_qname) && !not_qname.contains(item) {
5363 not_qname.push(item.clone());
5364 }
5365 }
5366 NotQNameItem::Defined | NotQNameItem::DefinedSibling => {
5367 if other
5368 .not_qname
5369 .iter()
5370 .any(|o| std::mem::discriminant(o) == std::mem::discriminant(item))
5371 && !not_qname.contains(item)
5372 {
5373 not_qname.push(item.clone());
5374 }
5375 }
5376 };
5377
5378 for item in &a.not_qname {
5379 consider(item, b);
5380 }
5381 for item in &b.not_qname {
5382 consider(item, a);
5383 }
5384
5385 let process_contents = if process_contents_strictness(a.process_contents)
5386 <= process_contents_strictness(b.process_contents)
5387 {
5388 a.process_contents
5389 } else {
5390 b.process_contents
5391 };
5392
5393 EffectiveAttributeWildcard {
5394 namespace,
5395 not_qname,
5396 process_contents,
5397 }
5398}
5399
5400fn collect_effective_group_wildcards(
5408 schema_set: &SchemaSet,
5409 ag_key: AttributeGroupKey,
5410 out: &mut Vec<EffectiveAttributeWildcard>,
5411 depth: usize,
5412) -> SchemaResult<()> {
5413 if depth > 20 {
5414 return Err(attribute_group_cycle_error());
5415 }
5416
5417 let Some(ag) = schema_set.arenas.attribute_groups.get(ag_key) else {
5418 return Ok(());
5419 };
5420
5421 if let Some(ref_key) = ag.resolved_ref {
5422 return collect_effective_group_wildcards(schema_set, ref_key, out, depth + 1);
5423 }
5424
5425 if let Some(eff) = effective_attribute_wildcard_for_group(schema_set, ag, depth + 1)? {
5426 out.push(eff);
5427 }
5428
5429 Ok(())
5430}
5431
5432fn effective_attribute_wildcard_for_group(
5437 schema_set: &SchemaSet,
5438 ag: &crate::arenas::AttributeGroupData,
5439 depth: usize,
5440) -> SchemaResult<Option<EffectiveAttributeWildcard>> {
5441 if depth > 20 {
5442 return Err(attribute_group_cycle_error());
5443 }
5444
5445 if let Some(ref_key) = ag.resolved_ref {
5446 let Some(target) = schema_set.arenas.attribute_groups.get(ref_key) else {
5447 return Ok(None);
5448 };
5449 return effective_attribute_wildcard_for_group(schema_set, target, depth + 1);
5450 }
5451
5452 let local = ag
5453 .attribute_wildcard
5454 .as_ref()
5455 .map(|w| normalize_attribute_wildcard(schema_set, w, ag.target_namespace));
5456
5457 let mut w: Vec<EffectiveAttributeWildcard> = Vec::new();
5458 for &nested_key in &ag.resolved_attribute_groups {
5459 collect_effective_group_wildcards(schema_set, nested_key, &mut w, depth + 1)?;
5460 }
5461
5462 Ok(combine_effective_wildcards(local, w))
5463}
5464
5465fn effective_attribute_wildcard_restricts(
5491 schema_set: &SchemaSet,
5492 derived: &EffectiveAttributeWildcard,
5493 base: &EffectiveAttributeWildcard,
5494) -> Result<(), &'static str> {
5495 use crate::parser::frames::NotQNameItem;
5496
5497 if !canonical_ns_subset(&derived.namespace, &base.namespace) {
5498 return Err("namespace constraint is not a subset of the base wildcard");
5499 }
5500
5501 for item in &base.not_qname {
5506 match item {
5507 NotQNameItem::QName {
5508 namespace,
5509 local_name,
5510 } => {
5511 if effective_wildcard_allows_attribute(schema_set, derived, *namespace, *local_name)
5512 {
5513 return Err(
5514 "notQName exclusions do not cover the base wildcard's disallowed names",
5515 );
5516 }
5517 }
5518 NotQNameItem::Defined => {
5520 if !derived
5521 .not_qname
5522 .iter()
5523 .any(|d| matches!(d, NotQNameItem::Defined))
5524 {
5525 return Err("base wildcard excludes ##defined but derived does not");
5526 }
5527 }
5528 NotQNameItem::DefinedSibling => {
5530 if !derived
5531 .not_qname
5532 .iter()
5533 .any(|d| matches!(d, NotQNameItem::DefinedSibling))
5534 {
5535 return Err("base wildcard excludes ##definedSibling but derived does not");
5536 }
5537 }
5538 }
5539 }
5540
5541 if process_contents_strictness(derived.process_contents)
5542 < process_contents_strictness(base.process_contents)
5543 {
5544 return Err("processContents is weaker than the base wildcard");
5545 }
5546
5547 Ok(())
5548}
5549
5550pub(crate) fn effective_wildcard_allows_attribute(
5559 schema_set: &SchemaSet,
5560 wc: &EffectiveAttributeWildcard,
5561 attr_namespace: Option<NameId>,
5562 attr_name: NameId,
5563) -> bool {
5564 let ns_ok = match &wc.namespace {
5566 CanonicalNs::Any => true,
5567 CanonicalNs::Enum(set) => set.contains(&attr_namespace),
5568 CanonicalNs::Not(set) => !set.contains(&attr_namespace),
5569 };
5570 if !ns_ok {
5571 return false;
5572 }
5573
5574 for item in &wc.not_qname {
5576 match item {
5577 crate::parser::frames::NotQNameItem::QName {
5578 namespace: qns,
5579 local_name,
5580 } => {
5581 if *qns == attr_namespace && *local_name == attr_name {
5582 return false;
5583 }
5584 }
5585 crate::parser::frames::NotQNameItem::Defined => {
5586 if schema_set
5587 .lookup_attribute(attr_namespace, attr_name)
5588 .is_some()
5589 {
5590 return false;
5591 }
5592 }
5593 crate::parser::frames::NotQNameItem::DefinedSibling => {
5594 }
5597 }
5598 }
5599
5600 true
5601}
5602
5603fn collect_attribute_group_uses(
5605 schema_set: &SchemaSet,
5606 ag_key: AttributeGroupKey,
5607 result: &mut Vec<EffectiveAttributeUse>,
5608 depth: usize,
5609) {
5610 if depth > 20 {
5611 return;
5612 }
5613
5614 let Some(ag) = schema_set.arenas.attribute_groups.get(ag_key) else {
5615 return;
5616 };
5617
5618 if let Some(ref_key) = ag.resolved_ref {
5619 collect_attribute_group_uses(schema_set, ref_key, result, depth + 1);
5620 return;
5621 }
5622
5623 for (i, attr_use) in ag.attributes.iter().enumerate() {
5624 let resolved = ag.resolved_attributes.get(i);
5625 if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
5626 result.push(eau);
5627 }
5628 }
5629
5630 for &nested_key in &ag.resolved_attribute_groups {
5631 collect_attribute_group_uses(schema_set, nested_key, result, depth + 1);
5632 }
5633}
5634
5635fn is_type_derived_from(schema_set: &SchemaSet, derived_key: TypeKey, base_key: TypeKey) -> bool {
5637 schema_set.is_type_derived_from(derived_key, base_key, DerivationSet::empty())
5638}
5639
5640enum WildcardRestrictionOutcome {
5644 DerivedAbsent,
5646 AddedInDerived,
5648 NotSubset(&'static str),
5651 Valid,
5653}
5654
5655fn classify_attribute_wildcard_restriction(
5659 schema_set: &SchemaSet,
5660 derived_eff: Option<&EffectiveAttributeWildcard>,
5661 base_eff: Option<&EffectiveAttributeWildcard>,
5662) -> WildcardRestrictionOutcome {
5663 match (derived_eff, base_eff) {
5664 (None, _) => WildcardRestrictionOutcome::DerivedAbsent,
5665 (Some(_), None) => WildcardRestrictionOutcome::AddedInDerived,
5666 (Some(d), Some(b)) => match effective_attribute_wildcard_restricts(schema_set, d, b) {
5667 Ok(()) => WildcardRestrictionOutcome::Valid,
5668 Err(reason) => WildcardRestrictionOutcome::NotSubset(reason),
5669 },
5670 }
5671}
5672
5673fn complex_type_has_no_attribute_wildcard_source(
5677 type_def: &crate::arenas::ComplexTypeDefData,
5678) -> bool {
5679 type_def.attribute_wildcard.is_none() && type_def.resolved_attribute_groups.is_empty()
5680}
5681
5682fn validate_attribute_restriction(
5691 schema_set: &SchemaSet,
5692 derived: &crate::arenas::ComplexTypeDefData,
5693 base: &crate::arenas::ComplexTypeDefData,
5694) -> SchemaResult<()> {
5695 let derived_attrs = collect_effective_attribute_uses(schema_set, derived);
5696 let base_attrs = collect_effective_attribute_uses(schema_set, base);
5697
5698 let location = derived
5699 .source
5700 .as_ref()
5701 .and_then(|s| schema_set.source_maps.locate(s));
5702 let type_name = format_type_name(schema_set, derived.name, derived.target_namespace);
5703 let base_name = format_type_name(schema_set, base.name, base.target_namespace);
5704
5705 for base_attr in &base_attrs {
5716 if base_attr.use_kind != AttributeUseKind::Required {
5717 continue;
5718 }
5719
5720 let derived_attr = derived_attrs
5722 .iter()
5723 .find(|a| a.name == base_attr.name && a.target_namespace == base_attr.target_namespace);
5724
5725 match derived_attr {
5726 Some(da) if da.use_kind == AttributeUseKind::Required => {}
5728 None => {}
5730 Some(_) => {
5734 let attr_name_str = schema_set.name_table.resolve(base_attr.name);
5735 return Err(SchemaError::structural(
5736 "derivation-ok-restriction",
5737 format!(
5738 "Complex type '{}' restricting '{}': base type requires attribute '{}' \
5739 but the derived type weakens it to optional or prohibited",
5740 type_name, base_name, attr_name_str,
5741 ),
5742 location,
5743 ));
5744 }
5745 }
5746 }
5747
5748 for derived_attr in &derived_attrs {
5750 let Some(derived_type_key) = derived_attr.resolved_type else {
5751 continue;
5752 };
5753
5754 let base_attr = base_attrs.iter().find(|a| {
5755 a.name == derived_attr.name && a.target_namespace == derived_attr.target_namespace
5756 });
5757 let Some(base_attr) = base_attr else { continue };
5758 let Some(base_type_key) = base_attr.resolved_type else {
5759 continue;
5760 };
5761
5762 if derived_type_key == base_type_key {
5763 continue;
5764 }
5765
5766 if !is_type_derived_from(schema_set, derived_type_key, base_type_key) {
5767 let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
5768 return Err(SchemaError::structural(
5769 "derivation-ok-restriction",
5770 format!(
5771 "Complex type '{}' restricting '{}': attribute '{}' has a type \
5772 that is not validly derived from the base attribute type",
5773 type_name, base_name, attr_name_str,
5774 ),
5775 location,
5776 ));
5777 }
5778 }
5779
5780 for base_attr in &base_attrs {
5786 let Some(base_fixed) = base_attr.fixed_value.as_deref() else {
5787 continue;
5788 };
5789 let Some(derived_attr) = derived_attrs
5791 .iter()
5792 .find(|a| a.name == base_attr.name && a.target_namespace == base_attr.target_namespace)
5793 else {
5794 continue;
5796 };
5797 match derived_attr.fixed_value.as_deref() {
5798 Some(d_fixed)
5799 if crate::validation::simple::fixed_values_equal(
5800 d_fixed,
5801 base_fixed,
5802 base_attr.resolved_type,
5803 schema_set,
5804 ) => {}
5805 Some(d_fixed) => {
5806 let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
5807 return Err(SchemaError::structural(
5808 "derivation-ok-restriction",
5809 format!(
5810 "Complex type '{}' restricting '{}': attribute '{}' \
5811 changes 'fixed' value from '{}' to '{}'",
5812 type_name, base_name, attr_name_str, base_fixed, d_fixed,
5813 ),
5814 location,
5815 ));
5816 }
5817 None => {
5818 let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
5821 let what = if derived_attr.default_value.is_some() {
5822 "uses 'default' (cannot weaken base 'fixed')"
5823 } else {
5824 "drops the 'fixed' value constraint"
5825 };
5826 return Err(SchemaError::structural(
5827 "derivation-ok-restriction",
5828 format!(
5829 "Complex type '{}' restricting '{}': attribute '{}' {}",
5830 type_name, base_name, attr_name_str, what,
5831 ),
5832 location,
5833 ));
5834 }
5835 }
5836 }
5837
5838 if schema_set.is_xsd11() {
5846 for derived_attr in &derived_attrs {
5847 let Some(base_attr) = base_attrs.iter().find(|a| {
5848 a.name == derived_attr.name && a.target_namespace == derived_attr.target_namespace
5849 }) else {
5850 continue;
5851 };
5852 if base_attr.inheritable != derived_attr.inheritable {
5853 let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
5854 return Err(SchemaError::structural(
5855 "derivation-ok-restriction",
5856 format!(
5857 "Complex type '{}' restricting '{}': attribute '{}' changes \
5858 {{inheritable}} from {} to {}",
5859 type_name,
5860 base_name,
5861 attr_name_str,
5862 base_attr.inheritable,
5863 derived_attr.inheritable,
5864 ),
5865 location,
5866 ));
5867 }
5868 }
5869 }
5870
5871 if !complex_type_has_no_attribute_wildcard_source(derived) {
5878 let derived_eff = effective_attribute_wildcard(
5879 schema_set,
5880 derived.attribute_wildcard.as_ref(),
5881 derived.target_namespace,
5882 &derived.resolved_attribute_groups,
5883 )?;
5884 let base_eff = effective_attribute_wildcard(
5885 schema_set,
5886 base.attribute_wildcard.as_ref(),
5887 base.target_namespace,
5888 &base.resolved_attribute_groups,
5889 )?;
5890
5891 match classify_attribute_wildcard_restriction(
5892 schema_set,
5893 derived_eff.as_ref(),
5894 base_eff.as_ref(),
5895 ) {
5896 WildcardRestrictionOutcome::DerivedAbsent | WildcardRestrictionOutcome::Valid => {}
5897 WildcardRestrictionOutcome::AddedInDerived => {
5898 return Err(SchemaError::structural(
5899 "derivation-ok-restriction",
5900 format!(
5901 "Complex type '{}' restricting '{}': derived type has an attribute \
5902 wildcard but the base type does not",
5903 type_name, base_name,
5904 ),
5905 location,
5906 ));
5907 }
5908 WildcardRestrictionOutcome::NotSubset(reason) => {
5909 return Err(SchemaError::structural(
5910 "derivation-ok-restriction",
5911 format!(
5912 "Complex type '{}' restricting '{}': attribute wildcard is not \
5913 a valid restriction of the base wildcard: {}",
5914 type_name, base_name, reason,
5915 ),
5916 location,
5917 ));
5918 }
5919 }
5920 }
5921
5922 Ok(())
5923}
5924
5925fn is_bound_self_violation(
5931 err: &crate::validation::errors::ValidationError,
5932 kind: FacetKind,
5933 schema_set: &SchemaSet,
5934 base_key: TypeKey,
5935 value: &str,
5936) -> bool {
5937 let code = match kind {
5938 FacetKind::MaxExclusive => "cvc-maxExclusive-valid",
5939 FacetKind::MaxInclusive => "cvc-maxInclusive-valid",
5940 FacetKind::MinExclusive => "cvc-minExclusive-valid",
5941 FacetKind::MinInclusive => "cvc-minInclusive-valid",
5942 _ => return false,
5943 };
5944 if err.constraint != code {
5945 return false;
5946 }
5947 let Some(base_bound) = find_base_bound_literal(schema_set, base_key, kind) else {
5948 return false;
5949 };
5950 let Some(v) = parse_past_own_bound(schema_set, base_key, value) else {
5951 return false;
5952 };
5953 let Some(b) = parse_past_own_bound(schema_set, base_key, &base_bound) else {
5954 return false;
5955 };
5956 v.typed_value == b.typed_value
5957}
5958
5959fn parse_past_own_bound(
5963 schema_set: &SchemaSet,
5964 base_key: TypeKey,
5965 value: &str,
5966) -> Option<crate::validation::simple::SimpleTypeResult> {
5967 if let Ok(r) = crate::validation::simple::validate_simple_type(value, base_key, schema_set) {
5968 return Some(r);
5969 }
5970 let without_bounds = lexical_base(schema_set, base_key)?;
5971 crate::validation::simple::validate_simple_type(value, without_bounds, schema_set).ok()
5972}
5973
5974fn lexical_base(schema_set: &SchemaSet, base_key: TypeKey) -> Option<TypeKey> {
5977 let mut current = base_key;
5978 for _ in 0..100 {
5979 match current {
5980 TypeKey::Simple(sk) => {
5981 let st = schema_set.arenas.simple_types.get(sk)?;
5982 let has_bounds = st.facets.min_inclusive.is_some()
5983 || st.facets.min_exclusive.is_some()
5984 || st.facets.max_inclusive.is_some()
5985 || st.facets.max_exclusive.is_some();
5986 if !has_bounds {
5987 return Some(current);
5988 }
5989 current = st.resolved_base_type?;
5990 }
5991 TypeKey::Complex(_) => return None,
5992 }
5993 }
5994 None
5995}
5996
5997fn find_base_bound_literal(
6000 schema_set: &SchemaSet,
6001 base_key: TypeKey,
6002 kind: FacetKind,
6003) -> Option<String> {
6004 let mut current = base_key;
6005 for _ in 0..100 {
6006 match current {
6007 TypeKey::Simple(sk) => {
6008 let st = schema_set.arenas.simple_types.get(sk)?;
6009 let literal = match kind {
6010 FacetKind::MaxExclusive => {
6011 st.facets.max_exclusive.as_ref().map(|f| f.value.clone())
6012 }
6013 FacetKind::MaxInclusive => {
6014 st.facets.max_inclusive.as_ref().map(|f| f.value.clone())
6015 }
6016 FacetKind::MinExclusive => {
6017 st.facets.min_exclusive.as_ref().map(|f| f.value.clone())
6018 }
6019 FacetKind::MinInclusive => {
6020 st.facets.min_inclusive.as_ref().map(|f| f.value.clone())
6021 }
6022 _ => None,
6023 };
6024 if let Some(v) = literal {
6025 return Some(v);
6026 }
6027 current = st.resolved_base_type?;
6028 }
6029 TypeKey::Complex(_) => return None,
6030 }
6031 }
6032 None
6033}
6034
6035fn effective_simple_content_type_key(
6038 schema_set: &SchemaSet,
6039 type_def: &crate::arenas::ComplexTypeDefData,
6040) -> Option<TypeKey> {
6041 let mut current_base = type_def.resolved_base_type?;
6042 for _ in 0..50 {
6043 match current_base {
6044 TypeKey::Simple(sk) => return Some(TypeKey::Simple(sk)),
6045 TypeKey::Complex(ck) => {
6046 let ct = schema_set.arenas.complex_types.get(ck)?;
6047 current_base = ct.resolved_base_type?;
6048 }
6049 }
6050 }
6051 None
6052}
6053
6054fn validate_simple_content_restriction(
6060 schema_set: &SchemaSet,
6061 derived: &crate::arenas::ComplexTypeDefData,
6062 base: &crate::arenas::ComplexTypeDefData,
6063) -> SchemaResult<()> {
6064 let ComplexContentResult::Simple(ref sc) = derived.content else {
6066 return Ok(());
6067 };
6068
6069 let Some(ref inline_st) = sc.content_type else {
6070 return Ok(());
6071 };
6072
6073 let Some(base_simple_key) = effective_simple_content_type_key(schema_set, base) else {
6075 return Ok(());
6076 };
6077
6078 if let TypeKey::Simple(sk) = base_simple_key {
6084 if sk == schema_set.builtin_types().any_simple_type {
6085 return Ok(());
6086 }
6087 }
6088
6089 let base_variety = match base_simple_key {
6091 TypeKey::Simple(sk) => schema_set.arenas.simple_types.get(sk).map(|st| st.variety),
6092 TypeKey::Complex(_) => None,
6093 };
6094
6095 let Some(base_variety) = base_variety else {
6096 return Ok(());
6097 };
6098
6099 let derived_variety = inline_st.variety;
6104
6105 if derived_variety != base_variety {
6106 if let Some(inline_resolved_base) = resolve_inline_simple_type_base(schema_set, inline_st) {
6112 if is_type_derived_from(schema_set, inline_resolved_base, base_simple_key) {
6113 return Ok(());
6114 }
6115 }
6116
6117 let location = derived
6118 .source
6119 .as_ref()
6120 .and_then(|s| schema_set.source_maps.locate(s));
6121 let type_name = format_type_name(schema_set, derived.name, derived.target_namespace);
6122 let base_name = format_type_name(schema_set, base.name, base.target_namespace);
6123 return Err(SchemaError::structural(
6124 "derivation-ok-restriction",
6125 format!(
6126 "Complex type '{}' restricting '{}': simpleContent inline type \
6127 has variety {:?} which is not a valid restriction of the base \
6128 type's simple content (variety {:?})",
6129 type_name, base_name, derived_variety, base_variety,
6130 ),
6131 location,
6132 ));
6133 }
6134
6135 Ok(())
6136}
6137
6138fn resolve_inline_simple_type_base(
6142 schema_set: &SchemaSet,
6143 inline_st: &crate::parser::frames::SimpleTypeResult,
6144) -> Option<TypeKey> {
6145 match &inline_st.base_type {
6150 Some(crate::parser::frames::TypeRefResult::QName(qname)) => {
6151 schema_set.lookup_type(qname.namespace, qname.local_name)
6152 }
6153 _ => None,
6154 }
6155}
6156
6157pub fn validate_attribute_id_constraints(schema_set: &SchemaSet) -> SchemaResult<()> {
6162 use crate::types::XmlTypeCode;
6163
6164 if !schema_set.is_xsd10() {
6165 return Ok(());
6166 }
6167
6168 let id_key = match schema_set.builtin_types().get_by_type_code(XmlTypeCode::Id) {
6169 Some(k) => k,
6170 None => return Ok(()),
6171 };
6172
6173 for (_key, attr_data) in schema_set.arenas.attributes.iter() {
6174 if attr_data.default_value.is_none() && attr_data.fixed_value.is_none() {
6175 continue;
6176 }
6177 if let Some(TypeKey::Simple(st_key)) = attr_data.resolved_type {
6178 if schema_set.derives_from(st_key, id_key) {
6179 let attr_name = attr_data
6180 .name
6181 .map(|n| schema_set.name_table.resolve(n).to_string())
6182 .unwrap_or_else(|| "(anonymous)".to_string());
6183 let constraint = if attr_data.default_value.is_some() {
6184 "default"
6185 } else {
6186 "fixed"
6187 };
6188 return Err(SchemaError::structural(
6189 "cos-attribute-decl",
6190 format!(
6191 "Attribute '{}' has type xs:ID (or derived) and must not have a {} value constraint",
6192 attr_name, constraint
6193 ),
6194 schema_set.locate(attr_data.source.as_ref()),
6195 ));
6196 }
6197 }
6198 }
6199
6200 for (_key, ct_data) in schema_set.arenas.complex_types.iter() {
6201 for (i, attr_use) in ct_data.attributes.iter().enumerate() {
6202 if attr_use.use_kind == AttributeUseKind::Prohibited {
6203 continue;
6204 }
6205 let resolved = ct_data.resolved_attributes.get(i);
6206 let ref_decl = resolved
6207 .and_then(|r| r.resolved_ref)
6208 .and_then(|k| schema_set.arenas.attributes.get(k));
6209
6210 let has_constraint = attr_use.attribute.default_value.is_some()
6211 || attr_use.attribute.fixed_value.is_some()
6212 || ref_decl.is_some_and(|d| d.default_value.is_some() || d.fixed_value.is_some());
6213 if !has_constraint {
6214 continue;
6215 }
6216
6217 let attr_type = resolved
6218 .and_then(|r| r.resolved_type)
6219 .or_else(|| ref_decl.and_then(|d| d.resolved_type));
6220 if let Some(TypeKey::Simple(st_key)) = attr_type {
6221 if schema_set.derives_from(st_key, id_key) {
6222 let attr_name = attr_use
6223 .attribute
6224 .name
6225 .map(|n| schema_set.name_table.resolve(n).to_string())
6226 .or_else(|| {
6227 ref_decl
6228 .and_then(|d| d.name)
6229 .map(|n| schema_set.name_table.resolve(n).to_string())
6230 })
6231 .unwrap_or_else(|| "(anonymous)".to_string());
6232 let constraint = if attr_use.attribute.default_value.is_some()
6233 || ref_decl.and_then(|d| d.default_value.as_ref()).is_some()
6234 {
6235 "default"
6236 } else {
6237 "fixed"
6238 };
6239 let location = attr_use
6240 .attribute
6241 .source
6242 .as_ref()
6243 .or(ct_data.source.as_ref())
6244 .and_then(|s| schema_set.source_maps.locate(s));
6245 return Err(SchemaError::structural(
6246 "cos-attribute-decl",
6247 format!(
6248 "Attribute '{}' has type xs:ID (or derived) and must not have a {} value constraint",
6249 attr_name, constraint
6250 ),
6251 location,
6252 ));
6253 }
6254 }
6255 }
6256 }
6257
6258 Ok(())
6259}
6260
6261pub fn validate_attribute_value_constraints(schema_set: &SchemaSet) -> SchemaResult<()> {
6268 for (_key, attr) in schema_set.arenas.attributes.iter() {
6270 if attr.source.is_none() {
6271 continue;
6273 }
6274 if matches!(attr.resolved_type, Some(TypeKey::Complex(_))) {
6278 let attr_name = attr
6279 .name
6280 .map(|n| schema_set.name_table.resolve(n).to_string())
6281 .unwrap_or_else(|| "(anonymous)".to_string());
6282 return Err(SchemaError::structural(
6283 "a-props-correct",
6284 format!(
6285 "Attribute '{}' references a complex type; the type definition of an \
6286 attribute must be a simple type",
6287 attr_name,
6288 ),
6289 schema_set.locate(attr.source.as_ref()),
6290 ));
6291 }
6292 let (value, is_fixed) = match (&attr.default_value, &attr.fixed_value) {
6293 (Some(v), _) => (v.as_str(), false),
6294 (_, Some(v)) => (v.as_str(), true),
6295 (None, None) => continue,
6296 };
6297 let Some(type_key @ TypeKey::Simple(_)) = attr.resolved_type else {
6298 continue;
6299 };
6300 if crate::validation::simple::validate_simple_type(value, type_key, schema_set).is_err() {
6301 let attr_name = attr
6302 .name
6303 .map(|n| schema_set.name_table.resolve(n).to_string())
6304 .unwrap_or_else(|| "(anonymous)".to_string());
6305 let constraint = if is_fixed { "fixed" } else { "default" };
6306 return Err(SchemaError::structural(
6307 "a-props-correct",
6308 format!(
6309 "Attribute '{}' {} value '{}' is not valid for its declared type",
6310 attr_name, constraint, value
6311 ),
6312 schema_set.locate(attr.source.as_ref()),
6313 ));
6314 }
6315 }
6316
6317 for (_key, ct) in schema_set.arenas.complex_types.iter() {
6319 for (i, attr_use) in ct.attributes.iter().enumerate() {
6320 if attr_use.use_kind == AttributeUseKind::Prohibited {
6321 continue;
6322 }
6323 let resolved = ct.resolved_attributes.get(i);
6324 let ref_decl = resolved
6325 .and_then(|r| r.resolved_ref)
6326 .and_then(|k| schema_set.arenas.attributes.get(k));
6327
6328 let value_constraint: Option<(&str, bool)> = attr_use
6333 .attribute
6334 .fixed_value
6335 .as_deref()
6336 .map(|v| (v, true))
6337 .or_else(|| {
6338 attr_use
6339 .attribute
6340 .default_value
6341 .as_deref()
6342 .map(|v| (v, false))
6343 })
6344 .or_else(|| {
6345 ref_decl.and_then(|d| {
6346 d.fixed_value
6347 .as_deref()
6348 .map(|v| (v, true))
6349 .or_else(|| d.default_value.as_deref().map(|v| (v, false)))
6350 })
6351 });
6352
6353 if let (Some(use_fixed), Some(decl_fixed)) = (
6360 attr_use.attribute.fixed_value.as_deref(),
6361 ref_decl.and_then(|d| d.fixed_value.as_deref()),
6362 ) {
6363 use crate::types::WhitespaceMode;
6364 let normalize = |s: &str| -> String {
6365 crate::types::facets::normalize_whitespace(s, WhitespaceMode::Collapse)
6366 };
6367 if normalize(use_fixed) != normalize(decl_fixed) {
6368 let attr_name = ref_decl
6369 .and_then(|d| d.name)
6370 .map(|n| schema_set.name_table.resolve(n).to_string())
6371 .unwrap_or_else(|| "(anonymous)".to_string());
6372 let location = attr_use
6373 .attribute
6374 .source
6375 .as_ref()
6376 .or(ct.source.as_ref())
6377 .and_then(|s| schema_set.source_maps.locate(s));
6378 return Err(SchemaError::structural(
6379 "au-props-correct",
6380 format!(
6381 "Attribute use 'fixed' value '{}' on '{}' does not match the \
6382 referenced attribute declaration's 'fixed' value '{}'",
6383 use_fixed, attr_name, decl_fixed,
6384 ),
6385 location,
6386 ));
6387 }
6388 }
6389
6390 if attr_use.attribute.default_value.is_some()
6393 && ref_decl.and_then(|d| d.fixed_value.as_deref()).is_some()
6394 {
6395 let attr_name = ref_decl
6396 .and_then(|d| d.name)
6397 .map(|n| schema_set.name_table.resolve(n).to_string())
6398 .unwrap_or_else(|| "(anonymous)".to_string());
6399 let location = attr_use
6400 .attribute
6401 .source
6402 .as_ref()
6403 .or(ct.source.as_ref())
6404 .and_then(|s| schema_set.source_maps.locate(s));
6405 return Err(SchemaError::structural(
6406 "au-props-correct",
6407 format!(
6408 "Attribute use cannot specify 'default' for '{}' because the \
6409 referenced attribute declaration has 'fixed'",
6410 attr_name,
6411 ),
6412 location,
6413 ));
6414 }
6415
6416 let Some((value, is_fixed)) = value_constraint else {
6417 continue;
6418 };
6419
6420 let attr_type = resolved
6421 .and_then(|r| r.resolved_type)
6422 .or_else(|| ref_decl.and_then(|d| d.resolved_type));
6423 let Some(type_key @ TypeKey::Simple(_)) = attr_type else {
6424 continue;
6425 };
6426 if crate::validation::simple::validate_simple_type(value, type_key, schema_set).is_err()
6427 {
6428 let attr_name = attr_use
6429 .attribute
6430 .name
6431 .map(|n| schema_set.name_table.resolve(n).to_string())
6432 .or_else(|| {
6433 ref_decl
6434 .and_then(|d| d.name)
6435 .map(|n| schema_set.name_table.resolve(n).to_string())
6436 })
6437 .unwrap_or_else(|| "(anonymous)".to_string());
6438 let constraint = if is_fixed { "fixed" } else { "default" };
6439 let location = attr_use
6440 .attribute
6441 .source
6442 .as_ref()
6443 .or(ct.source.as_ref())
6444 .and_then(|s| schema_set.source_maps.locate(s));
6445 return Err(SchemaError::structural(
6446 "a-props-correct",
6447 format!(
6448 "Attribute '{}' {} value '{}' is not valid for its declared type",
6449 attr_name, constraint, value
6450 ),
6451 location,
6452 ));
6453 }
6454 }
6455 }
6456
6457 Ok(())
6458}
6459
6460pub fn validate_element_value_constraints(schema_set: &SchemaSet) -> SchemaResult<()> {
6467 use crate::parser::frames::ComplexContentResult;
6468 use crate::types::XmlTypeCode;
6469
6470 let id_key = schema_set.builtin_types().get_by_type_code(XmlTypeCode::Id);
6471 let any_type_key = TypeKey::Complex(schema_set.any_type_key());
6472
6473 for (_key, elem) in schema_set.arenas.elements.iter() {
6474 let (value, is_fixed) = match (&elem.default_value, &elem.fixed_value) {
6475 (Some(v), _) => (v.as_str(), false),
6476 (_, Some(v)) => (v.as_str(), true),
6477 (None, None) => continue,
6478 };
6479
6480 if elem.resolved_ref.is_some() {
6482 continue;
6483 }
6484
6485 let type_key = match elem.resolved_type {
6486 Some(tk) if tk != any_type_key => tk,
6487 _ => continue,
6488 };
6489
6490 let elem_name = || {
6491 elem.name
6492 .map(|n| schema_set.name_table.resolve_ref(n))
6493 .unwrap_or("(anonymous)")
6494 };
6495 let location = || schema_set.locate(elem.source.as_ref());
6496 let constraint = if is_fixed { "fixed" } else { "default" };
6497
6498 if let TypeKey::Complex(ct_key) = type_key {
6504 if let Some(ct) = schema_set.arenas.complex_types.get(ct_key) {
6505 let simple_content = matches!(ct.content, ComplexContentResult::Simple(_));
6506 if !simple_content && !ct.mixed {
6507 return Err(SchemaError::structural(
6508 "src-element",
6509 format!(
6510 "Element '{}' has '{}' value but its type is a complex type \
6511 with non-mixed, non-simple content",
6512 elem_name(),
6513 constraint
6514 ),
6515 location(),
6516 ));
6517 }
6518 }
6519 }
6520
6521 if !schema_set.is_xsd11() {
6525 if let (Some(id_simple_key), TypeKey::Simple(st_key)) = (id_key, type_key) {
6526 if schema_set.derives_from(st_key, id_simple_key) {
6527 return Err(SchemaError::structural(
6528 "e-props-correct.4",
6529 format!(
6530 "Element '{}' has type xs:ID (or derived) and must not have a {} value constraint",
6531 elem_name(), constraint
6532 ),
6533 location(),
6534 ));
6535 }
6536 }
6537 }
6538
6539 let effective_type = match type_key {
6541 TypeKey::Simple(_) => Some(type_key),
6542 TypeKey::Complex(ck) => schema_set
6543 .arenas
6544 .complex_types
6545 .get(ck)
6546 .and_then(|ct| effective_simple_content_type_key(schema_set, ct)),
6547 };
6548
6549 if let Some(st_key) = effective_type {
6550 if crate::validation::simple::validate_simple_type(value, st_key, schema_set).is_err() {
6551 return Err(SchemaError::structural(
6552 "e-props-correct.2",
6553 format!(
6554 "Element '{}' {} value '{}' is not valid for its declared type",
6555 elem_name(),
6556 constraint,
6557 value
6558 ),
6559 location(),
6560 ));
6561 }
6562 }
6563 }
6564
6565 Ok(())
6566}
6567
6568#[cfg(feature = "xsd11")]
6576pub fn validate_element_type_alternatives(schema_set: &SchemaSet) -> SchemaResult<()> {
6577 if !schema_set.is_xsd11() {
6578 return Ok(());
6579 }
6580 for (_key, elem) in schema_set.arenas.elements.iter() {
6581 let alts = &elem.alternatives;
6582 if alts.len() < 2 {
6583 continue;
6584 }
6585 for alt in &alts[..alts.len() - 1] {
6586 if alt.test.is_none() {
6587 let name = elem
6588 .name
6589 .map(|n| schema_set.name_table.resolve_ref(n))
6590 .unwrap_or("(anonymous)");
6591 let location = schema_set.locate(elem.source.as_ref());
6592 return Err(SchemaError::structural(
6593 "src-type-alternative",
6594 format!(
6595 "Element '{}': <xs:alternative> without a 'test' attribute is only \
6596 permitted as the last alternative",
6597 name
6598 ),
6599 location,
6600 ));
6601 }
6602 }
6603 }
6604 Ok(())
6605}
6606
6607pub fn validate_complex_type_attribute_uniqueness(schema_set: &SchemaSet) -> SchemaResult<()> {
6622 use std::collections::{HashMap, HashSet};
6623
6624 #[derive(Hash, PartialEq, Eq, Clone, Copy)]
6632 enum AttrDeclId {
6633 GlobalRef(AttributeKey),
6634 InlineGroup(AttributeGroupKey, usize),
6635 InlineComplex(ComplexTypeKey, usize),
6636 }
6637
6638 fn walk_attribute_group(
6639 schema_set: &SchemaSet,
6640 ag_key: AttributeGroupKey,
6641 visiting_groups: &mut HashSet<AttributeGroupKey>,
6642 seen: &mut HashSet<AttrDeclId>,
6643 out: &mut Vec<EffectiveAttributeUse>,
6644 ) {
6645 if !visiting_groups.insert(ag_key) {
6646 return;
6647 }
6648 let Some(ag) = schema_set.arenas.attribute_groups.get(ag_key) else {
6649 visiting_groups.remove(&ag_key);
6650 return;
6651 };
6652 if let Some(ref_key) = ag.resolved_ref {
6653 walk_attribute_group(schema_set, ref_key, visiting_groups, seen, out);
6654 visiting_groups.remove(&ag_key);
6655 return;
6656 }
6657
6658 for (i, attr_use) in ag.attributes.iter().enumerate() {
6659 let resolved = ag.resolved_attributes.get(i);
6660 let decl_id = if let Some(global_key) = resolved.and_then(|r| r.resolved_ref) {
6661 AttrDeclId::GlobalRef(global_key)
6662 } else {
6663 AttrDeclId::InlineGroup(ag_key, i)
6664 };
6665 if seen.insert(decl_id) {
6666 if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
6667 out.push(eau);
6668 }
6669 }
6670 }
6671 for &nested in &ag.resolved_attribute_groups {
6672 walk_attribute_group(schema_set, nested, visiting_groups, seen, out);
6673 }
6674
6675 visiting_groups.remove(&ag_key);
6676 }
6677
6678 fn collect_with_dedup(
6679 schema_set: &SchemaSet,
6680 type_def: &crate::arenas::ComplexTypeDefData,
6681 ct_key: ComplexTypeKey,
6682 depth: usize,
6683 visiting_groups: &mut HashSet<AttributeGroupKey>,
6684 seen: &mut HashSet<AttrDeclId>,
6685 out: &mut Vec<EffectiveAttributeUse>,
6686 ) {
6687 if depth > 50 {
6688 return;
6689 }
6690 for (i, attr_use) in type_def.attributes.iter().enumerate() {
6691 let resolved = type_def.resolved_attributes.get(i);
6692 let decl_id = if let Some(global_key) = resolved.and_then(|r| r.resolved_ref) {
6693 AttrDeclId::GlobalRef(global_key)
6694 } else {
6695 AttrDeclId::InlineComplex(ct_key, i)
6696 };
6697 if seen.insert(decl_id) {
6698 if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
6699 out.push(eau);
6700 }
6701 }
6702 }
6703 for &ag_key in &type_def.resolved_attribute_groups {
6704 visiting_groups.clear();
6705 walk_attribute_group(schema_set, ag_key, visiting_groups, seen, out);
6706 }
6707 if type_def.derivation_method == Some(DerivationMethod::Extension) {
6708 if let Some(TypeKey::Complex(base_key)) = type_def.resolved_base_type {
6709 if let Some(base) = schema_set.arenas.complex_types.get(base_key) {
6710 collect_with_dedup(
6711 schema_set,
6712 base,
6713 base_key,
6714 depth + 1,
6715 visiting_groups,
6716 seen,
6717 out,
6718 );
6719 }
6720 }
6721 }
6722 }
6723
6724 let mut seen: HashSet<AttrDeclId> = HashSet::new();
6727 let mut attrs: Vec<EffectiveAttributeUse> = Vec::new();
6728 let mut visiting_groups: HashSet<AttributeGroupKey> = HashSet::new();
6729 let mut by_name: HashMap<(Option<NameId>, NameId), ()> = HashMap::new();
6730
6731 let id_key_for_xsd10 = if schema_set.is_xsd10() {
6735 schema_set
6736 .builtin_types()
6737 .get_by_type_code(crate::types::XmlTypeCode::Id)
6738 } else {
6739 None
6740 };
6741
6742 for (key, type_def) in schema_set.arenas.complex_types.iter() {
6743 seen.clear();
6744 attrs.clear();
6745 by_name.clear();
6746 collect_with_dedup(
6747 schema_set,
6748 type_def,
6749 key,
6750 0,
6751 &mut visiting_groups,
6752 &mut seen,
6753 &mut attrs,
6754 );
6755
6756 attrs.retain(|eau| eau.use_kind != AttributeUseKind::Prohibited);
6760
6761 for attr in &attrs {
6762 if by_name
6763 .insert((attr.target_namespace, attr.name), ())
6764 .is_some()
6765 {
6766 let attr_name_str = schema_set.name_table.resolve(attr.name);
6767 let type_name =
6768 format_type_name(schema_set, type_def.name, type_def.target_namespace);
6769 let location = type_def
6770 .source
6771 .as_ref()
6772 .and_then(|s| schema_set.source_maps.locate(s));
6773 return Err(SchemaError::structural(
6774 "ct-props-correct",
6775 format!(
6776 "Complex type '{}': two distinct attribute declarations \
6777 with the same expanded name '{}' (ct-props-correct \
6778 clause 4 / ag-props-correct clause 2)",
6779 type_name, attr_name_str,
6780 ),
6781 location,
6782 ));
6783 }
6784 }
6785
6786 if let Some(id_key) = id_key_for_xsd10 {
6792 let mut id_attrs = attrs.iter().filter(|attr| match attr.resolved_type {
6793 Some(TypeKey::Simple(st_key)) => schema_set.derives_from(st_key, id_key),
6794 _ => false,
6795 });
6796 if let (Some(first), Some(second)) = (id_attrs.next(), id_attrs.next()) {
6797 let first_name = schema_set.name_table.resolve(first.name);
6798 let second_name = schema_set.name_table.resolve(second.name);
6799 let type_name =
6800 format_type_name(schema_set, type_def.name, type_def.target_namespace);
6801 let location = type_def
6802 .source
6803 .as_ref()
6804 .and_then(|s| schema_set.source_maps.locate(s));
6805 return Err(SchemaError::structural(
6806 "ct-props-correct",
6807 format!(
6808 "Complex type '{}': attributes '{}' and '{}' both have \
6809 xs:ID-derived types (ct-props-correct clause 4; XSD 1.0 only)",
6810 type_name, first_name, second_name,
6811 ),
6812 location,
6813 ));
6814 }
6815 }
6816 }
6817 Ok(())
6818}
6819
6820#[cfg(feature = "xsd11")]
6836pub fn validate_wildcard_disallowed_names(schema_set: &SchemaSet) -> SchemaResult<()> {
6837 if !schema_set.is_xsd11() {
6838 return Ok(());
6839 }
6840
6841 fn check_wildcard(
6842 schema_set: &SchemaSet,
6843 wc: &WildcardResult,
6844 target_ns: Option<NameId>,
6845 ) -> SchemaResult<()> {
6846 use crate::parser::frames::NotQNameItem;
6847
6848 for item in &wc.not_qname {
6849 let NotQNameItem::QName {
6850 namespace: q_ns,
6851 local_name,
6852 } = item
6853 else {
6854 continue;
6855 };
6856 let admitted_by_constraint =
6860 wildcard_namespace_matches(&wc.namespace, *q_ns, target_ns);
6861 let excluded_by_not_namespace = wc
6862 .not_namespace
6863 .iter()
6864 .any(|t| t.resolve(target_ns) == *q_ns);
6865 if !admitted_by_constraint || excluded_by_not_namespace {
6866 let location = schema_set.locate(wc.source.as_ref());
6867 let qname_text = match q_ns {
6868 Some(ns) => format!(
6869 "{{{}}}:{}",
6870 schema_set.name_table.resolve_ref(*ns),
6871 schema_set.name_table.resolve_ref(*local_name),
6872 ),
6873 None => schema_set.name_table.resolve_ref(*local_name).to_string(),
6874 };
6875 return Err(SchemaError::structural(
6876 "w-props-correct",
6877 format!(
6878 "notQName entry '{}' is not admitted by the wildcard's \
6879 namespace constraint (§3.10.6.1 rule 4)",
6880 qname_text
6881 ),
6882 location,
6883 ));
6884 }
6885 }
6886 Ok(())
6887 }
6888
6889 fn check_particle(
6890 schema_set: &SchemaSet,
6891 particle: &ParticleResult,
6892 target_ns: Option<NameId>,
6893 depth: usize,
6894 ) -> SchemaResult<()> {
6895 if depth > 100 {
6896 return Ok(());
6897 }
6898 match &particle.term {
6899 ParticleTerm::Any(wc) => check_wildcard(schema_set, wc, target_ns)?,
6900 ParticleTerm::Group(group) => {
6901 for child in &group.particles {
6902 check_particle(schema_set, child, target_ns, depth + 1)?;
6903 }
6904 }
6905 ParticleTerm::Element(_) => {}
6906 }
6907 Ok(())
6908 }
6909
6910 for (_key, ct) in schema_set.arenas.complex_types.iter() {
6914 let target_ns = ct.target_namespace;
6915 if let Some(wc) = ct.attribute_wildcard.as_ref() {
6916 check_wildcard(schema_set, wc, target_ns)?;
6917 }
6918 match &ct.content {
6919 ComplexContentResult::Empty => {}
6920 ComplexContentResult::Simple(sc) => {
6921 if let Some(wc) = sc.attribute_wildcard.as_ref() {
6922 check_wildcard(schema_set, wc, target_ns)?;
6923 }
6924 }
6925 ComplexContentResult::Complex(cc) => {
6926 if let Some(wc) = cc.attribute_wildcard.as_ref() {
6927 check_wildcard(schema_set, wc, target_ns)?;
6928 }
6929 if let Some(p) = cc.particle.as_ref() {
6930 check_particle(schema_set, p, target_ns, 0)?;
6931 }
6932 if let Some(oc) = cc.open_content.as_ref() {
6933 if let Some(wc) = oc.wildcard.as_ref() {
6934 check_wildcard(schema_set, wc, target_ns)?;
6935 }
6936 }
6937 }
6938 }
6939 if let Some(oc) = ct.open_content.as_ref() {
6940 if let Some(wc) = oc.wildcard.as_ref() {
6941 check_wildcard(schema_set, wc, target_ns)?;
6942 }
6943 }
6944 }
6945
6946 for (_key, ag) in schema_set.arenas.attribute_groups.iter() {
6948 if let Some(wc) = ag.attribute_wildcard.as_ref() {
6949 check_wildcard(schema_set, wc, ag.target_namespace)?;
6950 }
6951 }
6952
6953 for (_key, mg) in schema_set.arenas.model_groups.iter() {
6955 for child in &mg.particles {
6956 check_particle(schema_set, child, mg.target_namespace, 0)?;
6957 }
6958 }
6959
6960 Ok(())
6961}
6962
6963#[cfg(feature = "xsd11")]
6973pub fn validate_wildcard_element_type_table_consistency(
6974 schema_set: &SchemaSet,
6975) -> SchemaResult<()> {
6976 use crate::parser::frames::AlternativeResult;
6977
6978 if !schema_set.is_xsd11() {
6979 return Ok(());
6980 }
6981
6982 #[allow(clippy::too_many_arguments)]
6987 fn walk_collect<'a>(
6988 particle: &'a ParticleResult,
6989 target_ns: Option<NameId>,
6990 schema_set: &'a SchemaSet,
6991 local_keys: &[Option<ElementKey>],
6992 flat_idx: &mut usize,
6993 local_elems: &mut Vec<(Option<NameId>, NameId, ElementKey, Option<SourceRef>)>,
6994 wildcards: &mut Vec<&'a WildcardResult>,
6995 depth: usize,
6996 ) {
6997 if depth > 100 {
6998 return;
6999 }
7000 match &particle.term {
7001 ParticleTerm::Element(elem) => {
7002 if let Some(ref_qn) = &elem.ref_name {
7003 *flat_idx += 1;
7005 let _ = ref_qn;
7006 } else if let Some(name) = elem.name {
7007 let ns = elem.target_namespace.or(target_ns);
7008 let idx = *flat_idx;
7009 *flat_idx += 1;
7010 if let Some(key) = local_keys.get(idx).copied().flatten() {
7011 local_elems.push((ns, name, key, elem.source.clone()));
7012 }
7013 }
7014 }
7015 ParticleTerm::Any(wc) => {
7016 wildcards.push(wc);
7017 }
7018 ParticleTerm::Group(group) => {
7019 if let Some(ref_qn) = &group.ref_name {
7020 if let Some(group_key) =
7021 schema_set.lookup_model_group(ref_qn.namespace, ref_qn.local_name)
7022 {
7023 let mg = &schema_set.arenas.model_groups[group_key];
7024 let mg_ns = mg.target_namespace.or(target_ns);
7025 let mut group_flat_idx = 0usize;
7026 for child in &mg.particles {
7027 walk_collect(
7028 child,
7029 mg_ns,
7030 schema_set,
7031 &mg.resolved_particle_elements,
7032 &mut group_flat_idx,
7033 local_elems,
7034 wildcards,
7035 depth + 1,
7036 );
7037 }
7038 }
7039 } else {
7042 for child in &group.particles {
7043 walk_collect(
7044 child,
7045 target_ns,
7046 schema_set,
7047 local_keys,
7048 flat_idx,
7049 local_elems,
7050 wildcards,
7051 depth + 1,
7052 );
7053 }
7054 }
7055 }
7056 }
7057 }
7058
7059 fn alt_effective_type(alt: &AlternativeResult, schema_set: &SchemaSet) -> Option<TypeKey> {
7064 use crate::parser::frames::TypeRefResult;
7065 if let Some(t) = alt.resolved_type {
7066 return Some(t);
7067 }
7068 if let Some(TypeRefResult::QName(qname)) = &alt.type_ref {
7069 return schema_set
7070 .lookup_type(qname.namespace, qname.local_name)
7071 .or_else(|| {
7072 schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name)
7073 });
7074 }
7075 None
7076 }
7077
7078 fn alternatives_equivalent(
7079 a: &[AlternativeResult],
7080 b: &[AlternativeResult],
7081 schema_set: &SchemaSet,
7082 ) -> bool {
7083 if a.len() != b.len() {
7084 return false;
7085 }
7086 for (x, y) in a.iter().zip(b.iter()) {
7087 if x.test != y.test {
7088 return false;
7089 }
7090 if alt_effective_type(x, schema_set) != alt_effective_type(y, schema_set) {
7091 return false;
7092 }
7093 }
7094 true
7095 }
7096
7097 for (_key, ct) in schema_set.arenas.complex_types.iter() {
7098 let target_ns = ct.target_namespace;
7099 let ComplexContentResult::Complex(cc) = &ct.content else {
7100 continue;
7101 };
7102 let Some(particle) = cc.particle.as_ref() else {
7103 continue;
7104 };
7105
7106 let mut local_elems: Vec<(Option<NameId>, NameId, ElementKey, Option<SourceRef>)> =
7107 Vec::new();
7108 let mut wildcards: Vec<&WildcardResult> = Vec::new();
7109 let mut flat_idx = 0usize;
7110 walk_collect(
7111 particle,
7112 target_ns,
7113 schema_set,
7114 &ct.resolved_content_particle_elements,
7115 &mut flat_idx,
7116 &mut local_elems,
7117 &mut wildcards,
7118 0,
7119 );
7120
7121 if let Some(oc) = cc.open_content.as_ref() {
7123 if let Some(wc) = oc.wildcard.as_ref() {
7124 wildcards.push(wc);
7125 }
7126 }
7127
7128 if wildcards.is_empty() {
7129 continue;
7130 }
7131
7132 for (l_ns, l_name, l_key, l_source) in &local_elems {
7133 let global_key = schema_set.lookup_element(*l_ns, *l_name);
7134 let Some(g_key) = global_key else {
7135 continue;
7136 };
7137 if *l_key == g_key {
7141 continue;
7142 }
7143 let l_decl = &schema_set.arenas.elements[*l_key];
7144 let g_decl = &schema_set.arenas.elements[g_key];
7145
7146 let admitted = wildcards.iter().any(|wc| {
7149 if matches!(wc.process_contents, ProcessContents::Skip) {
7150 return false;
7151 }
7152 wildcard_result_admits_qname(wc, target_ns, *l_ns, *l_name)
7153 });
7154 if !admitted {
7155 continue;
7156 }
7157
7158 if !alternatives_equivalent(&l_decl.alternatives, &g_decl.alternatives, schema_set) {
7159 let location = schema_set
7160 .locate(l_source.as_ref())
7161 .or_else(|| schema_set.locate(ct.source.as_ref()));
7162 let qname = match l_ns {
7163 Some(ns) => format!(
7164 "{{{}}}:{}",
7165 schema_set.name_table.resolve_ref(*ns),
7166 schema_set.name_table.resolve_ref(*l_name),
7167 ),
7168 None => schema_set.name_table.resolve_ref(*l_name).to_string(),
7169 };
7170 return Err(SchemaError::structural(
7171 "cos-element-consistent",
7172 format!(
7173 "Local element '{}' is in the same content model as a strict/lax \
7174 wildcard that admits its expanded name; the local element's type \
7175 table is not equivalent to that of the top-level declaration of \
7176 the same name (§3.8.6.3 / cos-element-consistent)",
7177 qname
7178 ),
7179 location,
7180 ));
7181 }
7182 }
7183 }
7184
7185 Ok(())
7186}
7187
7188#[cfg(feature = "xsd11")]
7192type LocalElementEntry = (Option<NameId>, NameId, ElementKey, Option<SourceRef>);
7193
7194#[cfg(feature = "xsd11")]
7203fn walk_collect_local_elements(
7204 particle: &ParticleResult,
7205 target_ns: Option<NameId>,
7206 local_keys: &[Option<ElementKey>],
7207 flat_idx: &mut usize,
7208 out: &mut Vec<LocalElementEntry>,
7209) {
7210 if let ParticleTerm::Group(group) = &particle.term {
7211 walk_group_local_elements(&group.particles, target_ns, local_keys, flat_idx, out);
7212 }
7213}
7214
7215#[cfg(feature = "xsd11")]
7216fn walk_group_local_elements(
7217 particles: &[ParticleResult],
7218 target_ns: Option<NameId>,
7219 local_keys: &[Option<ElementKey>],
7220 flat_idx: &mut usize,
7221 out: &mut Vec<LocalElementEntry>,
7222) {
7223 for p in particles {
7224 match &p.term {
7225 ParticleTerm::Element(elem) if elem.ref_name.is_none() => {
7226 if let Some(Some(elem_key)) = local_keys.get(*flat_idx) {
7227 let ns = elem.target_namespace.or(target_ns);
7228 if let Some(name) = elem.name {
7229 out.push((ns, name, *elem_key, elem.source.clone()));
7230 }
7231 }
7232 *flat_idx += 1;
7233 }
7234 ParticleTerm::Element(_) => {
7235 *flat_idx += 1;
7236 }
7237 ParticleTerm::Group(group) if group.ref_name.is_none() => {
7238 walk_group_local_elements(&group.particles, target_ns, local_keys, flat_idx, out);
7239 }
7240 _ => {}
7241 }
7242 }
7243}
7244
7245#[cfg(feature = "xsd11")]
7247fn collect_local_elements(ct: &crate::arenas::ComplexTypeDefData) -> Vec<LocalElementEntry> {
7248 let mut out = Vec::new();
7249 let ComplexContentResult::Complex(cc) = &ct.content else {
7250 return out;
7251 };
7252 let Some(particle) = cc.particle.as_ref() else {
7253 return out;
7254 };
7255 let mut flat_idx = 0usize;
7256 walk_collect_local_elements(
7257 particle,
7258 ct.target_namespace,
7259 &ct.resolved_content_particle_elements,
7260 &mut flat_idx,
7261 &mut out,
7262 );
7263 out
7264}
7265
7266#[cfg(feature = "xsd11")]
7270fn alt_effective_type(
7271 alt: &crate::parser::frames::AlternativeResult,
7272 schema_set: &SchemaSet,
7273) -> Option<TypeKey> {
7274 use crate::parser::frames::TypeRefResult;
7275 if let Some(t) = alt.resolved_type {
7276 return Some(t);
7277 }
7278 if let Some(TypeRefResult::QName(qname)) = &alt.type_ref {
7279 return schema_set
7280 .lookup_type(qname.namespace, qname.local_name)
7281 .or_else(|| schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name));
7282 }
7283 None
7284}
7285
7286#[cfg(feature = "xsd11")]
7289fn alternatives_equivalent(
7290 a: &[crate::parser::frames::AlternativeResult],
7291 b: &[crate::parser::frames::AlternativeResult],
7292 schema_set: &SchemaSet,
7293) -> bool {
7294 if a.len() != b.len() {
7295 return false;
7296 }
7297 a.iter().zip(b.iter()).all(|(x, y)| {
7298 x.test == y.test && alt_effective_type(x, schema_set) == alt_effective_type(y, schema_set)
7299 })
7300}
7301
7302#[cfg(feature = "xsd11")]
7306pub fn validate_local_element_type_table_consistency(schema_set: &SchemaSet) -> SchemaResult<()> {
7307 use std::collections::HashMap;
7308
7309 if !schema_set.is_xsd11() {
7310 return Ok(());
7311 }
7312
7313 for (_key, ct) in schema_set.arenas.complex_types.iter() {
7314 let local_elems = collect_local_elements(ct);
7315 if local_elems.len() < 2 {
7316 continue;
7317 }
7318
7319 let mut by_name: HashMap<(Option<NameId>, NameId), Vec<usize>> = HashMap::new();
7320 for (i, (ns, name, _, _)) in local_elems.iter().enumerate() {
7321 by_name.entry((*ns, *name)).or_default().push(i);
7322 }
7323
7324 for (qname, indices) in &by_name {
7325 if indices.len() < 2 {
7326 continue;
7327 }
7328 let first_idx = indices[0];
7329 let first_decl = &schema_set.arenas.elements[local_elems[first_idx].2];
7330 for &idx in &indices[1..] {
7331 let other_decl = &schema_set.arenas.elements[local_elems[idx].2];
7332 if alternatives_equivalent(
7333 &first_decl.alternatives,
7334 &other_decl.alternatives,
7335 schema_set,
7336 ) {
7337 continue;
7338 }
7339 let qn_str = match qname.0 {
7340 Some(ns) => format!(
7341 "{{{}}}{}",
7342 schema_set.name_table.resolve_ref(ns),
7343 schema_set.name_table.resolve_ref(qname.1),
7344 ),
7345 None => schema_set.name_table.resolve_ref(qname.1).to_string(),
7346 };
7347 let location = schema_set
7348 .locate(local_elems[idx].3.as_ref())
7349 .or_else(|| schema_set.locate(ct.source.as_ref()));
7350 return Err(SchemaError::structural(
7351 "cos-element-consistent",
7352 format!(
7353 "Two local element declarations of '{}' appear in the same \
7354 content model but their type tables are not equivalent \
7355 (§3.8.6.3 / cos-element-consistent)",
7356 qn_str
7357 ),
7358 location,
7359 ));
7360 }
7361 }
7362 }
7363
7364 Ok(())
7365}
7366
7367#[cfg(feature = "xsd11")]
7375pub fn validate_restriction_local_element_type_table_consistency(
7376 schema_set: &SchemaSet,
7377) -> SchemaResult<()> {
7378 use std::collections::HashMap;
7379
7380 if !schema_set.is_xsd11() {
7381 return Ok(());
7382 }
7383
7384 for (_key, ct) in schema_set.arenas.complex_types.iter() {
7385 if ct.derivation_method != Some(crate::parser::frames::DerivationMethod::Restriction) {
7389 continue;
7390 }
7391 let Some(TypeKey::Complex(base_ck)) = ct.resolved_base_type else {
7392 continue;
7393 };
7394 let Some(base_ct) = schema_set.arenas.complex_types.get(base_ck) else {
7395 continue;
7396 };
7397
7398 let derived_locals = collect_local_elements(ct);
7399 if derived_locals.is_empty() {
7400 continue;
7401 }
7402 let base_locals = collect_local_elements(base_ct);
7403 if base_locals.is_empty() {
7404 continue;
7405 }
7406
7407 let mut base_by_name: HashMap<(Option<NameId>, NameId), ElementKey> = HashMap::new();
7410 for (ns, name, ek, _) in &base_locals {
7411 base_by_name.entry((*ns, *name)).or_insert(*ek);
7412 }
7413
7414 for (ns, name, derived_ek, derived_src) in &derived_locals {
7415 let Some(&base_ek) = base_by_name.get(&(*ns, *name)) else {
7416 continue;
7417 };
7418 let derived_decl = &schema_set.arenas.elements[*derived_ek];
7419 let base_decl = &schema_set.arenas.elements[base_ek];
7420 if alternatives_equivalent(
7421 &derived_decl.alternatives,
7422 &base_decl.alternatives,
7423 schema_set,
7424 ) {
7425 continue;
7426 }
7427 let qn_str = match ns {
7428 Some(ns_id) => format!(
7429 "{{{}}}{}",
7430 schema_set.name_table.resolve_ref(*ns_id),
7431 schema_set.name_table.resolve_ref(*name),
7432 ),
7433 None => schema_set.name_table.resolve_ref(*name).to_string(),
7434 };
7435 let location = schema_set
7436 .locate(derived_src.as_ref())
7437 .or_else(|| schema_set.locate(ct.source.as_ref()));
7438 let derived_name = format_type_name(schema_set, ct.name, ct.target_namespace);
7439 let base_name = format_type_name(schema_set, base_ct.name, base_ct.target_namespace);
7440 return Err(SchemaError::structural(
7441 "cos-element-consistent",
7442 format!(
7443 "Complex type '{}' restricting '{}': local element '{}' has a \
7444 type table that is not equivalent to the base type's local \
7445 element of the same name (§3.4.6.3 / cos-element-consistent)",
7446 derived_name, base_name, qn_str,
7447 ),
7448 location,
7449 ));
7450 }
7451 }
7452
7453 Ok(())
7454}
7455
7456#[cfg(feature = "xsd11")]
7460fn wildcard_result_admits_qname(
7461 wc: &WildcardResult,
7462 target_ns: Option<NameId>,
7463 ns: Option<NameId>,
7464 name: NameId,
7465) -> bool {
7466 use crate::parser::frames::NotQNameItem;
7467 if !wildcard_namespace_matches(&wc.namespace, ns, target_ns) {
7468 return false;
7469 }
7470 if wc.not_namespace.iter().any(|t| t.resolve(target_ns) == ns) {
7471 return false;
7472 }
7473 !wc.not_qname.iter().any(|item| match item {
7474 NotQNameItem::QName {
7475 namespace,
7476 local_name,
7477 } => *namespace == ns && *local_name == name,
7478 NotQNameItem::Defined | NotQNameItem::DefinedSibling => true,
7479 })
7480}
7481
7482pub fn validate_xsd10_annotation_source_anyuri(schema_set: &SchemaSet) -> SchemaResult<()> {
7500 use crate::schema::annotation::{Annotation, AnnotationItem};
7501 use crate::types::validators::is_strict_xsd10_anyuri;
7502
7503 if !schema_set.is_xsd10() {
7504 return Ok(());
7505 }
7506
7507 fn check_annotation(
7508 schema_set: &SchemaSet,
7509 annotation: Option<&Annotation>,
7510 ) -> SchemaResult<()> {
7511 let Some(annotation) = annotation else {
7512 return Ok(());
7513 };
7514 for item in &annotation.items {
7515 match item {
7516 AnnotationItem::AppInfo(ai) => {
7517 if let Some(ref src) = ai.source {
7518 if !is_strict_xsd10_anyuri(src) {
7519 let location = ai
7520 .source_ref
7521 .as_ref()
7522 .and_then(|s| schema_set.source_maps.locate(s));
7523 return Err(SchemaError::structural(
7524 "cvc-datatype-valid",
7525 format!(
7526 "<xs:appinfo source=\"{}\"> is not a valid xs:anyURI \
7527 (XSD 1.0 strict scheme syntax)",
7528 src
7529 ),
7530 location,
7531 ));
7532 }
7533 }
7534 }
7535 AnnotationItem::Documentation(d) => {
7536 if let Some(ref src) = d.source {
7537 if !is_strict_xsd10_anyuri(src) {
7538 let location = d
7539 .source_ref
7540 .as_ref()
7541 .and_then(|s| schema_set.source_maps.locate(s));
7542 return Err(SchemaError::structural(
7543 "cvc-datatype-valid",
7544 format!(
7545 "<xs:documentation source=\"{}\"> is not a valid \
7546 xs:anyURI (XSD 1.0 strict scheme syntax)",
7547 src
7548 ),
7549 location,
7550 ));
7551 }
7552 }
7553 }
7554 }
7555 }
7556 Ok(())
7557 }
7558
7559 for (_k, ct) in schema_set.arenas.complex_types.iter() {
7562 check_annotation(schema_set, ct.annotation.as_ref())?;
7563 }
7564 for (_k, st) in schema_set.arenas.simple_types.iter() {
7565 check_annotation(schema_set, st.annotation.as_ref())?;
7566 }
7567 for (_k, el) in schema_set.arenas.elements.iter() {
7568 check_annotation(schema_set, el.annotation.as_ref())?;
7569 }
7570 for (_k, at) in schema_set.arenas.attributes.iter() {
7571 check_annotation(schema_set, at.annotation.as_ref())?;
7572 }
7573 for (_k, ag) in schema_set.arenas.attribute_groups.iter() {
7574 check_annotation(schema_set, ag.annotation.as_ref())?;
7575 }
7576 for (_k, mg) in schema_set.arenas.model_groups.iter() {
7577 check_annotation(schema_set, mg.annotation.as_ref())?;
7578 }
7579 for (_k, n) in schema_set.arenas.notations.iter() {
7580 check_annotation(schema_set, n.annotation.as_ref())?;
7581 }
7582 for (_k, ic) in schema_set.arenas.identity_constraints.iter() {
7583 check_annotation(schema_set, ic.annotation.as_ref())?;
7584 }
7585 for doc in &schema_set.documents {
7588 for ann in &doc.annotations {
7589 check_annotation(schema_set, Some(ann))?;
7590 }
7591 }
7592 Ok(())
7593}
7594
7595pub fn validate_no_xsi_attribute_declarations(schema_set: &SchemaSet) -> SchemaResult<()> {
7602 use crate::namespace::table::well_known;
7603
7604 for (_key, attr) in schema_set.arenas.attributes.iter() {
7605 if attr.source.is_none() {
7606 continue;
7610 }
7611 let Some(ns) = attr.target_namespace else {
7612 continue;
7613 };
7614 if ns != well_known::XSI_NAMESPACE {
7615 continue;
7616 }
7617 let attr_name = attr
7618 .name
7619 .map(|n| schema_set.name_table.resolve_ref(n).to_string())
7620 .unwrap_or_else(|| "(anonymous)".to_string());
7621 let location = schema_set.locate(attr.source.as_ref());
7622 return Err(SchemaError::structural(
7623 "no-xsi",
7624 format!(
7625 "Attribute declaration '{}' has target namespace \
7626 'http://www.w3.org/2001/XMLSchema-instance', which is \
7627 reserved (no-xsi, §3.2.6.4)",
7628 attr_name
7629 ),
7630 location,
7631 ));
7632 }
7633
7634 let any_xsi_owner = schema_set
7648 .documents
7649 .iter()
7650 .any(|d| d.target_namespace == Some(well_known::XSI_NAMESPACE));
7651 let owners_to_scan = any_xsi_owner;
7652 for (_key, group) in schema_set.arenas.attribute_groups.iter() {
7653 check_no_xsi_in_attribute_uses(
7654 schema_set,
7655 &group.attributes,
7656 group.target_namespace,
7657 owners_to_scan,
7658 )?;
7659 }
7660 for (_key, ct) in schema_set.arenas.complex_types.iter() {
7661 check_no_xsi_in_attribute_uses(
7662 schema_set,
7663 &ct.attributes,
7664 ct.target_namespace,
7665 owners_to_scan,
7666 )?;
7667 }
7668 Ok(())
7669}
7670
7671fn check_no_xsi_in_attribute_uses(
7675 schema_set: &SchemaSet,
7676 attrs: &[crate::parser::frames::AttributeUseResult],
7677 owner_target_namespace: Option<NameId>,
7678 owner_could_be_xsi: bool,
7679) -> SchemaResult<()> {
7680 use crate::namespace::table::well_known;
7681
7682 for attr_use in attrs {
7683 if attr_use.attribute.ref_name.is_some() {
7684 continue;
7685 }
7686 let explicit_xsi = attr_use.attribute.target_namespace == Some(well_known::XSI_NAMESPACE);
7690 if !explicit_xsi && !owner_could_be_xsi {
7691 continue;
7692 }
7693 let effective_ns = schema_set.effective_local_attribute_namespace(
7694 attr_use.attribute.target_namespace,
7695 attr_use.attribute.form.as_deref(),
7696 attr_use.attribute.source.as_ref(),
7697 owner_target_namespace,
7698 );
7699 if effective_ns != Some(well_known::XSI_NAMESPACE) {
7700 continue;
7701 }
7702 let attr_name = attr_use
7703 .attribute
7704 .name
7705 .map(|n| schema_set.name_table.resolve_ref(n).to_string())
7706 .unwrap_or_else(|| "(anonymous)".to_string());
7707 let location = schema_set.locate(attr_use.attribute.source.as_ref());
7708 return Err(SchemaError::structural(
7709 "no-xsi",
7710 format!(
7711 "Attribute declaration '{}' has target namespace \
7712 'http://www.w3.org/2001/XMLSchema-instance', which is \
7713 reserved (no-xsi, §3.2.6.4)",
7714 attr_name
7715 ),
7716 location,
7717 ));
7718 }
7719 Ok(())
7720}
7721
7722pub fn validate_local_decl_target_namespace(schema_set: &SchemaSet) -> SchemaResult<()> {
7740 use crate::parser::frames::{ComplexContentResult, ParticleResult, ParticleTerm};
7741
7742 fn find_divergent_local_element<'a>(
7743 schema_set: &'a SchemaSet,
7744 particle: &'a ParticleResult,
7745 schema_tns: Option<NameId>,
7746 depth: usize,
7747 ) -> Option<(Option<SourceRef>, String)> {
7748 if depth > 100 {
7749 return None;
7750 }
7751 match &particle.term {
7752 ParticleTerm::Element(elem) => {
7753 if elem.ref_name.is_some() {
7754 return None;
7755 }
7756 if let Some(ns) = elem.target_namespace {
7757 if Some(ns) != schema_tns {
7758 let name_str = elem
7759 .name
7760 .map(|n| schema_set.name_table.resolve_ref(n).to_string())
7761 .unwrap_or_default();
7762 return Some((elem.source.clone(), name_str));
7763 }
7764 }
7765 }
7766 ParticleTerm::Group(group) => {
7767 if group.ref_name.is_none() {
7771 for child in &group.particles {
7772 if let Some(found) =
7773 find_divergent_local_element(schema_set, child, schema_tns, depth + 1)
7774 {
7775 return Some(found);
7776 }
7777 }
7778 }
7779 }
7780 ParticleTerm::Any(_) => {}
7781 }
7782 None
7783 }
7784
7785 for (_, ct) in schema_set.arenas.complex_types.iter() {
7786 let schema_tns = ct.target_namespace;
7787 let restriction_of_non_any = match (ct.derivation_method, ct.resolved_base_type) {
7788 (Some(crate::parser::frames::DerivationMethod::Restriction), Some(base_key)) => {
7789 !schema_set.is_any_type(base_key)
7790 }
7791 _ => false,
7792 };
7793 if restriction_of_non_any {
7794 continue;
7795 }
7796
7797 let particle_opt = match &ct.content {
7800 ComplexContentResult::Complex(def) => def.particle.as_ref(),
7801 _ => None,
7802 };
7803 if let Some(particle) = particle_opt {
7804 if let Some((src, name)) =
7805 find_divergent_local_element(schema_set, particle, schema_tns, 0)
7806 {
7807 let location = schema_set
7808 .locate(src.as_ref())
7809 .or_else(|| schema_set.locate(ct.source.as_ref()));
7810 return Err(SchemaError::structural(
7811 "src-element",
7812 format!(
7813 "Local element '{}' has an explicit targetNamespace differing from the \
7814 schema's, but is not inside a <restriction> of a non-anyType base \
7815 (src-element §3.3.3 clause 4.3)",
7816 name
7817 ),
7818 location,
7819 ));
7820 }
7821 }
7822
7823 for au in &ct.attributes {
7825 let attr = &au.attribute;
7826 if attr.ref_name.is_some() {
7827 continue;
7828 }
7829 let Some(ns) = attr.target_namespace else {
7830 continue;
7831 };
7832 if Some(ns) == schema_tns {
7833 continue;
7834 }
7835 let name = attr
7836 .name
7837 .map(|n| schema_set.name_table.resolve_ref(n).to_string())
7838 .unwrap_or_default();
7839 let location = schema_set
7840 .locate(attr.source.as_ref())
7841 .or_else(|| schema_set.locate(ct.source.as_ref()));
7842 return Err(SchemaError::structural(
7843 "src-attribute",
7844 format!(
7845 "Local attribute '{}' has an explicit targetNamespace differing from the \
7846 schema's, but is not inside a <restriction> of a non-anyType base \
7847 (src-attribute §3.2.3 clause 6.3)",
7848 name
7849 ),
7850 location,
7851 ));
7852 }
7853 }
7854 Ok(())
7855}
7856
7857pub fn validate_substitution_group_element_consistency(schema_set: &SchemaSet) -> SchemaResult<()> {
7870 use crate::parser::frames::{ComplexContentResult, ParticleResult, ParticleTerm};
7871 use std::collections::HashMap;
7872
7873 type Entry = (TypeKey, Option<SourceRef>);
7874
7875 let mut subst_index: HashMap<ElementKey, Vec<ElementKey>> = HashMap::new();
7878 for (mk, m) in schema_set.arenas.elements.iter() {
7879 for &head in &m.resolved_substitution_groups {
7880 subst_index.entry(head).or_default().push(mk);
7881 }
7882 }
7883
7884 #[allow(clippy::too_many_arguments)]
7885 fn walk_particle(
7886 schema_set: &SchemaSet,
7887 particle: &ParticleResult,
7888 target_ns: Option<NameId>,
7889 local_keys: &[Option<ElementKey>],
7890 flat_idx: &mut usize,
7891 subst_index: &HashMap<ElementKey, Vec<ElementKey>>,
7892 out: &mut HashMap<(Option<NameId>, NameId), Vec<Entry>>,
7893 depth: usize,
7894 ) {
7895 if depth > 100 {
7896 return;
7897 }
7898 match &particle.term {
7899 ParticleTerm::Element(elem) => {
7900 if let Some(ref_qn) = &elem.ref_name {
7901 *flat_idx += 1;
7902 let Some(head_key) =
7905 schema_set.lookup_element(ref_qn.namespace, ref_qn.local_name)
7906 else {
7907 return;
7908 };
7909 let mut visited: std::collections::HashSet<ElementKey> =
7910 std::collections::HashSet::new();
7911 let mut stack = vec![head_key];
7912 while let Some(current) = stack.pop() {
7913 if !visited.insert(current) {
7914 continue;
7915 }
7916 let Some(decl) = schema_set.arenas.elements.get(current) else {
7917 continue;
7918 };
7919 if !decl.is_abstract || schema_set.is_xsd11() {
7923 if let (Some(name), Some(t)) = (decl.name, decl.resolved_type) {
7924 out.entry((decl.target_namespace, name))
7925 .or_default()
7926 .push((t, particle.source.clone()));
7927 }
7928 }
7929 if let Some(members) = subst_index.get(¤t) {
7930 stack.extend(members.iter().copied());
7931 }
7932 }
7933 } else {
7934 let idx = *flat_idx;
7935 *flat_idx += 1;
7936 if let Some(Some(elem_key)) = local_keys.get(idx) {
7937 if let Some(decl) = schema_set.arenas.elements.get(*elem_key) {
7938 if let (Some(name), Some(t)) = (decl.name, decl.resolved_type) {
7939 let ns = decl.target_namespace;
7945 out.entry((ns, name))
7946 .or_default()
7947 .push((t, decl.source.clone()));
7948 }
7949 }
7950 }
7951 }
7952 }
7953 ParticleTerm::Group(group) => {
7954 if let Some(ref_qn) = &group.ref_name {
7955 if let Some(group_key) =
7958 schema_set.lookup_model_group(ref_qn.namespace, ref_qn.local_name)
7959 {
7960 let mg = &schema_set.arenas.model_groups[group_key];
7961 let inner_ns = mg.target_namespace.or(target_ns);
7962 let mut inner_idx = 0usize;
7963 for child in &mg.particles {
7964 walk_particle(
7965 schema_set,
7966 child,
7967 inner_ns,
7968 &mg.resolved_particle_elements,
7969 &mut inner_idx,
7970 subst_index,
7971 out,
7972 depth + 1,
7973 );
7974 }
7975 }
7976 } else {
7977 for child in &group.particles {
7978 walk_particle(
7979 schema_set,
7980 child,
7981 target_ns,
7982 local_keys,
7983 flat_idx,
7984 subst_index,
7985 out,
7986 depth + 1,
7987 );
7988 }
7989 }
7990 }
7991 ParticleTerm::Any(_) => {}
7992 }
7993 }
7994
7995 for (_key, ct) in schema_set.arenas.complex_types.iter() {
7996 let ComplexContentResult::Complex(cc) = &ct.content else {
7997 continue;
7998 };
7999 let Some(particle) = cc.particle.as_ref() else {
8000 continue;
8001 };
8002 let mut entries: HashMap<(Option<NameId>, NameId), Vec<Entry>> = HashMap::new();
8003 let mut flat_idx = 0usize;
8004 walk_particle(
8005 schema_set,
8006 particle,
8007 ct.target_namespace,
8008 &ct.resolved_content_particle_elements,
8009 &mut flat_idx,
8010 &subst_index,
8011 &mut entries,
8012 0,
8013 );
8014
8015 for ((ns, name), list) in &entries {
8016 if list.len() < 2 {
8017 continue;
8018 }
8019 let first_type = list[0].0;
8020 for (other_type, other_src) in &list[1..] {
8021 if *other_type == first_type {
8022 continue;
8023 }
8024 let qn_str = format_type_name(schema_set, Some(*name), *ns);
8025 let location = schema_set
8026 .locate(other_src.as_ref())
8027 .or_else(|| schema_set.locate(list[0].1.as_ref()))
8028 .or_else(|| schema_set.locate(ct.source.as_ref()));
8029 return Err(SchemaError::structural(
8030 "cos-element-consistent",
8031 format!(
8032 "Element declarations for '{}' in the same content model \
8033 (counting substitution-group expansion) have different \
8034 {{type definition}}s (§3.8.6.3 / cos-element-consistent)",
8035 qn_str
8036 ),
8037 location,
8038 ));
8039 }
8040 }
8041 }
8042 Ok(())
8043}
8044
8045fn collect_flat_attribute_uses_for_group(
8076 schema_set: &SchemaSet,
8077 ag_key: AttributeGroupKey,
8078) -> Vec<EffectiveAttributeUse> {
8079 let mut result = Vec::new();
8080 collect_attribute_group_uses(schema_set, ag_key, &mut result, 0);
8081 result.retain(|eau| eau.use_kind != AttributeUseKind::Prohibited);
8082 result
8083}
8084
8085fn make_redefine_group_restriction_error(
8088 schema_set: &SchemaSet,
8089 derived: &crate::arenas::ModelGroupData,
8090 detail: &str,
8091) -> SchemaError {
8092 let name = format_type_name(schema_set, derived.name, derived.target_namespace);
8093 let location = derived
8094 .source
8095 .as_ref()
8096 .and_then(|s| schema_set.source_maps.locate(s));
8097 SchemaError::structural(
8098 "src-redefine.6.2.2",
8099 format!(
8100 "Redefined group '{}' must be a valid restriction of the original \
8101 (§src-redefine 6.2.2): {}",
8102 name, detail,
8103 ),
8104 location,
8105 )
8106}
8107
8108fn make_redefine_attr_group_restriction_error(
8111 schema_set: &SchemaSet,
8112 derived: &crate::arenas::AttributeGroupData,
8113 detail: &str,
8114) -> SchemaError {
8115 let name = format_type_name(schema_set, derived.name, derived.target_namespace);
8116 let location = derived
8117 .source
8118 .as_ref()
8119 .and_then(|s| schema_set.source_maps.locate(s));
8120 SchemaError::structural(
8121 "src-redefine.7.2.2",
8122 format!(
8123 "Redefined attribute group '{}' must be a valid restriction of the original \
8124 (§src-redefine 7.2.2): {}",
8125 name, detail,
8126 ),
8127 location,
8128 )
8129}
8130
8131fn validate_all_redefine_group_restrictions(
8136 schema_set: &SchemaSet,
8137 errors: &mut Vec<SchemaError>,
8138 stats: &mut DerivationStats,
8139) {
8140 for (_key, derived) in schema_set.arenas.model_groups.iter() {
8141 if !derived.redefine_requires_restriction_check {
8142 continue;
8143 }
8144 let Some(original_key) = derived.redefine_original else {
8145 continue;
8146 };
8147 let Some(original) = schema_set.arenas.model_groups.get(original_key) else {
8148 continue;
8149 };
8150
8151 let derived_particle = match normalize_model_group_as_particle(schema_set, derived) {
8152 Ok(p) => p,
8153 Err(e) => {
8154 errors.push(e);
8155 stats.errors += 1;
8156 continue;
8157 }
8158 };
8159 let base_particle = match normalize_model_group_as_particle(schema_set, original) {
8160 Ok(p) => p,
8161 Err(e) => {
8162 errors.push(e);
8163 stats.errors += 1;
8164 continue;
8165 }
8166 };
8167
8168 if is_effectively_empty(&derived_particle) {
8177 if !particle_is_emptiable(&base_particle) {
8178 errors.push(make_redefine_group_restriction_error(
8179 schema_set,
8180 derived,
8181 "removes required content model of the original group",
8182 ));
8183 stats.errors += 1;
8184 }
8185 continue;
8186 }
8187
8188 if !particle_restricts(schema_set, &derived_particle, &base_particle) {
8189 errors.push(make_redefine_group_restriction_error(
8190 schema_set,
8191 derived,
8192 "content model is not a valid restriction of the original group",
8193 ));
8194 stats.errors += 1;
8195 }
8196 }
8197}
8198
8199fn validate_all_redefine_attribute_group_restrictions(
8209 schema_set: &SchemaSet,
8210 errors: &mut Vec<SchemaError>,
8211 stats: &mut DerivationStats,
8212) {
8213 for (_key, derived) in schema_set.arenas.attribute_groups.iter() {
8214 if !derived.redefine_requires_restriction_check {
8215 continue;
8216 }
8217 let Some(original_key) = derived.redefine_original else {
8218 continue;
8219 };
8220 let Some(original) = schema_set.arenas.attribute_groups.get(original_key) else {
8221 continue;
8222 };
8223
8224 let derived_attrs = collect_flat_attribute_uses_for_group(schema_set, _key);
8228 let base_attrs = collect_flat_attribute_uses_for_group(schema_set, original_key);
8229
8230 let base_effective_wc = match effective_attribute_wildcard(
8235 schema_set,
8236 original.attribute_wildcard.as_ref(),
8237 original.target_namespace,
8238 &original.resolved_attribute_groups,
8239 ) {
8240 Ok(eff) => eff,
8241 Err(e) => {
8242 errors.push(e);
8243 stats.errors += 1;
8244 continue;
8245 }
8246 };
8247
8248 let mut failed = false;
8253 for da in &derived_attrs {
8254 if let Some(ba) = base_attrs
8256 .iter()
8257 .find(|b| b.name == da.name && b.target_namespace == da.target_namespace)
8258 {
8259 if let (Some(dt), Some(bt)) = (da.resolved_type, ba.resolved_type) {
8262 if dt != bt && !is_type_derived_from(schema_set, dt, bt) {
8263 let attr_name_str = schema_set.name_table.resolve(da.name).to_string();
8264 errors.push(make_redefine_attr_group_restriction_error(
8265 schema_set,
8266 derived,
8267 &format!(
8268 "attribute '{}' has a type that is not validly derived from the \
8269 base attribute type",
8270 attr_name_str,
8271 ),
8272 ));
8273 stats.errors += 1;
8274 failed = true;
8275 break;
8276 }
8277 }
8278 if let Some(ref base_fixed) = ba.fixed_value {
8285 let derived_matches =
8286 da.fixed_value.as_ref().is_some_and(|dv| dv == base_fixed);
8287 if !derived_matches {
8288 let attr_name_str = schema_set.name_table.resolve(da.name).to_string();
8289 errors.push(make_redefine_attr_group_restriction_error(
8290 schema_set,
8291 derived,
8292 &format!(
8293 "attribute '{}' relaxes or removes the base 'fixed=\"{}\"' \
8294 value constraint",
8295 attr_name_str, base_fixed,
8296 ),
8297 ));
8298 stats.errors += 1;
8299 failed = true;
8300 break;
8301 }
8302 }
8303 continue;
8304 }
8305 if let Some(ref bwc) = base_effective_wc {
8308 if effective_wildcard_allows_attribute(
8309 schema_set,
8310 bwc,
8311 da.target_namespace,
8312 da.name,
8313 ) {
8314 continue;
8315 }
8316 }
8317 let attr_name_str = schema_set.name_table.resolve(da.name).to_string();
8319 errors.push(make_redefine_attr_group_restriction_error(
8320 schema_set,
8321 derived,
8322 &format!(
8323 "attribute '{}' is not present in the original and is not admitted by \
8324 the original's attribute wildcard",
8325 attr_name_str,
8326 ),
8327 ));
8328 stats.errors += 1;
8329 failed = true;
8330 break;
8331 }
8332 if failed {
8333 continue;
8334 }
8335
8336 let mut req_failed = false;
8339 for ba in &base_attrs {
8340 if ba.use_kind != AttributeUseKind::Required {
8341 continue;
8342 }
8343 let matching = derived_attrs
8344 .iter()
8345 .find(|d| d.name == ba.name && d.target_namespace == ba.target_namespace);
8346 match matching {
8347 Some(da) if da.use_kind == AttributeUseKind::Required => {}
8348 _ => {
8349 let attr_name_str = schema_set.name_table.resolve(ba.name).to_string();
8350 errors.push(make_redefine_attr_group_restriction_error(
8351 schema_set,
8352 derived,
8353 &format!(
8354 "base attribute '{}' is required but the redefined group does not \
8355 declare it as required",
8356 attr_name_str,
8357 ),
8358 ));
8359 stats.errors += 1;
8360 req_failed = true;
8361 break;
8362 }
8363 }
8364 }
8365 if req_failed {
8366 continue;
8367 }
8368
8369 let derived_effective_wc = match effective_attribute_wildcard(
8375 schema_set,
8376 derived.attribute_wildcard.as_ref(),
8377 derived.target_namespace,
8378 &derived.resolved_attribute_groups,
8379 ) {
8380 Ok(eff) => eff,
8381 Err(e) => {
8382 errors.push(e);
8383 stats.errors += 1;
8384 continue;
8385 }
8386 };
8387 match classify_attribute_wildcard_restriction(
8388 schema_set,
8389 derived_effective_wc.as_ref(),
8390 base_effective_wc.as_ref(),
8391 ) {
8392 WildcardRestrictionOutcome::DerivedAbsent | WildcardRestrictionOutcome::Valid => {}
8393 WildcardRestrictionOutcome::AddedInDerived => {
8394 errors.push(make_redefine_attr_group_restriction_error(
8395 schema_set,
8396 derived,
8397 "redefined attribute group declares an attribute wildcard but \
8398 the original has none",
8399 ));
8400 stats.errors += 1;
8401 }
8402 WildcardRestrictionOutcome::NotSubset(reason) => {
8403 errors.push(make_redefine_attr_group_restriction_error(
8404 schema_set,
8405 derived,
8406 &format!(
8407 "attribute wildcard is not a valid restriction of the \
8408 original: {}",
8409 reason,
8410 ),
8411 ));
8412 stats.errors += 1;
8413 }
8414 }
8415 }
8416}
8417
8418#[cfg(test)]
8419mod tests {
8420 use super::*;
8421 use crate::arenas::{ComplexTypeDefData, SimpleTypeDefData};
8422 use crate::parser::frames::ComplexContentResult;
8423 use crate::schema::model::DerivationSet;
8424
8425 fn create_simple_type_data(
8426 name: Option<NameId>,
8427 variety: SimpleTypeVariety,
8428 ) -> SimpleTypeDefData {
8429 SimpleTypeDefData {
8430 name,
8431 target_namespace: None,
8432 variety,
8433 base_type: None,
8434 item_type: None,
8435 member_types: Vec::new(),
8436 facets: FacetSet::new(),
8437 final_derivation: DerivationSet::empty(),
8438 id: None,
8439 derivation_id: None,
8440 annotation: None,
8441 source: None,
8442 resolved_base_type: None,
8443 resolved_item_type: None,
8444 resolved_member_types: Vec::new(),
8445 redefine_original: None,
8446 deferred_item_type_error: None,
8447 }
8448 }
8449
8450 fn create_complex_type_data(name: Option<NameId>) -> ComplexTypeDefData {
8451 ComplexTypeDefData {
8452 name,
8453 target_namespace: None,
8454 base_type: None,
8455 derivation_method: None,
8456 content: ComplexContentResult::Empty,
8457 open_content: None,
8458 attributes: Vec::new(),
8459 attribute_groups: Vec::new(),
8460 attribute_wildcard: None,
8461 mixed: false,
8462 is_abstract: false,
8463 final_derivation: DerivationSet::empty(),
8464 block: DerivationSet::empty(),
8465 default_attributes_apply: true,
8466 id: None,
8467 #[cfg(feature = "xsd11")]
8468 assertions: Vec::new(),
8469 #[cfg(feature = "xsd11")]
8470 xpath_default_namespace: None,
8471 annotation: None,
8472 source: None,
8473 resolved_base_type: None,
8474 resolved_attribute_groups: Vec::new(),
8475 resolved_attributes: Vec::new(),
8476 resolved_content_particle_types: Vec::new(),
8477 resolved_content_particle_elements: Vec::new(),
8478 resolved_simple_content_type: None,
8479 redefine_original: None,
8480 }
8481 }
8482
8483 #[test]
8484 fn test_derivation_stats_default() {
8485 let stats = DerivationStats::default();
8486 assert_eq!(stats.simple_types_validated, 0);
8487 assert_eq!(stats.complex_types_validated, 0);
8488 assert_eq!(stats.errors, 0);
8489 }
8490
8491 #[test]
8492 fn test_validate_empty_schema() {
8493 let schema_set = SchemaSet::new();
8494 let dep_graph = DependencyGraph::new();
8495
8496 let result = validate_all_derivations(&schema_set, &dep_graph);
8497 assert!(result.is_ok());
8498
8499 let stats = result.unwrap();
8500 assert_eq!(stats.simple_types_validated, 0);
8501 assert_eq!(stats.complex_types_validated, 0);
8502 }
8503
8504 #[test]
8505 fn test_validate_atomic_type_no_base() {
8506 let mut schema_set = SchemaSet::new();
8507 let type_data = create_simple_type_data(None, SimpleTypeVariety::Atomic);
8508 let key = schema_set.arenas.alloc_simple_type(type_data);
8509
8510 let mut stats = DerivationStats::default();
8511 let result = validate_simple_type(&schema_set, key, &mut stats);
8512
8513 assert!(result.is_ok());
8514 assert_eq!(stats.simple_types_validated, 1);
8515 }
8516
8517 #[test]
8518 fn test_validate_list_type_no_item() {
8519 let mut schema_set = SchemaSet::new();
8520 let type_data = create_simple_type_data(None, SimpleTypeVariety::List);
8521 let key = schema_set.arenas.alloc_simple_type(type_data);
8522
8523 let mut stats = DerivationStats::default();
8524 let result = validate_simple_type(&schema_set, key, &mut stats);
8525
8526 assert!(result.is_ok());
8527 assert_eq!(stats.list_types_validated, 1);
8528 }
8529
8530 #[test]
8531 fn test_validate_union_type_no_members() {
8532 let mut schema_set = SchemaSet::new();
8533 let type_data = create_simple_type_data(None, SimpleTypeVariety::Union);
8534 let key = schema_set.arenas.alloc_simple_type(type_data);
8535
8536 let mut stats = DerivationStats::default();
8537 let result = validate_simple_type(&schema_set, key, &mut stats);
8538
8539 assert!(result.is_ok());
8540 assert_eq!(stats.union_types_validated, 1);
8541 }
8542
8543 #[test]
8544 fn test_validate_list_of_atomic() {
8545 let mut schema_set = SchemaSet::new();
8546
8547 let item_type_data = create_simple_type_data(None, SimpleTypeVariety::Atomic);
8549 let item_key = schema_set.arenas.alloc_simple_type(item_type_data);
8550
8551 let mut list_type_data = create_simple_type_data(None, SimpleTypeVariety::List);
8553 list_type_data.resolved_item_type = Some(TypeKey::Simple(item_key));
8554 let list_key = schema_set.arenas.alloc_simple_type(list_type_data);
8555
8556 let mut stats = DerivationStats::default();
8557 let result = validate_simple_type(&schema_set, list_key, &mut stats);
8558
8559 assert!(result.is_ok());
8560 }
8561
8562 #[test]
8563 fn test_validate_list_of_list_error() {
8564 let mut schema_set = SchemaSet::new();
8565
8566 let inner_list_data = create_simple_type_data(None, SimpleTypeVariety::List);
8568 let inner_key = schema_set.arenas.alloc_simple_type(inner_list_data);
8569
8570 let mut outer_list_data = create_simple_type_data(None, SimpleTypeVariety::List);
8572 outer_list_data.resolved_item_type = Some(TypeKey::Simple(inner_key));
8573 let outer_key = schema_set.arenas.alloc_simple_type(outer_list_data);
8574
8575 let mut stats = DerivationStats::default();
8576 let result = validate_simple_type(&schema_set, outer_key, &mut stats);
8577
8578 assert!(result.is_err());
8579 if let Err(SchemaError::StructuralError { constraint, .. }) = result {
8580 assert_eq!(constraint, "cos-list-of-atomic");
8581 } else {
8582 panic!("Expected structural error with cos-list-of-atomic constraint");
8583 }
8584 }
8585
8586 #[test]
8587 fn test_validate_union_with_complex_member_error() {
8588 let mut schema_set = SchemaSet::new();
8589
8590 let complex_data = create_complex_type_data(None);
8592 let complex_key = schema_set.arenas.alloc_complex_type(complex_data);
8593
8594 let mut union_data = create_simple_type_data(None, SimpleTypeVariety::Union);
8596 union_data.resolved_member_types = vec![TypeKey::Complex(complex_key)];
8597 let union_key = schema_set.arenas.alloc_simple_type(union_data);
8598
8599 let mut stats = DerivationStats::default();
8600 let result = validate_simple_type(&schema_set, union_key, &mut stats);
8601
8602 assert!(result.is_err());
8603 if let Err(SchemaError::StructuralError { constraint, .. }) = result {
8604 assert_eq!(constraint, "cos-union-memberTypes");
8605 } else {
8606 panic!("Expected structural error with cos-union-memberTypes constraint");
8607 }
8608 }
8609
8610 #[test]
8611 fn test_validate_complex_type_no_base() {
8612 let mut schema_set = SchemaSet::new();
8613 let type_data = create_complex_type_data(None);
8614 let key = schema_set.arenas.alloc_complex_type(type_data);
8615
8616 let mut stats = DerivationStats::default();
8617 let result = validate_complex_type(&schema_set, key, &mut stats);
8618
8619 assert!(result.is_ok());
8620 assert_eq!(stats.complex_types_validated, 1);
8621 }
8622
8623 #[test]
8624 fn test_validate_complex_extension() {
8625 let mut schema_set = SchemaSet::new();
8626
8627 let base_data = create_complex_type_data(None);
8629 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8630
8631 let mut derived_data = create_complex_type_data(None);
8633 derived_data.derivation_method = Some(DerivationMethod::Extension);
8634 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8635 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8636
8637 let mut stats = DerivationStats::default();
8638 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8639
8640 assert!(result.is_ok());
8641 assert_eq!(stats.extensions_validated, 1);
8642 }
8643
8644 #[test]
8645 fn test_validate_complex_restriction() {
8646 let mut schema_set = SchemaSet::new();
8647
8648 let base_data = create_complex_type_data(None);
8650 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8651
8652 let mut derived_data = create_complex_type_data(None);
8654 derived_data.derivation_method = Some(DerivationMethod::Restriction);
8655 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8656 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8657
8658 let mut stats = DerivationStats::default();
8659 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8660
8661 assert!(result.is_ok());
8662 assert_eq!(stats.restrictions_validated, 1);
8663 }
8664
8665 #[test]
8666 fn test_validate_extension_of_final_type_error() {
8667 let mut schema_set = SchemaSet::new();
8668
8669 let mut base_data = create_complex_type_data(None);
8671 base_data.final_derivation = DerivationSet::extension();
8672 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8673
8674 let mut derived_data = create_complex_type_data(None);
8676 derived_data.derivation_method = Some(DerivationMethod::Extension);
8677 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8678 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8679
8680 let mut stats = DerivationStats::default();
8681 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8682
8683 assert!(result.is_err());
8684 if let Err(SchemaError::StructuralError { constraint, .. }) = result {
8685 assert_eq!(constraint, "cos-ct-extends");
8686 } else {
8687 panic!("Expected structural error with cos-ct-extends constraint");
8688 }
8689 }
8690
8691 #[test]
8692 fn test_validate_extension_of_final_default_type_error() {
8693 let mut schema_set = SchemaSet::new();
8696
8697 let mut base_data = create_complex_type_data(None);
8698 base_data.final_derivation = DerivationSet::extension();
8699 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8700
8701 let mut derived_data = create_complex_type_data(None);
8703 derived_data.derivation_method = Some(DerivationMethod::Extension);
8704 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8705 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8706
8707 let mut stats = DerivationStats::default();
8708 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8709
8710 assert!(result.is_err());
8711 if let Err(SchemaError::StructuralError { constraint, .. }) = result {
8712 assert_eq!(constraint, "cos-ct-extends");
8713 } else {
8714 panic!("Expected structural error with cos-ct-extends constraint");
8715 }
8716 }
8717
8718 #[test]
8719 fn test_validate_restriction_of_final_type_error() {
8720 let mut schema_set = SchemaSet::new();
8721
8722 let mut base_data = create_complex_type_data(None);
8724 base_data.final_derivation = DerivationSet::restriction();
8725 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8726
8727 let mut derived_data = create_complex_type_data(None);
8729 derived_data.derivation_method = Some(DerivationMethod::Restriction);
8730 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8731 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8732
8733 let mut stats = DerivationStats::default();
8734 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8735
8736 assert!(result.is_err());
8737 if let Err(SchemaError::StructuralError { constraint, .. }) = result {
8738 assert_eq!(constraint, "derivation-ok-restriction");
8739 } else {
8740 panic!("Expected structural error with derivation-ok-restriction constraint");
8741 }
8742 }
8743
8744 #[test]
8745 fn test_format_type_name_anonymous() {
8746 let schema_set = SchemaSet::new();
8747 let name = format_type_name(&schema_set, None, None);
8748 assert_eq!(name, "(anonymous)");
8749 }
8750
8751 #[test]
8752 fn test_format_type_name_with_namespace() {
8753 let schema_set = SchemaSet::new();
8754 let name_id = schema_set.name_table.add("myType");
8755 let ns_id = schema_set.name_table.add("http://example.com");
8756 let name = format_type_name(&schema_set, Some(name_id), Some(ns_id));
8757 assert_eq!(name, "{http://example.com}myType");
8758 }
8759
8760 #[test]
8761 fn test_format_type_name_no_namespace() {
8762 let schema_set = SchemaSet::new();
8763 let name_id = schema_set.name_table.add("myType");
8764 let name = format_type_name(&schema_set, Some(name_id), None);
8765 assert_eq!(name, "myType");
8766 }
8767
8768 #[cfg(feature = "xsd11")]
8773 fn make_open_content(
8774 mode: crate::parser::frames::OpenContentMode,
8775 namespace: crate::parser::frames::WildcardNamespace,
8776 pc: crate::parser::frames::ProcessContents,
8777 ) -> crate::parser::frames::OpenContentResult {
8778 crate::parser::frames::OpenContentResult {
8779 mode,
8780 wildcard: Some(crate::parser::frames::WildcardResult {
8781 namespace,
8782 process_contents: pc,
8783 not_namespace: Vec::new(),
8784 not_qname: Vec::new(),
8785 id: None,
8786 annotation: None,
8787 source: None,
8788 }),
8789 id: None,
8790 annotation: None,
8791 source: None,
8792 }
8793 }
8794
8795 #[cfg(feature = "xsd11")]
8796 #[test]
8797 fn test_extension_suffix_cannot_extend_interleave() {
8798 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
8799
8800 let mut schema_set = SchemaSet::new();
8801
8802 let mut base_data = create_complex_type_data(None);
8803 base_data.open_content = Some(make_open_content(
8804 OpenContentMode::Interleave,
8805 WildcardNamespace::Any,
8806 ProcessContents::Lax,
8807 ));
8808 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8809
8810 let mut derived_data = create_complex_type_data(None);
8811 derived_data.derivation_method = Some(DerivationMethod::Extension);
8812 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8813 derived_data.open_content = Some(make_open_content(
8814 OpenContentMode::Suffix,
8815 WildcardNamespace::Any,
8816 ProcessContents::Lax,
8817 ));
8818 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8819
8820 let mut stats = DerivationStats::default();
8821 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8822
8823 assert!(result.is_err());
8824 if let Err(SchemaError::StructuralError { constraint, .. }) = result {
8825 assert_eq!(constraint, "cos-ct-extends");
8826 } else {
8827 panic!("Expected cos-ct-extends error");
8828 }
8829 }
8830
8831 #[cfg(feature = "xsd11")]
8832 #[test]
8833 fn test_extension_interleave_extends_interleave_valid() {
8834 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
8835
8836 let mut schema_set = SchemaSet::new();
8837
8838 let mut base_data = create_complex_type_data(None);
8839 base_data.open_content = Some(make_open_content(
8840 OpenContentMode::Interleave,
8841 WildcardNamespace::Any,
8842 ProcessContents::Lax,
8843 ));
8844 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8845
8846 let mut derived_data = create_complex_type_data(None);
8847 derived_data.derivation_method = Some(DerivationMethod::Extension);
8848 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8849 derived_data.open_content = Some(make_open_content(
8850 OpenContentMode::Interleave,
8851 WildcardNamespace::Any,
8852 ProcessContents::Lax,
8853 ));
8854 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8855
8856 let mut stats = DerivationStats::default();
8857 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8858 assert!(result.is_ok());
8859 }
8860
8861 #[cfg(feature = "xsd11")]
8862 #[test]
8863 fn test_extension_base_has_oc_derived_has_none() {
8864 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
8865
8866 let mut schema_set = SchemaSet::new();
8872
8873 let mut base_data = create_complex_type_data(None);
8874 base_data.open_content = Some(make_open_content(
8875 OpenContentMode::Interleave,
8876 WildcardNamespace::Any,
8877 ProcessContents::Lax,
8878 ));
8879 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8880
8881 let mut derived_data = create_complex_type_data(None);
8882 derived_data.derivation_method = Some(DerivationMethod::Extension);
8883 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8884 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8886
8887 let mut stats = DerivationStats::default();
8888 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8889
8890 assert!(
8891 result.is_ok(),
8892 "derived inherits BOT per clause 6.1: {:?}",
8893 result
8894 );
8895 }
8896
8897 #[cfg(feature = "xsd11")]
8898 #[test]
8899 fn test_extension_base_no_oc_derived_adds_oc_valid() {
8900 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
8901
8902 let mut schema_set = SchemaSet::new();
8903
8904 let base_data = create_complex_type_data(None);
8906 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8907
8908 let mut derived_data = create_complex_type_data(None);
8909 derived_data.derivation_method = Some(DerivationMethod::Extension);
8910 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8911 derived_data.open_content = Some(make_open_content(
8912 OpenContentMode::Interleave,
8913 WildcardNamespace::Any,
8914 ProcessContents::Lax,
8915 ));
8916 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8917
8918 let mut stats = DerivationStats::default();
8919 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8920 assert!(result.is_ok());
8921 }
8922
8923 #[cfg(feature = "xsd11")]
8924 #[test]
8925 fn test_restriction_adds_oc_when_base_has_none() {
8926 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
8927
8928 let mut schema_set = SchemaSet::new();
8929
8930 let base_data = create_complex_type_data(None);
8932 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8933
8934 let mut derived_data = create_complex_type_data(None);
8935 derived_data.derivation_method = Some(DerivationMethod::Restriction);
8936 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8937 derived_data.open_content = Some(make_open_content(
8938 OpenContentMode::Interleave,
8939 WildcardNamespace::Any,
8940 ProcessContents::Lax,
8941 ));
8942 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8943
8944 let mut stats = DerivationStats::default();
8945 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8946
8947 assert!(result.is_err());
8948 if let Err(SchemaError::StructuralError { constraint, .. }) = result {
8949 assert_eq!(constraint, "derivation-ok-restriction");
8950 } else {
8951 panic!("Expected derivation-ok-restriction error");
8952 }
8953 }
8954
8955 #[cfg(feature = "xsd11")]
8956 #[test]
8957 fn test_restriction_removes_oc_valid() {
8958 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
8959
8960 let mut schema_set = SchemaSet::new();
8961
8962 let mut base_data = create_complex_type_data(None);
8963 base_data.open_content = Some(make_open_content(
8964 OpenContentMode::Interleave,
8965 WildcardNamespace::Any,
8966 ProcessContents::Lax,
8967 ));
8968 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8969
8970 let mut derived_data = create_complex_type_data(None);
8971 derived_data.derivation_method = Some(DerivationMethod::Restriction);
8972 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
8973 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
8975
8976 let mut stats = DerivationStats::default();
8977 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
8978 assert!(result.is_ok());
8979 }
8980
8981 #[cfg(feature = "xsd11")]
8982 #[test]
8983 fn test_restriction_empty_derived_allows_interleave_over_suffix() {
8984 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
8989
8990 let mut schema_set = SchemaSet::new();
8991
8992 let mut base_data = create_complex_type_data(None);
8993 base_data.open_content = Some(make_open_content(
8994 OpenContentMode::Suffix,
8995 WildcardNamespace::Any,
8996 ProcessContents::Lax,
8997 ));
8998 let base_key = schema_set.arenas.alloc_complex_type(base_data);
8999
9000 let mut derived_data = create_complex_type_data(None);
9001 derived_data.derivation_method = Some(DerivationMethod::Restriction);
9002 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
9003 derived_data.open_content = Some(make_open_content(
9004 OpenContentMode::Interleave,
9005 WildcardNamespace::Any,
9006 ProcessContents::Lax,
9007 ));
9008 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
9009
9010 let mut stats = DerivationStats::default();
9011 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
9012 assert!(
9013 result.is_ok(),
9014 "empty derived content should accept interleave over suffix, got {:?}",
9015 result.err(),
9016 );
9017 }
9018
9019 #[cfg(feature = "xsd11")]
9020 #[test]
9021 fn test_restriction_suffix_restricts_interleave_valid() {
9022 use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
9023
9024 let mut schema_set = SchemaSet::new();
9025
9026 let mut base_data = create_complex_type_data(None);
9027 base_data.open_content = Some(make_open_content(
9028 OpenContentMode::Interleave,
9029 WildcardNamespace::Any,
9030 ProcessContents::Lax,
9031 ));
9032 let base_key = schema_set.arenas.alloc_complex_type(base_data);
9033
9034 let mut derived_data = create_complex_type_data(None);
9035 derived_data.derivation_method = Some(DerivationMethod::Restriction);
9036 derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
9037 derived_data.open_content = Some(make_open_content(
9038 OpenContentMode::Suffix,
9039 WildcardNamespace::Any,
9040 ProcessContents::Lax,
9041 ));
9042 let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
9043
9044 let mut stats = DerivationStats::default();
9045 let result = validate_complex_type(&schema_set, derived_key, &mut stats);
9046 assert!(result.is_ok());
9047 }
9048
9049 #[cfg(feature = "xsd11")]
9057 #[test]
9058 fn test_ns_subset_local_not_subset_of_other() {
9059 use crate::parser::frames::WildcardNamespace;
9063
9064 let schema_set = SchemaSet::new();
9065 let urn_a = schema_set.name_table.add("urn:a");
9066
9067 let result = is_namespace_subset(
9068 &WildcardNamespace::Local,
9069 None,
9070 &WildcardNamespace::Other,
9071 Some(urn_a),
9072 );
9073 assert!(
9074 !result,
9075 "##local must NOT be a subset of ##other (absent is excluded)"
9076 );
9077 }
9078
9079 #[cfg(feature = "xsd11")]
9080 #[test]
9081 fn test_ns_subset_other_no_tns_not_subset_of_other_with_tns() {
9082 use crate::parser::frames::WildcardNamespace;
9086
9087 let schema_set = SchemaSet::new();
9088 let urn_a = schema_set.name_table.add("urn:a");
9089
9090 let result = is_namespace_subset(
9091 &WildcardNamespace::Other,
9092 None,
9093 &WildcardNamespace::Other,
9094 Some(urn_a),
9095 );
9096 assert!(
9097 !result,
9098 "##other(tns=None) must NOT be a subset of ##other(tns=urn:a)"
9099 );
9100 }
9101
9102 #[cfg(feature = "xsd11")]
9103 #[test]
9104 fn test_ns_subset_other_with_tns_is_subset_of_other_no_tns() {
9105 use crate::parser::frames::WildcardNamespace;
9109
9110 let schema_set = SchemaSet::new();
9111 let urn_a = schema_set.name_table.add("urn:a");
9112
9113 let result = is_namespace_subset(
9114 &WildcardNamespace::Other,
9115 Some(urn_a),
9116 &WildcardNamespace::Other,
9117 None,
9118 );
9119 assert!(
9120 result,
9121 "##other(tns=urn:a) MUST be a subset of ##other(tns=None)"
9122 );
9123 }
9124
9125 #[cfg(feature = "xsd11")]
9126 #[test]
9127 fn test_ns_subset_list_with_tns_uri_not_subset_of_other() {
9128 use crate::parser::frames::{NamespaceToken, WildcardNamespace};
9132
9133 let schema_set = SchemaSet::new();
9134 let urn_a = schema_set.name_table.add("urn:a");
9135 let urn_b = schema_set.name_table.add("urn:b");
9136
9137 let result = is_namespace_subset(
9138 &WildcardNamespace::List(vec![NamespaceToken::Uri(urn_a), NamespaceToken::Uri(urn_b)]),
9139 None,
9140 &WildcardNamespace::Other,
9141 Some(urn_a),
9142 );
9143 assert!(
9144 !result,
9145 "List containing base's target ns must NOT be a subset of ##other"
9146 );
9147 }
9148
9149 fn default_wildcard(ns: WildcardNamespace) -> WildcardResult {
9163 WildcardResult {
9164 namespace: ns,
9165 process_contents: ProcessContents::Strict,
9166 not_namespace: Vec::new(),
9167 not_qname: Vec::new(),
9168 id: None,
9169 annotation: None,
9170 source: None,
9171 }
9172 }
9173
9174 fn admits(
9179 schema_set: &SchemaSet,
9180 w: &WildcardResult,
9181 target_ns: Option<NameId>,
9182 attr_ns: Option<NameId>,
9183 attr_name: NameId,
9184 ) -> bool {
9185 let eff = normalize_attribute_wildcard(schema_set, w, target_ns);
9186 effective_wildcard_allows_attribute(schema_set, &eff, attr_ns, attr_name)
9187 }
9188
9189 #[test]
9190 fn test_effective_wildcard_any_admits_anything() {
9191 let schema_set = SchemaSet::new();
9192 let name = schema_set.name_table.add("foo");
9193 let w = default_wildcard(WildcardNamespace::Any);
9194 assert!(admits(&schema_set, &w, None, None, name));
9195 }
9196
9197 #[test]
9198 fn test_effective_wildcard_other_excludes_target_ns() {
9199 let schema_set = SchemaSet::new();
9201 let ns = schema_set.name_table.add("urn:foo");
9202 let name = schema_set.name_table.add("bar");
9203 let w = default_wildcard(WildcardNamespace::Other);
9204 assert!(
9205 !admits(&schema_set, &w, Some(ns), Some(ns), name),
9206 "##other must NOT admit the target namespace"
9207 );
9208 }
9209
9210 #[test]
9211 fn test_effective_wildcard_other_admits_different_ns() {
9212 let schema_set = SchemaSet::new();
9213 let tns = schema_set.name_table.add("urn:foo");
9214 let other_ns = schema_set.name_table.add("urn:bar");
9215 let name = schema_set.name_table.add("qux");
9216 let w = default_wildcard(WildcardNamespace::Other);
9217 assert!(
9218 admits(&schema_set, &w, Some(tns), Some(other_ns), name),
9219 "##other must admit a namespace different from the target"
9220 );
9221 }
9222
9223 #[test]
9224 fn test_effective_wildcard_other_absent_ns_xsd10_vs_xsd11() {
9225 let schema_10 = SchemaSet::new(); let tns = schema_10.name_table.add("urn:foo");
9230 let name = schema_10.name_table.add("local_attr");
9231 let w = default_wildcard(WildcardNamespace::Other);
9232
9233 assert!(
9234 !admits(&schema_10, &w, Some(tns), None, name),
9235 "XSD 1.0: ##other must NOT admit the absent namespace"
9236 );
9237
9238 let schema_11 = SchemaSet::xsd11();
9239 let tns11 = schema_11.name_table.add("urn:foo");
9240 let name11 = schema_11.name_table.add("local_attr");
9241 assert!(
9242 admits(&schema_11, &w, Some(tns11), None, name11),
9243 "XSD 1.1: ##other MUST admit the absent namespace"
9244 );
9245 }
9246
9247 #[test]
9248 fn test_effective_wildcard_defined_excludes_declared_only() {
9249 use crate::arenas::AttributeDeclData;
9252 use crate::parser::frames::NotQNameItem;
9253
9254 let mut schema_set = SchemaSet::new();
9255 let declared_name = schema_set.name_table.add("declared_attr");
9256 let undeclared_name = schema_set.name_table.add("undeclared_attr");
9257
9258 let attr_data = AttributeDeclData {
9259 name: Some(declared_name),
9260 target_namespace: None,
9261 ref_name: None,
9262 type_ref: None,
9263 inline_type: None,
9264 default_value: None,
9265 fixed_value: None,
9266 use_kind: None,
9267 form: None,
9268 inheritable: false,
9269 id: None,
9270 annotation: None,
9271 source: None,
9272 resolved_type: None,
9273 resolved_ref: None,
9274 };
9275 let attr_key = schema_set.arenas.alloc_attribute(attr_data);
9276 schema_set
9277 .get_or_create_namespace(None)
9278 .register_attribute(declared_name, attr_key);
9279
9280 let mut w = default_wildcard(WildcardNamespace::Any);
9281 w.not_qname = vec![NotQNameItem::Defined];
9282
9283 assert!(
9284 !admits(&schema_set, &w, None, None, declared_name),
9285 "##defined MUST exclude globally-declared attributes"
9286 );
9287 assert!(
9288 admits(&schema_set, &w, None, None, undeclared_name),
9289 "##defined MUST NOT exclude attributes that are not globally declared"
9290 );
9291 }
9292
9293 #[test]
9294 fn test_effective_wildcard_not_qname_literal_excludes() {
9295 use crate::parser::frames::NotQNameItem;
9296
9297 let schema_set = SchemaSet::new();
9298 let blocked = schema_set.name_table.add("blocked");
9299 let allowed = schema_set.name_table.add("allowed");
9300
9301 let mut w = default_wildcard(WildcardNamespace::Any);
9302 w.not_qname = vec![NotQNameItem::QName {
9303 namespace: None,
9304 local_name: blocked,
9305 }];
9306
9307 assert!(!admits(&schema_set, &w, None, None, blocked));
9308 assert!(admits(&schema_set, &w, None, None, allowed));
9309 }
9310
9311 #[test]
9312 fn test_particle_restricts_all_required_over_empty_all_rejects() {
9313 let schema_set = SchemaSet::new();
9319 let e1_name = schema_set.name_table.add("e1");
9320 let any_type = TypeKey::Complex(schema_set.any_type_key());
9321
9322 let make_elem = |min_occurs: u32, max_occurs: Option<u32>| NormalizedParticle {
9323 term: NormalizedParticleTerm::Element(NormalizedElement {
9324 name: e1_name,
9325 namespace: None,
9326 type_key: any_type,
9327 element_key: None,
9328 block: DerivationSet::empty(),
9329 nillable: false,
9330 fixed_value: None,
9331 }),
9332 min_occurs,
9333 max_occurs,
9334 source: None,
9335 };
9336
9337 let derived = NormalizedParticle {
9338 term: NormalizedParticleTerm::Group(NormalizedGroup {
9339 compositor: Compositor::All,
9340 particles: vec![make_elem(1, Some(1))],
9341 }),
9342 min_occurs: 1,
9343 max_occurs: Some(1),
9344 source: None,
9345 };
9346 let base_empty_all = NormalizedParticle {
9347 term: NormalizedParticleTerm::Group(NormalizedGroup {
9348 compositor: Compositor::All,
9349 particles: Vec::new(),
9350 }),
9351 min_occurs: 1,
9352 max_occurs: Some(1),
9353 source: None,
9354 };
9355
9356 assert!(
9357 !particle_restricts(&schema_set, &derived, &base_empty_all),
9358 "all{{e1{{1,1}}}} must NOT restrict all{{}} — derived adds a required particle"
9359 );
9360 }
9361
9362 #[test]
9363 fn test_collect_flat_attribute_uses_filters_prohibited() {
9364 use crate::arenas::AttributeGroupData;
9367 use crate::parser::frames::{
9368 AttributeFrameResult, AttributeUseKind as AuK, AttributeUseResult,
9369 };
9370
9371 let mut schema_set = SchemaSet::new();
9372 let grp_name = schema_set.name_table.add("ag");
9373 let opt_name = schema_set.name_table.add("opt");
9374 let banned_name = schema_set.name_table.add("banned");
9375
9376 let make_attr = |name: NameId, kind: AuK| AttributeUseResult {
9377 attribute: AttributeFrameResult {
9378 name: Some(name),
9379 ref_name: None,
9380 target_namespace: None,
9381 type_ref: None,
9382 inline_type: None,
9383 default_value: None,
9384 fixed_value: None,
9385 use_kind: None,
9386 form: None,
9387 inheritable: false,
9388 id: None,
9389 annotation: None,
9390 source: None,
9391 },
9392 use_kind: kind,
9393 };
9394
9395 let ag = AttributeGroupData {
9396 name: Some(grp_name),
9397 target_namespace: None,
9398 ref_name: None,
9399 attributes: vec![
9400 make_attr(opt_name, AuK::Optional),
9401 make_attr(banned_name, AuK::Prohibited),
9402 ],
9403 attribute_groups: Vec::new(),
9404 attribute_wildcard: None,
9405 id: None,
9406 annotation: None,
9407 source: None,
9408 resolved_ref: None,
9409 resolved_attribute_groups: Vec::new(),
9410 resolved_attributes: vec![
9411 crate::arenas::ResolvedAttributeUse {
9412 resolved_type: None,
9413 resolved_ref: None,
9414 },
9415 crate::arenas::ResolvedAttributeUse {
9416 resolved_type: None,
9417 resolved_ref: None,
9418 },
9419 ],
9420 redefine_original: None,
9421 redefine_requires_restriction_check: false,
9422 };
9423 let ag_key = schema_set.arenas.alloc_attribute_group(ag);
9424
9425 let uses = collect_flat_attribute_uses_for_group(&schema_set, ag_key);
9426 assert_eq!(
9428 uses.len(),
9429 1,
9430 "prohibited attribute uses must be filtered out"
9431 );
9432 assert_eq!(uses[0].name, opt_name);
9433 }
9434
9435 fn wildcard_with_ns(namespace: WildcardNamespace) -> WildcardResult {
9440 WildcardResult {
9441 namespace,
9442 process_contents: ProcessContents::Strict,
9443 not_namespace: Vec::new(),
9444 not_qname: Vec::new(),
9445 id: None,
9446 annotation: None,
9447 source: None,
9448 }
9449 }
9450
9451 #[test]
9452 fn test_normalize_any() {
9453 let schema_set = SchemaSet::new();
9454 let wc = wildcard_with_ns(WildcardNamespace::Any);
9455 let eff = normalize_attribute_wildcard(&schema_set, &wc, None);
9456 assert!(matches!(eff.namespace, CanonicalNs::Any));
9457 }
9458
9459 #[test]
9460 fn test_normalize_list_resolves_tokens() {
9461 use crate::parser::frames::NamespaceToken;
9462 let schema_set = SchemaSet::new();
9463 let ns_a = schema_set.name_table.add("http://a");
9464 let target = schema_set.name_table.add("http://t");
9465
9466 let wc = wildcard_with_ns(WildcardNamespace::List(vec![
9467 NamespaceToken::Uri(ns_a),
9468 NamespaceToken::TargetNamespace,
9469 NamespaceToken::Local,
9470 ]));
9471 let eff = normalize_attribute_wildcard(&schema_set, &wc, Some(target));
9472 match eff.namespace {
9473 CanonicalNs::Enum(set) => {
9474 assert!(set.contains(&Some(ns_a)));
9475 assert!(set.contains(&Some(target)));
9476 assert!(set.contains(&None));
9477 assert_eq!(set.len(), 3);
9478 }
9479 _ => panic!("expected Enum"),
9480 }
9481 }
9482
9483 #[test]
9484 fn test_normalize_other_xsd10_vs_xsd11() {
9485 let schema_10 = SchemaSet::new();
9489 let schema_11 = SchemaSet::xsd11();
9490 let target_10 = schema_10.name_table.add("http://t");
9491 let target_11 = schema_11.name_table.add("http://t");
9492
9493 let wc10 = wildcard_with_ns(WildcardNamespace::Other);
9494 let wc11 = wildcard_with_ns(WildcardNamespace::Other);
9495
9496 let eff10 = normalize_attribute_wildcard(&schema_10, &wc10, Some(target_10));
9497 let eff11 = normalize_attribute_wildcard(&schema_11, &wc11, Some(target_11));
9498
9499 match eff10.namespace {
9500 CanonicalNs::Not(set) => {
9501 assert!(set.contains(&Some(target_10)));
9502 assert!(set.contains(&None), "XSD 1.0 ##other excludes absent");
9503 }
9504 _ => panic!("expected Not"),
9505 }
9506 match eff11.namespace {
9507 CanonicalNs::Not(set) => {
9508 assert!(set.contains(&Some(target_11)));
9509 assert!(!set.contains(&None), "XSD 1.1 ##other admits absent");
9510 }
9511 _ => panic!("expected Not"),
9512 }
9513 }
9514
9515 #[test]
9516 fn test_normalize_other_absent_target_namespace() {
9517 let schema_10 = SchemaSet::new();
9524 let schema_11 = SchemaSet::xsd11();
9525
9526 let wc = wildcard_with_ns(WildcardNamespace::Other);
9527 let eff10 = normalize_attribute_wildcard(&schema_10, &wc, None);
9528 let eff11 = normalize_attribute_wildcard(&schema_11, &wc, None);
9529
9530 for (label, eff) in [("XSD 1.0", eff10), ("XSD 1.1", eff11)] {
9531 match eff.namespace {
9532 CanonicalNs::Not(set) => {
9533 assert!(
9534 set.contains(&None),
9535 "{}: ##other with absent target MUST exclude the absent namespace",
9536 label,
9537 );
9538 }
9539 other => panic!("{}: expected Not, got {:?}", label, other),
9540 }
9541 }
9542 }
9543
9544 #[test]
9545 fn test_effective_wildcard_restricts_defined_covers_declared_qname() {
9546 use crate::arenas::AttributeDeclData;
9553 use crate::parser::frames::NotQNameItem;
9554
9555 let mut schema_set = SchemaSet::new();
9556 let declared_name = schema_set.name_table.add("declared_attr");
9557
9558 let attr_data = AttributeDeclData {
9560 name: Some(declared_name),
9561 target_namespace: None,
9562 ref_name: None,
9563 type_ref: None,
9564 inline_type: None,
9565 default_value: None,
9566 fixed_value: None,
9567 use_kind: None,
9568 form: None,
9569 inheritable: false,
9570 id: None,
9571 annotation: None,
9572 source: None,
9573 resolved_type: None,
9574 resolved_ref: None,
9575 };
9576 let attr_key = schema_set.arenas.alloc_attribute(attr_data);
9577 schema_set
9578 .get_or_create_namespace(None)
9579 .register_attribute(declared_name, attr_key);
9580
9581 let base = EffectiveAttributeWildcard {
9583 namespace: CanonicalNs::Any,
9584 not_qname: vec![NotQNameItem::QName {
9585 namespace: None,
9586 local_name: declared_name,
9587 }],
9588 process_contents: ProcessContents::Strict,
9589 };
9590 let derived = EffectiveAttributeWildcard {
9593 namespace: CanonicalNs::Any,
9594 not_qname: vec![NotQNameItem::Defined],
9595 process_contents: ProcessContents::Strict,
9596 };
9597
9598 assert!(
9599 effective_attribute_wildcard_restricts(&schema_set, &derived, &base).is_ok(),
9600 "derived ##defined must cover a base literal QName exclusion \
9601 when the attribute is globally declared"
9602 );
9603
9604 let undeclared = schema_set.name_table.add("undeclared_attr");
9606 let base_undeclared = EffectiveAttributeWildcard {
9607 namespace: CanonicalNs::Any,
9608 not_qname: vec![NotQNameItem::QName {
9609 namespace: None,
9610 local_name: undeclared,
9611 }],
9612 process_contents: ProcessContents::Strict,
9613 };
9614 assert!(
9615 effective_attribute_wildcard_restricts(&schema_set, &derived, &base_undeclared)
9616 .is_err(),
9617 "derived ##defined must NOT cover a base QName exclusion \
9618 when the attribute is not globally declared"
9619 );
9620 }
9621
9622 #[test]
9623 fn test_normalize_folds_not_namespace() {
9624 use crate::parser::frames::NamespaceToken;
9625 let schema_set = SchemaSet::new();
9626 let ns_a = schema_set.name_table.add("http://a");
9627
9628 let mut wc = wildcard_with_ns(WildcardNamespace::Any);
9630 wc.not_namespace = vec![NamespaceToken::Uri(ns_a)];
9631 let eff = normalize_attribute_wildcard(&schema_set, &wc, None);
9632 match eff.namespace {
9633 CanonicalNs::Not(set) => {
9634 assert_eq!(set.len(), 1);
9635 assert!(set.contains(&Some(ns_a)));
9636 }
9637 _ => panic!("expected Not"),
9638 }
9639 }
9640
9641 #[test]
9642 fn test_intersect_any_is_identity() {
9643 let mut s = std::collections::HashSet::new();
9644 let schema_set = SchemaSet::new();
9645 let ns_a = schema_set.name_table.add("http://a");
9646 s.insert(Some(ns_a));
9647
9648 let enum_a = CanonicalNs::Enum(s.clone());
9649 let result = intersect_canonical_ns(&CanonicalNs::Any, &enum_a);
9650 assert_eq!(result, enum_a);
9651 let result2 = intersect_canonical_ns(&enum_a, &CanonicalNs::Any);
9652 assert_eq!(result2, enum_a);
9653 }
9654
9655 #[test]
9656 fn test_intersect_enum_enum_is_set_intersection() {
9657 let schema_set = SchemaSet::new();
9658 let ns_a = schema_set.name_table.add("http://a");
9659 let ns_b = schema_set.name_table.add("http://b");
9660 let ns_c = schema_set.name_table.add("http://c");
9661
9662 let mut s1 = std::collections::HashSet::new();
9663 s1.insert(Some(ns_a));
9664 s1.insert(Some(ns_b));
9665 let mut s2 = std::collections::HashSet::new();
9666 s2.insert(Some(ns_b));
9667 s2.insert(Some(ns_c));
9668
9669 let result = intersect_canonical_ns(&CanonicalNs::Enum(s1), &CanonicalNs::Enum(s2));
9670 match result {
9671 CanonicalNs::Enum(set) => {
9672 assert_eq!(set.len(), 1);
9673 assert!(set.contains(&Some(ns_b)));
9674 }
9675 _ => panic!("expected Enum"),
9676 }
9677 }
9678
9679 #[test]
9680 fn test_intersect_enum_not_is_set_difference() {
9681 let schema_set = SchemaSet::new();
9682 let ns_a = schema_set.name_table.add("http://a");
9683 let ns_b = schema_set.name_table.add("http://b");
9684
9685 let mut s = std::collections::HashSet::new();
9686 s.insert(Some(ns_a));
9687 s.insert(Some(ns_b));
9688 let mut n = std::collections::HashSet::new();
9689 n.insert(Some(ns_b));
9690
9691 let result = intersect_canonical_ns(&CanonicalNs::Enum(s), &CanonicalNs::Not(n));
9692 match result {
9693 CanonicalNs::Enum(set) => {
9694 assert_eq!(set.len(), 1);
9695 assert!(set.contains(&Some(ns_a)));
9696 }
9697 _ => panic!("expected Enum"),
9698 }
9699 }
9700
9701 #[test]
9702 fn test_intersect_not_not_is_union_of_exclusions() {
9703 let schema_set = SchemaSet::new();
9704 let ns_a = schema_set.name_table.add("http://a");
9705 let ns_b = schema_set.name_table.add("http://b");
9706
9707 let mut n1 = std::collections::HashSet::new();
9708 n1.insert(Some(ns_a));
9709 let mut n2 = std::collections::HashSet::new();
9710 n2.insert(Some(ns_b));
9711
9712 let result = intersect_canonical_ns(&CanonicalNs::Not(n1), &CanonicalNs::Not(n2));
9713 match result {
9714 CanonicalNs::Not(set) => {
9715 assert_eq!(set.len(), 2);
9716 assert!(set.contains(&Some(ns_a)));
9717 assert!(set.contains(&Some(ns_b)));
9718 }
9719 _ => panic!("expected Not"),
9720 }
9721 }
9722
9723 #[test]
9724 fn test_canonical_ns_subset_various() {
9725 let schema_set = SchemaSet::new();
9726 let ns_a = schema_set.name_table.add("http://a");
9727 let ns_b = schema_set.name_table.add("http://b");
9728
9729 let empty_set = std::collections::HashSet::new();
9730 let mut s_a = std::collections::HashSet::new();
9731 s_a.insert(Some(ns_a));
9732 let mut s_ab = std::collections::HashSet::new();
9733 s_ab.insert(Some(ns_a));
9734 s_ab.insert(Some(ns_b));
9735
9736 assert!(canonical_ns_subset(&CanonicalNs::Any, &CanonicalNs::Any));
9738 assert!(canonical_ns_subset(
9739 &CanonicalNs::Enum(s_a.clone()),
9740 &CanonicalNs::Any
9741 ));
9742 assert!(canonical_ns_subset(
9743 &CanonicalNs::Not(s_a.clone()),
9744 &CanonicalNs::Any
9745 ));
9746
9747 assert!(!canonical_ns_subset(
9749 &CanonicalNs::Any,
9750 &CanonicalNs::Enum(s_a.clone())
9751 ));
9752
9753 assert!(canonical_ns_subset(
9755 &CanonicalNs::Enum(s_a.clone()),
9756 &CanonicalNs::Enum(s_ab.clone()),
9757 ));
9758 assert!(!canonical_ns_subset(
9759 &CanonicalNs::Enum(s_ab.clone()),
9760 &CanonicalNs::Enum(s_a.clone()),
9761 ));
9762
9763 assert!(canonical_ns_subset(
9765 &CanonicalNs::Enum(s_a.clone()),
9766 &CanonicalNs::Not(empty_set.clone()),
9767 ));
9768 assert!(!canonical_ns_subset(
9769 &CanonicalNs::Enum(s_a.clone()),
9770 &CanonicalNs::Not(s_a.clone()),
9771 ));
9772
9773 assert!(canonical_ns_subset(
9775 &CanonicalNs::Not(s_ab.clone()),
9776 &CanonicalNs::Not(s_a.clone()),
9777 ));
9778 assert!(!canonical_ns_subset(
9779 &CanonicalNs::Not(s_a.clone()),
9780 &CanonicalNs::Not(s_ab.clone()),
9781 ));
9782
9783 assert!(!canonical_ns_subset(
9785 &CanonicalNs::Not(empty_set),
9786 &CanonicalNs::Enum(s_a),
9787 ));
9788 }
9789
9790 #[test]
9791 fn test_effective_attribute_wildcard_absent_no_groups_returns_none() {
9792 let schema_set = SchemaSet::new();
9793 let result = effective_attribute_wildcard(&schema_set, None, None, &[]);
9794 assert!(matches!(result, Ok(None)));
9795 }
9796
9797 #[test]
9798 fn test_effective_attribute_wildcard_local_only() {
9799 let schema_set = SchemaSet::new();
9800 let wc = wildcard_with_ns(WildcardNamespace::Any);
9801 let result = effective_attribute_wildcard(&schema_set, Some(&wc), None, &[]).unwrap();
9802 let eff = result.expect("expected Some");
9803 assert!(matches!(eff.namespace, CanonicalNs::Any));
9804 }
9805
9806 #[test]
9807 fn test_effective_attribute_wildcard_intersects_across_group_and_local() {
9808 use crate::arenas::AttributeGroupData;
9809 use crate::parser::frames::NamespaceToken;
9810
9811 let mut schema_set = SchemaSet::new();
9812 let ns_a = schema_set.name_table.add("http://a");
9813 let ns_b = schema_set.name_table.add("http://b");
9814
9815 let group_wc = WildcardResult {
9817 namespace: WildcardNamespace::List(vec![
9818 NamespaceToken::Uri(ns_a),
9819 NamespaceToken::Uri(ns_b),
9820 ]),
9821 process_contents: ProcessContents::Strict,
9822 not_namespace: Vec::new(),
9823 not_qname: Vec::new(),
9824 id: None,
9825 annotation: None,
9826 source: None,
9827 };
9828 let group = AttributeGroupData {
9829 name: None,
9830 target_namespace: None,
9831 ref_name: None,
9832 attributes: Vec::new(),
9833 attribute_groups: Vec::new(),
9834 attribute_wildcard: Some(group_wc),
9835 id: None,
9836 annotation: None,
9837 source: None,
9838 resolved_ref: None,
9839 resolved_attribute_groups: Vec::new(),
9840 resolved_attributes: Vec::new(),
9841 redefine_original: None,
9842 redefine_requires_restriction_check: false,
9843 };
9844 let group_key = schema_set.arenas.alloc_attribute_group(group);
9845
9846 let local = WildcardResult {
9848 namespace: WildcardNamespace::List(vec![NamespaceToken::Uri(ns_a)]),
9849 process_contents: ProcessContents::Strict,
9850 not_namespace: Vec::new(),
9851 not_qname: Vec::new(),
9852 id: None,
9853 annotation: None,
9854 source: None,
9855 };
9856 let result =
9857 effective_attribute_wildcard(&schema_set, Some(&local), None, &[group_key]).unwrap();
9858 let eff = result.expect("expected Some");
9859 match eff.namespace {
9860 CanonicalNs::Enum(set) => {
9861 assert_eq!(set.len(), 1);
9862 assert!(set.contains(&Some(ns_a)));
9863 }
9864 other => panic!("expected Enum({{ns_a}}), got {:?}", other),
9865 }
9866 }
9867
9868 #[test]
9869 fn test_effective_attribute_wildcard_no_local_uses_first_group_pc() {
9870 use crate::arenas::AttributeGroupData;
9871
9872 let mut schema_set = SchemaSet::new();
9873 let group_wc = WildcardResult {
9874 namespace: WildcardNamespace::Any,
9875 process_contents: ProcessContents::Lax,
9876 not_namespace: Vec::new(),
9877 not_qname: Vec::new(),
9878 id: None,
9879 annotation: None,
9880 source: None,
9881 };
9882 let group = AttributeGroupData {
9883 name: None,
9884 target_namespace: None,
9885 ref_name: None,
9886 attributes: Vec::new(),
9887 attribute_groups: Vec::new(),
9888 attribute_wildcard: Some(group_wc),
9889 id: None,
9890 annotation: None,
9891 source: None,
9892 resolved_ref: None,
9893 resolved_attribute_groups: Vec::new(),
9894 resolved_attributes: Vec::new(),
9895 redefine_original: None,
9896 redefine_requires_restriction_check: false,
9897 };
9898 let group_key = schema_set.arenas.alloc_attribute_group(group);
9899
9900 let result = effective_attribute_wildcard(&schema_set, None, None, &[group_key]).unwrap();
9902 let eff = result.expect("expected Some");
9903 assert_eq!(eff.process_contents, ProcessContents::Lax);
9904 assert!(matches!(eff.namespace, CanonicalNs::Any));
9905 }
9906
9907 #[test]
9908 fn test_effective_wildcard_allows_attribute_basic() {
9909 let schema_set = SchemaSet::new();
9910 let name = schema_set.name_table.add("foo");
9911 let ns_a = schema_set.name_table.add("http://a");
9912
9913 let any_eff = EffectiveAttributeWildcard {
9914 namespace: CanonicalNs::Any,
9915 not_qname: Vec::new(),
9916 process_contents: ProcessContents::Strict,
9917 };
9918 assert!(effective_wildcard_allows_attribute(
9919 &schema_set,
9920 &any_eff,
9921 Some(ns_a),
9922 name,
9923 ));
9924
9925 let mut s = std::collections::HashSet::new();
9926 s.insert(Some(ns_a));
9927 let enum_eff = EffectiveAttributeWildcard {
9928 namespace: CanonicalNs::Enum(s),
9929 not_qname: Vec::new(),
9930 process_contents: ProcessContents::Strict,
9931 };
9932 assert!(effective_wildcard_allows_attribute(
9933 &schema_set,
9934 &enum_eff,
9935 Some(ns_a),
9936 name,
9937 ));
9938 assert!(!effective_wildcard_allows_attribute(
9939 &schema_set,
9940 &enum_eff,
9941 None,
9942 name,
9943 ));
9944 }
9945
9946 #[test]
9947 fn test_effective_wildcard_restricts_enforces_subset() {
9948 let schema_set = SchemaSet::new();
9949 let ns_a = schema_set.name_table.add("http://a");
9950 let ns_b = schema_set.name_table.add("http://b");
9951
9952 let mut s_a = std::collections::HashSet::new();
9953 s_a.insert(Some(ns_a));
9954 let mut s_ab = std::collections::HashSet::new();
9955 s_ab.insert(Some(ns_a));
9956 s_ab.insert(Some(ns_b));
9957
9958 let derived_narrow = EffectiveAttributeWildcard {
9959 namespace: CanonicalNs::Enum(s_a.clone()),
9960 not_qname: Vec::new(),
9961 process_contents: ProcessContents::Strict,
9962 };
9963 let base_wide = EffectiveAttributeWildcard {
9964 namespace: CanonicalNs::Enum(s_ab.clone()),
9965 not_qname: Vec::new(),
9966 process_contents: ProcessContents::Strict,
9967 };
9968
9969 assert!(
9970 effective_attribute_wildcard_restricts(&schema_set, &derived_narrow, &base_wide)
9971 .is_ok()
9972 );
9973 assert!(
9974 effective_attribute_wildcard_restricts(&schema_set, &base_wide, &derived_narrow)
9975 .is_err()
9976 );
9977 }
9978
9979 #[test]
9980 fn test_effective_wildcard_restricts_enforces_process_contents() {
9981 let schema_set = SchemaSet::new();
9982 let skip_eff = EffectiveAttributeWildcard {
9983 namespace: CanonicalNs::Any,
9984 not_qname: Vec::new(),
9985 process_contents: ProcessContents::Skip,
9986 };
9987 let strict_eff = EffectiveAttributeWildcard {
9988 namespace: CanonicalNs::Any,
9989 not_qname: Vec::new(),
9990 process_contents: ProcessContents::Strict,
9991 };
9992 assert!(
9994 effective_attribute_wildcard_restricts(&schema_set, &strict_eff, &skip_eff).is_ok()
9995 );
9996 assert!(
9998 effective_attribute_wildcard_restricts(&schema_set, &skip_eff, &strict_eff).is_err()
9999 );
10000 }
10001
10002 #[test]
10003 fn test_validate_attribute_restriction_rejects_added_wildcard() {
10004 let mut schema_set = SchemaSet::new();
10006 let base = create_complex_type_data(None);
10007 let base_key = schema_set.arenas.alloc_complex_type(base);
10008
10009 let mut derived = create_complex_type_data(None);
10010 derived.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::Any));
10011 derived.derivation_method = Some(DerivationMethod::Restriction);
10012 derived.resolved_base_type = Some(TypeKey::Complex(base_key));
10013 let derived_key = schema_set.arenas.alloc_complex_type(derived);
10014
10015 let derived_ref = schema_set.arenas.complex_types.get(derived_key).unwrap();
10016 let base_ref = schema_set.arenas.complex_types.get(base_key).unwrap();
10017 let result = validate_attribute_restriction(&schema_set, derived_ref, base_ref);
10018 assert!(result.is_err());
10019 if let Err(SchemaError::StructuralError {
10020 constraint,
10021 message,
10022 ..
10023 }) = result
10024 {
10025 assert_eq!(constraint, "derivation-ok-restriction");
10026 assert!(
10027 message.contains("wildcard"),
10028 "message should mention wildcard, got: {}",
10029 message
10030 );
10031 } else {
10032 panic!("expected StructuralError");
10033 }
10034 }
10035
10036 #[test]
10037 fn test_validate_attribute_restriction_accepts_narrower_wildcard() {
10038 use crate::parser::frames::NamespaceToken;
10039 let mut schema_set = SchemaSet::new();
10040 let ns_a = schema_set.name_table.add("http://a");
10041
10042 let mut base = create_complex_type_data(None);
10043 base.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::Any));
10044 let base_key = schema_set.arenas.alloc_complex_type(base);
10045
10046 let mut derived = create_complex_type_data(None);
10047 derived.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::List(vec![
10048 NamespaceToken::Uri(ns_a),
10049 ])));
10050 derived.derivation_method = Some(DerivationMethod::Restriction);
10051 derived.resolved_base_type = Some(TypeKey::Complex(base_key));
10052 let derived_key = schema_set.arenas.alloc_complex_type(derived);
10053
10054 let derived_ref = schema_set.arenas.complex_types.get(derived_key).unwrap();
10055 let base_ref = schema_set.arenas.complex_types.get(base_key).unwrap();
10056 assert!(validate_attribute_restriction(&schema_set, derived_ref, base_ref).is_ok());
10057 }
10058
10059 #[test]
10060 fn test_validate_attribute_restriction_allows_removing_wildcard() {
10061 let mut schema_set = SchemaSet::new();
10064 let mut base = create_complex_type_data(None);
10065 base.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::Any));
10066 let base_key = schema_set.arenas.alloc_complex_type(base);
10067
10068 let mut derived = create_complex_type_data(None);
10069 derived.derivation_method = Some(DerivationMethod::Restriction);
10070 derived.resolved_base_type = Some(TypeKey::Complex(base_key));
10071 let derived_key = schema_set.arenas.alloc_complex_type(derived);
10072
10073 let derived_ref = schema_set.arenas.complex_types.get(derived_key).unwrap();
10074 let base_ref = schema_set.arenas.complex_types.get(base_key).unwrap();
10075 assert!(validate_attribute_restriction(&schema_set, derived_ref, base_ref).is_ok());
10076 }
10077
10078 #[test]
10079 fn test_redefine_attribute_group_rejects_broader_wildcard() {
10080 use crate::arenas::AttributeGroupData;
10085 use crate::parser::frames::NamespaceToken;
10086
10087 let mut schema_set = SchemaSet::new();
10088 let ns_a = schema_set.name_table.add("http://a");
10089
10090 let mut original_wc = wildcard_with_ns(WildcardNamespace::Any);
10092 original_wc.not_namespace = vec![NamespaceToken::Uri(ns_a)];
10093 let original = AttributeGroupData {
10094 name: None,
10095 target_namespace: None,
10096 ref_name: None,
10097 attributes: Vec::new(),
10098 attribute_groups: Vec::new(),
10099 attribute_wildcard: Some(original_wc),
10100 id: None,
10101 annotation: None,
10102 source: None,
10103 resolved_ref: None,
10104 resolved_attribute_groups: Vec::new(),
10105 resolved_attributes: Vec::new(),
10106 redefine_original: None,
10107 redefine_requires_restriction_check: false,
10108 };
10109 let original_key = schema_set.arenas.alloc_attribute_group(original);
10110
10111 let derived = AttributeGroupData {
10113 name: None,
10114 target_namespace: None,
10115 ref_name: None,
10116 attributes: Vec::new(),
10117 attribute_groups: Vec::new(),
10118 attribute_wildcard: Some(wildcard_with_ns(WildcardNamespace::Any)),
10119 id: None,
10120 annotation: None,
10121 source: None,
10122 resolved_ref: None,
10123 resolved_attribute_groups: Vec::new(),
10124 resolved_attributes: Vec::new(),
10125 redefine_original: Some(original_key),
10126 redefine_requires_restriction_check: true,
10127 };
10128 schema_set.arenas.alloc_attribute_group(derived);
10129
10130 let mut errors = Vec::new();
10131 let mut stats = DerivationStats::default();
10132 validate_all_redefine_attribute_group_restrictions(&schema_set, &mut errors, &mut stats);
10133
10134 assert!(
10135 !errors.is_empty(),
10136 "expected a src-redefine.7.2.2 error for broader derived wildcard"
10137 );
10138 let msg = match &errors[0] {
10139 SchemaError::StructuralError {
10140 constraint,
10141 message,
10142 ..
10143 } => {
10144 assert_eq!(*constraint, "src-redefine.7.2.2");
10145 message.clone()
10146 }
10147 _ => panic!("expected StructuralError"),
10148 };
10149 assert!(
10150 msg.contains("wildcard") || msg.contains("restriction"),
10151 "error should mention wildcard restriction, got: {}",
10152 msg
10153 );
10154 }
10155
10156 #[test]
10157 fn test_redefine_attribute_group_effective_wildcard_admits_inherited_attr() {
10158 use crate::arenas::{AttributeGroupData, ResolvedAttributeUse};
10165 use crate::parser::frames::{
10166 AttributeFrameResult, AttributeUseKind as AuK, AttributeUseResult, NamespaceToken,
10167 };
10168
10169 let mut schema_set = SchemaSet::new();
10170 let ns_a = schema_set.name_table.add("http://a");
10171 let attr_name = schema_set.name_table.add("foo");
10172
10173 let nested_wc = WildcardResult {
10175 namespace: WildcardNamespace::List(vec![NamespaceToken::Uri(ns_a)]),
10176 process_contents: ProcessContents::Strict,
10177 not_namespace: Vec::new(),
10178 not_qname: Vec::new(),
10179 id: None,
10180 annotation: None,
10181 source: None,
10182 };
10183 let nested = AttributeGroupData {
10184 name: None,
10185 target_namespace: None,
10186 ref_name: None,
10187 attributes: Vec::new(),
10188 attribute_groups: Vec::new(),
10189 attribute_wildcard: Some(nested_wc),
10190 id: None,
10191 annotation: None,
10192 source: None,
10193 resolved_ref: None,
10194 resolved_attribute_groups: Vec::new(),
10195 resolved_attributes: Vec::new(),
10196 redefine_original: None,
10197 redefine_requires_restriction_check: false,
10198 };
10199 let nested_key = schema_set.arenas.alloc_attribute_group(nested);
10200
10201 let original = AttributeGroupData {
10203 name: None,
10204 target_namespace: None,
10205 ref_name: None,
10206 attributes: Vec::new(),
10207 attribute_groups: Vec::new(),
10208 attribute_wildcard: None,
10209 id: None,
10210 annotation: None,
10211 source: None,
10212 resolved_ref: None,
10213 resolved_attribute_groups: vec![nested_key],
10214 resolved_attributes: Vec::new(),
10215 redefine_original: None,
10216 redefine_requires_restriction_check: false,
10217 };
10218 let original_key = schema_set.arenas.alloc_attribute_group(original);
10219
10220 let attr_use = AttributeUseResult {
10223 attribute: AttributeFrameResult {
10224 name: Some(attr_name),
10225 ref_name: None,
10226 target_namespace: Some(ns_a),
10227 type_ref: None,
10228 inline_type: None,
10229 default_value: None,
10230 fixed_value: None,
10231 use_kind: None,
10232 form: None,
10233 inheritable: false,
10234 id: None,
10235 annotation: None,
10236 source: None,
10237 },
10238 use_kind: AuK::Optional,
10239 };
10240 let derived = AttributeGroupData {
10241 name: None,
10242 target_namespace: None,
10243 ref_name: None,
10244 attributes: vec![attr_use],
10245 attribute_groups: Vec::new(),
10246 attribute_wildcard: None,
10247 id: None,
10248 annotation: None,
10249 source: None,
10250 resolved_ref: None,
10251 resolved_attribute_groups: vec![nested_key],
10252 resolved_attributes: vec![ResolvedAttributeUse {
10253 resolved_type: None,
10254 resolved_ref: None,
10255 }],
10256 redefine_original: Some(original_key),
10257 redefine_requires_restriction_check: true,
10258 };
10259 schema_set.arenas.alloc_attribute_group(derived);
10260
10261 let mut errors = Vec::new();
10262 let mut stats = DerivationStats::default();
10263 validate_all_redefine_attribute_group_restrictions(&schema_set, &mut errors, &mut stats);
10264
10265 assert!(
10266 errors.is_empty(),
10267 "attribute admitted by inherited effective wildcard should not error; got: {:?}",
10268 errors
10269 .iter()
10270 .map(|e| format!("{:?}", e))
10271 .collect::<Vec<_>>()
10272 );
10273 }
10274}