1#![forbid(unsafe_code)]
2
3use std::collections::HashMap;
21use std::sync::Arc;
22
23use sup_xml_tree::dom::{Document, Node, NodeKind};
24
25use crate::parser::parse_str;
26use crate::error::{ErrorDomain, ErrorLevel, Result, XmlError};
27use crate::options::ParseOptions;
28
29pub const RELAXNG_NS: &str = "http://relaxng.org/ns/structure/1.0";
31
32#[derive(Debug, Clone)]
39pub enum Pattern {
40 Empty,
42 NotAllowed,
44 Text,
46 Value {
49 datatype: String,
50 text: String,
51 },
52 Data {
55 datatype: String,
56 params: Vec<(String, String)>,
57 },
58 List(Arc<Pattern>),
61 Element {
63 name: NameClass,
64 child: Arc<Pattern>,
65 },
66 Attribute {
68 name: NameClass,
69 child: Arc<Pattern>,
70 },
71 Group(Arc<Pattern>, Arc<Pattern>),
73 Interleave(Arc<Pattern>, Arc<Pattern>),
75 Choice(Arc<Pattern>, Arc<Pattern>),
77 OneOrMore(Arc<Pattern>),
79 Ref(String),
81 After(Arc<Pattern>, Arc<Pattern>),
85}
86
87#[derive(Debug, Clone)]
90pub enum NameClass {
91 Name {
93 namespace: String,
94 local: String,
95 },
96 AnyName(Option<Box<NameClass>>),
98 NsName {
100 namespace: String,
101 except: Option<Box<NameClass>>,
102 },
103 Choice(Box<NameClass>, Box<NameClass>),
105 Nothing,
107}
108
109impl NameClass {
110 #[allow(dead_code)]
111 fn describe(&self) -> String {
112 match self {
113 NameClass::Name { namespace, local } => {
114 if namespace.is_empty() {
115 local.clone()
116 } else {
117 format!("{{{namespace}}}{local}")
118 }
119 }
120 NameClass::AnyName(_) => "*".into(),
121 NameClass::NsName { namespace, .. } => format!("{{{namespace}}}*"),
122 NameClass::Choice(a, b) => format!("{} | {}", a.describe(), b.describe()),
123 NameClass::Nothing => "<nothing>".into(),
124 }
125 }
126}
127
128#[derive(Debug, Clone)]
130pub struct RngSchema {
131 pub start: Arc<Pattern>,
132 pub defines: HashMap<String, Arc<Pattern>>,
133}
134
135fn p_empty() -> Arc<Pattern> { Arc::new(Pattern::Empty) }
142fn p_not_allowed() -> Arc<Pattern> { Arc::new(Pattern::NotAllowed) }
143
144fn choice(a: Arc<Pattern>, b: Arc<Pattern>) -> Arc<Pattern> {
145 match (&*a, &*b) {
146 (Pattern::NotAllowed, _) => b,
147 (_, Pattern::NotAllowed) => a,
148 _ => Arc::new(Pattern::Choice(a, b)),
149 }
150}
151
152fn group(a: Arc<Pattern>, b: Arc<Pattern>) -> Arc<Pattern> {
153 match (&*a, &*b) {
154 (Pattern::NotAllowed, _) | (_, Pattern::NotAllowed) => p_not_allowed(),
155 (Pattern::Empty, _) => b,
156 (_, Pattern::Empty) => a,
157 _ => Arc::new(Pattern::Group(a, b)),
158 }
159}
160
161fn interleave(a: Arc<Pattern>, b: Arc<Pattern>) -> Arc<Pattern> {
162 match (&*a, &*b) {
163 (Pattern::NotAllowed, _) | (_, Pattern::NotAllowed) => p_not_allowed(),
164 (Pattern::Empty, _) => b,
165 (_, Pattern::Empty) => a,
166 _ => Arc::new(Pattern::Interleave(a, b)),
167 }
168}
169
170fn one_or_more(p: Arc<Pattern>) -> Arc<Pattern> {
171 match &*p {
172 Pattern::NotAllowed => p_not_allowed(),
173 Pattern::Empty => p_empty(),
174 _ => Arc::new(Pattern::OneOrMore(p)),
175 }
176}
177
178fn after(a: Arc<Pattern>, b: Arc<Pattern>) -> Arc<Pattern> {
179 match &*a {
180 Pattern::NotAllowed => p_not_allowed(),
181 _ => Arc::new(Pattern::After(a, b)),
182 }
183}
184
185fn nullable(p: &Pattern, defs: &HashMap<String, Arc<Pattern>>) -> bool {
189 match p {
190 Pattern::Empty | Pattern::Text => true,
191 Pattern::NotAllowed
192 | Pattern::Value { .. }
193 | Pattern::Data { .. }
194 | Pattern::List(_)
195 | Pattern::Element { .. }
196 | Pattern::Attribute { .. }
197 | Pattern::After(_, _) => false,
198 Pattern::Group(a, b) | Pattern::Interleave(a, b) => {
199 nullable(a, defs) && nullable(b, defs)
200 }
201 Pattern::Choice(a, b) => nullable(a, defs) || nullable(b, defs),
202 Pattern::OneOrMore(inner) => nullable(inner, defs),
203 Pattern::Ref(name) => match defs.get(name) {
204 Some(target) => nullable(target, defs),
205 None => false,
206 },
207 }
208}
209
210fn name_class_matches(nc: &NameClass, ns: &str, local: &str) -> bool {
213 match nc {
214 NameClass::Name { namespace, local: l } => namespace == ns && l == local,
215 NameClass::AnyName(except) => {
216 except.as_ref().is_none_or(|e| !name_class_matches(e, ns, local))
217 }
218 NameClass::NsName { namespace, except } => {
219 namespace == ns
220 && except.as_ref().is_none_or(|e| !name_class_matches(e, ns, local))
221 }
222 NameClass::Choice(a, b) => name_class_matches(a, ns, local)
223 || name_class_matches(b, ns, local),
224 NameClass::Nothing => false,
225 }
226}
227
228fn child_deriv<'a>(
234 p: &Pattern,
235 ns: &str,
236 local: &str,
237 atts: &[(String, String, String)],
238 children: &[&'a Node<'a>],
239 defs: &HashMap<String, Arc<Pattern>>,
240) -> Arc<Pattern> {
241 match p {
242 Pattern::Element { name, child } => {
243 if !name_class_matches(name, ns, local) {
244 return p_not_allowed();
245 }
246 let after_atts = consume_attributes(child.clone(), atts, defs);
248 if matches!(&*after_atts, Pattern::NotAllowed) {
249 return p_not_allowed();
250 }
251 let after_content = consume_content(after_atts, children, defs);
253 if nullable(&after_content, defs) {
254 p_empty()
255 } else {
256 p_not_allowed()
257 }
258 }
259 Pattern::Choice(a, b) => choice(
260 child_deriv(a, ns, local, atts, children, defs),
261 child_deriv(b, ns, local, atts, children, defs),
262 ),
263 Pattern::Group(a, b) => {
264 let d_a = child_deriv(a, ns, local, atts, children, defs);
265 let in_a = group(d_a, b.clone());
266 if nullable(a, defs) {
267 let d_b = child_deriv(b, ns, local, atts, children, defs);
268 choice(in_a, d_b)
269 } else {
270 in_a
271 }
272 }
273 Pattern::Interleave(a, b) => {
274 let d_a = child_deriv(a, ns, local, atts, children, defs);
275 let d_b = child_deriv(b, ns, local, atts, children, defs);
276 choice(interleave(d_a, b.clone()), interleave(a.clone(), d_b))
277 }
278 Pattern::OneOrMore(inner) => {
279 let d = child_deriv(inner, ns, local, atts, children, defs);
280 group(
281 d,
282 choice(p_empty(), Arc::new(Pattern::OneOrMore(inner.clone()))),
283 )
284 }
285 Pattern::After(a, b) => after(
286 child_deriv(a, ns, local, atts, children, defs),
287 b.clone(),
288 ),
289 Pattern::Ref(name) => match defs.get(name) {
290 Some(target) => child_deriv(target, ns, local, atts, children, defs),
291 None => p_not_allowed(),
292 },
293 _ => p_not_allowed(),
294 }
295}
296
297fn text_deriv(p: &Pattern, text: &str, defs: &HashMap<String, Arc<Pattern>>) -> Arc<Pattern> {
299 match p {
300 Pattern::Text => p_empty(),
301 Pattern::Value { datatype, text: expected } => {
302 if value_matches(datatype, text, expected) {
303 p_empty()
304 } else {
305 p_not_allowed()
306 }
307 }
308 Pattern::Data { datatype, params } => {
309 if data_matches(datatype, params, text) {
310 #[cfg(feature = "xsd")]
311 record_id_value(datatype, text);
312 p_empty()
313 } else {
314 p_not_allowed()
315 }
316 }
317 Pattern::List(inner) => {
318 let tokens: Vec<&str> = text.split_whitespace().collect();
319 let mut current = inner.clone();
320 for t in &tokens {
321 current = text_deriv(¤t, t, defs);
322 if matches!(&*current, Pattern::NotAllowed) {
323 return p_not_allowed();
324 }
325 }
326 if nullable(¤t, defs) {
327 p_empty()
328 } else {
329 p_not_allowed()
330 }
331 }
332 Pattern::Choice(a, b) => choice(
333 text_deriv(a, text, defs),
334 text_deriv(b, text, defs),
335 ),
336 Pattern::Group(a, b) => {
337 let d_a = text_deriv(a, text, defs);
338 let in_a = group(d_a, b.clone());
339 if nullable(a, defs) {
340 let d_b = text_deriv(b, text, defs);
341 choice(in_a, d_b)
342 } else {
343 in_a
344 }
345 }
346 Pattern::Interleave(a, b) => {
347 let d_a = text_deriv(a, text, defs);
348 let d_b = text_deriv(b, text, defs);
349 choice(interleave(d_a, b.clone()), interleave(a.clone(), d_b))
350 }
351 Pattern::OneOrMore(inner) => {
352 let d = text_deriv(inner, text, defs);
353 group(
354 d,
355 choice(p_empty(), Arc::new(Pattern::OneOrMore(inner.clone()))),
356 )
357 }
358 Pattern::After(a, b) => after(text_deriv(a, text, defs), b.clone()),
359 Pattern::Ref(name) => match defs.get(name) {
360 Some(target) => text_deriv(target, text, defs),
361 None => p_not_allowed(),
362 },
363 Pattern::Empty => {
364 if text.trim().is_empty() {
365 p_empty()
366 } else {
367 p_not_allowed()
368 }
369 }
370 _ => p_not_allowed(),
371 }
372}
373
374fn att_deriv(
376 p: &Pattern,
377 ns: &str,
378 local: &str,
379 value: &str,
380 defs: &HashMap<String, Arc<Pattern>>,
381) -> Arc<Pattern> {
382 match p {
383 Pattern::Attribute { name, child } => {
384 if !name_class_matches(name, ns, local) {
385 return p_not_allowed();
386 }
387 let after_text = text_deriv(child, value, defs);
388 if nullable(&after_text, defs) {
389 p_empty()
390 } else {
391 p_not_allowed()
392 }
393 }
394 Pattern::Choice(a, b) => choice(
395 att_deriv(a, ns, local, value, defs),
396 att_deriv(b, ns, local, value, defs),
397 ),
398 Pattern::Group(a, b) => {
399 let d_a = att_deriv(a, ns, local, value, defs);
400 let in_a = group(d_a, b.clone());
401 let d_b = att_deriv(b, ns, local, value, defs);
402 let in_b = group(a.clone(), d_b);
403 choice(in_a, in_b)
404 }
405 Pattern::Interleave(a, b) => {
406 let d_a = att_deriv(a, ns, local, value, defs);
407 let d_b = att_deriv(b, ns, local, value, defs);
408 choice(interleave(d_a, b.clone()), interleave(a.clone(), d_b))
409 }
410 Pattern::OneOrMore(inner) => {
411 let d = att_deriv(inner, ns, local, value, defs);
412 group(
413 d,
414 choice(p_empty(), Arc::new(Pattern::OneOrMore(inner.clone()))),
415 )
416 }
417 Pattern::After(a, b) => after(att_deriv(a, ns, local, value, defs), b.clone()),
418 Pattern::Ref(name) => match defs.get(name) {
419 Some(target) => att_deriv(target, ns, local, value, defs),
420 None => p_not_allowed(),
421 },
422 _ => p_not_allowed(),
423 }
424}
425
426fn consume_content<'a>(
429 p: Arc<Pattern>,
430 children: &[&'a Node<'a>],
431 defs: &HashMap<String, Arc<Pattern>>,
432) -> Arc<Pattern> {
433 let mut current = p;
434 for c in children {
435 if matches!(&*current, Pattern::NotAllowed) {
436 return p_not_allowed();
437 }
438 current = match c.kind {
439 NodeKind::Element => {
440 let (ns, local) = name_split(c);
441 let atts = collect_attributes(c);
442 let child_refs: Vec<&Node<'_>> = c
443 .children()
444 .filter(|n| {
445 !(n.kind == NodeKind::Text
446 && n.content().trim().is_empty())
447 })
448 .collect();
449 child_deriv(¤t, &ns, local, &atts, &child_refs, defs)
450 }
451 NodeKind::Text => {
452 let txt = c.content();
453 if txt.trim().is_empty() {
454 current
457 } else {
458 text_deriv(¤t, txt, defs)
459 }
460 }
461 NodeKind::CData => text_deriv(¤t, c.content(), defs),
462 _ => current,
464 };
465 }
466 current
467}
468
469fn consume_attributes(
472 p: Arc<Pattern>,
473 atts: &[(String, String, String)],
474 defs: &HashMap<String, Arc<Pattern>>,
475) -> Arc<Pattern> {
476 let mut current = p;
477 for (ns, local, value) in atts {
478 if matches!(&*current, Pattern::NotAllowed) {
479 return p_not_allowed();
480 }
481 current = att_deriv(¤t, ns, local, value, defs);
482 }
483 current
484}
485
486fn name_split<'a>(elem: &'a Node<'a>) -> (String, &'a str) {
493 debug_assert!(elem.kind == NodeKind::Element, "name_split on non-element");
494 let name: &str = elem.name();
495 let local = name.rsplit_once(':').map(|(_, l)| l).unwrap_or(name);
496 let ns = elem
497 .namespace
498 .get()
499 .map(|n| n.href().to_string())
500 .unwrap_or_default();
501 (ns, local)
502}
503
504fn is_foreign_element<'a>(elem: &'a Node<'a>) -> bool {
512 let (ns, _) = name_split(elem);
513 !ns.is_empty() && ns != RELAXNG_NS
514}
515
516fn collect_attributes<'a>(elem: &'a Node<'a>) -> Vec<(String, String, String)> {
518 debug_assert!(elem.kind == NodeKind::Element, "collect_attributes on non-element");
519 elem.attributes()
520 .filter_map(|a| {
521 let aname: &str = a.name();
522 if aname == "xmlns" || aname.starts_with("xmlns:") {
523 return None;
524 }
525 let local = aname.rsplit_once(':').map(|(_, l)| l).unwrap_or(aname);
526 let ns = a.namespace.get().map(|n| n.href().to_string()).unwrap_or_default();
527 Some((ns, local.to_string(), a.value().to_string()))
528 })
529 .collect()
530}
531
532fn value_matches(datatype: &str, actual: &str, expected: &str) -> bool {
535 if datatype == "token" || datatype == "NMTOKEN" {
536 let a_norm = actual.split_whitespace().collect::<Vec<_>>().join(" ");
537 let e_norm = expected.split_whitespace().collect::<Vec<_>>().join(" ");
538 a_norm == e_norm
539 } else {
540 actual == expected
541 }
542}
543
544fn data_matches(datatype: &str, _params: &[(String, String)], text: &str) -> bool {
545 #[cfg(feature = "xsd")]
546 {
547 if let Some(checker) = xsd_datatype_checker(datatype) {
548 return checker(text);
549 }
550 }
551 let _ = datatype;
552 !text.is_empty() || datatype == "string"
553}
554
555#[cfg(feature = "xsd")]
566#[derive(Default)]
567struct IdRefState {
568 ids: std::collections::HashSet<String>,
569 idrefs: Vec<String>,
570}
571
572#[cfg(feature = "xsd")]
573thread_local! {
574 static ID_REFS: std::cell::RefCell<Option<IdRefState>> =
575 const { std::cell::RefCell::new(None) };
576}
577
578#[cfg(feature = "xsd")]
582fn record_id_value(datatype: &str, text: &str) {
583 ID_REFS.with(|c| {
584 if let Some(st) = c.borrow_mut().as_mut() {
585 match datatype {
586 "ID" => { st.ids.insert(text.trim().to_string()); }
587 "IDREF" => st.idrefs.push(text.trim().to_string()),
588 "IDREFS" => st.idrefs.extend(text.split_whitespace().map(str::to_string)),
589 _ => {}
590 }
591 }
592 });
593}
594
595#[cfg(feature = "xsd")]
598fn schema_uses_idref(schema: &RngSchema) -> bool {
599 fn walk(p: &Pattern) -> bool {
600 match p {
601 Pattern::Data { datatype, .. } => matches!(datatype.as_str(), "IDREF" | "IDREFS"),
602 Pattern::List(a) | Pattern::OneOrMore(a) => walk(a),
603 Pattern::Element { child, .. } | Pattern::Attribute { child, .. } => walk(child),
604 Pattern::Group(a, b) | Pattern::Interleave(a, b)
605 | Pattern::Choice(a, b) | Pattern::After(a, b) => walk(a) || walk(b),
606 _ => false,
607 }
608 }
609 walk(schema.start.as_ref()) || schema.defines.values().any(|p| walk(p.as_ref()))
610}
611
612#[cfg(feature = "xsd")]
614fn set_idref_tracking(on: bool) {
615 ID_REFS.with(|c| *c.borrow_mut() = on.then(IdRefState::default));
616}
617
618#[cfg(feature = "xsd")]
621fn first_unresolved_idref() -> Option<String> {
622 ID_REFS.with(|c| {
623 c.borrow_mut().take().and_then(|st| {
624 st.idrefs.iter().find(|r| !st.ids.contains(*r)).cloned()
625 })
626 })
627}
628
629#[cfg(feature = "xsd")]
630fn xsd_datatype_checker(datatype: &str) -> Option<fn(&str) -> bool> {
631 match datatype {
632 "string" | "token" | "normalizedString" | "Name" | "NCName" | "NMTOKEN" | "QName"
633 | "ID" | "IDREF" | "IDREFS" | "ENTITY" | "ENTITIES" | "language" => {
634 Some(|s| !s.is_empty())
635 }
636 "boolean" => Some(|s| matches!(s.trim(), "true" | "false" | "1" | "0")),
637 "decimal" | "double" | "float" => Some(|s| s.trim().parse::<f64>().is_ok()),
638 "integer" | "int" | "long" | "short" | "byte" => {
639 Some(|s| s.trim().parse::<i64>().is_ok())
640 }
641 "positiveInteger" => Some(|s| s.trim().parse::<i64>().is_ok_and(|n| n > 0)),
642 "nonNegativeInteger" | "unsignedInt" | "unsignedLong" | "unsignedShort"
643 | "unsignedByte" => Some(|s| s.trim().parse::<u64>().is_ok()),
644 "negativeInteger" => Some(|s| s.trim().parse::<i64>().is_ok_and(|n| n < 0)),
645 "nonPositiveInteger" => Some(|s| s.trim().parse::<i64>().is_ok_and(|n| n <= 0)),
646 "anyURI" => Some(|s| !s.is_empty()),
647 "date" => Some(|s| s.len() >= 10 && s[..10].chars().filter(|c| *c == '-').count() == 2),
648 _ => None,
649 }
650}
651
652pub fn parse_schema(source: &str) -> Result<RngSchema> {
658 parse_schema_with_base(source, None)
659}
660
661pub fn parse_schema_with_base(source: &str, base: Option<&str>) -> Result<RngSchema> {
665 let opts = ParseOptions {
666 namespace_aware: true,
667 ..ParseOptions::default()
668 };
669 let doc = parse_str(source, &opts)?;
670 parse_schema_doc(&doc, base)
671}
672
673fn parse_schema_doc(doc: &Document, base: Option<&str>) -> Result<RngSchema> {
674 let root = doc.root();
675 if root.kind != NodeKind::Element {
676 return Err(schema_err("schema root must be an element"));
677 }
678 let mut ctx = SchemaCtx {
679 defines: HashMap::new(),
680 define_combines: HashMap::new(),
681 start_combine: None,
682 default_ns: String::new(),
683 base: base.map(str::to_string),
684 };
685 if let Some(ns) = attr(root, "ns") {
686 ctx.default_ns = ns.to_string();
687 }
688
689 let (root_ns, local) = name_split(root);
694 if !root_ns.is_empty() && root_ns != RELAXNG_NS {
695 return Err(schema_err(format!(
696 "schema root <{}> is not in the RELAX NG namespace",
697 root.name()
698 )));
699 }
700 let start = match local {
701 "grammar" => parse_grammar(root, &mut ctx)?,
702 _ => parse_pattern_element(root, &ctx)?,
703 };
704
705 Ok(RngSchema { start, defines: ctx.defines })
706}
707
708struct SchemaCtx {
709 defines: HashMap<String, Arc<Pattern>>,
710 define_combines: HashMap<String, Option<CombineKind>>,
711 start_combine: Option<CombineKind>,
712 default_ns: String,
713 base: Option<String>,
716}
717
718#[derive(Debug, Clone, Copy, PartialEq, Eq)]
719enum CombineKind {
720 Choice,
721 Interleave,
722}
723
724fn parse_combine_attr<'a>(elem: &'a Node<'a>, ctx_label: &str) -> Result<Option<CombineKind>> {
725 match attr(elem, "combine") {
726 None => Ok(None),
727 Some("choice") => Ok(Some(CombineKind::Choice)),
728 Some("interleave") => Ok(Some(CombineKind::Interleave)),
729 Some(other) => Err(schema_err(format!(
730 "invalid combine={other:?} on {ctx_label} — must be \"choice\" or \"interleave\""
731 ))),
732 }
733}
734
735fn merge_with_combine(
736 name: &str,
737 existing: Arc<Pattern>,
738 new_pat: Arc<Pattern>,
739 prev_combine: Option<CombineKind>,
740 new_combine: Option<CombineKind>,
741 label: &str,
742) -> Result<(Arc<Pattern>, CombineKind)> {
743 let effective = match (prev_combine, new_combine) {
744 (Some(a), Some(b)) if a == b => a,
745 (Some(a), Some(b)) => {
746 return Err(schema_err(format!(
747 "inconsistent combine values for {label} {name:?}: previously {a:?}, now {b:?}"
748 )));
749 }
750 (None, Some(b)) => b,
751 (Some(a), None) => a,
752 (None, None) => {
753 return Err(schema_err(format!(
754 "duplicate {label} {name:?} requires a combine attribute (\"choice\" or \"interleave\") \
755 on at least the second occurrence — RelaxNG spec § 4.17"
756 )));
757 }
758 };
759 let merged = match effective {
760 CombineKind::Choice => choice(existing, new_pat),
761 CombineKind::Interleave => interleave(existing, new_pat),
762 };
763 Ok((merged, effective))
764}
765
766fn parse_grammar<'a>(elem: &'a Node<'a>, ctx: &mut SchemaCtx) -> Result<Arc<Pattern>> {
767 if let Some(ns) = attr(elem, "ns") {
768 ctx.default_ns = ns.to_string();
769 }
770 let mut start: Option<Arc<Pattern>> = None;
771 for child in elem.children() {
772 if child.kind != NodeKind::Element || is_foreign_element(child) {
773 continue;
774 }
775 match local_name(child.name()) {
776 "start" => handle_start(child, ctx, &mut start)?,
777 "define" => handle_define(child, ctx)?,
778 "div" => parse_grammar_div(child, ctx, &mut start)?,
779 "include" => handle_include(child, ctx, &mut start)?,
780 other => {
781 return Err(schema_err(format!(
782 "unsupported grammar child <{other}>"
783 )));
784 }
785 }
786 }
787 start.ok_or_else(|| schema_err("<grammar> requires a <start>"))
788}
789
790fn handle_include<'a>(
795 elem: &'a Node<'a>,
796 ctx: &mut SchemaCtx,
797 start: &mut Option<Arc<Pattern>>,
798) -> Result<()> {
799 let href = attr(elem, "href")
800 .ok_or_else(|| schema_err("<include> requires an href attribute"))?;
801 let resolved = crate::resolve_uri(href, ctx.base.as_deref());
802 let path = resolved.strip_prefix("file://").unwrap_or(&resolved);
803 let src = std::fs::read_to_string(path)
804 .map_err(|e| schema_err(format!("<include href=\"{href}\">: {e}")))?;
805 let opts = ParseOptions { namespace_aware: true, ..ParseOptions::default() };
806 let inc_doc = parse_str(&src, &opts)?;
807 let inc_root = inc_doc.root();
808 if local_name(inc_root.name()) != "grammar" {
809 return Err(schema_err("<include> href must point to a <grammar>"));
810 }
811
812 let mut overridden: std::collections::HashSet<String> = std::collections::HashSet::new();
816 let mut override_start = false;
817 for ovc in elem.children() {
818 if ovc.kind != NodeKind::Element || is_foreign_element(ovc) {
819 continue;
820 }
821 match local_name(ovc.name()) {
822 "define" => {
823 if let Some(n) = attr(ovc, "name") {
824 overridden.insert(n.to_string());
825 }
826 handle_define(ovc, ctx)?;
827 }
828 "start" => {
829 override_start = true;
830 handle_start(ovc, ctx, start)?;
831 }
832 _ => {}
833 }
834 }
835
836 let saved_base = ctx.base.take();
839 ctx.base = Some(resolved.clone());
840 if let Some(ns) = attr(inc_root, "ns") {
841 let _ = ns;
845 }
846 for child in inc_root.children() {
847 if child.kind != NodeKind::Element || is_foreign_element(child) {
848 continue;
849 }
850 match local_name(child.name()) {
851 "start" if !override_start => handle_start(child, ctx, start)?,
852 "start" => {}
853 "define" => {
854 if attr(child, "name").map(|n| !overridden.contains(n)).unwrap_or(true) {
855 handle_define(child, ctx)?;
856 }
857 }
858 "div" => parse_grammar_div(child, ctx, start)?,
859 "include" => handle_include(child, ctx, start)?,
860 _ => {}
861 }
862 }
863 ctx.base = saved_base;
864 Ok(())
865}
866
867fn parse_grammar_div<'a>(
868 elem: &'a Node<'a>,
869 ctx: &mut SchemaCtx,
870 start: &mut Option<Arc<Pattern>>,
871) -> Result<()> {
872 for child in elem.children() {
873 if child.kind != NodeKind::Element || is_foreign_element(child) {
874 continue;
875 }
876 match local_name(child.name()) {
877 "start" => handle_start(child, ctx, start)?,
878 "define" => handle_define(child, ctx)?,
879 "div" => parse_grammar_div(child, ctx, start)?,
880 _ => {}
881 }
882 }
883 Ok(())
884}
885
886fn handle_start<'a>(
887 e: &'a Node<'a>,
888 ctx: &mut SchemaCtx,
889 start: &mut Option<Arc<Pattern>>,
890) -> Result<()> {
891 let new_combine = parse_combine_attr(e, "<start>")?;
892 let new_pat = combine_seq(parse_pattern_children(e, ctx)?);
893 match start.take() {
894 None => {
895 *start = Some(new_pat);
896 ctx.start_combine = new_combine;
897 }
898 Some(existing) => {
899 let prev = ctx.start_combine;
900 let (merged, effective) =
901 merge_with_combine("<start>", existing, new_pat, prev, new_combine, "<start>")?;
902 *start = Some(merged);
903 ctx.start_combine = Some(effective);
904 }
905 }
906 Ok(())
907}
908
909fn handle_define<'a>(e: &'a Node<'a>, ctx: &mut SchemaCtx) -> Result<()> {
910 let name = attr(e, "name")
911 .ok_or_else(|| schema_err("<define> missing name attribute"))?
912 .to_string();
913 let new_combine = parse_combine_attr(e, &format!("<define name={name:?}/>"))?;
914 let new_pat = combine_seq(parse_pattern_children(e, ctx)?);
915 match ctx.defines.get(&name).cloned() {
916 None => {
917 ctx.defines.insert(name.clone(), new_pat);
918 ctx.define_combines.insert(name, new_combine);
919 }
920 Some(existing) => {
921 let prev_combine = ctx.define_combines.get(&name).copied().flatten();
922 let (merged, effective) = merge_with_combine(
923 &name,
924 existing,
925 new_pat,
926 prev_combine,
927 new_combine,
928 "<define>",
929 )?;
930 ctx.defines.insert(name.clone(), merged);
931 ctx.define_combines.insert(name, Some(effective));
932 }
933 }
934 Ok(())
935}
936
937fn parse_pattern_element<'a>(elem: &'a Node<'a>, ctx: &SchemaCtx) -> Result<Arc<Pattern>> {
938 debug_assert!(elem.kind == NodeKind::Element, "parse_pattern_element on non-element");
939 match local_name(elem.name()) {
940 "element" => {
941 let nc = parse_name_class(elem, ctx, false)?;
942 let skip_first_child = attr(elem, "name").is_none();
943 let pats = parse_content_patterns(elem, ctx, skip_first_child)?;
944 if pats.is_empty() {
948 return Err(schema_err("<element> pattern has no content pattern"));
949 }
950 let child = combine_seq(pats);
951 Ok(Arc::new(Pattern::Element { name: nc, child }))
952 }
953 "attribute" => {
954 let nc = parse_name_class(elem, ctx, true)?;
955 let skip_first_child = attr(elem, "name").is_none();
956 let pats = parse_content_patterns(elem, ctx, skip_first_child)?;
957 let child = if pats.is_empty() {
958 Arc::new(Pattern::Text)
959 } else {
960 combine_seq(pats)
961 };
962 Ok(Arc::new(Pattern::Attribute { name: nc, child }))
963 }
964 "text" => Ok(Arc::new(Pattern::Text)),
965 "empty" => Ok(p_empty()),
966 "notAllowed" => Ok(p_not_allowed()),
967 "value" => {
968 let datatype = attr(elem, "type").unwrap_or("token").to_string();
969 let text = elem.text_content().unwrap_or("").to_string();
970 Ok(Arc::new(Pattern::Value { datatype, text }))
971 }
972 "data" => {
973 let datatype = attr(elem, "type").unwrap_or("string").to_string();
974 let params: Vec<(String, String)> = elem
975 .children()
976 .filter_map(|c| {
977 if c.kind == NodeKind::Element && local_name(c.name()) == "param" {
978 let name = attr(c, "name")?.to_string();
979 let value = c.text_content().unwrap_or("").to_string();
980 return Some((name, value));
981 }
982 None
983 })
984 .collect();
985 Ok(Arc::new(Pattern::Data { datatype, params }))
986 }
987 "list" => {
988 let inner = combine_seq(parse_pattern_children(elem, ctx)?);
989 Ok(Arc::new(Pattern::List(inner)))
990 }
991 "group" => Ok(combine_seq(parse_pattern_children(elem, ctx)?)),
992 "choice" => {
993 let pats = parse_pattern_children(elem, ctx)?;
994 Ok(combine_choice(pats))
995 }
996 "interleave" => {
997 let pats = parse_pattern_children(elem, ctx)?;
998 Ok(combine_interleave(pats))
999 }
1000 "mixed" => {
1001 let inner = combine_seq(parse_pattern_children(elem, ctx)?);
1002 Ok(interleave(Arc::new(Pattern::Text), inner))
1003 }
1004 "optional" => {
1005 let p = combine_seq(parse_pattern_children(elem, ctx)?);
1006 Ok(choice(p_empty(), p))
1007 }
1008 "zeroOrMore" => {
1009 let p = combine_seq(parse_pattern_children(elem, ctx)?);
1010 Ok(choice(p_empty(), one_or_more(p)))
1011 }
1012 "oneOrMore" => {
1013 let p = combine_seq(parse_pattern_children(elem, ctx)?);
1014 Ok(one_or_more(p))
1015 }
1016 "ref" => {
1017 let name = attr(elem, "name")
1018 .ok_or_else(|| schema_err("<ref> missing name attribute"))?
1019 .to_string();
1020 Ok(Arc::new(Pattern::Ref(name)))
1021 }
1022 other => Err(schema_err(format!(
1023 "unsupported pattern <{other}> — see module docs for the v1 subset"
1024 ))),
1025 }
1026}
1027
1028fn parse_pattern_children<'a>(elem: &'a Node<'a>, ctx: &SchemaCtx) -> Result<Vec<Arc<Pattern>>> {
1029 parse_content_patterns(elem, ctx, false)
1030}
1031
1032fn parse_content_patterns<'a>(
1036 elem: &'a Node<'a>,
1037 ctx: &SchemaCtx,
1038 skip_first_child: bool,
1039) -> Result<Vec<Arc<Pattern>>> {
1040 let mut out = Vec::new();
1041 let mut skipped = !skip_first_child;
1042 for child in elem.children() {
1043 if child.kind != NodeKind::Element || is_foreign_element(child) {
1044 continue;
1045 }
1046 if !skipped {
1047 skipped = true;
1048 continue;
1049 }
1050 let local = local_name(child.name());
1051 if matches!(local, "name" | "anyName" | "nsName") {
1052 continue;
1053 }
1054 out.push(parse_pattern_element(child, ctx)?);
1055 }
1056 Ok(out)
1057}
1058
1059fn resolve_name_class_qname(
1069 elem: &Node<'_>,
1070 name: &str,
1071 default_ns: &str,
1072 is_attribute: bool,
1073) -> (String, String) {
1074 match name.split_once(':') {
1075 Some((prefix, local)) => {
1076 let ns = resolve_schema_prefix(elem, prefix)
1077 .unwrap_or_else(|| default_ns.to_string());
1078 (ns, local.to_string())
1079 }
1080 None => {
1081 let ns = if is_attribute { String::new() } else { default_ns.to_string() };
1082 (ns, name.to_string())
1083 }
1084 }
1085}
1086
1087fn resolve_schema_prefix(elem: &Node<'_>, prefix: &str) -> Option<String> {
1091 if prefix == "xml" {
1092 return Some("http://www.w3.org/XML/1998/namespace".to_string());
1093 }
1094 let mut cur = Some(elem);
1095 while let Some(e) = cur {
1096 for (p, href) in e.ns_declarations() {
1097 if p == Some(prefix) {
1098 return Some(href.to_string());
1099 }
1100 }
1101 cur = e.parent.get();
1102 }
1103 None
1104}
1105
1106fn parse_name_class<'a>(elem: &'a Node<'a>, ctx: &SchemaCtx, is_attribute: bool) -> Result<NameClass> {
1108 let elem_default_ns = attr(elem, "ns")
1109 .map(|s| s.to_string())
1110 .unwrap_or_else(|| ctx.default_ns.clone());
1111
1112 if let Some(name) = attr(elem, "name") {
1113 let (namespace, local) =
1114 resolve_name_class_qname(elem, name, &elem_default_ns, is_attribute);
1115 return Ok(NameClass::Name { namespace, local });
1116 }
1117
1118 for child in elem.children() {
1119 if child.kind != NodeKind::Element {
1120 continue;
1121 }
1122 if let Some(nc) = parse_name_class_element(child, &elem_default_ns, is_attribute)? {
1123 return Ok(nc);
1124 }
1125 }
1126 Err(schema_err(format!(
1127 "<{}> needs a name attribute or child <name>/<anyName>/<nsName>/<choice>",
1128 local_name(elem.name())
1129 )))
1130}
1131
1132fn parse_name_class_element<'a>(
1133 elem: &'a Node<'a>,
1134 default_ns: &str,
1135 is_attribute: bool,
1136) -> Result<Option<NameClass>> {
1137 match local_name(elem.name()) {
1138 "name" => {
1139 let raw = elem.text_content().unwrap_or("").trim().to_string();
1140 let (namespace, local) = match attr(elem, "ns") {
1143 Some(ns) => (ns.to_string(), raw),
1144 None => resolve_name_class_qname(elem, &raw, default_ns, is_attribute),
1145 };
1146 Ok(Some(NameClass::Name { namespace, local }))
1147 }
1148 "anyName" => {
1149 let except = parse_except_name_class(elem, default_ns, is_attribute)?;
1150 Ok(Some(NameClass::AnyName(except.map(Box::new))))
1151 }
1152 "nsName" => {
1153 let ns = attr(elem, "ns")
1154 .map(|s| s.to_string())
1155 .unwrap_or_else(|| default_ns.to_string());
1156 let except = parse_except_name_class(elem, default_ns, is_attribute)?;
1157 Ok(Some(NameClass::NsName {
1158 namespace: ns,
1159 except: except.map(Box::new),
1160 }))
1161 }
1162 "choice" => {
1163 let mut alts = Vec::new();
1164 for child in elem.children() {
1165 if child.kind != NodeKind::Element {
1166 continue;
1167 }
1168 if let Some(nc) = parse_name_class_element(child, default_ns, is_attribute)? {
1169 alts.push(nc);
1170 }
1171 }
1172 Ok(Some(combine_name_choice(alts)))
1173 }
1174 _ => Ok(None),
1175 }
1176}
1177
1178fn parse_except_name_class<'a>(
1179 elem: &'a Node<'a>,
1180 default_ns: &str,
1181 is_attribute: bool,
1182) -> Result<Option<NameClass>> {
1183 for child in elem.children() {
1184 if child.kind != NodeKind::Element {
1185 continue;
1186 }
1187 if local_name(child.name()) != "except" {
1188 continue;
1189 }
1190 let mut alts = Vec::new();
1191 for ec in child.children() {
1192 if ec.kind != NodeKind::Element {
1193 continue;
1194 }
1195 if let Some(nc) = parse_name_class_element(ec, default_ns, is_attribute)? {
1196 alts.push(nc);
1197 }
1198 }
1199 if alts.is_empty() {
1200 return Err(schema_err("<except> needs at least one name class"));
1201 }
1202 return Ok(Some(combine_name_choice(alts)));
1203 }
1204 Ok(None)
1205}
1206
1207fn combine_name_choice(alts: Vec<NameClass>) -> NameClass {
1208 if alts.is_empty() {
1209 return NameClass::Nothing;
1210 }
1211 let mut iter = alts.into_iter();
1212 let mut acc = iter.next().unwrap();
1213 for a in iter {
1214 acc = NameClass::Choice(Box::new(acc), Box::new(a));
1215 }
1216 acc
1217}
1218
1219fn combine_seq(mut pats: Vec<Arc<Pattern>>) -> Arc<Pattern> {
1220 match pats.len() {
1221 0 => p_empty(),
1222 1 => pats.remove(0),
1223 _ => {
1224 let mut iter = pats.into_iter();
1225 let mut acc = iter.next().unwrap();
1226 for p in iter {
1227 acc = group(acc, p);
1228 }
1229 acc
1230 }
1231 }
1232}
1233
1234fn combine_choice(mut pats: Vec<Arc<Pattern>>) -> Arc<Pattern> {
1235 match pats.len() {
1236 0 => p_not_allowed(),
1237 1 => pats.remove(0),
1238 _ => {
1239 let mut iter = pats.into_iter();
1240 let mut acc = iter.next().unwrap();
1241 for p in iter {
1242 acc = choice(acc, p);
1243 }
1244 acc
1245 }
1246 }
1247}
1248
1249fn combine_interleave(mut pats: Vec<Arc<Pattern>>) -> Arc<Pattern> {
1250 match pats.len() {
1251 0 => p_empty(),
1252 1 => pats.remove(0),
1253 _ => {
1254 let mut iter = pats.into_iter();
1255 let mut acc = iter.next().unwrap();
1256 for p in iter {
1257 acc = interleave(acc, p);
1258 }
1259 acc
1260 }
1261 }
1262}
1263
1264fn attr<'a>(elem: &'a Node<'a>, name: &str) -> Option<&'a str> {
1265 elem.attributes().find_map(|a| {
1266 if a.name() == name {
1267 Some(a.value())
1268 } else {
1269 None
1270 }
1271 })
1272}
1273
1274fn local_name(name: &str) -> &str {
1275 name.rsplit_once(':').map(|(_, l)| l).unwrap_or(name)
1276}
1277
1278pub fn validate(schema: &RngSchema, doc: &Document) -> Result<()> {
1282 let root = doc.root();
1283 if root.kind != NodeKind::Element {
1284 return Err(validation_err("document root is not an element"));
1285 }
1286 let (ns, local) = name_split(root);
1287 let atts = collect_attributes(root);
1288 let children: Vec<&Node<'_>> = root
1289 .children()
1290 .filter(|n| {
1291 !(n.kind == NodeKind::Text && n.content().trim().is_empty())
1292 })
1293 .collect();
1294 #[cfg(feature = "xsd")]
1296 let track_idrefs = schema_uses_idref(schema);
1297 #[cfg(feature = "xsd")]
1298 set_idref_tracking(track_idrefs);
1299 let result = child_deriv(
1300 &schema.start,
1301 &ns,
1302 local,
1303 &atts,
1304 &children,
1305 &schema.defines,
1306 );
1307 if nullable(&result, &schema.defines) {
1308 #[cfg(feature = "xsd")]
1311 if track_idrefs {
1312 if let Some(missing) = first_unresolved_idref() {
1313 return Err(validation_err(format!(
1314 "IDREF value \"{missing}\" does not reference a declared ID"
1315 )));
1316 }
1317 }
1318 Ok(())
1319 } else {
1320 #[cfg(feature = "xsd")]
1321 set_idref_tracking(false);
1322 let located = element_content_for(&schema.start, &ns, local, &schema.defines)
1327 .map(|content| consume_attributes(content, &atts, &schema.defines))
1328 .and_then(|after_atts| locate_unexpected_child(after_atts, &children, &schema.defines));
1329 match located {
1330 Some((name, line)) => {
1331 let mut e = validation_err(format!("Did not expect element {name} there"));
1332 e.code = crate::error::ErrorCode::RelaxngErrElemwrong;
1333 e.line = Some(line);
1334 Err(e)
1335 }
1336 None => Err(validation_err(format!(
1337 "document root <{}> did not match the schema",
1338 root.name()
1339 ))),
1340 }
1341 }
1342}
1343
1344fn element_content_for(
1348 p: &Pattern,
1349 ns: &str,
1350 local: &str,
1351 defs: &HashMap<String, Arc<Pattern>>,
1352) -> Option<Arc<Pattern>> {
1353 match p {
1354 Pattern::Element { name, child } if name_class_matches(name, ns, local) => {
1355 Some(child.clone())
1356 }
1357 Pattern::Ref(n) => defs.get(n).and_then(|d| element_content_for(d, ns, local, defs)),
1358 Pattern::Choice(a, b) => element_content_for(a, ns, local, defs)
1359 .or_else(|| element_content_for(b, ns, local, defs)),
1360 _ => None,
1361 }
1362}
1363
1364fn locate_unexpected_child<'a>(
1368 content: Arc<Pattern>,
1369 children: &[&'a Node<'a>],
1370 defs: &HashMap<String, Arc<Pattern>>,
1371) -> Option<(String, u32)> {
1372 let mut current = content;
1373 for c in children {
1374 match c.kind {
1375 NodeKind::Element => {
1376 let (cns, cl) = name_split(c);
1377 let catts = collect_attributes(c);
1378 let cchildren: Vec<&Node<'_>> = c
1379 .children()
1380 .filter(|n| !(n.kind == NodeKind::Text && n.content().trim().is_empty()))
1381 .collect();
1382 let next = child_deriv(¤t, &cns, cl, &catts, &cchildren, defs);
1383 if matches!(&*next, Pattern::NotAllowed) {
1384 return Some((cl.to_string(), c.line_no()));
1385 }
1386 current = next;
1387 }
1388 NodeKind::Text if !c.content().trim().is_empty() => {
1389 current = text_deriv(¤t, c.content(), defs);
1390 }
1391 NodeKind::CData => current = text_deriv(¤t, c.content(), defs),
1392 _ => {}
1393 }
1394 }
1395 None
1396}
1397
1398fn schema_err(msg: impl Into<String>) -> XmlError {
1401 XmlError::new(ErrorDomain::Validation, ErrorLevel::Fatal, msg)
1402}
1403
1404fn validation_err(msg: impl Into<String>) -> XmlError {
1405 XmlError::new(ErrorDomain::RelaxNGValidate, ErrorLevel::Error, msg)
1409}
1410
1411#[cfg(test)]
1414mod tests {
1415 use super::*;
1416
1417 fn schema(s: &str) -> RngSchema {
1418 parse_schema(s).expect("schema parses")
1419 }
1420
1421 fn doc(s: &str) -> Document {
1422 let opts = ParseOptions {
1425 namespace_aware: true,
1426 ..ParseOptions::default()
1427 };
1428 parse_str(s, &opts).expect("doc parses")
1429 }
1430
1431 #[test]
1432 fn simple_element_with_text_validates() {
1433 let s = schema(
1434 r#"<element name="greeting" xmlns="http://relaxng.org/ns/structure/1.0">
1435 <text/>
1436 </element>"#,
1437 );
1438 validate(&s, &doc("<greeting>hello</greeting>")).unwrap();
1439 }
1440
1441 #[test]
1442 fn wrong_element_name_rejected() {
1443 let s = schema(
1444 r#"<element name="greeting" xmlns="http://relaxng.org/ns/structure/1.0"><text/></element>"#,
1445 );
1446 let err = validate(&s, &doc("<farewell>bye</farewell>")).expect_err("wrong name");
1447 assert!(err.message.contains("did not match"), "got: {}", err.message);
1448 }
1449
1450 #[test]
1451 fn attribute_with_value_validates() {
1452 let s = schema(
1453 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1454 <attribute name="kind"><value>small</value></attribute>
1455 </element>"#,
1456 );
1457 validate(&s, &doc(r#"<r kind="small"/>"#)).unwrap();
1458 assert!(validate(&s, &doc(r#"<r kind="huge"/>"#)).is_err());
1459 }
1460
1461 #[test]
1462 fn missing_required_attribute_rejected() {
1463 let s = schema(
1464 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1465 <attribute name="id"><text/></attribute>
1466 </element>"#,
1467 );
1468 assert!(validate(&s, &doc("<r/>")).is_err());
1469 }
1470
1471 #[test]
1472 fn group_of_elements_validates_in_order() {
1473 let s = schema(
1474 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1475 <group>
1476 <element name="a"><text/></element>
1477 <element name="b"><text/></element>
1478 </group>
1479 </element>"#,
1480 );
1481 validate(&s, &doc("<r><a>1</a><b>2</b></r>")).unwrap();
1482 assert!(
1483 validate(&s, &doc("<r><b>2</b><a>1</a></r>")).is_err(),
1484 "wrong order should fail"
1485 );
1486 }
1487
1488 #[test]
1489 fn choice_picks_matching_alternative() {
1490 let s = schema(
1491 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1492 <choice>
1493 <element name="a"><text/></element>
1494 <element name="b"><text/></element>
1495 </choice>
1496 </element>"#,
1497 );
1498 validate(&s, &doc("<r><a>1</a></r>")).unwrap();
1499 validate(&s, &doc("<r><b>2</b></r>")).unwrap();
1500 assert!(validate(&s, &doc("<r><c>3</c></r>")).is_err());
1501 }
1502
1503 #[test]
1504 fn zero_or_more_matches_any_count() {
1505 let s = schema(
1506 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1507 <zeroOrMore><element name="item"><text/></element></zeroOrMore>
1508 </element>"#,
1509 );
1510 validate(&s, &doc("<r/>")).unwrap();
1511 validate(&s, &doc("<r><item>1</item></r>")).unwrap();
1512 validate(
1513 &s,
1514 &doc("<r><item>1</item><item>2</item><item>3</item></r>"),
1515 )
1516 .unwrap();
1517 }
1518
1519 #[test]
1520 fn one_or_more_requires_at_least_one() {
1521 let s = schema(
1522 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1523 <oneOrMore><element name="item"><text/></element></oneOrMore>
1524 </element>"#,
1525 );
1526 validate(&s, &doc("<r><item>1</item></r>")).unwrap();
1527 assert!(validate(&s, &doc("<r/>")).is_err());
1528 }
1529
1530 #[test]
1531 fn optional_is_zero_or_one() {
1532 let s = schema(
1533 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1534 <group>
1535 <element name="req"><text/></element>
1536 <optional><element name="opt"><text/></element></optional>
1537 </group>
1538 </element>"#,
1539 );
1540 validate(&s, &doc("<r><req>x</req></r>")).unwrap();
1541 validate(&s, &doc("<r><req>x</req><opt>y</opt></r>")).unwrap();
1542 }
1543
1544 #[test]
1545 fn grammar_with_define_and_ref() {
1546 let s = schema(
1547 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1548 <start>
1549 <element name="root"><ref name="payload"/></element>
1550 </start>
1551 <define name="payload">
1552 <oneOrMore><element name="item"><text/></element></oneOrMore>
1553 </define>
1554 </grammar>"#,
1555 );
1556 validate(&s, &doc("<root><item>a</item><item>b</item></root>")).unwrap();
1557 }
1558
1559 #[test]
1560 fn empty_pattern_requires_no_content() {
1561 let s = schema(
1562 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0"><empty/></element>"#,
1563 );
1564 validate(&s, &doc("<r/>")).unwrap();
1565 assert!(validate(&s, &doc("<r><x/></r>")).is_err());
1566 }
1567
1568 #[test]
1569 fn interleave_accepts_any_order() {
1570 let s = schema(
1571 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1572 <interleave>
1573 <element name="a"><text/></element>
1574 <element name="b"><text/></element>
1575 </interleave>
1576 </element>"#,
1577 );
1578 validate(&s, &doc("<r><a>1</a><b>2</b></r>")).unwrap();
1579 validate(&s, &doc("<r><b>2</b><a>1</a></r>")).unwrap();
1580 assert!(validate(&s, &doc("<r><a>1</a></r>")).is_err());
1581 assert!(validate(&s, &doc("<r><a>1</a><a>2</a></r>")).is_err());
1582 }
1583
1584 #[test]
1585 fn interleave_with_zero_or_more() {
1586 let s = schema(
1587 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1588 <interleave>
1589 <element name="head"><text/></element>
1590 <zeroOrMore><element name="item"><text/></element></zeroOrMore>
1591 </interleave>
1592 </element>"#,
1593 );
1594 validate(&s, &doc("<r><head>h</head></r>")).unwrap();
1595 validate(
1596 &s,
1597 &doc("<r><item>1</item><head>h</head><item>2</item></r>"),
1598 )
1599 .unwrap();
1600 validate(
1601 &s,
1602 &doc("<r><head>h</head><item>1</item><item>2</item></r>"),
1603 )
1604 .unwrap();
1605 }
1606
1607 #[test]
1608 fn mixed_allows_text_interleaved_with_elements() {
1609 let s = schema(
1610 r#"<element name="p" xmlns="http://relaxng.org/ns/structure/1.0">
1611 <mixed>
1612 <zeroOrMore><element name="b"><text/></element></zeroOrMore>
1613 </mixed>
1614 </element>"#,
1615 );
1616 validate(&s, &doc("<p>hello <b>world</b></p>")).unwrap();
1617 validate(&s, &doc("<p>plain text</p>")).unwrap();
1618 validate(&s, &doc("<p><b>x</b></p>")).unwrap();
1619 }
1620
1621 #[test]
1622 fn data_string_accepts_any_text() {
1623 let s = schema(
1624 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1625 <data type="string"/>
1626 </element>"#,
1627 );
1628 validate(&s, &doc("<r>any text</r>")).unwrap();
1629 }
1630
1631 #[cfg(feature = "xsd")]
1632 #[test]
1633 fn data_positive_integer_validates() {
1634 let s = schema(
1635 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1636 <data type="positiveInteger"/>
1637 </element>"#,
1638 );
1639 validate(&s, &doc("<r>42</r>")).unwrap();
1640 validate(&s, &doc("<r>1</r>")).unwrap();
1641 assert!(validate(&s, &doc("<r>0</r>")).is_err(), "0 is not positive");
1642 assert!(validate(&s, &doc("<r>-5</r>")).is_err(), "negative");
1643 assert!(validate(&s, &doc("<r>abc</r>")).is_err(), "not integer");
1644 }
1645
1646 #[cfg(feature = "xsd")]
1647 #[test]
1648 fn data_boolean_in_attribute() {
1649 let s = schema(
1650 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1651 <attribute name="enabled"><data type="boolean"/></attribute>
1652 </element>"#,
1653 );
1654 validate(&s, &doc(r#"<r enabled="true"/>"#)).unwrap();
1655 validate(&s, &doc(r#"<r enabled="false"/>"#)).unwrap();
1656 assert!(validate(&s, &doc(r#"<r enabled="maybe"/>"#)).is_err());
1657 }
1658
1659 #[test]
1660 fn list_of_tokens() {
1661 let s = schema(
1662 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1663 <list>
1664 <oneOrMore><value>x</value></oneOrMore>
1665 </list>
1666 </element>"#,
1667 );
1668 validate(&s, &doc("<r>x x x</r>")).unwrap();
1669 validate(&s, &doc("<r>x</r>")).unwrap();
1670 assert!(validate(&s, &doc("<r>x y</r>")).is_err(), "y is not x");
1671 }
1672
1673 #[test]
1674 fn any_name_matches_any_element() {
1675 let s = schema(
1676 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="root">
1677 <oneOrMore>
1678 <element><anyName/><text/></element>
1679 </oneOrMore>
1680 </element>"#,
1681 );
1682 validate(&s, &doc("<root><a>1</a><b>2</b><z>3</z></root>")).unwrap();
1683 }
1684
1685 #[test]
1686 fn ns_name_filters_by_namespace() {
1687 let s = schema(
1688 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="root">
1689 <oneOrMore>
1690 <element><nsName ns="urn:custom"/><text/></element>
1691 </oneOrMore>
1692 </element>"#,
1693 );
1694 validate(&s, &doc(r#"<root><x xmlns="urn:custom">1</x></root>"#)).unwrap();
1697 assert!(validate(&s, &doc(r#"<root><x xmlns="urn:other">1</x></root>"#)).is_err());
1698 }
1699
1700 #[test]
1701 fn name_class_choice() {
1702 let s = schema(
1703 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="root">
1704 <oneOrMore>
1705 <element>
1706 <choice><name>a</name><name>b</name></choice>
1707 <text/>
1708 </element>
1709 </oneOrMore>
1710 </element>"#,
1711 );
1712 validate(&s, &doc("<root><a>1</a><b>2</b></root>")).unwrap();
1713 assert!(validate(&s, &doc("<root><c>1</c></root>")).is_err());
1714 }
1715
1716 #[test]
1717 fn nested_choice_resolves_via_derivative() {
1718 let s = schema(
1719 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="r">
1720 <choice>
1721 <group>
1722 <element name="a"><text/></element>
1723 <element name="b"><text/></element>
1724 </group>
1725 <group>
1726 <element name="a"><text/></element>
1727 <element name="c"><text/></element>
1728 </group>
1729 </choice>
1730 </element>"#,
1731 );
1732 validate(&s, &doc("<r><a>1</a><b>2</b></r>")).unwrap();
1733 validate(&s, &doc("<r><a>1</a><c>3</c></r>")).unwrap();
1734 assert!(validate(&s, &doc("<r><a>1</a><d>4</d></r>")).is_err());
1735 }
1736
1737 #[test]
1738 fn deeply_recursive_grammar() {
1739 let s = schema(
1740 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1741 <start>
1742 <element name="tree"><ref name="branches"/></element>
1743 </start>
1744 <define name="branches">
1745 <zeroOrMore>
1746 <element name="branch"><ref name="branches"/></element>
1747 </zeroOrMore>
1748 </define>
1749 </grammar>"#,
1750 );
1751 validate(&s, &doc("<tree/>")).unwrap();
1752 validate(&s, &doc("<tree><branch/></tree>")).unwrap();
1753 validate(
1754 &s,
1755 &doc("<tree><branch><branch><branch/></branch></branch></tree>"),
1756 )
1757 .unwrap();
1758 }
1759
1760 #[test]
1761 fn unsupported_pattern_errors_at_schema_parse() {
1762 let result = parse_schema(
1763 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="r">
1764 <unknownPattern/>
1765 </element>"#,
1766 );
1767 assert!(result.is_err());
1768 }
1769
1770 #[test]
1771 fn empty_attribute_value_via_data() {
1772 let s = schema(
1773 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
1774 <attribute name="x"><data type="string"/></attribute>
1775 </element>"#,
1776 );
1777 validate(&s, &doc(r#"<r x="value"/>"#)).unwrap();
1778 }
1779
1780 #[test]
1783 fn combine_choice_merges_two_defines() {
1784 let s = schema(
1785 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1786 <start>
1787 <element name="root"><ref name="content"/></element>
1788 </start>
1789 <define name="content" combine="choice">
1790 <element name="a"><text/></element>
1791 </define>
1792 <define name="content" combine="choice">
1793 <element name="b"><text/></element>
1794 </define>
1795 </grammar>"#,
1796 );
1797 validate(&s, &doc("<root><a>1</a></root>")).unwrap();
1798 validate(&s, &doc("<root><b>2</b></root>")).unwrap();
1799 assert!(
1800 validate(&s, &doc("<root><c>3</c></root>")).is_err(),
1801 "<c> not in either define"
1802 );
1803 }
1804
1805 #[test]
1806 fn combine_choice_merges_three_defines() {
1807 let s = schema(
1808 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1809 <start>
1810 <element name="root"><ref name="content"/></element>
1811 </start>
1812 <define name="content" combine="choice">
1813 <element name="a"><text/></element>
1814 </define>
1815 <define name="content" combine="choice">
1816 <element name="b"><text/></element>
1817 </define>
1818 <define name="content" combine="choice">
1819 <element name="c"><text/></element>
1820 </define>
1821 </grammar>"#,
1822 );
1823 validate(&s, &doc("<root><a>1</a></root>")).unwrap();
1824 validate(&s, &doc("<root><b>2</b></root>")).unwrap();
1825 validate(&s, &doc("<root><c>3</c></root>")).unwrap();
1826 assert!(validate(&s, &doc("<root><d>4</d></root>")).is_err());
1827 }
1828
1829 #[test]
1830 fn combine_interleave_merges_two_defines() {
1831 let s = schema(
1832 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1833 <start>
1834 <element name="root"><ref name="content"/></element>
1835 </start>
1836 <define name="content" combine="interleave">
1837 <element name="head"><text/></element>
1838 </define>
1839 <define name="content" combine="interleave">
1840 <element name="body"><text/></element>
1841 </define>
1842 </grammar>"#,
1843 );
1844 validate(&s, &doc("<root><head>h</head><body>b</body></root>")).unwrap();
1845 validate(&s, &doc("<root><body>b</body><head>h</head></root>")).unwrap();
1846 assert!(validate(&s, &doc("<root><head>h</head></root>")).is_err());
1847 assert!(validate(&s, &doc("<root><body>b</body></root>")).is_err());
1848 }
1849
1850 #[test]
1851 fn combine_one_bare_define_plus_one_with_combine() {
1852 let s = schema(
1853 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1854 <start>
1855 <element name="root"><ref name="content"/></element>
1856 </start>
1857 <define name="content">
1858 <element name="a"><text/></element>
1859 </define>
1860 <define name="content" combine="choice">
1861 <element name="b"><text/></element>
1862 </define>
1863 </grammar>"#,
1864 );
1865 validate(&s, &doc("<root><a>1</a></root>")).unwrap();
1866 validate(&s, &doc("<root><b>2</b></root>")).unwrap();
1867 }
1868
1869 #[test]
1870 fn combine_on_start_element() {
1871 let s = schema(
1872 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1873 <start combine="choice">
1874 <element name="a"><text/></element>
1875 </start>
1876 <start combine="choice">
1877 <element name="b"><text/></element>
1878 </start>
1879 </grammar>"#,
1880 );
1881 validate(&s, &doc("<a>1</a>")).unwrap();
1882 validate(&s, &doc("<b>2</b>")).unwrap();
1883 }
1884
1885 #[test]
1886 fn combine_duplicate_define_without_combine_errors() {
1887 let result = parse_schema(
1888 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1889 <start><element name="r"><ref name="x"/></element></start>
1890 <define name="x"><element name="a"><text/></element></define>
1891 <define name="x"><element name="b"><text/></element></define>
1892 </grammar>"#,
1893 );
1894 assert!(
1895 result.is_err(),
1896 "duplicate define with no combine should be rejected"
1897 );
1898 let err = result.unwrap_err();
1899 assert!(
1900 err.message.contains("combine"),
1901 "error should mention combine; got: {}",
1902 err.message
1903 );
1904 }
1905
1906 #[test]
1907 fn combine_inconsistent_values_error() {
1908 let result = parse_schema(
1909 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1910 <start><element name="r"><ref name="x"/></element></start>
1911 <define name="x" combine="choice">
1912 <element name="a"><text/></element>
1913 </define>
1914 <define name="x" combine="interleave">
1915 <element name="b"><text/></element>
1916 </define>
1917 </grammar>"#,
1918 );
1919 assert!(
1920 result.is_err(),
1921 "inconsistent combine values must be rejected"
1922 );
1923 let err = result.unwrap_err();
1924 assert!(
1925 err.message.contains("combine") || err.message.contains("inconsistent"),
1926 "error should mention combine; got: {}",
1927 err.message
1928 );
1929 }
1930
1931 #[test]
1932 fn combine_invalid_value_errors() {
1933 let result = parse_schema(
1934 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1935 <start><element name="r"><ref name="x"/></element></start>
1936 <define name="x" combine="merge">
1937 <element name="a"><text/></element>
1938 </define>
1939 <define name="x" combine="merge">
1940 <element name="b"><text/></element>
1941 </define>
1942 </grammar>"#,
1943 );
1944 assert!(result.is_err(), "invalid combine value must be rejected");
1945 }
1946
1947 #[test]
1948 fn combine_single_define_with_combine_attribute() {
1949 let s = schema(
1950 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
1951 <start>
1952 <element name="root"><ref name="content"/></element>
1953 </start>
1954 <define name="content" combine="choice">
1955 <element name="a"><text/></element>
1956 </define>
1957 </grammar>"#,
1958 );
1959 validate(&s, &doc("<root><a>1</a></root>")).unwrap();
1960 }
1961
1962 #[test]
1965 fn name_class_describe_all_variants() {
1966 let n1 = NameClass::Name {
1967 namespace: String::new(),
1968 local: "elt".into(),
1969 };
1970 assert_eq!(n1.describe(), "elt");
1971
1972 let n2 = NameClass::Name {
1973 namespace: "urn:x".into(),
1974 local: "elt".into(),
1975 };
1976 assert_eq!(n2.describe(), "{urn:x}elt");
1977
1978 assert_eq!(NameClass::AnyName(None).describe(), "*");
1979
1980 let n3 = NameClass::NsName {
1981 namespace: "urn:y".into(),
1982 except: None,
1983 };
1984 assert_eq!(n3.describe(), "{urn:y}*");
1985
1986 let n4 = NameClass::Choice(
1987 Box::new(NameClass::Name { namespace: String::new(), local: "a".into() }),
1988 Box::new(NameClass::Name { namespace: String::new(), local: "b".into() }),
1989 );
1990 assert_eq!(n4.describe(), "a | b");
1991
1992 assert_eq!(NameClass::Nothing.describe(), "<nothing>");
1993 }
1994
1995 #[test]
1998 fn attribute_choice_of_two_attrs() {
1999 let s = schema(
2000 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
2001 <choice>
2002 <attribute name="x"><text/></attribute>
2003 <attribute name="y"><text/></attribute>
2004 </choice>
2005 </element>"#,
2006 );
2007 validate(&s, &doc(r#"<r x="1"/>"#)).unwrap();
2008 validate(&s, &doc(r#"<r y="2"/>"#)).unwrap();
2009 assert!(validate(&s, &doc(r#"<r z="3"/>"#)).is_err());
2010 }
2011
2012 #[test]
2013 fn attribute_group_requires_both() {
2014 let s = schema(
2015 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
2016 <group>
2017 <attribute name="x"><text/></attribute>
2018 <attribute name="y"><text/></attribute>
2019 </group>
2020 </element>"#,
2021 );
2022 validate(&s, &doc(r#"<r x="1" y="2"/>"#)).unwrap();
2023 validate(&s, &doc(r#"<r y="2" x="1"/>"#)).unwrap();
2024 assert!(validate(&s, &doc(r#"<r x="1"/>"#)).is_err());
2025 }
2026
2027 #[test]
2028 fn attribute_interleave_accepts_any_order() {
2029 let s = schema(
2030 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
2031 <interleave>
2032 <attribute name="x"><text/></attribute>
2033 <attribute name="y"><text/></attribute>
2034 </interleave>
2035 </element>"#,
2036 );
2037 validate(&s, &doc(r#"<r x="1" y="2"/>"#)).unwrap();
2038 validate(&s, &doc(r#"<r y="2" x="1"/>"#)).unwrap();
2039 }
2040
2041 #[test]
2042 fn attribute_one_or_more_with_named_attrs() {
2043 let s = schema(
2046 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
2047 <oneOrMore>
2048 <attribute><anyName/><text/></attribute>
2049 </oneOrMore>
2050 </element>"#,
2051 );
2052 validate(&s, &doc(r#"<r a="1"/>"#)).unwrap();
2053 validate(&s, &doc(r#"<r a="1" b="2" c="3"/>"#)).unwrap();
2054 assert!(validate(&s, &doc("<r/>")).is_err(), "oneOrMore requires ≥1");
2055 }
2056
2057 #[test]
2060 fn attribute_value_constraint() {
2061 let s = schema(
2062 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
2063 <attribute name="kind">
2064 <choice>
2065 <value>a</value>
2066 <value>b</value>
2067 </choice>
2068 </attribute>
2069 </element>"#,
2070 );
2071 validate(&s, &doc(r#"<r kind="a"/>"#)).unwrap();
2072 validate(&s, &doc(r#"<r kind="b"/>"#)).unwrap();
2073 assert!(validate(&s, &doc(r#"<r kind="c"/>"#)).is_err());
2074 }
2075
2076 #[test]
2079 fn ns_name_with_except() {
2080 let s = schema(
2083 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="root">
2084 <oneOrMore>
2085 <element>
2086 <nsName ns="urn:n">
2087 <except><name ns="urn:n">forbidden</name></except>
2088 </nsName>
2089 <text/>
2090 </element>
2091 </oneOrMore>
2092 </element>"#,
2093 );
2094 validate(&s, &doc(r#"<root><a xmlns="urn:n">1</a></root>"#)).unwrap();
2095 assert!(validate(&s,
2096 &doc(r#"<root><forbidden xmlns="urn:n">1</forbidden></root>"#))
2097 .is_err());
2098 }
2099
2100 #[test]
2101 fn any_name_with_except() {
2102 let s = schema(
2105 r#"<element xmlns="http://relaxng.org/ns/structure/1.0" name="root">
2106 <oneOrMore>
2107 <element>
2108 <anyName>
2109 <except><name>banned</name></except>
2110 </anyName>
2111 <text/>
2112 </element>
2113 </oneOrMore>
2114 </element>"#,
2115 );
2116 validate(&s, &doc("<root><a>1</a><b>2</b></root>")).unwrap();
2117 assert!(validate(&s, &doc("<root><banned>1</banned></root>")).is_err());
2118 }
2119
2120 #[test]
2123 fn nested_group_with_optional_middle() {
2124 let s = schema(
2125 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
2126 <group>
2127 <element name="head"><text/></element>
2128 <optional><element name="body"><text/></element></optional>
2129 <element name="foot"><text/></element>
2130 </group>
2131 </element>"#,
2132 );
2133 validate(&s, &doc("<r><head>h</head><foot>f</foot></r>")).unwrap();
2134 validate(&s,
2135 &doc("<r><head>h</head><body>b</body><foot>f</foot></r>")).unwrap();
2136 assert!(validate(&s, &doc("<r><head>h</head></r>")).is_err());
2137 }
2138
2139 #[test]
2142 fn ref_to_undefined_name_errors_at_validate() {
2143 let result = parse_schema(
2146 r#"<grammar xmlns="http://relaxng.org/ns/structure/1.0">
2147 <start><element name="r"><ref name="undefined"/></element></start>
2148 </grammar>"#,
2149 );
2150 if let Ok(s) = result {
2154 assert!(validate(&s, &doc("<r>x</r>")).is_err());
2155 }
2156 }
2157
2158 #[test]
2161 fn list_of_data_typed_tokens() {
2162 let s = schema(
2163 r#"<element name="r" xmlns="http://relaxng.org/ns/structure/1.0">
2164 <list>
2165 <oneOrMore><data type="string"/></oneOrMore>
2166 </list>
2167 </element>"#,
2168 );
2169 validate(&s, &doc("<r>a b c</r>")).unwrap();
2170 validate(&s, &doc("<r>single</r>")).unwrap();
2171 }
2172}