use std::collections::HashMap;
use std::sync::Arc;
use super::error::SchemaCompileError;
use super::schema::{
ContentModel, GroupKind, MaxOccurs, NamespaceConstraint, Particle, ProcessContents,
QName, Term, Wildcard,
};
pub type StateId = u32;
#[derive(Debug)]
pub struct Dfa {
pub states: Vec<DfaState>,
pub initial: StateId,
pub defined_siblings: Arc<[QName]>,
}
#[derive(Debug, Default)]
pub struct DfaState {
pub accept: bool,
pub on_element: Vec<ElementTransition>,
pub on_wildcard: Vec<WildcardTransition>,
}
#[derive(Debug, Clone)]
pub struct WildcardTransition {
pub wc: Wildcard,
pub next: StateId,
pub synthetic: bool,
}
#[derive(Debug, Clone)]
pub struct ElementTransition {
pub name: QName,
pub next: StateId,
pub decl: Arc<super::schema::ElementDecl>,
}
impl Dfa {
pub fn step(
&self,
state: StateId,
qn: &QName,
mut wildcard_admits: impl FnMut(&Wildcard, &QName) -> bool,
) -> Option<DfaTransition> {
let s = &self.states[state as usize];
for t in &s.on_element {
if &t.name == qn {
return Some(DfaTransition::Element {
next: t.next,
decl: t.decl.clone(),
});
}
}
for t in &s.on_wildcard {
if wildcard_admits(&t.wc, qn) {
return Some(DfaTransition::Wildcard {
next: t.next,
process_contents: t.wc.process_contents,
});
}
}
None
}
pub fn is_accept(&self, state: StateId) -> bool {
self.states[state as usize].accept
}
}
#[derive(Debug)]
pub enum DfaTransition {
Element { next: StateId, decl: Arc<super::schema::ElementDecl> },
Wildcard { next: StateId, process_contents: ProcessContents },
}
#[derive(Debug)]
pub enum ContentMatcher {
Dfa(Arc<Dfa>),
All,
None,
}
pub(super) fn derivation_methods_between(
child: &super::schema::TypeRef,
ancestor: &super::schema::TypeRef,
types: &HashMap<QName, super::schema::TypeRef>,
) -> Option<super::schema::BlockSet> {
use super::schema::{BlockSet, TypeRef};
use super::types::DerivationMethod;
fn resolve(tr: &TypeRef, types: &HashMap<QName, TypeRef>) -> TypeRef {
if let TypeRef::Simple(st) = tr {
if let Some(name) = &st.name {
if let Some(rest) = name.strip_prefix("UNRESOLVED:") {
let qn = if let Some(rest) = rest.strip_prefix('{') {
if let Some(end) = rest.find('}') {
let ns = &rest[..end];
let local = &rest[end + 1..];
QName::new(if ns.is_empty() { None } else { Some(ns) }, local)
} else { QName::new(None, rest) }
} else { QName::new(None, rest) };
if let Some(real) = types.get(&qn) { return real.clone(); }
}
}
}
tr.clone()
}
fn eq(a: &TypeRef, b: &TypeRef) -> bool {
match (a, b) {
(TypeRef::Simple(x), TypeRef::Simple(y)) => Arc::ptr_eq(x, y) || x.name == y.name,
(TypeRef::Complex(x), TypeRef::Complex(y)) => Arc::ptr_eq(x, y) || x.name == y.name,
_ => false,
}
}
fn is_any_type(tr: &TypeRef) -> bool {
match tr {
TypeRef::Complex(c) => c.name.as_ref().map(|n|
n.namespace.as_deref() == Some(QName::XSD_NS) && &*n.local == "anyType"
).unwrap_or(false),
_ => false,
}
}
let child = resolve(child, types);
let ancestor = resolve(ancestor, types);
if eq(&child, &ancestor) { return Some(BlockSet::empty()); }
if is_any_type(&ancestor) {
return Some(BlockSet::RESTRICTION | BlockSet::EXTENSION);
}
if let TypeRef::Complex(ct) = &child {
let mut methods = BlockSet::empty();
let mut cur: Arc<super::types::ComplexType> = ct.clone();
for _ in 0..64 {
let d = match cur.derivation.as_ref() {
Some(d) => d,
None => return if is_any_type(&ancestor) {
Some(methods | BlockSet::EXTENSION)
} else { None },
};
methods |= match d.method {
DerivationMethod::Restriction => BlockSet::RESTRICTION,
DerivationMethod::Extension => BlockSet::EXTENSION,
};
let base = resolve(&d.base, types);
if eq(&base, &ancestor) { return Some(methods); }
match base {
TypeRef::Complex(next) => { cur = next; }
TypeRef::Simple(_) => return None,
}
}
None
} else if let (TypeRef::Simple(c_st), TypeRef::Simple(a_st)) = (&child, &ancestor) {
use super::types::Variety;
fn is_any_simple_type(st: &super::types::SimpleType) -> bool {
match st.name.as_deref() {
Some("anySimpleType") => true,
Some(s) => s.starts_with("UNRESOLVED:") && s.ends_with("anySimpleType"),
None => false,
}
}
if is_any_simple_type(a_st) {
return Some(super::schema::BlockSet::RESTRICTION);
}
match (&c_st.variety, &a_st.variety) {
(Variety::Atomic, Variety::Atomic) => {
if c_st.builtin.derives_from(a_st.builtin) {
Some(super::schema::BlockSet::RESTRICTION)
} else {
None
}
}
(Variety::List { .. }, _) | (Variety::Union { .. }, _) => None,
_ => None,
}
} else {
None
}
}
fn resolve_typeref_via_types(
tr: &super::schema::TypeRef,
types: &HashMap<QName, super::schema::TypeRef>,
) -> super::schema::TypeRef {
use super::schema::TypeRef;
if let TypeRef::Simple(st) = tr {
if let Some(name) = &st.name {
if let Some(rest) = name.strip_prefix("UNRESOLVED:") {
let qn = if let Some(rest) = rest.strip_prefix('{') {
if let Some(end) = rest.find('}') {
let ns = &rest[..end];
let local = &rest[end + 1..];
QName::new(if ns.is_empty() { None } else { Some(ns) }, local)
} else { QName::new(None, rest) }
} else { QName::new(None, rest) };
if let Some(real) = types.get(&qn) { return real.clone(); }
}
}
}
tr.clone()
}
pub fn build_matcher(
cm: &ContentModel,
substitutions: &HashMap<QName, Vec<Arc<super::schema::ElementDecl>>>,
types: &HashMap<QName, super::schema::TypeRef>,
) -> Result<ContentMatcher, SchemaCompileError> {
build_matcher_with_target_ns(cm, substitutions, types, None)
}
pub fn build_matcher_with_target_ns(
cm: &ContentModel,
substitutions: &HashMap<QName, Vec<Arc<super::schema::ElementDecl>>>,
types: &HashMap<QName, super::schema::TypeRef>,
target_ns: Option<&str>,
) -> Result<ContentMatcher, SchemaCompileError> {
match cm {
ContentModel::Empty | ContentModel::Simple(_) => Ok(ContentMatcher::None),
ContentModel::Complex { root, .. } => {
if let Term::Group { kind: GroupKind::All, .. } = &root.term {
return Ok(ContentMatcher::All);
}
let defined_siblings = collect_defined_siblings(root);
let mut b = DfaBuilder::new(defined_siblings.clone(),
target_ns.map(|s| Arc::<str>::from(s)));
let frag = b.compile_particle(root, substitutions, types)?;
let initial = b.merge_into_initial(&frag)?;
for &x in &frag.exits {
b.states[x as usize].accept = true;
}
if frag.entries.iter().any(|e| frag.exits.contains(e)) {
b.states[initial as usize].accept = true;
}
Ok(ContentMatcher::Dfa(Arc::new(Dfa {
states: b.states,
initial,
defined_siblings,
})))
}
}
}
struct Fragment {
entries: Vec<StateId>,
exits: Vec<StateId>,
}
struct DfaBuilder {
states: Vec<DfaState>,
defined_siblings: Arc<[QName]>,
target_ns: Option<Arc<str>>,
}
impl DfaBuilder {
fn new(defined_siblings: Arc<[QName]>, target_ns: Option<Arc<str>>) -> Self {
Self { states: vec![DfaState::default()], defined_siblings, target_ns }
}
fn new_state(&mut self) -> StateId {
let id = self.states.len() as StateId;
self.states.push(DfaState::default());
id
}
fn add_element(
&mut self,
from: StateId,
name: QName,
to: StateId,
decl: Arc<super::schema::ElementDecl>,
) -> Result<(), SchemaCompileError> {
let s = &mut self.states[from as usize];
if let Some(existing) = s.on_element.iter().find(|t| t.name == name) {
if Arc::ptr_eq(&existing.decl, &decl) {
return Ok(());
}
return Err(SchemaCompileError::msg(format!(
"Unique Particle Attribution violation: element <{name}> reachable two ways from the same content-model position"
)));
}
let target_ns = self.target_ns.as_deref();
for t in &s.on_wildcard {
if t.synthetic { continue; }
if t.next == to { continue; }
if wildcard_might_admit(&t.wc, &name, &self.defined_siblings, target_ns) {
return Err(SchemaCompileError::msg(format!(
"Unique Particle Attribution violation: element <{name}> \
is also admitted by a wildcard reachable from the same \
content-model position"
)));
}
}
s.on_element.push(ElementTransition { name, next: to, decl });
Ok(())
}
fn add_wildcard_inner(&mut self, from: StateId, wc: Wildcard, to: StateId, synthetic: bool)
-> Result<(), SchemaCompileError>
{
if synthetic {
self.states[from as usize].on_wildcard.push(WildcardTransition {
wc, next: to, synthetic,
});
return Ok(());
}
self.add_wildcard_checked(from, wc, to)
}
fn add_wildcard(&mut self, from: StateId, wc: Wildcard, to: StateId)
-> Result<(), SchemaCompileError>
{
self.add_wildcard_inner(from, wc, to, false)
}
fn add_wildcard_checked(&mut self, from: StateId, wc: Wildcard, to: StateId)
-> Result<(), SchemaCompileError>
{
let target_ns = self.target_ns.as_deref();
for t in &self.states[from as usize].on_wildcard {
if t.synthetic { continue; }
if t.next == to { continue; }
if wildcards_overlap(&t.wc, &wc, target_ns) {
return Err(SchemaCompileError::msg(
"Unique Particle Attribution violation: two wildcards reachable \
from the same content-model position have overlapping namespace \
constraints and attribute to different particles"
));
}
}
for t in &self.states[from as usize].on_element {
if t.next == to { continue; }
if wildcard_might_admit(&wc, &t.name, &self.defined_siblings, target_ns) {
return Err(SchemaCompileError::msg(format!(
"Unique Particle Attribution violation: wildcard admits \
element <{}> which is also reachable as a named transition \
from the same content-model position",
t.name,
)));
}
}
self.states[from as usize].on_wildcard.push(WildcardTransition {
wc, next: to, synthetic: false,
});
Ok(())
}
fn copy_outgoing(&mut self, src: StateId, dst: StateId)
-> Result<(), SchemaCompileError>
{
let src_state = &self.states[src as usize];
let elems = src_state.on_element.clone();
let wilds = src_state.on_wildcard.clone();
let src_accept = src_state.accept;
for t in elems {
self.add_element(dst, t.name, t.next, t.decl)?;
}
for t in wilds {
self.add_wildcard_inner(dst, t.wc, t.next, t.synthetic)?;
}
if src_accept {
self.states[dst as usize].accept = true;
}
Ok(())
}
fn merge_into_initial(&mut self, frag: &Fragment)
-> Result<StateId, SchemaCompileError>
{
let initial = 0; for &e in &frag.entries {
self.copy_outgoing(e, initial)?;
}
Ok(initial)
}
fn compile_particle(
&mut self,
p: &Particle,
subs: &HashMap<QName, Vec<Arc<super::schema::ElementDecl>>>,
types: &HashMap<QName, super::schema::TypeRef>,
) -> Result<Fragment, SchemaCompileError> {
if matches!(p.max_occurs, MaxOccurs::Bounded(0)) {
let s = self.new_state();
return Ok(Fragment { entries: vec![s], exits: vec![s] });
}
match &p.term {
Term::Element(decl) => {
let mut targets: Vec<(QName, Arc<super::schema::ElementDecl>)> = Vec::new();
targets.push((decl.name.clone(), decl.clone()));
let blocks_substitution = decl.block
.contains(super::schema::BlockSet::SUBSTITUTION);
if !blocks_substitution
&& let Some(subs_list) = subs.get(&decl.name)
{
let head_type_block: super::schema::BlockSet =
match resolve_typeref_via_types(&decl.type_def, types) {
super::schema::TypeRef::Complex(ct) => ct.block,
_ => super::schema::BlockSet::empty(),
};
let blocked_methods = (decl.block | head_type_block) & (
super::schema::BlockSet::RESTRICTION
| super::schema::BlockSet::EXTENSION
);
for sub in subs_list {
if !blocked_methods.is_empty() {
if let Some(used) = derivation_methods_between(
&sub.type_def, &decl.type_def, types,
) {
if !(used & blocked_methods).is_empty() {
continue;
}
}
}
targets.push((sub.name.clone(), sub.clone()));
}
}
self.compile_repeated(p.min_occurs, p.max_occurs,
|b, from, to| {
for (n, d) in &targets {
b.add_element(from, n.clone(), to, d.clone())?;
}
Ok(())
})
}
Term::Wildcard(wc) => {
let wc = wc.clone();
self.compile_repeated(p.min_occurs, p.max_occurs,
|b, from, to| b.add_wildcard(from, wc.clone(), to))
}
Term::Group { kind, particles } => {
if matches!(p.max_occurs, MaxOccurs::Unbounded)
&& p.min_occurs > 1
&& particles.len() == 1
&& matches!(kind, GroupKind::Sequence | GroupKind::Choice)
{
let inner = &particles[0];
let inner_min = inner.min_occurs.saturating_mul(p.min_occurs);
let collapsed = Particle {
term: inner.term.clone(),
min_occurs: inner_min,
max_occurs: MaxOccurs::Unbounded,
};
return self.compile_particle(&collapsed, subs, types);
}
let build_inner = |this: &mut Self| -> Result<Fragment, SchemaCompileError> {
match kind {
GroupKind::Sequence => this.compile_sequence(particles, subs, types),
GroupKind::Choice => this.compile_choice(particles, subs, types),
GroupKind::All => Err(SchemaCompileError::msg(
"nested xs:all is not supported in v1 (use sequence/choice)"
)),
}
};
self.repeat_group(p.min_occurs, p.max_occurs, build_inner)
}
Term::GroupRef(_name) => {
let _ = p.min_occurs;
let _ = p.max_occurs;
let wc = Wildcard {
namespaces: NamespaceConstraint::Any,
process_contents: ProcessContents::Lax,
not_qnames: Vec::new(),
not_namespaces: Vec::new(),
not_qname_defined: false,
not_qname_defined_sibling: false,
};
self.compile_repeated(0, MaxOccurs::Unbounded,
|b, from, to| b.add_wildcard_inner(from, wc.clone(), to, true))
}
}
}
fn compile_repeated<F>(
&mut self,
min: u32,
max: MaxOccurs,
mut add: F,
) -> Result<Fragment, SchemaCompileError>
where
F: FnMut(&mut Self, StateId, StateId) -> Result<(), SchemaCompileError>,
{
const LARGE_BOUND: u32 = 128;
let treat_as_unbounded = matches!(max, MaxOccurs::Unbounded)
|| matches!(max, MaxOccurs::Bounded(n) if n > LARGE_BOUND);
let max_n: u32 = match max {
MaxOccurs::Unbounded => u32::MAX, MaxOccurs::Bounded(n) => n,
};
let chain_len = if treat_as_unbounded {
min.max(1)
} else {
max_n
};
let chain_len = chain_len.min(LARGE_BOUND);
let mut state_ids = Vec::with_capacity(chain_len as usize + 1);
for _ in 0..=chain_len {
state_ids.push(self.new_state());
}
for i in 0..chain_len as usize {
add(self, state_ids[i], state_ids[i + 1])?;
}
if treat_as_unbounded {
let last = *state_ids.last().unwrap();
add(self, last, last)?;
}
let entries = vec![state_ids[0]];
let exits = if min == 0 {
let mut e = vec![state_ids[0]];
e.extend_from_slice(&state_ids[1..]);
e
} else {
state_ids[(min as usize).min(state_ids.len() - 1)..].to_vec()
};
Ok(Fragment { entries, exits })
}
fn compile_sequence(
&mut self,
particles: &[Particle],
subs: &HashMap<QName, Vec<Arc<super::schema::ElementDecl>>>,
types: &HashMap<QName, super::schema::TypeRef>,
) -> Result<Fragment, SchemaCompileError> {
if particles.is_empty() {
let s = self.new_state();
return Ok(Fragment { entries: vec![s], exits: vec![s] });
}
let first = self.compile_particle(&particles[0], subs, types)?;
if particles.len() == 1 {
return Ok(first);
}
let mut current = first;
for p in &particles[1..] {
let next = self.compile_particle(p, subs, types)?;
current = self.concat(current, next)?;
}
Ok(current)
}
fn concat(&mut self, a: Fragment, b: Fragment)
-> Result<Fragment, SchemaCompileError>
{
for &exit in &a.exits {
for &entry in &b.entries {
self.copy_outgoing(entry, exit)?;
}
}
let entries = a.entries.clone();
let mut exits = b.exits.clone();
if b.entries.iter().any(|e| b.exits.contains(e)) {
for e in &a.exits {
if !exits.contains(e) { exits.push(*e); }
}
}
Ok(Fragment { entries, exits })
}
fn compile_choice(
&mut self,
particles: &[Particle],
subs: &HashMap<QName, Vec<Arc<super::schema::ElementDecl>>>,
types: &HashMap<QName, super::schema::TypeRef>,
) -> Result<Fragment, SchemaCompileError> {
if particles.is_empty() {
let s = self.new_state();
return Ok(Fragment { entries: vec![s], exits: vec![s] });
}
let mut entries = Vec::new();
let mut exits = Vec::new();
for p in particles {
let frag = self.compile_particle(p, subs, types)?;
entries.extend(frag.entries);
exits.extend(frag.exits);
}
Ok(Fragment { entries, exits })
}
fn repeat_group<F>(
&mut self,
min: u32,
max: MaxOccurs,
mut build_inner: F,
) -> Result<Fragment, SchemaCompileError>
where
F: FnMut(&mut Self) -> Result<Fragment, SchemaCompileError>,
{
if min <= 1 {
let inner = build_inner(self)?;
return self.repeat_fragment(inner, min, max);
}
let required = (min as usize).min(8);
let mut current = build_inner(self)?;
let mut last_entries = current.entries.clone();
for _ in 1..required {
let next = build_inner(self)?;
last_entries = next.entries.clone();
current = self.concat(current, next)?;
}
let _ = max;
let pseudo = Fragment { entries: last_entries, exits: current.exits.clone() };
self.add_loopback_transitions(&pseudo)?;
Ok(current)
}
fn repeat_fragment(
&mut self,
frag: Fragment,
min: u32,
max: MaxOccurs,
) -> Result<Fragment, SchemaCompileError> {
if min == 1 && matches!(max, MaxOccurs::Bounded(1)) {
return Ok(frag);
}
if min == 0 && matches!(max, MaxOccurs::Bounded(1)) {
let mut frag = frag;
for &e in &frag.entries.clone() {
if !frag.exits.contains(&e) {
frag.exits.push(e);
}
}
return Ok(frag);
}
if matches!(max, MaxOccurs::Unbounded) {
let mut frag = frag;
self.add_loopback_transitions(&frag)?;
if min == 0 {
for &e in &frag.entries.clone() {
if !frag.exits.contains(&e) {
frag.exits.push(e);
}
}
}
return Ok(frag);
}
let mut frag = frag;
self.add_loopback_transitions(&frag)?;
if min == 0 {
for &e in &frag.entries.clone() {
if !frag.exits.contains(&e) {
frag.exits.push(e);
}
}
}
Ok(frag)
}
fn add_loopback_transitions(&mut self, frag: &Fragment) -> Result<(), SchemaCompileError> {
for &exit in &frag.exits {
for &entry in &frag.entries {
if exit == entry { continue; }
let entry_elems = self.states[entry as usize].on_element.clone();
let entry_wilds = self.states[entry as usize].on_wildcard.clone();
let exit_state = &mut self.states[exit as usize];
for t in entry_elems {
if !exit_state.on_element.iter().any(|e| e.name == t.name) {
exit_state.on_element.push(t);
}
}
for t in entry_wilds {
let already_present = exit_state.on_wildcard.iter()
.any(|other| std::ptr::eq(&other.wc as *const _, &t.wc as *const _));
if !already_present {
exit_state.on_wildcard.push(t);
}
}
}
}
Ok(())
}
}
fn wildcard_might_admit(
wc: &Wildcard,
qn: &QName,
defined_siblings: &[QName],
target_ns: Option<&str>,
) -> bool {
use NamespaceConstraint::*;
if !qn.local.is_empty() {
for forbidden in &wc.not_qnames {
if forbidden.namespace == qn.namespace && forbidden.local == qn.local {
return false;
}
}
}
if wc.not_qname_defined_sibling && defined_siblings.iter().any(|s| s == qn) {
return false;
}
let ns = qn.namespace.as_deref();
match &wc.namespaces {
Any => true,
Other => ns.is_some() && ns != target_ns,
List(allowed) => allowed.iter().any(|item| match (item.as_deref(), ns) {
(None, None) => true,
(Some(a), Some(b)) => a == b,
_ => false,
}),
}
}
fn wildcards_overlap(a: &Wildcard, b: &Wildcard, target_ns: Option<&str>) -> bool {
use NamespaceConstraint::*;
fn list_admits_other(list: &[Option<Arc<str>>], target_ns: Option<&str>) -> bool {
list.iter().any(|entry| match entry.as_deref() {
None => false,
Some(ns) => target_ns != Some(ns),
})
}
match (&a.namespaces, &b.namespaces) {
(Any, _) | (_, Any) => true,
(Other, Other) => true,
(Other, List(l)) | (List(l), Other) => list_admits_other(l, target_ns),
(List(la), List(lb)) => la.iter().any(|x| lb.iter().any(|y| x == y)),
}
}
pub(super) fn wildcard_admits(
wc: &Wildcard,
qn: &QName,
target_ns: Option<&str>,
is_defined: impl FnOnce(&QName) -> bool,
is_sibling: impl FnOnce(&QName) -> bool,
) -> bool {
let ns = qn.namespace.as_deref();
let ns_ok = match &wc.namespaces {
NamespaceConstraint::Any => true,
NamespaceConstraint::Other => ns != target_ns && ns.is_some(),
NamespaceConstraint::List(allowed) => allowed.iter().any(|item| {
match (item.as_deref(), ns) {
(None, None) => true,
(Some(a), Some(b)) => a == b,
_ => false,
}
}),
};
if !ns_ok { return false; }
for item in &wc.not_namespaces {
match (item.as_deref(), ns) {
(None, None) => return false,
(Some(a), Some(b)) if a == b => return false,
_ => {}
}
}
if !qn.local.is_empty() {
for forbidden in &wc.not_qnames {
if forbidden.namespace == qn.namespace && forbidden.local == qn.local {
return false;
}
}
}
if wc.not_qname_defined && is_defined(qn) {
return false;
}
if wc.not_qname_defined_sibling && is_sibling(qn) {
return false;
}
true
}
fn collect_defined_siblings(root: &Particle) -> Arc<[QName]> {
let mut out: Vec<QName> = Vec::new();
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(_) => {}
}
}
walk(root, &mut out);
out.sort_by(|a, b| (a.namespace.as_deref(), a.local.as_ref())
.cmp(&(b.namespace.as_deref(), b.local.as_ref())));
out.into()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::xsd::schema::{ElementDecl, BlockSet};
use crate::xsd::types::SimpleType;
use crate::xsd::BuiltinType;
fn elem_decl(name: &str) -> Arc<ElementDecl> {
Arc::new(ElementDecl {
name: QName::new(None, name),
type_def: super::super::TypeRef::Simple(Arc::new(SimpleType::of_builtin(BuiltinType::String))),
nillable: false,
default: None, fixed: None,
abstract_: false, substitution_group: None,
block: BlockSet::default(), final_: BlockSet::default(),
identity: Vec::new(),
})
}
fn one_of(name: &str) -> Particle {
Particle {
min_occurs: 1, max_occurs: MaxOccurs::Bounded(1),
term: Term::Element(elem_decl(name)),
}
}
fn unbounded(name: &str) -> Particle {
Particle {
min_occurs: 1, max_occurs: MaxOccurs::Unbounded,
term: Term::Element(elem_decl(name)),
}
}
fn optional(name: &str) -> Particle {
Particle {
min_occurs: 0, max_occurs: MaxOccurs::Bounded(1),
term: Term::Element(elem_decl(name)),
}
}
fn sequence(particles: Vec<Particle>) -> ContentModel {
ContentModel::Complex {
root: Particle {
min_occurs: 1, max_occurs: MaxOccurs::Bounded(1),
term: Term::Group { kind: GroupKind::Sequence, particles: particles.into() },
},
mixed: false,
}
}
fn choice(particles: Vec<Particle>) -> ContentModel {
ContentModel::Complex {
root: Particle {
min_occurs: 1, max_occurs: MaxOccurs::Bounded(1),
term: Term::Group { kind: GroupKind::Choice, particles: particles.into() },
},
mixed: false,
}
}
fn matcher(cm: &ContentModel) -> Arc<Dfa> {
match build_matcher(cm, &HashMap::new(), &HashMap::new()).unwrap() {
ContentMatcher::Dfa(d) => d,
_ => panic!("expected DFA"),
}
}
fn run<'a>(dfa: &Dfa, names: impl IntoIterator<Item = &'a str>) -> Option<bool> {
let mut s = dfa.initial;
for n in names {
let qn = QName::new(None, n);
let step = dfa.step(s, &qn, |wc, qn| {
wildcard_admits(wc, qn, None, |_| false, |q| {
dfa.defined_siblings.iter().any(|n| n == q)
})
});
match step {
Some(DfaTransition::Element { next, .. }) => s = next,
Some(DfaTransition::Wildcard { next, .. }) => s = next,
None => return None,
}
}
Some(dfa.is_accept(s))
}
#[test]
fn sequence_single_element() {
let cm = sequence(vec![one_of("a")]);
let dfa = matcher(&cm);
assert_eq!(run(&dfa, ["a"]), Some(true));
assert_eq!(run(&dfa, []), Some(false)); assert_eq!(run(&dfa, ["a", "a"]), None); assert_eq!(run(&dfa, ["b"]), None); }
#[test]
fn sequence_multiple_elements() {
let cm = sequence(vec![one_of("a"), one_of("b"), one_of("c")]);
let dfa = matcher(&cm);
assert_eq!(run(&dfa, ["a", "b", "c"]), Some(true));
assert_eq!(run(&dfa, ["a", "b"]), Some(false)); assert_eq!(run(&dfa, ["a", "c"]), None); assert_eq!(run(&dfa, ["b", "a", "c"]), None); }
#[test]
fn sequence_with_unbounded() {
let cm = sequence(vec![unbounded("item")]);
let dfa = matcher(&cm);
assert_eq!(run(&dfa, ["item"]), Some(true));
assert_eq!(run(&dfa, ["item", "item", "item"]), Some(true));
assert_eq!(run(&dfa, []), Some(false)); }
#[test]
fn sequence_with_optional_tail() {
let cm = sequence(vec![one_of("a"), optional("b")]);
let dfa = matcher(&cm);
assert_eq!(run(&dfa, ["a"]), Some(true));
assert_eq!(run(&dfa, ["a", "b"]), Some(true));
assert_eq!(run(&dfa, []), Some(false));
}
#[test]
fn choice_first_branch() {
let cm = choice(vec![one_of("a"), one_of("b")]);
let dfa = matcher(&cm);
assert_eq!(run(&dfa, ["a"]), Some(true));
assert_eq!(run(&dfa, ["b"]), Some(true));
assert_eq!(run(&dfa, ["c"]), None);
assert_eq!(run(&dfa, ["a", "b"]), None); }
#[test]
fn upa_violation_is_compile_error() {
let cm = choice(vec![one_of("a"), one_of("a")]);
let r = build_matcher(&cm, &HashMap::new(), &HashMap::new());
assert!(r.is_err(), "expected UPA error");
}
}