1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3
4use crate::SchemaResolver;
5use crate::grammar::{Combine, Grammar, GrammarItem, NameClass, Pattern, Root, SchemaDocument};
6use crate::syntax::parse_with_inherited_ns;
7
8#[derive(Clone, Debug)]
15pub struct CompiledSchema {
16 pub(crate) start: Pattern,
17 pub(crate) definitions: BTreeMap<String, Pattern>,
18}
19
20impl CompiledSchema {
21 pub fn definition_count(&self) -> usize {
24 self.definitions.len()
25 }
26 pub fn has_start(&self) -> bool {
30 !matches!(self.start, Pattern::NotAllowed)
31 }
32}
33
34#[derive(Clone, Debug, Eq, PartialEq)]
38pub struct SchemaError {
39 message: String,
40}
41
42impl SchemaError {
43 fn new(message: impl Into<String>) -> Self {
44 Self {
45 message: message.into(),
46 }
47 }
48}
49
50impl fmt::Display for SchemaError {
51 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
52 formatter.write_str(&self.message)
53 }
54}
55
56impl std::error::Error for SchemaError {}
57
58pub fn simplify(
63 document: &SchemaDocument,
64 resolver: &impl SchemaResolver,
65) -> Result<CompiledSchema, SchemaError> {
66 let mut loading = BTreeSet::new();
67 simplify_root(document, resolver, &mut loading)
68}
69
70fn simplify_root(
71 document: &SchemaDocument,
72 resolver: &impl SchemaResolver,
73 loading: &mut BTreeSet<String>,
74) -> Result<CompiledSchema, SchemaError> {
75 let compiled = match &document.root {
76 Root::Pattern(pattern) => {
77 let mut extra = BTreeMap::new();
78 let mut next_id = 0usize;
79 let start = simplify_pattern(
80 pattern,
81 resolver,
82 loading,
83 &mut extra,
84 &mut next_id,
85 &BTreeSet::new(),
86 None,
87 )?;
88 Ok(CompiledSchema {
89 start,
90 definitions: extra,
91 })
92 }
93 Root::Grammar(grammar) => simplify_grammar(grammar, resolver, loading),
94 }?;
95 check_illegal_recursion(&compiled.start, &compiled.definitions)?;
96 let mut scope = RestrictionScope {
97 definitions: &compiled.definitions,
98 visiting: BTreeSet::new(),
99 verified: BTreeSet::new(),
100 };
101 check_restrictions(&compiled.start, false, false, false, false, &mut scope)?;
102 let mut reachable = BTreeSet::new();
109 collect_reachable_defines(&compiled.start, &compiled.definitions, &mut reachable);
110 for (def_name, pattern) in compiled.definitions.iter() {
111 if reachable.contains(def_name.as_str()) {
112 continue;
113 }
114 check_restrictions(pattern, false, false, false, false, &mut scope)?;
115 }
116 check_group_restrictions(&compiled.start, &compiled.definitions)?;
117 for pattern in compiled.definitions.values() {
118 check_group_restrictions(pattern, &compiled.definitions)?;
119 }
120 check_start_restrictions(&compiled.start, &compiled.definitions, &mut BTreeSet::new())?;
121 Ok(compiled)
122}
123
124fn check_illegal_recursion(
135 start: &Pattern,
136 definitions: &BTreeMap<String, Pattern>,
137) -> Result<(), SchemaError> {
138 let mut reachable = BTreeSet::new();
139 collect_reachable_defines(start, definitions, &mut reachable);
140 let mut graph: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
141 for name in &reachable {
142 let pattern = &definitions[*name];
143 let mut edges = Vec::new();
144 collect_ref_edges(pattern, &mut edges);
145 graph.insert(*name, edges);
146 }
147 let mut state: BTreeMap<&str, RefVisit> = BTreeMap::new();
148 for name in graph.keys().copied().collect::<Vec<_>>() {
149 if !state.contains_key(name) {
150 visit_ref_graph(name, &graph, &mut state)?;
151 }
152 }
153 Ok(())
154}
155
156fn collect_reachable_defines<'a>(
159 pattern: &'a Pattern,
160 definitions: &'a BTreeMap<String, Pattern>,
161 reachable: &mut BTreeSet<&'a str>,
162) {
163 match pattern {
164 Pattern::Ref(name) => {
165 if let Some(definition) = definitions.get(name)
166 && reachable.insert(name.as_str())
167 {
168 collect_reachable_defines(definition, definitions, reachable);
169 }
170 }
171 Pattern::Element { body, .. } | Pattern::Attribute { body, .. } => {
172 for child in body {
173 collect_reachable_defines(child, definitions, reachable);
174 }
175 }
176 Pattern::Group(values)
177 | Pattern::Interleave(values)
178 | Pattern::Choice(values)
179 | Pattern::OneOrMore(values)
180 | Pattern::List(values)
181 | Pattern::Optional(values)
184 | Pattern::ZeroOrMore(values)
185 | Pattern::Mixed(values) => {
186 for child in values {
187 collect_reachable_defines(child, definitions, reachable);
188 }
189 }
190 Pattern::Data { except, .. } => {
191 for child in except {
192 collect_reachable_defines(child, definitions, reachable);
193 }
194 }
195 _ => {}
196 }
197}
198
199#[derive(Clone, Copy, PartialEq, Eq)]
200enum RefVisit {
201 Visiting,
202 Done,
203}
204
205fn visit_ref_graph<'a>(
206 name: &'a str,
207 graph: &BTreeMap<&'a str, Vec<&'a str>>,
208 state: &mut BTreeMap<&'a str, RefVisit>,
209) -> Result<(), SchemaError> {
210 state.insert(name, RefVisit::Visiting);
211 if let Some(edges) = graph.get(name) {
212 for &target in edges {
213 match state.get(target) {
214 Some(RefVisit::Visiting) => {
215 return Err(SchemaError::new(format!(
216 "RELAX NG §4.19: `{target}` illegally refers to itself without an intervening element"
217 )));
218 }
219 Some(RefVisit::Done) => {}
220 None if graph.contains_key(target) => {
221 visit_ref_graph(target, graph, state)?;
222 }
223 None => {}
224 }
225 }
226 }
227 state.insert(name, RefVisit::Done);
228 Ok(())
229}
230
231fn collect_ref_edges<'a>(pattern: &'a Pattern, edges: &mut Vec<&'a str>) {
236 match pattern {
237 Pattern::Ref(name) => edges.push(name.as_str()),
238 Pattern::Group(values)
239 | Pattern::Interleave(values)
240 | Pattern::Choice(values)
241 | Pattern::OneOrMore(values)
242 | Pattern::Optional(values)
244 | Pattern::ZeroOrMore(values)
245 | Pattern::Mixed(values) => {
246 for child in values {
247 collect_ref_edges(child, edges);
248 }
249 }
250 _ => {}
251 }
252}
253
254fn resolve_grammar_items(
262 items: &[GrammarItem],
263 resolver: &impl SchemaResolver,
264 loading: &mut BTreeSet<String>,
265 missing_start: &str,
266) -> Result<(Pattern, BTreeMap<String, Pattern>), SchemaError> {
267 let items = flatten_items(items, resolver, loading)?;
268 let (starts, definitions) = collect_items(items)?;
269 let start = merge("start", starts)?.ok_or_else(|| SchemaError::new(missing_start))?;
270 let definitions = definitions
271 .into_iter()
272 .map(|(name, fragments)| {
273 let pattern = merge(&name, fragments)?
274 .ok_or_else(|| SchemaError::new(format!("`{name}` has no pattern")))?;
275 Ok((name, pattern))
276 })
277 .collect::<Result<_, SchemaError>>()?;
278 Ok((start, definitions))
279}
280
281fn simplify_grammar(
282 grammar: &Grammar,
283 resolver: &impl SchemaResolver,
284 loading: &mut BTreeSet<String>,
285) -> Result<CompiledSchema, SchemaError> {
286 let (start, definitions) =
287 resolve_grammar_items(&grammar.items, resolver, loading, "grammar has no start")?;
288 check_illegal_recursion(&start, &definitions)?;
294 let mut extra = BTreeMap::new();
295 let mut next_id = 0usize;
296 let scope: BTreeSet<String> = definitions.keys().cloned().collect();
297 let start = simplify_pattern(
298 &start,
299 resolver,
300 loading,
301 &mut extra,
302 &mut next_id,
303 &scope,
304 None,
305 )?;
306 let mut compiled_definitions: BTreeMap<String, Pattern> = definitions
307 .into_iter()
308 .map(|(name, pattern)| {
309 Ok((
310 name,
311 simplify_pattern(
312 &pattern,
313 resolver,
314 loading,
315 &mut extra,
316 &mut next_id,
317 &scope,
318 None,
319 )?,
320 ))
321 })
322 .collect::<Result<_, SchemaError>>()?;
323 compiled_definitions.extend(extra);
324 Ok(CompiledSchema {
325 start,
326 definitions: compiled_definitions,
327 })
328}
329
330type Fragments = Vec<(Option<Combine>, Vec<Pattern>)>;
334
335fn collect_items(
342 items: Vec<GrammarItem>,
343) -> Result<(Fragments, BTreeMap<String, Fragments>), SchemaError> {
344 let mut starts = Vec::new();
345 let mut definitions: BTreeMap<String, Fragments> = BTreeMap::new();
346 for item in items {
347 match item {
348 GrammarItem::Start { combine, body } => starts.push((combine, body)),
349 GrammarItem::Define {
350 name,
351 combine,
352 body,
353 } => definitions.entry(name).or_default().push((combine, body)),
354 GrammarItem::Div(_) | GrammarItem::Include { .. } => {
355 unreachable!("items are flattened")
356 }
357 }
358 }
359 Ok((starts, definitions))
360}
361
362fn flatten_items(
363 items: &[GrammarItem],
364 resolver: &impl SchemaResolver,
365 loading: &mut BTreeSet<String>,
366) -> Result<Vec<GrammarItem>, SchemaError> {
367 let mut output = Vec::new();
368 for item in items {
369 match item {
370 GrammarItem::Div(children) => {
371 output.extend(flatten_items(children, resolver, loading)?)
372 }
373 GrammarItem::Include {
374 href,
375 body,
376 context,
377 ..
378 } => {
379 let key = format!("{}::{href}", context.base_uri);
380 if !loading.insert(key.clone()) {
381 return Err(SchemaError::new("include cycle"));
382 }
383 let result = resolver
384 .resolve(href, &context.base_uri)
385 .map_err(|error| SchemaError::new(error.to_string()))
386 .and_then(|source| {
387 parse_with_inherited_ns(
393 &source,
394 context.ns.clone(),
395 context.default_namespace.clone(),
396 )
397 .map_err(|error| SchemaError::new(error.to_string()))
398 })
399 .and_then(|included| {
400 let Root::Grammar(grammar) = included.root else {
401 return Err(SchemaError::new("include must resolve to a grammar"));
402 };
403 let mut included_items =
404 flatten_items(&grammar.items, resolver, loading)?;
405 let overriding_items = flatten_items(body, resolver, loading)?;
406 check_include_overrides_exist(&included_items, &overriding_items)?;
407 let local_names: BTreeSet<_> = overriding_items
408 .iter()
409 .filter_map(|item| match item {
410 GrammarItem::Define { name, .. } => Some(name),
411 _ => None,
412 })
413 .collect();
414 included_items.retain(|item| !matches!(item, GrammarItem::Define { name, .. } if local_names.contains(name)));
415 included_items.extend(overriding_items);
416 Ok(included_items)
417 });
418 loading.remove(&key);
419 output.extend(result?);
420 }
421 item => output.push(item.clone()),
422 }
423 }
424 Ok(output)
425}
426
427fn check_include_overrides_exist(
435 included: &[GrammarItem],
436 overriding: &[GrammarItem],
437) -> Result<(), SchemaError> {
438 let has_start = included
439 .iter()
440 .any(|item| matches!(item, GrammarItem::Start { .. }));
441 let define_names: BTreeSet<&String> = included
442 .iter()
443 .filter_map(|item| match item {
444 GrammarItem::Define { name, .. } => Some(name),
445 _ => None,
446 })
447 .collect();
448 for item in overriding {
449 match item {
450 GrammarItem::Start { .. } if !has_start => {
451 return Err(SchemaError::new(
452 "RELAX NG §4.7: include overrides `start`, but the included grammar has none",
453 ));
454 }
455 GrammarItem::Define { name, .. } if !define_names.contains(name) => {
456 return Err(SchemaError::new(format!(
457 "RELAX NG §4.7: include overrides `define name=\"{name}\"`, but the included grammar has no such define"
458 )));
459 }
460 _ => {}
461 }
462 }
463 Ok(())
464}
465
466fn merge(
467 name: &str,
468 fragments: Vec<(Option<Combine>, Vec<Pattern>)>,
469) -> Result<Option<Pattern>, SchemaError> {
470 let mut combine = None;
471 let mut without_combine = 0usize;
472 let mut patterns = Vec::new();
473 for (method, bodies) in fragments {
474 match method {
478 Some(method) => {
479 if combine.is_some_and(|existing| existing != method) {
480 return Err(SchemaError::new(format!(
481 "`{name}` has inconsistent combine"
482 )));
483 }
484 combine = Some(method);
485 }
486 None => without_combine += 1,
487 }
488 patterns.extend(bodies);
489 }
490 if without_combine > 1 {
491 return Err(SchemaError::new(format!(
492 "`{name}` has more than one definition without combine"
493 )));
494 }
495 if patterns.is_empty() {
496 return Ok(None);
497 }
498 if patterns.len() == 1 {
499 return Ok(Some(patterns.pop().expect("one pattern")));
500 }
501 match combine {
502 Some(Combine::Choice) => Ok(Some(Pattern::Choice(patterns))),
503 Some(Combine::Interleave) => Ok(Some(Pattern::Interleave(patterns))),
504 None => Err(SchemaError::new(format!(
505 "duplicate `{name}` without combine"
506 ))),
507 }
508}
509
510fn simplify_pattern(
511 pattern: &Pattern,
512 resolver: &impl SchemaResolver,
513 loading: &mut BTreeSet<String>,
514 extra: &mut BTreeMap<String, Pattern>,
515 next_id: &mut usize,
516 scope: &BTreeSet<String>,
524 parent_scope: Option<&BTreeSet<String>>,
525) -> Result<Pattern, SchemaError> {
526 let mut children = |patterns: &[Pattern]| {
527 patterns
528 .iter()
529 .map(|child| {
530 simplify_pattern(
531 child,
532 resolver,
533 loading,
534 extra,
535 next_id,
536 scope,
537 parent_scope,
538 )
539 })
540 .collect::<Result<Vec<_>, _>>()
541 };
542 Ok(match pattern {
543 Pattern::Ref(name) => {
544 if !scope.contains(name) {
545 return Err(SchemaError::new(format!(
546 "RELAX NG §4.18: `ref` to undefined `{name}`"
547 )));
548 }
549 pattern.clone()
550 }
551 Pattern::ParentRef(name) => {
552 if !parent_scope.is_some_and(|names| names.contains(name)) {
553 return Err(SchemaError::new(format!(
554 "RELAX NG §4.18: `parentRef` to undefined `{name}`"
555 )));
556 }
557 pattern.clone()
558 }
559 Pattern::Optional(values) => choice(vec![group(children(values)?), Pattern::Empty]),
560 Pattern::ZeroOrMore(values) => {
561 let repeated = one_or_more(group(children(values)?));
562 choice(vec![repeated, Pattern::Empty])
563 }
564 Pattern::Mixed(values) => interleave(vec![group(children(values)?), Pattern::Text]),
565 Pattern::OneOrMore(values) => one_or_more(group(children(values)?)),
566 Pattern::List(values) => wrap_unless_not_allowed(group(children(values)?), Pattern::List),
567 Pattern::Group(values) => group(children(values)?),
568 Pattern::Choice(values) => choice(children(values)?),
569 Pattern::Interleave(values) => interleave(children(values)?),
570 Pattern::Element {
571 name,
572 body,
573 context,
574 } => {
575 check_name_class_constraints(name)?;
576 Pattern::Element {
577 name: name.clone(),
578 body: vec![group(children(body)?)],
579 context: context.clone(),
580 }
581 }
582 Pattern::Attribute {
583 name,
584 body,
585 context,
586 } => {
587 check_name_class_constraints(name)?;
588 check_attribute_name_class_constraints(name)?;
589 wrap_unless_not_allowed(group(children(body)?), |body| Pattern::Attribute {
590 name: name.clone(),
591 body,
592 context: context.clone(),
593 })
594 }
595 Pattern::Data {
596 datatype,
597 datatype_library,
598 params,
599 except,
600 context,
601 } => {
602 check_builtin_datatype_constraints(datatype, datatype_library, !params.is_empty())?;
603 Pattern::Data {
604 datatype: datatype.clone(),
605 datatype_library: datatype_library.clone(),
606 params: params.clone(),
607 except: children(except)?
610 .into_iter()
611 .filter(|value| !matches!(value, Pattern::NotAllowed))
612 .collect(),
613 context: context.clone(),
614 }
615 }
616 Pattern::Value {
617 datatype: Some(datatype),
618 datatype_library,
619 ..
620 } => {
621 check_builtin_datatype_constraints(datatype, datatype_library, false)?;
622 pattern.clone()
623 }
624 Pattern::ExternalRef { href, context, .. } => {
625 let key = format!("{}::{href}", context.base_uri);
626 if !loading.insert(key.clone()) {
627 return Err(SchemaError::new("externalRef cycle"));
628 }
629 let result = resolver
637 .resolve(href, &context.base_uri)
638 .map_err(|error| SchemaError::new(error.to_string()))
639 .and_then(|source| {
640 parse_with_inherited_ns(
646 &source,
647 context.ns.clone(),
648 context.default_namespace.clone(),
649 )
650 .map_err(|error| SchemaError::new(error.to_string()))
651 })
652 .and_then(|resolved| match resolved.root {
653 Root::Pattern(value) => simplify_pattern(
654 &value,
655 resolver,
656 loading,
657 extra,
658 next_id,
659 &BTreeSet::new(),
660 None,
661 ),
662 Root::Grammar(_) => {
663 Err(SchemaError::new("externalRef must resolve to a pattern"))
664 }
665 });
666 loading.remove(&key);
667 result?
668 }
669 Pattern::Grammar(nested) => {
670 let (start_body, raw_defines) = resolve_grammar_items(
671 &nested.items,
672 resolver,
673 loading,
674 "nested grammar has no start",
675 )?;
676 let nested_scope: BTreeSet<String> = raw_defines.keys().cloned().collect();
677
678 let simplified_start = simplify_pattern(
679 &start_body,
680 resolver,
681 loading,
682 extra,
683 next_id,
684 &nested_scope,
685 Some(scope),
686 )?;
687 let mut simplified_defines = BTreeMap::new();
688 for (name, body) in &raw_defines {
689 simplified_defines.insert(
690 name.clone(),
691 simplify_pattern(
692 body,
693 resolver,
694 loading,
695 extra,
696 next_id,
697 &nested_scope,
698 Some(scope),
699 )?,
700 );
701 }
702
703 *next_id += 1;
710 let tag = *next_id;
711 let rename: BTreeMap<String, String> = raw_defines
712 .keys()
713 .map(|name| (name.clone(), format!("{name}#{tag}")))
714 .collect();
715 for (name, pattern) in simplified_defines {
716 extra.insert(rename[&name].clone(), rewrite_refs(&pattern, &rename));
717 }
718 rewrite_refs(&simplified_start, &rename)
719 }
720 value => value.clone(),
721 })
722}
723
724fn rewrite_refs(pattern: &Pattern, rename: &BTreeMap<String, String>) -> Pattern {
728 let rewrite_all = |values: &[Pattern]| -> Vec<Pattern> {
729 values
730 .iter()
731 .map(|value| rewrite_refs(value, rename))
732 .collect()
733 };
734 match pattern {
735 Pattern::Ref(name) => {
736 Pattern::Ref(rename.get(name).cloned().unwrap_or_else(|| name.clone()))
737 }
738 Pattern::ParentRef(name) => Pattern::Ref(name.clone()),
739 Pattern::Element {
740 name,
741 body,
742 context,
743 } => Pattern::Element {
744 name: name.clone(),
745 body: rewrite_all(body),
746 context: context.clone(),
747 },
748 Pattern::Attribute {
749 name,
750 body,
751 context,
752 } => Pattern::Attribute {
753 name: name.clone(),
754 body: rewrite_all(body),
755 context: context.clone(),
756 },
757 Pattern::Group(values) => Pattern::Group(rewrite_all(values)),
758 Pattern::Interleave(values) => Pattern::Interleave(rewrite_all(values)),
759 Pattern::Choice(values) => Pattern::Choice(rewrite_all(values)),
760 Pattern::Optional(values) => Pattern::Optional(rewrite_all(values)),
761 Pattern::ZeroOrMore(values) => Pattern::ZeroOrMore(rewrite_all(values)),
762 Pattern::OneOrMore(values) => Pattern::OneOrMore(rewrite_all(values)),
763 Pattern::List(values) => Pattern::List(rewrite_all(values)),
764 Pattern::Mixed(values) => Pattern::Mixed(rewrite_all(values)),
765 Pattern::Data {
766 datatype,
767 datatype_library,
768 params,
769 except,
770 context,
771 } => Pattern::Data {
772 datatype: datatype.clone(),
773 datatype_library: datatype_library.clone(),
774 params: params.clone(),
775 except: rewrite_all(except),
776 context: context.clone(),
777 },
778 Pattern::Grammar(_) => unreachable!("nested grammars are flattened before rewriting refs"),
779 value => value.clone(),
780 }
781}
782
783fn drop_identity_empty(mut values: Vec<Pattern>) -> Vec<Pattern> {
792 if values.len() > 1 {
793 values.retain(|value| !matches!(value, Pattern::Empty));
794 }
795 values
796}
797
798fn collapse_sequence(values: Vec<Pattern>, wrap: fn(Vec<Pattern>) -> Pattern) -> Pattern {
807 if values
808 .iter()
809 .any(|value| matches!(value, Pattern::NotAllowed))
810 {
811 return Pattern::NotAllowed;
812 }
813 let mut values = drop_identity_empty(values);
814 if values.is_empty() {
815 Pattern::Empty
816 } else if values.len() == 1 {
817 values.pop().expect("one")
818 } else {
819 wrap(values)
820 }
821}
822pub(crate) fn group(values: Vec<Pattern>) -> Pattern {
823 collapse_sequence(values, Pattern::Group)
824}
825pub(crate) fn choice(mut values: Vec<Pattern>) -> Pattern {
826 values.retain(|value| !matches!(value, Pattern::NotAllowed));
827 if values.is_empty() {
828 Pattern::NotAllowed
829 } else if values.len() == 1 {
830 values.pop().expect("one")
831 } else {
832 Pattern::Choice(values)
833 }
834}
835pub(crate) fn interleave(values: Vec<Pattern>) -> Pattern {
836 collapse_sequence(values, Pattern::Interleave)
837}
838fn wrap_unless_not_allowed(body: Pattern, wrap: impl FnOnce(Vec<Pattern>) -> Pattern) -> Pattern {
843 if matches!(body, Pattern::NotAllowed) {
844 Pattern::NotAllowed
845 } else {
846 wrap(vec![body])
847 }
848}
849
850pub(crate) fn one_or_more(body: Pattern) -> Pattern {
851 wrap_unless_not_allowed(body, Pattern::OneOrMore)
852}
853
854struct RestrictionScope<'a> {
857 definitions: &'a BTreeMap<String, Pattern>,
858 visiting: BTreeSet<String>,
868 verified: BTreeSet<(String, bool, bool, bool, bool)>,
876}
877
878fn check_restrictions(
879 pattern: &Pattern,
880 in_attribute: bool,
881 in_list: bool,
882 in_one_or_more: bool,
883 in_one_or_more_container: bool,
888 scope: &mut RestrictionScope,
889) -> Result<(), SchemaError> {
890 match pattern {
891 Pattern::Ref(name) => {
892 let key = (
893 name.clone(),
894 in_attribute,
895 in_list,
896 in_one_or_more,
897 in_one_or_more_container,
898 );
899 if scope.verified.contains(&key) {
900 return Ok(());
901 }
902 if let Some(definition) = scope.definitions.get(name)
903 && scope.visiting.insert(name.clone())
904 {
905 check_restrictions(
906 definition,
907 in_attribute,
908 in_list,
909 in_one_or_more,
910 in_one_or_more_container,
911 scope,
912 )?;
913 scope.visiting.remove(name);
914 scope.verified.insert(key);
915 }
916 }
917 Pattern::Attribute { name, body, .. } => {
918 if in_list {
919 return Err(SchemaError::new(
920 "RELAX NG §7.1.3: a list pattern cannot contain attribute",
921 ));
922 }
923 if in_attribute {
924 return Err(SchemaError::new(
925 "RELAX NG §7.1.1: an attribute pattern cannot contain an attribute",
926 ));
927 }
928 if in_one_or_more_container {
929 return Err(SchemaError::new(
930 "RELAX NG §7.1.2: a oneOrMore pattern cannot wrap a group/interleave containing an attribute",
931 ));
932 }
933 if name_class_has_infinite_names(name) && !in_one_or_more {
934 return Err(SchemaError::new(
935 "RELAX NG §7.3: an attribute with an anyName or nsName name class must have a oneOrMore ancestor",
936 ));
937 }
938 body.iter().try_for_each(|child| {
939 check_restrictions(
940 child,
941 true,
942 in_list,
943 in_one_or_more,
944 in_one_or_more_container,
945 scope,
946 )
947 })?;
948 }
949 Pattern::List(body) => {
950 if in_list {
951 return Err(SchemaError::new(
952 "RELAX NG §7.1.3: a list pattern cannot contain list",
953 ));
954 }
955 body.iter().try_for_each(|child| {
956 check_restrictions(
957 child,
958 in_attribute,
959 true,
960 in_one_or_more,
961 in_one_or_more_container,
962 scope,
963 )
964 })?;
965 }
966 Pattern::Interleave(values) if in_list => {
967 return Err(SchemaError::new(
968 "RELAX NG §7.1.3: a list pattern cannot contain interleave",
969 ));
970 }
971 Pattern::Text if in_list => {
972 return Err(SchemaError::new(
973 "RELAX NG §7.1.3: a list pattern cannot contain text",
974 ));
975 }
976 Pattern::Element { .. } if in_attribute => {
977 return Err(SchemaError::new(
978 "RELAX NG §7.1.1: an attribute pattern cannot contain an element",
979 ));
980 }
981 Pattern::Element { .. } if in_list => {
982 return Err(SchemaError::new(
983 "RELAX NG §7.1.3: a list pattern cannot contain element",
984 ));
985 }
986 Pattern::OneOrMore(values) | Pattern::ZeroOrMore(values) => {
991 values.iter().try_for_each(|child| {
992 check_restrictions(
993 child,
994 in_attribute,
995 in_list,
996 true,
997 in_one_or_more_container,
998 scope,
999 )
1000 })?;
1001 }
1002 Pattern::Group(values) | Pattern::Interleave(values) => {
1003 let in_container = in_one_or_more_container || in_one_or_more;
1004 values.iter().try_for_each(|child| {
1005 check_restrictions(
1006 child,
1007 in_attribute,
1008 in_list,
1009 in_one_or_more,
1010 in_container,
1011 scope,
1012 )
1013 })?;
1014 }
1015 Pattern::Choice(values) | Pattern::Optional(values) | Pattern::Mixed(values) => {
1016 values.iter().try_for_each(|child| {
1017 check_restrictions(
1018 child,
1019 in_attribute,
1020 in_list,
1021 in_one_or_more,
1022 in_one_or_more_container,
1023 scope,
1024 )
1025 })?;
1026 }
1027 Pattern::Element { body, .. } => {
1028 body.iter().try_for_each(|child| {
1029 check_restrictions(child, false, false, false, false, scope)
1030 })?;
1031 }
1032 Pattern::Data { except, .. } => {
1033 except.iter().try_for_each(check_except_restrictions)?;
1034 }
1035 _ => {}
1036 }
1037 Ok(())
1038}
1039
1040fn check_builtin_datatype_constraints(
1049 datatype: &str,
1050 datatype_library: &Option<String>,
1051 has_params: bool,
1052) -> Result<(), SchemaError> {
1053 if datatype_library.as_deref() != Some("") {
1054 return Ok(());
1055 }
1056 if datatype != "string" && datatype != "token" {
1057 return Err(SchemaError::new(format!(
1058 "RELAX NG §4.16: `{datatype}` is not a datatype of the built-in (empty) datatype library"
1059 )));
1060 }
1061 if has_params {
1062 return Err(SchemaError::new(
1063 "RELAX NG §4.16: the built-in (empty) datatype library's datatypes take no parameters",
1064 ));
1065 }
1066 Ok(())
1067}
1068
1069fn check_except_restrictions(pattern: &Pattern) -> Result<(), SchemaError> {
1073 match pattern {
1074 Pattern::Data { except, .. } => except.iter().try_for_each(check_except_restrictions),
1075 Pattern::Value { .. } => Ok(()),
1076 Pattern::Choice(values) => values.iter().try_for_each(check_except_restrictions),
1077 _ => Err(SchemaError::new(
1078 "RELAX NG §7.1.4: a data/except pattern may only contain data, value or choice",
1079 )),
1080 }
1081}
1082
1083fn check_start_restrictions(
1088 pattern: &Pattern,
1089 definitions: &BTreeMap<String, Pattern>,
1090 visiting: &mut BTreeSet<String>,
1091) -> Result<(), SchemaError> {
1092 match pattern {
1093 Pattern::Element { .. } | Pattern::NotAllowed => Ok(()),
1094 Pattern::Choice(values) => values
1095 .iter()
1096 .try_for_each(|child| check_start_restrictions(child, definitions, visiting)),
1097 Pattern::Ref(name) => {
1098 if let Some(definition) = definitions.get(name)
1099 && visiting.insert(name.clone())
1100 {
1101 check_start_restrictions(definition, definitions, visiting)?;
1102 visiting.remove(name);
1103 }
1104 Ok(())
1105 }
1106 Pattern::Attribute { .. } => Err(SchemaError::new(
1107 "RELAX NG §7.1.5: start cannot contain attribute",
1108 )),
1109 Pattern::Data { .. } => Err(SchemaError::new(
1110 "RELAX NG §7.1.5: start cannot contain data",
1111 )),
1112 Pattern::Value { .. } => Err(SchemaError::new(
1113 "RELAX NG §7.1.5: start cannot contain value",
1114 )),
1115 Pattern::Text => Err(SchemaError::new(
1116 "RELAX NG §7.1.5: start cannot contain text",
1117 )),
1118 Pattern::List(_) => Err(SchemaError::new(
1119 "RELAX NG §7.1.5: start cannot contain list",
1120 )),
1121 Pattern::Group(_) => Err(SchemaError::new(
1122 "RELAX NG §7.1.5: start cannot contain group",
1123 )),
1124 Pattern::Interleave(_) => Err(SchemaError::new(
1125 "RELAX NG §7.1.5: start cannot contain interleave",
1126 )),
1127 Pattern::OneOrMore(_) => Err(SchemaError::new(
1128 "RELAX NG §7.1.5: start cannot contain oneOrMore",
1129 )),
1130 Pattern::Empty => Err(SchemaError::new(
1131 "RELAX NG §7.1.5: start cannot contain empty",
1132 )),
1133 Pattern::Optional(_)
1134 | Pattern::ZeroOrMore(_)
1135 | Pattern::Mixed(_)
1136 | Pattern::ExternalRef { .. }
1137 | Pattern::ParentRef(_)
1138 | Pattern::Grammar(_) => {
1139 unreachable!("resolved into other patterns by simplify_pattern")
1140 }
1141 }
1142}
1143
1144fn check_name_class_constraints(class: &NameClass) -> Result<(), SchemaError> {
1150 match class {
1151 NameClass::Any { except, .. } => {
1152 for item in except {
1153 if name_class_contains_any(item) {
1154 return Err(SchemaError::new(
1155 "RELAX NG §4.16: an anyName's except must not contain anyName",
1156 ));
1157 }
1158 check_name_class_constraints(item)?;
1159 }
1160 Ok(())
1161 }
1162 NameClass::Namespace { except, .. } => {
1163 for item in except {
1164 if name_class_contains_any(item) || name_class_contains_namespace(item) {
1165 return Err(SchemaError::new(
1166 "RELAX NG §4.16: an nsName's except must not contain nsName or anyName",
1167 ));
1168 }
1169 check_name_class_constraints(item)?;
1170 }
1171 Ok(())
1172 }
1173 NameClass::Name { .. } => Ok(()),
1174 NameClass::Choice(options) => {
1175 for item in options {
1176 check_name_class_constraints(item)?;
1177 }
1178 Ok(())
1179 }
1180 }
1181}
1182
1183const XMLNS_NAMESPACE: &str = "http://www.w3.org/2000/xmlns";
1184
1185fn is_xmlns_namespace(namespace: &Option<String>) -> bool {
1188 namespace
1189 .as_deref()
1190 .is_some_and(|value| value.trim_end_matches('/') == XMLNS_NAMESPACE)
1191}
1192
1193fn check_attribute_name_class_constraints(class: &NameClass) -> Result<(), SchemaError> {
1198 match class {
1199 NameClass::Name {
1200 lexical, namespace, ..
1201 } => {
1202 if ns_eq(namespace, &None) && lexical == "xmlns" {
1203 return Err(SchemaError::new(
1204 "RELAX NG §4.16: an attribute name class must not match `xmlns` in no namespace",
1205 ));
1206 }
1207 if is_xmlns_namespace(namespace) {
1208 return Err(SchemaError::new(format!(
1209 "RELAX NG §4.16: an attribute name class must not use the {XMLNS_NAMESPACE} namespace"
1210 )));
1211 }
1212 Ok(())
1213 }
1214 NameClass::Namespace {
1215 namespace, except, ..
1216 } => {
1217 if is_xmlns_namespace(namespace) {
1218 return Err(SchemaError::new(format!(
1219 "RELAX NG §4.16: an attribute name class must not use the {XMLNS_NAMESPACE} namespace"
1220 )));
1221 }
1222 except
1223 .iter()
1224 .try_for_each(check_attribute_name_class_constraints)
1225 }
1226 NameClass::Any { except, .. } => except
1227 .iter()
1228 .try_for_each(check_attribute_name_class_constraints),
1229 NameClass::Choice(options) => options
1230 .iter()
1231 .try_for_each(check_attribute_name_class_constraints),
1232 }
1233}
1234
1235fn name_class_contains(class: &NameClass, is_target: fn(&NameClass) -> bool) -> bool {
1240 if is_target(class) {
1241 return true;
1242 }
1243 match class {
1244 NameClass::Name { .. } => false,
1245 NameClass::Any { except, .. } | NameClass::Namespace { except, .. } => except
1246 .iter()
1247 .any(|item| name_class_contains(item, is_target)),
1248 NameClass::Choice(options) => options
1249 .iter()
1250 .any(|item| name_class_contains(item, is_target)),
1251 }
1252}
1253
1254fn name_class_contains_any(class: &NameClass) -> bool {
1255 name_class_contains(class, |class| matches!(class, NameClass::Any { .. }))
1256}
1257
1258fn name_class_contains_namespace(class: &NameClass) -> bool {
1259 name_class_contains(class, |class| matches!(class, NameClass::Namespace { .. }))
1260}
1261
1262fn name_class_has_infinite_names(class: &NameClass) -> bool {
1265 match class {
1266 NameClass::Name { .. } => false,
1267 NameClass::Any { .. } | NameClass::Namespace { .. } => true,
1268 NameClass::Choice(options) => options.iter().any(name_class_has_infinite_names),
1269 }
1270}
1271
1272fn name_class_atoms(class: &NameClass) -> Vec<&NameClass> {
1275 match class {
1276 NameClass::Choice(options) => options.iter().flat_map(name_class_atoms).collect(),
1277 atom => vec![atom],
1278 }
1279}
1280
1281pub(crate) fn ns_eq(a: &Option<String>, b: &Option<String>) -> bool {
1286 a.as_deref().unwrap_or("") == b.as_deref().unwrap_or("")
1287}
1288
1289pub(crate) fn name_class_matches(
1294 class: &NameClass,
1295 namespace: &Option<String>,
1296 local: &str,
1297) -> bool {
1298 match class {
1299 NameClass::Name {
1300 lexical,
1301 namespace: ns,
1302 ..
1303 } => ns_eq(ns, namespace) && lexical == local,
1304 NameClass::Any { except, .. } => !except
1305 .iter()
1306 .any(|item| name_class_matches(item, namespace, local)),
1307 NameClass::Namespace {
1308 namespace: ns,
1309 except,
1310 ..
1311 } => {
1312 ns_eq(ns, namespace)
1313 && !except
1314 .iter()
1315 .any(|item| name_class_matches(item, namespace, local))
1316 }
1317 NameClass::Choice(options) => options
1318 .iter()
1319 .any(|item| name_class_matches(item, namespace, local)),
1320 }
1321}
1322
1323fn excludes_everything(except: &[NameClass]) -> bool {
1328 except.iter().any(|item| {
1329 name_class_atoms(item)
1330 .into_iter()
1331 .any(|atom| matches!(atom, NameClass::Any { except, .. } if except.is_empty()))
1332 })
1333}
1334
1335fn name_class_atom_overlaps(a: &NameClass, b: &NameClass) -> bool {
1336 match (a, b) {
1337 (
1338 NameClass::Name {
1339 lexical: l1,
1340 namespace: n1,
1341 ..
1342 },
1343 NameClass::Name {
1344 lexical: l2,
1345 namespace: n2,
1346 ..
1347 },
1348 ) => l1 == l2 && ns_eq(n1, n2),
1349 (
1350 NameClass::Name {
1351 lexical, namespace, ..
1352 },
1353 other,
1354 )
1355 | (
1356 other,
1357 NameClass::Name {
1358 lexical, namespace, ..
1359 },
1360 ) => name_class_matches(other, namespace, lexical),
1361 (
1362 NameClass::Namespace {
1363 namespace: n1,
1364 except: e1,
1365 ..
1366 },
1367 NameClass::Namespace {
1368 namespace: n2,
1369 except: e2,
1370 ..
1371 },
1372 ) => ns_eq(n1, n2) && !excludes_everything(e1) && !excludes_everything(e2),
1373 (NameClass::Namespace { except, .. }, NameClass::Any { except: other, .. })
1374 | (NameClass::Any { except: other, .. }, NameClass::Namespace { except, .. }) => {
1375 !excludes_everything(except) && !excludes_everything(other)
1376 }
1377 (NameClass::Any { except: e1, .. }, NameClass::Any { except: e2, .. }) => {
1378 !excludes_everything(e1) && !excludes_everything(e2)
1379 }
1380 (NameClass::Choice(_), _) | (_, NameClass::Choice(_)) => {
1381 unreachable!("choices are flattened into atoms before comparison")
1382 }
1383 }
1384}
1385
1386fn name_classes_overlap(a: &NameClass, b: &NameClass) -> bool {
1388 let atoms_b = name_class_atoms(b);
1389 name_class_atoms(a)
1390 .into_iter()
1391 .any(|x| atoms_b.iter().any(|y| name_class_atom_overlaps(x, y)))
1392}
1393
1394#[derive(Default)]
1401struct Occurrences<'a> {
1402 attributes: Vec<&'a NameClass>,
1403 elements: Vec<&'a NameClass>,
1404 has_text: bool,
1405 child_capable: bool,
1408 string_capable: bool,
1411}
1412
1413fn collect_occurrences<'a>(
1414 pattern: &'a Pattern,
1415 definitions: &'a BTreeMap<String, Pattern>,
1416 visiting: &mut BTreeSet<String>,
1417 out: &mut Occurrences<'a>,
1418) {
1419 match pattern {
1420 Pattern::Attribute { name, .. } => out.attributes.push(name),
1421 Pattern::Element { name, .. } => {
1422 out.elements.push(name);
1423 out.child_capable = true;
1424 }
1425 Pattern::Text => {
1426 out.has_text = true;
1427 out.child_capable = true;
1428 }
1429 Pattern::Data { .. } | Pattern::Value { .. } => {
1430 out.child_capable = true;
1431 out.string_capable = true;
1432 }
1433 Pattern::List(_) => {
1434 out.child_capable = true;
1437 out.string_capable = true;
1438 }
1439 Pattern::Ref(target) => {
1440 if let Some(definition) = definitions.get(target)
1441 && visiting.insert(target.clone())
1442 {
1443 collect_occurrences(definition, definitions, visiting, out);
1444 visiting.remove(target);
1445 }
1446 }
1447 Pattern::Group(values)
1448 | Pattern::Choice(values)
1449 | Pattern::Interleave(values)
1450 | Pattern::Optional(values)
1451 | Pattern::ZeroOrMore(values)
1452 | Pattern::OneOrMore(values)
1453 | Pattern::Mixed(values) => {
1454 for child in values {
1455 collect_occurrences(child, definitions, visiting, out);
1456 }
1457 }
1458 Pattern::Grammar(_) | Pattern::ExternalRef { .. } | Pattern::ParentRef(_) => {
1459 unreachable!(
1460 "nested grammars, externalRef and parentRef are resolved by simplify_pattern"
1461 )
1462 }
1463 Pattern::Empty | Pattern::NotAllowed => {}
1464 }
1465}
1466
1467fn check_pairwise(
1472 values: &[Pattern],
1473 definitions: &BTreeMap<String, Pattern>,
1474 is_interleave: bool,
1475) -> Result<(), SchemaError> {
1476 let occurrences: Vec<Occurrences> = values
1477 .iter()
1478 .map(|value| {
1479 let mut out = Occurrences::default();
1480 collect_occurrences(value, definitions, &mut BTreeSet::new(), &mut out);
1481 out
1482 })
1483 .collect();
1484 for i in 0..occurrences.len() {
1485 for j in (i + 1)..occurrences.len() {
1486 let (left, right) = (&occurrences[i], &occurrences[j]);
1487 if (left.child_capable && right.string_capable)
1488 || (left.string_capable && right.child_capable)
1489 {
1490 return Err(SchemaError::new(
1491 "RELAX NG §7.2: a pattern matching a child and a pattern matching a single string must be alternatives",
1492 ));
1493 }
1494 for a in &occurrences[i].attributes {
1495 for b in &occurrences[j].attributes {
1496 if name_classes_overlap(a, b) {
1497 return Err(SchemaError::new(
1498 "RELAX NG §7.3: a group/interleave must not allow two attributes with the same name",
1499 ));
1500 }
1501 }
1502 }
1503 if is_interleave {
1504 if occurrences[i].has_text && occurrences[j].has_text {
1505 return Err(SchemaError::new(
1506 "RELAX NG §7.4: a text pattern must not occur on both sides of an interleave",
1507 ));
1508 }
1509 for a in &occurrences[i].elements {
1510 for b in &occurrences[j].elements {
1511 if name_classes_overlap(a, b) {
1512 return Err(SchemaError::new(
1513 "RELAX NG §7.4: an interleave must not allow overlapping element names on both sides",
1514 ));
1515 }
1516 }
1517 }
1518 }
1519 }
1520 }
1521 Ok(())
1522}
1523
1524fn check_group_restrictions(
1530 pattern: &Pattern,
1531 definitions: &BTreeMap<String, Pattern>,
1532) -> Result<(), SchemaError> {
1533 let recurse = |values: &[Pattern]| -> Result<(), SchemaError> {
1534 values
1535 .iter()
1536 .try_for_each(|child| check_group_restrictions(child, definitions))
1537 };
1538 match pattern {
1539 Pattern::Group(values) => {
1540 check_pairwise(values, definitions, false)?;
1541 recurse(values)?;
1542 }
1543 Pattern::Interleave(values) => {
1544 check_pairwise(values, definitions, true)?;
1545 recurse(values)?;
1546 }
1547 Pattern::Element { body, .. } | Pattern::Attribute { body, .. } => recurse(body)?,
1548 Pattern::Choice(values)
1549 | Pattern::Optional(values)
1550 | Pattern::ZeroOrMore(values)
1551 | Pattern::OneOrMore(values)
1552 | Pattern::Mixed(values) => recurse(values)?,
1553 _ => {}
1560 }
1561 Ok(())
1562}
1563
1564#[cfg(test)]
1565mod tests {
1566 use super::*;
1567 use crate::{SchemaSource, SchemaSyntax};
1568
1569 struct Resolver(BTreeMap<String, SchemaSource>);
1570
1571 impl SchemaResolver for Resolver {
1572 fn resolve(
1573 &self,
1574 href: &str,
1575 _base_uri: &str,
1576 ) -> Result<SchemaSource, crate::ResolveError> {
1577 self.0
1578 .get(href)
1579 .cloned()
1580 .ok_or_else(|| crate::ResolveError::new("not found"))
1581 }
1582 }
1583
1584 fn compact(text: &str) -> SchemaDocument {
1585 crate::parse(&SchemaSource::new(
1586 text,
1587 "memory:/main.rnc",
1588 SchemaSyntax::Compact,
1589 ))
1590 .expect("schema parses")
1591 }
1592
1593 fn xml(text: &str) -> SchemaDocument {
1594 crate::parse(&SchemaSource::new(
1595 text,
1596 "memory:/main.rng",
1597 SchemaSyntax::Xml,
1598 ))
1599 .expect("schema parses")
1600 }
1601
1602 #[test]
1603 fn include_override_replaces_the_included_definition() {
1604 let resolver = Resolver(BTreeMap::from([(
1605 "base.rnc".into(),
1606 SchemaSource::new(
1607 "start = root\nroot = element base { empty }",
1608 "memory:/base.rnc",
1609 SchemaSyntax::Compact,
1610 ),
1611 )]));
1612 let schema = compact("include \"base.rnc\" { root = element override { empty } }");
1613 let compiled = simplify(&schema, &resolver).expect("schema simplifies");
1614 assert!(compiled.has_start());
1615 assert_eq!(compiled.definition_count(), 1);
1616 }
1617
1618 #[test]
1636 fn default_namespace_inherits_across_include_into_a_file_that_declares_none() {
1637 let resolver = Resolver(BTreeMap::from([(
1638 "root.rnc".into(),
1639 SchemaSource::new(
1640 "root.elem = element root { empty }",
1641 "memory:/root.rnc",
1642 SchemaSyntax::Compact,
1643 ),
1644 )]));
1645 let schema =
1646 compact("default namespace = \"urn:example\"\ninclude \"root.rnc\"\nstart = root.elem");
1647 let compiled = simplify(&schema, &resolver).expect("schema simplifies");
1648
1649 struct NamespacedRoot;
1650 impl crate::Element for NamespacedRoot {
1651 type Location = ();
1652 fn name(&self) -> crate::ExpandedName {
1653 crate::ExpandedName {
1654 namespace: Some("urn:example".to_string()),
1655 local: "root".to_string(),
1656 }
1657 }
1658 fn attributes(&self) -> impl Iterator<Item = (crate::ExpandedName, String)> {
1659 std::iter::empty()
1660 }
1661 fn children(&self) -> impl Iterator<Item = crate::Content<Self>> {
1662 std::iter::empty()
1663 }
1664 }
1665
1666 let registry = crate::DatatypeRegistry::new();
1667 let errors = crate::validate(&compiled, ®istry, &NamespacedRoot)
1668 .expect("builtin-only registry needs no datatype support here");
1669 assert!(
1670 errors.is_empty(),
1671 "expected the namespaced root element to validate cleanly, got {errors:?}"
1672 );
1673 }
1674
1675 #[test]
1676 fn combine_requires_consistent_methods() {
1677 let resolver = Resolver(BTreeMap::new());
1678 let schema = compact("start = element root { empty }\npart |= empty\npart &= empty");
1679 assert!(simplify(&schema, &resolver).is_err());
1680 }
1681
1682 #[test]
1683 fn combine_allows_exactly_one_fragment_without_it() {
1684 let resolver = Resolver(BTreeMap::new());
1685 let schema = compact(
1686 "start = x\nx |= element foo1 { empty }\nx |= element foo2 { empty }\nx = element foo3 { empty }",
1687 );
1688 assert!(simplify(&schema, &resolver).is_ok());
1689 }
1690
1691 #[test]
1692 fn combine_rejects_more_than_one_fragment_without_it() {
1693 let resolver = Resolver(BTreeMap::new());
1694 let schema = compact(
1695 "start = x\nx = element foo1 { empty }\nx |= element foo2 { empty }\nx = element foo3 { empty }",
1696 );
1697 assert!(simplify(&schema, &resolver).is_err());
1698 }
1699
1700 #[test]
1701 fn nested_grammar_is_flattened_with_renamed_definitions() {
1702 let resolver = Resolver(BTreeMap::new());
1703 let schema = compact(
1704 "start = element root { grammar { start = foo\nfoo = element leaf { empty } } }",
1705 );
1706 let compiled = simplify(&schema, &resolver).expect("schema simplifies");
1707 assert!(compiled.has_start());
1708 assert_eq!(compiled.definition_count(), 1);
1709 let Pattern::Element { body, .. } = &compiled.start else {
1710 panic!("expected element pattern");
1711 };
1712 let Pattern::Ref(name) = &body[0] else {
1713 panic!("expected ref to renamed nested definition");
1714 };
1715 assert_ne!(name, "foo");
1716 let target = compiled
1717 .definitions
1718 .get(name)
1719 .expect("renamed definition exists");
1720 assert!(matches!(target, Pattern::Element { .. }));
1721 }
1722
1723 #[test]
1724 fn parent_ref_resolves_into_the_enclosing_scope() {
1725 let resolver = Resolver(BTreeMap::new());
1726 let schema = compact(
1727 "start = element root { grammar { start = element nested { parent shared } } }\nshared = element marker { empty }",
1728 );
1729 let compiled = simplify(&schema, &resolver).expect("schema simplifies");
1730 assert!(compiled.has_start());
1731 assert_eq!(compiled.definition_count(), 1);
1734 let Pattern::Element { body, .. } = &compiled.start else {
1735 panic!("expected element pattern");
1736 };
1737 let Pattern::Element {
1738 body: nested_body, ..
1739 } = &body[0]
1740 else {
1741 panic!("expected nested element pattern");
1742 };
1743 assert_eq!(nested_body[0], Pattern::Ref("shared".into()));
1744 assert!(compiled.definitions.contains_key("shared"));
1745 }
1746
1747 #[test]
1748 fn duplicate_attribute_names_in_a_group_are_rejected() {
1749 let resolver = Resolver(BTreeMap::new());
1750 let schema =
1751 compact("start = element root { attribute id { text }, attribute id { text } }");
1752 assert!(simplify(&schema, &resolver).is_err());
1753 }
1754
1755 #[test]
1756 fn duplicate_attribute_names_reached_through_refs_are_rejected() {
1757 let resolver = Resolver(BTreeMap::new());
1758 let schema = compact(
1759 "start = element root { a, b }\na = attribute id { text }\nb = attribute id { text }",
1760 );
1761 assert!(simplify(&schema, &resolver).is_err());
1762 }
1763
1764 #[test]
1765 fn distinct_attribute_names_in_a_group_are_allowed() {
1766 let resolver = Resolver(BTreeMap::new());
1767 let schema =
1768 compact("start = element root { attribute id { text }, attribute name { text } }");
1769 assert!(simplify(&schema, &resolver).is_ok());
1770 }
1771
1772 #[test]
1773 fn wildcard_attribute_without_one_or_more_ancestor_is_rejected() {
1774 let resolver = Resolver(BTreeMap::new());
1775 let schema = compact("start = element root { attribute * { text } }");
1776 assert!(simplify(&schema, &resolver).is_err());
1777 }
1778
1779 #[test]
1780 fn attribute_cannot_contain_an_element() {
1781 let resolver = Resolver(BTreeMap::new());
1782 let schema = compact("start = element root { attribute bar { element baz { empty } } }");
1783 assert!(simplify(&schema, &resolver).is_err());
1784 }
1785
1786 #[test]
1787 fn list_cannot_contain_an_element() {
1788 let resolver = Resolver(BTreeMap::new());
1789 let schema = compact("start = element root { list { element bar { empty } } }");
1790 assert!(simplify(&schema, &resolver).is_err());
1791 }
1792
1793 #[test]
1794 fn one_or_more_cannot_wrap_a_group_containing_an_attribute() {
1795 let resolver = Resolver(BTreeMap::new());
1796 let schema =
1797 compact("start = element root { (attribute bar { text }, attribute baz { text })+ }");
1798 assert!(simplify(&schema, &resolver).is_err());
1799 }
1800
1801 #[test]
1802 fn one_or_more_around_a_bare_attribute_is_allowed() {
1803 let resolver = Resolver(BTreeMap::new());
1804 let schema = compact("start = element root { (attribute bar { text })+ }");
1805 assert!(simplify(&schema, &resolver).is_ok());
1806 }
1807
1808 #[test]
1809 fn ref_to_an_undefined_name_is_rejected() {
1810 let resolver = Resolver(BTreeMap::new());
1811 let schema = compact("start = missing");
1812 assert!(simplify(&schema, &resolver).is_err());
1813 }
1814
1815 #[test]
1816 fn ref_reaching_itself_without_an_element_is_rejected() {
1817 let resolver = Resolver(BTreeMap::new());
1818 let schema = compact("start = element root { bar }\nbar = (element bar { empty } | bar)");
1819 assert!(simplify(&schema, &resolver).is_err());
1820 }
1821
1822 #[test]
1823 fn ref_recursing_through_an_element_is_allowed() {
1824 let resolver = Resolver(BTreeMap::new());
1825 let schema = compact("start = bar\nbar = element bar { bar? }");
1826 assert!(simplify(&schema, &resolver).is_ok());
1827 }
1828
1829 #[test]
1830 fn an_unreachable_self_referencing_define_is_allowed() {
1831 let resolver = Resolver(BTreeMap::new());
1832 let schema = compact("start = element root { empty }\nunused = unused");
1833 assert!(simplify(&schema, &resolver).is_ok());
1834 }
1835
1836 #[test]
1837 fn wildcard_attribute_with_one_or_more_ancestor_is_allowed() {
1838 let resolver = Resolver(BTreeMap::new());
1839 let schema = compact("start = element root { (attribute * { text })+ }");
1840 assert!(simplify(&schema, &resolver).is_ok());
1841 }
1842
1843 #[test]
1844 fn wildcard_attribute_with_zero_or_more_ancestor_is_allowed() {
1845 let resolver = Resolver(BTreeMap::new());
1848 let schema = compact("start = element root { (attribute * { text })* }");
1849 assert!(simplify(&schema, &resolver).is_ok());
1850 }
1851
1852 #[test]
1853 fn wildcard_attribute_wrapped_in_one_or_more_only_at_the_ref_site_is_allowed() {
1854 let resolver = Resolver(BTreeMap::new());
1859 let schema = compact(
1860 "start = element root { wildcard-attr* }\nwildcard-attr = attribute * { text }",
1861 );
1862 assert!(simplify(&schema, &resolver).is_ok());
1863 }
1864
1865 #[test]
1866 fn wildcard_attribute_ref_without_any_one_or_more_wrapping_is_rejected() {
1867 let resolver = Resolver(BTreeMap::new());
1868 let schema =
1869 compact("start = element root { wildcard-attr }\nwildcard-attr = attribute * { text }");
1870 assert!(simplify(&schema, &resolver).is_err());
1871 }
1872
1873 #[test]
1874 fn duplicate_text_in_interleave_is_rejected() {
1875 let resolver = Resolver(BTreeMap::new());
1876 let schema = compact("start = element root { text & text }");
1877 assert!(simplify(&schema, &resolver).is_err());
1878 }
1879
1880 #[test]
1881 fn duplicate_referenced_element_names_in_interleave_are_rejected() {
1882 let resolver = Resolver(BTreeMap::new());
1883 let schema = compact(
1884 "start = element root { a & b }\na = element item { empty }\nb = element item { empty }",
1885 );
1886 assert!(simplify(&schema, &resolver).is_err());
1887 }
1888
1889 #[test]
1890 fn direct_duplicate_element_names_in_interleave_are_also_rejected() {
1891 let resolver = Resolver(BTreeMap::new());
1895 let schema =
1896 compact("start = element root { element item { empty } & element item { empty } }");
1897 assert!(simplify(&schema, &resolver).is_err());
1898 }
1899
1900 #[test]
1901 fn start_cannot_be_a_bare_attribute() {
1902 let resolver = Resolver(BTreeMap::new());
1903 let schema = compact("start = attribute foo { text }");
1904 assert!(simplify(&schema, &resolver).is_err());
1905 }
1906
1907 #[test]
1908 fn a_bare_pattern_root_is_also_checked_against_start_restrictions() {
1909 let resolver = Resolver(BTreeMap::new());
1910 let schema = compact("text");
1911 assert!(simplify(&schema, &resolver).is_err());
1912 }
1913
1914 #[test]
1915 fn data_except_cannot_contain_text() {
1916 let resolver = Resolver(BTreeMap::new());
1917 let schema = compact("start = element root { string {} - text }");
1918 assert!(simplify(&schema, &resolver).is_err());
1919 }
1920
1921 #[test]
1922 fn data_except_with_a_value_is_allowed() {
1923 let resolver = Resolver(BTreeMap::new());
1924 let schema = compact("start = element root { string {} - \"foo\" }");
1925 assert!(simplify(&schema, &resolver).is_ok());
1926 }
1927
1928 #[test]
1929 fn unknown_type_in_the_built_in_datatype_library_is_rejected() {
1930 let resolver = Resolver(BTreeMap::new());
1934 let schema = xml(
1935 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="foo"><data type="tok"/></element>"#,
1936 );
1937 assert!(simplify(&schema, &resolver).is_err());
1938 }
1939
1940 #[test]
1941 fn datatype_library_is_inherited_from_an_ancestor_element() {
1942 let resolver = Resolver(BTreeMap::new());
1943 let schema = xml(
1944 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="foo" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"><data type="NCName"/></element>"#,
1945 );
1946 assert!(simplify(&schema, &resolver).is_ok());
1947 }
1948
1949 #[test]
1950 fn a_string_pattern_sequenced_with_an_element_is_rejected() {
1951 let resolver = Resolver(BTreeMap::new());
1952 let schema = compact("start = element root { string, element child { empty } }");
1953 assert!(simplify(&schema, &resolver).is_err());
1954 }
1955
1956 #[test]
1957 fn a_string_pattern_as_an_alternative_to_an_element_is_allowed() {
1958 let resolver = Resolver(BTreeMap::new());
1959 let schema = compact("start = element root { string | element child { empty } }");
1960 assert!(simplify(&schema, &resolver).is_ok());
1961 }
1962
1963 #[test]
1964 fn string_sequences_inside_list_are_not_restricted_by_7_2() {
1965 let resolver = Resolver(BTreeMap::new());
1966 let schema = compact("start = element root { list { string, string } }");
1967 assert!(simplify(&schema, &resolver).is_ok());
1968 }
1969
1970 #[test]
1971 fn external_reference_is_resolved_through_the_caller() {
1972 let resolver = Resolver(BTreeMap::from([(
1973 "part.rnc".into(),
1974 SchemaSource::new(
1975 "element child { empty }",
1976 "memory:/part.rnc",
1977 SchemaSyntax::Compact,
1978 ),
1979 )]));
1980 let schema = compact("start = external \"part.rnc\"");
1981 assert!(
1982 simplify(&schema, &resolver)
1983 .expect("external pattern simplifies")
1984 .has_start()
1985 );
1986 }
1987}