1use crate::compiler::{
8 term_matches_with_substitution, ActiveStates, AllGroupModel, AllGroupState,
9 ContentModelMatcher, NfaTable, NfaTerm, OpenContentMode as AllGroupOpenContentMode,
10 SubstitutionGroupMap, TermMatchResult,
11};
12use crate::ids::{ElementKey, NameId, TypeKey};
13use crate::schema::model::XsdVersion;
14use crate::types::complex::{
15 not_qnames_exclude, NamespaceConstraint, OpenContentMode as TypesOpenContentMode,
16 ProcessContents,
17};
18
19#[derive(Debug, Clone)]
21pub struct OpenContentInfo {
22 pub mode: TypesOpenContentMode,
24 pub namespace_constraint: NamespaceConstraint,
26 pub process_contents: ProcessContents,
28 pub not_qnames: Vec<(Option<NameId>, NameId)>,
30}
31
32#[derive(Debug, Clone, Copy)]
34pub struct ElementMatchInfo {
35 pub element_key: Option<ElementKey>,
37 pub resolved_type: Option<TypeKey>,
39 pub process_contents: Option<ProcessContents>,
41}
42
43#[cfg(feature = "xsd11")]
45#[derive(Debug, Clone)]
46pub enum AllGroupExtPhase {
47 AllGroup,
49 Nfa(ActiveStates),
51}
52
53#[derive(Debug, Clone)]
59pub enum ContentValidatorState {
60 Nfa {
62 nfa: NfaTable,
63 active_states: ActiveStates,
64 open_content: Option<OpenContentInfo>,
65 },
66 AllGroup {
68 model: AllGroupModel,
69 state: AllGroupState,
70 suffix_locked: bool,
74 },
75 #[cfg(feature = "xsd11")]
77 AllGroupExtension {
78 model: AllGroupModel,
79 state: AllGroupState,
80 extension_nfa: NfaTable,
81 phase: AllGroupExtPhase,
82 },
83 Simple,
85 Empty,
87}
88
89impl ContentValidatorState {
90 pub fn from_matcher(matcher: ContentModelMatcher) -> Self {
92 match matcher {
93 ContentModelMatcher::Nfa(nfa) => Self::from_nfa(nfa),
94 ContentModelMatcher::AllGroup(model) => Self::from_all_group(model),
95 ContentModelMatcher::WithOpenContent {
96 nfa,
97 mode,
98 wildcard,
99 } => {
100 let oc = wildcard.map(|w| OpenContentInfo {
101 mode,
102 namespace_constraint: w.namespace_constraint,
103 process_contents: w.process_contents,
104 not_qnames: w.not_qnames,
105 });
106 let initial = ActiveStates::from_nfa(&nfa);
107 Self::Nfa {
108 nfa,
109 active_states: initial,
110 open_content: oc,
111 }
112 }
113 #[cfg(feature = "xsd11")]
114 ContentModelMatcher::AllGroupExtension {
115 base_model,
116 extension_nfa,
117 } => {
118 let state = base_model.create_state();
119 Self::AllGroupExtension {
120 model: base_model,
121 state,
122 extension_nfa,
123 phase: AllGroupExtPhase::AllGroup,
124 }
125 }
126 }
127 }
128
129 pub fn from_nfa(nfa: NfaTable) -> Self {
133 let initial = ActiveStates::from_nfa(&nfa);
134 ContentValidatorState::Nfa {
135 nfa,
136 active_states: initial,
137 open_content: None,
138 }
139 }
140
141 pub fn from_all_group(model: AllGroupModel) -> Self {
143 let state = model.create_state();
144 ContentValidatorState::AllGroup {
145 model,
146 state,
147 suffix_locked: false,
148 }
149 }
150
151 pub fn advance_element(
157 &mut self,
158 name: NameId,
159 namespace: Option<NameId>,
160 target_ns: Option<NameId>,
161 xsd_version: XsdVersion,
162 subst_groups: Option<&SubstitutionGroupMap>,
163 ) -> Option<ElementMatchInfo> {
164 match self {
165 ContentValidatorState::Nfa {
166 nfa,
167 active_states,
168 open_content,
169 } => {
170 let mi = active_states.find_match_info(
172 nfa,
173 name,
174 namespace,
175 target_ns,
176 subst_groups,
177 xsd_version,
178 );
179 let match_info = ElementMatchInfo {
180 element_key: mi.element_key,
181 resolved_type: mi.resolved_type,
182 process_contents: mi.process_contents,
183 };
184
185 let next = match xsd_version {
186 XsdVersion::V1_0 => active_states.clone().advance(
187 nfa,
188 name,
189 namespace,
190 target_ns,
191 subst_groups,
192 xsd_version,
193 ),
194 XsdVersion::V1_1 => active_states.clone().advance_with_priority(
195 nfa,
196 name,
197 namespace,
198 target_ns,
199 subst_groups,
200 xsd_version,
201 ),
202 };
203 if next.is_empty() {
204 if let Some(oc) = open_content {
206 let allow = match oc.mode {
207 TypesOpenContentMode::Interleave => true,
208 TypesOpenContentMode::Suffix => active_states.contains_accept(nfa),
209 TypesOpenContentMode::None => false,
210 };
211 if allow
212 && open_content_allows(
213 &oc.namespace_constraint,
214 &oc.not_qnames,
215 name,
216 namespace,
217 target_ns,
218 )
219 {
220 if matches!(oc.mode, TypesOpenContentMode::Suffix) {
223 *active_states = ActiveStates::Simple([nfa.accept_state].into());
224 }
225 return Some(ElementMatchInfo {
226 element_key: None,
227 resolved_type: None,
228 process_contents: Some(oc.process_contents),
229 });
230 }
231 }
232 return None;
233 }
234 *active_states = next;
235 Some(match_info)
236 }
237 ContentValidatorState::AllGroup {
238 model,
239 state,
240 suffix_locked,
241 } => {
242 if !*suffix_locked {
245 for (i, particle) in model.particles.iter().enumerate() {
246 if !state.can_accept(model, i) {
247 continue;
248 }
249 let result = term_matches_with_substitution(
250 &particle.term,
251 name,
252 namespace,
253 target_ns,
254 subst_groups,
255 xsd_version,
256 );
257 if result == TermMatchResult::Match {
258 if state.accept(model, i) {
259 let info = match &particle.term {
260 NfaTerm::Element {
261 name: term_name,
262 namespace: term_ns,
263 element_key,
264 resolved_type,
265 } => {
266 if *term_name == name && *term_ns == namespace {
267 ElementMatchInfo {
269 element_key: *element_key,
270 resolved_type: *resolved_type,
271 process_contents: None,
272 }
273 } else {
274 ElementMatchInfo {
276 element_key: None,
277 resolved_type: None,
278 process_contents: None,
279 }
280 }
281 }
282 NfaTerm::Wildcard {
283 process_contents, ..
284 } => ElementMatchInfo {
285 element_key: None,
286 resolved_type: None,
287 process_contents: Some(*process_contents),
288 },
289 };
290 return Some(info);
291 }
292 return None;
293 }
294 }
295 }
296 if let Some(oc) = &model.open_content {
299 let allow = match oc.mode {
300 AllGroupOpenContentMode::Interleave => true,
301 AllGroupOpenContentMode::Suffix => state.is_satisfied(model),
302 AllGroupOpenContentMode::None => false,
303 };
304 if allow
305 && open_content_allows(
306 &oc.namespace_constraint,
307 &oc.not_qnames,
308 name,
309 namespace,
310 target_ns,
311 )
312 {
313 if matches!(oc.mode, AllGroupOpenContentMode::Suffix) {
317 *suffix_locked = true;
318 }
319 return Some(ElementMatchInfo {
320 element_key: None,
321 resolved_type: None,
322 process_contents: Some(oc.process_contents),
323 });
324 }
325 }
326 None
327 }
328 #[cfg(feature = "xsd11")]
329 ContentValidatorState::AllGroupExtension {
330 model,
331 state,
332 extension_nfa,
333 phase,
334 } => {
335 match phase {
336 AllGroupExtPhase::AllGroup => {
337 for (i, particle) in model.particles.iter().enumerate() {
339 if !state.can_accept(model, i) {
340 continue;
341 }
342 let result = term_matches_with_substitution(
343 &particle.term,
344 name,
345 namespace,
346 target_ns,
347 subst_groups,
348 xsd_version,
349 );
350 if result == TermMatchResult::Match {
351 if state.accept(model, i) {
352 let info = match &particle.term {
353 NfaTerm::Element {
354 name: term_name,
355 namespace: term_ns,
356 element_key,
357 resolved_type,
358 } => {
359 if *term_name == name && *term_ns == namespace {
360 ElementMatchInfo {
361 element_key: *element_key,
362 resolved_type: *resolved_type,
363 process_contents: None,
364 }
365 } else {
366 ElementMatchInfo {
368 element_key: None,
369 resolved_type: None,
370 process_contents: None,
371 }
372 }
373 }
374 NfaTerm::Wildcard {
375 process_contents, ..
376 } => ElementMatchInfo {
377 element_key: None,
378 resolved_type: None,
379 process_contents: Some(*process_contents),
380 },
381 };
382 return Some(info);
383 }
384 return None;
385 }
386 }
387
388 if state.is_satisfied(model) {
391 let initial = ActiveStates::from_nfa(extension_nfa);
392 let mi = initial.find_match_info(
393 extension_nfa,
394 name,
395 namespace,
396 target_ns,
397 subst_groups,
398 xsd_version,
399 );
400 let match_info = ElementMatchInfo {
401 element_key: mi.element_key,
402 resolved_type: mi.resolved_type,
403 process_contents: mi.process_contents,
404 };
405 let next = initial.advance_with_priority(
406 extension_nfa,
407 name,
408 namespace,
409 target_ns,
410 subst_groups,
411 xsd_version,
412 );
413 if !next.is_empty() {
414 *phase = AllGroupExtPhase::Nfa(next);
415 return Some(match_info);
416 }
417 }
418
419 if let Some(oc) = &model.open_content {
421 let allow = match oc.mode {
422 AllGroupOpenContentMode::Interleave => true,
423 AllGroupOpenContentMode::Suffix => state.is_satisfied(model),
424 AllGroupOpenContentMode::None => false,
425 };
426 if allow
427 && open_content_allows(
428 &oc.namespace_constraint,
429 &oc.not_qnames,
430 name,
431 namespace,
432 target_ns,
433 )
434 {
435 return Some(ElementMatchInfo {
436 element_key: None,
437 resolved_type: None,
438 process_contents: Some(oc.process_contents),
439 });
440 }
441 }
442 None
443 }
444 AllGroupExtPhase::Nfa(active_states) => {
445 let mi = active_states.find_match_info(
447 extension_nfa,
448 name,
449 namespace,
450 target_ns,
451 subst_groups,
452 xsd_version,
453 );
454 let match_info = ElementMatchInfo {
455 element_key: mi.element_key,
456 resolved_type: mi.resolved_type,
457 process_contents: mi.process_contents,
458 };
459 let next = active_states.clone().advance_with_priority(
460 extension_nfa,
461 name,
462 namespace,
463 target_ns,
464 subst_groups,
465 xsd_version,
466 );
467 if next.is_empty() {
468 if let Some(oc) = &model.open_content {
470 let allow = match oc.mode {
471 AllGroupOpenContentMode::Interleave => true,
472 AllGroupOpenContentMode::Suffix => {
473 active_states.contains_accept(extension_nfa)
474 }
475 AllGroupOpenContentMode::None => false,
476 };
477 if allow
478 && open_content_allows(
479 &oc.namespace_constraint,
480 &oc.not_qnames,
481 name,
482 namespace,
483 target_ns,
484 )
485 {
486 return Some(ElementMatchInfo {
487 element_key: None,
488 resolved_type: None,
489 process_contents: Some(oc.process_contents),
490 });
491 }
492 }
493 return None;
494 }
495 *active_states = next;
496 Some(match_info)
497 }
498 }
499 }
500 ContentValidatorState::Simple | ContentValidatorState::Empty => {
501 None
503 }
504 }
505 }
506
507 pub fn is_complete(&self) -> bool {
512 match self {
513 ContentValidatorState::Nfa {
514 nfa, active_states, ..
515 } => active_states.contains_accept(nfa),
516 ContentValidatorState::AllGroup { model, state, .. } => {
517 if model.outer_optional && !state.has_any_consumed() {
520 return true;
521 }
522 state.is_satisfied(model)
523 }
524 #[cfg(feature = "xsd11")]
525 ContentValidatorState::AllGroupExtension {
526 model,
527 state,
528 extension_nfa,
529 phase,
530 } => {
531 let all_satisfied = if model.outer_optional && !state.has_any_consumed() {
533 true
534 } else {
535 state.is_satisfied(model)
536 };
537 if !all_satisfied {
538 return false;
539 }
540 match phase {
541 AllGroupExtPhase::AllGroup => {
542 let initial = ActiveStates::from_nfa(extension_nfa);
544 initial.contains_accept(extension_nfa)
545 }
546 AllGroupExtPhase::Nfa(active_states) => {
547 active_states.contains_accept(extension_nfa)
548 }
549 }
550 }
551 ContentValidatorState::Simple | ContentValidatorState::Empty => true,
552 }
553 }
554
555 pub fn would_accept(
559 &self,
560 name: NameId,
561 namespace: Option<NameId>,
562 target_ns: Option<NameId>,
563 xsd_version: XsdVersion,
564 subst_groups: Option<&SubstitutionGroupMap>,
565 ) -> bool {
566 match self {
567 ContentValidatorState::Nfa {
568 nfa,
569 active_states,
570 open_content,
571 } => {
572 let next = match xsd_version {
573 XsdVersion::V1_0 => active_states.clone().advance(
574 nfa,
575 name,
576 namespace,
577 target_ns,
578 subst_groups,
579 xsd_version,
580 ),
581 XsdVersion::V1_1 => active_states.clone().advance_with_priority(
582 nfa,
583 name,
584 namespace,
585 target_ns,
586 subst_groups,
587 xsd_version,
588 ),
589 };
590 if !next.is_empty() {
591 return true;
592 }
593 if let Some(oc) = open_content {
595 let allow = match oc.mode {
596 TypesOpenContentMode::Interleave => true,
597 TypesOpenContentMode::Suffix => active_states.contains_accept(nfa),
598 TypesOpenContentMode::None => false,
599 };
600 if allow
601 && open_content_allows(
602 &oc.namespace_constraint,
603 &oc.not_qnames,
604 name,
605 namespace,
606 target_ns,
607 )
608 {
609 return true;
610 }
611 }
612 false
613 }
614 ContentValidatorState::AllGroup {
615 model,
616 state,
617 suffix_locked,
618 } => {
619 if !*suffix_locked {
620 for (i, particle) in model.particles.iter().enumerate() {
621 if !state.can_accept(model, i) {
622 continue;
623 }
624 let result = term_matches_with_substitution(
625 &particle.term,
626 name,
627 namespace,
628 target_ns,
629 subst_groups,
630 xsd_version,
631 );
632 if result == TermMatchResult::Match {
633 return true;
634 }
635 }
636 }
637 if let Some(oc) = &model.open_content {
639 let allow = match oc.mode {
640 AllGroupOpenContentMode::Interleave => true,
641 AllGroupOpenContentMode::Suffix => state.is_satisfied(model),
642 AllGroupOpenContentMode::None => false,
643 };
644 if allow
645 && open_content_allows(
646 &oc.namespace_constraint,
647 &oc.not_qnames,
648 name,
649 namespace,
650 target_ns,
651 )
652 {
653 return true;
654 }
655 }
656 false
657 }
658 #[cfg(feature = "xsd11")]
659 ContentValidatorState::AllGroupExtension {
660 model,
661 state,
662 extension_nfa,
663 phase,
664 } => {
665 match phase {
666 AllGroupExtPhase::AllGroup => {
667 for (i, particle) in model.particles.iter().enumerate() {
669 if !state.can_accept(model, i) {
670 continue;
671 }
672 let result = term_matches_with_substitution(
673 &particle.term,
674 name,
675 namespace,
676 target_ns,
677 subst_groups,
678 xsd_version,
679 );
680 if result == TermMatchResult::Match {
681 return true;
682 }
683 }
684 if state.is_satisfied(model) {
686 let initial = ActiveStates::from_nfa(extension_nfa);
687 let next = initial.advance_with_priority(
688 extension_nfa,
689 name,
690 namespace,
691 target_ns,
692 subst_groups,
693 xsd_version,
694 );
695 if !next.is_empty() {
696 return true;
697 }
698 }
699 if let Some(oc) = &model.open_content {
701 let allow = match oc.mode {
702 AllGroupOpenContentMode::Interleave => true,
703 AllGroupOpenContentMode::Suffix => state.is_satisfied(model),
704 AllGroupOpenContentMode::None => false,
705 };
706 if allow
707 && open_content_allows(
708 &oc.namespace_constraint,
709 &oc.not_qnames,
710 name,
711 namespace,
712 target_ns,
713 )
714 {
715 return true;
716 }
717 }
718 false
719 }
720 AllGroupExtPhase::Nfa(active_states) => {
721 let next = active_states.clone().advance_with_priority(
723 extension_nfa,
724 name,
725 namespace,
726 target_ns,
727 subst_groups,
728 xsd_version,
729 );
730 if !next.is_empty() {
731 return true;
732 }
733 if let Some(oc) = &model.open_content {
735 let allow = match oc.mode {
736 AllGroupOpenContentMode::Interleave => true,
737 AllGroupOpenContentMode::Suffix => {
738 active_states.contains_accept(extension_nfa)
739 }
740 AllGroupOpenContentMode::None => false,
741 };
742 if allow
743 && open_content_allows(
744 &oc.namespace_constraint,
745 &oc.not_qnames,
746 name,
747 namespace,
748 target_ns,
749 )
750 {
751 return true;
752 }
753 }
754 false
755 }
756 }
757 }
758 ContentValidatorState::Simple | ContentValidatorState::Empty => false,
759 }
760 }
761}
762
763fn open_content_allows(
767 ns_constraint: &NamespaceConstraint,
768 not_qnames: &[(Option<NameId>, NameId)],
769 name: NameId,
770 namespace: Option<NameId>,
771 target_ns: Option<NameId>,
772) -> bool {
773 ns_constraint.matches(namespace, target_ns, XsdVersion::V1_1)
774 && !not_qnames_exclude(not_qnames, namespace, name)
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780 use crate::compiler::{NfaState, NfaTerm};
781
782 fn single_element_nfa(name: NameId, namespace: Option<NameId>) -> NfaTable {
784 let mut s0 = NfaState::epsilon(0, None);
788 s0.add_epsilon(1);
789
790 let mut s1 = NfaState::with_term(1, NfaTerm::element(name, namespace, None), None);
791 s1.add_consume(2);
792
793 let s2 = NfaState::epsilon(2, None);
794
795 NfaTable::new(vec![s0, s1, s2], 0, 2)
796 }
797
798 fn sequence_nfa(
800 name_a: NameId,
801 ns_a: Option<NameId>,
802 name_b: NameId,
803 ns_b: Option<NameId>,
804 ) -> NfaTable {
805 let mut s0 = NfaState::epsilon(0, None);
811 s0.add_epsilon(1);
812
813 let mut s1 = NfaState::with_term(1, NfaTerm::element(name_a, ns_a, None), None);
814 s1.add_consume(2);
815
816 let mut s2 = NfaState::epsilon(2, None);
817 s2.add_epsilon(3);
818
819 let mut s3 = NfaState::with_term(3, NfaTerm::element(name_b, ns_b, None), None);
820 s3.add_consume(4);
821
822 let s4 = NfaState::epsilon(4, None);
823
824 NfaTable::new(vec![s0, s1, s2, s3, s4], 0, 4)
825 }
826
827 #[test]
828 fn test_nfa_single_element_accepted() {
829 let name = NameId(1);
830 let nfa = single_element_nfa(name, None);
831 let mut state = ContentValidatorState::from_nfa(nfa);
832
833 assert!(
834 !state.is_complete(),
835 "should not be complete before any element"
836 );
837 assert!(state
838 .advance_element(name, None, None, XsdVersion::V1_0, None)
839 .is_some());
840 assert!(
841 state.is_complete(),
842 "should be complete after matching element"
843 );
844 }
845
846 #[test]
847 fn test_nfa_single_element_rejected() {
848 let name = NameId(1);
849 let wrong_name = NameId(2);
850 let nfa = single_element_nfa(name, None);
851 let mut state = ContentValidatorState::from_nfa(nfa);
852
853 assert!(state
854 .advance_element(wrong_name, None, None, XsdVersion::V1_0, None)
855 .is_none());
856 assert!(!state.is_complete());
857 }
858
859 #[test]
860 fn test_nfa_sequence() {
861 let a = NameId(10);
862 let b = NameId(20);
863 let nfa = sequence_nfa(a, None, b, None);
864 let mut state = ContentValidatorState::from_nfa(nfa);
865
866 assert!(!state.is_complete());
867 assert!(state
868 .advance_element(a, None, None, XsdVersion::V1_0, None)
869 .is_some());
870 assert!(!state.is_complete(), "only first element seen");
871 assert!(state
872 .advance_element(b, None, None, XsdVersion::V1_0, None)
873 .is_some());
874 assert!(state.is_complete(), "both elements matched");
875 }
876
877 #[test]
878 fn test_nfa_sequence_wrong_order() {
879 let a = NameId(10);
880 let b = NameId(20);
881 let nfa = sequence_nfa(a, None, b, None);
882 let mut state = ContentValidatorState::from_nfa(nfa);
883
884 assert!(state
886 .advance_element(b, None, None, XsdVersion::V1_0, None)
887 .is_none());
888 }
889
890 #[test]
891 fn test_nfa_would_accept() {
892 let name = NameId(1);
893 let wrong = NameId(2);
894 let nfa = single_element_nfa(name, None);
895 let state = ContentValidatorState::from_nfa(nfa);
896
897 assert!(state.would_accept(name, None, None, XsdVersion::V1_0, None));
898 assert!(!state.would_accept(wrong, None, None, XsdVersion::V1_0, None));
899 }
900
901 #[test]
902 fn test_all_group_any_order() {
903 use crate::compiler::{AllParticle, MaxOccurs};
904
905 let a = NameId(10);
906 let b = NameId(20);
907
908 let model = AllGroupModel::new(vec![
909 AllParticle::new(
910 NfaTerm::element(a, None, None),
911 1,
912 MaxOccurs::Bounded(1),
913 None,
914 ),
915 AllParticle::new(
916 NfaTerm::element(b, None, None),
917 1,
918 MaxOccurs::Bounded(1),
919 None,
920 ),
921 ]);
922
923 let mut state = ContentValidatorState::from_all_group(model);
925 assert!(!state.is_complete());
926 assert!(state
927 .advance_element(b, None, None, XsdVersion::V1_0, None)
928 .is_some());
929 assert!(
930 !state.is_complete(),
931 "only one of two required particles matched"
932 );
933 assert!(state
934 .advance_element(a, None, None, XsdVersion::V1_0, None)
935 .is_some());
936 assert!(state.is_complete(), "both particles satisfied");
937 }
938
939 #[test]
940 fn test_all_group_missing_required() {
941 use crate::compiler::{AllParticle, MaxOccurs};
942
943 let a = NameId(10);
944 let b = NameId(20);
945
946 let model = AllGroupModel::new(vec![
947 AllParticle::new(
948 NfaTerm::element(a, None, None),
949 1,
950 MaxOccurs::Bounded(1),
951 None,
952 ),
953 AllParticle::new(
954 NfaTerm::element(b, None, None),
955 1,
956 MaxOccurs::Bounded(1),
957 None,
958 ),
959 ]);
960
961 let mut state = ContentValidatorState::from_all_group(model);
962 assert!(state
963 .advance_element(a, None, None, XsdVersion::V1_0, None)
964 .is_some());
965 assert!(!state.is_complete(), "b is still required");
967 }
968
969 #[test]
970 fn test_simple_rejects_elements() {
971 let mut state = ContentValidatorState::Simple;
972 assert!(state
973 .advance_element(NameId(1), None, None, XsdVersion::V1_0, None)
974 .is_none());
975 assert!(state.is_complete());
976 }
977
978 #[test]
979 fn test_empty_rejects_elements() {
980 let mut state = ContentValidatorState::Empty;
981 assert!(state
982 .advance_element(NameId(1), None, None, XsdVersion::V1_0, None)
983 .is_none());
984 assert!(state.is_complete());
985 }
986
987 #[test]
988 fn test_from_matcher_nfa() {
989 let name = NameId(1);
990 let nfa = single_element_nfa(name, None);
991 let matcher = ContentModelMatcher::Nfa(nfa);
992 let mut state = ContentValidatorState::from_matcher(matcher);
993 assert!(state
994 .advance_element(name, None, None, XsdVersion::V1_0, None)
995 .is_some());
996 assert!(state.is_complete());
997 }
998
999 #[test]
1000 fn test_from_matcher_all_group() {
1001 use crate::compiler::{AllParticle, MaxOccurs};
1002
1003 let a = NameId(5);
1004 let model = AllGroupModel::new(vec![AllParticle::new(
1005 NfaTerm::element(a, None, None),
1006 0,
1007 MaxOccurs::Bounded(1),
1008 None,
1009 )]);
1010 let matcher = ContentModelMatcher::AllGroup(model);
1011 let state = ContentValidatorState::from_matcher(matcher);
1012 assert!(state.is_complete());
1014 }
1015
1016 use crate::compiler::OpenContentMode as AllGroupOCMode;
1019 use crate::compiler::{AllParticle, MaxOccurs, OpenContentWildcard};
1020
1021 fn all_group_with_open_content(
1022 mode: AllGroupOCMode,
1023 ns_constraint: NamespaceConstraint,
1024 ) -> AllGroupModel {
1025 let a = NameId(10);
1026 let mut model = AllGroupModel::new(vec![AllParticle::new(
1027 NfaTerm::element(a, None, None),
1028 1,
1029 MaxOccurs::Bounded(1),
1030 None,
1031 )]);
1032 model.open_content = Some(OpenContentWildcard {
1033 namespace_constraint: ns_constraint,
1034 process_contents: ProcessContents::Lax,
1035 mode,
1036 not_qnames: Vec::new(),
1037 });
1038 model
1039 }
1040
1041 #[test]
1042 fn test_all_group_open_content_interleave() {
1043 let model =
1044 all_group_with_open_content(AllGroupOCMode::Interleave, NamespaceConstraint::Any);
1045 let mut state = ContentValidatorState::from_all_group(model);
1046
1047 let extra = NameId(99);
1048 let a = NameId(10);
1049
1050 let info = state.advance_element(extra, None, None, XsdVersion::V1_1, None);
1052 assert!(info.is_some(), "interleave should accept extra element");
1053 assert!(info.unwrap().process_contents.is_some());
1054
1055 assert!(state
1057 .advance_element(a, None, None, XsdVersion::V1_1, None)
1058 .is_some());
1059 assert!(state.is_complete());
1060
1061 let info2 = state.advance_element(extra, None, None, XsdVersion::V1_1, None);
1063 assert!(
1064 info2.is_some(),
1065 "interleave should accept extra element after satisfaction"
1066 );
1067 }
1068
1069 #[test]
1070 fn test_all_group_open_content_suffix() {
1071 let model = all_group_with_open_content(AllGroupOCMode::Suffix, NamespaceConstraint::Any);
1072 let mut state = ContentValidatorState::from_all_group(model);
1073
1074 let extra = NameId(99);
1075 let a = NameId(10);
1076
1077 assert!(
1079 state
1080 .advance_element(extra, None, None, XsdVersion::V1_1, None)
1081 .is_none(),
1082 "suffix should reject extra element before satisfaction"
1083 );
1084
1085 assert!(state
1087 .advance_element(a, None, None, XsdVersion::V1_1, None)
1088 .is_some());
1089 assert!(state.is_complete());
1090
1091 assert!(
1093 state
1094 .advance_element(extra, None, None, XsdVersion::V1_1, None)
1095 .is_some(),
1096 "suffix should accept extra element after satisfaction"
1097 );
1098 }
1099
1100 #[test]
1101 fn test_nfa_open_content_interleave() {
1102 let a = NameId(10);
1103 let nfa = single_element_nfa(a, None);
1104 let oc = OpenContentInfo {
1105 mode: TypesOpenContentMode::Interleave,
1106 namespace_constraint: NamespaceConstraint::Any,
1107 process_contents: ProcessContents::Lax,
1108 not_qnames: Vec::new(),
1109 };
1110 let initial = ActiveStates::from_nfa(&nfa);
1111 let mut state = ContentValidatorState::Nfa {
1112 nfa,
1113 active_states: initial,
1114 open_content: Some(oc),
1115 };
1116
1117 let extra = NameId(99);
1118
1119 let info = state.advance_element(extra, None, None, XsdVersion::V1_1, None);
1121 assert!(
1122 info.is_some(),
1123 "interleave should accept extra element before NFA match"
1124 );
1125
1126 assert!(state
1128 .advance_element(a, None, None, XsdVersion::V1_1, None)
1129 .is_some());
1130 assert!(state.is_complete());
1131 }
1132
1133 #[test]
1134 fn test_nfa_open_content_suffix() {
1135 let a = NameId(10);
1136 let nfa = single_element_nfa(a, None);
1137 let oc = OpenContentInfo {
1138 mode: TypesOpenContentMode::Suffix,
1139 namespace_constraint: NamespaceConstraint::Any,
1140 process_contents: ProcessContents::Lax,
1141 not_qnames: Vec::new(),
1142 };
1143 let initial = ActiveStates::from_nfa(&nfa);
1144 let mut state = ContentValidatorState::Nfa {
1145 nfa,
1146 active_states: initial,
1147 open_content: Some(oc),
1148 };
1149
1150 let extra = NameId(99);
1151 let a = NameId(10);
1152
1153 assert!(
1155 state
1156 .advance_element(extra, None, None, XsdVersion::V1_1, None)
1157 .is_none(),
1158 "suffix should reject extra element before accept state"
1159 );
1160
1161 assert!(state
1163 .advance_element(a, None, None, XsdVersion::V1_1, None)
1164 .is_some());
1165 assert!(state.is_complete());
1166
1167 assert!(
1169 state
1170 .advance_element(extra, None, None, XsdVersion::V1_1, None)
1171 .is_some(),
1172 "suffix should accept extra element after accept state"
1173 );
1174 }
1175
1176 #[test]
1177 fn test_open_content_namespace_constraint() {
1178 let target_ns = Some(NameId(100));
1179 let other_ns = Some(NameId(200));
1180
1181 let model = all_group_with_open_content(
1182 AllGroupOCMode::Interleave,
1183 NamespaceConstraint::Other, );
1185 let mut state = ContentValidatorState::from_all_group(model);
1186
1187 let extra = NameId(99);
1188
1189 assert!(
1191 state
1192 .advance_element(extra, target_ns, target_ns, XsdVersion::V1_1, None)
1193 .is_none(),
1194 "open content with ##other should reject target namespace"
1195 );
1196
1197 assert!(
1199 state
1200 .advance_element(extra, other_ns, target_ns, XsdVersion::V1_1, None)
1201 .is_some(),
1202 "open content with ##other should accept other namespace"
1203 );
1204 }
1205
1206 #[test]
1207 fn test_would_accept_with_open_content() {
1208 let model =
1209 all_group_with_open_content(AllGroupOCMode::Interleave, NamespaceConstraint::Any);
1210 let state = ContentValidatorState::from_all_group(model);
1211
1212 let extra = NameId(99);
1213 let a = NameId(10);
1214
1215 assert!(state.would_accept(a, None, None, XsdVersion::V1_1, None));
1217 assert!(state.would_accept(extra, None, None, XsdVersion::V1_1, None));
1218
1219 let nfa = single_element_nfa(a, None);
1221 let oc = OpenContentInfo {
1222 mode: TypesOpenContentMode::Interleave,
1223 namespace_constraint: NamespaceConstraint::Any,
1224 process_contents: ProcessContents::Lax,
1225 not_qnames: Vec::new(),
1226 };
1227 let initial = ActiveStates::from_nfa(&nfa);
1228 let state = ContentValidatorState::Nfa {
1229 nfa,
1230 active_states: initial,
1231 open_content: Some(oc),
1232 };
1233 assert!(state.would_accept(a, None, None, XsdVersion::V1_1, None));
1234 assert!(state.would_accept(extra, None, None, XsdVersion::V1_1, None));
1235 }
1236
1237 #[cfg(feature = "xsd11")]
1240 fn make_all_group_extension_state(
1241 all_particles: Vec<AllParticle>,
1242 ext_nfa: NfaTable,
1243 ) -> ContentValidatorState {
1244 let model = AllGroupModel::new(all_particles);
1245 let matcher = ContentModelMatcher::AllGroupExtension {
1246 base_model: model,
1247 extension_nfa: ext_nfa,
1248 };
1249 ContentValidatorState::from_matcher(matcher)
1250 }
1251
1252 #[cfg(feature = "xsd11")]
1254 #[test]
1255 fn test_all_group_extension_basic_composite() {
1256 let a = NameId(10);
1257 let b = NameId(20);
1258 let c = NameId(30);
1259
1260 let particles = vec![
1261 AllParticle::new(
1262 NfaTerm::element(a, None, None),
1263 1,
1264 MaxOccurs::Bounded(1),
1265 None,
1266 ),
1267 AllParticle::new(
1268 NfaTerm::element(b, None, None),
1269 1,
1270 MaxOccurs::Bounded(1),
1271 None,
1272 ),
1273 ];
1274 let ext_nfa = single_element_nfa(c, None);
1275
1276 let mut state = make_all_group_extension_state(particles.clone(), ext_nfa.clone());
1278 assert!(state
1279 .advance_element(a, None, None, XsdVersion::V1_1, None)
1280 .is_some());
1281 assert!(state
1282 .advance_element(b, None, None, XsdVersion::V1_1, None)
1283 .is_some());
1284 assert!(state
1285 .advance_element(c, None, None, XsdVersion::V1_1, None)
1286 .is_some());
1287 assert!(state.is_complete());
1288
1289 let mut state = make_all_group_extension_state(particles.clone(), ext_nfa.clone());
1291 assert!(state
1292 .advance_element(b, None, None, XsdVersion::V1_1, None)
1293 .is_some());
1294 assert!(state
1295 .advance_element(a, None, None, XsdVersion::V1_1, None)
1296 .is_some());
1297 assert!(state
1298 .advance_element(c, None, None, XsdVersion::V1_1, None)
1299 .is_some());
1300 assert!(state.is_complete());
1301
1302 let mut state = make_all_group_extension_state(particles.clone(), ext_nfa.clone());
1304 assert!(state
1305 .advance_element(c, None, None, XsdVersion::V1_1, None)
1306 .is_none());
1307
1308 let mut state = make_all_group_extension_state(particles, ext_nfa);
1310 assert!(state
1311 .advance_element(a, None, None, XsdVersion::V1_1, None)
1312 .is_some());
1313 assert!(state
1314 .advance_element(c, None, None, XsdVersion::V1_1, None)
1315 .is_none());
1316 }
1317
1318 #[cfg(feature = "xsd11")]
1320 #[test]
1321 fn test_all_group_extension_optional_all_group() {
1322 let a = NameId(10);
1323 let b = NameId(20);
1324 let c = NameId(30);
1325
1326 let particles = vec![
1327 AllParticle::new(
1328 NfaTerm::element(a, None, None),
1329 0,
1330 MaxOccurs::Bounded(1),
1331 None,
1332 ),
1333 AllParticle::new(
1334 NfaTerm::element(b, None, None),
1335 0,
1336 MaxOccurs::Bounded(1),
1337 None,
1338 ),
1339 ];
1340 let ext_nfa = single_element_nfa(c, None);
1341
1342 let mut state = make_all_group_extension_state(particles, ext_nfa);
1344 assert!(state
1345 .advance_element(c, None, None, XsdVersion::V1_1, None)
1346 .is_some());
1347 assert!(state.is_complete());
1348 }
1349
1350 #[cfg(feature = "xsd11")]
1352 #[test]
1353 fn test_all_group_extension_is_complete() {
1354 let a = NameId(10);
1355 let b = NameId(20);
1356 let c = NameId(30);
1357
1358 let particles = vec![
1359 AllParticle::new(
1360 NfaTerm::element(a, None, None),
1361 1,
1362 MaxOccurs::Bounded(1),
1363 None,
1364 ),
1365 AllParticle::new(
1366 NfaTerm::element(b, None, None),
1367 1,
1368 MaxOccurs::Bounded(1),
1369 None,
1370 ),
1371 ];
1372 let ext_nfa = single_element_nfa(c, None);
1373
1374 let mut state = make_all_group_extension_state(particles, ext_nfa);
1375 assert!(!state.is_complete(), "not complete initially");
1376
1377 assert!(state
1378 .advance_element(a, None, None, XsdVersion::V1_1, None)
1379 .is_some());
1380 assert!(!state.is_complete(), "not complete after A only");
1381
1382 assert!(state
1383 .advance_element(b, None, None, XsdVersion::V1_1, None)
1384 .is_some());
1385 assert!(
1386 !state.is_complete(),
1387 "not complete after A,B — extension C still required"
1388 );
1389
1390 assert!(state
1391 .advance_element(c, None, None, XsdVersion::V1_1, None)
1392 .is_some());
1393 assert!(state.is_complete(), "complete after A,B,C");
1394 }
1395
1396 #[cfg(feature = "xsd11")]
1398 #[test]
1399 fn test_all_group_extension_would_accept() {
1400 let a = NameId(10);
1401 let b = NameId(20);
1402 let c = NameId(30);
1403
1404 let particles = vec![
1405 AllParticle::new(
1406 NfaTerm::element(a, None, None),
1407 1,
1408 MaxOccurs::Bounded(1),
1409 None,
1410 ),
1411 AllParticle::new(
1412 NfaTerm::element(b, None, None),
1413 1,
1414 MaxOccurs::Bounded(1),
1415 None,
1416 ),
1417 ];
1418 let ext_nfa = single_element_nfa(c, None);
1419
1420 let mut state = make_all_group_extension_state(particles, ext_nfa);
1421
1422 assert!(state.would_accept(a, None, None, XsdVersion::V1_1, None));
1424 assert!(state.would_accept(b, None, None, XsdVersion::V1_1, None));
1425 assert!(!state.would_accept(c, None, None, XsdVersion::V1_1, None));
1426
1427 state.advance_element(a, None, None, XsdVersion::V1_1, None);
1429 state.advance_element(b, None, None, XsdVersion::V1_1, None);
1430 assert!(!state.would_accept(a, None, None, XsdVersion::V1_1, None));
1431 assert!(!state.would_accept(b, None, None, XsdVersion::V1_1, None));
1432 assert!(state.would_accept(c, None, None, XsdVersion::V1_1, None));
1433 }
1434
1435 #[test]
1438 fn test_open_content_not_namespace_constraint() {
1439 let ns1 = Some(NameId(100));
1441 let ns2 = Some(NameId(200));
1442 let a = NameId(10);
1443 let extra = NameId(99);
1444
1445 let mut model = AllGroupModel::new(vec![AllParticle::new(
1446 NfaTerm::element(a, None, None),
1447 1,
1448 MaxOccurs::Bounded(1),
1449 None,
1450 )]);
1451 model.open_content = Some(OpenContentWildcard {
1452 namespace_constraint: NamespaceConstraint::Not(vec![ns1]),
1453 process_contents: ProcessContents::Lax,
1454 mode: AllGroupOCMode::Interleave,
1455 not_qnames: Vec::new(),
1456 });
1457 let mut state = ContentValidatorState::from_all_group(model);
1458
1459 assert!(
1461 state
1462 .advance_element(extra, ns1, None, XsdVersion::V1_1, None)
1463 .is_none(),
1464 "Not([ns1]) should reject elements from ns1"
1465 );
1466
1467 assert!(
1469 state
1470 .advance_element(extra, ns2, None, XsdVersion::V1_1, None)
1471 .is_some(),
1472 "Not([ns1]) should accept elements from ns2"
1473 );
1474 }
1475
1476 #[test]
1477 fn test_open_content_not_qnames_exclusion() {
1478 let a = NameId(10);
1480 let excluded = NameId(50);
1481 let allowed = NameId(60);
1482
1483 let mut model = AllGroupModel::new(vec![AllParticle::new(
1484 NfaTerm::element(a, None, None),
1485 1,
1486 MaxOccurs::Bounded(1),
1487 None,
1488 )]);
1489 model.open_content = Some(OpenContentWildcard {
1490 namespace_constraint: NamespaceConstraint::Any,
1491 process_contents: ProcessContents::Lax,
1492 mode: AllGroupOCMode::Interleave,
1493 not_qnames: vec![(None, excluded)], });
1495 let mut state = ContentValidatorState::from_all_group(model);
1496
1497 assert!(
1499 state
1500 .advance_element(excluded, None, None, XsdVersion::V1_1, None)
1501 .is_none(),
1502 "notQName should reject excluded element"
1503 );
1504
1505 assert!(
1507 state
1508 .advance_element(allowed, None, None, XsdVersion::V1_1, None)
1509 .is_some(),
1510 "notQName should accept non-excluded element"
1511 );
1512 }
1513
1514 #[test]
1515 fn test_nfa_open_content_not_qnames_exclusion() {
1516 let a = NameId(10);
1518 let excluded = NameId(50);
1519 let allowed = NameId(60);
1520 let nfa = single_element_nfa(a, None);
1521 let oc = OpenContentInfo {
1522 mode: TypesOpenContentMode::Interleave,
1523 namespace_constraint: NamespaceConstraint::Any,
1524 process_contents: ProcessContents::Lax,
1525 not_qnames: vec![(None, excluded)],
1526 };
1527 let initial = ActiveStates::from_nfa(&nfa);
1528 let mut state = ContentValidatorState::Nfa {
1529 nfa,
1530 active_states: initial,
1531 open_content: Some(oc),
1532 };
1533
1534 assert!(
1536 state
1537 .advance_element(excluded, None, None, XsdVersion::V1_1, None)
1538 .is_none(),
1539 "NFA open content notQName should reject excluded element"
1540 );
1541
1542 assert!(
1544 state
1545 .advance_element(allowed, None, None, XsdVersion::V1_1, None)
1546 .is_some(),
1547 "NFA open content notQName should accept non-excluded element"
1548 );
1549 }
1550
1551 #[test]
1552 fn test_would_accept_respects_not_qnames() {
1553 let a = NameId(10);
1554 let excluded = NameId(50);
1555 let allowed = NameId(60);
1556
1557 let mut model = AllGroupModel::new(vec![AllParticle::new(
1558 NfaTerm::element(a, None, None),
1559 1,
1560 MaxOccurs::Bounded(1),
1561 None,
1562 )]);
1563 model.open_content = Some(OpenContentWildcard {
1564 namespace_constraint: NamespaceConstraint::Any,
1565 process_contents: ProcessContents::Lax,
1566 mode: AllGroupOCMode::Interleave,
1567 not_qnames: vec![(None, excluded)],
1568 });
1569 let state = ContentValidatorState::from_all_group(model);
1570
1571 assert!(!state.would_accept(excluded, None, None, XsdVersion::V1_1, None));
1572 assert!(state.would_accept(allowed, None, None, XsdVersion::V1_1, None));
1573 }
1574}