use std::cell::{Cell, RefCell};
use std::collections::BTreeMap;
use oxml::{Document, NodeId};
use crate::datatype::WhiteSpace;
use crate::model::{
AttributeDecl, BuiltIn, Content, Facets, Identity, IdentityKind,
NamespaceConstraint, Occurs, Particle, ProcessContents, Schema, SimpleType,
Variety, Wildcard,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchemaError {
pub message: String,
}
impl std::fmt::Display for SchemaError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for SchemaError {}
#[derive(Debug, Default)]
struct Tops {
elements: BTreeMap<String, NodeId>,
attributes: BTreeMap<String, NodeId>,
groups: BTreeMap<String, NodeId>,
attribute_groups: BTreeMap<String, NodeId>,
complex_types: BTreeMap<String, NodeId>,
}
#[derive(Default)]
struct Memo {
content: BTreeMap<usize, Content>,
attributes: BTreeMap<usize, Vec<AttributeDecl>>,
}
struct Session<'a> {
doc: &'a Document,
tops: Tops,
memo: RefCell<Memo>,
budget: Cell<usize>,
}
impl Session<'_> {
fn ctx<'s>(&'s self, schema: &'s Schema) -> Ctx<'s> {
Ctx {
doc: self.doc,
schema,
tops: &self.tops,
memo: &self.memo,
budget: &self.budget,
depth: 0,
}
}
}
struct Ctx<'a> {
doc: &'a Document,
schema: &'a Schema,
tops: &'a Tops,
memo: &'a RefCell<Memo>,
depth: usize,
budget: &'a Cell<usize>,
}
impl<'a> Ctx<'a> {
fn deeper(&self) -> Option<Ctx<'a>> {
(self.depth < MAX_REFERENCE_DEPTH).then(|| Ctx {
doc: self.doc,
schema: self.schema,
tops: self.tops,
memo: self.memo,
depth: self.depth + 1,
budget: self.budget,
})
}
fn charge(&self) -> Result<(), SchemaError> {
self.charge_many(1)
}
fn charge_many(&self, n: usize) -> Result<(), SchemaError> {
let spent = self.budget.get().saturating_add(n);
self.budget.set(spent);
if spent > MAX_PARTICLES {
return err(format!(
"the schema's content models expand past {MAX_PARTICLES} \
particles; a type that references another twice doubles \
at every level"
));
}
Ok(())
}
}
const MAX_REFERENCE_DEPTH: usize = 64;
const MAX_PARTICLES: usize = 100_000;
fn err<T>(message: impl Into<String>) -> Result<T, SchemaError> {
Err(SchemaError {
message: message.into(),
})
}
pub fn parse_schema(xsd: &str) -> Result<Schema, SchemaError> {
parse_schema_with(xsd, &crate::resolve::NoSchemas)
}
pub fn parse_schema_with(
xsd: &str,
source: &dyn crate::resolve::SchemaSource,
) -> Result<Schema, SchemaError> {
parse_schema_at(xsd, source, 0, &mut Vec::new())
}
fn parse_schema_at(
xsd: &str,
source: &dyn crate::resolve::SchemaSource,
depth: usize,
visited: &mut Vec<String>,
) -> Result<Schema, SchemaError> {
let doc = oxml::parse(xsd).map_err(|e| SchemaError {
message: format!("the schema is not well-formed XML: {e}"),
})?;
let root = schema_root(&doc)?;
check_structure(&doc)?;
check_ids(&doc)?;
let tops = index_top_level(&doc, root);
check_derivation(&doc, &tops)?;
let referenced = load_referenced(&doc, root, source, depth, visited)?;
let session = Session {
doc: &doc,
tops,
memo: RefCell::new(Memo::default()),
budget: Cell::new(0),
};
let mut schema = Schema {
target_namespace: doc
.attribute(root, "targetNamespace")
.map(str::to_owned),
elements: BTreeMap::new(),
named_simple_types: BTreeMap::new(),
named_complex_types: BTreeMap::new(),
};
for other in referenced {
for (name, ty) in other.named_simple_types {
let _ = schema.named_simple_types.entry(name).or_insert(ty);
}
for (name, ty) in other.named_complex_types {
let _ = schema.named_complex_types.entry(name).or_insert(ty);
}
for (name, particle) in other.elements {
let _ = schema.elements.entry(name).or_insert(particle);
}
}
for &child in doc.children(root) {
if local_name(&doc, child) == Some("simpleType") {
if let Some(name) = doc.attribute(child, "name") {
let st = parse_simple_type(&session.ctx(&schema), child);
let _ = schema.named_simple_types.insert(name.to_owned(), st);
}
}
}
for (name, &node) in &session.tops.complex_types {
let content = parse_complex_type(&session.ctx(&schema), node)?;
let _ = schema.named_complex_types.insert(name.clone(), content);
}
for &child in doc.children(root) {
if local_name(&doc, child) == Some("element") {
let particle = parse_element(&session.ctx(&schema), child)?;
let _ = schema.elements.insert(particle.name.clone(), particle);
}
}
Ok(schema)
}
fn load_referenced(
doc: &Document,
root: NodeId,
source: &dyn crate::resolve::SchemaSource,
depth: usize,
visited: &mut Vec<String>,
) -> Result<Vec<Schema>, SchemaError> {
if depth > MAX_REFERENCE_DEPTH {
return Ok(Vec::new());
}
let mut out = Vec::new();
for &child in doc.children(root) {
if !matches!(local_name(doc, child), Some("import" | "include")) {
continue;
}
let Some(location) = doc.attribute(child, "schemaLocation") else {
continue;
};
if visited.iter().any(|seen| seen == location) {
continue;
}
visited.push(location.to_owned());
let Some(text) = source.fetch(location) else {
continue;
};
out.push(parse_schema_at(text, source, depth + 1, visited)?);
}
Ok(out)
}
fn schema_root(doc: &Document) -> Result<NodeId, SchemaError> {
let Some(root) = doc.root_element() else {
return err("the schema has no root element");
};
if local_name(doc, root) != Some("schema") {
return err("the root element must be xs:schema");
}
Ok(root)
}
fn index_top_level(doc: &Document, root: NodeId) -> Tops {
let mut tops = Tops::default();
for &child in doc.children(root) {
let (Some(kind), Some(name)) =
(local_name(doc, child), doc.attribute(child, "name"))
else {
continue;
};
let table = match kind {
"element" => &mut tops.elements,
"attribute" => &mut tops.attributes,
"group" => &mut tops.groups,
"attributeGroup" => &mut tops.attribute_groups,
"complexType" => &mut tops.complex_types,
_ => continue,
};
let _ = table.insert(name.to_owned(), child);
}
tops
}
fn check_structure(doc: &Document) -> Result<(), SchemaError> {
for id in doc.descendants() {
let Some(name) = local_name(doc, id) else {
continue;
};
let children: Vec<&str> = doc
.children(id)
.iter()
.filter_map(|&c| local_name(doc, c))
.collect();
let annotations =
children.iter().filter(|c| **c == "annotation").count();
if annotations > 1 {
return err(format!(
"xs:{name} has {annotations} xs:annotation children; \
at most one is permitted"
));
}
if annotations == 1 && children.first() != Some(&"annotation") {
return err(format!(
"xs:annotation must be the first child of xs:{name}"
));
}
let exclusive: &[&str] = match name {
"extension" | "restriction" => {
&["group", "all", "choice", "sequence"]
}
"complexType" => &[
"simpleContent",
"complexContent",
"group",
"all",
"choice",
"sequence",
],
"simpleType" => &["restriction", "list", "union"],
"element" | "attribute" => &["simpleType", "complexType"],
_ => &[],
};
let present: Vec<&&str> =
children.iter().filter(|c| exclusive.contains(c)).collect();
if present.len() > 1 {
return err(format!(
"xs:{name} has both xs:{} and xs:{}; they are mutually \
exclusive",
present[0], present[1]
));
}
if name == "simpleType" && present.is_empty() {
return err(
"xs:simpleType must contain a restriction, list or union",
);
}
if matches!(name, "element" | "attribute")
&& doc.attribute(id, "type").is_some()
&& !present.is_empty()
{
return err(format!(
"xs:{name} has both a `type` attribute and an inline type"
));
}
if name == "complexType" {
let wrapped = children
.iter()
.any(|c| matches!(*c, "simpleContent" | "complexContent"));
if wrapped {
if let Some(stray) = children.iter().find(|c| {
!matches!(
**c,
"simpleContent" | "complexContent" | "annotation"
)
}) {
return err(format!(
"xs:{stray} may not sit beside xs:simpleContent or \
xs:complexContent; it belongs inside the extension \
or restriction"
));
}
}
}
if name == "restriction" {
check_facet_values(doc, id)?;
}
if matches!(name, "complexType" | "attributeGroup" | "extension") {
check_attribute_names(doc, id)?;
}
if matches!(name, "sequence" | "choice" | "all" | "group") {
check_declarations_consistent(doc, id)?;
}
if matches!(name, "element" | "attribute")
&& doc.attribute(id, "ref").is_some()
&& doc.attribute(id, "name").is_some()
{
return err(format!("xs:{name} has both `name` and `ref`"));
}
}
Ok(())
}
fn check_derivation(doc: &Document, tops: &Tops) -> Result<(), SchemaError> {
if doc
.descendants()
.any(|id| doc.attribute(id, "substitutionGroup").is_some())
{
return Ok(());
}
let groups = |name: &str| tops.groups.get(name).copied();
let type_derives = |derived: &str, base: &str| -> bool {
if !tops.complex_types.contains_key(derived) {
return true;
}
let mut at = tops.complex_types.get(derived).copied();
for _ in 0..32 {
let Some(node) = at else { return false };
let Some(next) = ["complexContent", "simpleContent"]
.into_iter()
.find_map(|w| first_child_named(doc, node, w))
.and_then(|w| {
["extension", "restriction"]
.into_iter()
.find_map(|k| first_child_named(doc, w, k))
})
.and_then(|d| doc.attribute(d, "base"))
else {
return false;
};
let local = next.rsplit(':').next().unwrap_or(next);
if local == base {
return true;
}
at = tops.complex_types.get(local).copied();
}
false
};
for id in doc.descendants() {
if local_name(doc, id) != Some("restriction") {
continue;
}
if doc.parent(id).and_then(|p| local_name(doc, p))
!= Some("complexContent")
{
continue;
}
let Some(base_name) = doc.attribute(id, "base") else {
continue;
};
let local = base_name.rsplit(':').next().unwrap_or(base_name);
let Some(&base_node) = tops.complex_types.get(local) else {
continue;
};
let model = |host: NodeId| {
doc.children(host)
.iter()
.copied()
.find_map(|c| crate::derive::particle_of(doc, c, &groups, 0))
};
let (Some(derived), Some(base)) = (model(id), model(base_node)) else {
continue;
};
if !crate::derive::is_valid_restriction(&derived, &base, &type_derives)
{
return err(format!(
"this content model is not a valid restriction of \
`{base_name}`"
));
}
}
Ok(())
}
fn check_ids(doc: &Document) -> Result<(), SchemaError> {
let id_type = crate::datatype::Datatype::from_name("ID");
let mut seen: Vec<&str> = Vec::new();
for node in doc.descendants() {
let Some(id) = doc.attribute(node, "id") else {
continue;
};
if !id_type.is_some_and(|t| t.accepts(id)) {
return err(format!("`id=\"{id}\"` is not a valid xs:ID"));
}
if seen.contains(&id) {
return err(format!(
"`id=\"{id}\"` appears twice; the id attribute is an \
xs:ID and must be unique"
));
}
seen.push(id);
}
Ok(())
}
fn check_facet_values(doc: &Document, id: NodeId) -> Result<(), SchemaError> {
let Some(base) = doc.attribute(id, "base") else {
return Ok(());
};
let Some(datatype) = crate::datatype::Datatype::from_name(base) else {
return Ok(());
};
for &facet in doc.children(id) {
let (Some(kind), Some(value)) =
(local_name(doc, facet), doc.attribute(facet, "value"))
else {
continue;
};
let ok = match kind {
"enumeration" | "minInclusive" | "maxInclusive"
| "minExclusive" | "maxExclusive" => datatype.accepts(value),
"length" | "minLength" | "maxLength" | "totalDigits"
| "fractionDigits" => value.parse::<usize>().is_ok(),
_ => true,
};
if !ok {
return err(format!(
"xs:{kind} value `{value}` is not a valid {base}"
));
}
}
Ok(())
}
fn check_attribute_names(
doc: &Document,
id: NodeId,
) -> Result<(), SchemaError> {
let mut seen: Vec<&str> = Vec::new();
for &child in doc.children(id) {
if local_name(doc, child) != Some("attribute") {
continue;
}
let Some(name) = doc
.attribute(child, "name")
.or_else(|| doc.attribute(child, "ref"))
else {
continue;
};
let local = name.rsplit(':').next().unwrap_or(name);
if seen.contains(&local) {
return err(format!("`{local}` is declared twice on one type"));
}
seen.push(local);
}
Ok(())
}
fn check_declarations_consistent(
doc: &Document,
id: NodeId,
) -> Result<(), SchemaError> {
let mut seen: Vec<(String, String)> = Vec::new();
collect_declarations(doc, id, &mut seen);
for (i, (name, signature)) in seen.iter().enumerate() {
if let Some((_, other)) = seen[..i]
.iter()
.find(|(n, other)| n == name && other != signature)
{
return err(format!(
"`{name}` is declared twice in one content model with \
different types (`{other}` and `{signature}`)"
));
}
}
Ok(())
}
fn collect_declarations(
doc: &Document,
id: NodeId,
out: &mut Vec<(String, String)>,
) {
for &child in doc.children(id) {
match local_name(doc, child) {
Some("element") => {
if doc.attribute(child, "ref").is_some() {
continue;
}
let Some(name) = doc.attribute(child, "name") else {
continue;
};
let Some(signature) = doc.attribute(child, "type") else {
continue;
};
out.push((name.to_owned(), signature.to_owned()));
}
Some("sequence" | "choice" | "all") => {
collect_declarations(doc, child, out);
}
_ => {}
}
}
}
fn local_name(doc: &Document, id: NodeId) -> Option<&str> {
doc.element_name(id).map(|n| n.local.as_str())
}
fn children_named<'a>(
doc: &'a Document,
id: NodeId,
name: &'a str,
) -> impl Iterator<Item = NodeId> + 'a {
doc.children(id)
.iter()
.copied()
.filter(move |&c| local_name(doc, c) == Some(name))
}
fn first_child_named(doc: &Document, id: NodeId, name: &str) -> Option<NodeId> {
children_named(doc, id, name).next()
}
fn parse_occurs(doc: &Document, id: NodeId) -> Occurs {
let min = doc
.attribute(id, "minOccurs")
.and_then(|v| v.parse().ok())
.unwrap_or(1);
let max = match doc.attribute(id, "maxOccurs") {
Some("unbounded") => None,
Some(v) => v.parse().ok().or(Some(1)),
None => Some(1),
};
Occurs { min, max }
}
fn parse_element(ctx: &Ctx, id: NodeId) -> Result<Particle, SchemaError> {
let doc = ctx.doc;
if let Some(reference) = doc.attribute(id, "ref") {
let local = reference.rsplit(':').next().unwrap_or(reference);
let Some(&target) = ctx.tops.elements.get(local) else {
return Ok(unenforceable_element(local, parse_occurs(doc, id)));
};
let Some(inner) = ctx.deeper() else {
return Ok(unenforceable_element(local, parse_occurs(doc, id)));
};
let mut particle = parse_element(&inner, target)?;
particle.occurs = parse_occurs(doc, id);
return Ok(particle);
}
let Some(name) = doc.attribute(id, "name") else {
return err("an xs:element has no name");
};
let occurs = parse_occurs(doc, id);
let content = if let Some(type_name) = doc.attribute(id, "type") {
resolve_named_type(ctx, type_name)
} else if let Some(ct) = first_child_named(doc, id, "complexType") {
parse_complex_type(ctx, ct)?
} else if let Some(st) = first_child_named(doc, id, "simpleType") {
Content::Simple(Box::new(parse_simple_type(ctx, st)))
} else {
Content::Any
};
let attributes = if let Some(ct) = first_child_named(doc, id, "complexType")
{
parse_attributes(ctx, ct)?
} else if let Some(type_name) = doc.attribute(id, "type") {
let local = type_name.rsplit(':').next().unwrap_or(type_name);
match ctx.tops.complex_types.get(local) {
Some(&node) => parse_attributes(ctx, node)?,
None => Vec::new(),
}
} else {
Vec::new()
};
ctx.charge()?;
Ok(Particle {
name: name.to_owned(),
occurs,
content: Box::new(content),
attributes,
fixed: doc.attribute(id, "fixed").map(str::to_owned),
nillable: doc.attribute(id, "nillable") == Some("true"),
wildcard: None,
any_attribute: any_attribute_of(ctx, id),
identities: parse_identities(doc, id),
})
}
fn parse_identities(doc: &Document, id: NodeId) -> Vec<Identity> {
let mut out = Vec::new();
for &child in doc.children(id) {
let kind = match local_name(doc, child) {
Some("unique") => IdentityKind::Unique,
Some("key") => IdentityKind::Key,
Some("keyref") => IdentityKind::KeyRef,
_ => continue,
};
let Some(selector) = first_child_named(doc, child, "selector")
.and_then(|s| doc.attribute(s, "xpath"))
else {
continue;
};
let fields: Vec<String> = children_named(doc, child, "field")
.filter_map(|f| doc.attribute(f, "xpath").map(str::to_owned))
.collect();
if fields.is_empty() {
continue;
}
out.push(Identity {
kind,
name: doc.attribute(child, "name").unwrap_or_default().to_owned(),
selector: selector.to_owned(),
fields,
refer: doc
.attribute(child, "refer")
.map(|r| r.rsplit(':').next().unwrap_or(r).to_owned()),
});
}
out
}
fn any_attribute_of(ctx: &Ctx, id: NodeId) -> Option<Wildcard> {
let doc = ctx.doc;
let host = first_child_named(doc, id, "complexType").or_else(|| {
let name = doc.attribute(id, "type")?;
let local = name.rsplit(':').next().unwrap_or(name);
ctx.tops.complex_types.get(local).copied()
})?;
let mut places = vec![host];
for wrapper in ["complexContent", "simpleContent"] {
if let Some(w) = first_child_named(doc, host, wrapper) {
for kind in ["extension", "restriction"] {
if let Some(node) = first_child_named(doc, w, kind) {
places.push(node);
}
}
}
}
places
.into_iter()
.find_map(|p| first_child_named(doc, p, "anyAttribute"))
.map(|node| parse_wildcard(ctx, node))
}
fn unenforceable_element(name: &str, occurs: Occurs) -> Particle {
Particle {
name: name.to_owned(),
occurs,
content: Box::new(Content::Any),
attributes: Vec::new(),
fixed: None,
nillable: true,
wildcard: None,
any_attribute: None,
identities: Vec::new(),
}
}
fn resolve_named_type(ctx: &Ctx, name: &str) -> Content {
let local = name.rsplit(':').next().unwrap_or(name);
if let Some(st) = ctx.schema.named_simple_types.get(local) {
return Content::Simple(Box::new(st.clone()));
}
if let Some(ct) = ctx.schema.named_complex_types.get(local) {
return ct.clone();
}
if let Some(&node) = ctx.tops.complex_types.get(local) {
if let Some(inner) = ctx.deeper() {
if let Ok(content) = parse_complex_type(&inner, node) {
return content;
}
}
}
BuiltIn::from_name(name).map_or(Content::Any, |b| {
Content::Simple(Box::new(SimpleType::atomic(b)))
})
}
fn parse_complex_type(ctx: &Ctx, id: NodeId) -> Result<Content, SchemaError> {
let cached = ctx.memo.borrow().content.get(&id.index()).cloned();
if let Some(hit) = cached {
ctx.charge_many(particle_count(&hit))?;
return Ok(hit);
}
let content = parse_complex_type_uncached(ctx, id)?;
ctx.charge_many(particle_count(&content))?;
let _ = ctx
.memo
.borrow_mut()
.content
.insert(id.index(), content.clone());
Ok(content)
}
fn particle_count(content: &Content) -> usize {
match content {
Content::Sequence(p) | Content::Choice(p) | Content::All(p) => p
.iter()
.map(|particle| 1 + particle_count(&particle.content))
.sum(),
_ => 0,
}
}
fn parse_complex_type_uncached(
ctx: &Ctx,
id: NodeId,
) -> Result<Content, SchemaError> {
let doc = ctx.doc;
if let Some(cc) = first_child_named(doc, id, "complexContent") {
return parse_complex_content(ctx, cc);
}
if let Some(group) = model_group(doc, id) {
return parse_model_group(ctx, group);
}
if let Some(sc) = first_child_named(doc, id, "simpleContent") {
for kind in ["extension", "restriction"] {
if let Some(node) = first_child_named(doc, sc, kind) {
if let Some(base) = doc.attribute(node, "base") {
return Ok(resolve_named_type(ctx, base));
}
}
}
return Ok(Content::Any);
}
Ok(Content::Empty)
}
fn parse_wildcard(ctx: &Ctx, id: NodeId) -> Wildcard {
let doc = ctx.doc;
let target = ctx.schema.target_namespace.clone();
let namespaces = match doc.attribute(id, "namespace") {
None | Some("##any") => NamespaceConstraint::Any,
Some("##other") => NamespaceConstraint::Other,
Some(list) => NamespaceConstraint::List(
list.split_whitespace()
.map(|item| match item {
"##targetNamespace" => target.clone(),
"##local" => None,
uri => Some(uri.to_owned()),
})
.collect(),
),
};
let process = match doc.attribute(id, "processContents") {
Some("skip") => ProcessContents::Skip,
Some("lax") => ProcessContents::Lax,
_ => ProcessContents::Strict,
};
Wildcard {
namespaces,
process,
}
}
fn model_group(doc: &Document, id: NodeId) -> Option<NodeId> {
["sequence", "choice", "all", "group"]
.into_iter()
.find_map(|name| first_child_named(doc, id, name))
}
fn parse_model_group(ctx: &Ctx, id: NodeId) -> Result<Content, SchemaError> {
let doc = ctx.doc;
match local_name(doc, id) {
Some("sequence") => {
Ok(Content::Sequence(group_particles(ctx, id, "sequence")?))
}
Some("choice") => {
Ok(Content::Choice(group_particles(ctx, id, "choice")?))
}
Some("all") => Ok(Content::All(group_particles(ctx, id, "all")?)),
Some("group") => {
let Some(reference) = doc.attribute(id, "ref") else {
return match model_group(doc, id) {
Some(inner) => parse_model_group(ctx, inner),
None => Ok(Content::Empty),
};
};
let local = reference.rsplit(':').next().unwrap_or(reference);
let Some(&target) = ctx.tops.groups.get(local) else {
return Ok(Content::Any);
};
let Some(inner) = ctx.deeper() else {
return Ok(Content::Any);
};
match model_group(doc, target) {
Some(group) => parse_model_group(&inner, group),
None => Ok(Content::Empty),
}
}
_ => Ok(Content::Empty),
}
}
fn parse_complex_content(
ctx: &Ctx,
id: NodeId,
) -> Result<Content, SchemaError> {
let doc = ctx.doc;
let Some(node) = first_child_named(doc, id, "extension")
.or_else(|| first_child_named(doc, id, "restriction"))
else {
return Ok(Content::Any);
};
let extending = local_name(doc, node) == Some("extension");
let own = match model_group(doc, node) {
Some(group) => parse_model_group(ctx, group)?,
None => Content::Empty,
};
let Some(base_name) = doc.attribute(node, "base") else {
return Ok(own);
};
let base = resolve_named_type(ctx, base_name);
if !extending {
return Ok(own);
}
Ok(match (base, own) {
(Content::Sequence(mut a), Content::Sequence(b)) => {
a.extend(b);
Content::Sequence(a)
}
(Content::Empty, own) => own,
(base, _) => base,
})
}
fn group_particles(
ctx: &Ctx,
id: NodeId,
kind: &str,
) -> Result<Vec<Particle>, SchemaError> {
let mut particles = parse_particles(ctx, id)?;
let group = parse_occurs(ctx.doc, id);
if group == Occurs::default() {
return Ok(particles);
}
if let [only] = particles.as_mut_slice() {
only.occurs = Occurs {
min: only.occurs.min.saturating_mul(group.min),
max: match (only.occurs.max, group.max) {
(Some(a), Some(b)) => Some(a.saturating_mul(b)),
_ => None,
},
};
}
let _ = kind;
Ok(particles)
}
fn parse_particles(
ctx: &Ctx,
id: NodeId,
) -> Result<Vec<Particle>, SchemaError> {
let doc = ctx.doc;
let mut out = Vec::new();
for &child in doc.children(id) {
match local_name(doc, child) {
Some("element") => out.push(parse_element(ctx, child)?),
Some("any") => out.push(Particle {
name: String::new(),
occurs: parse_occurs(doc, child),
content: Box::new(Content::Any),
attributes: Vec::new(),
fixed: None,
nillable: false,
wildcard: Some(parse_wildcard(ctx, child)),
any_attribute: None,
identities: Vec::new(),
}),
Some("sequence" | "choice" | "all" | "group") => {
let content = parse_model_group(ctx, child)?;
match content {
Content::Sequence(p)
| Content::Choice(p)
| Content::All(p) => out.extend(p),
_ => {}
}
}
_ => {}
}
}
Ok(out)
}
fn parse_attributes(
ctx: &Ctx,
id: NodeId,
) -> Result<Vec<AttributeDecl>, SchemaError> {
if let Some(hit) = ctx.memo.borrow().attributes.get(&id.index()) {
return Ok(hit.clone());
}
let attributes = parse_attributes_uncached(ctx, id)?;
let _ = ctx
.memo
.borrow_mut()
.attributes
.insert(id.index(), attributes.clone());
Ok(attributes)
}
fn parse_attributes_uncached(
ctx: &Ctx,
id: NodeId,
) -> Result<Vec<AttributeDecl>, SchemaError> {
let doc = ctx.doc;
let mut out = Vec::new();
let mut hosts = vec![id];
for wrapper in ["complexContent", "simpleContent"] {
let Some(w) = first_child_named(doc, id, wrapper) else {
continue;
};
for kind in ["extension", "restriction"] {
let Some(node) = first_child_named(doc, w, kind) else {
continue;
};
hosts.push(node);
if let Some(base) = doc.attribute(node, "base") {
let local = base.rsplit(':').next().unwrap_or(base);
if let (Some(&target), Some(inner)) =
(ctx.tops.complex_types.get(local), ctx.deeper())
{
out.extend(parse_attributes(&inner, target)?);
}
}
}
}
for host in hosts {
for &child in doc.children(host) {
match local_name(doc, child) {
Some("attribute") => {
if let Some(decl) = parse_attribute(ctx, child)? {
out.push(decl);
}
}
Some("attributeGroup") => {
let Some(reference) = doc.attribute(child, "ref") else {
continue;
};
let local =
reference.rsplit(':').next().unwrap_or(reference);
let Some(&target) = ctx.tops.attribute_groups.get(local)
else {
continue;
};
let Some(inner) = ctx.deeper() else {
continue;
};
out.extend(parse_attributes(&inner, target)?);
}
_ => {}
}
}
}
let mut seen: Vec<String> = Vec::new();
out.reverse();
out.retain(|d| {
if seen.contains(&d.name) {
false
} else {
seen.push(d.name.clone());
true
}
});
out.reverse();
Ok(out)
}
fn parse_attribute(
ctx: &Ctx,
id: NodeId,
) -> Result<Option<AttributeDecl>, SchemaError> {
let doc = ctx.doc;
let use_attr = doc.attribute(id, "use");
if let Some(reference) = doc.attribute(id, "ref") {
let local = reference.rsplit(':').next().unwrap_or(reference);
let Some(&target) = ctx.tops.attributes.get(local) else {
return Ok(None);
};
let Some(inner) = ctx.deeper() else {
return Ok(None);
};
let Some(mut decl) = parse_attribute(&inner, target)? else {
return Ok(None);
};
decl.required = use_attr == Some("required");
decl.prohibited = use_attr == Some("prohibited");
if let Some(fixed) = doc.attribute(id, "fixed") {
decl.fixed = Some(fixed.to_owned());
}
return Ok(Some(decl));
}
let Some(name) = doc.attribute(id, "name") else {
return Ok(None);
};
let simple_type = if let Some(t) = doc.attribute(id, "type") {
match resolve_named_type(ctx, t) {
Content::Simple(st) => *st,
_ => SimpleType::atomic(BuiltIn::String),
}
} else if let Some(st) = first_child_named(doc, id, "simpleType") {
parse_simple_type(ctx, st)
} else {
SimpleType::atomic(BuiltIn::String)
};
Ok(Some(AttributeDecl {
name: name.to_owned(),
required: use_attr == Some("required"),
simple_type,
fixed: doc.attribute(id, "fixed").map(str::to_owned),
prohibited: use_attr == Some("prohibited"),
}))
}
fn parse_simple_type(ctx: &Ctx, id: NodeId) -> SimpleType {
let doc = ctx.doc;
if let Some(list) = first_child_named(doc, id, "list") {
return parse_list(ctx, list, Facets::default());
}
if let Some(union) = first_child_named(doc, id, "union") {
return parse_union(ctx, union, Facets::default());
}
let Some(restriction) = first_child_named(doc, id, "restriction") else {
return SimpleType::atomic(BuiltIn::String);
};
let inherited = doc
.attribute(restriction, "base")
.and_then(|b| named_simple_type(b, ctx.schema))
.filter(|st| st.variety != Variety::Atomic);
let base_name = doc.attribute(restriction, "base").unwrap_or("string");
let base = match resolve_named_type(ctx, base_name) {
Content::Simple(st) => st.base,
_ => BuiltIn::String,
};
let mut facets = Facets::default();
for &facet in doc.children(restriction) {
let Some(kind) = local_name(doc, facet) else {
continue;
};
let Some(value) = doc.attribute(facet, "value") else {
continue;
};
match kind {
"enumeration" => facets.enumeration.push(value.to_owned()),
"pattern" => {
facets.pattern = crate::pattern::Pattern::compile(value).ok();
}
"minLength" => facets.min_length = value.parse().ok(),
"maxLength" => facets.max_length = value.parse().ok(),
"length" => facets.length = value.parse().ok(),
"minInclusive" => {
facets.min_inclusive = Some(value.to_owned());
}
"maxInclusive" => {
facets.max_inclusive = Some(value.to_owned());
}
"minExclusive" => {
facets.min_exclusive = Some(value.to_owned());
}
"maxExclusive" => {
facets.max_exclusive = Some(value.to_owned());
}
"totalDigits" => facets.total_digits = value.parse().ok(),
"fractionDigits" => facets.fraction_digits = value.parse().ok(),
"whiteSpace" => {
facets.white_space = match value {
"preserve" => Some(WhiteSpace::Preserve),
"replace" => Some(WhiteSpace::Replace),
"collapse" => Some(WhiteSpace::Collapse),
_ => None,
};
}
_ => {}
}
}
if let Some(mut inherited) = inherited {
inherited.facets = facets;
return inherited;
}
for host in [
Some(restriction),
first_child_named(doc, restriction, "simpleType"),
]
.into_iter()
.flatten()
{
if let Some(list) = first_child_named(doc, host, "list") {
return parse_list(ctx, list, facets);
}
if let Some(union) = first_child_named(doc, host, "union") {
return parse_union(ctx, union, facets);
}
}
SimpleType {
base,
facets,
variety: Variety::Atomic,
}
}
fn named_simple_type(name: &str, schema: &Schema) -> Option<SimpleType> {
let local = name.rsplit(':').next().unwrap_or(name);
schema.named_simple_types.get(local).cloned()
}
fn parse_list(ctx: &Ctx, id: NodeId, facets: Facets) -> SimpleType {
let doc = ctx.doc;
let item = if let Some(name) = doc.attribute(id, "itemType") {
item_type(ctx, name)
} else if let Some(inline) = first_child_named(doc, id, "simpleType") {
parse_simple_type(ctx, inline)
} else {
SimpleType::atomic(BuiltIn::String)
};
SimpleType {
base: BuiltIn::AnySimpleType,
facets,
variety: Variety::List(Box::new(item)),
}
}
fn parse_union(ctx: &Ctx, id: NodeId, facets: Facets) -> SimpleType {
let doc = ctx.doc;
let mut members: Vec<SimpleType> = doc
.attribute(id, "memberTypes")
.unwrap_or_default()
.split_whitespace()
.map(|name| item_type(ctx, name))
.collect();
for &child in doc.children(id) {
if local_name(doc, child) == Some("simpleType") {
members.push(parse_simple_type(ctx, child));
}
}
if members.is_empty() {
members.push(SimpleType::atomic(BuiltIn::String));
}
SimpleType {
base: BuiltIn::AnySimpleType,
facets,
variety: Variety::Union(members),
}
}
fn item_type(ctx: &Ctx, name: &str) -> SimpleType {
if let Some(named) = named_simple_type(name, ctx.schema) {
return named;
}
BuiltIn::from_name(name)
.map_or_else(|| SimpleType::atomic(BuiltIn::String), SimpleType::atomic)
}