use std::collections::HashSet;
use std::sync::Arc;
use crate::schema::types::{
CompiledSchema, ComplexType, ContentModel, ContentModelType, ElementDef, FlattenedChildren,
NsName, Particle, TypeDef,
};
use super::XsdCompiler;
impl XsdCompiler {
pub(crate) fn build_type_children_cache(&self, schema: &mut CompiledSchema) {
let type_names: Vec<String> = schema.types.keys().cloned().collect();
for type_name in &type_names {
if let Some(TypeDef::Complex(complex)) = schema.types.get(type_name) {
let flattened = Arc::new(self.flatten_type_children_ns(complex, schema));
if let Some(ns_name) = self.resolve_to_ns(type_name) {
schema
.ns_type_children_cache
.insert(ns_name, Arc::clone(&flattened));
}
schema
.type_children_cache
.insert(type_name.clone(), Arc::clone(&flattened));
let local_name = type_name
.split_once(':')
.map(|(_, local)| local)
.unwrap_or(type_name);
schema
.type_children_cache
.entry(local_name.to_string())
.or_insert(Arc::clone(&flattened));
}
}
let import_types: Vec<(String, FlattenedChildren)> = schema
.imports
.values()
.flat_map(|imported| {
imported.types.iter().filter_map(|(type_name, type_def)| {
if let TypeDef::Complex(complex) = type_def {
let flattened = self.flatten_type_children_ns(complex, schema);
Some((type_name.clone(), flattened))
} else {
None
}
})
})
.collect();
for (type_name, flattened) in import_types {
let flattened = Arc::new(flattened);
if let Some(ns_name) = self.resolve_to_ns(&type_name) {
schema
.ns_type_children_cache
.insert(ns_name, Arc::clone(&flattened));
}
schema
.type_children_cache
.insert(type_name.clone(), Arc::clone(&flattened));
let local_name = type_name
.split_once(':')
.map(|(_, local)| local)
.unwrap_or(&type_name);
schema
.type_children_cache
.entry(local_name.to_string())
.or_insert(Arc::clone(&flattened));
}
}
fn resolve_to_ns(&self, key: &str) -> Option<NsName> {
if let Some((prefix, local)) = key.split_once(':') {
let ns_uri = self.namespace_bindings.get(prefix)?;
Some(NsName::new(ns_uri.clone(), local))
} else {
let ns = self.current_target_ns.as_deref().unwrap_or("").to_string();
Some(NsName::new(ns, key))
}
}
#[allow(clippy::result_unit_err)]
fn effective_particle(
&self,
complex: &ComplexType,
schema: &CompiledSchema,
depth: usize,
) -> Result<Option<Particle>, ()> {
if depth > 16 {
return Err(());
}
use crate::schema::types::DerivationMethod;
if complex.derivation == Some(DerivationMethod::Extension)
&& let Some(base_name) = &complex.base_type
{
let base_local = base_name
.split_once(':')
.map(|(_, l)| l)
.unwrap_or(base_name);
let base_part = if base_local == "anyType" {
None
} else {
let base = self
.resolve_to_ns(base_name)
.and_then(|ns| schema.get_type_by_ns(&ns.namespace_uri, &ns.local_name))
.or_else(|| schema.get_type(base_name));
match base {
Some(TypeDef::Complex(b)) => self.effective_particle(b, schema, depth + 1)?,
Some(_) => None, None => return Err(()),
}
};
let own = complex.particle.as_deref().cloned();
return Ok(match (base_part, own) {
(None, None) => None,
(Some(b), None) => Some(b),
(None, Some(o)) => Some(o),
(Some(b), Some(o)) => Some(Particle::Sequence {
min: 1,
max: Some(1),
items: vec![b, o],
}),
});
}
Ok(complex.particle.as_deref().cloned())
}
fn build_type_automaton(
&self,
complex: &ComplexType,
schema: &CompiledSchema,
) -> Option<crate::schema::xsd::content_automaton::ContentAutomaton> {
let particle = self.effective_particle(complex, schema, 0).ok()??;
let subst = |head: &str| -> Vec<String> {
if let Some(members) = schema.transitive_substitution_groups.get(head) {
return (**members).clone();
}
if let Some((_, local)) = head.split_once(':')
&& let Some(members) = schema.transitive_substitution_groups.get(local)
{
return (**members).clone();
}
Vec::new()
};
crate::schema::xsd::content_automaton::build_automaton(&particle, &subst)
}
fn flatten_type_children_ns(
&self,
complex: &ComplexType,
schema: &CompiledSchema,
) -> FlattenedChildren {
let mut visited = HashSet::new();
let elements = self.collect_elements_with_inheritance_ns(complex, schema, &mut visited);
let content_model_type = match &complex.content {
ContentModel::Sequence(_) => ContentModelType::Sequence,
ContentModel::Choice(_) => ContentModelType::Choice,
ContentModel::All(_) => ContentModelType::All,
ContentModel::ComplexExtension { .. } => ContentModelType::Sequence,
ContentModel::Empty => ContentModelType::Empty,
ContentModel::SimpleContent { .. } => ContentModelType::Empty,
ContentModel::Any { .. } => ContentModelType::Sequence,
};
let mut flattened = FlattenedChildren::with_content_model(content_model_type);
for elem in elements {
flattened
.constraints
.insert(elem.name.clone(), (elem.min_occurs, elem.max_occurs));
}
flattened.wildcard = inherited_wildcard(complex, schema);
flattened.automaton = self.build_type_automaton(complex, schema).map(Arc::new);
flattened
}
fn collect_elements_with_inheritance_ns(
&self,
complex: &ComplexType,
schema: &CompiledSchema,
visited: &mut HashSet<String>,
) -> Vec<ElementDef> {
let mut elements = Vec::new();
match &complex.content {
ContentModel::Sequence(elems)
| ContentModel::Choice(elems)
| ContentModel::All(elems) => {
elements.extend(elems.iter().cloned());
}
ContentModel::ComplexExtension {
base_type,
elements: ext_elements,
} => {
if !visited.contains(base_type.as_str()) {
visited.insert(base_type.clone());
let base_complex = if let Some(ns_name) = self.resolve_to_ns(base_type) {
schema.get_type_by_ns(&ns_name.namespace_uri, &ns_name.local_name)
} else {
None
};
let base_complex = base_complex.or_else(|| schema.get_type(base_type.as_str()));
if let Some(TypeDef::Complex(base_complex)) = base_complex {
let base_elements = self.collect_elements_with_inheritance_ns(
base_complex,
schema,
visited,
);
elements.extend(base_elements);
}
}
elements.extend(ext_elements.iter().cloned());
}
_ => {}
}
elements
}
}
pub(crate) fn inherited_wildcard(
complex: &crate::schema::types::ComplexType,
schema: &CompiledSchema,
) -> Option<crate::schema::types::WildcardConstraint> {
if complex.wildcard.is_some() {
return complex.wildcard.clone();
}
let mut base = complex.base_type.clone();
for _ in 0..16 {
let Some(b) = base else { break };
match schema.get_type(&b) {
Some(crate::schema::types::TypeDef::Complex(c)) => {
if c.wildcard.is_some() {
return c.wildcard.clone();
}
base = c.base_type.clone();
}
_ => break,
}
}
None
}