use std::collections::{HashMap, HashSet};
use crate::error::Result;
use crate::schema::error::SchemaError;
use super::super::types::*;
const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema";
const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
const XSI_NS: &str = "http://www.w3.org/2001/XMLSchema-instance";
use crate::schema::xsd::builtin::is_builtin_xsd_type_local;
use super::facet_checks::{FacetBase, check_facets, classify_builtin_base};
fn is_always_lenient(ns: &str) -> bool {
ns == XML_NS
|| ns == XSI_NS
|| ns == crate::schema::xsd::builtin::GML_NAMESPACE
|| ns == crate::schema::xsd::builtin::GML31_NAMESPACE
}
#[derive(Debug, Default)]
pub(crate) struct Strictness {
defined: HashSet<String>,
poisoned: HashSet<String>,
}
impl Strictness {
pub(crate) fn compute(schemas: &[XsdSchema]) -> Self {
let mut defined: HashSet<String> = HashSet::new();
for schema in schemas {
defined.insert(tns_key(schema));
}
let has_chameleon_doc = schemas
.iter()
.any(|s| s.target_namespace.is_none() && schemas.len() > 1);
let mut poisoned: HashSet<String> = HashSet::new();
for schema in schemas {
for import in &schema.imports {
let ns = import.namespace.clone().unwrap_or_default();
if !defined.contains(&ns) {
poisoned.insert(ns);
}
}
if !schema.includes.is_empty() || !schema.redefines.is_empty() {
let own = tns_key(schema);
let satisfied = schemas
.iter()
.any(|other| !std::ptr::eq(other, schema) && tns_key(other) == own);
if !satisfied || (has_chameleon_doc && schema.target_namespace.is_some()) {
poisoned.insert(own);
}
}
}
Self { defined, poisoned }
}
fn is_strict(&self, ns: &str) -> bool {
if self.poisoned.contains(ns) || is_always_lenient(ns) {
return false;
}
ns == XSD_NS || self.defined.contains(ns)
}
}
pub(super) fn tns_key(schema: &XsdSchema) -> String {
schema.target_namespace.clone().unwrap_or_default()
}
#[derive(Debug, Clone, Copy)]
struct KeyInfo {
is_key_or_unique: bool,
field_count: usize,
}
#[derive(Debug, Clone, Copy, Default)]
struct TypeInfo {
is_complex: bool,
final_extension: bool,
final_restriction: bool,
final_list: bool,
final_union: bool,
}
fn final_flags(control: Option<&DerivationControl>) -> (bool, bool, bool, bool) {
match control {
None => (false, false, false, false),
Some(DerivationControl::All) => (true, true, true, true),
Some(DerivationControl::List(types)) => (
types.contains(&DerivationType::Extension),
types.contains(&DerivationType::Restriction),
types.contains(&DerivationType::List),
types.contains(&DerivationType::Union),
),
}
}
#[derive(Debug, Default)]
struct SymbolTables {
types: HashMap<String, HashMap<String, TypeInfo>>,
elements: HashMap<String, HashSet<String>>,
attributes: HashMap<String, HashSet<String>>,
groups: HashMap<String, HashSet<String>>,
attribute_groups: HashMap<String, HashSet<String>>,
keys: HashMap<String, HashMap<String, KeyInfo>>,
}
impl SymbolTables {
fn build(schemas: &[XsdSchema]) -> Self {
let mut tables = Self::default();
for schema in schemas {
let ns = tns_key(schema);
for type_def in &schema.types {
if let Some(name) = type_def.name() {
let control = match type_def {
XsdTypeDef::Complex(ct) => ct.final_.as_ref(),
XsdTypeDef::Simple(st) => st.final_.as_ref(),
}
.or(schema.final_default.as_ref());
let (ext, restr, list, union) = final_flags(control);
let info = if type_def.is_complex() {
TypeInfo {
is_complex: true,
final_extension: ext,
final_restriction: restr,
final_list: false,
final_union: false,
}
} else {
TypeInfo {
is_complex: false,
final_extension: false,
final_restriction: restr,
final_list: list,
final_union: union,
}
};
tables
.types
.entry(ns.clone())
.or_default()
.insert(name.to_string(), info);
}
}
for element in &schema.elements {
tables
.elements
.entry(ns.clone())
.or_default()
.insert(element.name.clone());
collect_constraint_names(element, &ns, &mut tables.keys);
}
for attr in &schema.attributes {
if let Some(name) = &attr.name {
tables
.attributes
.entry(ns.clone())
.or_default()
.insert(name.clone());
}
}
for group in &schema.groups {
if let Some(name) = &group.name {
tables
.groups
.entry(ns.clone())
.or_default()
.insert(name.clone());
}
}
for ag in &schema.attribute_groups {
if let Some(name) = &ag.name {
tables
.attribute_groups
.entry(ns.clone())
.or_default()
.insert(name.clone());
}
}
for type_def in &schema.types {
collect_constraints_in_type(type_def, &ns, &mut tables.keys);
}
}
tables
}
fn lookup_key(&self, ns: &str, local: &str) -> Option<KeyInfo> {
if let Some(info) = self.keys.get(ns).and_then(|m| m.get(local)) {
return Some(*info);
}
if !ns.is_empty() {
return self.keys.get("").and_then(|m| m.get(local)).copied();
}
None
}
fn lookup_type_info(&self, ns: &str, local: &str) -> Option<TypeInfo> {
if let Some(info) = self.types.get(ns).and_then(|m| m.get(local)) {
return Some(*info);
}
if !ns.is_empty()
&& let Some(info) = self.types.get("").and_then(|m| m.get(local))
{
return Some(*info);
}
if (ns == XSD_NS || ns.is_empty()) && is_builtin_xsd_type_local(local) {
return Some(TypeInfo {
is_complex: local == "anyType",
..TypeInfo::default()
});
}
None
}
fn contains(&self, kind: RefKind, ns: &str, local: &str) -> bool {
if kind == RefKind::Key {
return self.lookup_key(ns, local).is_some();
}
if kind == RefKind::Type {
return self.lookup_type_info(ns, local).is_some();
}
let table = match kind {
RefKind::Element => &self.elements,
RefKind::Attribute => &self.attributes,
RefKind::Group => &self.groups,
RefKind::AttributeGroup => &self.attribute_groups,
RefKind::Type | RefKind::Key => unreachable!("handled above"),
};
if table.get(ns).is_some_and(|set| set.contains(local)) {
return true;
}
!ns.is_empty() && table.get("").is_some_and(|set| set.contains(local))
}
}
fn collect_constraint_names(
element: &XsdElement,
ns: &str,
keys: &mut HashMap<String, HashMap<String, KeyInfo>>,
) {
for ic in &element.identity_constraints {
keys.entry(ns.to_string()).or_default().insert(
ic.name.clone(),
KeyInfo {
is_key_or_unique: ic.constraint_type != XsdConstraintType::KeyRef,
field_count: ic.fields.len(),
},
);
}
if let Some(inline) = &element.inline_type {
collect_constraints_in_type(inline, ns, keys);
}
}
fn collect_constraints_in_type(
type_def: &XsdTypeDef,
ns: &str,
keys: &mut HashMap<String, HashMap<String, KeyInfo>>,
) {
let XsdTypeDef::Complex(ct) = type_def else {
return;
};
let mut walk_particle = |particle: &XsdParticle| {
collect_constraints_in_particle(particle, ns, keys);
};
match &ct.content {
XsdComplexContent::Particle(p) => walk_particle(p),
XsdComplexContent::ComplexContent(cc) => match &cc.derivation {
XsdComplexContentDerivation::Extension(ext) => {
if let Some(p) = &ext.particle {
walk_particle(p);
}
}
XsdComplexContentDerivation::Restriction(r) => {
if let Some(p) = &r.particle {
walk_particle(p);
}
}
},
_ => {}
}
}
fn collect_constraints_in_particle(
particle: &XsdParticle,
ns: &str,
keys: &mut HashMap<String, HashMap<String, KeyInfo>>,
) {
match particle {
XsdParticle::Sequence(seq) => {
for item in &seq.particles {
collect_constraints_in_item(item, ns, keys);
}
}
XsdParticle::Choice(choice) => {
for item in &choice.particles {
collect_constraints_in_item(item, ns, keys);
}
}
XsdParticle::All(all) => {
for element in &all.elements {
collect_constraint_names(element, ns, keys);
}
}
XsdParticle::GroupRef(_) | XsdParticle::Any(_) => {}
}
}
fn collect_constraints_in_item(
item: &XsdParticleItem,
ns: &str,
keys: &mut HashMap<String, HashMap<String, KeyInfo>>,
) {
match item {
XsdParticleItem::Element(element) => collect_constraint_names(element, ns, keys),
XsdParticleItem::Sequence(seq) => {
for nested in &seq.particles {
collect_constraints_in_item(nested, ns, keys);
}
}
XsdParticleItem::Choice(choice) => {
for nested in &choice.particles {
collect_constraints_in_item(nested, ns, keys);
}
}
XsdParticleItem::GroupRef(_) | XsdParticleItem::Any(_) => {}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RefKind {
Type,
Element,
Attribute,
Group,
AttributeGroup,
Key,
}
impl RefKind {
fn as_str(self) -> &'static str {
match self {
RefKind::Type => "type",
RefKind::Element => "element",
RefKind::Attribute => "attribute",
RefKind::Group => "group",
RefKind::AttributeGroup => "attribute group",
RefKind::Key => "identity constraint",
}
}
}
pub(crate) fn check_references(schemas: &[XsdSchema], strictness: &Strictness) -> Result<()> {
let tables = SymbolTables::build(schemas);
super::cycles::check_definition_cycles(schemas)?;
for schema in schemas {
let checker = Checker {
schema,
strictness,
tables: &tables,
};
checker.check_schema()?;
}
Ok(())
}
struct Checker<'a> {
schema: &'a XsdSchema,
strictness: &'a Strictness,
tables: &'a SymbolTables,
}
impl Checker<'_> {
fn resolve_ns(&self, prefix: Option<&str>) -> Option<String> {
match prefix {
Some("xml") => Some(XML_NS.to_string()),
Some(p) => self.schema.namespace_bindings.get(p).cloned(),
None => Some(
self.schema
.namespace_bindings
.get("")
.cloned()
.unwrap_or_default(),
),
}
}
fn check_ref(&self, kind: RefKind, qname: &QName, from: &str) -> Result<()> {
self.check_ref_inner(kind, qname, from, true)
}
fn check_ref_lazy(&self, kind: RefKind, qname: &QName, from: &str) -> Result<()> {
self.check_ref_inner(kind, qname, from, false)
}
fn check_ref_inner(
&self,
kind: RefKind,
qname: &QName,
from: &str,
require_existence: bool,
) -> Result<()> {
let prefix = qname.prefix.as_deref().map(str::trim);
let local = qname.local.trim();
let Some(ns) = self.resolve_ns(prefix) else {
return Err(SchemaError::DanglingReference {
kind: kind.as_str(),
name: qname.to_string_full(),
referenced_from: format!("{} (undeclared namespace prefix)", from),
}
.into());
};
if self.tables.contains(kind, &ns, local) {
return Ok(());
}
if prefix.is_none()
&& let Some(own) = &self.schema.target_namespace
&& *own != ns
&& self.tables.contains(kind, own, local)
{
return Ok(());
}
let must_resolve = self.strictness.is_strict(&ns) && (require_existence || ns == XSD_NS);
if !must_resolve {
return Ok(());
}
Err(SchemaError::DanglingReference {
kind: kind.as_str(),
name: qname.to_string_full(),
referenced_from: from.to_string(),
}
.into())
}
fn check_schema(&self) -> Result<()> {
for element in &self.schema.elements {
self.check_element_at(element, "top-level element", true)?;
}
for type_def in &self.schema.types {
self.check_type_def(type_def)?;
}
for attr in &self.schema.attributes {
self.check_attribute(attr, "top-level attribute")?;
}
for group in &self.schema.groups {
if let Some(particle) = &group.particle {
let from = format!("group '{}'", group.name.as_deref().unwrap_or("(anonymous)"));
self.check_particle(particle, &from)?;
}
}
for ag in &self.schema.attribute_groups {
let from = format!(
"attribute group '{}'",
ag.name.as_deref().unwrap_or("(anonymous)")
);
self.check_attribute_group_body(ag, &from)?;
}
Ok(())
}
fn check_element(&self, element: &XsdElement, from: &str) -> Result<()> {
self.check_element_at(element, from, false)
}
fn check_element_at(&self, element: &XsdElement, from: &str, top_level: bool) -> Result<()> {
if let Some(r) = &element.ref_ {
self.check_ref(RefKind::Element, r, from)?;
}
if let Some(t) = &element.type_ref {
if top_level {
self.check_ref_lazy(RefKind::Type, t, from)?;
} else {
self.check_ref(RefKind::Type, t, from)?;
}
}
if let Some(sg) = &element.substitution_group {
self.check_ref_lazy(RefKind::Element, sg, from)?;
}
if let Some(inline) = &element.inline_type {
self.check_type_def(inline)?;
}
for ic in &element.identity_constraints {
if let Some(refer) = &ic.refer {
let from = format!("keyref '{}'", ic.name);
self.check_ref(RefKind::Key, refer, &from)?;
self.check_keyref_target(ic, refer)?;
}
}
Ok(())
}
fn facet_base(&self, base: Option<&QName>) -> FacetBase {
let Some(qname) = base else {
return FacetBase::Unknown;
};
let prefix = qname.prefix.as_deref().map(str::trim);
let local = qname.local.trim();
let Some(ns) = self.resolve_ns(prefix) else {
return FacetBase::Unknown;
};
if self
.tables
.types
.get(&ns)
.is_some_and(|m| m.contains_key(local))
{
return FacetBase::Unknown;
}
if ns == XSD_NS || (ns.is_empty() && is_builtin_xsd_type_local(local)) {
classify_builtin_base(local)
} else {
FacetBase::Unknown
}
}
fn resolve_type_info(&self, qname: &QName) -> Option<TypeInfo> {
let prefix = qname.prefix.as_deref().map(str::trim);
let local = qname.local.trim();
let ns = self.resolve_ns(prefix)?;
if let Some(info) = self.tables.lookup_type_info(&ns, local) {
return Some(info);
}
if prefix.is_none()
&& let Some(own) = &self.schema.target_namespace
&& *own != ns
{
return self.tables.lookup_type_info(own, local);
}
None
}
fn require_simple_type(&self, qname: &QName, from: &str) -> Result<()> {
if self.resolve_type_info(qname).is_some_and(|i| i.is_complex) {
return Err(SchemaError::InvalidSchema {
message: format!(
"{} requires a simple type, but '{}' is complex",
from, qname
),
}
.into());
}
Ok(())
}
fn require_complex_type(&self, qname: &QName, from: &str) -> Result<()> {
if self.resolve_type_info(qname).is_some_and(|i| !i.is_complex) {
return Err(SchemaError::InvalidSchema {
message: format!(
"{} requires a complex type, but '{}' is simple",
from, qname
),
}
.into());
}
Ok(())
}
fn check_final(&self, qname: &QName, method: DerivationType, from: &str) -> Result<()> {
let Some(info) = self.resolve_type_info(qname) else {
return Ok(());
};
let blocked = match method {
DerivationType::Extension => info.final_extension,
DerivationType::Restriction => info.final_restriction,
DerivationType::List => info.final_list,
DerivationType::Union => info.final_union,
DerivationType::Substitution => false,
};
if blocked {
return Err(SchemaError::InvalidSchema {
message: format!(
"type '{}' is final and cannot be used for {} ({})",
qname,
match method {
DerivationType::Extension => "derivation by extension",
DerivationType::Restriction => "derivation by restriction",
DerivationType::List => "a list item type",
DerivationType::Union => "a union member type",
DerivationType::Substitution => "substitution",
},
from
),
}
.into());
}
Ok(())
}
fn check_keyref_target(&self, ic: &XsdIdentityConstraint, refer: &QName) -> Result<()> {
let prefix = refer.prefix.as_deref().map(str::trim);
let local = refer.local.trim();
let Some(ns) = self.resolve_ns(prefix) else {
return Ok(()); };
let Some(info) = self.tables.lookup_key(&ns, local) else {
return Ok(()); };
if !info.is_key_or_unique {
return Err(SchemaError::InvalidSchema {
message: format!(
"keyref '{}' must refer to a key or unique constraint, but '{}' is a keyref",
ic.name, refer
),
}
.into());
}
if info.field_count != ic.fields.len() {
return Err(SchemaError::InvalidSchema {
message: format!(
"keyref '{}' has {} field(s) but referred constraint '{}' has {}",
ic.name,
ic.fields.len(),
refer,
info.field_count
),
}
.into());
}
Ok(())
}
fn check_attribute(&self, attr: &XsdAttribute, from: &str) -> Result<()> {
if let Some(r) = &attr.ref_ {
self.check_ref(RefKind::Attribute, r, from)?;
}
if let Some(t) = &attr.type_ref {
self.check_ref(RefKind::Type, t, from)?;
self.require_simple_type(t, "attribute type")?;
}
if let Some(inline) = &attr.inline_type {
self.check_simple_type(inline)?;
}
Ok(())
}
fn check_type_def(&self, type_def: &XsdTypeDef) -> Result<()> {
match type_def {
XsdTypeDef::Simple(st) => self.check_simple_type(st),
XsdTypeDef::Complex(ct) => self.check_complex_type(ct),
}
}
fn check_simple_type(&self, st: &XsdSimpleType) -> Result<()> {
let from = format!(
"simple type '{}'",
st.name.as_deref().unwrap_or("(anonymous)")
);
match &st.content {
XsdSimpleTypeContent::Restriction(r) => {
if let Some(base) = &r.base {
self.check_ref(RefKind::Type, base, &from)?;
self.require_simple_type(base, "simple type restriction base")?;
self.check_final(base, DerivationType::Restriction, &from)?;
}
if let Some(inline) = &r.inline_base {
self.check_simple_type(inline)?;
}
check_facets(&r.facets, self.facet_base(r.base.as_ref()))?;
}
XsdSimpleTypeContent::List(list) => {
if let Some(item) = &list.item_type {
self.check_ref_lazy(RefKind::Type, item, &from)?;
self.require_simple_type(item, "list item type")?;
self.check_final(item, DerivationType::List, &from)?;
}
if let Some(inline) = &list.inline_type {
self.check_simple_type(inline)?;
}
}
XsdSimpleTypeContent::Union(union) => {
for member in &union.member_types {
self.check_ref_lazy(RefKind::Type, member, &from)?;
self.require_simple_type(member, "union member type")?;
self.check_final(member, DerivationType::Union, &from)?;
}
for inline in &union.inline_types {
self.check_simple_type(inline)?;
}
}
}
Ok(())
}
fn check_complex_type(&self, ct: &XsdComplexType) -> Result<()> {
let from = format!(
"complex type '{}'",
ct.name.as_deref().unwrap_or("(anonymous)")
);
match &ct.content {
XsdComplexContent::Empty => {}
XsdComplexContent::Particle(p) => self.check_particle(p, &from)?,
XsdComplexContent::SimpleContent(sc) => match &sc.derivation {
XsdSimpleContentDerivation::Extension(ext) => {
self.check_ref(RefKind::Type, &ext.base, &from)?;
self.check_final(&ext.base, DerivationType::Extension, &from)?;
for attr in &ext.attributes {
self.check_attribute(attr, &from)?;
}
for ag in &ext.attribute_groups {
self.check_ref(RefKind::AttributeGroup, ag, &from)?;
}
}
XsdSimpleContentDerivation::Restriction(r) => {
self.check_ref(RefKind::Type, &r.base, &from)?;
self.check_final(&r.base, DerivationType::Restriction, &from)?;
check_facets(&r.facets, self.facet_base(Some(&r.base)))?;
for attr in &r.attributes {
self.check_attribute(attr, &from)?;
}
for ag in &r.attribute_groups {
self.check_ref(RefKind::AttributeGroup, ag, &from)?;
}
}
},
XsdComplexContent::ComplexContent(cc) => match &cc.derivation {
XsdComplexContentDerivation::Extension(ext) => {
self.check_ref(RefKind::Type, &ext.base, &from)?;
self.require_complex_type(&ext.base, "complexContent extension base")?;
self.check_final(&ext.base, DerivationType::Extension, &from)?;
if let Some(p) = &ext.particle {
self.check_particle(p, &from)?;
}
for attr in &ext.attributes {
self.check_attribute(attr, &from)?;
}
for ag in &ext.attribute_groups {
self.check_ref(RefKind::AttributeGroup, ag, &from)?;
}
}
XsdComplexContentDerivation::Restriction(r) => {
self.check_ref(RefKind::Type, &r.base, &from)?;
self.require_complex_type(&r.base, "complexContent restriction base")?;
self.check_final(&r.base, DerivationType::Restriction, &from)?;
if let Some(p) = &r.particle {
self.check_particle(p, &from)?;
}
for attr in &r.attributes {
self.check_attribute(attr, &from)?;
}
for ag in &r.attribute_groups {
self.check_ref(RefKind::AttributeGroup, ag, &from)?;
}
}
},
}
for attr in &ct.attributes {
self.check_attribute(attr, &from)?;
}
for ag in &ct.attribute_groups {
self.check_ref(RefKind::AttributeGroup, ag, &from)?;
}
Ok(())
}
fn check_attribute_group_body(&self, ag: &XsdAttributeGroup, from: &str) -> Result<()> {
if let Some(r) = &ag.ref_ {
self.check_ref(RefKind::AttributeGroup, r, from)?;
}
for attr in &ag.attributes {
self.check_attribute(attr, from)?;
}
for nested in &ag.attribute_groups {
self.check_ref(RefKind::AttributeGroup, nested, from)?;
}
Ok(())
}
fn check_particle(&self, particle: &XsdParticle, from: &str) -> Result<()> {
match particle {
XsdParticle::Sequence(seq) => {
for item in &seq.particles {
self.check_item(item, from)?;
}
}
XsdParticle::Choice(choice) => {
for item in &choice.particles {
self.check_item(item, from)?;
}
}
XsdParticle::All(all) => {
for element in &all.elements {
self.check_element(element, from)?;
}
}
XsdParticle::GroupRef(group_ref) => {
self.check_ref(RefKind::Group, &group_ref.name, from)?;
}
XsdParticle::Any(_) => {}
}
Ok(())
}
fn check_item(&self, item: &XsdParticleItem, from: &str) -> Result<()> {
match item {
XsdParticleItem::Element(element) => self.check_element(element, from),
XsdParticleItem::Sequence(seq) => {
for nested in &seq.particles {
self.check_item(nested, from)?;
}
Ok(())
}
XsdParticleItem::Choice(choice) => {
for nested in &choice.particles {
self.check_item(nested, from)?;
}
Ok(())
}
XsdParticleItem::GroupRef(group_ref) => {
self.check_ref(RefKind::Group, &group_ref.name, from)
}
XsdParticleItem::Any(_) => Ok(()),
}
}
}