use std::sync::Arc;
use rustc_hash::FxHashMap as HashMap;
use crate::reader::{Attr, EventInto, XmlReader};
use super::error::{ValidationError, ValidationIssue, ValidationKind, ValidationOptions};
mod walker;
pub(crate) use walker::DocumentEventSource;
pub(crate) trait XsdEventSource<'x> {
fn next_into(&mut self, attr_buf: &mut Vec<Attr<'x>>) -> crate::error::Result<EventInto<'x>>;
fn last_start_offset(&self) -> Option<usize>;
fn src_offset(&self) -> usize;
fn line_col_at(&self, offset: usize) -> (u32, u32);
fn fill_default_attr(&self, _name: &str, _value: &str) {}
fn current_node_key(&self) -> Option<usize> { None }
}
impl<'x> XsdEventSource<'x> for XmlReader<'x> {
#[inline] fn next_into(&mut self, buf: &mut Vec<Attr<'x>>) -> crate::error::Result<EventInto<'x>> {
XmlReader::next_into(self, buf)
}
#[inline] fn last_start_offset(&self) -> Option<usize> {
XmlReader::last_start_offset(self)
}
#[inline] fn src_offset(&self) -> usize {
XmlReader::src_offset(self)
}
#[inline] fn line_col_at(&self, offset: usize) -> (u32, u32) {
XmlReader::line_col_at(self, offset)
}
}
use super::identity::{
ConstraintKind, FieldPath, NameTest, PathExpr, PathStep, SelectorPath,
};
use super::schema::{
AttributeUseKind, BlockSet, ContentModel, ElementDecl, GroupKind,
Particle, ProcessContents, QName, Schema, Term, TypeRef, Wildcard,
};
use super::types::{BuiltinType, ComplexType, DerivationMethod, SimpleType};
impl Schema {
pub fn validate_str(&self, xml: &str) -> Result<(), ValidationError> {
self.validate_str_opts(xml, ValidationOptions::default())
}
pub fn validate_bytes(&self, xml: &[u8]) -> Result<(), ValidationError> {
let s = std::str::from_utf8(xml).map_err(|e| ValidationError::single(
ValidationIssue {
message: format!("invalid UTF-8: {e}"),
line: None, column: None, path: String::new(),
kind: ValidationKind::Other,
expected: Vec::new(), value: None, type_name: None,
}
))?;
self.validate_str(s)
}
pub fn validate_str_opts(&self, xml: &str, opts: ValidationOptions)
-> Result<(), ValidationError>
{
let mut v = Validator::new(self, xml, opts);
v.run();
if v.issues.is_empty() {
Ok(())
} else {
Err(ValidationError { issues: v.issues })
}
}
pub fn validate_doc(&self, doc: &sup_xml_tree::dom::Document)
-> std::result::Result<(), ValidationError>
{
self.validate_doc_opts(doc, ValidationOptions::default())
}
pub fn validate_doc_opts(
&self, doc: &sup_xml_tree::dom::Document, opts: ValidationOptions,
) -> std::result::Result<(), ValidationError> {
let source = DocumentEventSource::new(doc);
let mut v = Validator::new_with_source(self, source, opts);
v.run();
if v.issues.is_empty() {
Ok(())
} else {
Err(ValidationError { issues: v.issues })
}
}
pub fn validate_doc_typed(&self, doc: &sup_xml_tree::dom::Document)
-> (std::result::Result<(), ValidationError>, PsviTypes)
{
let source = DocumentEventSource::new(doc);
let mut v = Validator::new_with_source(self, source, ValidationOptions::default());
v.type_sink = Some(HashMap::default());
v.run();
let psvi = PsviTypes { by_node: v.type_sink.take().unwrap_or_default() };
let res = if v.issues.is_empty() {
Ok(())
} else {
Err(ValidationError { issues: v.issues })
};
(res, psvi)
}
}
#[derive(Default)]
pub struct PsviTypes {
by_node: HashMap<usize, TypeRef>,
}
impl PsviTypes {
pub fn governing_type(&self, node: &sup_xml_tree::dom::Node) -> Option<&TypeRef> {
self.by_node.get(&(node as *const sup_xml_tree::dom::Node as usize))
}
pub fn is_empty(&self) -> bool { self.by_node.is_empty() }
}
const XSI_NS: &str = "http://www.w3.org/2001/XMLSchema-instance";
struct Validator<'s, 'x, E: XsdEventSource<'x>> {
schema: &'s Schema,
reader: E,
opts: ValidationOptions,
_src: std::marker::PhantomData<&'x str>,
issues: Vec<ValidationIssue>,
stack: Vec<ElementCtx<'s>>,
attr_buf: Vec<Attr<'x>>,
ns_stack: Vec<HashMap<String, String>>,
key_scopes: Vec<KeyScope>,
type_sink: Option<HashMap<usize, TypeRef>>,
}
struct ElementCtx<'s> {
decl: Arc<ElementDecl>,
type_def: TypeRef,
cached_attrs: Vec<(QName, String)>,
declared_scope: usize,
matched_constraints: Vec<(usize, usize)>,
collect_depth: usize,
child_snapshots: Vec<ChildSnapshot>,
_phantom: std::marker::PhantomData<&'s ()>,
cursor: ContentCursor,
text_buf: String,
is_nil: bool,
name: QName,
pushed_ns: bool,
seen_attrs: Vec<QName>,
sibling_index: u32,
child_counters: Vec<(Arc<str>, u32)>,
start_offset: u32,
}
enum ContentCursor {
None,
Dfa {
dfa: Arc<super::dfa::Dfa>,
state: super::dfa::StateId,
},
Group {
kind: GroupKind,
particles: Arc<[Particle]>,
idx: usize,
cur_count: u32,
all_seen: HashMap<usize, u32>,
outer_min: u32,
},
}
struct KeyScope {
declaring_depth: usize,
declaring_offset: u32,
decl: Arc<ElementDecl>,
collected: Vec<Vec<KeyTuple>>,
}
type KeyTuple = Vec<Option<String>>;
#[derive(Debug)]
#[derive(Clone)]
pub(super) struct ChildSnapshot {
pub(super) name: QName,
pub(super) attrs: Vec<(QName, String)>,
pub(super) text: String,
pub(super) children: Vec<ChildSnapshot>,
}
fn max_field_child_descent(fp: &FieldPath) -> usize {
fp.paths.iter()
.map(|p| p.steps.iter().filter(|s| matches!(s, PathStep::Child(_))).count())
.max()
.unwrap_or(0)
}
fn bump_child_counter(counters: &mut Vec<(Arc<str>, u32)>, local: &Arc<str>) -> u32 {
for (name, n) in counters.iter_mut() {
if name.as_ref() == local.as_ref() {
*n += 1;
return *n;
}
}
counters.push((local.clone(), 1));
1
}
fn snapshot_attrs<F>(attrs: &[Attr], mut to_qname: F) -> Vec<(QName, String)>
where
F: FnMut(&str) -> QName,
{
attrs.iter()
.filter(|a| {
let n = a.name;
!n.starts_with("xmlns") && !n.starts_with("xsi:")
})
.map(|a| (to_qname(a.name), a.value.to_string()))
.collect()
}
impl<'s, 'x> Validator<'s, 'x, XmlReader<'x>> {
fn new(schema: &'s Schema, xml: &'x str, opts: ValidationOptions) -> Self {
Self::new_with_source(schema, XmlReader::from_str(xml), opts)
}
}
impl<'s, 'x, E: XsdEventSource<'x>> Validator<'s, 'x, E> {
fn new_with_source(schema: &'s Schema, source: E, opts: ValidationOptions) -> Self {
Self {
schema,
reader: source,
opts,
issues: Vec::new(),
stack: Vec::new(),
attr_buf: Vec::new(),
ns_stack: vec![HashMap::default()],
key_scopes: Vec::new(),
type_sink: None,
_src: std::marker::PhantomData,
}
}
fn record_governing_type(&mut self, type_def: &TypeRef) {
let Some(key) = self.reader.current_node_key() else { return };
if let Some(sink) = self.type_sink.as_mut() {
sink.insert(key, type_def.clone());
}
}
fn report(&mut self, kind: ValidationKind, message: impl Into<String>) -> bool {
let offset = self.stack.last()
.map(|c| c.start_offset as usize)
.unwrap_or_else(|| self.reader.src_offset());
self.report_at(kind, message, offset)
}
fn report_at(&mut self, kind: ValidationKind, message: impl Into<String>, offset: usize) -> bool {
let (line, col) = self.reader.line_col_at(offset);
let path = self.current_path();
let issue = ValidationIssue {
message: message.into(),
line: Some(line), column: Some(col),
path,
kind,
expected: Vec::new(), value: None, type_name: None,
};
self.issues.push(issue);
if self.opts.fail_fast || self.issues.len() >= self.opts.max_issues {
return true; }
false
}
fn current_path(&self) -> String {
if self.stack.is_empty() { return String::new(); }
let mut s = String::new();
for ctx in &self.stack {
s.push('/');
s.push_str(&ctx.name.local);
if ctx.sibling_index >= 2 {
use std::fmt::Write;
let _ = write!(s, "[{}]", ctx.sibling_index);
}
}
s
}
fn push_ns_scope(&mut self, attrs: &[Attr<'x>]) -> bool {
let has_ns = attrs.iter().any(|a| {
let n = a.name;
n == "xmlns" || n.starts_with("xmlns:")
});
if !has_ns { return false; }
let mut new = self.ns_stack.last().cloned().unwrap_or_default();
for a in attrs {
let n = a.name;
if n == "xmlns" {
new.insert(String::new(), a.value.to_string());
} else if let Some(prefix) = n.strip_prefix("xmlns:") {
new.insert(prefix.to_string(), a.value.to_string());
}
}
self.ns_stack.push(new);
true
}
fn pop_ns_scope_if(&mut self, pushed: bool) {
if pushed { self.ns_stack.pop(); }
}
fn resolve_prefix(&self, prefix: &str) -> Option<&str> {
for scope in self.ns_stack.iter().rev() {
if let Some(uri) = scope.get(prefix) {
return Some(uri.as_str());
}
}
None
}
fn parse_element_qname(&self, raw: &str) -> QName {
match raw.split_once(':') {
Some((p, local)) => QName {
namespace: self.resolve_prefix(p).map(Arc::from),
local: Arc::from(local),
},
None => QName {
namespace: self.resolve_prefix("").map(Arc::from),
local: Arc::from(raw),
},
}
}
fn parse_attribute_qname(&self, raw: &str) -> QName {
match raw.split_once(':') {
Some((p, local)) => QName {
namespace: self.resolve_prefix(p).map(Arc::from),
local: Arc::from(local),
},
None => QName {
namespace: None,
local: Arc::from(raw),
},
}
}
fn parse_qname_value(&self, raw: &str) -> QName {
self.parse_element_qname(raw)
}
fn run(&mut self) {
loop {
let ev = match self.reader.next_into(&mut self.attr_buf) {
Ok(ev) => ev,
Err(e) => {
self.issues.push(ValidationIssue {
message: format!("XML parse error: {e}"),
line: e.line, column: e.column, path: self.current_path(),
kind: ValidationKind::Other,
expected: Vec::new(), value: None, type_name: None,
});
return;
}
};
match ev {
EventInto::Comment(_)
| EventInto::Pi { .. }
| EventInto::EntityRef { .. } => continue,
EventInto::StartElement { name } => {
let event_offset = self.reader.last_start_offset()
.unwrap_or_else(|| self.reader.src_offset());
let attrs = std::mem::take(&mut self.attr_buf);
let bail = self.handle_start(name, &attrs, event_offset);
self.attr_buf = attrs;
if bail { return; }
}
EventInto::EndElement { .. } => {
if self.handle_end() { return; }
}
EventInto::Text(t) | EventInto::CData(t) => {
if let Some(ctx) = self.stack.last_mut() {
ctx.text_buf.push_str(&t);
}
}
EventInto::Eof => {
if !self.stack.is_empty() {
self.report(ValidationKind::Other,
"unexpected EOF (unclosed elements)");
}
return;
}
}
}
}
fn handle_start(
&mut self,
name: std::borrow::Cow<'x, str>,
attrs: &[Attr<'x>],
event_offset: usize,
) -> bool {
let pushed_ns = self.push_ns_scope(attrs);
let qn = self.parse_element_qname(&name);
let decl = if let Some(parent) = self.stack.last_mut() {
let m = match_in_cursor(&qn, &mut parent.cursor, self.schema);
match m {
MatchOutcome::Element(decl) => decl,
MatchOutcome::Wildcard(wc) => {
return self.handle_wildcard_match(&qn, attrs, wc, pushed_ns, event_offset);
}
MatchOutcome::None => {
let expected = expected_element_names(&parent.cursor);
let stop = self.report_at(ValidationKind::UnexpectedElement,
format!("unexpected element <{qn}>"), event_offset);
if let Some(last) = self.issues.last_mut() {
last.expected = expected;
}
if stop { return true; }
return self.skip_body_into_issues(pushed_ns);
}
}
} else {
match self.schema.element(&qn) {
Some(d) => d.clone(),
None => {
if let Some(xsi_type) = find_xsi_type(&attrs) {
let type_qn = self.parse_qname_value(xsi_type);
if let Some(tr) = self.lookup_type_by_qname(&type_qn) {
Arc::new(ElementDecl {
name: qn.clone(),
type_def: tr,
nillable: true,
default: None,
fixed: None,
abstract_: false,
substitution_group: None,
identity: Vec::new(),
block: super::schema::BlockSet::empty(),
final_: super::schema::BlockSet::empty(),
})
} else {
self.report_at(ValidationKind::UnexpectedElement,
format!("root element <{qn}>: xsi:type {type_qn} not declared in schema"),
event_offset);
return self.skip_body_into_issues(pushed_ns);
}
} else {
self.report_at(ValidationKind::UnexpectedElement,
format!("root element <{qn}> not declared in schema"),
event_offset);
return self.skip_body_into_issues(pushed_ns);
}
}
}
};
let mut type_def = self.resolve_type(&decl.type_def);
let mut xsi_type_override_applied = false;
if let Some(xsi_type) = find_xsi_type(&attrs) {
let override_qn = self.parse_qname_value(xsi_type);
if let Some(t) = self.lookup_type_by_qname(&override_qn) {
let declared = type_def.clone();
match self.derivation_methods_from(&t, &declared) {
None => {
self.report_at(ValidationKind::TypeMismatch,
format!("xsi:type {override_qn} does not derive from declared type"),
event_offset);
}
Some(methods) => {
let type_block = if let TypeRef::Complex(declared_ct) = &declared {
declared_ct.block
} else { BlockSet::empty() };
let element_blocked = (decl.block | type_block) & methods;
if !element_blocked.is_empty() {
self.report_at(ValidationKind::TypeMismatch,
format!(
"xsi:type {override_qn} blocked by block={}",
format_block_set(element_blocked),
),
event_offset);
} else {
let final_blocked = if let TypeRef::Complex(declared_ct) = &declared {
declared_ct.final_ & methods
} else {
BlockSet::empty()
};
if !final_blocked.is_empty() {
self.report_at(ValidationKind::TypeMismatch,
format!(
"xsi:type {override_qn} blocked by base type final={}",
format_block_set(final_blocked),
),
event_offset);
} else {
let target_is_abstract = matches!(&t,
TypeRef::Complex(ct) if ct.abstract_);
if !target_is_abstract {
type_def = t;
xsi_type_override_applied = true;
} else {
self.report_at(ValidationKind::TypeMismatch,
format!("xsi:type {override_qn} is abstract"),
event_offset);
}
}
}
}
}
} else {
self.report_at(ValidationKind::TypeMismatch,
format!("xsi:type {override_qn:?} not declared in schema"),
event_offset);
}
}
if decl.abstract_ && !xsi_type_override_applied {
self.report_at(ValidationKind::UnexpectedElement,
format!("element <{qn}> is abstract and cannot appear in instances"),
event_offset);
return self.skip_body_into_issues(pushed_ns);
}
let is_nil = match find_xsi_nil(&attrs) {
None => false,
Some(raw) => match parse_xsi_nil(raw) {
XsiNilParse::True => true,
XsiNilParse::False => false,
XsiNilParse::Invalid(bad) => {
self.report_at(ValidationKind::TypeMismatch,
format!("xsi:nil value {bad:?} is not a valid xs:boolean \
(expected \"true\", \"false\", \"1\", or \"0\")"),
event_offset);
false
}
},
};
if is_nil && !decl.nillable {
self.report_at(ValidationKind::NillableViolation,
format!("xsi:nil on non-nillable element <{qn}>"),
event_offset);
}
let seen_attrs = self.validate_attrs_against_type(&type_def, &attrs, event_offset);
let cursor = if is_nil { ContentCursor::None } else { build_cursor(&type_def) };
let declared_scope = if !decl.identity.is_empty() {
let collected = vec![Vec::new(); decl.identity.len()];
self.key_scopes.push(KeyScope {
declaring_depth: self.stack.len(),
declaring_offset: event_offset as u32,
decl: decl.clone(),
collected,
});
self.key_scopes.len() - 1
} else {
usize::MAX
};
let parent_collect_depth = self.stack.last()
.map(|p| p.collect_depth).unwrap_or(0);
let parent_collects = parent_collect_depth > 0;
let has_assertions = matches!(
&type_def, TypeRef::Complex(ct) if !ct.assertions.is_empty()
);
let need_capture = parent_collects || has_assertions;
let (matched, mut cached_attrs) = self.check_active_scopes(&qn, &attrs);
if cached_attrs.is_empty() && need_capture {
cached_attrs = snapshot_attrs(&attrs, |s| self.parse_attribute_qname(s));
}
let own_collect_depth = if has_assertions {
usize::MAX
} else {
matched.iter().map(|(si, ci)| {
self.key_scopes[*si].decl.identity[*ci].fields.iter()
.map(max_field_child_descent)
.max().unwrap_or(0)
}).max().unwrap_or(0)
};
let collect_depth = own_collect_depth
.max(parent_collect_depth.saturating_sub(1));
let sibling_index = match self.stack.last_mut() {
Some(parent) => bump_child_counter(&mut parent.child_counters, &qn.local),
None => 1,
};
self.record_governing_type(&type_def);
self.stack.push(ElementCtx {
decl,
type_def,
cursor,
text_buf: String::new(),
is_nil,
name: qn,
pushed_ns,
seen_attrs,
cached_attrs,
declared_scope,
matched_constraints: matched,
collect_depth,
child_snapshots: Vec::new(),
start_offset: event_offset as u32,
_phantom: std::marker::PhantomData,
sibling_index,
child_counters: Vec::new(),
});
false
}
fn check_active_scopes(
&self,
qn: &QName,
attrs: &[Attr<'x>],
) -> (Vec<(usize, usize)>, Vec<(QName, String)>) {
if self.key_scopes.is_empty() {
return (Vec::new(), Vec::new());
}
let depth = self.stack.len();
let mut matched = Vec::new();
for (si, scope) in self.key_scopes.iter().enumerate() {
let rel = depth.saturating_sub(scope.declaring_depth);
for (ci, c) in scope.decl.identity.iter().enumerate() {
if selector_matches(&c.selector, &self.stack, qn, rel) {
matched.push((si, ci));
}
}
}
let cached_attrs = if matched.is_empty() {
Vec::new()
} else {
snapshot_attrs(attrs, |s| self.parse_attribute_qname(s))
};
(matched, cached_attrs)
}
fn run_simple_type_assertions(
&mut self,
st: &super::types::SimpleType,
value: &str,
ctx_off: usize,
) {
if st.assertions.is_empty() { return; }
for a in &st.assertions {
use super::assertion::{eval_simple_assertion, AssertOutcome};
match eval_simple_assertion(a, value) {
AssertOutcome::Pass => {}
AssertOutcome::Fail => {
self.report_at(ValidationKind::AssertionViolation,
format!("assertion failed: {}", a.test), ctx_off);
}
AssertOutcome::Unevaluable(_) => {}
}
}
}
fn handle_end(&mut self) -> bool {
let mut ctx = self.stack.pop().expect("EndElement with empty stack");
self.pop_ns_scope_if(ctx.pushed_ns);
let element_simple_type =
self.field_dot_simple_type(&ctx.type_def);
for (si, ci) in &ctx.matched_constraints {
let constraint = self.key_scopes[*si].decl.identity[*ci].clone();
let fields = constraint.fields.clone();
let canonicalisable =
fields.iter().all(field_path_is_dot)
&& self.constraint_peers_all_dot(*si, *ci);
let mut tuple: KeyTuple = Vec::with_capacity(fields.len());
let mut ambiguous = false;
for fp in fields {
match eval_field(
&fp, &ctx.cached_attrs, &ctx.text_buf, &ctx.child_snapshots,
) {
FieldEval::Single(v) => {
let canon = if canonicalisable {
canonical_field_key(&v, element_simple_type.as_ref())
} else {
v
};
tuple.push(Some(canon));
}
FieldEval::Missing => tuple.push(None),
FieldEval::Ambiguous => { ambiguous = true; tuple.push(None); }
}
}
if ambiguous {
self.report(ValidationKind::Other, format!(
"<xs:{} {:?}>: a field xpath selected more than one node",
match constraint.kind {
ConstraintKind::Key => "key",
ConstraintKind::Unique => "unique",
ConstraintKind::KeyRef => "keyref",
},
constraint.name.local,
));
}
self.key_scopes[*si].collected[*ci].push(tuple);
}
if ctx.declared_scope != usize::MAX {
let scope = self.key_scopes.pop().expect("scope stack out of sync");
self.finalize_key_scope(&scope);
}
if let Some(parent) = self.stack.last_mut() {
if parent.collect_depth > 0 {
parent.child_snapshots.push(ChildSnapshot {
name: ctx.name.clone(),
attrs: ctx.cached_attrs.clone(),
text: ctx.text_buf.clone(),
children: std::mem::take(&mut ctx.child_snapshots),
});
}
}
let ctx_off = ctx.start_offset as usize;
if let ContentCursor::Dfa { dfa, state } = &ctx.cursor {
if !dfa.is_accept(*state) {
let expected: Vec<String> = dfa.states[*state as usize]
.on_element.iter()
.map(|t| t.name.local.to_string())
.collect();
let msg = if expected.is_empty() {
"element content does not match the schema (no valid continuation)".to_string()
} else if expected.len() == 1 {
format!("missing required element <{}>", expected[0])
} else {
format!("missing required element (one of: {})", expected.join(", "))
};
self.report_at(ValidationKind::MissingRequiredElement, msg, ctx_off);
}
}
if let ContentCursor::Group { kind, particles, idx, cur_count, all_seen, outer_min, .. } = &ctx.cursor {
match kind {
GroupKind::Sequence => {
if *idx < particles.len() {
let p = &particles[*idx];
if *cur_count < p.min_occurs {
let _ = self.handle_min_occurs_violation(p, ctx_off);
}
for p in &particles[*idx + 1..] {
if p.min_occurs > 0 {
let _ = self.handle_min_occurs_violation(p, ctx_off);
}
}
}
}
GroupKind::Choice => {
if *cur_count == 0 && !particles.is_empty() {
self.report_at(ValidationKind::MissingRequiredElement,
"no branch of <xs:choice> matched", ctx_off);
}
}
GroupKind::All => {
let saw_anything = all_seen.values().any(|&n| n > 0);
if *outer_min == 0 && !saw_anything {
} else {
for (i, p) in particles.iter().enumerate() {
let seen = all_seen.get(&i).copied().unwrap_or(0);
if seen < p.min_occurs {
let _ = self.handle_min_occurs_violation(p, ctx_off);
}
}
}
}
}
}
match (&ctx.type_def, ctx.is_nil) {
(_, true) => {
if !ctx.text_buf.trim().is_empty() {
self.report_at(ValidationKind::NillableViolation,
"xsi:nil element must have empty content", ctx_off);
}
}
(TypeRef::Simple(st), _) => {
let real = self.resolve_simple_type(st);
let to_check = effective_text(&ctx.text_buf, ctx.decl.default.as_deref(),
ctx.decl.fixed.as_deref());
if let Err(e) = real.validate_only(to_check) {
let elem_local = ctx.decl.name.local.to_string();
self.report_at(e.kind, format!("element content: {}", e.message), ctx_off);
if let Some(last) = self.issues.last_mut() {
last.value = Some(to_check.to_string());
last.type_name = Some(format!("xs:{}", real.builtin.name()));
let idx = if ctx.sibling_index >= 2 {
format!("[{}]", ctx.sibling_index)
} else {
String::new()
};
last.path = format!("{}/{elem_local}{idx}", last.path);
}
}
self.run_simple_type_assertions(&real, to_check, ctx_off);
}
(TypeRef::Complex(ct), _) => {
match &ct.content {
ContentModel::Empty => {
if !ctx.text_buf.trim().is_empty() {
self.report_at(ValidationKind::TypeMismatch,
"complexType with empty content cannot have text", ctx_off);
}
}
ContentModel::Simple(st) => {
let effective = self.simple_content_effective_type(ct, st);
let real = self.resolve_simple_type(&effective);
let to_check = effective_text(&ctx.text_buf, ctx.decl.default.as_deref(),
ctx.decl.fixed.as_deref());
if let Err(e) = real.validate_only(to_check) {
let elem_local = ctx.decl.name.local.to_string();
self.report_at(e.kind,
format!("element content: {}", e.message), ctx_off);
if let Some(last) = self.issues.last_mut() {
last.value = Some(to_check.to_string());
last.type_name = Some(format!("xs:{}", real.builtin.name()));
let idx = if ctx.sibling_index >= 2 {
format!("[{}]", ctx.sibling_index)
} else {
String::new()
};
last.path = format!("{}/{elem_local}{idx}", last.path);
}
}
self.run_simple_type_assertions(&real, to_check, ctx_off);
}
ContentModel::Complex { mixed, .. } => {
if !*mixed && !ctx.text_buf.trim().is_empty() {
self.report_at(ValidationKind::TypeMismatch,
"non-mixed complexType cannot have text content", ctx_off);
}
}
}
}
}
if !ctx.is_nil
&& let Some(fixed) = &ctx.decl.fixed
&& !ctx.text_buf.is_empty()
&& ctx.text_buf.trim() != fixed.trim()
{
self.report_at(ValidationKind::TypeMismatch,
format!("element content {:?} doesn't match fixed value {:?}",
ctx.text_buf, fixed), ctx_off);
}
let _ = ctx.seen_attrs;
if let TypeRef::Complex(ct) = &ctx.type_def {
if !ct.assertions.is_empty() {
let snapshot = ChildSnapshot {
name: ctx.name.clone(),
attrs: ctx.cached_attrs.clone(),
text: ctx.text_buf.clone(),
children: ctx.child_snapshots.clone(),
};
for a in &ct.assertions {
use super::assertion::{eval_complex_assert, AssertOutcome};
match eval_complex_assert(a, &snapshot) {
AssertOutcome::Pass => {}
AssertOutcome::Fail => {
self.report_at(ValidationKind::AssertionViolation,
format!("assertion failed: {}", a.test), ctx_off);
}
AssertOutcome::Unevaluable(_why) => {
}
}
}
}
}
false
}
fn handle_wildcard_match(
&mut self,
qn: &QName,
attrs: &[Attr<'x>],
wc: Wildcard,
pushed_ns: bool,
event_offset: usize,
) -> bool {
match wc.process_contents {
ProcessContents::Skip => {
self.skip_body_into_issues(pushed_ns)
}
ProcessContents::Lax => {
if let Some(decl) = self.schema.element(qn) {
let decl = decl.clone();
self.push_decl_ctx(qn, decl, attrs, pushed_ns, event_offset);
false
} else {
self.skip_body_into_issues(pushed_ns)
}
}
ProcessContents::Strict => {
if let Some(decl) = self.schema.element(qn) {
let decl = decl.clone();
self.push_decl_ctx(qn, decl, attrs, pushed_ns, event_offset);
false
} else {
self.report_at(ValidationKind::UnexpectedElement,
format!("strict wildcard: <{qn}> not declared in schema"),
event_offset);
self.skip_body_into_issues(pushed_ns)
}
}
}
}
fn validate_attrs_against_type(
&mut self,
type_def: &TypeRef,
attrs: &[Attr<'x>],
event_offset: usize,
) -> Vec<QName> {
let mut seen_attrs = Vec::with_capacity(attrs.len());
let TypeRef::Complex(ct) = type_def else { return seen_attrs };
for a in attrs {
let aname = a.name;
if aname == "xmlns" || aname.starts_with("xmlns:") { continue; }
let (prefix_opt, local): (Option<&str>, &str) =
match aname.split_once(':') {
Some((p, l)) => (Some(p), l),
None => (None, aname),
};
let ns_uri: Option<&str> = prefix_opt.and_then(|p| self.resolve_prefix(p));
if ns_uri == Some(XSI_NS) { continue; }
let matched = ct.attributes.iter().find(|au| {
au.use_kind != AttributeUseKind::Prohibited
&& au.decl.name.local.as_ref() == local
&& au.decl.name.namespace.as_deref() == ns_uri
});
match matched {
Some(au) => {
let referenced = self.schema.attribute(&au.decl.name);
let resolved_type = referenced
.map(|d| d.type_def.clone())
.unwrap_or_else(|| au.decl.type_def.clone());
let st = self.resolve_simple_type(&resolved_type);
let val = a.value.as_ref();
if let Err(e) = st.validate_only(val) {
self.report_at(e.kind,
format!("attribute {}: {}", au.decl.name, e.message),
event_offset);
}
let fixed = au.fixed.as_deref()
.or(au.decl.fixed.as_deref())
.or_else(|| referenced.and_then(|d| d.fixed.as_deref()));
if let Some(f) = fixed {
if val.trim() != f.trim() {
self.report_at(ValidationKind::TypeMismatch,
format!("attribute {} value {val:?} \
doesn't match fixed value {f:?}",
au.decl.name),
event_offset);
}
}
seen_attrs.push(au.decl.name.clone());
}
None => {
let attr_qn = QName {
namespace: ns_uri.map(Arc::from),
local: Arc::from(local),
};
if let Some(wc) = &ct.any_attribute {
if attr_wildcard_accepts_qname(wc, &attr_qn, self.schema, ct) {
seen_attrs.push(attr_qn);
continue;
}
}
self.report_at(ValidationKind::UnexpectedAttribute,
format!("unexpected attribute {attr_qn}"),
event_offset);
}
}
}
for au in &ct.attributes {
let fill_value: Option<&str> =
if self.opts.apply_attribute_defaults && au.decl.name.namespace.is_none() {
au.default.as_deref()
.or(au.decl.default.as_deref())
.or(au.fixed.as_deref())
.or(au.decl.fixed.as_deref())
} else {
None
};
if au.use_kind != AttributeUseKind::Required && fill_value.is_none() {
continue;
}
let present = seen_attrs.iter().any(|n|
n == &au.decl.name
|| (n.local == au.decl.name.local
&& n.namespace.is_none()
&& au.decl.name.namespace.is_none()));
if present { continue; }
if let Some(value) = fill_value {
self.reader.fill_default_attr(au.decl.name.local.as_ref(), value);
continue;
}
if au.use_kind == AttributeUseKind::Required {
self.report_at(ValidationKind::MissingRequiredAttribute,
format!("missing required attribute {}", au.decl.name),
event_offset);
}
}
seen_attrs
}
fn push_decl_ctx(&mut self, qn: &QName, decl: Arc<ElementDecl>, attrs: &[Attr<'x>],
pushed_ns: bool, event_offset: usize,
) {
let type_def = self.resolve_type(&decl.type_def);
let _ = self.validate_attrs_against_type(&type_def, &attrs, event_offset);
let cursor = build_cursor(&type_def);
let declared_scope = if !decl.identity.is_empty() {
let collected = vec![Vec::new(); decl.identity.len()];
self.key_scopes.push(KeyScope {
declaring_depth: self.stack.len(),
declaring_offset: event_offset as u32,
decl: decl.clone(),
collected,
});
self.key_scopes.len() - 1
} else { usize::MAX };
let parent_collect_depth = self.stack.last()
.map(|p| p.collect_depth).unwrap_or(0);
let parent_collects = parent_collect_depth > 0;
let (matched, mut cached_attrs) = self.check_active_scopes(qn, &attrs);
if cached_attrs.is_empty() && parent_collects {
cached_attrs = snapshot_attrs(&attrs, |s| self.parse_attribute_qname(s));
}
let own_collect_depth = matched.iter().map(|(si, ci)| {
self.key_scopes[*si].decl.identity[*ci].fields.iter()
.map(max_field_child_descent)
.max().unwrap_or(0)
}).max().unwrap_or(0);
let collect_depth = own_collect_depth
.max(parent_collect_depth.saturating_sub(1));
let sibling_index = match self.stack.last_mut() {
Some(parent) => bump_child_counter(&mut parent.child_counters, &qn.local),
None => 1,
};
self.record_governing_type(&type_def);
self.stack.push(ElementCtx {
decl,
type_def,
cursor,
text_buf: String::new(),
is_nil: false,
name: qn.clone(),
pushed_ns,
seen_attrs: Vec::new(),
cached_attrs,
declared_scope,
matched_constraints: matched,
collect_depth,
child_snapshots: Vec::new(),
start_offset: event_offset as u32,
_phantom: std::marker::PhantomData,
sibling_index,
child_counters: Vec::new(),
});
let _ = attrs;
}
fn skip_body_into_issues(&mut self, pushed_ns: bool) -> bool {
let mut depth = 1usize;
while depth > 0 {
match self.reader.next_into(&mut self.attr_buf) {
Ok(EventInto::StartElement { .. }) => depth += 1,
Ok(EventInto::EndElement { .. }) => depth -= 1,
Ok(EventInto::Eof) => {
self.report(ValidationKind::Other, "unexpected EOF in skipped subtree");
return true;
}
Err(e) => {
self.issues.push(ValidationIssue {
message: format!("XML parse error: {e}"),
line: e.line, column: e.column, path: self.current_path(),
kind: ValidationKind::Other,
expected: Vec::new(), value: None, type_name: None,
});
return true;
}
_ => {}
}
}
self.pop_ns_scope_if(pushed_ns);
false
}
fn handle_min_occurs_violation(&mut self, p: &Particle, at_offset: usize) -> bool {
let what = match &p.term {
Term::Element(e) => format!("element <{}>", e.name),
Term::Group { .. } => "group".to_string(),
Term::Wildcard(_) => "wildcard".to_string(),
Term::GroupRef(name) => format!("group {name}"),
};
self.report_at(ValidationKind::MissingRequiredElement,
format!("missing required {what} (minOccurs={})", p.min_occurs),
at_offset)
}
fn resolve_type(&self, t: &TypeRef) -> TypeRef {
if let TypeRef::Simple(st) = t {
if let Some(name) = &st.name {
if let Some(rest) = name.strip_prefix("UNRESOLVED:") {
let qn = parse_unresolved_marker(rest);
if let Some(real) = self.lookup_type_by_qname(&qn) {
return real;
}
}
}
}
t.clone()
}
fn constraint_peers_all_dot(&self, scope_idx: usize, constraint_idx: usize) -> bool {
use super::identity::ConstraintKind;
let me = &self.key_scopes[scope_idx].decl.identity[constraint_idx];
match me.kind {
ConstraintKind::Key | ConstraintKind::Unique => {
for scope in &self.key_scopes {
for c in scope.decl.identity.iter() {
if c.kind != ConstraintKind::KeyRef { continue; }
if c.refer.as_ref() != Some(&me.name) { continue; }
if !c.fields.iter().all(field_path_is_dot) {
return false;
}
}
}
true
}
ConstraintKind::KeyRef => {
let Some(target_name) = me.refer.as_ref() else { return true; };
for scope in &self.key_scopes {
for c in scope.decl.identity.iter() {
if !matches!(c.kind,
ConstraintKind::Key | ConstraintKind::Unique) { continue; }
if &c.name != target_name { continue; }
if !c.fields.iter().all(field_path_is_dot) {
return false;
}
}
}
true
}
}
}
fn field_dot_simple_type(
&self, type_def: &TypeRef,
) -> Option<Arc<SimpleType>> {
match type_def {
TypeRef::Simple(st) => Some(self.resolve_simple_type(st)),
TypeRef::Complex(ct) => match &ct.content {
ContentModel::Simple(st) => {
let effective = self.simple_content_effective_type(ct, st);
Some(self.resolve_simple_type(&effective))
}
_ => None,
},
}
}
fn simple_content_effective_type(
&self, ct: &Arc<ComplexType>, current: &Arc<SimpleType>,
) -> Arc<SimpleType> {
let is_placeholder = current.name.is_none()
&& matches!(current.builtin, super::types::BuiltinType::String);
if !is_placeholder { return current.clone(); }
let Some(d) = &ct.derivation else { return current.clone(); };
if d.method != super::types::DerivationMethod::Extension {
return current.clone();
}
let base = self.resolve_type(&d.base);
match base {
TypeRef::Simple(s) => s,
TypeRef::Complex(_) => current.clone(),
}
}
fn resolve_simple_type(&self, st: &Arc<SimpleType>) -> Arc<SimpleType> {
let top = if let Some(name) = &st.name {
if let Some(rest) = name.strip_prefix("UNRESOLVED:") {
let qn = parse_unresolved_marker(rest);
if let Some(TypeRef::Simple(real)) = self.lookup_type_by_qname(&qn) {
real
} else {
st.clone()
}
} else { st.clone() }
} else { st.clone() };
match &top.variety {
super::types::Variety::Atomic => top,
super::types::Variety::List { item_type } => {
let new_item = self.resolve_simple_type(item_type);
if Arc::ptr_eq(&new_item, item_type) {
top
} else {
let mut t: SimpleType = (*top).clone();
t.variety = super::types::Variety::List { item_type: new_item };
Arc::new(t)
}
}
super::types::Variety::Union { members } => {
let new_members: Vec<Arc<SimpleType>> = members.iter()
.map(|m| self.resolve_simple_type(m))
.collect();
let changed = new_members.iter().zip(members.iter())
.any(|(a, b)| !Arc::ptr_eq(a, b));
if !changed {
top
} else {
let mut t: SimpleType = (*top).clone();
t.variety = super::types::Variety::Union { members: new_members };
Arc::new(t)
}
}
}
}
fn lookup_type_by_qname(&self, qn: &QName) -> Option<TypeRef> {
if qn.namespace.as_deref() == Some(QName::XSD_NS) {
if let Some(b) = BuiltinType::from_name(&qn.local) {
return Some(TypeRef::Simple(Arc::new(SimpleType {
name: Some(qn.local.clone()),
builtin: b,
facets: super::facets::FacetSet::default(),
whitespace: b.default_whitespace(),
variety: super::types::Variety::Atomic,
final_: super::schema::BlockSet::default(),
assertions: Vec::new(),
})));
}
}
self.schema.type_def(qn).cloned()
}
}
fn build_cursor(type_def: &TypeRef) -> ContentCursor {
match type_def {
TypeRef::Simple(_) => ContentCursor::None,
TypeRef::Complex(ct) => match ct.matcher.get() {
Some(super::dfa::ContentMatcher::Dfa(dfa)) => ContentCursor::Dfa {
dfa: dfa.clone(),
state: dfa.initial,
},
Some(super::dfa::ContentMatcher::All) => match &ct.content {
ContentModel::Complex { root: Particle { term: Term::Group { kind, particles }, min_occurs, .. }, .. } => {
ContentCursor::Group {
kind: *kind,
particles: Arc::clone(particles),
idx: 0,
cur_count: 0,
all_seen: HashMap::default(),
outer_min: *min_occurs,
}
}
_ => ContentCursor::None,
},
Some(super::dfa::ContentMatcher::None) | None => ContentCursor::None,
},
}
}
fn find_xsi_type<'a>(attrs: &'a [Attr]) -> Option<&'a str> {
attrs.iter().find(|a| a.name() == "xsi:type").map(|a| a.value.as_ref())
}
fn effective_text<'a>(text: &'a str, default: Option<&'a str>, fixed: Option<&'a str>) -> &'a str {
if !text.is_empty() { return text; }
fixed.or(default).unwrap_or(text)
}
fn find_xsi_nil<'a>(attrs: &'a [Attr]) -> Option<&'a str> {
attrs.iter().find(|a| a.name() == "xsi:nil").map(|a| a.value.as_ref())
}
enum XsiNilParse<'a> {
True,
False,
Invalid(&'a str),
}
fn parse_xsi_nil(raw: &str) -> XsiNilParse<'_> {
match raw {
"true" | "1" => XsiNilParse::True,
"false" | "0" => XsiNilParse::False,
other => XsiNilParse::Invalid(other),
}
}
fn parse_unresolved_marker(s: &str) -> QName {
if let Some(rest) = s.strip_prefix('{') {
if let Some(end) = rest.find('}') {
let ns = &rest[..end];
let local = &rest[end + 1..];
return QName::new(if ns.is_empty() { None } else { Some(ns) }, local);
}
}
QName::new(None, s)
}
enum MatchOutcome {
Element(Arc<ElementDecl>),
Wildcard(Wildcard),
None,
}
fn expected_element_names(cursor: &ContentCursor) -> Vec<String> {
match cursor {
ContentCursor::Dfa { dfa, state } => dfa.states[*state as usize]
.on_element
.iter()
.map(|t| t.name.local.to_string())
.collect(),
ContentCursor::Group { particles, idx, .. } => particles
.get(*idx)
.map(particle_element_names)
.unwrap_or_default(),
ContentCursor::None => Vec::new(),
}
}
fn particle_element_names(p: &Particle) -> Vec<String> {
match &p.term {
super::schema::Term::Element(decl) => vec![decl.name.local.to_string()],
super::schema::Term::Group { particles, .. } => {
particles.iter().flat_map(particle_element_names).collect()
}
_ => Vec::new(),
}
}
fn match_in_cursor(qn: &QName, cursor: &mut ContentCursor, schema: &Schema) -> MatchOutcome {
match cursor {
ContentCursor::None => MatchOutcome::None,
ContentCursor::Dfa { dfa, state } => {
let target_ns = schema.target_namespace();
let siblings = dfa.defined_siblings.clone();
let step = dfa.step(*state, qn, |wc, qn| {
super::dfa::wildcard_admits(
wc, qn, target_ns,
|q| schema.element(q).is_some(),
|q| siblings.iter().any(|s| s == q),
)
});
match step {
Some(super::dfa::DfaTransition::Element { next, decl }) => {
*state = next;
MatchOutcome::Element(decl)
}
Some(super::dfa::DfaTransition::Wildcard { next, process_contents }) => {
*state = next;
MatchOutcome::Wildcard(super::schema::Wildcard {
namespaces: super::schema::NamespaceConstraint::Any,
process_contents,
not_qnames: Vec::new(),
not_namespaces: Vec::new(),
not_qname_defined: false,
not_qname_defined_sibling: false,
})
}
None => MatchOutcome::None,
}
}
ContentCursor::Group { kind, particles, idx, cur_count, all_seen, .. } => {
match kind {
GroupKind::Sequence => match_sequence(qn, particles, idx, cur_count, schema),
GroupKind::Choice => match_choice(qn, particles, idx, cur_count, schema),
GroupKind::All => match_all(qn, particles, all_seen, schema),
}
}
}
}
fn match_sequence(
qn: &QName,
particles: &Arc<[Particle]>,
idx: &mut usize,
cur_count: &mut u32,
schema: &Schema,
) -> MatchOutcome {
while *idx < particles.len() {
let p = &particles[*idx];
if particle_accepts(p, qn, schema) {
if !p.max_occurs.allows(*cur_count + 1) {
*idx += 1;
*cur_count = 0;
continue;
}
*cur_count += 1;
return outcome_for(p, qn, schema);
}
if *cur_count < p.min_occurs {
return MatchOutcome::None;
}
*idx += 1;
*cur_count = 0;
}
MatchOutcome::None
}
fn match_choice(
qn: &QName,
particles: &Arc<[Particle]>,
idx: &mut usize,
cur_count: &mut u32,
schema: &Schema,
) -> MatchOutcome {
if *cur_count == 0 {
for (i, p) in particles.iter().enumerate() {
if particle_accepts(p, qn, schema) {
*idx = i;
*cur_count = 1;
return outcome_for(p, qn, schema);
}
}
return MatchOutcome::None;
}
let p = &particles[*idx];
if particle_accepts(p, qn, schema) && p.max_occurs.allows(*cur_count + 1) {
*cur_count += 1;
return outcome_for(p, qn, schema);
}
MatchOutcome::None
}
fn match_all(
qn: &QName,
particles: &Arc<[Particle]>,
all_seen: &mut HashMap<usize, u32>,
schema: &Schema,
) -> MatchOutcome {
let siblings = collect_particle_siblings(particles);
for (i, p) in particles.iter().enumerate() {
if particle_accepts_with_siblings(p, qn, schema, &siblings) {
let seen = all_seen.entry(i).or_insert(0);
if !p.max_occurs.allows(*seen + 1) {
continue;
}
*seen += 1;
return outcome_for(p, qn, schema);
}
}
MatchOutcome::None
}
fn collect_particle_siblings(particles: &[Particle]) -> Vec<QName> {
fn walk(p: &Particle, out: &mut Vec<QName>) {
match &p.term {
Term::Element(decl) => {
if !out.iter().any(|n| n == &decl.name) {
out.push(decl.name.clone());
}
}
Term::Group { particles, .. } => {
for c in particles.iter() { walk(c, out); }
}
Term::Wildcard(_) | Term::GroupRef(_) => {}
}
}
let mut out = Vec::new();
for p in particles { walk(p, &mut out); }
out
}
fn particle_accepts(p: &Particle, qn: &QName, schema: &Schema) -> bool {
match &p.term {
Term::Element(decl) => {
if &decl.name == qn { return true; }
if decl.block.contains(super::schema::BlockSet::SUBSTITUTION) {
return false;
}
for sub in schema.substitutes_for(&decl.name) {
if &sub.name == qn { return true; }
}
false
}
Term::Wildcard(wc) => wildcard_accepts_qname(wc, qn, schema, &[]),
Term::Group { particles, .. } => particles.iter().any(|p2| particle_accepts(p2, qn, schema)),
Term::GroupRef(_) => false,
}
}
fn particle_accepts_with_siblings(
p: &Particle,
qn: &QName,
schema: &Schema,
siblings: &[QName],
) -> bool {
match &p.term {
Term::Wildcard(wc) => wildcard_accepts_qname(wc, qn, schema, siblings),
Term::Group { particles, .. } => particles
.iter()
.any(|p2| particle_accepts_with_siblings(p2, qn, schema, siblings)),
_ => particle_accepts(p, qn, schema),
}
}
fn derivation_methods_between(
child: &TypeRef,
ancestor: &TypeRef,
schema: &Schema,
) -> Option<BlockSet> {
let child = resolve_typeref_via_schema(child, schema);
let ancestor = resolve_typeref_via_schema(ancestor, schema);
if type_refs_equal(&child, &ancestor) {
return Some(BlockSet::empty());
}
if is_any_type(&ancestor) {
return Some(BlockSet::RESTRICTION);
}
if is_any_simple_type(&ancestor) {
return match &child {
TypeRef::Simple(_) => Some(BlockSet::RESTRICTION),
TypeRef::Complex(_) => None,
};
}
match &child {
TypeRef::Complex(ct) => {
let mut methods = BlockSet::empty();
let mut cur: Arc<ComplexType> = ct.clone();
for _ in 0..64 {
let d = cur.derivation.as_ref()?;
methods |= match d.method {
DerivationMethod::Restriction => BlockSet::RESTRICTION,
DerivationMethod::Extension => BlockSet::EXTENSION,
};
let resolved_base = resolve_typeref_via_schema(&d.base, schema);
if type_refs_equal(&resolved_base, &ancestor) {
return Some(methods);
}
match resolved_base {
TypeRef::Complex(next) => { cur = next; }
TypeRef::Simple(_) => return None,
}
}
None
}
TypeRef::Simple(s_child) => {
if let TypeRef::Simple(s_anc) = &ancestor {
if s_child.builtin == s_anc.builtin {
Some(BlockSet::RESTRICTION)
} else { None }
} else { None }
}
}
}
fn resolve_typeref_via_schema(tr: &TypeRef, schema: &Schema) -> TypeRef {
if let TypeRef::Simple(st) = tr {
if let Some(name) = &st.name {
if let Some(rest) = name.strip_prefix("UNRESOLVED:") {
let qn = parse_unresolved_marker(rest);
if let Some(real) = schema.type_def(&qn) {
return real.clone();
}
}
}
}
tr.clone()
}
fn wildcard_accepts_qname(
wc: &Wildcard,
qn: &QName,
schema: &Schema,
siblings: &[QName],
) -> bool {
super::dfa::wildcard_admits(
wc, qn, schema.target_namespace(),
|q| schema.element(q).is_some(),
|q| siblings.iter().any(|s| s == q),
)
}
fn attr_wildcard_accepts_qname(
wc: &Wildcard,
qn: &QName,
schema: &Schema,
ct: &ComplexType,
) -> bool {
super::dfa::wildcard_admits(
wc, qn, schema.target_namespace(),
|q| schema.attribute(q).is_some(),
|q| ct.attributes.iter().any(|au| &au.decl.name == q),
)
}
fn outcome_for(p: &Particle, qn: &QName, schema: &Schema) -> MatchOutcome {
match &p.term {
Term::Element(decl) => {
if &decl.name == qn {
return MatchOutcome::Element(decl.clone());
}
if !decl.block.contains(super::schema::BlockSet::SUBSTITUTION) {
for sub in schema.substitutes_for(&decl.name) {
if &sub.name == qn {
let methods = derivation_methods_between(
&sub.type_def, &decl.type_def, schema,
);
if let Some(m) = methods {
if !(decl.block & m).is_empty() {
return MatchOutcome::Element(decl.clone());
}
}
return MatchOutcome::Element(sub.clone());
}
}
}
MatchOutcome::Element(decl.clone())
}
Term::Wildcard(wc) => MatchOutcome::Wildcard(wc.clone()),
Term::Group { particles, .. } => {
for p in particles.iter() {
if particle_accepts(p, qn, schema) {
return outcome_for(p, qn, schema);
}
}
MatchOutcome::None
}
Term::GroupRef(_) => MatchOutcome::None,
}
}
fn selector_matches<'s>(
sel: &SelectorPath,
stack: &[ElementCtx<'s>],
qn: &QName,
rel_depth: usize,
) -> bool {
sel.paths.iter().any(|p| path_matches(p, stack, qn, rel_depth))
}
fn path_matches<'s>(
p: &PathExpr,
stack: &[ElementCtx<'s>],
qn: &QName,
rel_depth: usize,
) -> bool {
let n = p.steps.len();
if n == 0 {
return rel_depth == 0;
}
if p.descendant {
if rel_depth < n { return false; }
} else {
if rel_depth != n { return false; }
}
let stack_top = stack.len();
for (k, step) in p.steps.iter().rev().enumerate() {
let nm = match step {
PathStep::Child(nm) | PathStep::Attribute(nm) => nm,
};
let target_qn: &QName = if k == 0 {
qn
} else {
&stack[stack_top - k].name
};
if !name_test_matches_qname(nm, target_qn) {
return false;
}
}
true
}
fn name_test_matches_qname(nt: &NameTest, qn: &QName) -> bool {
match nt {
NameTest::Any => true,
NameTest::Name(want) => {
want.local == qn.local
&& (want.namespace.is_none() || want.namespace == qn.namespace)
}
NameTest::AnyInNs(ns) => qn.namespace.as_deref() == Some(ns.as_ref()),
}
}
enum FieldEval {
Missing,
Single(String),
Ambiguous,
}
fn eval_field(
fp: &FieldPath,
cached_attrs: &[(QName, String)],
text_buf: &str,
children: &[ChildSnapshot],
) -> FieldEval {
for path in &fp.paths {
match eval_path_steps(&path.steps, cached_attrs, text_buf, children) {
FieldEval::Single(v) => return FieldEval::Single(v),
FieldEval::Ambiguous => return FieldEval::Ambiguous,
FieldEval::Missing => continue,
}
}
FieldEval::Missing
}
fn eval_path_steps(
steps: &[PathStep],
attrs: &[(QName, String)],
text: &str,
children: &[ChildSnapshot],
) -> FieldEval {
match steps {
[] => FieldEval::Single(text.trim().to_string()),
[PathStep::Attribute(nm)] => match lookup_attr(attrs, nm) {
Some(v) => FieldEval::Single(v),
None => FieldEval::Missing,
},
[PathStep::Child(nm), rest @ ..] => {
let mut matched: Option<&ChildSnapshot> = None;
for c in children {
if name_test_matches_qname(nm, &c.name) {
if matched.is_some() {
return FieldEval::Ambiguous;
}
matched = Some(c);
}
}
match matched {
Some(c) => eval_path_steps(rest, &c.attrs, &c.text, &c.children),
None => FieldEval::Missing,
}
}
_ => FieldEval::Missing,
}
}
fn lookup_attr(attrs: &[(QName, String)], nt: &NameTest) -> Option<String> {
match nt {
NameTest::Any => attrs.first().map(|(_, v)| v.clone()),
NameTest::Name(want) => attrs.iter()
.find(|(qn, _)| {
qn.local == want.local
&& (want.namespace.is_none() || want.namespace == qn.namespace)
})
.map(|(_, v)| v.clone()),
NameTest::AnyInNs(ns) => attrs.iter()
.find(|(qn, _)| qn.namespace.as_deref() == Some(ns.as_ref()))
.map(|(_, v)| v.clone()),
}
}
impl<'s, 'x, E: XsdEventSource<'x>> Validator<'s, 'x, E> {
fn finalize_key_scope(&mut self, scope: &KeyScope) {
let off = scope.declaring_offset as usize;
for (ci, c) in scope.decl.identity.iter().enumerate() {
let tuples = &scope.collected[ci];
match c.kind {
ConstraintKind::Key | ConstraintKind::Unique => {
let mut seen: HashMap<&KeyTuple, ()> = HashMap::default();
for t in tuples {
let has_null = t.iter().any(|f| f.is_none());
if has_null {
if c.kind == ConstraintKind::Key {
self.report_at(
ValidationKind::Other,
format!(
"<xs:key {:?}>: a selected element is missing one of the field values",
c.name.local
),
off,
);
}
continue;
}
if seen.insert(t, ()).is_some() {
let pretty = format_tuple(t);
self.report_at(
ValidationKind::KeyNotUnique,
format!(
"<xs:{} {:?}>: duplicate key value {pretty}",
if c.kind == ConstraintKind::Key { "key" } else { "unique" },
c.name.local
),
off,
);
}
}
}
ConstraintKind::KeyRef => {
let refer = match &c.refer {
Some(r) => r,
None => continue, };
let dangling: Vec<KeyTuple> = {
let referenced: Option<&Vec<KeyTuple>> = scope.decl.identity.iter()
.position(|other| &other.name == refer)
.map(|i| &scope.collected[i])
.or_else(|| {
for outer in self.key_scopes.iter().rev() {
if let Some(i) = outer.decl.identity.iter()
.position(|other| &other.name == refer)
{
return Some(&outer.collected[i]);
}
}
None
});
match referenced {
None => {
self.report_at(
ValidationKind::Other,
format!(
"<xs:keyref {:?}>: refer={refer} not found in scope",
c.name.local
),
off,
);
continue;
}
Some(target) => tuples.iter()
.filter(|t| t.iter().all(|f| f.is_some()))
.filter(|t| !target.contains(t))
.cloned()
.collect(),
}
};
for t in dangling {
let pretty = format_tuple(&t);
self.report_at(
ValidationKind::KeyRefDangling,
format!(
"<xs:keyref {:?}>: value {pretty} has no matching key",
c.name.local
),
off,
);
}
}
}
}
}
}
impl<'s, 'x, E: XsdEventSource<'x>> Validator<'s, 'x, E> {
fn derivation_methods_from(&self, child: &TypeRef, ancestor: &TypeRef) -> Option<BlockSet> {
let child = self.resolve_type(child);
let ancestor = self.resolve_type(ancestor);
if type_refs_equal(&child, &ancestor) {
return Some(BlockSet::empty());
}
if is_any_type(&ancestor) {
return Some(BlockSet::RESTRICTION);
}
if is_any_simple_type(&ancestor) {
return match &child {
TypeRef::Simple(_) => Some(BlockSet::RESTRICTION),
TypeRef::Complex(_) => None,
};
}
match &child {
TypeRef::Complex(ct) => {
let mut methods = BlockSet::empty();
let mut cur: Arc<ComplexType> = ct.clone();
for _ in 0..64 {
let d = cur.derivation.as_ref()?;
methods |= match d.method {
DerivationMethod::Restriction => BlockSet::RESTRICTION,
DerivationMethod::Extension => BlockSet::EXTENSION,
};
let resolved_base = self.resolve_type(&d.base);
if type_refs_equal(&resolved_base, &ancestor) {
return Some(methods);
}
match resolved_base {
TypeRef::Complex(next) => { cur = next; }
TypeRef::Simple(_) => return None,
}
}
None
}
TypeRef::Simple(s_child) => {
if let TypeRef::Simple(s_anc) = &ancestor {
if s_child.builtin.derives_from(s_anc.builtin) {
return Some(BlockSet::RESTRICTION);
}
if s_child.name.as_deref() == s_anc.name.as_deref()
&& s_child.name.is_some()
{
return Some(BlockSet::empty());
}
use super::types::Variety;
if let Variety::Union { members } = &s_anc.variety {
for m in members {
if self.derivation_methods_from(
&TypeRef::Simple(s_child.clone()),
&TypeRef::Simple(m.clone()),
).is_some() {
return Some(BlockSet::RESTRICTION);
}
}
}
}
None
}
}
}
}
fn is_any_type(t: &TypeRef) -> bool {
match t {
TypeRef::Complex(ct) => ct.name.as_ref()
.is_some_and(|n| n.namespace.as_deref() == Some(QName::XSD_NS)
&& n.local.as_ref() == "anyType"),
TypeRef::Simple(st) => is_xsd_placeholder(st, "anyType"),
}
}
fn is_any_simple_type(t: &TypeRef) -> bool {
match t {
TypeRef::Simple(st) => is_xsd_placeholder(st, "anySimpleType")
|| st.name.as_deref() == Some("anySimpleType")
|| matches!(st.builtin, super::types::BuiltinType::AnySimpleType),
_ => false,
}
}
fn is_xsd_placeholder(st: &Arc<SimpleType>, local: &str) -> bool {
st.name.as_deref()
.and_then(|n| n.strip_prefix("UNRESOLVED:"))
.map(parse_unresolved_marker)
.is_some_and(|qn| qn.namespace.as_deref() == Some(QName::XSD_NS)
&& qn.local.as_ref() == local)
}
fn type_refs_equal(a: &TypeRef, b: &TypeRef) -> bool {
match (a, b) {
(TypeRef::Complex(x), TypeRef::Complex(y)) => {
Arc::ptr_eq(x, y) || (x.name.is_some() && x.name == y.name)
}
(TypeRef::Simple(x), TypeRef::Simple(y)) => {
Arc::ptr_eq(x, y)
|| (x.builtin == y.builtin && x.name == y.name)
}
_ => false,
}
}
fn format_block_set(b: BlockSet) -> String {
let mut parts: Vec<&str> = Vec::new();
if b.contains(BlockSet::RESTRICTION) { parts.push("restriction"); }
if b.contains(BlockSet::EXTENSION) { parts.push("extension"); }
if b.contains(BlockSet::SUBSTITUTION) { parts.push("substitution"); }
parts.join(" ")
}
fn field_path_is_dot(fp: &super::identity::FieldPath) -> bool {
fp.paths.iter().all(|p| p.steps.is_empty())
}
fn canonical_field_key(raw: &str, simple_type: Option<&Arc<SimpleType>>) -> String {
use super::types::{BuiltinType, Value, parse_lexical};
let Some(st) = simple_type else { return raw.to_string(); };
let prim = st.builtin.primitive();
let tag = prim.name();
let prepared: std::borrow::Cow<'_, str> = match st.whitespace {
super::WhitespaceMode::Preserve => std::borrow::Cow::Borrowed(raw),
super::WhitespaceMode::Replace =>
std::borrow::Cow::Owned(raw.chars().map(|c| match c {
'\t' | '\n' | '\r' => ' ',
_ => c,
}).collect()),
super::WhitespaceMode::Collapse =>
std::borrow::Cow::Owned(raw.split_whitespace().collect::<Vec<_>>().join(" ")),
};
let parsed = parse_lexical(st.builtin, &prepared);
let body: String = match parsed {
Ok(Value::Bool(b)) => if b { "true".into() } else { "false".into() },
Ok(Value::Decimal(d)) => d.normalize().to_string(),
Ok(Value::Int(n)) => n.to_string(),
Ok(Value::BigInt(b)) => {
let sign = if b.negative { "-" } else { "" };
format!("{sign}{}", b.digits)
}
Ok(Value::Float(f)) => format!("{f:?}"),
Ok(Value::Double(d)) => format!("{d:?}"),
Ok(Value::String(s)) => s,
Ok(Value::Token(t)) => t,
Ok(Value::Bytes(bs)) => bs.iter().map(|b| format!("{b:02X}")).collect(),
Ok(Value::DateTime(dt)) => format!("{dt:?}"),
Ok(Value::Date(d)) => format!("{d:?}"),
Ok(Value::Time(t)) => format!("{t:?}"),
Ok(Value::GYearMonth(g)) => format!("{g:?}"),
Ok(Value::GYear(g)) => format!("{g:?}"),
Ok(Value::GMonthDay(g)) => format!("{g:?}"),
Ok(Value::GDay(g)) => format!("{g:?}"),
Ok(Value::GMonth(g)) => format!("{g:?}"),
Ok(Value::Duration(d)) => format!("{d:?}"),
Err(_) => prepared.to_string(),
};
let _ = BuiltinType::AnySimpleType; format!("{tag}:{body}")
}
fn format_tuple(t: &KeyTuple) -> String {
let mut s = String::from("(");
for (i, v) in t.iter().enumerate() {
if i > 0 { s.push_str(", "); }
match v {
Some(v) => { s.push('"'); s.push_str(v); s.push('"'); }
None => s.push_str("<null>"),
}
}
s.push(')');
s
}
#[cfg(test)]
mod tests {
use super::*;
fn xsd_str(extra_decls: &str) -> String {
format!(
r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test"
xmlns="urn:test"
elementFormDefault="qualified">
{extra_decls}
</xs:schema>"#
)
}
fn instance_ns() -> &'static str { r#"xmlns="urn:test""# }
#[test]
fn validate_simple_string() {
let s = Schema::compile_str(&xsd_str(
r#"<xs:element name="msg" type="xs:string"/>"#
)).unwrap();
s.validate_str(&format!(r#"<msg {ns}>hello</msg>"#, ns = instance_ns())).unwrap();
}
#[test]
fn validate_typed_int_value() {
let s = Schema::compile_str(&xsd_str(
r#"<xs:element name="age" type="xs:int"/>"#
)).unwrap();
assert!(s.validate_str(&format!(r#"<age {}>42</age>"#, instance_ns())).is_ok());
assert!(s.validate_str(&format!(r#"<age {}>not-an-int</age>"#, instance_ns())).is_err());
}
#[test]
fn validate_facet_pattern() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="zip" type="ZipCode"/>
<xs:simpleType name="ZipCode">
<xs:restriction base="xs:string">
<xs:pattern value="\d{5}(-\d{4})?"/>
</xs:restriction>
</xs:simpleType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(r#"<zip {ns}>12345</zip>"#)).is_ok());
assert!(s.validate_str(&format!(r#"<zip {ns}>12345-6789</zip>"#)).is_ok());
assert!(s.validate_str(&format!(r#"<zip {ns}>not-a-zip</zip>"#)).is_err());
}
#[test]
fn validate_complex_sequence() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="person" type="Person"/>
<xs:complexType name="Person">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="age" type="xs:int"/>
</xs:sequence>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(
r#"<person {ns}><name>Ada</name><age>30</age></person>"#
)).is_ok());
assert!(s.validate_str(&format!(
r#"<person {ns}><age>30</age><name>Ada</name></person>"#
)).is_err());
}
#[test]
fn validate_required_attribute() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="thing" type="Thing"/>
<xs:complexType name="Thing">
<xs:attribute name="id" type="xs:int" use="required"/>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(r#"<thing {ns} id="1"/>"#)).is_ok());
assert!(s.validate_str(&format!(r#"<thing {ns}/>"#)).is_err());
}
#[test]
fn validate_attribute_type() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="thing" type="Thing"/>
<xs:complexType name="Thing">
<xs:attribute name="age" type="xs:int" use="required"/>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(r#"<thing {ns} age="not-int"/>"#)).is_err());
}
#[test]
fn validate_max_occurs() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="items" type="Items"/>
<xs:complexType name="Items">
<xs:sequence>
<xs:element name="item" type="xs:string" maxOccurs="3"/>
</xs:sequence>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(
r#"<items {ns}><item>a</item><item>b</item><item>c</item></items>"#
)).is_ok());
assert!(s.validate_str(&format!(
r#"<items {ns}><item>a</item><item>b</item><item>c</item><item>d</item></items>"#
)).is_err());
}
#[test]
fn validate_min_occurs() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="items" type="Items"/>
<xs:complexType name="Items">
<xs:sequence>
<xs:element name="item" type="xs:string"
minOccurs="2" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(
r#"<items {ns}><item>a</item><item>b</item></items>"#
)).is_ok());
assert!(s.validate_str(&format!(
r#"<items {ns}><item>a</item></items>"#
)).is_err());
}
#[test]
fn validate_unbounded() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="items" type="Items"/>
<xs:complexType name="Items">
<xs:sequence>
<xs:element name="item" type="xs:string" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
let mut xml = format!(r#"<items {ns}>"#);
for _ in 0..50 { xml.push_str("<item>x</item>"); }
xml.push_str("</items>");
assert!(s.validate_str(&xml).is_ok());
}
#[test]
fn validate_choice() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="either" type="Either"/>
<xs:complexType name="Either">
<xs:choice>
<xs:element name="left" type="xs:int"/>
<xs:element name="right" type="xs:string"/>
</xs:choice>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(r#"<either {ns}><left>1</left></either>"#)).is_ok());
assert!(s.validate_str(&format!(r#"<either {ns}><right>hi</right></either>"#)).is_ok());
assert!(s.validate_str(&format!(r#"<either {ns}/>"#)).is_err());
}
#[test]
fn validate_all_any_order() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="both" type="Both"/>
<xs:complexType name="Both">
<xs:all>
<xs:element name="a" type="xs:int"/>
<xs:element name="b" type="xs:int"/>
</xs:all>
</xs:complexType>
"#)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(r#"<both {ns}><a>1</a><b>2</b></both>"#)).is_ok());
assert!(s.validate_str(&format!(r#"<both {ns}><b>2</b><a>1</a></both>"#)).is_ok());
assert!(s.validate_str(&format!(r#"<both {ns}><a>1</a></both>"#)).is_err());
}
#[test]
fn validate_xsi_nil() {
let s = Schema::compile_str(&xsd_str(
r#"<xs:element name="opt" type="xs:int" nillable="true"/>"#
)).unwrap();
let inst = format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns(), xsi = XSI_NS,
);
assert!(s.validate_str(&inst).is_ok());
}
#[test]
fn validate_xsi_nil_on_non_nillable_fails() {
let s = Schema::compile_str(&xsd_str(
r#"<xs:element name="opt" type="xs:int"/>"#
)).unwrap();
let inst = format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns(), xsi = XSI_NS,
);
assert!(s.validate_str(&inst).is_err());
}
#[test]
fn validate_unknown_root_fails() {
let s = Schema::compile_str(&xsd_str(
r#"<xs:element name="known" type="xs:string"/>"#
)).unwrap();
let ns = instance_ns();
assert!(s.validate_str(&format!(r#"<unknown {ns}>x</unknown>"#)).is_err());
}
#[test]
fn validate_collects_multiple_issues_when_not_fail_fast() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="thing" type="Thing"/>
<xs:complexType name="Thing">
<xs:attribute name="id" type="xs:int" use="required"/>
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:complexType>
"#)).unwrap();
let inst = format!(r#"<thing {}/>"#, instance_ns());
let opts = ValidationOptions { fail_fast: false, max_issues: 100, ..Default::default() };
let err = s.validate_str_opts(&inst, opts).unwrap_err();
assert!(err.issues.len() >= 2);
}
fn parts_xsd() -> String {
xsd_str(r#"
<xs:element name="catalog">
<xs:complexType>
<xs:sequence>
<xs:element name="parts">
<xs:complexType>
<xs:sequence>
<xs:element name="part" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="num" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="orders">
<xs:complexType>
<xs:sequence>
<xs:element name="line" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="part" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="partKey">
<xs:selector xpath=".//part"/>
<xs:field xpath="@num"/>
</xs:key>
<xs:keyref name="lineRef" refer="partKey">
<xs:selector xpath=".//line"/>
<xs:field xpath="@part"/>
</xs:keyref>
</xs:element>
"#)
}
#[test]
fn xs_key_accepts_unique_values() {
let s = Schema::compile_str(&parts_xsd()).unwrap();
let xml = format!(
r#"<catalog {ns}>
<parts>
<part num="A1"/><part num="A2"/><part num="A3"/>
</parts>
<orders>
<line part="A1"/><line part="A3"/>
</orders>
</catalog>"#,
ns = instance_ns(),
);
s.validate_str(&xml).unwrap();
}
#[test]
fn xs_key_rejects_duplicates() {
let s = Schema::compile_str(&parts_xsd()).unwrap();
let xml = format!(
r#"<catalog {ns}>
<parts>
<part num="A1"/><part num="A1"/>
</parts>
<orders>
<line part="A1"/>
</orders>
</catalog>"#,
ns = instance_ns(),
);
let err = s.validate_str(&xml).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyNotUnique)
), "expected KeyNotUnique, got {:?}", err.issues);
}
#[test]
fn xs_keyref_rejects_dangling() {
let s = Schema::compile_str(&parts_xsd()).unwrap();
let xml = format!(
r#"<catalog {ns}>
<parts>
<part num="A1"/>
</parts>
<orders>
<line part="UNKNOWN"/>
</orders>
</catalog>"#,
ns = instance_ns(),
);
let err = s.validate_str(&xml).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyRefDangling)
), "expected KeyRefDangling, got {:?}", err.issues);
}
#[test]
fn xs_unique_allows_missing_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="x" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="id" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:unique name="xUnique">
<xs:selector xpath=".//x"/>
<xs:field xpath="@id"/>
</xs:unique>
</xs:element>
"#)).unwrap();
let xml = format!(
r#"<root {ns}><x id="A"/><x/><x id="B"/></root>"#,
ns = instance_ns(),
);
s.validate_str(&xml).unwrap();
let bad = format!(
r#"<root {ns}><x id="A"/><x id="A"/></root>"#,
ns = instance_ns(),
);
assert!(s.validate_str(&bad).is_err());
}
#[test]
fn dfa_rejects_ambiguous_schema_at_compile_time() {
let xsd = xsd_str(r#"
<xs:complexType name="Bad">
<xs:sequence>
<xs:element name="x" type="xs:string" minOccurs="0"/>
<xs:element name="x" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:element name="root" type="Bad"/>
"#);
let err = Schema::compile_str(&xsd).unwrap_err();
assert!(err.message.contains("Unique Particle Attribution"),
"expected UPA error, got {:?}", err.message);
}
#[test]
fn dfa_rejects_ambiguous_choice_at_compile_time() {
let xsd = xsd_str(r#"
<xs:element name="root">
<xs:complexType>
<xs:choice>
<xs:element name="x" type="xs:string"/>
<xs:element name="x" type="xs:int"/>
</xs:choice>
</xs:complexType>
</xs:element>
"#);
assert!(Schema::compile_str(&xsd).is_err());
}
#[test]
fn dfa_error_message_lists_expected_elements() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="alpha" type="xs:string"/>
<xs:element name="beta" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let xml = format!(r#"<root {ns}><alpha>x</alpha></root>"#, ns = instance_ns());
let err = s.validate_str(&xml).unwrap_err();
assert!(err.issues.iter().any(|i| i.message.contains("beta")),
"expected `beta` in error, got {:?}", err.issues);
}
#[test]
fn dfa_handles_substitution_groups() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="figure" type="xs:string" abstract="true"/>
<xs:element name="image" type="xs:string" substitutionGroup="figure"/>
<xs:element name="chart" type="xs:string" substitutionGroup="figure"/>
<xs:element name="report">
<xs:complexType>
<xs:sequence>
<xs:element ref="figure" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let xml = format!(
r#"<report {ns}><image>i</image><chart>c</chart><image>j</image></report>"#,
ns = instance_ns(),
);
s.validate_str(&xml).unwrap();
}
#[test]
fn dfa_unbounded_works_correctly() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="items">
<xs:complexType>
<xs:sequence>
<xs:element name="item" type="xs:int" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let ns = instance_ns();
for n in [1, 5, 100, 1000] {
let mut xml = format!(r#"<items {ns}>"#);
for i in 0..n { xml.push_str(&format!("<item>{i}</item>")); }
xml.push_str("</items>");
s.validate_str(&xml)
.unwrap_or_else(|e| panic!("n={n} should validate: {e:?}"));
}
}
#[test]
fn dfa_accepts_optional_followed_by_required() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="msg">
<xs:complexType>
<xs:sequence>
<xs:element name="header" type="xs:string" minOccurs="0"/>
<xs:element name="body" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let ns = instance_ns();
s.validate_str(&format!(
r#"<msg {ns}><header>h</header><body>b</body></msg>"#)).unwrap();
s.validate_str(&format!(
r#"<msg {ns}><body>b</body></msg>"#)).unwrap();
assert!(s.validate_str(&format!(
r#"<msg {ns}><header>h</header></msg>"#)).is_err());
}
#[test]
fn xs_key_with_child_attribute_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="id">
<xs:complexType>
<xs:attribute name="value" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="userKey">
<xs:selector xpath=".//user"/>
<xs:field xpath="id/@value"/>
</xs:key>
</xs:element>
"#)).unwrap();
let ok = format!(
r#"<users {ns}>
<user><id value="alice"/></user>
<user><id value="bob"/></user>
</users>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
let dup = format!(
r#"<users {ns}>
<user><id value="alice"/></user>
<user><id value="alice"/></user>
</users>"#,
ns = instance_ns(),
);
let err = s.validate_str(&dup).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyNotUnique)
), "expected KeyNotUnique, got {:?}", err.issues);
}
#[test]
fn xs_key_with_child_text_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="userKey">
<xs:selector xpath=".//user"/>
<xs:field xpath="name"/>
</xs:key>
</xs:element>
"#)).unwrap();
let ok = format!(
r#"<users {ns}>
<user><name>alice</name></user>
<user><name>bob</name></user>
</users>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
let dup = format!(
r#"<users {ns}>
<user><name>alice</name></user>
<user><name>alice</name></user>
</users>"#,
ns = instance_ns(),
);
let err = s.validate_str(&dup).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyNotUnique)
), "expected KeyNotUnique, got {:?}", err.issues);
}
#[test]
fn xs_key_text_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="cities">
<xs:complexType>
<xs:sequence>
<xs:element name="city" type="xs:string" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:key name="cityKey">
<xs:selector xpath=".//city"/>
<xs:field xpath="."/>
</xs:key>
</xs:element>
"#)).unwrap();
let ok = format!(
r#"<cities {ns}><city>Boston</city><city>NYC</city></cities>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
let bad = format!(
r#"<cities {ns}><city>Boston</city><city>Boston</city></cities>"#,
ns = instance_ns(),
);
assert!(s.validate_str(&bad).is_err());
}
#[test]
fn xs_key_with_grandchild_text_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string"/>
<xs:element name="last" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="userKey">
<xs:selector xpath=".//user"/>
<xs:field xpath="name/first"/>
</xs:key>
</xs:element>
"#)).unwrap();
let ok = format!(
r#"<users {ns}>
<user><name><first>Alice</first><last>A</last></name></user>
<user><name><first>Bob</first><last>B</last></name></user>
</users>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
let dup = format!(
r#"<users {ns}>
<user><name><first>Alice</first><last>A</last></name></user>
<user><name><first>Alice</first><last>Z</last></name></user>
</users>"#,
ns = instance_ns(),
);
let err = s.validate_str(&dup).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyNotUnique)
), "expected KeyNotUnique on duplicate name/first, got {:?}", err.issues);
}
#[test]
fn xs_key_with_grandchild_attribute_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="name">
<xs:complexType>
<xs:sequence>
<xs:element name="first">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="lang" type="xs:string"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="userKey">
<xs:selector xpath=".//user"/>
<xs:field xpath="name/first/@lang"/>
</xs:key>
</xs:element>
"#)).unwrap();
let ok = format!(
r#"<users {ns}>
<user><name><first lang="en">Alice</first></name></user>
<user><name><first lang="fr">Alphonse</first></name></user>
</users>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
let dup = format!(
r#"<users {ns}>
<user><name><first lang="en">Alice</first></name></user>
<user><name><first lang="en">Bob</first></name></user>
</users>"#,
ns = instance_ns(),
);
let err = s.validate_str(&dup).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyNotUnique)
), "expected KeyNotUnique on duplicate @lang, got {:?}", err.issues);
}
#[test]
fn xs_keyref_with_deep_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="catalog">
<xs:complexType>
<xs:sequence>
<xs:element name="parts">
<xs:complexType>
<xs:sequence>
<xs:element name="part" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="header">
<xs:complexType>
<xs:sequence>
<xs:element name="meta">
<xs:complexType>
<xs:attribute name="sku" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="orders">
<xs:complexType>
<xs:sequence>
<xs:element name="line" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="header">
<xs:complexType>
<xs:sequence>
<xs:element name="ref">
<xs:complexType>
<xs:attribute name="sku" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="partKey">
<xs:selector xpath=".//part"/>
<xs:field xpath="header/meta/@sku"/>
</xs:key>
<xs:keyref name="lineRef" refer="partKey">
<xs:selector xpath=".//line"/>
<xs:field xpath="header/ref/@sku"/>
</xs:keyref>
</xs:element>
"#)).unwrap();
let ok = format!(
r#"<catalog {ns}>
<parts>
<part><header><meta sku="A1"/></header></part>
<part><header><meta sku="A2"/></header></part>
</parts>
<orders>
<line><header><ref sku="A1"/></header></line>
<line><header><ref sku="A2"/></header></line>
</orders>
</catalog>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
let bad = format!(
r#"<catalog {ns}>
<parts>
<part><header><meta sku="A1"/></header></part>
</parts>
<orders>
<line><header><ref sku="A1"/></header></line>
<line><header><ref sku="UNKNOWN"/></header></line>
</orders>
</catalog>"#,
ns = instance_ns(),
);
let err = s.validate_str(&bad).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyRefDangling)
), "expected KeyRefDangling on unknown sku, got {:?}", err.issues);
}
#[test]
fn xs_unique_three_level_child_path() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="item" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="a">
<xs:complexType>
<xs:sequence>
<xs:element name="b">
<xs:complexType>
<xs:sequence>
<xs:element name="c" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:unique name="itemUnique">
<xs:selector xpath=".//item"/>
<xs:field xpath="a/b/c"/>
</xs:unique>
</xs:element>
"#)).unwrap();
let ok = format!(
r#"<root {ns}>
<item><a><b><c>x</c></b></a></item>
<item><a><b><c>y</c></b></a></item>
</root>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
let dup = format!(
r#"<root {ns}>
<item><a><b><c>x</c></b></a></item>
<item><a><b><c>x</c></b></a></item>
</root>"#,
ns = instance_ns(),
);
let err = s.validate_str(&dup).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::KeyNotUnique)
), "expected KeyNotUnique on duplicate a/b/c, got {:?}", err.issues);
}
#[test]
fn xs_key_deep_path_missing_intermediate_is_missing_field() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="name" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="first" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="userKey">
<xs:selector xpath=".//user"/>
<xs:field xpath="name/first"/>
</xs:key>
</xs:element>
"#)).unwrap();
let bad = format!(
r#"<users {ns}>
<user><name><first>Alice</first></name></user>
<user/>
</users>"#,
ns = instance_ns(),
);
let err = s.validate_str(&bad).unwrap_err();
let missing: Vec<&ValidationIssue> = err.issues.iter()
.filter(|i| i.message.contains("missing one of the field values"))
.collect();
assert_eq!(missing.len(), 1,
"expected exactly one missing-field error, got {:?}", err.issues);
}
#[test]
fn issue_carries_line_and_column_for_simple_content() {
let s = Schema::compile_str(&xsd_str(
r#"<xs:element name="age" type="xs:int"/>"#
)).unwrap();
let bad = format!(r#"<age {}>not-an-int</age>"#, instance_ns());
let err = s.validate_str(&bad).unwrap_err();
let issue = &err.issues[0];
assert_eq!(issue.line, Some(1), "single-line input, expected line 1, got {issue:?}");
assert!(issue.column.is_some(), "expected column to be filled, got {issue:?}");
}
#[test]
fn issue_line_points_at_offending_element_in_multiline_input() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="r">
<xs:complexType>
<xs:sequence>
<xs:element name="bad" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let bad = format!("<r {}>\n \n \n <bad>not-an-int</bad>\n</r>", instance_ns());
let err = s.validate_str(&bad).unwrap_err();
let issue = err.issues.iter()
.find(|i| i.message.contains("element content"))
.expect("expected element-content error");
assert_eq!(issue.line, Some(4),
"expected <bad> on line 4, got {issue:?}");
}
#[test]
fn issue_line_for_missing_required_element_points_at_parent() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="r">
<xs:complexType>
<xs:sequence>
<xs:element name="x" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let bad = format!("\n<r {}>\n</r>", instance_ns());
let err = s.validate_str(&bad).unwrap_err();
let issue = err.issues.iter()
.find(|i| i.message.contains("missing required element"))
.expect("expected missing-required-element error");
assert_eq!(issue.line, Some(2),
"expected <r> on line 2, got {issue:?}");
}
#[test]
fn issue_line_for_unexpected_element_points_at_the_element_not_parent() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="r">
<xs:complexType>
<xs:sequence>
<xs:element name="ok" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let bad = format!("<r {}>\n <bad/>\n</r>", instance_ns());
let err = s.validate_str(&bad).unwrap_err();
let issue = err.issues.iter()
.find(|i| i.message.contains("unexpected element"))
.expect("expected unexpected-element error");
assert_eq!(issue.line, Some(2),
"expected <bad> on line 2, got {issue:?}");
}
#[test]
fn issue_line_for_missing_required_attribute_points_at_element() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="r">
<xs:complexType>
<xs:attribute name="must" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
"#)).unwrap();
let bad = format!("\n\n<r {}/>", instance_ns());
let err = s.validate_str(&bad).unwrap_err();
let issue = err.issues.iter()
.find(|i| i.message.contains("missing required attribute"))
.expect("expected missing-required-attribute error");
assert_eq!(issue.line, Some(3),
"expected <r> on line 3, got {issue:?}");
}
#[test]
fn xsi_nil_true_with_empty_content_validates() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="opt" type="xs:int" nillable="true"/>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
s.validate_str(&format!(r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns())).unwrap();
s.validate_str(&format!(r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"> </opt>"#,
ns = instance_ns())).unwrap();
}
#[test]
fn xsi_nil_true_with_non_empty_content_fails() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="opt" type="xs:int" nillable="true"/>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
let err = s.validate_str(&format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true">42</opt>"#,
ns = instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::NillableViolation)
), "expected NillableViolation, got {:?}", err.issues);
}
#[test]
fn xsi_nil_on_non_nillable_element_fails() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="opt" type="xs:int"/>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
let err = s.validate_str(&format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::NillableViolation)
&& i.message.contains("non-nillable")
), "expected non-nillable rejection, got {:?}", err.issues);
}
#[test]
fn xsi_nil_with_required_attribute_still_validates_attribute() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="opt" nillable="true">
<xs:complexType>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
s.validate_str(&format!(
r#"<opt id="x" {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns())).unwrap();
let err = s.validate_str(&format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::MissingRequiredAttribute)
), "expected MissingRequiredAttribute even under xsi:nil, got {:?}", err.issues);
}
#[test]
fn xsi_nil_skips_required_children_check() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="opt" nillable="true">
<xs:complexType>
<xs:sequence>
<xs:element name="req" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
let err = s.validate_str(&format!(r#"<opt {ns}/>"#, ns = instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::MissingRequiredElement)
));
s.validate_str(&format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns())).unwrap();
}
#[test]
fn xsi_nil_overrides_fixed_value_check() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="opt" type="xs:string" nillable="true" fixed="ABC"/>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
s.validate_str(&format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="true"/>"#,
ns = instance_ns())).unwrap();
s.validate_str(&format!(r#"<opt {ns}>ABC</opt>"#, ns = instance_ns())).unwrap();
let err = s.validate_str(&format!(
r#"<opt {ns}>XYZ</opt>"#, ns = instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i| i.message.contains("fixed")),
"expected fixed mismatch, got {:?}", err.issues);
}
#[test]
fn xsi_nil_false_validates_normally() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="opt" type="xs:int" nillable="true"/>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
s.validate_str(&format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="false">42</opt>"#,
ns = instance_ns())).unwrap();
let err = s.validate_str(&format!(
r#"<opt {ns} xmlns:xsi="{xsi}" xsi:nil="false">foo</opt>"#,
ns = instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::TypeMismatch)
), "expected TypeMismatch for non-int content, got {:?}", err.issues);
}
#[test]
fn xsi_nil_with_xsi_type_uses_substituted_type() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="Base">
<xs:sequence>
<xs:element name="child" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Derived">
<xs:complexContent>
<xs:extension base="Base">
<xs:sequence>
<xs:element name="extra" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="x" type="Base" nillable="true"/>
"#)).unwrap();
let xsi = "http://www.w3.org/2001/XMLSchema-instance";
s.validate_str(&format!(
r#"<x {ns} xmlns:xsi="{xsi}" xsi:type="Derived" xsi:nil="true"/>"#,
ns = instance_ns())).unwrap();
}
#[test]
fn redefine_replaces_simple_type_in_included_schema() {
let included = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test"
xmlns="urn:test">
<xs:simpleType name="Code">
<xs:restriction base="xs:string">
<xs:length value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
"#;
let outer = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test"
xmlns="urn:test">
<xs:redefine schemaLocation="included.xsd">
<xs:simpleType name="Code">
<xs:restriction base="Code">
<xs:enumeration value="ABC"/>
<xs:enumeration value="XYZ"/>
</xs:restriction>
</xs:simpleType>
</xs:redefine>
<xs:element name="c" type="Code"/>
</xs:schema>
"#;
let resolver = super::super::resolver::InMemoryResolver::new()
.with("included.xsd", included.as_bytes().to_vec());
let schema = Schema::compile_with(outer, resolver).unwrap();
schema.validate_str(&format!(r#"<c {}>ABC</c>"#, instance_ns())).unwrap();
schema.validate_str(&format!(r#"<c {}>XYZ</c>"#, instance_ns())).unwrap();
let err = schema.validate_str(&format!(r#"<c {}>DEF</c>"#, instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i| i.message.contains("enumeration")),
"expected enumeration failure, got {:?}", err.issues);
let err = schema.validate_str(&format!(r#"<c {}>TOOLONG</c>"#, instance_ns())).unwrap_err();
assert!(!err.issues.is_empty(),
"expected validation failure for too-long input, got ok");
}
#[test]
fn redefine_complex_type_extension_adds_fields() {
let included = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test"
xmlns="urn:test"
elementFormDefault="qualified">
<xs:complexType name="Address">
<xs:sequence>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:element name="addr" type="Address"/>
</xs:schema>
"#;
let outer = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:t="urn:test"
targetNamespace="urn:test"
xmlns="urn:test"
elementFormDefault="qualified">
<xs:redefine schemaLocation="included.xsd">
<xs:complexType name="Address">
<xs:complexContent>
<xs:extension base="t:Address">
<xs:sequence>
<xs:element name="country" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:redefine>
</xs:schema>
"#;
let resolver = super::super::resolver::InMemoryResolver::new()
.with("included.xsd", included.as_bytes().to_vec());
let schema = Schema::compile_with(outer, resolver).unwrap();
schema.validate_str(&format!(
r#"<addr {ns}><city>SF</city><country>US</country></addr>"#,
ns = instance_ns(),
)).unwrap();
let err = schema.validate_str(&format!(
r#"<addr {ns}><city>SF</city></addr>"#,
ns = instance_ns(),
)).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::MissingRequiredElement)
&& i.message.contains("country")
), "expected missing-country error, got {:?}", err.issues);
}
#[test]
fn redefine_with_no_body_acts_like_include() {
let included = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test"
xmlns="urn:test">
<xs:element name="msg" type="xs:string"/>
</xs:schema>
"#;
let outer = r#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test"
xmlns="urn:test">
<xs:redefine schemaLocation="included.xsd"/>
</xs:schema>
"#;
let resolver = super::super::resolver::InMemoryResolver::new()
.with("included.xsd", included.as_bytes().to_vec());
let schema = Schema::compile_with(outer, resolver).unwrap();
schema.validate_str(&format!(r#"<msg {}>hi</msg>"#, instance_ns())).unwrap();
}
#[test]
fn restriction_chain_composes_facets_top_down() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:simpleType name="A">
<xs:restriction base="xs:string">
<xs:maxLength value="20"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="B">
<xs:restriction base="A">
<xs:pattern value="[A-Z]+"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="C">
<xs:restriction base="B">
<xs:enumeration value="FOO"/>
<xs:enumeration value="BAR"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="v" type="C"/>
"#)).unwrap();
s.validate_str(&format!(r#"<v {}>FOO</v>"#, instance_ns())).unwrap();
s.validate_str(&format!(r#"<v {}>BAR</v>"#, instance_ns())).unwrap();
let err = s.validate_str(&format!(r#"<v {}>ABC</v>"#, instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i| i.message.contains("enumeration")),
"expected enumeration failure, got {:?}", err.issues);
let err = s.validate_str(&format!(r#"<v {}>foo</v>"#, instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i| i.message.contains("pattern")),
"expected pattern failure, got {:?}", err.issues);
}
#[test]
fn restriction_preserves_list_variety_from_base() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:simpleType name="IntList">
<xs:list itemType="xs:int"/>
</xs:simpleType>
<xs:simpleType name="ThreeInts">
<xs:restriction base="IntList">
<xs:length value="3"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="nums" type="ThreeInts"/>
"#)).unwrap();
s.validate_str(&format!(r#"<nums {}>1 2 3</nums>"#, instance_ns())).unwrap();
let err = s.validate_str(&format!(r#"<nums {}>1 2</nums>"#, instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i| i.message.contains("2 item(s)")),
"expected list-length error counting items, got {:?}", err.issues);
let err = s.validate_str(&format!(r#"<v {}>1 foo 3</v>"#, instance_ns())).unwrap_err();
assert!(!err.issues.is_empty(),
"expected validation failure for non-int item, got ok");
}
#[test]
fn xsi_type_accepts_derived_complex_type() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="Address">
<xs:sequence>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="USAddress">
<xs:complexContent>
<xs:extension base="Address">
<xs:sequence>
<xs:element name="state" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="addr" type="Address"/>
"#)).unwrap();
let ok = format!(
r#"<addr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="USAddress" {ns}>
<city>SF</city>
<state>CA</state>
</addr>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
}
#[test]
fn extension_three_level_chain_merges_all_levels() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="A">
<xs:sequence>
<xs:element name="a" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="B">
<xs:complexContent>
<xs:extension base="A">
<xs:sequence>
<xs:element name="b" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="C">
<xs:complexContent>
<xs:extension base="B">
<xs:sequence>
<xs:element name="c" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="root" type="C"/>
"#)).unwrap();
let ok = format!(
r#"<root {}>
<a>x</a><b>y</b><c>z</c>
</root>"#,
instance_ns(),
);
s.validate_str(&ok).unwrap();
}
#[test]
fn extension_merges_attributes() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="Tagged">
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="TaggedNamed">
<xs:complexContent>
<xs:extension base="Tagged">
<xs:attribute name="name" type="xs:string" use="required"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="item" type="TaggedNamed"/>
"#)).unwrap();
s.validate_str(&format!(r#"<item id="x" name="y" {}/>"#, instance_ns())).unwrap();
let err = s.validate_str(&format!(r#"<item name="y" {}/>"#, instance_ns())).unwrap_err();
assert!(err.issues.iter().any(|i| i.message.contains("missing required attribute")
&& i.message.contains("id")
), "expected missing-id error, got {:?}", err.issues);
}
#[test]
fn xsi_type_rejects_unrelated_complex_type() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="Address">
<xs:sequence>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Person">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:element name="addr" type="Address"/>
"#)).unwrap();
let bad = format!(
r#"<addr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="Person" {ns}>
<name>alice</name>
</addr>"#,
ns = instance_ns(),
);
let err = s.validate_str(&bad).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::TypeMismatch)
&& i.message.contains("does not derive from")
), "expected derivation-failure error, got {:?}", err.issues);
}
#[test]
fn xsi_type_accepts_identity_no_op() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="Address">
<xs:sequence>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:element name="addr" type="Address"/>
"#)).unwrap();
let ok = format!(
r#"<addr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="Address" {ns}>
<city>SF</city>
</addr>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
}
#[test]
fn xsi_type_blocked_by_element_block_extension() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="Address">
<xs:sequence>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="USAddress">
<xs:complexContent>
<xs:extension base="Address">
<xs:sequence>
<xs:element name="state" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="addr" type="Address" block="extension"/>
"#)).unwrap();
let bad = format!(
r#"<addr xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="USAddress" {ns}>
<city>SF</city>
<state>CA</state>
</addr>"#,
ns = instance_ns(),
);
let err = s.validate_str(&bad).unwrap_err();
assert!(err.issues.iter().any(|i|
matches!(i.kind, ValidationKind::TypeMismatch)
&& i.message.contains("blocked")
), "expected block= rejection, got {:?}", err.issues);
}
#[test]
fn xsi_type_blocked_by_base_type_final_extension() {
let result = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="Address" final="extension">
<xs:sequence>
<xs:element name="city" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="USAddress">
<xs:complexContent>
<xs:extension base="Address">
<xs:sequence>
<xs:element name="state" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="addr" type="Address"/>
"#));
let err = result.expect_err("schema must not compile");
assert!(
err.message.contains("final") && err.message.contains("extension"),
"expected diagnostic mentioning final/extension, got: {}",
err.message,
);
}
#[test]
fn xsi_type_two_level_chain_derivation_check_accepts() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:complexType name="A">
<xs:sequence>
<xs:element name="a" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="B">
<xs:complexContent>
<xs:extension base="A">
<xs:sequence>
<xs:element name="b" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="C">
<xs:complexContent>
<xs:extension base="B">
<xs:sequence>
<xs:element name="c" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="root" type="A" nillable="true"/>
"#)).unwrap();
let ok = format!(
r#"<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="C" xsi:nil="true" {ns}/>"#,
ns = instance_ns(),
);
s.validate_str(&ok).unwrap();
}
#[test]
fn xs_list_of_int_validates_each_item() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:simpleType name="IntList">
<xs:list itemType="xs:int"/>
</xs:simpleType>
<xs:element name="nums" type="IntList"/>
"#)).unwrap();
s.validate_str(&format!(r#"<nums {}>1 2 3</nums>"#, instance_ns())).unwrap();
s.validate_str(&format!(r#"<nums {}></nums>"#, instance_ns())).unwrap();
let err = s.validate_str(&format!(r#"<nums {}>1 foo 3</nums>"#, instance_ns())).unwrap_err();
assert!(!err.issues.is_empty(),
"expected at least one issue for 'foo' as int, got {:?}", err.issues);
}
#[test]
fn xs_list_length_facet_counts_items_not_chars() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:simpleType name="IntList">
<xs:list itemType="xs:int"/>
</xs:simpleType>
<xs:simpleType name="ThreeInts">
<xs:restriction base="IntList">
<xs:length value="3"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="nums" type="ThreeInts"/>
"#)).unwrap();
s.validate_str(&format!(r#"<nums {}>1 2 3</nums>"#, instance_ns())).unwrap();
let too_few = s.validate_str(&format!(r#"<nums {}>1 2</nums>"#, instance_ns())).unwrap_err();
assert!(too_few.issues.iter().any(|i| i.message.contains("length")),
"expected length-facet error for 2 items, got {:?}", too_few.issues);
let too_many = s.validate_str(&format!(r#"<nums {}>1 2 3 4</nums>"#, instance_ns())).unwrap_err();
assert!(too_many.issues.iter().any(|i| i.message.contains("length")),
"expected length-facet error for 4 items, got {:?}", too_many.issues);
}
#[test]
fn xs_union_accepts_any_member_type() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:simpleType name="IntOrDate">
<xs:union memberTypes="xs:int xs:date"/>
</xs:simpleType>
<xs:element name="val" type="IntOrDate"/>
"#)).unwrap();
s.validate_str(&format!(r#"<val {}>42</val>"#, instance_ns())).unwrap();
s.validate_str(&format!(r#"<val {}>2026-05-16</val>"#, instance_ns())).unwrap();
let err = s.validate_str(&format!(r#"<val {}>nonsense</val>"#, instance_ns())).unwrap_err();
assert!(!err.issues.is_empty(),
"expected union-failure, got {:?}", err.issues);
}
#[test]
fn xs_list_facet_length_unit_test() {
use super::super::types::{SimpleType, Variety};
use super::super::facets::{Facet, FacetSet};
let mut list_facets = FacetSet::default();
list_facets.push(Facet::Length(3));
let three_ints = SimpleType {
name: Some("ThreeInts".into()),
builtin: BuiltinType::String,
facets: list_facets,
whitespace: super::super::whitespace::WhitespaceMode::Collapse,
variety: Variety::List {
item_type: Arc::new(SimpleType::of_builtin(BuiltinType::Int)),
},
final_: super::super::schema::BlockSet::default(),
assertions: Vec::new(),
};
three_ints.validate("1 2 3").unwrap();
let err = three_ints.validate("1 2").unwrap_err();
assert!(err.message.contains("length") && err.message.contains("2 item(s)"),
"expected list length error mentioning item count, got {}", err.message);
let err = three_ints.validate("1 2 3 4").unwrap_err();
assert!(err.message.contains("length") && err.message.contains("4 item(s)"),
"expected list length error mentioning item count, got {}", err.message);
let err = three_ints.validate("1 foo 3").unwrap_err();
assert!(err.message.contains("list item #2"),
"expected list-item error citing position, got {}", err.message);
}
#[test]
fn xs_union_with_nested_simpletypes() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:simpleType name="SmallOrBig">
<xs:union>
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:maxInclusive value="10"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="1000"/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
<xs:element name="n" type="SmallOrBig"/>
"#)).unwrap();
s.validate_str(&format!(r#"<n {}>5</n>"#, instance_ns())).unwrap();
s.validate_str(&format!(r#"<n {}>2000</n>"#, instance_ns())).unwrap();
let err = s.validate_str(&format!(r#"<n {}>500</n>"#, instance_ns())).unwrap_err();
assert!(!err.issues.is_empty(),
"expected union-failure for 500 (between 10 and 1000), got {:?}", err.issues);
}
#[test]
fn issue_line_for_duplicate_key_points_at_constraint_declaring_element() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="users">
<xs:complexType>
<xs:sequence>
<xs:element name="user" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name="userKey">
<xs:selector xpath=".//user"/>
<xs:field xpath="@id"/>
</xs:key>
</xs:element>
"#)).unwrap();
let bad = format!(
"\n\n<users {}>\n <user id=\"A\"/>\n <user id=\"A\"/>\n</users>",
instance_ns()
);
let err = s.validate_str(&bad).unwrap_err();
let issue = err.issues.iter()
.find(|i| matches!(i.kind, ValidationKind::KeyNotUnique))
.expect("expected KeyNotUnique error");
assert_eq!(issue.line, Some(3),
"expected <users> (declaring element) on line 3, got {issue:?}");
}
#[test]
fn validate_doc_typed_records_governing_types() {
let s = Schema::compile_str(&xsd_str(r#"
<xs:element name="root">
<xs:complexType>
<xs:sequence>
<xs:element name="count" type="xs:integer"/>
<xs:element name="label" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
"#)).unwrap();
let mut opts = crate::ParseOptions::default();
opts.namespace_aware = true;
let doc = crate::parse_str(
&format!(r#"<root {}><count>3</count><label>hi</label></root>"#, instance_ns()),
&opts,
).unwrap();
let (res, psvi) = s.validate_doc_typed(&doc);
assert!(res.is_ok(), "expected valid doc, got {res:?}");
assert!(!psvi.is_empty(), "expected recorded type annotations");
let root = doc.root();
assert!(psvi.governing_type(root).is_some(), "root should be typed");
for child in root.children().filter(|n|
matches!(n.kind, sup_xml_tree::dom::NodeKind::Element))
{
let ty = psvi.governing_type(child)
.unwrap_or_else(|| panic!("{} should be typed", child.name()));
let TypeRef::Simple(st) = ty else { panic!("expected simple type") };
match child.name() {
"count" => assert_eq!(st.builtin, BuiltinType::Integer),
"label" => assert_eq!(st.builtin, BuiltinType::String),
other => panic!("unexpected child {other}"),
}
}
}
}