use crate::error::{SchemaError, SchemaResult};
use crate::ids::{
AttributeGroupKey, AttributeKey, ComplexTypeKey, ElementKey, NameId, SimpleTypeKey, TypeKey,
};
use crate::parser::frames::{
AttributeUseKind, ComplexContentResult, Compositor, DerivationMethod, ElementFrameResult,
ModelGroupDefResult, ParticleResult, ParticleTerm, ProcessContents, SimpleTypeVariety,
WildcardNamespace, WildcardResult,
};
#[cfg(feature = "xsd11")]
use crate::parser::frames::{OpenContentMode, OpenContentResult};
use crate::parser::location::{SourceLocation, SourceRef};
use crate::schema::dependencies::DependencyGraph;
use crate::schema::model::DerivationSet;
use crate::schema::SchemaSet;
use crate::types::facets::{FacetKind, FacetSet};
#[derive(Debug, Default)]
pub struct DerivationStats {
pub simple_types_validated: usize,
pub complex_types_validated: usize,
pub list_types_validated: usize,
pub union_types_validated: usize,
pub restrictions_validated: usize,
pub extensions_validated: usize,
pub errors: usize,
}
pub fn validate_all_derivations(
schema_set: &SchemaSet,
dep_graph: &DependencyGraph,
) -> SchemaResult<DerivationStats> {
let mut stats = DerivationStats::default();
let mut errors: Vec<SchemaError> = Vec::new();
for &type_key in dep_graph.compilation_order() {
match type_key {
TypeKey::Simple(key) => {
if let Err(e) = validate_simple_type(schema_set, key, &mut stats) {
errors.push(e);
stats.errors += 1;
}
}
TypeKey::Complex(key) => {
if let Err(e) = validate_complex_type(schema_set, key, &mut stats) {
errors.push(e);
stats.errors += 1;
}
}
}
}
validate_all_redefine_group_restrictions(schema_set, &mut errors, &mut stats);
validate_all_redefine_attribute_group_restrictions(schema_set, &mut errors, &mut stats);
if schema_set.is_xsd10() {
validate_attribute_group_no_circular(schema_set, &mut errors);
} else {
validate_default_attribute_groups_no_circular(schema_set, &mut errors);
}
if let Some(first_error) = errors.into_iter().next() {
return Err(first_error);
}
Ok(stats)
}
fn validate_attribute_group_no_circular(schema_set: &SchemaSet, errors: &mut Vec<SchemaError>) {
use std::collections::HashSet;
let keys: Vec<_> = schema_set.arenas.attribute_groups.keys().collect();
for start in keys {
let mut path: Vec<AttributeGroupKey> = Vec::new();
let mut visited: HashSet<AttributeGroupKey> = HashSet::new();
if let Some(cycle_key) =
find_attribute_group_cycle(schema_set, start, &mut path, &mut visited)
{
let location = schema_set
.arenas
.attribute_groups
.get(cycle_key)
.and_then(|ag| ag.source.as_ref())
.and_then(|s| schema_set.source_maps.locate(s));
errors.push(SchemaError::structural(
"src-attribute_group",
"Circular attribute group reference detected",
location,
));
}
}
}
fn find_attribute_group_cycle(
schema_set: &SchemaSet,
key: AttributeGroupKey,
path: &mut Vec<AttributeGroupKey>,
visited: &mut std::collections::HashSet<AttributeGroupKey>,
) -> Option<AttributeGroupKey> {
if path.contains(&key) {
return Some(key);
}
if !visited.insert(key) {
return None;
}
path.push(key);
let result = if let Some(ag) = schema_set.arenas.attribute_groups.get(key) {
let mut found: Option<AttributeGroupKey> = None;
if let Some(ref_key) = ag.resolved_ref {
if let Some(c) = find_attribute_group_cycle(schema_set, ref_key, path, visited) {
found = Some(c);
}
}
if found.is_none() {
for &nested_key in &ag.resolved_attribute_groups {
if let Some(c) = find_attribute_group_cycle(schema_set, nested_key, path, visited) {
found = Some(c);
break;
}
}
}
found
} else {
None
};
path.pop();
result
}
fn validate_default_attribute_groups_no_circular(
schema_set: &SchemaSet,
errors: &mut Vec<SchemaError>,
) {
use std::collections::HashSet;
let mut seen_starts: HashSet<AttributeGroupKey> = HashSet::new();
for doc in &schema_set.documents {
let Some(ref qname) = doc.default_attributes else {
continue;
};
let Some(start) = schema_set.lookup_attribute_group(qname.namespace_uri, qname.local_name)
else {
continue; };
if !seen_starts.insert(start) {
continue;
}
let mut path: Vec<AttributeGroupKey> = Vec::new();
let mut visited: HashSet<AttributeGroupKey> = HashSet::new();
if let Some(cycle_key) =
find_attribute_group_cycle(schema_set, start, &mut path, &mut visited)
{
let location = schema_set
.arenas
.attribute_groups
.get(cycle_key)
.and_then(|ag| ag.source.as_ref())
.and_then(|s| schema_set.source_maps.locate(s));
errors.push(SchemaError::structural(
"src-attribute_group",
"Circular attribute group reference detected via defaultAttributes",
location,
));
}
}
}
fn validate_simple_type(
schema_set: &SchemaSet,
key: SimpleTypeKey,
stats: &mut DerivationStats,
) -> SchemaResult<()> {
let type_def = schema_set
.arenas
.simple_types
.get(key)
.ok_or_else(|| SchemaError::internal("Simple type not found in arena"))?;
stats.simple_types_validated += 1;
validate_applicable_facets(schema_set, type_def)?;
match type_def.variety {
SimpleTypeVariety::Atomic => {
validate_simple_restriction(schema_set, type_def, stats)?;
}
SimpleTypeVariety::List => {
stats.list_types_validated += 1;
validate_simple_list(schema_set, type_def)?;
validate_facets_against_resolved_base(schema_set, type_def)?;
}
SimpleTypeVariety::Union => {
stats.union_types_validated += 1;
validate_simple_union(schema_set, type_def)?;
validate_facets_against_resolved_base(schema_set, type_def)?;
}
}
Ok(())
}
fn validate_facets_against_resolved_base(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
) -> SchemaResult<()> {
let Some(base_key) = type_def.resolved_base_type else {
return Ok(());
};
if let Some(base_facets) = get_type_facets(schema_set, base_key)? {
type_def.facets.merge_with_base(&base_facets).map_err(|e| {
let (location, type_name) = type_error_context(schema_set, type_def);
SchemaError::structural(
"cos-st-restricts",
format!("Simple type '{}' has invalid restriction: {}", type_name, e),
location,
)
})?;
}
validate_facet_values_against_base_type(schema_set, type_def, base_key)?;
Ok(())
}
fn validate_applicable_facets(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
) -> SchemaResult<()> {
let facets = &type_def.facets;
match type_def.variety {
SimpleTypeVariety::List => {
let has_inapplicable = facets.min_inclusive.is_some()
|| facets.max_inclusive.is_some()
|| facets.min_exclusive.is_some()
|| facets.max_exclusive.is_some()
|| facets.total_digits.is_some()
|| facets.fraction_digits.is_some()
|| facets.explicit_timezone.is_some();
if has_inapplicable {
let (location, type_name) = type_error_context(schema_set, type_def);
let inapplicable = list_inapplicable_facets_for_list(facets);
return Err(SchemaError::structural(
"cos-applicable-facets",
format!(
"List type '{}' has inapplicable facet(s): {}",
type_name, inapplicable
),
location,
));
}
}
SimpleTypeVariety::Union => {
let has_inapplicable = facets.length.is_some()
|| facets.min_length.is_some()
|| facets.max_length.is_some()
|| facets.whitespace.is_some()
|| facets.min_inclusive.is_some()
|| facets.max_inclusive.is_some()
|| facets.min_exclusive.is_some()
|| facets.max_exclusive.is_some()
|| facets.total_digits.is_some()
|| facets.fraction_digits.is_some()
|| facets.explicit_timezone.is_some();
if has_inapplicable {
let (location, type_name) = type_error_context(schema_set, type_def);
let inapplicable = list_inapplicable_facets_for_union(facets);
return Err(SchemaError::structural(
"cos-applicable-facets",
format!(
"Union type '{}' has inapplicable facet(s): {}",
type_name, inapplicable
),
location,
));
}
}
SimpleTypeVariety::Atomic => {
if let Some(primitive_code) = primitive_type_code(schema_set, type_def) {
use crate::types::facets::{
facet_applicable_for_type, ExplicitTimezone, FacetApplicability, FacetKind,
WhitespaceMode,
};
let facets = &type_def.facets;
let mut bad: Vec<&'static str> = Vec::new();
let mut check = |present: bool, kind: FacetKind| {
if present
&& matches!(
facet_applicable_for_type(kind, primitive_code),
FacetApplicability::NotApplicable
)
{
bad.push(kind.name());
}
};
check(facets.length.is_some(), FacetKind::Length);
check(facets.min_length.is_some(), FacetKind::MinLength);
check(facets.max_length.is_some(), FacetKind::MaxLength);
check(facets.whitespace.is_some(), FacetKind::Whitespace);
check(facets.min_inclusive.is_some(), FacetKind::MinInclusive);
check(facets.max_inclusive.is_some(), FacetKind::MaxInclusive);
check(facets.min_exclusive.is_some(), FacetKind::MinExclusive);
check(facets.max_exclusive.is_some(), FacetKind::MaxExclusive);
check(facets.total_digits.is_some(), FacetKind::TotalDigits);
check(facets.fraction_digits.is_some(), FacetKind::FractionDigits);
check(
facets.explicit_timezone.is_some(),
FacetKind::ExplicitTimezone,
);
if let Some(ws) = &facets.whitespace {
if !matches!(
primitive_code,
crate::types::XmlTypeCode::String
| crate::types::XmlTypeCode::NormalizedString
| crate::types::XmlTypeCode::Token
| crate::types::XmlTypeCode::Language
| crate::types::XmlTypeCode::NmToken
| crate::types::XmlTypeCode::Name
| crate::types::XmlTypeCode::NCName
| crate::types::XmlTypeCode::Id
| crate::types::XmlTypeCode::IdRef
| crate::types::XmlTypeCode::Entity
) && ws.value != WhitespaceMode::Collapse
{
bad.push(FacetKind::Whitespace.name());
}
}
if let Some(tz) = &facets.explicit_timezone {
if primitive_code == crate::types::XmlTypeCode::DateTimeStamp
&& tz.value != ExplicitTimezone::Required
{
bad.push(FacetKind::ExplicitTimezone.name());
}
}
if !bad.is_empty() {
let (location, type_name) = type_error_context(schema_set, type_def);
return Err(SchemaError::structural(
"cos-applicable-facets",
format!(
"Atomic type '{}' has inapplicable facet(s) for primitive '{}': {}",
type_name,
primitive_code.local_name().unwrap_or("<unnamed>"),
bad.join(", ")
),
location,
));
}
}
}
}
Ok(())
}
fn primitive_type_code(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
) -> Option<crate::types::XmlTypeCode> {
let builtin = schema_set.builtin_types();
let mut current_base = type_def.resolved_base_type;
for _ in 0..64 {
let Some(TypeKey::Simple(k)) = current_base else {
return None;
};
if let Some(code) = builtin.get_type_code(k) {
return Some(code);
}
current_base = schema_set
.arenas
.simple_types
.get(k)
.and_then(|t| t.resolved_base_type);
}
None
}
fn reject_any_atomic_type(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
simple_key: SimpleTypeKey,
constraint: &'static str,
role: &'static str,
) -> SchemaResult<()> {
if !schema_set.builtin_types().is_any_atomic_type(simple_key) {
return Ok(());
}
let (location, type_name) = type_error_context(schema_set, type_def);
Err(SchemaError::structural(
constraint,
format!(
"Simple type '{}' cannot {} xs:anyAtomicType (abstract per XSD 1.1 bug 11103)",
type_name, role
),
location,
))
}
fn list_inapplicable_facets_for_list(facets: &FacetSet) -> String {
let mut names = Vec::new();
if facets.min_inclusive.is_some() {
names.push("minInclusive");
}
if facets.max_inclusive.is_some() {
names.push("maxInclusive");
}
if facets.min_exclusive.is_some() {
names.push("minExclusive");
}
if facets.max_exclusive.is_some() {
names.push("maxExclusive");
}
if facets.total_digits.is_some() {
names.push("totalDigits");
}
if facets.fraction_digits.is_some() {
names.push("fractionDigits");
}
if facets.explicit_timezone.is_some() {
names.push("explicitTimezone");
}
names.join(", ")
}
fn list_inapplicable_facets_for_union(facets: &FacetSet) -> String {
let mut names = Vec::new();
if facets.length.is_some() {
names.push("length");
}
if facets.min_length.is_some() {
names.push("minLength");
}
if facets.max_length.is_some() {
names.push("maxLength");
}
if facets.whitespace.is_some() {
names.push("whiteSpace");
}
if facets.min_inclusive.is_some() {
names.push("minInclusive");
}
if facets.max_inclusive.is_some() {
names.push("maxInclusive");
}
if facets.min_exclusive.is_some() {
names.push("minExclusive");
}
if facets.max_exclusive.is_some() {
names.push("maxExclusive");
}
if facets.total_digits.is_some() {
names.push("totalDigits");
}
if facets.fraction_digits.is_some() {
names.push("fractionDigits");
}
if facets.explicit_timezone.is_some() {
names.push("explicitTimezone");
}
names.join(", ")
}
fn validate_simple_restriction(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
stats: &mut DerivationStats,
) -> SchemaResult<()> {
let base_key = match type_def.resolved_base_type {
Some(key) => key,
None => return Ok(()), };
if let TypeKey::Complex(_) = base_key {
let (location, type_name) = type_error_context(schema_set, type_def);
return Err(SchemaError::structural(
"cos-st-restricts",
format!("Simple type '{}': base type must be a simple type definition (cos-st-restricts.1.1)", type_name),
location,
));
}
if let TypeKey::Simple(base_simple_key) = base_key {
reject_any_atomic_type(
schema_set,
type_def,
base_simple_key,
"cos-st-restricts",
"restrict",
)?;
}
stats.restrictions_validated += 1;
if let TypeKey::Simple(base_simple_key) = base_key {
if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
if base_type.final_derivation.contains_restriction() {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
format_type_name(schema_set, base_type.name, base_type.target_namespace);
return Err(SchemaError::structural(
"cos-st-restricts",
format!(
"Simple type '{}' cannot restrict '{}' because base type is final for restriction",
type_name, base_name
),
location,
));
}
}
}
let base_facets = get_type_facets(schema_set, base_key)?;
if let Some(ref base_facets) = base_facets {
type_def.facets.merge_with_base(base_facets).map_err(|e| {
let (location, type_name) = type_error_context(schema_set, type_def);
SchemaError::structural(
"cos-st-restricts",
format!("Simple type '{}' has invalid restriction: {}", type_name, e),
location,
)
})?;
}
validate_facet_values_against_base_type(schema_set, type_def, base_key)?;
Ok(())
}
fn validate_simple_list(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
) -> SchemaResult<()> {
if let Some(TypeKey::Simple(item_simple_key)) = type_def.resolved_item_type {
if let Some(item_type) = schema_set.arenas.simple_types.get(item_simple_key) {
if item_type.final_derivation.contains_list() {
let (location, type_name) = type_error_context(schema_set, type_def);
let item_name =
format_type_name(schema_set, item_type.name, item_type.target_namespace);
return Err(SchemaError::structural(
"cos-st-restricts",
format!(
"List type '{}' cannot use '{}' as item type because it is final for list",
type_name, item_name
),
location,
));
}
}
}
if let Some(TypeKey::Simple(base_simple_key)) = type_def.resolved_base_type {
if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
if base_type.final_derivation.contains_list() {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
format_type_name(schema_set, base_type.name, base_type.target_namespace);
return Err(SchemaError::structural(
"cos-st-restricts",
format!(
"List type '{}' cannot derive from '{}' because it is final for list",
type_name, base_name
),
location,
));
}
}
}
let item_key = match type_def.resolved_item_type {
Some(key) => key,
None => {
return Ok(());
}
};
match item_key {
TypeKey::Simple(simple_key) => {
reject_any_atomic_type(
schema_set,
type_def,
simple_key,
"cos-list-of-atomic",
"use as list item type",
)?;
if let Some(item_type) = schema_set.arenas.simple_types.get(simple_key) {
match item_type.variety {
SimpleTypeVariety::Atomic => {
}
SimpleTypeVariety::List => {
let (location, type_name) = type_error_context(schema_set, type_def);
return Err(SchemaError::structural(
"cos-list-of-atomic",
format!(
"List type '{}' has list item type, which is not allowed",
type_name
),
location,
));
}
SimpleTypeVariety::Union => {
if union_contains_list(schema_set, item_type) {
let (location, type_name) = type_error_context(schema_set, type_def);
return Err(SchemaError::structural(
"cos-list-of-atomic",
format!(
"List type '{}' has union item type containing list member",
type_name
),
location,
));
}
}
}
}
}
TypeKey::Complex(_) => {
let (location, type_name) = type_error_context(schema_set, type_def);
return Err(SchemaError::structural(
"cos-list-of-atomic",
format!(
"List type '{}' has complex item type, which is not allowed",
type_name
),
location,
));
}
}
Ok(())
}
fn union_contains_list(
schema_set: &SchemaSet,
union_type: &crate::arenas::SimpleTypeDefData,
) -> bool {
for member_key in &union_type.resolved_member_types {
if let TypeKey::Simple(simple_key) = member_key {
if let Some(member) = schema_set.arenas.simple_types.get(*simple_key) {
match member.variety {
SimpleTypeVariety::List => return true,
SimpleTypeVariety::Union => {
if union_contains_list(schema_set, member) {
return true;
}
}
SimpleTypeVariety::Atomic => {}
}
}
}
}
false
}
fn validate_simple_union(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
) -> SchemaResult<()> {
for member_key in &type_def.resolved_member_types {
if let TypeKey::Simple(simple_key) = member_key {
if let Some(member_type) = schema_set.arenas.simple_types.get(*simple_key) {
if member_type.final_derivation.contains_union() {
let (location, type_name) = type_error_context(schema_set, type_def);
let member_name = format_type_name(
schema_set,
member_type.name,
member_type.target_namespace,
);
return Err(SchemaError::structural(
"cos-st-restricts",
format!(
"Union type '{}' cannot use '{}' as member type because it is final for union",
type_name, member_name
),
location,
));
}
}
}
}
for member_key in &type_def.resolved_member_types {
match member_key {
TypeKey::Simple(simple_key) => {
reject_any_atomic_type(
schema_set,
type_def,
*simple_key,
"cos-union-memberTypes",
"use as union member type",
)?;
}
TypeKey::Complex(_) => {
let (location, type_name) = type_error_context(schema_set, type_def);
return Err(SchemaError::structural(
"cos-union-memberTypes",
format!(
"Union type '{}' has complex member type, which is not allowed",
type_name
),
location,
));
}
}
}
Ok(())
}
fn validate_complex_type(
schema_set: &SchemaSet,
key: ComplexTypeKey,
stats: &mut DerivationStats,
) -> SchemaResult<()> {
let type_def = schema_set
.arenas
.complex_types
.get(key)
.ok_or_else(|| SchemaError::internal("Complex type not found in arena"))?;
stats.complex_types_validated += 1;
match type_def.derivation_method {
Some(DerivationMethod::Extension) => {
stats.extensions_validated += 1;
validate_complex_extension(schema_set, key, type_def)?;
}
Some(DerivationMethod::Restriction) => {
stats.restrictions_validated += 1;
validate_complex_restriction(schema_set, type_def)?;
}
None => {
}
}
Ok(())
}
fn validate_complex_extension(
schema_set: &SchemaSet,
#[cfg_attr(not(feature = "xsd11"), allow(unused_variables))] derived_key: ComplexTypeKey,
type_def: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let base_key = match type_def.resolved_base_type {
Some(key) => key,
None => return Ok(()), };
match base_key {
TypeKey::Simple(base_simple_key) => {
if matches!(type_def.content, ComplexContentResult::Complex(_)) {
let (location, type_name) = type_error_context(schema_set, type_def);
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' cannot use complexContent extension from a simple type",
type_name,
),
location,
));
}
if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
if base_type.final_derivation.contains_extension() {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
format_type_name(schema_set, base_type.name, base_type.target_namespace);
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' cannot extend simple type '{}' because it is final for extension",
type_name, base_name,
),
location,
));
}
}
}
TypeKey::Complex(base_complex_key) => {
if let Some(base_type) = schema_set.arenas.complex_types.get(base_complex_key) {
if base_type.final_derivation.contains_extension() {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
format_type_name(schema_set, base_type.name, base_type.target_namespace);
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' cannot extend '{}' because base type is final for extension",
type_name, base_name
),
location,
));
}
if matches!(type_def.content, ComplexContentResult::Simple(_))
&& !matches!(base_type.content, ComplexContentResult::Simple(_))
{
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
format_type_name(schema_set, base_type.name, base_type.target_namespace);
return Err(SchemaError::structural(
"src-ct",
format!(
"Complex type '{}' uses xs:simpleContent extension but base '{}' \
does not have a simple {{content type}} (src-ct.2.1.1)",
type_name, base_name,
),
location,
));
}
if matches!(base_type.content, ComplexContentResult::Simple(_)) {
if let ComplexContentResult::Complex(ref complex) = type_def.content {
if complex.particle.is_some() || schema_set.is_xsd11() {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name = format_type_name(
schema_set,
base_type.name,
base_type.target_namespace,
);
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' cannot use complexContent to extend '{}' which has simpleContent{}",
type_name, base_name,
if complex.particle.is_some() { " with element content" }
else { " (XSD 1.1 cos-ct-extends clause 1.4)" },
),
location,
));
}
}
}
validate_extension_mixed_parity(schema_set, type_def, base_type)?;
if let ComplexContentResult::Complex(ref base_complex) = base_type.content {
if let Some(ref base_particle) = base_complex.particle {
if let ComplexContentResult::Complex(ref derived_complex) = type_def.content
{
if let Some(ref ext_particle) = derived_complex.particle {
let ext_compositor = match &ext_particle.term {
ParticleTerm::Group(mg) => mg.compositor,
_ => None,
};
let base_is_all = matches!(
base_particle.term,
ParticleTerm::Group(ModelGroupDefResult {
compositor: Some(Compositor::All),
..
})
);
match ext_compositor {
Some(Compositor::Sequence) => {
}
Some(Compositor::All)
if base_is_all
&& schema_set.xsd_version
== crate::schema::model::XsdVersion::V1_1 =>
{
}
Some(Compositor::Choice) if !base_is_all => {
}
Some(compositor @ (Compositor::All | Compositor::Choice)) => {
let location = type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
let type_name = format_type_name(
schema_set,
type_def.name,
type_def.target_namespace,
);
let base_name = format_type_name(
schema_set,
base_type.name,
base_type.target_namespace,
);
let (comp_name, reason) = match compositor {
Compositor::All => (
"all",
"the resulting content model would \
violate cos-all-limited placement \
constraints",
),
Compositor::Choice => (
"choice",
"the base type's xs:all particle would \
be nested inside a sequence, \
violating cos-all-limited.1",
),
Compositor::Sequence => unreachable!(),
};
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' cannot extend '{}' with \
an xs:{} compositor because the base type \
has non-empty content; {}",
type_name, base_name, comp_name, reason,
),
location,
));
}
None => {
}
}
}
}
}
}
#[cfg(feature = "xsd11")]
validate_open_content_extension(
schema_set,
derived_key,
type_def,
base_complex_key,
base_type,
)?;
}
}
}
Ok(())
}
fn validate_complex_restriction(
schema_set: &SchemaSet,
type_def: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let base_key = match type_def.resolved_base_type {
Some(key) => key,
None => return Ok(()), };
match base_key {
TypeKey::Simple(base_simple_key) => {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
if let Some(base_type) = schema_set.arenas.simple_types.get(base_simple_key) {
format_type_name(schema_set, base_type.name, base_type.target_namespace)
} else {
"(unknown)".to_string()
};
return Err(SchemaError::structural(
"ct-props-correct",
format!(
"Complex type '{}' cannot restrict simple type '{}'; \
derivation from a simple type must use extension",
type_name, base_name,
),
location,
));
}
TypeKey::Complex(base_complex_key) => {
if let Some(base_type) = schema_set.arenas.complex_types.get(base_complex_key) {
if base_type.final_derivation.contains_restriction() {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
format_type_name(schema_set, base_type.name, base_type.target_namespace);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' cannot restrict '{}' because base type is final for restriction",
type_name, base_name
),
location,
));
}
if let (
ComplexContentResult::Complex(base_complex),
ComplexContentResult::Complex(derived_complex),
) = (&base_type.content, &type_def.content)
{
let base_mixed = effective_mixed_of(base_type, base_complex);
let derived_mixed = effective_mixed_of(type_def, derived_complex);
if derived_mixed && !base_mixed {
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name = format_type_name(
schema_set,
base_type.name,
base_type.target_namespace,
);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' cannot restrict element-only base '{}' \
to mixed content (ยง3.4.6.4 clause 5.4.1)",
type_name, base_name,
),
location,
));
}
}
if matches!(type_def.content, ComplexContentResult::Simple(_))
&& !is_valid_simple_content_restriction_base(schema_set, base_type)
{
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name =
format_type_name(schema_set, base_type.name, base_type.target_namespace);
return Err(SchemaError::structural(
"src-ct",
format!(
"Complex type '{}' uses xs:simpleContent restriction but base '{}' \
does not have a simple {{content type}} nor mixed+emptiable content \
(src-ct.2.1.1 / 2.1.2)",
type_name, base_name,
),
location,
));
}
validate_content_particle_restriction(schema_set, type_def, base_type)?;
#[cfg(feature = "xsd11")]
validate_all_group_restriction_edc(schema_set, type_def, base_type)?;
#[cfg(feature = "xsd11")]
validate_open_content_restriction(schema_set, type_def, base_type)?;
validate_attribute_restriction(schema_set, type_def, base_type)?;
validate_simple_content_restriction(schema_set, type_def, base_type)?;
}
}
}
Ok(())
}
fn effective_mixed_of(
type_def: &crate::arenas::ComplexTypeDefData,
complex: &crate::parser::frames::ComplexContentDefResult,
) -> bool {
complex.mixed || type_def.mixed
}
fn validate_extension_mixed_parity(
schema_set: &SchemaSet,
type_def: &crate::arenas::ComplexTypeDefData,
base_type: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let ComplexContentResult::Complex(ref base_complex) = base_type.content else {
return Ok(());
};
let ComplexContentResult::Complex(ref derived_complex) = type_def.content else {
return Ok(());
};
let base_mixed = effective_mixed_of(base_type, base_complex);
let derived_mixed = effective_mixed_of(type_def, derived_complex);
if derived_mixed == base_mixed {
return Ok(());
}
if derived_complex.particle.is_none() && !derived_complex.mixed && !type_def.mixed {
return Ok(());
}
let (location, type_name) = type_error_context(schema_set, type_def);
let base_name = format_type_name(schema_set, base_type.name, base_type.target_namespace);
Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' cannot extend '{}' โ derived is {} but base is {} \
(cos-ct-extends clause 1.4.3.2.2.4.1)",
type_name,
base_name,
if derived_mixed {
"mixed"
} else {
"element-only"
},
if base_mixed { "mixed" } else { "element-only" },
),
location,
))
}
fn is_valid_simple_content_restriction_base(
schema_set: &SchemaSet,
base: &crate::arenas::ComplexTypeDefData,
) -> bool {
match &base.content {
ComplexContentResult::Simple(_) => true,
ComplexContentResult::Complex(complex) => {
if !complex.mixed {
return false;
}
match &complex.particle {
None => true,
Some(particle) => match normalize_type_particle(schema_set, base, particle) {
Ok(normalized) => particle_is_emptiable(&normalized),
Err(_) => false,
},
}
}
ComplexContentResult::Empty => base.mixed,
}
}
#[derive(Debug, Clone)]
struct NormalizedParticle {
term: NormalizedParticleTerm,
min_occurs: u32,
max_occurs: Option<u32>,
source: Option<SourceRef>,
}
#[derive(Debug, Clone)]
enum NormalizedParticleTerm {
Element(NormalizedElement),
Wildcard(Box<NormalizedWildcard>),
Group(NormalizedGroup),
}
#[derive(Debug, Clone)]
struct NormalizedElement {
name: NameId,
namespace: Option<NameId>,
type_key: TypeKey,
element_key: Option<ElementKey>,
block: DerivationSet,
nillable: bool,
fixed_value: Option<String>,
}
#[derive(Debug, Clone)]
struct NormalizedWildcard {
wildcard: WildcardResult,
target_namespace: Option<NameId>,
}
#[derive(Debug, Clone)]
struct NormalizedGroup {
compositor: Compositor,
particles: Vec<NormalizedParticle>,
}
struct ParticleNormalizer<'a> {
schema_set: &'a SchemaSet,
target_namespace: Option<NameId>,
resolved_types: &'a [Option<TypeKey>],
flat_index: usize,
depth: usize,
}
const MAX_PARTICLE_RESTRICTION_DEPTH: usize = 100;
impl<'a> ParticleNormalizer<'a> {
fn new(
schema_set: &'a SchemaSet,
target_namespace: Option<NameId>,
resolved_types: &'a [Option<TypeKey>],
) -> Self {
Self {
schema_set,
target_namespace,
resolved_types,
flat_index: 0,
depth: 0,
}
}
fn normalize_particle(
&mut self,
particle: &ParticleResult,
) -> SchemaResult<NormalizedParticle> {
if self.depth >= MAX_PARTICLE_RESTRICTION_DEPTH {
return Err(SchemaError::internal(
"particle restriction normalization exceeded recursion limit",
));
}
self.depth += 1;
let term = match &particle.term {
ParticleTerm::Element(elem) => {
let source = particle.source.as_ref().or(elem.source.as_ref());
NormalizedParticleTerm::Element(self.normalize_element(elem, source)?)
}
ParticleTerm::Any(wildcard) => {
NormalizedParticleTerm::Wildcard(Box::new(NormalizedWildcard {
wildcard: wildcard.clone(),
target_namespace: self.target_namespace,
}))
}
ParticleTerm::Group(group) => {
NormalizedParticleTerm::Group(self.normalize_group(group)?)
}
};
self.depth -= 1;
Ok(collapse_single_child_groups(NormalizedParticle {
term,
min_occurs: particle.min_occurs,
max_occurs: particle.max_occurs,
source: particle.source.clone(),
}))
}
fn normalize_element(
&mut self,
elem: &ElementFrameResult,
source: Option<&SourceRef>,
) -> SchemaResult<NormalizedElement> {
if let Some(ref_name) = &elem.ref_name {
let elem_key = self
.schema_set
.lookup_element(ref_name.namespace, ref_name.local_name);
let (type_key, block, nillable, fixed_value) = elem_key
.and_then(|key| self.schema_set.arenas.elements.get(key))
.map(|decl| {
let (eff_block, _) =
crate::compiler::substitution::effective_element_constraints(
self.schema_set,
decl,
);
let tk = decl
.resolved_type
.unwrap_or_else(|| TypeKey::Complex(self.schema_set.any_type_key()));
(tk, eff_block, decl.nillable, decl.fixed_value.clone())
})
.unwrap_or_else(|| {
(
TypeKey::Complex(self.schema_set.any_type_key()),
DerivationSet::empty(),
false,
None,
)
});
return Ok(NormalizedElement {
name: ref_name.local_name,
namespace: ref_name.namespace,
type_key,
element_key: elem_key,
block,
nillable,
fixed_value,
});
}
let name = elem
.name
.ok_or_else(|| SchemaError::internal("element particle missing name and ref"))?;
let index = self.flat_index;
self.flat_index += 1;
let namespace = self.schema_set.effective_local_element_namespace(
elem.target_namespace,
elem.form.as_deref(),
source,
self.target_namespace,
);
let type_key = self
.resolved_types
.get(index)
.copied()
.flatten()
.or_else(|| resolve_element_type_ref(self.schema_set, elem))
.unwrap_or_else(|| TypeKey::Complex(self.schema_set.any_type_key()));
let block = match elem.block {
Some(b) => b,
None => source
.and_then(|s| {
let doc_id = s.schema_defaults_doc.unwrap_or(s.doc_id);
self.schema_set
.documents
.get(doc_id as usize)
.map(|d| d.block_default)
})
.unwrap_or_default(),
};
Ok(NormalizedElement {
name,
namespace,
type_key,
element_key: None,
block,
nillable: elem.nillable,
fixed_value: elem.fixed_value.clone(),
})
}
fn normalize_group(&mut self, group: &ModelGroupDefResult) -> SchemaResult<NormalizedGroup> {
if let Some(ref_name) = &group.ref_name {
let group_key = self
.schema_set
.lookup_model_group(ref_name.namespace, ref_name.local_name)
.ok_or_else(|| SchemaError::internal("model group reference was not resolved"))?;
let group_data = self
.schema_set
.arenas
.get_model_group(group_key)
.ok_or_else(|| SchemaError::internal("resolved model group not found"))?;
let compositor = group_data
.compositor
.ok_or_else(|| SchemaError::internal("resolved model group missing compositor"))?;
let mut nested = ParticleNormalizer::new(
self.schema_set,
group_data.target_namespace,
&group_data.resolved_particle_types,
);
nested.depth = self.depth;
let particles = group_data
.particles
.iter()
.map(|particle| nested.normalize_particle(particle))
.collect::<SchemaResult<Vec<_>>>()?;
return Ok(NormalizedGroup {
compositor,
particles,
});
}
let compositor = group
.compositor
.ok_or_else(|| SchemaError::internal("inline model group missing compositor"))?;
let particles = group
.particles
.iter()
.map(|particle| self.normalize_particle(particle))
.collect::<SchemaResult<Vec<_>>>()?;
Ok(NormalizedGroup {
compositor,
particles,
})
}
}
fn resolve_element_type_ref(schema_set: &SchemaSet, elem: &ElementFrameResult) -> Option<TypeKey> {
match &elem.type_ref {
Some(crate::parser::frames::TypeRefResult::QName(qname)) => schema_set
.lookup_type(qname.namespace, qname.local_name)
.or_else(|| schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name)),
_ => None,
}
}
fn validate_content_particle_restriction(
schema_set: &SchemaSet,
derived: &crate::arenas::ComplexTypeDefData,
base: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let derived_particle = complex_content_particle(&derived.content);
let (effective_base, base_particle) = effective_base_content_particle(schema_set, base);
let location = derived
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
let type_name = format_type_name(schema_set, derived.name, derived.target_namespace);
let base_name = format_type_name(schema_set, base.name, base.target_namespace);
match (derived_particle, base_particle) {
(None, None) => Ok(()),
(Some(derived_particle), None) => {
let derived_particle = normalize_type_particle(schema_set, derived, derived_particle)?;
if !matches!(effective_base.content, ComplexContentResult::Simple(_))
&& is_effectively_empty(&derived_particle)
{
Ok(())
} else {
Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' adds particle content while restricting '{}' which has empty content",
type_name, base_name
),
location,
))
}
}
(None, Some(base_particle)) => {
let base_particle = normalize_type_particle(schema_set, effective_base, base_particle)?;
if particle_is_emptiable(&base_particle) {
Ok(())
} else {
Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' removes required particle content from base type '{}'",
type_name, base_name
),
location,
))
}
}
(Some(derived_particle), Some(base_particle)) => {
let derived_particle = normalize_type_particle(schema_set, derived, derived_particle)?;
let base_particle = normalize_type_particle(schema_set, effective_base, base_particle)?;
if is_effectively_empty(&derived_particle) {
if particle_is_emptiable(&base_particle) {
return Ok(());
} else {
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' removes required particle content from base type '{}'",
type_name, base_name
),
location,
));
}
}
if particle_restricts(schema_set, &derived_particle, &base_particle) {
Ok(())
} else {
Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Content model of '{}' is not a valid restriction of base type '{}'",
type_name, base_name
),
location,
))
}
}
}
}
fn is_empty_group(particle: &NormalizedParticle) -> bool {
matches!(&particle.term, NormalizedParticleTerm::Group(group) if group.particles.is_empty())
}
fn is_effectively_empty(particle: &NormalizedParticle) -> bool {
particle.max_occurs == Some(0) || is_empty_group(particle)
}
fn complex_content_particle(content: &ComplexContentResult) -> Option<&ParticleResult> {
match content {
ComplexContentResult::Complex(def) => def.particle.as_ref(),
ComplexContentResult::Empty | ComplexContentResult::Simple(_) => None,
}
}
fn effective_base_content_particle<'a>(
schema_set: &'a SchemaSet,
base: &'a crate::arenas::ComplexTypeDefData,
) -> (
&'a crate::arenas::ComplexTypeDefData,
Option<&'a ParticleResult>,
) {
let mut current = base;
let mut depth = 0;
loop {
if let Some(particle) = complex_content_particle(¤t.content) {
return (current, Some(particle));
}
if current.derivation_method != Some(DerivationMethod::Extension) {
return (current, None);
}
let Some(TypeKey::Complex(base_key)) = current.resolved_base_type else {
return (current, None);
};
let Some(base_type) = schema_set.arenas.complex_types.get(base_key) else {
return (current, None);
};
depth += 1;
if depth > 50 {
return (current, None); }
current = base_type;
}
}
fn normalize_type_particle(
schema_set: &SchemaSet,
type_def: &crate::arenas::ComplexTypeDefData,
particle: &ParticleResult,
) -> SchemaResult<NormalizedParticle> {
let mut normalizer = ParticleNormalizer::new(
schema_set,
type_def.target_namespace,
&type_def.resolved_content_particle_types,
);
let particle = normalizer.normalize_particle(particle)?;
let particle = remove_pointless_particles(particle);
if !schema_set.is_xsd11() {
let particle = flatten_same_compositor_groups(particle);
return Ok(particle);
}
Ok(particle)
}
fn normalize_model_group_as_particle(
schema_set: &SchemaSet,
group_data: &crate::arenas::ModelGroupData,
) -> SchemaResult<NormalizedParticle> {
let compositor = group_data
.compositor
.ok_or_else(|| SchemaError::internal("redefined named model group missing compositor"))?;
let mut normalizer = ParticleNormalizer::new(
schema_set,
group_data.target_namespace,
&group_data.resolved_particle_types,
);
let particles = group_data
.particles
.iter()
.map(|particle| normalizer.normalize_particle(particle))
.collect::<SchemaResult<Vec<_>>>()?;
let wrapper = NormalizedParticle {
term: NormalizedParticleTerm::Group(NormalizedGroup {
compositor,
particles,
}),
min_occurs: group_data.min_occurs,
max_occurs: group_data.max_occurs,
source: group_data.source.clone(),
};
let particle = collapse_single_child_groups(wrapper);
let particle = remove_pointless_particles(particle);
if !schema_set.is_xsd11() {
let particle = flatten_same_compositor_groups(particle);
return Ok(particle);
}
Ok(particle)
}
fn remove_pointless_particles(mut particle: NormalizedParticle) -> NormalizedParticle {
if let NormalizedParticleTerm::Group(group) = &mut particle.term {
group.particles = group
.particles
.drain(..)
.map(remove_pointless_particles)
.filter(|p| p.max_occurs != Some(0))
.collect();
}
particle
}
fn flatten_same_compositor_groups(mut particle: NormalizedParticle) -> NormalizedParticle {
if let NormalizedParticleTerm::Group(group) = &mut particle.term {
group.particles = group
.particles
.drain(..)
.map(flatten_same_compositor_groups)
.collect();
let parent_compositor = group.compositor;
let mut flattened = Vec::with_capacity(group.particles.len());
for child in group.particles.drain(..) {
if let NormalizedParticleTerm::Group(ref child_group) = child.term {
if child_group.compositor == parent_compositor
&& occurs_is_unit(child.min_occurs, child.max_occurs)
{
flattened.extend(child_group.particles.iter().cloned());
continue;
}
}
flattened.push(child);
}
group.particles = flattened;
}
particle
}
fn collapse_single_child_groups(mut particle: NormalizedParticle) -> NormalizedParticle {
if let NormalizedParticleTerm::Group(group) = &mut particle.term {
group.particles = group
.particles
.drain(..)
.map(collapse_single_child_groups)
.collect();
}
loop {
let child = match &particle.term {
NormalizedParticleTerm::Group(group)
if group.particles.len() == 1
&& can_collapse_single_child_group(
group.compositor,
particle.min_occurs,
particle.max_occurs,
&group.particles[0],
) =>
{
Some(group.particles[0].clone())
}
_ => None,
};
let Some(child) = child else {
return particle;
};
let (min_occurs, max_occurs) = multiply_occurs(
particle.min_occurs,
particle.max_occurs,
child.min_occurs,
child.max_occurs,
);
particle = NormalizedParticle {
term: child.term,
min_occurs,
max_occurs,
source: particle.source.clone().or(child.source),
};
}
}
fn can_collapse_single_child_group(
compositor: Compositor,
group_min_occurs: u32,
group_max_occurs: Option<u32>,
child: &NormalizedParticle,
) -> bool {
if compositor == Compositor::Choice {
return true;
}
occurs_is_unit(group_min_occurs, group_max_occurs) || child.max_occurs == Some(1)
}
fn occurs_is_unit(min_occurs: u32, max_occurs: Option<u32>) -> bool {
min_occurs == 1 && max_occurs == Some(1)
}
fn multiply_occurs(
left_min: u32,
left_max: Option<u32>,
right_min: u32,
right_max: Option<u32>,
) -> (u32, Option<u32>) {
let min_occurs = left_min.saturating_mul(right_min);
let max_occurs = match (left_max, right_max) {
(Some(left), Some(right)) => Some(left.saturating_mul(right)),
(Some(0), None) | (None, Some(0)) => Some(0),
_ => None,
};
(min_occurs, max_occurs)
}
fn fold_single_child_group(particle: &NormalizedParticle) -> Option<NormalizedParticle> {
if let NormalizedParticleTerm::Group(group) = &particle.term {
if group.particles.len() == 1
&& matches!(group.compositor, Compositor::Sequence | Compositor::All)
{
let child = &group.particles[0];
let (min_occurs, max_occurs) = multiply_occurs(
particle.min_occurs,
particle.max_occurs,
child.min_occurs,
child.max_occurs,
);
return Some(NormalizedParticle {
term: child.term.clone(),
min_occurs,
max_occurs,
source: particle.source.clone().or(child.source.clone()),
});
}
}
None
}
fn particle_restricts(
schema_set: &SchemaSet,
derived: &NormalizedParticle,
base: &NormalizedParticle,
) -> bool {
if schema_set.is_xsd11() {
if let Some(folded) = fold_single_child_group(derived) {
return particle_restricts(schema_set, &folded, base);
}
if let Some(folded_base) = fold_single_child_group(base) {
return particle_restricts(schema_set, derived, &folded_base);
}
}
if schema_set.is_xsd10()
&& derived.min_occurs == 0
&& !matches!(
&derived.term,
NormalizedParticleTerm::Group(group) if group.compositor == Compositor::Choice
)
&& matches!(
&base.term,
NormalizedParticleTerm::Group(group)
if group.compositor == Compositor::Choice
&& base.min_occurs == 0
&& base.max_occurs == Some(1)
&& group.particles.len() > 1
)
{
return false;
}
if let Some(base_branches) = expand_choice_branches(base) {
if let Some(derived_branches) = expand_choice_branches(derived) {
if schema_set.is_xsd10() {
return choice_branches_restrict_ordered(
schema_set,
&derived_branches,
&base_branches,
);
}
let base_has_emptiable_branch = base_branches.iter().any(particle_is_emptiable);
return derived_branches.iter().all(|branch| {
if base_branches
.iter()
.any(|candidate| particle_restricts(schema_set, branch, candidate))
{
return true;
}
if branch.min_occurs == 0 && base_has_emptiable_branch {
let mut non_empty = branch.clone();
non_empty.min_occurs = non_empty.min_occurs.max(1);
if non_empty.max_occurs.is_some_and(|m| m == 0) {
return true;
}
return base_branches
.iter()
.any(|candidate| particle_restricts(schema_set, &non_empty, candidate));
}
false
});
}
if let NormalizedParticleTerm::Group(derived_group) = &derived.term {
if derived_group.compositor == Compositor::Sequence {
if schema_set.is_xsd11() {
let any_branch = base_branches
.iter()
.any(|candidate| particle_restricts(schema_set, derived, candidate));
if any_branch {
return true;
}
}
let NormalizedParticleTerm::Group(base_group) = &base.term else {
unreachable!()
};
return sequence_restricts_choice(
schema_set,
derived,
derived_group,
base,
base_group,
);
}
}
return base_branches
.iter()
.any(|candidate| particle_restricts(schema_set, derived, candidate));
}
if let Some(derived_branches) = expand_choice_branches(derived) {
return derived_branches
.iter()
.all(|branch| particle_restricts(schema_set, branch, base));
}
match (&derived.term, &base.term) {
(
NormalizedParticleTerm::Element(derived_element),
NormalizedParticleTerm::Element(base_element),
) => {
let names_match = derived_element.name == base_element.name
&& derived_element.namespace == base_element.namespace;
let subst_match = !names_match
&& match (derived_element.element_key, base_element.element_key) {
(Some(d_key), Some(b_key)) => {
crate::compiler::substitution::is_element_substitutable_for(
schema_set, b_key, d_key,
)
}
_ => false,
};
(names_match || subst_match)
&& occurs_range_is_subset(
derived.min_occurs,
derived.max_occurs,
base.min_occurs,
base.max_occurs,
)
&& (base_element.nillable || !derived_element.nillable)
&& match (&base_element.fixed_value, &derived_element.fixed_value) {
(None, _) => true,
(Some(_), None) => false,
(Some(base_fixed), Some(derived_fixed)) => {
crate::validation::simple::fixed_values_equal(
derived_fixed,
base_fixed,
Some(derived_element.type_key),
schema_set,
)
}
}
&& derived_element.block.element_block_mask()
.contains(base_element.block.element_block_mask())
&& schema_set.is_type_derived_from(
derived_element.type_key,
base_element.type_key,
DerivationSet::extension(),
)
}
(
NormalizedParticleTerm::Element(element),
NormalizedParticleTerm::Wildcard(base_wildcard),
) => {
occurs_range_is_subset(
derived.min_occurs,
derived.max_occurs,
base.min_occurs,
base.max_occurs,
) && wildcard_allows_element(element, base_wildcard)
}
(
NormalizedParticleTerm::Wildcard(derived_wildcard),
NormalizedParticleTerm::Wildcard(base_wildcard),
) => {
occurs_range_is_subset(
derived.min_occurs,
derived.max_occurs,
base.min_occurs,
base.max_occurs,
) && wildcard_restricts(derived_wildcard, base_wildcard)
}
(
NormalizedParticleTerm::Group(derived_group),
NormalizedParticleTerm::Wildcard(base_wildcard),
) => group_particle_restricts_wildcard(derived, derived_group, base, base_wildcard),
(
NormalizedParticleTerm::Group(derived_group),
NormalizedParticleTerm::Group(base_group),
) if derived_group.compositor == base_group.compositor => {
if !occurs_range_is_subset(
derived.min_occurs,
derived.max_occurs,
base.min_occurs,
base.max_occurs,
) {
return false;
}
match derived_group.compositor {
Compositor::Sequence => sequence_particles_restrict(
schema_set,
&derived_group.particles,
&base_group.particles,
),
Compositor::All => all_particles_restrict(
schema_set,
&derived_group.particles,
&base_group.particles,
),
Compositor::Choice => unreachable!("choice particles are handled earlier"),
}
}
(
NormalizedParticleTerm::Group(derived_group),
NormalizedParticleTerm::Group(base_group),
) if derived_group.compositor == Compositor::Sequence
&& base_group.compositor == Compositor::All =>
{
if !occurs_range_is_subset(
derived.min_occurs,
derived.max_occurs,
base.min_occurs,
base.max_occurs,
) {
return false;
}
recurse_unordered(schema_set, &derived_group.particles, &base_group.particles)
}
(
NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_),
NormalizedParticleTerm::Group(base_group),
) if base_group.compositor == Compositor::Sequence => {
occurs_range_is_subset(1, Some(1), base.min_occurs, base.max_occurs)
&& sequence_particles_restrict(
schema_set,
std::slice::from_ref(derived),
&base_group.particles,
)
}
(
NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_),
NormalizedParticleTerm::Group(base_group),
) if base_group.compositor == Compositor::All => {
occurs_range_is_subset(1, Some(1), base.min_occurs, base.max_occurs)
&& all_particles_restrict(
schema_set,
std::slice::from_ref(derived),
&base_group.particles,
)
}
_ => false,
}
}
fn group_particle_restricts_wildcard(
derived: &NormalizedParticle,
group: &NormalizedGroup,
base: &NormalizedParticle,
wildcard: &NormalizedWildcard,
) -> bool {
let (derived_min, derived_max) = particle_total_occurrence_range(derived);
if !occurs_range_is_subset(derived_min, derived_max, base.min_occurs, base.max_occurs) {
return false;
}
group_particles_fit_wildcard(&group.particles, wildcard)
}
fn occurs_range_is_subset(
derived_min: u32,
derived_max: Option<u32>,
base_min: u32,
base_max: Option<u32>,
) -> bool {
if derived_min < base_min {
return false;
}
match (derived_max, base_max) {
(_, None) => true,
(Some(derived), Some(base)) => derived <= base,
(None, Some(_)) => false,
}
}
fn expand_choice_branches(particle: &NormalizedParticle) -> Option<Vec<NormalizedParticle>> {
let NormalizedParticleTerm::Group(group) = &particle.term else {
return None;
};
if group.compositor != Compositor::Choice {
return None;
}
Some(
group
.particles
.iter()
.map(|child| {
let (min_occurs, max_occurs) = multiply_occurs(
particle.min_occurs,
particle.max_occurs,
child.min_occurs,
child.max_occurs,
);
collapse_single_child_groups(NormalizedParticle {
term: child.term.clone(),
min_occurs,
max_occurs,
source: particle.source.clone().or(child.source.clone()),
})
})
.collect(),
)
}
fn particle_total_occurrence_range(particle: &NormalizedParticle) -> (u32, Option<u32>) {
let (term_min, term_max) = match &particle.term {
NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_) => (1, Some(1)),
NormalizedParticleTerm::Group(group) => match group.compositor {
Compositor::Sequence | Compositor::All => {
group
.particles
.iter()
.fold((0u32, Some(0u32)), |(acc_min, acc_max), child| {
let (child_min, child_max) = particle_total_occurrence_range(child);
(
acc_min.saturating_add(child_min),
add_optional_occurs(acc_max, child_max),
)
})
}
Compositor::Choice => {
let mut min_total: Option<u32> = None;
let mut max_total: Option<Option<u32>> = None;
for child in &group.particles {
let (child_min, child_max) = particle_total_occurrence_range(child);
min_total = Some(match min_total {
Some(current) => current.min(child_min),
None => child_min,
});
max_total = Some(match max_total {
Some(current) => max_optional_occurs(current, child_max),
None => child_max,
});
}
(min_total.unwrap_or(0), max_total.unwrap_or(Some(0)))
}
},
};
multiply_occurs(particle.min_occurs, particle.max_occurs, term_min, term_max)
}
fn add_optional_occurs(left: Option<u32>, right: Option<u32>) -> Option<u32> {
match (left, right) {
(Some(left), Some(right)) => Some(left.saturating_add(right)),
_ => None,
}
}
fn max_optional_occurs(left: Option<u32>, right: Option<u32>) -> Option<u32> {
match (left, right) {
(Some(left), Some(right)) => Some(left.max(right)),
_ => None,
}
}
fn group_particles_fit_wildcard(
particles: &[NormalizedParticle],
wildcard: &NormalizedWildcard,
) -> bool {
particles
.iter()
.all(|particle| particle_fits_wildcard(particle, wildcard))
}
fn particle_fits_wildcard(particle: &NormalizedParticle, wildcard: &NormalizedWildcard) -> bool {
if let Some(branches) = expand_choice_branches(particle) {
return branches
.iter()
.all(|branch| particle_fits_wildcard(branch, wildcard));
}
match &particle.term {
NormalizedParticleTerm::Element(element) => wildcard_allows_element(element, wildcard),
NormalizedParticleTerm::Wildcard(derived_wildcard) => {
wildcard_restricts(derived_wildcard, wildcard)
}
NormalizedParticleTerm::Group(group) => {
group_particles_fit_wildcard(&group.particles, wildcard)
}
}
}
fn sequence_restricts_choice(
schema_set: &SchemaSet,
derived: &NormalizedParticle,
derived_group: &NormalizedGroup,
base: &NormalizedParticle,
base_group: &NormalizedGroup,
) -> bool {
let base_branches = &base_group.particles;
let mut required_per_iter: u32 = 0;
let mut total_per_iter: u32 = 0;
for derived_child in &derived_group.particles {
let found = base_branches
.iter()
.any(|branch| particle_restricts(schema_set, derived_child, branch));
if !found {
return false;
}
if derived_child.min_occurs > 0 {
required_per_iter += 1;
}
if derived_child.max_occurs != Some(0) {
total_per_iter += 1;
}
}
let min_demand = derived.min_occurs.saturating_mul(required_per_iter);
let max_demand = match derived.max_occurs {
Some(m) => Some(m.saturating_mul(total_per_iter)),
None => {
if total_per_iter == 0 {
Some(0)
} else {
None
}
}
};
occurs_range_is_subset(min_demand, max_demand, base.min_occurs, base.max_occurs)
}
fn choice_branches_restrict_ordered(
schema_set: &SchemaSet,
derived_branches: &[NormalizedParticle],
base_branches: &[NormalizedParticle],
) -> bool {
let mut base_index = 0;
for derived in derived_branches {
let mut found = false;
while base_index < base_branches.len() {
if particle_restricts(schema_set, derived, &base_branches[base_index]) {
base_index += 1;
found = true;
break;
}
base_index += 1;
}
if !found {
return false;
}
}
true
}
fn sequence_particles_restrict(
schema_set: &SchemaSet,
derived_particles: &[NormalizedParticle],
base_particles: &[NormalizedParticle],
) -> bool {
let mut base_index = 0;
let mut derived_index = 0;
while derived_index < derived_particles.len() {
let mut matched = false;
while let Some(base) = base_particles.get(base_index) {
if particle_restricts(schema_set, &derived_particles[derived_index], base) {
matched = true;
base_index += 1;
derived_index += 1;
break;
}
if let NormalizedParticleTerm::Group(base_group) = &base.term {
if base_group.compositor == Compositor::Sequence && !base_group.particles.is_empty()
{
let unit_len = base_group.particles.len();
let remaining = derived_particles.len() - derived_index;
if remaining >= unit_len
&& sequence_particles_restrict(
schema_set,
&derived_particles[derived_index..derived_index + unit_len],
&base_group.particles,
)
{
matched = true;
base_index += 1;
derived_index += unit_len;
break;
}
}
}
if schema_set.is_xsd11() {
if let Some(branches) = expand_choice_branches(&derived_particles[derived_index]) {
let all_ok = branches.iter().all(|branch| {
let mut remaining = vec![branch.clone()];
remaining.extend_from_slice(&derived_particles[derived_index + 1..]);
sequence_particles_restrict(
schema_set,
&remaining,
&base_particles[base_index..],
)
});
if all_ok {
return true;
}
}
}
if schema_set.is_xsd11() {
if let NormalizedParticleTerm::Group(dg) = &derived_particles[derived_index].term {
if dg.compositor == Compositor::Sequence
&& occurs_is_unit(
derived_particles[derived_index].min_occurs,
derived_particles[derived_index].max_occurs,
)
&& !dg.particles.is_empty()
{
let mut inlined = dg.particles.clone();
inlined.extend_from_slice(&derived_particles[derived_index + 1..]);
if sequence_particles_restrict(
schema_set,
&inlined,
&base_particles[base_index..],
) {
return true;
}
}
}
}
if particle_is_emptiable(base) {
base_index += 1;
continue;
}
return false;
}
if !matched {
return false;
}
}
base_particles[base_index..]
.iter()
.all(particle_is_emptiable)
}
fn merge_duplicate_elements(particles: &[NormalizedParticle]) -> Vec<NormalizedParticle> {
let mut merged: Vec<NormalizedParticle> = Vec::with_capacity(particles.len());
for particle in particles {
let NormalizedParticleTerm::Element(elem) = &particle.term else {
merged.push(particle.clone());
continue;
};
let existing = merged.iter_mut().find(|m| {
matches!(
&m.term,
NormalizedParticleTerm::Element(other)
if other.name == elem.name && other.namespace == elem.namespace
)
});
if let Some(existing) = existing {
existing.min_occurs = existing.min_occurs.saturating_add(particle.min_occurs);
existing.max_occurs = match (existing.max_occurs, particle.max_occurs) {
(None, _) | (_, None) => None,
(Some(a), Some(b)) => Some(a.saturating_add(b)),
};
} else {
merged.push(particle.clone());
}
}
merged
}
fn recurse_unordered(
schema_set: &SchemaSet,
derived_particles: &[NormalizedParticle],
base_particles: &[NormalizedParticle],
) -> bool {
if let Some(result) = try_count_based_subsumption(schema_set, derived_particles, base_particles)
{
if result {
return true;
}
}
fn backtrack(
schema_set: &SchemaSet,
derived_particles: &[NormalizedParticle],
base_particles: &[NormalizedParticle],
used: &mut [bool],
derived_index: usize,
) -> bool {
if derived_index == derived_particles.len() {
return base_particles
.iter()
.enumerate()
.all(|(index, particle)| used[index] || particle_is_emptiable(particle));
}
for (base_index, base_particle) in base_particles.iter().enumerate() {
if used[base_index]
|| !particle_restricts(schema_set, &derived_particles[derived_index], base_particle)
{
continue;
}
used[base_index] = true;
if backtrack(
schema_set,
derived_particles,
base_particles,
used,
derived_index + 1,
) {
return true;
}
used[base_index] = false;
}
false
}
let merged_owned;
let derived_particles = if derived_particles
.iter()
.any(|p| matches!(&p.term, NormalizedParticleTerm::Element(_)))
&& has_duplicate_element_names(derived_particles)
{
merged_owned = merge_duplicate_elements(derived_particles);
&merged_owned[..]
} else {
derived_particles
};
let mut used = vec![false; base_particles.len()];
backtrack(schema_set, derived_particles, base_particles, &mut used, 0)
}
fn try_count_based_subsumption(
schema_set: &SchemaSet,
derived_particles: &[NormalizedParticle],
base_particles: &[NormalizedParticle],
) -> Option<bool> {
let expanded = expand_top_level_choices_for_unordered(derived_particles)?;
let mut buckets: Vec<Vec<(u32, Option<u32>)>> = vec![Vec::new(); base_particles.len()];
for derived in &expanded {
if matches!(&derived.term, NormalizedParticleTerm::Group(_)) {
return None;
}
match find_subsumption_bucket(schema_set, derived, base_particles)? {
BucketAssignment::Single(idx) => {
buckets[idx].push((derived.min_occurs, derived.max_occurs))
}
BucketAssignment::Partition(idxs) => {
for &i in &idxs {
let base = &base_particles[i];
if base.min_occurs > 0 {
return Some(false);
}
if !occurs_max_fits(derived.max_occurs, base.max_occurs) {
return Some(false);
}
}
for &i in &idxs {
buckets[i].push((0, derived.max_occurs));
}
}
BucketAssignment::None => return Some(false),
}
}
for (i, ranges) in buckets.iter().enumerate() {
let base = &base_particles[i];
if ranges.is_empty() {
if !particle_is_emptiable(base) {
return Some(false);
}
} else {
let (sum_min, sum_max) =
ranges
.iter()
.fold((0u32, Some(0u32)), |(amin, amax), &(min, max)| {
(amin.saturating_add(min), add_optional_occurs(amax, max))
});
if !occurs_range_is_subset(sum_min, sum_max, base.min_occurs, base.max_occurs) {
return Some(false);
}
}
}
Some(true)
}
fn expand_top_level_choices_for_unordered(
particles: &[NormalizedParticle],
) -> Option<Vec<NormalizedParticle>> {
let mut result = Vec::with_capacity(particles.len());
for p in particles {
match &p.term {
NormalizedParticleTerm::Group(group) if group.compositor == Compositor::Choice => {
let outer_max = p.max_occurs;
for branch in &group.particles {
if matches!(&branch.term, NormalizedParticleTerm::Group(_)) {
return None;
}
let new_max = match (outer_max, branch.max_occurs) {
(Some(om), Some(bm)) => Some(om.saturating_mul(bm)),
_ => None,
};
result.push(NormalizedParticle {
term: branch.term.clone(),
min_occurs: 0,
max_occurs: new_max,
source: branch.source.clone(),
});
}
}
NormalizedParticleTerm::Group(_) => {
return None;
}
_ => result.push(p.clone()),
}
}
Some(result)
}
#[derive(Debug, Clone)]
enum BucketAssignment {
Single(usize),
Partition(Vec<usize>),
None,
}
fn find_subsumption_bucket(
schema_set: &SchemaSet,
derived: &NormalizedParticle,
base_particles: &[NormalizedParticle],
) -> Option<BucketAssignment> {
match &derived.term {
NormalizedParticleTerm::Element(d_elem) => {
for (i, base) in base_particles.iter().enumerate() {
if let NormalizedParticleTerm::Element(b_elem) = &base.term {
if d_elem.name == b_elem.name
&& d_elem.namespace == b_elem.namespace
&& name_and_type_ok_no_occurs(schema_set, d_elem, b_elem)
{
return Some(BucketAssignment::Single(i));
}
}
}
for (i, base) in base_particles.iter().enumerate() {
if let NormalizedParticleTerm::Element(b_elem) = &base.term {
if d_elem.name == b_elem.name && d_elem.namespace == b_elem.namespace {
continue; }
if derived_element_substitutes_base(schema_set, d_elem, b_elem)
&& name_and_type_ok_no_occurs(schema_set, d_elem, b_elem)
{
return Some(BucketAssignment::Single(i));
}
}
}
for (i, base) in base_particles.iter().enumerate() {
if let NormalizedParticleTerm::Wildcard(b_wc) = &base.term {
if wildcard_allows_element(d_elem, b_wc) {
return Some(BucketAssignment::Single(i));
}
}
}
Some(BucketAssignment::None)
}
NormalizedParticleTerm::Wildcard(d_wc) => {
for (i, base) in base_particles.iter().enumerate() {
if let NormalizedParticleTerm::Wildcard(b_wc) = &base.term {
if wildcard_restricts(d_wc, b_wc) {
return Some(BucketAssignment::Single(i));
}
}
}
let candidate_idxs: Vec<usize> = base_particles
.iter()
.enumerate()
.filter_map(|(i, base)| {
let NormalizedParticleTerm::Wildcard(b_wc) = &base.term else {
return None;
};
if process_contents_strictness(d_wc.wildcard.process_contents)
< process_contents_strictness(b_wc.wildcard.process_contents)
{
return None;
}
Some(i)
})
.collect();
if candidate_idxs.len() >= 2 {
let bases: Vec<&NormalizedWildcard> = candidate_idxs
.iter()
.map(|&i| match &base_particles[i].term {
NormalizedParticleTerm::Wildcard(b_wc) => b_wc.as_ref(),
_ => unreachable!(),
})
.collect();
if let Some(spanned) = wildcard_subset_of_union(d_wc, &bases) {
let idxs: Vec<usize> = spanned
.into_iter()
.map(|local_idx| candidate_idxs[local_idx])
.collect();
return Some(BucketAssignment::Partition(idxs));
}
}
Some(BucketAssignment::None)
}
NormalizedParticleTerm::Group(_) => None,
}
}
fn occurs_max_fits(derived_max: Option<u32>, base_max: Option<u32>) -> bool {
match (derived_max, base_max) {
(_, None) => true,
(None, Some(_)) => false,
(Some(d), Some(b)) => d <= b,
}
}
fn name_and_type_ok_no_occurs(
schema_set: &SchemaSet,
derived: &NormalizedElement,
base: &NormalizedElement,
) -> bool {
if derived.nillable && !base.nillable {
return false;
}
match (&base.fixed_value, &derived.fixed_value) {
(None, _) => {}
(Some(_), None) => return false,
(Some(base_fixed), Some(derived_fixed)) => {
if !crate::validation::simple::fixed_values_equal(
derived_fixed,
base_fixed,
Some(derived.type_key),
schema_set,
) {
return false;
}
}
}
if !derived
.block
.element_block_mask()
.contains(base.block.element_block_mask())
{
return false;
}
schema_set.is_type_derived_from(derived.type_key, base.type_key, DerivationSet::extension())
}
fn derived_element_substitutes_base(
schema_set: &SchemaSet,
derived: &NormalizedElement,
base: &NormalizedElement,
) -> bool {
let d_key = derived
.element_key
.or_else(|| schema_set.lookup_element(derived.namespace, derived.name));
let b_key = base
.element_key
.or_else(|| schema_set.lookup_element(base.namespace, base.name));
match (d_key, b_key) {
(Some(d), Some(b)) => {
crate::compiler::substitution::is_element_substitutable_for(schema_set, b, d)
}
_ => false,
}
}
fn has_duplicate_element_names(particles: &[NormalizedParticle]) -> bool {
for (i, a) in particles.iter().enumerate() {
let NormalizedParticleTerm::Element(a_elem) = &a.term else {
continue;
};
for b in &particles[i + 1..] {
if let NormalizedParticleTerm::Element(b_elem) = &b.term {
if a_elem.name == b_elem.name && a_elem.namespace == b_elem.namespace {
return true;
}
}
}
}
false
}
fn all_particles_restrict(
schema_set: &SchemaSet,
derived_particles: &[NormalizedParticle],
base_particles: &[NormalizedParticle],
) -> bool {
if schema_set.is_xsd10() {
return sequence_particles_restrict(schema_set, derived_particles, base_particles);
}
recurse_unordered(schema_set, derived_particles, base_particles)
}
fn particle_is_emptiable(particle: &NormalizedParticle) -> bool {
if particle.min_occurs == 0 {
return true;
}
match &particle.term {
NormalizedParticleTerm::Element(_) | NormalizedParticleTerm::Wildcard(_) => false,
NormalizedParticleTerm::Group(group) => match group.compositor {
Compositor::Sequence | Compositor::All => {
group.particles.iter().all(particle_is_emptiable)
}
Compositor::Choice => group.particles.iter().any(particle_is_emptiable),
},
}
}
fn wildcard_restricts(derived: &NormalizedWildcard, base: &NormalizedWildcard) -> bool {
is_wildcard_ns_subset(
&derived.wildcard,
derived.target_namespace,
&base.wildcard,
base.target_namespace,
) && process_contents_strictness(derived.wildcard.process_contents)
>= process_contents_strictness(base.wildcard.process_contents)
}
fn wildcard_subset_of_union(
derived: &NormalizedWildcard,
bases: &[&NormalizedWildcard],
) -> Option<Vec<usize>> {
use crate::parser::frames::NotQNameItem;
let derived_target = derived.target_namespace;
let mut explicit_namespaces: Vec<Option<NameId>> = Vec::new();
let push_ns = |ns: Option<NameId>, out: &mut Vec<Option<NameId>>| {
if !out.contains(&ns) {
out.push(ns);
}
};
let collect_namespaces = |wc: &NormalizedWildcard, out: &mut Vec<Option<NameId>>| {
let target = wc.target_namespace;
match &wc.wildcard.namespace {
WildcardNamespace::TargetNamespace => {
if !out.contains(&target) {
out.push(target);
}
}
WildcardNamespace::Local => {
if !out.contains(&None) {
out.push(None);
}
}
WildcardNamespace::List(tokens) => {
for t in tokens {
let ns = t.resolve(target);
if !out.contains(&ns) {
out.push(ns);
}
}
}
_ => {}
}
for t in &wc.wildcard.not_namespace {
let ns = t.resolve(target);
if !out.contains(&ns) {
out.push(ns);
}
}
for item in &wc.wildcard.not_qname {
if let NotQNameItem::QName { namespace, .. } = item {
if !out.contains(namespace) {
out.push(*namespace);
}
}
}
};
collect_namespaces(derived, &mut explicit_namespaces);
for base in bases {
collect_namespaces(base, &mut explicit_namespaces);
}
push_ns(derived_target, &mut explicit_namespaces);
for base in bases {
push_ns(base.target_namespace, &mut explicit_namespaces);
}
push_ns(None, &mut explicit_namespaces);
let mut spanned: Vec<usize> = Vec::new();
let check_namespace = |ns: Option<NameId>,
is_explicit_ns: bool,
bases: &[&NormalizedWildcard],
spanned: &mut Vec<usize>|
-> bool {
if !wildcard_admits_ns(derived, ns) {
return true;
}
let admitting: Vec<usize> = (0..bases.len())
.filter(|&i| wildcard_admits_ns(bases[i], ns))
.collect();
if admitting.is_empty() {
return false;
}
let mut name_witnesses: Vec<NameId> = Vec::new();
let push_name = |n: NameId, out: &mut Vec<NameId>| {
if !out.contains(&n) {
out.push(n);
}
};
for item in &derived.wildcard.not_qname {
if let NotQNameItem::QName {
namespace,
local_name,
} = item
{
if *namespace == ns {
push_name(*local_name, &mut name_witnesses);
}
}
}
for &i in &admitting {
for item in &bases[i].wildcard.not_qname {
if let NotQNameItem::QName {
namespace,
local_name,
} = item
{
if *namespace == ns {
push_name(*local_name, &mut name_witnesses);
}
}
}
}
for name in &name_witnesses {
if !wildcard_admits_qname(derived, ns, *name) {
continue;
}
let any_admits = admitting
.iter()
.any(|&i| wildcard_admits_qname(bases[i], ns, *name));
if !any_admits {
return false;
}
}
let derived_admits_other = wildcard_admits_qname_symbolic_other(derived, ns);
if derived_admits_other {
let any_admits_other = admitting
.iter()
.any(|&i| wildcard_admits_qname_symbolic_other(bases[i], ns));
if !any_admits_other {
return false;
}
}
if is_explicit_ns {
for &i in &admitting {
if !spanned.contains(&i) {
spanned.push(i);
}
}
}
true
};
for ns in &explicit_namespaces {
if !check_namespace(*ns, true, bases, &mut spanned) {
return None;
}
}
let fresh_ns = Some(NameId(u32::MAX));
let derived_admits_fresh =
wildcard_admits_ns(derived, fresh_ns) || wildcard_admits_ns(derived, None);
if derived_admits_fresh {
let mut fresh_bases: Vec<usize> = (0..bases.len())
.filter(|&i| wildcard_admits_ns(bases[i], fresh_ns))
.collect();
if wildcard_admits_ns(derived, fresh_ns) && fresh_bases.is_empty() {
return None;
}
for &i in &fresh_bases {
if !spanned.contains(&i) {
spanned.push(i);
}
}
fresh_bases.clear();
}
if spanned.is_empty() {
return Some(spanned);
}
Some(spanned)
}
fn wildcard_admits_ns(wc: &NormalizedWildcard, ns: Option<NameId>) -> bool {
if !wildcard_namespace_matches(&wc.wildcard.namespace, ns, wc.target_namespace) {
return false;
}
!wc.wildcard
.not_namespace
.iter()
.any(|t| t.resolve(wc.target_namespace) == ns)
}
fn wildcard_admits_qname(wc: &NormalizedWildcard, ns: Option<NameId>, name: NameId) -> bool {
use crate::parser::frames::NotQNameItem;
if !wildcard_admits_ns(wc, ns) {
return false;
}
!wc.wildcard.not_qname.iter().any(|item| match item {
NotQNameItem::QName {
namespace,
local_name,
} => *namespace == ns && *local_name == name,
NotQNameItem::Defined | NotQNameItem::DefinedSibling => true,
})
}
fn wildcard_admits_qname_symbolic_other(wc: &NormalizedWildcard, ns: Option<NameId>) -> bool {
use crate::parser::frames::NotQNameItem;
if !wildcard_admits_ns(wc, ns) {
return false;
}
!wc.wildcard
.not_qname
.iter()
.any(|item| matches!(item, NotQNameItem::Defined | NotQNameItem::DefinedSibling))
}
fn wildcard_allows_element(element: &NormalizedElement, wildcard: &NormalizedWildcard) -> bool {
if !wildcard_namespace_matches(
&wildcard.wildcard.namespace,
element.namespace,
wildcard.target_namespace,
) {
return false;
}
let excluded_namespace = wildcard
.wildcard
.not_namespace
.iter()
.map(|token| token.resolve(wildcard.target_namespace))
.any(|namespace| namespace == element.namespace);
if excluded_namespace {
return false;
}
!wildcard_not_qname_excludes(
&wildcard.wildcard.not_qname,
element.namespace,
element.name,
)
}
fn wildcard_namespace_matches(
namespace: &WildcardNamespace,
element_namespace: Option<NameId>,
target_namespace: Option<NameId>,
) -> bool {
match namespace {
WildcardNamespace::Any => true,
WildcardNamespace::Other => {
!other_exclusion_set(target_namespace).contains(&element_namespace)
}
WildcardNamespace::TargetNamespace => element_namespace == target_namespace,
WildcardNamespace::Local => element_namespace.is_none(),
WildcardNamespace::List(tokens) => tokens
.iter()
.map(|token| token.resolve(target_namespace))
.any(|resolved| resolved == element_namespace),
}
}
fn wildcard_not_qname_excludes(
not_qname: &[crate::parser::frames::NotQNameItem],
namespace: Option<NameId>,
local_name: NameId,
) -> bool {
not_qname.iter().any(|item| match item {
crate::parser::frames::NotQNameItem::QName {
namespace: excluded_ns,
local_name: excluded_name,
} => *excluded_ns == namespace && *excluded_name == local_name,
crate::parser::frames::NotQNameItem::Defined => true,
crate::parser::frames::NotQNameItem::DefinedSibling => false,
})
}
#[cfg(feature = "xsd11")]
fn effective_open_content(oc: Option<&OpenContentResult>) -> Option<&OpenContentResult> {
oc.filter(|o| o.mode != OpenContentMode::None)
}
fn process_contents_strictness(pc: ProcessContents) -> u8 {
match pc {
ProcessContents::Strict => 2,
ProcessContents::Lax => 1,
ProcessContents::Skip => 0,
}
}
fn other_exclusion_set(target_ns: Option<NameId>) -> Vec<Option<NameId>> {
match target_ns {
Some(ns) => vec![Some(ns), None],
None => vec![None],
}
}
fn resolve_ns_set(
wns: &WildcardNamespace,
target_ns: Option<NameId>,
) -> Option<Vec<Option<NameId>>> {
match wns {
WildcardNamespace::Any | WildcardNamespace::Other => None,
WildcardNamespace::TargetNamespace => Some(vec![target_ns]),
WildcardNamespace::Local => Some(vec![None]),
WildcardNamespace::List(tokens) => {
Some(tokens.iter().map(|t| t.resolve(target_ns)).collect())
}
}
}
fn is_namespace_subset(
derived: &WildcardNamespace,
derived_target_ns: Option<NameId>,
base: &WildcardNamespace,
base_target_ns: Option<NameId>,
) -> bool {
match base {
WildcardNamespace::Any => true,
WildcardNamespace::Other => {
let base_excluded = other_exclusion_set(base_target_ns);
match derived {
WildcardNamespace::Any => false,
WildcardNamespace::Other => {
let derived_excluded = other_exclusion_set(derived_target_ns);
base_excluded.iter().all(|ns| derived_excluded.contains(ns))
}
_ => {
match resolve_ns_set(derived, derived_target_ns) {
Some(resolved) => resolved.iter().all(|ns| !base_excluded.contains(ns)),
None => false,
}
}
}
}
WildcardNamespace::TargetNamespace
| WildcardNamespace::Local
| WildcardNamespace::List(_) => {
let Some(base_set) = resolve_ns_set(base, base_target_ns) else {
return false;
};
match derived {
WildcardNamespace::Any | WildcardNamespace::Other => false,
_ => {
let Some(derived_set) = resolve_ns_set(derived, derived_target_ns) else {
return false;
};
derived_set.iter().all(|ns| base_set.contains(ns))
}
}
}
}
}
fn is_wildcard_ns_subset(
derived: &WildcardResult,
derived_target_ns: Option<NameId>,
base: &WildcardResult,
base_target_ns: Option<NameId>,
) -> bool {
if !is_namespace_subset(
&derived.namespace,
derived_target_ns,
&base.namespace,
base_target_ns,
) {
return false;
}
for base_excl in &base.not_namespace {
let base_ns = base_excl.resolve(base_target_ns);
let derived_allows =
wildcard_namespace_matches(&derived.namespace, base_ns, derived_target_ns)
&& !derived
.not_namespace
.iter()
.any(|d| d.resolve(derived_target_ns) == base_ns);
if derived_allows {
return false;
}
}
for item in &base.not_qname {
match item {
crate::parser::frames::NotQNameItem::QName { namespace, .. } => {
let derived_admits_ns =
wildcard_namespace_matches(&derived.namespace, *namespace, derived_target_ns)
&& !derived
.not_namespace
.iter()
.any(|t| t.resolve(derived_target_ns) == *namespace);
if derived_admits_ns && !derived.not_qname.contains(item) {
return false;
}
}
crate::parser::frames::NotQNameItem::Defined
| crate::parser::frames::NotQNameItem::DefinedSibling => {
if !derived.not_qname.contains(item) {
return false;
}
}
}
}
true
}
#[cfg(feature = "xsd11")]
fn validate_open_content_extension(
schema_set: &SchemaSet,
derived_key: ComplexTypeKey,
derived: &crate::arenas::ComplexTypeDefData,
base_key: ComplexTypeKey,
base: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let bot = compute_effective_open_content(schema_set, base_key);
let eot = compute_effective_open_content(schema_set, derived_key);
let Some(bot) = bot else {
return Ok(());
};
let (location, type_name) = type_error_context(schema_set, derived);
let base_name = format_type_name(schema_set, base.name, base.target_namespace);
let Some(eot) = eot else {
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' extends '{}' which has open content, \
but derived type has no open content",
type_name, base_name
),
location,
));
};
let mode_ok = eot.mode == OpenContentMode::Interleave
|| (bot.mode == OpenContentMode::Suffix && eot.mode == OpenContentMode::Suffix);
if !mode_ok {
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Complex type '{}' uses suffix open content mode but base type '{}' \
uses interleave mode โ suffix cannot extend interleave",
type_name, base_name
),
location,
));
}
if let (Some(bot_wc), Some(eot_wc)) = (bot.wildcard.as_ref(), eot.wildcard.as_ref()) {
if !is_wildcard_ns_subset(
bot_wc,
base.target_namespace,
eot_wc,
derived.target_namespace,
) {
return Err(SchemaError::structural(
"cos-ct-extends",
format!(
"Open content wildcard of '{}' is not a valid extension \
of base type '{}' wildcard",
type_name, base_name
),
location,
));
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
#[derive(Debug, Clone)]
struct EffectiveOpenContent {
mode: OpenContentMode,
wildcard: Option<WildcardResult>,
target_namespace: Option<NameId>,
}
#[cfg(feature = "xsd11")]
fn compute_effective_open_content(
schema_set: &SchemaSet,
key: ComplexTypeKey,
) -> Option<EffectiveOpenContent> {
compute_effective_open_content_bounded(schema_set, key, 0)
}
#[cfg(feature = "xsd11")]
fn compute_effective_open_content_bounded(
schema_set: &SchemaSet,
key: ComplexTypeKey,
depth: u32,
) -> Option<EffectiveOpenContent> {
if depth > 100 {
return None;
}
let type_data = schema_set.arenas.complex_types.get(key)?;
let target_ns = type_data.target_namespace;
let own_oc: Option<EffectiveOpenContent> =
type_data
.open_content
.as_ref()
.map(|oc| EffectiveOpenContent {
mode: oc.mode,
wildcard: oc.wildcard.clone(),
target_namespace: target_ns,
});
let wildcard_element: Option<EffectiveOpenContent> = if own_oc.is_some() {
own_oc
} else if let Some(default) = type_data
.source
.as_ref()
.and_then(|s| schema_set.documents.get(s.defaults_doc() as usize))
.and_then(|d| d.default_open_content.as_ref())
{
if default.applies_to_empty || !explicit_content_is_empty(schema_set, type_data, 0) {
default_open_content_to_effective(default, target_ns)
} else {
None
}
} else {
None };
let base_oc: Option<EffectiveOpenContent> = if matches!(
type_data.derivation_method,
Some(DerivationMethod::Extension)
) {
match type_data.resolved_base_type {
Some(TypeKey::Complex(base_key)) => {
compute_effective_open_content_bounded(schema_set, base_key, depth + 1)
}
_ => None,
}
} else {
None
};
let Some(we) = wildcard_element else {
return base_oc;
};
if we.mode == OpenContentMode::None {
return base_oc;
}
let wildcard = match (base_oc.as_ref(), we.wildcard.as_ref()) {
(Some(b), Some(w)) => match &b.wildcard {
Some(bw) => Some(wildcard_result_union(bw, b.target_namespace, w, target_ns)),
None => Some(w.clone()),
},
(None, Some(w)) => Some(w.clone()),
(Some(b), None) => b.wildcard.clone(),
(None, None) => None,
};
Some(EffectiveOpenContent {
mode: we.mode,
wildcard,
target_namespace: target_ns,
})
}
#[cfg(feature = "xsd11")]
fn explicit_content_is_empty(
schema_set: &SchemaSet,
type_data: &crate::arenas::ComplexTypeDefData,
depth: u32,
) -> bool {
if depth > 100 {
return true;
}
if !type_data.content.explicit_content_type_is_empty() {
return false;
}
if matches!(
type_data.derivation_method,
Some(DerivationMethod::Extension)
) {
if let Some(TypeKey::Complex(base_key)) = type_data.resolved_base_type {
if let Some(base_data) = schema_set.arenas.complex_types.get(base_key) {
return explicit_content_is_empty(schema_set, base_data, depth + 1);
}
}
}
true
}
#[cfg(feature = "xsd11")]
fn default_open_content_to_effective(
default: &crate::schema::model::DefaultOpenContent,
target_ns: Option<NameId>,
) -> Option<EffectiveOpenContent> {
let mode = match default.mode {
crate::schema::model::OpenContentMode::None => OpenContentMode::None,
crate::schema::model::OpenContentMode::Interleave => OpenContentMode::Interleave,
crate::schema::model::OpenContentMode::Suffix => OpenContentMode::Suffix,
};
if mode == OpenContentMode::None {
return None;
}
let wildcard = default.wildcard.as_ref().map(element_wildcard_to_result);
Some(EffectiveOpenContent {
mode,
wildcard,
target_namespace: target_ns,
})
}
#[cfg(feature = "xsd11")]
fn element_wildcard_to_result(ew: &crate::schema::wildcard::ElementWildcard) -> WildcardResult {
use crate::parser::frames::NotQNameItem;
use crate::schema::wildcard::{NamespaceConstraint, QNameDisallowed};
let (namespace, not_namespace) = match &ew.namespace_constraint {
NamespaceConstraint::Any => (WildcardNamespace::Any, Vec::new()),
NamespaceConstraint::Other => (WildcardNamespace::Other, Vec::new()),
NamespaceConstraint::Enumeration(nss) => (
WildcardNamespace::List(nss.iter().copied().map(ns_token).collect()),
Vec::new(),
),
NamespaceConstraint::Not(nss) => (
WildcardNamespace::Any,
nss.iter().copied().map(ns_token).collect(),
),
};
let process_contents = match ew.process_contents {
crate::schema::wildcard::ProcessContents::Strict => ProcessContents::Strict,
crate::schema::wildcard::ProcessContents::Lax => ProcessContents::Lax,
crate::schema::wildcard::ProcessContents::Skip => ProcessContents::Skip,
};
let not_qname = ew
.not_qnames
.iter()
.map(|q| match q {
QNameDisallowed::QName {
namespace,
local_name,
} => NotQNameItem::QName {
namespace: *namespace,
local_name: *local_name,
},
QNameDisallowed::Defined => NotQNameItem::Defined,
QNameDisallowed::DefinedSibling => NotQNameItem::DefinedSibling,
})
.collect();
WildcardResult {
namespace,
process_contents,
not_namespace,
not_qname,
id: ew.id.clone(),
annotation: None,
source: ew.source.clone(),
}
}
#[cfg(feature = "xsd11")]
#[derive(Debug, Clone)]
enum NsForm {
Pos(Vec<Option<NameId>>),
Neg(Vec<Option<NameId>>),
}
#[cfg(feature = "xsd11")]
fn wildcard_to_ns_form(
ns: &WildcardNamespace,
not_namespace: &[crate::parser::frames::NamespaceToken],
target_ns: Option<NameId>,
) -> NsForm {
let resolved_not: Vec<Option<NameId>> =
not_namespace.iter().map(|t| t.resolve(target_ns)).collect();
match ns {
WildcardNamespace::Any => NsForm::Neg(resolved_not),
WildcardNamespace::Other => {
let mut excl = other_exclusion_set(target_ns);
for r in resolved_not {
if !excl.contains(&r) {
excl.push(r);
}
}
NsForm::Neg(excl)
}
WildcardNamespace::TargetNamespace => {
let base = target_ns;
if resolved_not.contains(&base) {
NsForm::Pos(Vec::new())
} else {
NsForm::Pos(vec![base])
}
}
WildcardNamespace::Local => {
if resolved_not.contains(&None) {
NsForm::Pos(Vec::new())
} else {
NsForm::Pos(vec![None])
}
}
WildcardNamespace::List(tokens) => {
let allowed: Vec<Option<NameId>> = tokens
.iter()
.map(|t| t.resolve(target_ns))
.filter(|r| !resolved_not.contains(r))
.collect();
NsForm::Pos(allowed)
}
}
}
#[cfg(feature = "xsd11")]
fn ns_form_to_wildcard(
form: NsForm,
) -> (
WildcardNamespace,
Vec<crate::parser::frames::NamespaceToken>,
) {
use crate::parser::frames::NamespaceToken;
match form {
NsForm::Pos(list) => {
let tokens: Vec<NamespaceToken> = list.into_iter().map(ns_token).collect();
(WildcardNamespace::List(tokens), Vec::new())
}
NsForm::Neg(list) if list.is_empty() => (WildcardNamespace::Any, Vec::new()),
NsForm::Neg(list) => {
let tokens: Vec<NamespaceToken> = list.into_iter().map(ns_token).collect();
(WildcardNamespace::Any, tokens)
}
}
}
#[cfg(feature = "xsd11")]
fn ns_token(ns: Option<NameId>) -> crate::parser::frames::NamespaceToken {
match ns {
Some(id) => crate::parser::frames::NamespaceToken::Uri(id),
None => crate::parser::frames::NamespaceToken::Local,
}
}
#[cfg(feature = "xsd11")]
pub(crate) fn wildcard_result_union(
a: &WildcardResult,
a_tns: Option<NameId>,
b: &WildcardResult,
b_tns: Option<NameId>,
) -> WildcardResult {
let form_a = wildcard_to_ns_form(&a.namespace, &a.not_namespace, a_tns);
let form_b = wildcard_to_ns_form(&b.namespace, &b.not_namespace, b_tns);
let merged = match (form_a, form_b) {
(NsForm::Pos(mut pa), NsForm::Pos(pb)) => {
for item in pb {
if !pa.contains(&item) {
pa.push(item);
}
}
NsForm::Pos(pa)
}
(NsForm::Pos(pa), NsForm::Neg(nb)) | (NsForm::Neg(nb), NsForm::Pos(pa)) => {
NsForm::Neg(nb.into_iter().filter(|ns| !pa.contains(ns)).collect())
}
(NsForm::Neg(na), NsForm::Neg(nb)) => {
NsForm::Neg(na.into_iter().filter(|ns| nb.contains(ns)).collect())
}
};
let (namespace, not_namespace) = ns_form_to_wildcard(merged);
let not_qname: Vec<crate::parser::frames::NotQNameItem> = a
.not_qname
.iter()
.filter(|item| b.not_qname.contains(item))
.cloned()
.collect();
WildcardResult {
namespace,
process_contents: a.process_contents,
not_namespace,
not_qname,
id: None,
annotation: None,
source: a.source.clone(),
}
}
#[cfg(feature = "xsd11")]
fn derived_particle_is_empty(
schema_set: &SchemaSet,
derived: &crate::arenas::ComplexTypeDefData,
) -> bool {
let Some(particle) = complex_content_particle(&derived.content) else {
return true;
};
let Ok(normalized) = normalize_type_particle(schema_set, derived, particle) else {
return false;
};
is_effectively_empty(&normalized)
}
#[cfg(feature = "xsd11")]
fn base_content_single_wildcard<'a>(
schema_set: &'a SchemaSet,
base: &'a crate::arenas::ComplexTypeDefData,
) -> Option<NormalizedWildcard> {
let (particle_owner, particle) = effective_base_content_particle(schema_set, base);
let particle = particle?;
let normalized = normalize_type_particle(schema_set, particle_owner, particle).ok()?;
match &normalized.term {
NormalizedParticleTerm::Wildcard(wc) => Some((**wc).clone()),
NormalizedParticleTerm::Group(group) => {
if group.particles.len() == 1 {
if let NormalizedParticleTerm::Wildcard(wc) = &group.particles[0].term {
return Some((**wc).clone());
}
}
None
}
_ => None,
}
}
#[cfg(feature = "xsd11")]
fn validate_all_group_restriction_edc(
schema_set: &SchemaSet,
derived: &crate::arenas::ComplexTypeDefData,
base: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
use crate::parser::frames::ProcessContents;
let derived_particle = complex_content_particle(&derived.content);
let (effective_base, base_particle) = effective_base_content_particle(schema_set, base);
let (Some(derived_p), Some(base_p)) = (derived_particle, base_particle) else {
return Ok(());
};
let derived_norm = match normalize_type_particle(schema_set, derived, derived_p) {
Ok(n) => n,
Err(_) => return Ok(()),
};
let base_norm = match normalize_type_particle(schema_set, effective_base, base_p) {
Ok(n) => n,
Err(_) => return Ok(()),
};
if !is_top_all_group(&derived_norm) || !is_top_all_group(&base_norm) {
return Ok(());
}
let mut derived_local_qnames: Vec<(Option<NameId>, NameId)> = Vec::new();
let mut derived_wildcards: Vec<&NormalizedWildcard> = Vec::new();
if let NormalizedParticleTerm::Group(group) = &derived_norm.term {
for p in &group.particles {
match &p.term {
NormalizedParticleTerm::Element(elem) => {
derived_local_qnames.push((elem.namespace, elem.name));
}
NormalizedParticleTerm::Wildcard(wc) => {
derived_wildcards.push(wc.as_ref());
}
NormalizedParticleTerm::Group(_) => {}
}
}
}
if derived_wildcards.is_empty() {
return Ok(());
}
let base_locals: Vec<(Option<NameId>, NameId, TypeKey)> =
if let NormalizedParticleTerm::Group(group) = &base_norm.term {
group
.particles
.iter()
.filter_map(|p| match &p.term {
NormalizedParticleTerm::Element(elem) => {
Some((elem.namespace, elem.name, elem.type_key))
}
_ => None,
})
.collect()
} else {
Vec::new()
};
let (location, type_name) = type_error_context(schema_set, derived);
let base_name = format_type_name(schema_set, base.name, base.target_namespace);
for (l_ns, l_name, l_type) in &base_locals {
if derived_local_qnames.contains(&(*l_ns, *l_name)) {
continue;
}
for wc in &derived_wildcards {
if !wildcard_admits_qname(wc, *l_ns, *l_name) {
continue;
}
let pc = wc.wildcard.process_contents;
if matches!(pc, ProcessContents::Skip) {
continue;
}
let global_key = schema_set.lookup_element(*l_ns, *l_name);
let governing_type = global_key
.and_then(|k| schema_set.arenas.elements.get(k))
.and_then(|d| d.resolved_type);
match (pc, governing_type) {
(ProcessContents::Strict, None) => {
continue;
}
(ProcessContents::Lax, None) => {
}
(_, Some(gov_type)) => {
if schema_set.is_type_derived_from(gov_type, *l_type, DerivationSet::empty()) {
continue;
}
}
_ => continue,
}
return Err(SchemaError::structural(
"cos-element-consistent",
format!(
"Complex type '{}' restricts '{}' (xs:all) by removing local element \
while keeping a wildcard that admits the same QName; the wildcard's \
governing type is not validly substitutable for the base local element's \
type (cvc-complex-type rule 5 / tighter EDC for xs:all restriction)",
type_name, base_name,
),
location.clone(),
));
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
fn is_top_all_group(particle: &NormalizedParticle) -> bool {
matches!(
&particle.term,
NormalizedParticleTerm::Group(group) if group.compositor == Compositor::All
)
}
#[cfg(feature = "xsd11")]
fn validate_open_content_restriction(
schema_set: &SchemaSet,
derived: &crate::arenas::ComplexTypeDefData,
base: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let base_oc = effective_open_content(base.open_content.as_ref());
let derived_oc = effective_open_content(derived.open_content.as_ref());
if base_oc.is_none() && derived_oc.is_some() {
if derived_particle_is_empty(schema_set, derived) {
let derived_oc_wc = derived_oc.as_ref().and_then(|o| o.wildcard.as_ref());
if let Some(d_wc) = derived_oc_wc {
if let Some(base_wc) = base_content_single_wildcard(schema_set, base) {
if is_wildcard_ns_subset(
d_wc,
derived.target_namespace,
&base_wc.wildcard,
base_wc.target_namespace,
) {
return Ok(());
}
}
}
}
let (location, type_name) = type_error_context(schema_set, derived);
let base_name = format_type_name(schema_set, base.name, base.target_namespace);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricts '{}' which has no open content, \
but adds open content โ not allowed",
type_name, base_name
),
location,
));
}
let (Some(base_oc), Some(derived_oc)) = (base_oc, derived_oc) else {
return Ok(());
};
let (location, type_name) = type_error_context(schema_set, derived);
let base_name = format_type_name(schema_set, base.name, base.target_namespace);
if base_oc.mode == OpenContentMode::Suffix
&& derived_oc.mode == OpenContentMode::Interleave
&& !derived_particle_is_empty(schema_set, derived)
{
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' uses interleave open content mode but base type '{}' \
uses suffix mode โ interleave cannot restrict suffix",
type_name, base_name
),
location,
));
}
if let (Some(base_wc), Some(derived_wc)) =
(base_oc.wildcard.as_ref(), derived_oc.wildcard.as_ref())
{
if !is_wildcard_ns_subset(
derived_wc,
derived.target_namespace,
base_wc,
base.target_namespace,
) {
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Open content wildcard of '{}' is not a valid restriction \
of base type '{}' wildcard",
type_name, base_name
),
location,
));
}
if process_contents_strictness(derived_wc.process_contents)
< process_contents_strictness(base_wc.process_contents)
{
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Open content wildcard of '{}' has weaker processContents \
than base type '{}' wildcard",
type_name, base_name
),
location,
));
}
}
Ok(())
}
fn is_notation_or_qname_base(schema_set: &SchemaSet, key: TypeKey) -> bool {
let TypeKey::Simple(sk) = key else {
return false;
};
let bt = schema_set.builtin_types();
schema_set.derives_from(sk, bt.notation) || schema_set.derives_from(sk, bt.qname)
}
fn base_without_enumeration(schema_set: &SchemaSet, key: TypeKey) -> TypeKey {
let mut current = key;
for _ in 0..100 {
if let TypeKey::Simple(sk) = current {
if let Some(st_data) = schema_set.arenas.simple_types.get(sk) {
if st_data.facets.enumeration.is_none() {
return current;
}
if let Some(base) = st_data.resolved_base_type {
current = base;
continue;
}
}
}
break;
}
current
}
fn validate_facet_values_against_base_type(
schema_set: &SchemaSet,
type_def: &crate::arenas::SimpleTypeDefData,
base_key: TypeKey,
) -> SchemaResult<()> {
let (location, type_name) = type_error_context(schema_set, type_def);
if is_notation_or_qname_base(schema_set, base_key) {
if let Some(ref enum_facet) = type_def.facets.enumeration {
for value in &enum_facet.values {
if value.trim().is_empty() {
return Err(SchemaError::structural(
"enumeration-valid-restriction",
format!(
"Enumeration value '' in type '{}' is not in the value space of the base type",
type_name
),
location.clone(),
));
}
}
}
return Ok(());
}
if let Some(ref enum_facet) = type_def.facets.enumeration {
let enum_base = base_without_enumeration(schema_set, base_key);
for value in &enum_facet.values {
if crate::validation::simple::validate_simple_type(value, enum_base, schema_set)
.is_err()
{
return Err(SchemaError::structural(
"enumeration-valid-restriction",
format!(
"Enumeration value '{}' in type '{}' is not in the value space of the base type",
value, type_name
),
location.clone(),
));
}
}
}
let check_bound = |value: &str,
constraint: &'static str,
kind: FacetKind|
-> SchemaResult<()> {
match crate::validation::simple::validate_simple_type(value, base_key, schema_set) {
Ok(_) => Ok(()),
Err(err) if is_bound_self_violation(&err, kind, schema_set, base_key, value) => Ok(()),
Err(_) => Err(SchemaError::structural(
constraint,
format!(
"{} value '{}' in type '{}' is not in the value space of the base type",
kind.name(),
value,
type_name
),
location.clone(),
)),
}
};
if let Some(ref f) = type_def.facets.min_inclusive {
check_bound(
&f.value,
"minInclusive-valid-restriction",
FacetKind::MinInclusive,
)?;
}
if let Some(ref f) = type_def.facets.max_inclusive {
check_bound(
&f.value,
"maxInclusive-valid-restriction",
FacetKind::MaxInclusive,
)?;
}
if let Some(ref f) = type_def.facets.min_exclusive {
check_bound(
&f.value,
"minExclusive-valid-restriction",
FacetKind::MinExclusive,
)?;
}
if let Some(ref f) = type_def.facets.max_exclusive {
check_bound(
&f.value,
"maxExclusive-valid-restriction",
FacetKind::MaxExclusive,
)?;
}
validate_typed_bound_consistency(
schema_set,
&type_def.facets,
base_key,
&type_name,
&location,
)?;
Ok(())
}
fn validate_typed_bound_consistency(
schema_set: &SchemaSet,
facets: &FacetSet,
base_key: TypeKey,
type_name: &str,
location: &Option<SourceLocation>,
) -> SchemaResult<()> {
let check_pair = |lower: Option<&str>,
upper: Option<&str>,
lower_name: &'static str,
upper_name: &'static str,
allow_equal: bool|
-> SchemaResult<()> {
let (Some(lower), Some(upper)) = (lower, upper) else {
return Ok(());
};
let Some(cmp) = compare_bound_literals(schema_set, base_key, lower, upper) else {
return Ok(());
};
let valid = if allow_equal {
matches!(cmp, std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
} else {
cmp == std::cmp::Ordering::Less
};
if valid {
return Ok(());
}
Err(SchemaError::structural(
"cos-st-restricts",
format!(
"{} value '{}' is not below {} value '{}' in type '{}'",
lower_name, lower, upper_name, upper, type_name
),
location.clone(),
))
};
check_pair(
facets.min_inclusive.as_ref().map(|f| f.value.as_str()),
facets.max_inclusive.as_ref().map(|f| f.value.as_str()),
"minInclusive",
"maxInclusive",
true,
)?;
check_pair(
facets.min_exclusive.as_ref().map(|f| f.value.as_str()),
facets.max_exclusive.as_ref().map(|f| f.value.as_str()),
"minExclusive",
"maxExclusive",
false,
)?;
check_pair(
facets.min_inclusive.as_ref().map(|f| f.value.as_str()),
facets.max_exclusive.as_ref().map(|f| f.value.as_str()),
"minInclusive",
"maxExclusive",
false,
)?;
check_pair(
facets.min_exclusive.as_ref().map(|f| f.value.as_str()),
facets.max_inclusive.as_ref().map(|f| f.value.as_str()),
"minExclusive",
"maxInclusive",
false,
)
}
fn compare_bound_literals(
schema_set: &SchemaSet,
base_key: TypeKey,
lower: &str,
upper: &str,
) -> Option<std::cmp::Ordering> {
let parse_base = bound_comparison_base(schema_set, base_key);
let lower =
crate::validation::simple::validate_simple_type(lower, parse_base, schema_set).ok()?;
let upper =
crate::validation::simple::validate_simple_type(upper, parse_base, schema_set).ok()?;
compare_xml_values(&lower.typed_value, &upper.typed_value)
}
fn bound_comparison_base(schema_set: &SchemaSet, key: TypeKey) -> TypeKey {
let mut current = key;
for _ in 0..100 {
let TypeKey::Simple(sk) = current else {
return current;
};
let Some(st) = schema_set.arenas.simple_types.get(sk) else {
return current;
};
let has_bounds_or_enum = st.facets.enumeration.is_some()
|| st.facets.min_inclusive.is_some()
|| st.facets.min_exclusive.is_some()
|| st.facets.max_inclusive.is_some()
|| st.facets.max_exclusive.is_some();
if !has_bounds_or_enum {
return current;
}
let Some(base) = st.resolved_base_type else {
return current;
};
current = base;
}
current
}
fn compare_xml_values(
lower: &crate::types::value::XmlValue,
upper: &crate::types::value::XmlValue,
) -> Option<std::cmp::Ordering> {
use crate::types::value::XmlValueKind;
match (&lower.value, &upper.value) {
(XmlValueKind::Atomic(a), XmlValueKind::Atomic(b)) => compare_xml_atomic_values(a, b),
(XmlValueKind::Union(a), _) => compare_xml_values(a, upper),
(_, XmlValueKind::Union(b)) => compare_xml_values(lower, b),
_ => None,
}
}
fn compare_xml_atomic_values(
lower: &crate::types::value::XmlAtomicValue,
upper: &crate::types::value::XmlAtomicValue,
) -> Option<std::cmp::Ordering> {
use crate::types::value::XmlAtomicValue;
match (lower, upper) {
(XmlAtomicValue::DateTime(a), XmlAtomicValue::DateTime(b)) => a.partial_cmp(b),
(XmlAtomicValue::Date(a), XmlAtomicValue::Date(b)) => a.partial_cmp(b),
(XmlAtomicValue::Time(a), XmlAtomicValue::Time(b)) => a.partial_cmp(b),
(XmlAtomicValue::Duration(a), XmlAtomicValue::Duration(b)) => a.partial_cmp(b),
(XmlAtomicValue::YearMonthDuration(a), XmlAtomicValue::YearMonthDuration(b)) => {
a.partial_cmp(b)
}
(XmlAtomicValue::DayTimeDuration(a), XmlAtomicValue::DayTimeDuration(b)) => {
a.partial_cmp(b)
}
(XmlAtomicValue::GYearMonth(a), XmlAtomicValue::GYearMonth(b)) => a.partial_cmp(b),
(XmlAtomicValue::GYear(a), XmlAtomicValue::GYear(b)) => a.partial_cmp(b),
(XmlAtomicValue::GMonthDay(a), XmlAtomicValue::GMonthDay(b)) => a.partial_cmp(b),
(XmlAtomicValue::GDay(a), XmlAtomicValue::GDay(b)) => a.partial_cmp(b),
(XmlAtomicValue::GMonth(a), XmlAtomicValue::GMonth(b)) => a.partial_cmp(b),
_ => None,
}
}
fn get_type_facets(schema_set: &SchemaSet, type_key: TypeKey) -> SchemaResult<Option<FacetSet>> {
match type_key {
TypeKey::Simple(key) => {
if let Some(type_def) = schema_set.arenas.simple_types.get(key) {
Ok(Some(type_def.facets.clone()))
} else {
Ok(None)
}
}
TypeKey::Complex(_) => {
Ok(None)
}
}
}
pub(crate) fn format_type_name(
schema_set: &SchemaSet,
name: Option<NameId>,
namespace: Option<NameId>,
) -> String {
match name {
Some(name_id) => {
let local = schema_set.name_table.resolve(name_id);
match namespace {
Some(ns_id) => {
let ns = schema_set.name_table.resolve(ns_id);
if ns.is_empty() {
local.to_string()
} else {
format!("{{{}}}{}", ns, local)
}
}
None => local.to_string(),
}
}
None => "(anonymous)".to_string(),
}
}
pub(crate) trait TypeDefForError {
fn error_name(&self) -> Option<NameId>;
fn error_target_namespace(&self) -> Option<NameId>;
fn error_source(&self) -> Option<&SourceRef>;
}
impl TypeDefForError for crate::arenas::SimpleTypeDefData {
fn error_name(&self) -> Option<NameId> {
self.name
}
fn error_target_namespace(&self) -> Option<NameId> {
self.target_namespace
}
fn error_source(&self) -> Option<&SourceRef> {
self.source.as_ref()
}
}
impl TypeDefForError for crate::arenas::ComplexTypeDefData {
fn error_name(&self) -> Option<NameId> {
self.name
}
fn error_target_namespace(&self) -> Option<NameId> {
self.target_namespace
}
fn error_source(&self) -> Option<&SourceRef> {
self.source.as_ref()
}
}
pub(crate) fn type_error_context<T: TypeDefForError>(
schema_set: &SchemaSet,
type_def: &T,
) -> (Option<SourceLocation>, String) {
(
schema_set.locate(type_def.error_source()),
format_type_name(
schema_set,
type_def.error_name(),
type_def.error_target_namespace(),
),
)
}
struct EffectiveAttributeUse {
name: NameId,
target_namespace: Option<NameId>,
use_kind: AttributeUseKind,
resolved_type: Option<TypeKey>,
fixed_value: Option<String>,
default_value: Option<String>,
inheritable: bool,
}
fn resolve_single_attribute_use(
schema_set: &SchemaSet,
attr_use: &crate::parser::frames::AttributeUseResult,
resolved: Option<&crate::arenas::ResolvedAttributeUse>,
) -> Option<EffectiveAttributeUse> {
let (name, target_namespace) = if let Some(ref_name) = &attr_use.attribute.ref_name {
if let Some(resolved_attr) = resolved.and_then(|r| r.resolved_ref) {
let decl = schema_set.arenas.attributes.get(resolved_attr);
(
decl.and_then(|d| d.name)?,
decl.and_then(|d| d.target_namespace),
)
} else {
(ref_name.local_name, ref_name.namespace)
}
} else {
let n = attr_use.attribute.name?;
let ns = schema_set.effective_local_attribute_namespace(
attr_use.attribute.target_namespace,
attr_use.attribute.form.as_deref(),
attr_use.attribute.source.as_ref(),
None,
);
(n, ns)
};
let resolved_type = resolved.and_then(|r| r.resolved_type).or_else(|| {
resolved
.and_then(|r| r.resolved_ref)
.and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
.and_then(|decl| decl.resolved_type)
});
let fixed_value = attr_use.attribute.fixed_value.clone().or_else(|| {
resolved
.and_then(|r| r.resolved_ref)
.and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
.and_then(|decl| decl.fixed_value.clone())
});
let default_value = attr_use.attribute.default_value.clone().or_else(|| {
resolved
.and_then(|r| r.resolved_ref)
.and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
.and_then(|decl| decl.default_value.clone())
});
let inheritable = if attr_use.attribute.inheritable {
true
} else if attr_use.attribute.ref_name.is_some() {
resolved
.and_then(|r| r.resolved_ref)
.and_then(|ref_key| schema_set.arenas.attributes.get(ref_key))
.map(|decl| decl.inheritable)
.unwrap_or(false)
} else {
false
};
Some(EffectiveAttributeUse {
name,
target_namespace,
use_kind: attr_use.use_kind,
resolved_type,
fixed_value,
default_value,
inheritable,
})
}
fn collect_effective_attribute_uses(
schema_set: &SchemaSet,
type_def: &crate::arenas::ComplexTypeDefData,
) -> Vec<EffectiveAttributeUse> {
let mut result = Vec::new();
for (i, attr_use) in type_def.attributes.iter().enumerate() {
let resolved = type_def.resolved_attributes.get(i);
if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
result.push(eau);
}
}
for &ag_key in &type_def.resolved_attribute_groups {
collect_attribute_group_uses(schema_set, ag_key, &mut result, 0);
}
result
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum CanonicalNs {
Any,
Enum(std::collections::HashSet<Option<NameId>>),
Not(std::collections::HashSet<Option<NameId>>),
}
#[derive(Debug, Clone)]
pub(crate) struct EffectiveAttributeWildcard {
pub(crate) namespace: CanonicalNs,
pub(crate) not_qname: Vec<crate::parser::frames::NotQNameItem>,
pub(crate) process_contents: ProcessContents,
}
fn normalize_attribute_wildcard(
schema_set: &SchemaSet,
wc: &WildcardResult,
target_ns: Option<NameId>,
) -> EffectiveAttributeWildcard {
use std::collections::HashSet;
let base: CanonicalNs = match &wc.namespace {
WildcardNamespace::Any => CanonicalNs::Any,
WildcardNamespace::Other => {
let mut excl = HashSet::new();
excl.insert(target_ns);
if schema_set.is_xsd10() {
excl.insert(None);
}
CanonicalNs::Not(excl)
}
WildcardNamespace::TargetNamespace => {
let mut s = HashSet::new();
s.insert(target_ns);
CanonicalNs::Enum(s)
}
WildcardNamespace::Local => {
let mut s = HashSet::new();
s.insert(None);
CanonicalNs::Enum(s)
}
WildcardNamespace::List(tokens) => {
let mut s = HashSet::new();
for tok in tokens {
s.insert(tok.resolve(target_ns));
}
CanonicalNs::Enum(s)
}
};
let not_ns: HashSet<Option<NameId>> = wc
.not_namespace
.iter()
.map(|t| t.resolve(target_ns))
.collect();
let namespace = if not_ns.is_empty() {
base
} else {
match base {
CanonicalNs::Any => CanonicalNs::Not(not_ns),
CanonicalNs::Enum(set) => {
let filtered: HashSet<Option<NameId>> =
set.into_iter().filter(|ns| !not_ns.contains(ns)).collect();
CanonicalNs::Enum(filtered)
}
CanonicalNs::Not(set) => {
let mut combined = set;
combined.extend(not_ns);
CanonicalNs::Not(combined)
}
}
};
EffectiveAttributeWildcard {
namespace,
not_qname: wc.not_qname.clone(),
process_contents: wc.process_contents,
}
}
fn intersect_canonical_ns(a: &CanonicalNs, b: &CanonicalNs) -> CanonicalNs {
use std::collections::HashSet;
match (a, b) {
(CanonicalNs::Any, other) | (other, CanonicalNs::Any) => other.clone(),
(CanonicalNs::Enum(s1), CanonicalNs::Enum(s2)) => {
let inter: HashSet<Option<NameId>> = s1.intersection(s2).copied().collect();
CanonicalNs::Enum(inter)
}
(CanonicalNs::Enum(s), CanonicalNs::Not(n))
| (CanonicalNs::Not(n), CanonicalNs::Enum(s)) => {
let filtered: HashSet<Option<NameId>> =
s.iter().filter(|ns| !n.contains(ns)).copied().collect();
CanonicalNs::Enum(filtered)
}
(CanonicalNs::Not(n1), CanonicalNs::Not(n2)) => {
let mut union = n1.clone();
union.extend(n2.iter().copied());
CanonicalNs::Not(union)
}
}
}
fn union_canonical_ns(a: &CanonicalNs, b: &CanonicalNs) -> CanonicalNs {
use std::collections::HashSet;
match (a, b) {
(CanonicalNs::Any, _) | (_, CanonicalNs::Any) => CanonicalNs::Any,
(CanonicalNs::Enum(s1), CanonicalNs::Enum(s2)) => {
let mut union = s1.clone();
union.extend(s2.iter().copied());
CanonicalNs::Enum(union)
}
(CanonicalNs::Enum(s), CanonicalNs::Not(n))
| (CanonicalNs::Not(n), CanonicalNs::Enum(s)) => {
let filtered: HashSet<Option<NameId>> =
n.iter().filter(|ns| !s.contains(ns)).copied().collect();
if filtered.is_empty() {
CanonicalNs::Any
} else {
CanonicalNs::Not(filtered)
}
}
(CanonicalNs::Not(n1), CanonicalNs::Not(n2)) => {
let inter: HashSet<Option<NameId>> = n1.intersection(n2).copied().collect();
if inter.is_empty() {
CanonicalNs::Any
} else {
CanonicalNs::Not(inter)
}
}
}
}
fn canonical_ns_subset(a: &CanonicalNs, b: &CanonicalNs) -> bool {
match (a, b) {
(_, CanonicalNs::Any) => true,
(CanonicalNs::Any, _) => false,
(CanonicalNs::Enum(s), CanonicalNs::Enum(t)) => s.iter().all(|ns| t.contains(ns)),
(CanonicalNs::Enum(s), CanonicalNs::Not(n)) => s.iter().all(|ns| !n.contains(ns)),
(CanonicalNs::Not(n1), CanonicalNs::Not(n2)) => n2.iter().all(|ns| n1.contains(ns)),
(CanonicalNs::Not(_), CanonicalNs::Enum(_)) => false,
}
}
fn intersect_effective_attribute_wildcards(
a: &EffectiveAttributeWildcard,
b: &EffectiveAttributeWildcard,
) -> EffectiveAttributeWildcard {
let namespace = intersect_canonical_ns(&a.namespace, &b.namespace);
let mut not_qname = a.not_qname.clone();
for item in &b.not_qname {
if !not_qname.contains(item) {
not_qname.push(item.clone());
}
}
EffectiveAttributeWildcard {
namespace,
not_qname,
process_contents: a.process_contents,
}
}
fn attribute_group_cycle_error() -> SchemaError {
SchemaError::structural(
"derivation-ok-restriction",
"attribute group reference cycle exceeded max depth while computing \
effective attribute wildcard (ยง3.6.2.2)",
None,
)
}
fn combine_effective_wildcards(
local: Option<EffectiveAttributeWildcard>,
w: Vec<EffectiveAttributeWildcard>,
) -> Option<EffectiveAttributeWildcard> {
match (local, w.is_empty()) {
(None, true) => None,
(Some(l), true) => Some(l),
(Some(l), false) => Some(w.into_iter().fold(l, |acc, wi| {
intersect_effective_attribute_wildcards(&acc, &wi)
})),
(None, false) => {
let mut it = w.into_iter();
let first = it.next().expect("w is non-empty");
Some(it.fold(first, |acc, wi| {
intersect_effective_attribute_wildcards(&acc, &wi)
}))
}
}
}
pub(crate) fn effective_attribute_wildcard(
schema_set: &SchemaSet,
local_wc: Option<&WildcardResult>,
local_target_ns: Option<NameId>,
attribute_groups: &[AttributeGroupKey],
) -> SchemaResult<Option<EffectiveAttributeWildcard>> {
let local = local_wc.map(|w| normalize_attribute_wildcard(schema_set, w, local_target_ns));
let mut w: Vec<EffectiveAttributeWildcard> = Vec::new();
for &ag_key in attribute_groups {
collect_effective_group_wildcards(schema_set, ag_key, &mut w, 0)?;
}
Ok(combine_effective_wildcards(local, w))
}
pub(crate) fn compute_runtime_attribute_wildcard(
schema_set: &SchemaSet,
ct_key: ComplexTypeKey,
) -> Option<EffectiveAttributeWildcard> {
compute_runtime_attribute_wildcard_bounded(schema_set, ct_key, 0)
}
fn compute_runtime_attribute_wildcard_bounded(
schema_set: &SchemaSet,
ct_key: ComplexTypeKey,
depth: u32,
) -> Option<EffectiveAttributeWildcard> {
if depth > 100 {
return None;
}
let ct = schema_set.arenas.complex_types.get(ct_key)?;
let own_local = own_attribute_wildcard_ref(ct);
let own = effective_attribute_wildcard(
schema_set,
own_local,
ct.target_namespace,
&ct.resolved_attribute_groups,
)
.ok()
.flatten();
let Some(TypeKey::Complex(base_key)) = ct.resolved_base_type else {
return own;
};
if base_key == schema_set.any_type_key() {
return own;
}
match ct.derivation_method {
Some(DerivationMethod::Extension) => {
let base = compute_runtime_attribute_wildcard_bounded(schema_set, base_key, depth + 1);
match (own, base) {
(Some(a), Some(b)) => Some(union_effective_attribute_wildcards(&a, &b)),
(Some(a), None) => Some(a),
(None, Some(b)) => Some(b),
(None, None) => None,
}
}
_ => own,
}
}
fn own_attribute_wildcard_ref(ct: &crate::arenas::ComplexTypeDefData) -> Option<&WildcardResult> {
if let Some(wc) = ct.attribute_wildcard.as_ref() {
return Some(wc);
}
match &ct.content {
ComplexContentResult::Empty => None,
ComplexContentResult::Simple(sc) => sc.attribute_wildcard.as_ref(),
ComplexContentResult::Complex(cc) => cc.attribute_wildcard.as_ref(),
}
}
fn union_effective_attribute_wildcards(
a: &EffectiveAttributeWildcard,
b: &EffectiveAttributeWildcard,
) -> EffectiveAttributeWildcard {
use crate::parser::frames::NotQNameItem;
let namespace = union_canonical_ns(&a.namespace, &b.namespace);
let mut not_qname: Vec<NotQNameItem> = Vec::new();
let mut consider = |item: &NotQNameItem, other: &EffectiveAttributeWildcard| match item {
NotQNameItem::QName {
namespace,
local_name,
} => {
let admitted_by_other_ns = match &other.namespace {
CanonicalNs::Any => true,
CanonicalNs::Enum(set) => set.contains(namespace),
CanonicalNs::Not(set) => !set.contains(namespace),
};
let excluded_by_other_qname = other.not_qname.iter().any(|o| match o {
NotQNameItem::QName {
namespace: ons,
local_name: oln,
} => ons == namespace && oln == local_name,
NotQNameItem::Defined | NotQNameItem::DefinedSibling => false,
});
if (!admitted_by_other_ns || excluded_by_other_qname) && !not_qname.contains(item) {
not_qname.push(item.clone());
}
}
NotQNameItem::Defined | NotQNameItem::DefinedSibling => {
if other
.not_qname
.iter()
.any(|o| std::mem::discriminant(o) == std::mem::discriminant(item))
&& !not_qname.contains(item)
{
not_qname.push(item.clone());
}
}
};
for item in &a.not_qname {
consider(item, b);
}
for item in &b.not_qname {
consider(item, a);
}
let process_contents = if process_contents_strictness(a.process_contents)
<= process_contents_strictness(b.process_contents)
{
a.process_contents
} else {
b.process_contents
};
EffectiveAttributeWildcard {
namespace,
not_qname,
process_contents,
}
}
fn collect_effective_group_wildcards(
schema_set: &SchemaSet,
ag_key: AttributeGroupKey,
out: &mut Vec<EffectiveAttributeWildcard>,
depth: usize,
) -> SchemaResult<()> {
if depth > 20 {
return Err(attribute_group_cycle_error());
}
let Some(ag) = schema_set.arenas.attribute_groups.get(ag_key) else {
return Ok(());
};
if let Some(ref_key) = ag.resolved_ref {
return collect_effective_group_wildcards(schema_set, ref_key, out, depth + 1);
}
if let Some(eff) = effective_attribute_wildcard_for_group(schema_set, ag, depth + 1)? {
out.push(eff);
}
Ok(())
}
fn effective_attribute_wildcard_for_group(
schema_set: &SchemaSet,
ag: &crate::arenas::AttributeGroupData,
depth: usize,
) -> SchemaResult<Option<EffectiveAttributeWildcard>> {
if depth > 20 {
return Err(attribute_group_cycle_error());
}
if let Some(ref_key) = ag.resolved_ref {
let Some(target) = schema_set.arenas.attribute_groups.get(ref_key) else {
return Ok(None);
};
return effective_attribute_wildcard_for_group(schema_set, target, depth + 1);
}
let local = ag
.attribute_wildcard
.as_ref()
.map(|w| normalize_attribute_wildcard(schema_set, w, ag.target_namespace));
let mut w: Vec<EffectiveAttributeWildcard> = Vec::new();
for &nested_key in &ag.resolved_attribute_groups {
collect_effective_group_wildcards(schema_set, nested_key, &mut w, depth + 1)?;
}
Ok(combine_effective_wildcards(local, w))
}
fn effective_attribute_wildcard_restricts(
schema_set: &SchemaSet,
derived: &EffectiveAttributeWildcard,
base: &EffectiveAttributeWildcard,
) -> Result<(), &'static str> {
use crate::parser::frames::NotQNameItem;
if !canonical_ns_subset(&derived.namespace, &base.namespace) {
return Err("namespace constraint is not a subset of the base wildcard");
}
for item in &base.not_qname {
match item {
NotQNameItem::QName {
namespace,
local_name,
} => {
if effective_wildcard_allows_attribute(schema_set, derived, *namespace, *local_name)
{
return Err(
"notQName exclusions do not cover the base wildcard's disallowed names",
);
}
}
NotQNameItem::Defined => {
if !derived
.not_qname
.iter()
.any(|d| matches!(d, NotQNameItem::Defined))
{
return Err("base wildcard excludes ##defined but derived does not");
}
}
NotQNameItem::DefinedSibling => {
if !derived
.not_qname
.iter()
.any(|d| matches!(d, NotQNameItem::DefinedSibling))
{
return Err("base wildcard excludes ##definedSibling but derived does not");
}
}
}
}
if process_contents_strictness(derived.process_contents)
< process_contents_strictness(base.process_contents)
{
return Err("processContents is weaker than the base wildcard");
}
Ok(())
}
pub(crate) fn effective_wildcard_allows_attribute(
schema_set: &SchemaSet,
wc: &EffectiveAttributeWildcard,
attr_namespace: Option<NameId>,
attr_name: NameId,
) -> bool {
let ns_ok = match &wc.namespace {
CanonicalNs::Any => true,
CanonicalNs::Enum(set) => set.contains(&attr_namespace),
CanonicalNs::Not(set) => !set.contains(&attr_namespace),
};
if !ns_ok {
return false;
}
for item in &wc.not_qname {
match item {
crate::parser::frames::NotQNameItem::QName {
namespace: qns,
local_name,
} => {
if *qns == attr_namespace && *local_name == attr_name {
return false;
}
}
crate::parser::frames::NotQNameItem::Defined => {
if schema_set
.lookup_attribute(attr_namespace, attr_name)
.is_some()
{
return false;
}
}
crate::parser::frames::NotQNameItem::DefinedSibling => {
}
}
}
true
}
fn collect_attribute_group_uses(
schema_set: &SchemaSet,
ag_key: AttributeGroupKey,
result: &mut Vec<EffectiveAttributeUse>,
depth: usize,
) {
if depth > 20 {
return;
}
let Some(ag) = schema_set.arenas.attribute_groups.get(ag_key) else {
return;
};
if let Some(ref_key) = ag.resolved_ref {
collect_attribute_group_uses(schema_set, ref_key, result, depth + 1);
return;
}
for (i, attr_use) in ag.attributes.iter().enumerate() {
let resolved = ag.resolved_attributes.get(i);
if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
result.push(eau);
}
}
for &nested_key in &ag.resolved_attribute_groups {
collect_attribute_group_uses(schema_set, nested_key, result, depth + 1);
}
}
fn is_type_derived_from(schema_set: &SchemaSet, derived_key: TypeKey, base_key: TypeKey) -> bool {
schema_set.is_type_derived_from(derived_key, base_key, DerivationSet::empty())
}
enum WildcardRestrictionOutcome {
DerivedAbsent,
AddedInDerived,
NotSubset(&'static str),
Valid,
}
fn classify_attribute_wildcard_restriction(
schema_set: &SchemaSet,
derived_eff: Option<&EffectiveAttributeWildcard>,
base_eff: Option<&EffectiveAttributeWildcard>,
) -> WildcardRestrictionOutcome {
match (derived_eff, base_eff) {
(None, _) => WildcardRestrictionOutcome::DerivedAbsent,
(Some(_), None) => WildcardRestrictionOutcome::AddedInDerived,
(Some(d), Some(b)) => match effective_attribute_wildcard_restricts(schema_set, d, b) {
Ok(()) => WildcardRestrictionOutcome::Valid,
Err(reason) => WildcardRestrictionOutcome::NotSubset(reason),
},
}
}
fn complex_type_has_no_attribute_wildcard_source(
type_def: &crate::arenas::ComplexTypeDefData,
) -> bool {
type_def.attribute_wildcard.is_none() && type_def.resolved_attribute_groups.is_empty()
}
fn validate_attribute_restriction(
schema_set: &SchemaSet,
derived: &crate::arenas::ComplexTypeDefData,
base: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let derived_attrs = collect_effective_attribute_uses(schema_set, derived);
let base_attrs = collect_effective_attribute_uses(schema_set, base);
let location = derived
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
let type_name = format_type_name(schema_set, derived.name, derived.target_namespace);
let base_name = format_type_name(schema_set, base.name, base.target_namespace);
for base_attr in &base_attrs {
if base_attr.use_kind != AttributeUseKind::Required {
continue;
}
let derived_attr = derived_attrs
.iter()
.find(|a| a.name == base_attr.name && a.target_namespace == base_attr.target_namespace);
match derived_attr {
Some(da) if da.use_kind == AttributeUseKind::Required => {}
None => {}
Some(_) => {
let attr_name_str = schema_set.name_table.resolve(base_attr.name);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': base type requires attribute '{}' \
but the derived type weakens it to optional or prohibited",
type_name, base_name, attr_name_str,
),
location,
));
}
}
}
for derived_attr in &derived_attrs {
let Some(derived_type_key) = derived_attr.resolved_type else {
continue;
};
let base_attr = base_attrs.iter().find(|a| {
a.name == derived_attr.name && a.target_namespace == derived_attr.target_namespace
});
let Some(base_attr) = base_attr else { continue };
let Some(base_type_key) = base_attr.resolved_type else {
continue;
};
if derived_type_key == base_type_key {
continue;
}
if !is_type_derived_from(schema_set, derived_type_key, base_type_key) {
let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': attribute '{}' has a type \
that is not validly derived from the base attribute type",
type_name, base_name, attr_name_str,
),
location,
));
}
}
for base_attr in &base_attrs {
let Some(base_fixed) = base_attr.fixed_value.as_deref() else {
continue;
};
let Some(derived_attr) = derived_attrs
.iter()
.find(|a| a.name == base_attr.name && a.target_namespace == base_attr.target_namespace)
else {
continue;
};
match derived_attr.fixed_value.as_deref() {
Some(d_fixed)
if crate::validation::simple::fixed_values_equal(
d_fixed,
base_fixed,
base_attr.resolved_type,
schema_set,
) => {}
Some(d_fixed) => {
let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': attribute '{}' \
changes 'fixed' value from '{}' to '{}'",
type_name, base_name, attr_name_str, base_fixed, d_fixed,
),
location,
));
}
None => {
let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
let what = if derived_attr.default_value.is_some() {
"uses 'default' (cannot weaken base 'fixed')"
} else {
"drops the 'fixed' value constraint"
};
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': attribute '{}' {}",
type_name, base_name, attr_name_str, what,
),
location,
));
}
}
}
if schema_set.is_xsd11() {
for derived_attr in &derived_attrs {
let Some(base_attr) = base_attrs.iter().find(|a| {
a.name == derived_attr.name && a.target_namespace == derived_attr.target_namespace
}) else {
continue;
};
if base_attr.inheritable != derived_attr.inheritable {
let attr_name_str = schema_set.name_table.resolve(derived_attr.name);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': attribute '{}' changes \
{{inheritable}} from {} to {}",
type_name,
base_name,
attr_name_str,
base_attr.inheritable,
derived_attr.inheritable,
),
location,
));
}
}
}
if !complex_type_has_no_attribute_wildcard_source(derived) {
let derived_eff = effective_attribute_wildcard(
schema_set,
derived.attribute_wildcard.as_ref(),
derived.target_namespace,
&derived.resolved_attribute_groups,
)?;
let base_eff = effective_attribute_wildcard(
schema_set,
base.attribute_wildcard.as_ref(),
base.target_namespace,
&base.resolved_attribute_groups,
)?;
match classify_attribute_wildcard_restriction(
schema_set,
derived_eff.as_ref(),
base_eff.as_ref(),
) {
WildcardRestrictionOutcome::DerivedAbsent | WildcardRestrictionOutcome::Valid => {}
WildcardRestrictionOutcome::AddedInDerived => {
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': derived type has an attribute \
wildcard but the base type does not",
type_name, base_name,
),
location,
));
}
WildcardRestrictionOutcome::NotSubset(reason) => {
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': attribute wildcard is not \
a valid restriction of the base wildcard: {}",
type_name, base_name, reason,
),
location,
));
}
}
}
Ok(())
}
fn is_bound_self_violation(
err: &crate::validation::errors::ValidationError,
kind: FacetKind,
schema_set: &SchemaSet,
base_key: TypeKey,
value: &str,
) -> bool {
let code = match kind {
FacetKind::MaxExclusive => "cvc-maxExclusive-valid",
FacetKind::MaxInclusive => "cvc-maxInclusive-valid",
FacetKind::MinExclusive => "cvc-minExclusive-valid",
FacetKind::MinInclusive => "cvc-minInclusive-valid",
_ => return false,
};
if err.constraint != code {
return false;
}
let Some(base_bound) = find_base_bound_literal(schema_set, base_key, kind) else {
return false;
};
let Some(v) = parse_past_own_bound(schema_set, base_key, value) else {
return false;
};
let Some(b) = parse_past_own_bound(schema_set, base_key, &base_bound) else {
return false;
};
v.typed_value == b.typed_value
}
fn parse_past_own_bound(
schema_set: &SchemaSet,
base_key: TypeKey,
value: &str,
) -> Option<crate::validation::simple::SimpleTypeResult> {
if let Ok(r) = crate::validation::simple::validate_simple_type(value, base_key, schema_set) {
return Some(r);
}
let without_bounds = lexical_base(schema_set, base_key)?;
crate::validation::simple::validate_simple_type(value, without_bounds, schema_set).ok()
}
fn lexical_base(schema_set: &SchemaSet, base_key: TypeKey) -> Option<TypeKey> {
let mut current = base_key;
for _ in 0..100 {
match current {
TypeKey::Simple(sk) => {
let st = schema_set.arenas.simple_types.get(sk)?;
let has_bounds = st.facets.min_inclusive.is_some()
|| st.facets.min_exclusive.is_some()
|| st.facets.max_inclusive.is_some()
|| st.facets.max_exclusive.is_some();
if !has_bounds {
return Some(current);
}
current = st.resolved_base_type?;
}
TypeKey::Complex(_) => return None,
}
}
None
}
fn find_base_bound_literal(
schema_set: &SchemaSet,
base_key: TypeKey,
kind: FacetKind,
) -> Option<String> {
let mut current = base_key;
for _ in 0..100 {
match current {
TypeKey::Simple(sk) => {
let st = schema_set.arenas.simple_types.get(sk)?;
let literal = match kind {
FacetKind::MaxExclusive => {
st.facets.max_exclusive.as_ref().map(|f| f.value.clone())
}
FacetKind::MaxInclusive => {
st.facets.max_inclusive.as_ref().map(|f| f.value.clone())
}
FacetKind::MinExclusive => {
st.facets.min_exclusive.as_ref().map(|f| f.value.clone())
}
FacetKind::MinInclusive => {
st.facets.min_inclusive.as_ref().map(|f| f.value.clone())
}
_ => None,
};
if let Some(v) = literal {
return Some(v);
}
current = st.resolved_base_type?;
}
TypeKey::Complex(_) => return None,
}
}
None
}
fn effective_simple_content_type_key(
schema_set: &SchemaSet,
type_def: &crate::arenas::ComplexTypeDefData,
) -> Option<TypeKey> {
let mut current_base = type_def.resolved_base_type?;
for _ in 0..50 {
match current_base {
TypeKey::Simple(sk) => return Some(TypeKey::Simple(sk)),
TypeKey::Complex(ck) => {
let ct = schema_set.arenas.complex_types.get(ck)?;
current_base = ct.resolved_base_type?;
}
}
}
None
}
fn validate_simple_content_restriction(
schema_set: &SchemaSet,
derived: &crate::arenas::ComplexTypeDefData,
base: &crate::arenas::ComplexTypeDefData,
) -> SchemaResult<()> {
let ComplexContentResult::Simple(ref sc) = derived.content else {
return Ok(());
};
let Some(ref inline_st) = sc.content_type else {
return Ok(());
};
let Some(base_simple_key) = effective_simple_content_type_key(schema_set, base) else {
return Ok(());
};
if let TypeKey::Simple(sk) = base_simple_key {
if sk == schema_set.builtin_types().any_simple_type {
return Ok(());
}
}
let base_variety = match base_simple_key {
TypeKey::Simple(sk) => schema_set.arenas.simple_types.get(sk).map(|st| st.variety),
TypeKey::Complex(_) => None,
};
let Some(base_variety) = base_variety else {
return Ok(());
};
let derived_variety = inline_st.variety;
if derived_variety != base_variety {
if let Some(inline_resolved_base) = resolve_inline_simple_type_base(schema_set, inline_st) {
if is_type_derived_from(schema_set, inline_resolved_base, base_simple_key) {
return Ok(());
}
}
let location = derived
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
let type_name = format_type_name(schema_set, derived.name, derived.target_namespace);
let base_name = format_type_name(schema_set, base.name, base.target_namespace);
return Err(SchemaError::structural(
"derivation-ok-restriction",
format!(
"Complex type '{}' restricting '{}': simpleContent inline type \
has variety {:?} which is not a valid restriction of the base \
type's simple content (variety {:?})",
type_name, base_name, derived_variety, base_variety,
),
location,
));
}
Ok(())
}
fn resolve_inline_simple_type_base(
schema_set: &SchemaSet,
inline_st: &crate::parser::frames::SimpleTypeResult,
) -> Option<TypeKey> {
match &inline_st.base_type {
Some(crate::parser::frames::TypeRefResult::QName(qname)) => {
schema_set.lookup_type(qname.namespace, qname.local_name)
}
_ => None,
}
}
pub fn validate_attribute_id_constraints(schema_set: &SchemaSet) -> SchemaResult<()> {
use crate::types::XmlTypeCode;
if !schema_set.is_xsd10() {
return Ok(());
}
let id_key = match schema_set.builtin_types().get_by_type_code(XmlTypeCode::Id) {
Some(k) => k,
None => return Ok(()),
};
for (_key, attr_data) in schema_set.arenas.attributes.iter() {
if attr_data.default_value.is_none() && attr_data.fixed_value.is_none() {
continue;
}
if let Some(TypeKey::Simple(st_key)) = attr_data.resolved_type {
if schema_set.derives_from(st_key, id_key) {
let attr_name = attr_data
.name
.map(|n| schema_set.name_table.resolve(n).to_string())
.unwrap_or_else(|| "(anonymous)".to_string());
let constraint = if attr_data.default_value.is_some() {
"default"
} else {
"fixed"
};
return Err(SchemaError::structural(
"cos-attribute-decl",
format!(
"Attribute '{}' has type xs:ID (or derived) and must not have a {} value constraint",
attr_name, constraint
),
schema_set.locate(attr_data.source.as_ref()),
));
}
}
}
for (_key, ct_data) in schema_set.arenas.complex_types.iter() {
for (i, attr_use) in ct_data.attributes.iter().enumerate() {
if attr_use.use_kind == AttributeUseKind::Prohibited {
continue;
}
let resolved = ct_data.resolved_attributes.get(i);
let ref_decl = resolved
.and_then(|r| r.resolved_ref)
.and_then(|k| schema_set.arenas.attributes.get(k));
let has_constraint = attr_use.attribute.default_value.is_some()
|| attr_use.attribute.fixed_value.is_some()
|| ref_decl.is_some_and(|d| d.default_value.is_some() || d.fixed_value.is_some());
if !has_constraint {
continue;
}
let attr_type = resolved
.and_then(|r| r.resolved_type)
.or_else(|| ref_decl.and_then(|d| d.resolved_type));
if let Some(TypeKey::Simple(st_key)) = attr_type {
if schema_set.derives_from(st_key, id_key) {
let attr_name = attr_use
.attribute
.name
.map(|n| schema_set.name_table.resolve(n).to_string())
.or_else(|| {
ref_decl
.and_then(|d| d.name)
.map(|n| schema_set.name_table.resolve(n).to_string())
})
.unwrap_or_else(|| "(anonymous)".to_string());
let constraint = if attr_use.attribute.default_value.is_some()
|| ref_decl.and_then(|d| d.default_value.as_ref()).is_some()
{
"default"
} else {
"fixed"
};
let location = attr_use
.attribute
.source
.as_ref()
.or(ct_data.source.as_ref())
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"cos-attribute-decl",
format!(
"Attribute '{}' has type xs:ID (or derived) and must not have a {} value constraint",
attr_name, constraint
),
location,
));
}
}
}
}
Ok(())
}
pub fn validate_attribute_value_constraints(schema_set: &SchemaSet) -> SchemaResult<()> {
for (_key, attr) in schema_set.arenas.attributes.iter() {
if attr.source.is_none() {
continue;
}
if matches!(attr.resolved_type, Some(TypeKey::Complex(_))) {
let attr_name = attr
.name
.map(|n| schema_set.name_table.resolve(n).to_string())
.unwrap_or_else(|| "(anonymous)".to_string());
return Err(SchemaError::structural(
"a-props-correct",
format!(
"Attribute '{}' references a complex type; the type definition of an \
attribute must be a simple type",
attr_name,
),
schema_set.locate(attr.source.as_ref()),
));
}
let (value, is_fixed) = match (&attr.default_value, &attr.fixed_value) {
(Some(v), _) => (v.as_str(), false),
(_, Some(v)) => (v.as_str(), true),
(None, None) => continue,
};
let Some(type_key @ TypeKey::Simple(_)) = attr.resolved_type else {
continue;
};
if crate::validation::simple::validate_simple_type(value, type_key, schema_set).is_err() {
let attr_name = attr
.name
.map(|n| schema_set.name_table.resolve(n).to_string())
.unwrap_or_else(|| "(anonymous)".to_string());
let constraint = if is_fixed { "fixed" } else { "default" };
return Err(SchemaError::structural(
"a-props-correct",
format!(
"Attribute '{}' {} value '{}' is not valid for its declared type",
attr_name, constraint, value
),
schema_set.locate(attr.source.as_ref()),
));
}
}
for (_key, ct) in schema_set.arenas.complex_types.iter() {
for (i, attr_use) in ct.attributes.iter().enumerate() {
if attr_use.use_kind == AttributeUseKind::Prohibited {
continue;
}
let resolved = ct.resolved_attributes.get(i);
let ref_decl = resolved
.and_then(|r| r.resolved_ref)
.and_then(|k| schema_set.arenas.attributes.get(k));
let value_constraint: Option<(&str, bool)> = attr_use
.attribute
.fixed_value
.as_deref()
.map(|v| (v, true))
.or_else(|| {
attr_use
.attribute
.default_value
.as_deref()
.map(|v| (v, false))
})
.or_else(|| {
ref_decl.and_then(|d| {
d.fixed_value
.as_deref()
.map(|v| (v, true))
.or_else(|| d.default_value.as_deref().map(|v| (v, false)))
})
});
if let (Some(use_fixed), Some(decl_fixed)) = (
attr_use.attribute.fixed_value.as_deref(),
ref_decl.and_then(|d| d.fixed_value.as_deref()),
) {
use crate::types::WhitespaceMode;
let normalize = |s: &str| -> String {
crate::types::facets::normalize_whitespace(s, WhitespaceMode::Collapse)
};
if normalize(use_fixed) != normalize(decl_fixed) {
let attr_name = ref_decl
.and_then(|d| d.name)
.map(|n| schema_set.name_table.resolve(n).to_string())
.unwrap_or_else(|| "(anonymous)".to_string());
let location = attr_use
.attribute
.source
.as_ref()
.or(ct.source.as_ref())
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"au-props-correct",
format!(
"Attribute use 'fixed' value '{}' on '{}' does not match the \
referenced attribute declaration's 'fixed' value '{}'",
use_fixed, attr_name, decl_fixed,
),
location,
));
}
}
if attr_use.attribute.default_value.is_some()
&& ref_decl.and_then(|d| d.fixed_value.as_deref()).is_some()
{
let attr_name = ref_decl
.and_then(|d| d.name)
.map(|n| schema_set.name_table.resolve(n).to_string())
.unwrap_or_else(|| "(anonymous)".to_string());
let location = attr_use
.attribute
.source
.as_ref()
.or(ct.source.as_ref())
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"au-props-correct",
format!(
"Attribute use cannot specify 'default' for '{}' because the \
referenced attribute declaration has 'fixed'",
attr_name,
),
location,
));
}
let Some((value, is_fixed)) = value_constraint else {
continue;
};
let attr_type = resolved
.and_then(|r| r.resolved_type)
.or_else(|| ref_decl.and_then(|d| d.resolved_type));
let Some(type_key @ TypeKey::Simple(_)) = attr_type else {
continue;
};
if crate::validation::simple::validate_simple_type(value, type_key, schema_set).is_err()
{
let attr_name = attr_use
.attribute
.name
.map(|n| schema_set.name_table.resolve(n).to_string())
.or_else(|| {
ref_decl
.and_then(|d| d.name)
.map(|n| schema_set.name_table.resolve(n).to_string())
})
.unwrap_or_else(|| "(anonymous)".to_string());
let constraint = if is_fixed { "fixed" } else { "default" };
let location = attr_use
.attribute
.source
.as_ref()
.or(ct.source.as_ref())
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"a-props-correct",
format!(
"Attribute '{}' {} value '{}' is not valid for its declared type",
attr_name, constraint, value
),
location,
));
}
}
}
Ok(())
}
pub fn validate_element_value_constraints(schema_set: &SchemaSet) -> SchemaResult<()> {
use crate::parser::frames::ComplexContentResult;
use crate::types::XmlTypeCode;
let id_key = schema_set.builtin_types().get_by_type_code(XmlTypeCode::Id);
let any_type_key = TypeKey::Complex(schema_set.any_type_key());
for (_key, elem) in schema_set.arenas.elements.iter() {
let (value, is_fixed) = match (&elem.default_value, &elem.fixed_value) {
(Some(v), _) => (v.as_str(), false),
(_, Some(v)) => (v.as_str(), true),
(None, None) => continue,
};
if elem.resolved_ref.is_some() {
continue;
}
let type_key = match elem.resolved_type {
Some(tk) if tk != any_type_key => tk,
_ => continue,
};
let elem_name = || {
elem.name
.map(|n| schema_set.name_table.resolve_ref(n))
.unwrap_or("(anonymous)")
};
let location = || schema_set.locate(elem.source.as_ref());
let constraint = if is_fixed { "fixed" } else { "default" };
if let TypeKey::Complex(ct_key) = type_key {
if let Some(ct) = schema_set.arenas.complex_types.get(ct_key) {
let simple_content = matches!(ct.content, ComplexContentResult::Simple(_));
if !simple_content && !ct.mixed {
return Err(SchemaError::structural(
"src-element",
format!(
"Element '{}' has '{}' value but its type is a complex type \
with non-mixed, non-simple content",
elem_name(),
constraint
),
location(),
));
}
}
}
if !schema_set.is_xsd11() {
if let (Some(id_simple_key), TypeKey::Simple(st_key)) = (id_key, type_key) {
if schema_set.derives_from(st_key, id_simple_key) {
return Err(SchemaError::structural(
"e-props-correct.4",
format!(
"Element '{}' has type xs:ID (or derived) and must not have a {} value constraint",
elem_name(), constraint
),
location(),
));
}
}
}
let effective_type = match type_key {
TypeKey::Simple(_) => Some(type_key),
TypeKey::Complex(ck) => schema_set
.arenas
.complex_types
.get(ck)
.and_then(|ct| effective_simple_content_type_key(schema_set, ct)),
};
if let Some(st_key) = effective_type {
if crate::validation::simple::validate_simple_type(value, st_key, schema_set).is_err() {
return Err(SchemaError::structural(
"e-props-correct.2",
format!(
"Element '{}' {} value '{}' is not valid for its declared type",
elem_name(),
constraint,
value
),
location(),
));
}
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
pub fn validate_element_type_alternatives(schema_set: &SchemaSet) -> SchemaResult<()> {
if !schema_set.is_xsd11() {
return Ok(());
}
for (_key, elem) in schema_set.arenas.elements.iter() {
let alts = &elem.alternatives;
if alts.len() < 2 {
continue;
}
for alt in &alts[..alts.len() - 1] {
if alt.test.is_none() {
let name = elem
.name
.map(|n| schema_set.name_table.resolve_ref(n))
.unwrap_or("(anonymous)");
let location = schema_set.locate(elem.source.as_ref());
return Err(SchemaError::structural(
"src-type-alternative",
format!(
"Element '{}': <xs:alternative> without a 'test' attribute is only \
permitted as the last alternative",
name
),
location,
));
}
}
}
Ok(())
}
pub fn validate_complex_type_attribute_uniqueness(schema_set: &SchemaSet) -> SchemaResult<()> {
use std::collections::{HashMap, HashSet};
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
enum AttrDeclId {
GlobalRef(AttributeKey),
InlineGroup(AttributeGroupKey, usize),
InlineComplex(ComplexTypeKey, usize),
}
fn walk_attribute_group(
schema_set: &SchemaSet,
ag_key: AttributeGroupKey,
visiting_groups: &mut HashSet<AttributeGroupKey>,
seen: &mut HashSet<AttrDeclId>,
out: &mut Vec<EffectiveAttributeUse>,
) {
if !visiting_groups.insert(ag_key) {
return;
}
let Some(ag) = schema_set.arenas.attribute_groups.get(ag_key) else {
visiting_groups.remove(&ag_key);
return;
};
if let Some(ref_key) = ag.resolved_ref {
walk_attribute_group(schema_set, ref_key, visiting_groups, seen, out);
visiting_groups.remove(&ag_key);
return;
}
for (i, attr_use) in ag.attributes.iter().enumerate() {
let resolved = ag.resolved_attributes.get(i);
let decl_id = if let Some(global_key) = resolved.and_then(|r| r.resolved_ref) {
AttrDeclId::GlobalRef(global_key)
} else {
AttrDeclId::InlineGroup(ag_key, i)
};
if seen.insert(decl_id) {
if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
out.push(eau);
}
}
}
for &nested in &ag.resolved_attribute_groups {
walk_attribute_group(schema_set, nested, visiting_groups, seen, out);
}
visiting_groups.remove(&ag_key);
}
fn collect_with_dedup(
schema_set: &SchemaSet,
type_def: &crate::arenas::ComplexTypeDefData,
ct_key: ComplexTypeKey,
depth: usize,
visiting_groups: &mut HashSet<AttributeGroupKey>,
seen: &mut HashSet<AttrDeclId>,
out: &mut Vec<EffectiveAttributeUse>,
) {
if depth > 50 {
return;
}
for (i, attr_use) in type_def.attributes.iter().enumerate() {
let resolved = type_def.resolved_attributes.get(i);
let decl_id = if let Some(global_key) = resolved.and_then(|r| r.resolved_ref) {
AttrDeclId::GlobalRef(global_key)
} else {
AttrDeclId::InlineComplex(ct_key, i)
};
if seen.insert(decl_id) {
if let Some(eau) = resolve_single_attribute_use(schema_set, attr_use, resolved) {
out.push(eau);
}
}
}
for &ag_key in &type_def.resolved_attribute_groups {
visiting_groups.clear();
walk_attribute_group(schema_set, ag_key, visiting_groups, seen, out);
}
if type_def.derivation_method == Some(DerivationMethod::Extension) {
if let Some(TypeKey::Complex(base_key)) = type_def.resolved_base_type {
if let Some(base) = schema_set.arenas.complex_types.get(base_key) {
collect_with_dedup(
schema_set,
base,
base_key,
depth + 1,
visiting_groups,
seen,
out,
);
}
}
}
}
let mut seen: HashSet<AttrDeclId> = HashSet::new();
let mut attrs: Vec<EffectiveAttributeUse> = Vec::new();
let mut visiting_groups: HashSet<AttributeGroupKey> = HashSet::new();
let mut by_name: HashMap<(Option<NameId>, NameId), ()> = HashMap::new();
let id_key_for_xsd10 = if schema_set.is_xsd10() {
schema_set
.builtin_types()
.get_by_type_code(crate::types::XmlTypeCode::Id)
} else {
None
};
for (key, type_def) in schema_set.arenas.complex_types.iter() {
seen.clear();
attrs.clear();
by_name.clear();
collect_with_dedup(
schema_set,
type_def,
key,
0,
&mut visiting_groups,
&mut seen,
&mut attrs,
);
attrs.retain(|eau| eau.use_kind != AttributeUseKind::Prohibited);
for attr in &attrs {
if by_name
.insert((attr.target_namespace, attr.name), ())
.is_some()
{
let attr_name_str = schema_set.name_table.resolve(attr.name);
let type_name =
format_type_name(schema_set, type_def.name, type_def.target_namespace);
let location = type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"ct-props-correct",
format!(
"Complex type '{}': two distinct attribute declarations \
with the same expanded name '{}' (ct-props-correct \
clause 4 / ag-props-correct clause 2)",
type_name, attr_name_str,
),
location,
));
}
}
if let Some(id_key) = id_key_for_xsd10 {
let mut id_attrs = attrs.iter().filter(|attr| match attr.resolved_type {
Some(TypeKey::Simple(st_key)) => schema_set.derives_from(st_key, id_key),
_ => false,
});
if let (Some(first), Some(second)) = (id_attrs.next(), id_attrs.next()) {
let first_name = schema_set.name_table.resolve(first.name);
let second_name = schema_set.name_table.resolve(second.name);
let type_name =
format_type_name(schema_set, type_def.name, type_def.target_namespace);
let location = type_def
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"ct-props-correct",
format!(
"Complex type '{}': attributes '{}' and '{}' both have \
xs:ID-derived types (ct-props-correct clause 4; XSD 1.0 only)",
type_name, first_name, second_name,
),
location,
));
}
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
pub fn validate_wildcard_disallowed_names(schema_set: &SchemaSet) -> SchemaResult<()> {
if !schema_set.is_xsd11() {
return Ok(());
}
fn check_wildcard(
schema_set: &SchemaSet,
wc: &WildcardResult,
target_ns: Option<NameId>,
) -> SchemaResult<()> {
use crate::parser::frames::NotQNameItem;
for item in &wc.not_qname {
let NotQNameItem::QName {
namespace: q_ns,
local_name,
} = item
else {
continue;
};
let admitted_by_constraint =
wildcard_namespace_matches(&wc.namespace, *q_ns, target_ns);
let excluded_by_not_namespace = wc
.not_namespace
.iter()
.any(|t| t.resolve(target_ns) == *q_ns);
if !admitted_by_constraint || excluded_by_not_namespace {
let location = schema_set.locate(wc.source.as_ref());
let qname_text = match q_ns {
Some(ns) => format!(
"{{{}}}:{}",
schema_set.name_table.resolve_ref(*ns),
schema_set.name_table.resolve_ref(*local_name),
),
None => schema_set.name_table.resolve_ref(*local_name).to_string(),
};
return Err(SchemaError::structural(
"w-props-correct",
format!(
"notQName entry '{}' is not admitted by the wildcard's \
namespace constraint (ยง3.10.6.1 rule 4)",
qname_text
),
location,
));
}
}
Ok(())
}
fn check_particle(
schema_set: &SchemaSet,
particle: &ParticleResult,
target_ns: Option<NameId>,
depth: usize,
) -> SchemaResult<()> {
if depth > 100 {
return Ok(());
}
match &particle.term {
ParticleTerm::Any(wc) => check_wildcard(schema_set, wc, target_ns)?,
ParticleTerm::Group(group) => {
for child in &group.particles {
check_particle(schema_set, child, target_ns, depth + 1)?;
}
}
ParticleTerm::Element(_) => {}
}
Ok(())
}
for (_key, ct) in schema_set.arenas.complex_types.iter() {
let target_ns = ct.target_namespace;
if let Some(wc) = ct.attribute_wildcard.as_ref() {
check_wildcard(schema_set, wc, target_ns)?;
}
match &ct.content {
ComplexContentResult::Empty => {}
ComplexContentResult::Simple(sc) => {
if let Some(wc) = sc.attribute_wildcard.as_ref() {
check_wildcard(schema_set, wc, target_ns)?;
}
}
ComplexContentResult::Complex(cc) => {
if let Some(wc) = cc.attribute_wildcard.as_ref() {
check_wildcard(schema_set, wc, target_ns)?;
}
if let Some(p) = cc.particle.as_ref() {
check_particle(schema_set, p, target_ns, 0)?;
}
if let Some(oc) = cc.open_content.as_ref() {
if let Some(wc) = oc.wildcard.as_ref() {
check_wildcard(schema_set, wc, target_ns)?;
}
}
}
}
if let Some(oc) = ct.open_content.as_ref() {
if let Some(wc) = oc.wildcard.as_ref() {
check_wildcard(schema_set, wc, target_ns)?;
}
}
}
for (_key, ag) in schema_set.arenas.attribute_groups.iter() {
if let Some(wc) = ag.attribute_wildcard.as_ref() {
check_wildcard(schema_set, wc, ag.target_namespace)?;
}
}
for (_key, mg) in schema_set.arenas.model_groups.iter() {
for child in &mg.particles {
check_particle(schema_set, child, mg.target_namespace, 0)?;
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
pub fn validate_wildcard_element_type_table_consistency(
schema_set: &SchemaSet,
) -> SchemaResult<()> {
use crate::parser::frames::AlternativeResult;
if !schema_set.is_xsd11() {
return Ok(());
}
#[allow(clippy::too_many_arguments)]
fn walk_collect<'a>(
particle: &'a ParticleResult,
target_ns: Option<NameId>,
schema_set: &'a SchemaSet,
local_keys: &[Option<ElementKey>],
flat_idx: &mut usize,
local_elems: &mut Vec<(Option<NameId>, NameId, ElementKey, Option<SourceRef>)>,
wildcards: &mut Vec<&'a WildcardResult>,
depth: usize,
) {
if depth > 100 {
return;
}
match &particle.term {
ParticleTerm::Element(elem) => {
if let Some(ref_qn) = &elem.ref_name {
*flat_idx += 1;
let _ = ref_qn;
} else if let Some(name) = elem.name {
let ns = elem.target_namespace.or(target_ns);
let idx = *flat_idx;
*flat_idx += 1;
if let Some(key) = local_keys.get(idx).copied().flatten() {
local_elems.push((ns, name, key, elem.source.clone()));
}
}
}
ParticleTerm::Any(wc) => {
wildcards.push(wc);
}
ParticleTerm::Group(group) => {
if let Some(ref_qn) = &group.ref_name {
if let Some(group_key) =
schema_set.lookup_model_group(ref_qn.namespace, ref_qn.local_name)
{
let mg = &schema_set.arenas.model_groups[group_key];
let mg_ns = mg.target_namespace.or(target_ns);
let mut group_flat_idx = 0usize;
for child in &mg.particles {
walk_collect(
child,
mg_ns,
schema_set,
&mg.resolved_particle_elements,
&mut group_flat_idx,
local_elems,
wildcards,
depth + 1,
);
}
}
} else {
for child in &group.particles {
walk_collect(
child,
target_ns,
schema_set,
local_keys,
flat_idx,
local_elems,
wildcards,
depth + 1,
);
}
}
}
}
}
fn alt_effective_type(alt: &AlternativeResult, schema_set: &SchemaSet) -> Option<TypeKey> {
use crate::parser::frames::TypeRefResult;
if let Some(t) = alt.resolved_type {
return Some(t);
}
if let Some(TypeRefResult::QName(qname)) = &alt.type_ref {
return schema_set
.lookup_type(qname.namespace, qname.local_name)
.or_else(|| {
schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name)
});
}
None
}
fn alternatives_equivalent(
a: &[AlternativeResult],
b: &[AlternativeResult],
schema_set: &SchemaSet,
) -> bool {
if a.len() != b.len() {
return false;
}
for (x, y) in a.iter().zip(b.iter()) {
if x.test != y.test {
return false;
}
if alt_effective_type(x, schema_set) != alt_effective_type(y, schema_set) {
return false;
}
}
true
}
for (_key, ct) in schema_set.arenas.complex_types.iter() {
let target_ns = ct.target_namespace;
let ComplexContentResult::Complex(cc) = &ct.content else {
continue;
};
let Some(particle) = cc.particle.as_ref() else {
continue;
};
let mut local_elems: Vec<(Option<NameId>, NameId, ElementKey, Option<SourceRef>)> =
Vec::new();
let mut wildcards: Vec<&WildcardResult> = Vec::new();
let mut flat_idx = 0usize;
walk_collect(
particle,
target_ns,
schema_set,
&ct.resolved_content_particle_elements,
&mut flat_idx,
&mut local_elems,
&mut wildcards,
0,
);
if let Some(oc) = cc.open_content.as_ref() {
if let Some(wc) = oc.wildcard.as_ref() {
wildcards.push(wc);
}
}
if wildcards.is_empty() {
continue;
}
for (l_ns, l_name, l_key, l_source) in &local_elems {
let global_key = schema_set.lookup_element(*l_ns, *l_name);
let Some(g_key) = global_key else {
continue;
};
if *l_key == g_key {
continue;
}
let l_decl = &schema_set.arenas.elements[*l_key];
let g_decl = &schema_set.arenas.elements[g_key];
let admitted = wildcards.iter().any(|wc| {
if matches!(wc.process_contents, ProcessContents::Skip) {
return false;
}
wildcard_result_admits_qname(wc, target_ns, *l_ns, *l_name)
});
if !admitted {
continue;
}
if !alternatives_equivalent(&l_decl.alternatives, &g_decl.alternatives, schema_set) {
let location = schema_set
.locate(l_source.as_ref())
.or_else(|| schema_set.locate(ct.source.as_ref()));
let qname = match l_ns {
Some(ns) => format!(
"{{{}}}:{}",
schema_set.name_table.resolve_ref(*ns),
schema_set.name_table.resolve_ref(*l_name),
),
None => schema_set.name_table.resolve_ref(*l_name).to_string(),
};
return Err(SchemaError::structural(
"cos-element-consistent",
format!(
"Local element '{}' is in the same content model as a strict/lax \
wildcard that admits its expanded name; the local element's type \
table is not equivalent to that of the top-level declaration of \
the same name (ยง3.8.6.3 / cos-element-consistent)",
qname
),
location,
));
}
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
type LocalElementEntry = (Option<NameId>, NameId, ElementKey, Option<SourceRef>);
#[cfg(feature = "xsd11")]
fn walk_collect_local_elements(
particle: &ParticleResult,
target_ns: Option<NameId>,
local_keys: &[Option<ElementKey>],
flat_idx: &mut usize,
out: &mut Vec<LocalElementEntry>,
) {
if let ParticleTerm::Group(group) = &particle.term {
walk_group_local_elements(&group.particles, target_ns, local_keys, flat_idx, out);
}
}
#[cfg(feature = "xsd11")]
fn walk_group_local_elements(
particles: &[ParticleResult],
target_ns: Option<NameId>,
local_keys: &[Option<ElementKey>],
flat_idx: &mut usize,
out: &mut Vec<LocalElementEntry>,
) {
for p in particles {
match &p.term {
ParticleTerm::Element(elem) if elem.ref_name.is_none() => {
if let Some(Some(elem_key)) = local_keys.get(*flat_idx) {
let ns = elem.target_namespace.or(target_ns);
if let Some(name) = elem.name {
out.push((ns, name, *elem_key, elem.source.clone()));
}
}
*flat_idx += 1;
}
ParticleTerm::Element(_) => {
*flat_idx += 1;
}
ParticleTerm::Group(group) if group.ref_name.is_none() => {
walk_group_local_elements(&group.particles, target_ns, local_keys, flat_idx, out);
}
_ => {}
}
}
}
#[cfg(feature = "xsd11")]
fn collect_local_elements(ct: &crate::arenas::ComplexTypeDefData) -> Vec<LocalElementEntry> {
let mut out = Vec::new();
let ComplexContentResult::Complex(cc) = &ct.content else {
return out;
};
let Some(particle) = cc.particle.as_ref() else {
return out;
};
let mut flat_idx = 0usize;
walk_collect_local_elements(
particle,
ct.target_namespace,
&ct.resolved_content_particle_elements,
&mut flat_idx,
&mut out,
);
out
}
#[cfg(feature = "xsd11")]
fn alt_effective_type(
alt: &crate::parser::frames::AlternativeResult,
schema_set: &SchemaSet,
) -> Option<TypeKey> {
use crate::parser::frames::TypeRefResult;
if let Some(t) = alt.resolved_type {
return Some(t);
}
if let Some(TypeRefResult::QName(qname)) = &alt.type_ref {
return schema_set
.lookup_type(qname.namespace, qname.local_name)
.or_else(|| schema_set.get_built_in_type_by_qname(qname.namespace, qname.local_name));
}
None
}
#[cfg(feature = "xsd11")]
fn alternatives_equivalent(
a: &[crate::parser::frames::AlternativeResult],
b: &[crate::parser::frames::AlternativeResult],
schema_set: &SchemaSet,
) -> bool {
if a.len() != b.len() {
return false;
}
a.iter().zip(b.iter()).all(|(x, y)| {
x.test == y.test && alt_effective_type(x, schema_set) == alt_effective_type(y, schema_set)
})
}
#[cfg(feature = "xsd11")]
pub fn validate_local_element_type_table_consistency(schema_set: &SchemaSet) -> SchemaResult<()> {
use std::collections::HashMap;
if !schema_set.is_xsd11() {
return Ok(());
}
for (_key, ct) in schema_set.arenas.complex_types.iter() {
let local_elems = collect_local_elements(ct);
if local_elems.len() < 2 {
continue;
}
let mut by_name: HashMap<(Option<NameId>, NameId), Vec<usize>> = HashMap::new();
for (i, (ns, name, _, _)) in local_elems.iter().enumerate() {
by_name.entry((*ns, *name)).or_default().push(i);
}
for (qname, indices) in &by_name {
if indices.len() < 2 {
continue;
}
let first_idx = indices[0];
let first_decl = &schema_set.arenas.elements[local_elems[first_idx].2];
for &idx in &indices[1..] {
let other_decl = &schema_set.arenas.elements[local_elems[idx].2];
if alternatives_equivalent(
&first_decl.alternatives,
&other_decl.alternatives,
schema_set,
) {
continue;
}
let qn_str = match qname.0 {
Some(ns) => format!(
"{{{}}}{}",
schema_set.name_table.resolve_ref(ns),
schema_set.name_table.resolve_ref(qname.1),
),
None => schema_set.name_table.resolve_ref(qname.1).to_string(),
};
let location = schema_set
.locate(local_elems[idx].3.as_ref())
.or_else(|| schema_set.locate(ct.source.as_ref()));
return Err(SchemaError::structural(
"cos-element-consistent",
format!(
"Two local element declarations of '{}' appear in the same \
content model but their type tables are not equivalent \
(ยง3.8.6.3 / cos-element-consistent)",
qn_str
),
location,
));
}
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
pub fn validate_restriction_local_element_type_table_consistency(
schema_set: &SchemaSet,
) -> SchemaResult<()> {
use std::collections::HashMap;
if !schema_set.is_xsd11() {
return Ok(());
}
for (_key, ct) in schema_set.arenas.complex_types.iter() {
if ct.derivation_method != Some(crate::parser::frames::DerivationMethod::Restriction) {
continue;
}
let Some(TypeKey::Complex(base_ck)) = ct.resolved_base_type else {
continue;
};
let Some(base_ct) = schema_set.arenas.complex_types.get(base_ck) else {
continue;
};
let derived_locals = collect_local_elements(ct);
if derived_locals.is_empty() {
continue;
}
let base_locals = collect_local_elements(base_ct);
if base_locals.is_empty() {
continue;
}
let mut base_by_name: HashMap<(Option<NameId>, NameId), ElementKey> = HashMap::new();
for (ns, name, ek, _) in &base_locals {
base_by_name.entry((*ns, *name)).or_insert(*ek);
}
for (ns, name, derived_ek, derived_src) in &derived_locals {
let Some(&base_ek) = base_by_name.get(&(*ns, *name)) else {
continue;
};
let derived_decl = &schema_set.arenas.elements[*derived_ek];
let base_decl = &schema_set.arenas.elements[base_ek];
if alternatives_equivalent(
&derived_decl.alternatives,
&base_decl.alternatives,
schema_set,
) {
continue;
}
let qn_str = match ns {
Some(ns_id) => format!(
"{{{}}}{}",
schema_set.name_table.resolve_ref(*ns_id),
schema_set.name_table.resolve_ref(*name),
),
None => schema_set.name_table.resolve_ref(*name).to_string(),
};
let location = schema_set
.locate(derived_src.as_ref())
.or_else(|| schema_set.locate(ct.source.as_ref()));
let derived_name = format_type_name(schema_set, ct.name, ct.target_namespace);
let base_name = format_type_name(schema_set, base_ct.name, base_ct.target_namespace);
return Err(SchemaError::structural(
"cos-element-consistent",
format!(
"Complex type '{}' restricting '{}': local element '{}' has a \
type table that is not equivalent to the base type's local \
element of the same name (ยง3.4.6.3 / cos-element-consistent)",
derived_name, base_name, qn_str,
),
location,
));
}
}
Ok(())
}
#[cfg(feature = "xsd11")]
fn wildcard_result_admits_qname(
wc: &WildcardResult,
target_ns: Option<NameId>,
ns: Option<NameId>,
name: NameId,
) -> bool {
use crate::parser::frames::NotQNameItem;
if !wildcard_namespace_matches(&wc.namespace, ns, target_ns) {
return false;
}
if wc.not_namespace.iter().any(|t| t.resolve(target_ns) == ns) {
return false;
}
!wc.not_qname.iter().any(|item| match item {
NotQNameItem::QName {
namespace,
local_name,
} => *namespace == ns && *local_name == name,
NotQNameItem::Defined | NotQNameItem::DefinedSibling => true,
})
}
pub fn validate_xsd10_annotation_source_anyuri(schema_set: &SchemaSet) -> SchemaResult<()> {
use crate::schema::annotation::{Annotation, AnnotationItem};
use crate::types::validators::is_strict_xsd10_anyuri;
if !schema_set.is_xsd10() {
return Ok(());
}
fn check_annotation(
schema_set: &SchemaSet,
annotation: Option<&Annotation>,
) -> SchemaResult<()> {
let Some(annotation) = annotation else {
return Ok(());
};
for item in &annotation.items {
match item {
AnnotationItem::AppInfo(ai) => {
if let Some(ref src) = ai.source {
if !is_strict_xsd10_anyuri(src) {
let location = ai
.source_ref
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"cvc-datatype-valid",
format!(
"<xs:appinfo source=\"{}\"> is not a valid xs:anyURI \
(XSD 1.0 strict scheme syntax)",
src
),
location,
));
}
}
}
AnnotationItem::Documentation(d) => {
if let Some(ref src) = d.source {
if !is_strict_xsd10_anyuri(src) {
let location = d
.source_ref
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
return Err(SchemaError::structural(
"cvc-datatype-valid",
format!(
"<xs:documentation source=\"{}\"> is not a valid \
xs:anyURI (XSD 1.0 strict scheme syntax)",
src
),
location,
));
}
}
}
}
}
Ok(())
}
for (_k, ct) in schema_set.arenas.complex_types.iter() {
check_annotation(schema_set, ct.annotation.as_ref())?;
}
for (_k, st) in schema_set.arenas.simple_types.iter() {
check_annotation(schema_set, st.annotation.as_ref())?;
}
for (_k, el) in schema_set.arenas.elements.iter() {
check_annotation(schema_set, el.annotation.as_ref())?;
}
for (_k, at) in schema_set.arenas.attributes.iter() {
check_annotation(schema_set, at.annotation.as_ref())?;
}
for (_k, ag) in schema_set.arenas.attribute_groups.iter() {
check_annotation(schema_set, ag.annotation.as_ref())?;
}
for (_k, mg) in schema_set.arenas.model_groups.iter() {
check_annotation(schema_set, mg.annotation.as_ref())?;
}
for (_k, n) in schema_set.arenas.notations.iter() {
check_annotation(schema_set, n.annotation.as_ref())?;
}
for (_k, ic) in schema_set.arenas.identity_constraints.iter() {
check_annotation(schema_set, ic.annotation.as_ref())?;
}
for doc in &schema_set.documents {
for ann in &doc.annotations {
check_annotation(schema_set, Some(ann))?;
}
}
Ok(())
}
pub fn validate_no_xsi_attribute_declarations(schema_set: &SchemaSet) -> SchemaResult<()> {
use crate::namespace::table::well_known;
for (_key, attr) in schema_set.arenas.attributes.iter() {
if attr.source.is_none() {
continue;
}
let Some(ns) = attr.target_namespace else {
continue;
};
if ns != well_known::XSI_NAMESPACE {
continue;
}
let attr_name = attr
.name
.map(|n| schema_set.name_table.resolve_ref(n).to_string())
.unwrap_or_else(|| "(anonymous)".to_string());
let location = schema_set.locate(attr.source.as_ref());
return Err(SchemaError::structural(
"no-xsi",
format!(
"Attribute declaration '{}' has target namespace \
'http://www.w3.org/2001/XMLSchema-instance', which is \
reserved (no-xsi, ยง3.2.6.4)",
attr_name
),
location,
));
}
let any_xsi_owner = schema_set
.documents
.iter()
.any(|d| d.target_namespace == Some(well_known::XSI_NAMESPACE));
let owners_to_scan = any_xsi_owner;
for (_key, group) in schema_set.arenas.attribute_groups.iter() {
check_no_xsi_in_attribute_uses(
schema_set,
&group.attributes,
group.target_namespace,
owners_to_scan,
)?;
}
for (_key, ct) in schema_set.arenas.complex_types.iter() {
check_no_xsi_in_attribute_uses(
schema_set,
&ct.attributes,
ct.target_namespace,
owners_to_scan,
)?;
}
Ok(())
}
fn check_no_xsi_in_attribute_uses(
schema_set: &SchemaSet,
attrs: &[crate::parser::frames::AttributeUseResult],
owner_target_namespace: Option<NameId>,
owner_could_be_xsi: bool,
) -> SchemaResult<()> {
use crate::namespace::table::well_known;
for attr_use in attrs {
if attr_use.attribute.ref_name.is_some() {
continue;
}
let explicit_xsi = attr_use.attribute.target_namespace == Some(well_known::XSI_NAMESPACE);
if !explicit_xsi && !owner_could_be_xsi {
continue;
}
let effective_ns = schema_set.effective_local_attribute_namespace(
attr_use.attribute.target_namespace,
attr_use.attribute.form.as_deref(),
attr_use.attribute.source.as_ref(),
owner_target_namespace,
);
if effective_ns != Some(well_known::XSI_NAMESPACE) {
continue;
}
let attr_name = attr_use
.attribute
.name
.map(|n| schema_set.name_table.resolve_ref(n).to_string())
.unwrap_or_else(|| "(anonymous)".to_string());
let location = schema_set.locate(attr_use.attribute.source.as_ref());
return Err(SchemaError::structural(
"no-xsi",
format!(
"Attribute declaration '{}' has target namespace \
'http://www.w3.org/2001/XMLSchema-instance', which is \
reserved (no-xsi, ยง3.2.6.4)",
attr_name
),
location,
));
}
Ok(())
}
pub fn validate_local_decl_target_namespace(schema_set: &SchemaSet) -> SchemaResult<()> {
use crate::parser::frames::{ComplexContentResult, ParticleResult, ParticleTerm};
fn find_divergent_local_element<'a>(
schema_set: &'a SchemaSet,
particle: &'a ParticleResult,
schema_tns: Option<NameId>,
depth: usize,
) -> Option<(Option<SourceRef>, String)> {
if depth > 100 {
return None;
}
match &particle.term {
ParticleTerm::Element(elem) => {
if elem.ref_name.is_some() {
return None;
}
if let Some(ns) = elem.target_namespace {
if Some(ns) != schema_tns {
let name_str = elem
.name
.map(|n| schema_set.name_table.resolve_ref(n).to_string())
.unwrap_or_default();
return Some((elem.source.clone(), name_str));
}
}
}
ParticleTerm::Group(group) => {
if group.ref_name.is_none() {
for child in &group.particles {
if let Some(found) =
find_divergent_local_element(schema_set, child, schema_tns, depth + 1)
{
return Some(found);
}
}
}
}
ParticleTerm::Any(_) => {}
}
None
}
for (_, ct) in schema_set.arenas.complex_types.iter() {
let schema_tns = ct.target_namespace;
let restriction_of_non_any = match (ct.derivation_method, ct.resolved_base_type) {
(Some(crate::parser::frames::DerivationMethod::Restriction), Some(base_key)) => {
!schema_set.is_any_type(base_key)
}
_ => false,
};
if restriction_of_non_any {
continue;
}
let particle_opt = match &ct.content {
ComplexContentResult::Complex(def) => def.particle.as_ref(),
_ => None,
};
if let Some(particle) = particle_opt {
if let Some((src, name)) =
find_divergent_local_element(schema_set, particle, schema_tns, 0)
{
let location = schema_set
.locate(src.as_ref())
.or_else(|| schema_set.locate(ct.source.as_ref()));
return Err(SchemaError::structural(
"src-element",
format!(
"Local element '{}' has an explicit targetNamespace differing from the \
schema's, but is not inside a <restriction> of a non-anyType base \
(src-element ยง3.3.3 clause 4.3)",
name
),
location,
));
}
}
for au in &ct.attributes {
let attr = &au.attribute;
if attr.ref_name.is_some() {
continue;
}
let Some(ns) = attr.target_namespace else {
continue;
};
if Some(ns) == schema_tns {
continue;
}
let name = attr
.name
.map(|n| schema_set.name_table.resolve_ref(n).to_string())
.unwrap_or_default();
let location = schema_set
.locate(attr.source.as_ref())
.or_else(|| schema_set.locate(ct.source.as_ref()));
return Err(SchemaError::structural(
"src-attribute",
format!(
"Local attribute '{}' has an explicit targetNamespace differing from the \
schema's, but is not inside a <restriction> of a non-anyType base \
(src-attribute ยง3.2.3 clause 6.3)",
name
),
location,
));
}
}
Ok(())
}
pub fn validate_substitution_group_element_consistency(schema_set: &SchemaSet) -> SchemaResult<()> {
use crate::parser::frames::{ComplexContentResult, ParticleResult, ParticleTerm};
use std::collections::HashMap;
type Entry = (TypeKey, Option<SourceRef>);
let mut subst_index: HashMap<ElementKey, Vec<ElementKey>> = HashMap::new();
for (mk, m) in schema_set.arenas.elements.iter() {
for &head in &m.resolved_substitution_groups {
subst_index.entry(head).or_default().push(mk);
}
}
#[allow(clippy::too_many_arguments)]
fn walk_particle(
schema_set: &SchemaSet,
particle: &ParticleResult,
target_ns: Option<NameId>,
local_keys: &[Option<ElementKey>],
flat_idx: &mut usize,
subst_index: &HashMap<ElementKey, Vec<ElementKey>>,
out: &mut HashMap<(Option<NameId>, NameId), Vec<Entry>>,
depth: usize,
) {
if depth > 100 {
return;
}
match &particle.term {
ParticleTerm::Element(elem) => {
if let Some(ref_qn) = &elem.ref_name {
*flat_idx += 1;
let Some(head_key) =
schema_set.lookup_element(ref_qn.namespace, ref_qn.local_name)
else {
return;
};
let mut visited: std::collections::HashSet<ElementKey> =
std::collections::HashSet::new();
let mut stack = vec![head_key];
while let Some(current) = stack.pop() {
if !visited.insert(current) {
continue;
}
let Some(decl) = schema_set.arenas.elements.get(current) else {
continue;
};
if !decl.is_abstract || schema_set.is_xsd11() {
if let (Some(name), Some(t)) = (decl.name, decl.resolved_type) {
out.entry((decl.target_namespace, name))
.or_default()
.push((t, particle.source.clone()));
}
}
if let Some(members) = subst_index.get(¤t) {
stack.extend(members.iter().copied());
}
}
} else {
let idx = *flat_idx;
*flat_idx += 1;
if let Some(Some(elem_key)) = local_keys.get(idx) {
if let Some(decl) = schema_set.arenas.elements.get(*elem_key) {
if let (Some(name), Some(t)) = (decl.name, decl.resolved_type) {
let ns = decl.target_namespace;
out.entry((ns, name))
.or_default()
.push((t, decl.source.clone()));
}
}
}
}
}
ParticleTerm::Group(group) => {
if let Some(ref_qn) = &group.ref_name {
if let Some(group_key) =
schema_set.lookup_model_group(ref_qn.namespace, ref_qn.local_name)
{
let mg = &schema_set.arenas.model_groups[group_key];
let inner_ns = mg.target_namespace.or(target_ns);
let mut inner_idx = 0usize;
for child in &mg.particles {
walk_particle(
schema_set,
child,
inner_ns,
&mg.resolved_particle_elements,
&mut inner_idx,
subst_index,
out,
depth + 1,
);
}
}
} else {
for child in &group.particles {
walk_particle(
schema_set,
child,
target_ns,
local_keys,
flat_idx,
subst_index,
out,
depth + 1,
);
}
}
}
ParticleTerm::Any(_) => {}
}
}
for (_key, ct) in schema_set.arenas.complex_types.iter() {
let ComplexContentResult::Complex(cc) = &ct.content else {
continue;
};
let Some(particle) = cc.particle.as_ref() else {
continue;
};
let mut entries: HashMap<(Option<NameId>, NameId), Vec<Entry>> = HashMap::new();
let mut flat_idx = 0usize;
walk_particle(
schema_set,
particle,
ct.target_namespace,
&ct.resolved_content_particle_elements,
&mut flat_idx,
&subst_index,
&mut entries,
0,
);
for ((ns, name), list) in &entries {
if list.len() < 2 {
continue;
}
let first_type = list[0].0;
for (other_type, other_src) in &list[1..] {
if *other_type == first_type {
continue;
}
let qn_str = format_type_name(schema_set, Some(*name), *ns);
let location = schema_set
.locate(other_src.as_ref())
.or_else(|| schema_set.locate(list[0].1.as_ref()))
.or_else(|| schema_set.locate(ct.source.as_ref()));
return Err(SchemaError::structural(
"cos-element-consistent",
format!(
"Element declarations for '{}' in the same content model \
(counting substitution-group expansion) have different \
{{type definition}}s (ยง3.8.6.3 / cos-element-consistent)",
qn_str
),
location,
));
}
}
}
Ok(())
}
fn collect_flat_attribute_uses_for_group(
schema_set: &SchemaSet,
ag_key: AttributeGroupKey,
) -> Vec<EffectiveAttributeUse> {
let mut result = Vec::new();
collect_attribute_group_uses(schema_set, ag_key, &mut result, 0);
result.retain(|eau| eau.use_kind != AttributeUseKind::Prohibited);
result
}
fn make_redefine_group_restriction_error(
schema_set: &SchemaSet,
derived: &crate::arenas::ModelGroupData,
detail: &str,
) -> SchemaError {
let name = format_type_name(schema_set, derived.name, derived.target_namespace);
let location = derived
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
SchemaError::structural(
"src-redefine.6.2.2",
format!(
"Redefined group '{}' must be a valid restriction of the original \
(ยงsrc-redefine 6.2.2): {}",
name, detail,
),
location,
)
}
fn make_redefine_attr_group_restriction_error(
schema_set: &SchemaSet,
derived: &crate::arenas::AttributeGroupData,
detail: &str,
) -> SchemaError {
let name = format_type_name(schema_set, derived.name, derived.target_namespace);
let location = derived
.source
.as_ref()
.and_then(|s| schema_set.source_maps.locate(s));
SchemaError::structural(
"src-redefine.7.2.2",
format!(
"Redefined attribute group '{}' must be a valid restriction of the original \
(ยงsrc-redefine 7.2.2): {}",
name, detail,
),
location,
)
}
fn validate_all_redefine_group_restrictions(
schema_set: &SchemaSet,
errors: &mut Vec<SchemaError>,
stats: &mut DerivationStats,
) {
for (_key, derived) in schema_set.arenas.model_groups.iter() {
if !derived.redefine_requires_restriction_check {
continue;
}
let Some(original_key) = derived.redefine_original else {
continue;
};
let Some(original) = schema_set.arenas.model_groups.get(original_key) else {
continue;
};
let derived_particle = match normalize_model_group_as_particle(schema_set, derived) {
Ok(p) => p,
Err(e) => {
errors.push(e);
stats.errors += 1;
continue;
}
};
let base_particle = match normalize_model_group_as_particle(schema_set, original) {
Ok(p) => p,
Err(e) => {
errors.push(e);
stats.errors += 1;
continue;
}
};
if is_effectively_empty(&derived_particle) {
if !particle_is_emptiable(&base_particle) {
errors.push(make_redefine_group_restriction_error(
schema_set,
derived,
"removes required content model of the original group",
));
stats.errors += 1;
}
continue;
}
if !particle_restricts(schema_set, &derived_particle, &base_particle) {
errors.push(make_redefine_group_restriction_error(
schema_set,
derived,
"content model is not a valid restriction of the original group",
));
stats.errors += 1;
}
}
}
fn validate_all_redefine_attribute_group_restrictions(
schema_set: &SchemaSet,
errors: &mut Vec<SchemaError>,
stats: &mut DerivationStats,
) {
for (_key, derived) in schema_set.arenas.attribute_groups.iter() {
if !derived.redefine_requires_restriction_check {
continue;
}
let Some(original_key) = derived.redefine_original else {
continue;
};
let Some(original) = schema_set.arenas.attribute_groups.get(original_key) else {
continue;
};
let derived_attrs = collect_flat_attribute_uses_for_group(schema_set, _key);
let base_attrs = collect_flat_attribute_uses_for_group(schema_set, original_key);
let base_effective_wc = match effective_attribute_wildcard(
schema_set,
original.attribute_wildcard.as_ref(),
original.target_namespace,
&original.resolved_attribute_groups,
) {
Ok(eff) => eff,
Err(e) => {
errors.push(e);
stats.errors += 1;
continue;
}
};
let mut failed = false;
for da in &derived_attrs {
if let Some(ba) = base_attrs
.iter()
.find(|b| b.name == da.name && b.target_namespace == da.target_namespace)
{
if let (Some(dt), Some(bt)) = (da.resolved_type, ba.resolved_type) {
if dt != bt && !is_type_derived_from(schema_set, dt, bt) {
let attr_name_str = schema_set.name_table.resolve(da.name).to_string();
errors.push(make_redefine_attr_group_restriction_error(
schema_set,
derived,
&format!(
"attribute '{}' has a type that is not validly derived from the \
base attribute type",
attr_name_str,
),
));
stats.errors += 1;
failed = true;
break;
}
}
if let Some(ref base_fixed) = ba.fixed_value {
let derived_matches =
da.fixed_value.as_ref().is_some_and(|dv| dv == base_fixed);
if !derived_matches {
let attr_name_str = schema_set.name_table.resolve(da.name).to_string();
errors.push(make_redefine_attr_group_restriction_error(
schema_set,
derived,
&format!(
"attribute '{}' relaxes or removes the base 'fixed=\"{}\"' \
value constraint",
attr_name_str, base_fixed,
),
));
stats.errors += 1;
failed = true;
break;
}
}
continue;
}
if let Some(ref bwc) = base_effective_wc {
if effective_wildcard_allows_attribute(
schema_set,
bwc,
da.target_namespace,
da.name,
) {
continue;
}
}
let attr_name_str = schema_set.name_table.resolve(da.name).to_string();
errors.push(make_redefine_attr_group_restriction_error(
schema_set,
derived,
&format!(
"attribute '{}' is not present in the original and is not admitted by \
the original's attribute wildcard",
attr_name_str,
),
));
stats.errors += 1;
failed = true;
break;
}
if failed {
continue;
}
let mut req_failed = false;
for ba in &base_attrs {
if ba.use_kind != AttributeUseKind::Required {
continue;
}
let matching = derived_attrs
.iter()
.find(|d| d.name == ba.name && d.target_namespace == ba.target_namespace);
match matching {
Some(da) if da.use_kind == AttributeUseKind::Required => {}
_ => {
let attr_name_str = schema_set.name_table.resolve(ba.name).to_string();
errors.push(make_redefine_attr_group_restriction_error(
schema_set,
derived,
&format!(
"base attribute '{}' is required but the redefined group does not \
declare it as required",
attr_name_str,
),
));
stats.errors += 1;
req_failed = true;
break;
}
}
}
if req_failed {
continue;
}
let derived_effective_wc = match effective_attribute_wildcard(
schema_set,
derived.attribute_wildcard.as_ref(),
derived.target_namespace,
&derived.resolved_attribute_groups,
) {
Ok(eff) => eff,
Err(e) => {
errors.push(e);
stats.errors += 1;
continue;
}
};
match classify_attribute_wildcard_restriction(
schema_set,
derived_effective_wc.as_ref(),
base_effective_wc.as_ref(),
) {
WildcardRestrictionOutcome::DerivedAbsent | WildcardRestrictionOutcome::Valid => {}
WildcardRestrictionOutcome::AddedInDerived => {
errors.push(make_redefine_attr_group_restriction_error(
schema_set,
derived,
"redefined attribute group declares an attribute wildcard but \
the original has none",
));
stats.errors += 1;
}
WildcardRestrictionOutcome::NotSubset(reason) => {
errors.push(make_redefine_attr_group_restriction_error(
schema_set,
derived,
&format!(
"attribute wildcard is not a valid restriction of the \
original: {}",
reason,
),
));
stats.errors += 1;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arenas::{ComplexTypeDefData, SimpleTypeDefData};
use crate::parser::frames::ComplexContentResult;
use crate::schema::model::DerivationSet;
fn create_simple_type_data(
name: Option<NameId>,
variety: SimpleTypeVariety,
) -> SimpleTypeDefData {
SimpleTypeDefData {
name,
target_namespace: None,
variety,
base_type: None,
item_type: None,
member_types: Vec::new(),
facets: FacetSet::new(),
final_derivation: DerivationSet::empty(),
id: None,
derivation_id: None,
annotation: None,
source: None,
resolved_base_type: None,
resolved_item_type: None,
resolved_member_types: Vec::new(),
redefine_original: None,
deferred_item_type_error: None,
}
}
fn create_complex_type_data(name: Option<NameId>) -> ComplexTypeDefData {
ComplexTypeDefData {
name,
target_namespace: None,
base_type: None,
derivation_method: None,
content: ComplexContentResult::Empty,
open_content: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: None,
mixed: false,
is_abstract: false,
final_derivation: DerivationSet::empty(),
block: DerivationSet::empty(),
default_attributes_apply: true,
id: None,
#[cfg(feature = "xsd11")]
assertions: Vec::new(),
#[cfg(feature = "xsd11")]
xpath_default_namespace: None,
annotation: None,
source: None,
resolved_base_type: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
resolved_content_particle_types: Vec::new(),
resolved_content_particle_elements: Vec::new(),
resolved_simple_content_type: None,
redefine_original: None,
}
}
#[test]
fn test_derivation_stats_default() {
let stats = DerivationStats::default();
assert_eq!(stats.simple_types_validated, 0);
assert_eq!(stats.complex_types_validated, 0);
assert_eq!(stats.errors, 0);
}
#[test]
fn test_validate_empty_schema() {
let schema_set = SchemaSet::new();
let dep_graph = DependencyGraph::new();
let result = validate_all_derivations(&schema_set, &dep_graph);
assert!(result.is_ok());
let stats = result.unwrap();
assert_eq!(stats.simple_types_validated, 0);
assert_eq!(stats.complex_types_validated, 0);
}
#[test]
fn test_validate_atomic_type_no_base() {
let mut schema_set = SchemaSet::new();
let type_data = create_simple_type_data(None, SimpleTypeVariety::Atomic);
let key = schema_set.arenas.alloc_simple_type(type_data);
let mut stats = DerivationStats::default();
let result = validate_simple_type(&schema_set, key, &mut stats);
assert!(result.is_ok());
assert_eq!(stats.simple_types_validated, 1);
}
#[test]
fn test_validate_list_type_no_item() {
let mut schema_set = SchemaSet::new();
let type_data = create_simple_type_data(None, SimpleTypeVariety::List);
let key = schema_set.arenas.alloc_simple_type(type_data);
let mut stats = DerivationStats::default();
let result = validate_simple_type(&schema_set, key, &mut stats);
assert!(result.is_ok());
assert_eq!(stats.list_types_validated, 1);
}
#[test]
fn test_validate_union_type_no_members() {
let mut schema_set = SchemaSet::new();
let type_data = create_simple_type_data(None, SimpleTypeVariety::Union);
let key = schema_set.arenas.alloc_simple_type(type_data);
let mut stats = DerivationStats::default();
let result = validate_simple_type(&schema_set, key, &mut stats);
assert!(result.is_ok());
assert_eq!(stats.union_types_validated, 1);
}
#[test]
fn test_validate_list_of_atomic() {
let mut schema_set = SchemaSet::new();
let item_type_data = create_simple_type_data(None, SimpleTypeVariety::Atomic);
let item_key = schema_set.arenas.alloc_simple_type(item_type_data);
let mut list_type_data = create_simple_type_data(None, SimpleTypeVariety::List);
list_type_data.resolved_item_type = Some(TypeKey::Simple(item_key));
let list_key = schema_set.arenas.alloc_simple_type(list_type_data);
let mut stats = DerivationStats::default();
let result = validate_simple_type(&schema_set, list_key, &mut stats);
assert!(result.is_ok());
}
#[test]
fn test_validate_list_of_list_error() {
let mut schema_set = SchemaSet::new();
let inner_list_data = create_simple_type_data(None, SimpleTypeVariety::List);
let inner_key = schema_set.arenas.alloc_simple_type(inner_list_data);
let mut outer_list_data = create_simple_type_data(None, SimpleTypeVariety::List);
outer_list_data.resolved_item_type = Some(TypeKey::Simple(inner_key));
let outer_key = schema_set.arenas.alloc_simple_type(outer_list_data);
let mut stats = DerivationStats::default();
let result = validate_simple_type(&schema_set, outer_key, &mut stats);
assert!(result.is_err());
if let Err(SchemaError::StructuralError { constraint, .. }) = result {
assert_eq!(constraint, "cos-list-of-atomic");
} else {
panic!("Expected structural error with cos-list-of-atomic constraint");
}
}
#[test]
fn test_validate_union_with_complex_member_error() {
let mut schema_set = SchemaSet::new();
let complex_data = create_complex_type_data(None);
let complex_key = schema_set.arenas.alloc_complex_type(complex_data);
let mut union_data = create_simple_type_data(None, SimpleTypeVariety::Union);
union_data.resolved_member_types = vec![TypeKey::Complex(complex_key)];
let union_key = schema_set.arenas.alloc_simple_type(union_data);
let mut stats = DerivationStats::default();
let result = validate_simple_type(&schema_set, union_key, &mut stats);
assert!(result.is_err());
if let Err(SchemaError::StructuralError { constraint, .. }) = result {
assert_eq!(constraint, "cos-union-memberTypes");
} else {
panic!("Expected structural error with cos-union-memberTypes constraint");
}
}
#[test]
fn test_validate_complex_type_no_base() {
let mut schema_set = SchemaSet::new();
let type_data = create_complex_type_data(None);
let key = schema_set.arenas.alloc_complex_type(type_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, key, &mut stats);
assert!(result.is_ok());
assert_eq!(stats.complex_types_validated, 1);
}
#[test]
fn test_validate_complex_extension() {
let mut schema_set = SchemaSet::new();
let base_data = create_complex_type_data(None);
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Extension);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_ok());
assert_eq!(stats.extensions_validated, 1);
}
#[test]
fn test_validate_complex_restriction() {
let mut schema_set = SchemaSet::new();
let base_data = create_complex_type_data(None);
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Restriction);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_ok());
assert_eq!(stats.restrictions_validated, 1);
}
#[test]
fn test_validate_extension_of_final_type_error() {
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.final_derivation = DerivationSet::extension();
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Extension);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_err());
if let Err(SchemaError::StructuralError { constraint, .. }) = result {
assert_eq!(constraint, "cos-ct-extends");
} else {
panic!("Expected structural error with cos-ct-extends constraint");
}
}
#[test]
fn test_validate_extension_of_final_default_type_error() {
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.final_derivation = DerivationSet::extension();
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Extension);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_err());
if let Err(SchemaError::StructuralError { constraint, .. }) = result {
assert_eq!(constraint, "cos-ct-extends");
} else {
panic!("Expected structural error with cos-ct-extends constraint");
}
}
#[test]
fn test_validate_restriction_of_final_type_error() {
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.final_derivation = DerivationSet::restriction();
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Restriction);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_err());
if let Err(SchemaError::StructuralError { constraint, .. }) = result {
assert_eq!(constraint, "derivation-ok-restriction");
} else {
panic!("Expected structural error with derivation-ok-restriction constraint");
}
}
#[test]
fn test_format_type_name_anonymous() {
let schema_set = SchemaSet::new();
let name = format_type_name(&schema_set, None, None);
assert_eq!(name, "(anonymous)");
}
#[test]
fn test_format_type_name_with_namespace() {
let schema_set = SchemaSet::new();
let name_id = schema_set.name_table.add("myType");
let ns_id = schema_set.name_table.add("http://example.com");
let name = format_type_name(&schema_set, Some(name_id), Some(ns_id));
assert_eq!(name, "{http://example.com}myType");
}
#[test]
fn test_format_type_name_no_namespace() {
let schema_set = SchemaSet::new();
let name_id = schema_set.name_table.add("myType");
let name = format_type_name(&schema_set, Some(name_id), None);
assert_eq!(name, "myType");
}
#[cfg(feature = "xsd11")]
fn make_open_content(
mode: crate::parser::frames::OpenContentMode,
namespace: crate::parser::frames::WildcardNamespace,
pc: crate::parser::frames::ProcessContents,
) -> crate::parser::frames::OpenContentResult {
crate::parser::frames::OpenContentResult {
mode,
wildcard: Some(crate::parser::frames::WildcardResult {
namespace,
process_contents: pc,
not_namespace: Vec::new(),
not_qname: Vec::new(),
id: None,
annotation: None,
source: None,
}),
id: None,
annotation: None,
source: None,
}
}
#[cfg(feature = "xsd11")]
#[test]
fn test_extension_suffix_cannot_extend_interleave() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Extension);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
derived_data.open_content = Some(make_open_content(
OpenContentMode::Suffix,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_err());
if let Err(SchemaError::StructuralError { constraint, .. }) = result {
assert_eq!(constraint, "cos-ct-extends");
} else {
panic!("Expected cos-ct-extends error");
}
}
#[cfg(feature = "xsd11")]
#[test]
fn test_extension_interleave_extends_interleave_valid() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Extension);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
derived_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_ok());
}
#[cfg(feature = "xsd11")]
#[test]
fn test_extension_base_has_oc_derived_has_none() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Extension);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(
result.is_ok(),
"derived inherits BOT per clause 6.1: {:?}",
result
);
}
#[cfg(feature = "xsd11")]
#[test]
fn test_extension_base_no_oc_derived_adds_oc_valid() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let base_data = create_complex_type_data(None);
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Extension);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
derived_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_ok());
}
#[cfg(feature = "xsd11")]
#[test]
fn test_restriction_adds_oc_when_base_has_none() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let base_data = create_complex_type_data(None);
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Restriction);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
derived_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_err());
if let Err(SchemaError::StructuralError { constraint, .. }) = result {
assert_eq!(constraint, "derivation-ok-restriction");
} else {
panic!("Expected derivation-ok-restriction error");
}
}
#[cfg(feature = "xsd11")]
#[test]
fn test_restriction_removes_oc_valid() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Restriction);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_ok());
}
#[cfg(feature = "xsd11")]
#[test]
fn test_restriction_empty_derived_allows_interleave_over_suffix() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.open_content = Some(make_open_content(
OpenContentMode::Suffix,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Restriction);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
derived_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(
result.is_ok(),
"empty derived content should accept interleave over suffix, got {:?}",
result.err(),
);
}
#[cfg(feature = "xsd11")]
#[test]
fn test_restriction_suffix_restricts_interleave_valid() {
use crate::parser::frames::{OpenContentMode, ProcessContents, WildcardNamespace};
let mut schema_set = SchemaSet::new();
let mut base_data = create_complex_type_data(None);
base_data.open_content = Some(make_open_content(
OpenContentMode::Interleave,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let base_key = schema_set.arenas.alloc_complex_type(base_data);
let mut derived_data = create_complex_type_data(None);
derived_data.derivation_method = Some(DerivationMethod::Restriction);
derived_data.resolved_base_type = Some(TypeKey::Complex(base_key));
derived_data.open_content = Some(make_open_content(
OpenContentMode::Suffix,
WildcardNamespace::Any,
ProcessContents::Lax,
));
let derived_key = schema_set.arenas.alloc_complex_type(derived_data);
let mut stats = DerivationStats::default();
let result = validate_complex_type(&schema_set, derived_key, &mut stats);
assert!(result.is_ok());
}
#[cfg(feature = "xsd11")]
#[test]
fn test_ns_subset_local_not_subset_of_other() {
use crate::parser::frames::WildcardNamespace;
let schema_set = SchemaSet::new();
let urn_a = schema_set.name_table.add("urn:a");
let result = is_namespace_subset(
&WildcardNamespace::Local,
None,
&WildcardNamespace::Other,
Some(urn_a),
);
assert!(
!result,
"##local must NOT be a subset of ##other (absent is excluded)"
);
}
#[cfg(feature = "xsd11")]
#[test]
fn test_ns_subset_other_no_tns_not_subset_of_other_with_tns() {
use crate::parser::frames::WildcardNamespace;
let schema_set = SchemaSet::new();
let urn_a = schema_set.name_table.add("urn:a");
let result = is_namespace_subset(
&WildcardNamespace::Other,
None,
&WildcardNamespace::Other,
Some(urn_a),
);
assert!(
!result,
"##other(tns=None) must NOT be a subset of ##other(tns=urn:a)"
);
}
#[cfg(feature = "xsd11")]
#[test]
fn test_ns_subset_other_with_tns_is_subset_of_other_no_tns() {
use crate::parser::frames::WildcardNamespace;
let schema_set = SchemaSet::new();
let urn_a = schema_set.name_table.add("urn:a");
let result = is_namespace_subset(
&WildcardNamespace::Other,
Some(urn_a),
&WildcardNamespace::Other,
None,
);
assert!(
result,
"##other(tns=urn:a) MUST be a subset of ##other(tns=None)"
);
}
#[cfg(feature = "xsd11")]
#[test]
fn test_ns_subset_list_with_tns_uri_not_subset_of_other() {
use crate::parser::frames::{NamespaceToken, WildcardNamespace};
let schema_set = SchemaSet::new();
let urn_a = schema_set.name_table.add("urn:a");
let urn_b = schema_set.name_table.add("urn:b");
let result = is_namespace_subset(
&WildcardNamespace::List(vec![NamespaceToken::Uri(urn_a), NamespaceToken::Uri(urn_b)]),
None,
&WildcardNamespace::Other,
Some(urn_a),
);
assert!(
!result,
"List containing base's target ns must NOT be a subset of ##other"
);
}
fn default_wildcard(ns: WildcardNamespace) -> WildcardResult {
WildcardResult {
namespace: ns,
process_contents: ProcessContents::Strict,
not_namespace: Vec::new(),
not_qname: Vec::new(),
id: None,
annotation: None,
source: None,
}
}
fn admits(
schema_set: &SchemaSet,
w: &WildcardResult,
target_ns: Option<NameId>,
attr_ns: Option<NameId>,
attr_name: NameId,
) -> bool {
let eff = normalize_attribute_wildcard(schema_set, w, target_ns);
effective_wildcard_allows_attribute(schema_set, &eff, attr_ns, attr_name)
}
#[test]
fn test_effective_wildcard_any_admits_anything() {
let schema_set = SchemaSet::new();
let name = schema_set.name_table.add("foo");
let w = default_wildcard(WildcardNamespace::Any);
assert!(admits(&schema_set, &w, None, None, name));
}
#[test]
fn test_effective_wildcard_other_excludes_target_ns() {
let schema_set = SchemaSet::new();
let ns = schema_set.name_table.add("urn:foo");
let name = schema_set.name_table.add("bar");
let w = default_wildcard(WildcardNamespace::Other);
assert!(
!admits(&schema_set, &w, Some(ns), Some(ns), name),
"##other must NOT admit the target namespace"
);
}
#[test]
fn test_effective_wildcard_other_admits_different_ns() {
let schema_set = SchemaSet::new();
let tns = schema_set.name_table.add("urn:foo");
let other_ns = schema_set.name_table.add("urn:bar");
let name = schema_set.name_table.add("qux");
let w = default_wildcard(WildcardNamespace::Other);
assert!(
admits(&schema_set, &w, Some(tns), Some(other_ns), name),
"##other must admit a namespace different from the target"
);
}
#[test]
fn test_effective_wildcard_other_absent_ns_xsd10_vs_xsd11() {
let schema_10 = SchemaSet::new(); let tns = schema_10.name_table.add("urn:foo");
let name = schema_10.name_table.add("local_attr");
let w = default_wildcard(WildcardNamespace::Other);
assert!(
!admits(&schema_10, &w, Some(tns), None, name),
"XSD 1.0: ##other must NOT admit the absent namespace"
);
let schema_11 = SchemaSet::xsd11();
let tns11 = schema_11.name_table.add("urn:foo");
let name11 = schema_11.name_table.add("local_attr");
assert!(
admits(&schema_11, &w, Some(tns11), None, name11),
"XSD 1.1: ##other MUST admit the absent namespace"
);
}
#[test]
fn test_effective_wildcard_defined_excludes_declared_only() {
use crate::arenas::AttributeDeclData;
use crate::parser::frames::NotQNameItem;
let mut schema_set = SchemaSet::new();
let declared_name = schema_set.name_table.add("declared_attr");
let undeclared_name = schema_set.name_table.add("undeclared_attr");
let attr_data = AttributeDeclData {
name: Some(declared_name),
target_namespace: None,
ref_name: None,
type_ref: None,
inline_type: None,
default_value: None,
fixed_value: None,
use_kind: None,
form: None,
inheritable: false,
id: None,
annotation: None,
source: None,
resolved_type: None,
resolved_ref: None,
};
let attr_key = schema_set.arenas.alloc_attribute(attr_data);
schema_set
.get_or_create_namespace(None)
.register_attribute(declared_name, attr_key);
let mut w = default_wildcard(WildcardNamespace::Any);
w.not_qname = vec![NotQNameItem::Defined];
assert!(
!admits(&schema_set, &w, None, None, declared_name),
"##defined MUST exclude globally-declared attributes"
);
assert!(
admits(&schema_set, &w, None, None, undeclared_name),
"##defined MUST NOT exclude attributes that are not globally declared"
);
}
#[test]
fn test_effective_wildcard_not_qname_literal_excludes() {
use crate::parser::frames::NotQNameItem;
let schema_set = SchemaSet::new();
let blocked = schema_set.name_table.add("blocked");
let allowed = schema_set.name_table.add("allowed");
let mut w = default_wildcard(WildcardNamespace::Any);
w.not_qname = vec![NotQNameItem::QName {
namespace: None,
local_name: blocked,
}];
assert!(!admits(&schema_set, &w, None, None, blocked));
assert!(admits(&schema_set, &w, None, None, allowed));
}
#[test]
fn test_particle_restricts_all_required_over_empty_all_rejects() {
let schema_set = SchemaSet::new();
let e1_name = schema_set.name_table.add("e1");
let any_type = TypeKey::Complex(schema_set.any_type_key());
let make_elem = |min_occurs: u32, max_occurs: Option<u32>| NormalizedParticle {
term: NormalizedParticleTerm::Element(NormalizedElement {
name: e1_name,
namespace: None,
type_key: any_type,
element_key: None,
block: DerivationSet::empty(),
nillable: false,
fixed_value: None,
}),
min_occurs,
max_occurs,
source: None,
};
let derived = NormalizedParticle {
term: NormalizedParticleTerm::Group(NormalizedGroup {
compositor: Compositor::All,
particles: vec![make_elem(1, Some(1))],
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
};
let base_empty_all = NormalizedParticle {
term: NormalizedParticleTerm::Group(NormalizedGroup {
compositor: Compositor::All,
particles: Vec::new(),
}),
min_occurs: 1,
max_occurs: Some(1),
source: None,
};
assert!(
!particle_restricts(&schema_set, &derived, &base_empty_all),
"all{{e1{{1,1}}}} must NOT restrict all{{}} โ derived adds a required particle"
);
}
#[test]
fn test_collect_flat_attribute_uses_filters_prohibited() {
use crate::arenas::AttributeGroupData;
use crate::parser::frames::{
AttributeFrameResult, AttributeUseKind as AuK, AttributeUseResult,
};
let mut schema_set = SchemaSet::new();
let grp_name = schema_set.name_table.add("ag");
let opt_name = schema_set.name_table.add("opt");
let banned_name = schema_set.name_table.add("banned");
let make_attr = |name: NameId, kind: AuK| AttributeUseResult {
attribute: AttributeFrameResult {
name: Some(name),
ref_name: None,
target_namespace: None,
type_ref: None,
inline_type: None,
default_value: None,
fixed_value: None,
use_kind: None,
form: None,
inheritable: false,
id: None,
annotation: None,
source: None,
},
use_kind: kind,
};
let ag = AttributeGroupData {
name: Some(grp_name),
target_namespace: None,
ref_name: None,
attributes: vec![
make_attr(opt_name, AuK::Optional),
make_attr(banned_name, AuK::Prohibited),
],
attribute_groups: Vec::new(),
attribute_wildcard: None,
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: vec![
crate::arenas::ResolvedAttributeUse {
resolved_type: None,
resolved_ref: None,
},
crate::arenas::ResolvedAttributeUse {
resolved_type: None,
resolved_ref: None,
},
],
redefine_original: None,
redefine_requires_restriction_check: false,
};
let ag_key = schema_set.arenas.alloc_attribute_group(ag);
let uses = collect_flat_attribute_uses_for_group(&schema_set, ag_key);
assert_eq!(
uses.len(),
1,
"prohibited attribute uses must be filtered out"
);
assert_eq!(uses[0].name, opt_name);
}
fn wildcard_with_ns(namespace: WildcardNamespace) -> WildcardResult {
WildcardResult {
namespace,
process_contents: ProcessContents::Strict,
not_namespace: Vec::new(),
not_qname: Vec::new(),
id: None,
annotation: None,
source: None,
}
}
#[test]
fn test_normalize_any() {
let schema_set = SchemaSet::new();
let wc = wildcard_with_ns(WildcardNamespace::Any);
let eff = normalize_attribute_wildcard(&schema_set, &wc, None);
assert!(matches!(eff.namespace, CanonicalNs::Any));
}
#[test]
fn test_normalize_list_resolves_tokens() {
use crate::parser::frames::NamespaceToken;
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let target = schema_set.name_table.add("http://t");
let wc = wildcard_with_ns(WildcardNamespace::List(vec![
NamespaceToken::Uri(ns_a),
NamespaceToken::TargetNamespace,
NamespaceToken::Local,
]));
let eff = normalize_attribute_wildcard(&schema_set, &wc, Some(target));
match eff.namespace {
CanonicalNs::Enum(set) => {
assert!(set.contains(&Some(ns_a)));
assert!(set.contains(&Some(target)));
assert!(set.contains(&None));
assert_eq!(set.len(), 3);
}
_ => panic!("expected Enum"),
}
}
#[test]
fn test_normalize_other_xsd10_vs_xsd11() {
let schema_10 = SchemaSet::new();
let schema_11 = SchemaSet::xsd11();
let target_10 = schema_10.name_table.add("http://t");
let target_11 = schema_11.name_table.add("http://t");
let wc10 = wildcard_with_ns(WildcardNamespace::Other);
let wc11 = wildcard_with_ns(WildcardNamespace::Other);
let eff10 = normalize_attribute_wildcard(&schema_10, &wc10, Some(target_10));
let eff11 = normalize_attribute_wildcard(&schema_11, &wc11, Some(target_11));
match eff10.namespace {
CanonicalNs::Not(set) => {
assert!(set.contains(&Some(target_10)));
assert!(set.contains(&None), "XSD 1.0 ##other excludes absent");
}
_ => panic!("expected Not"),
}
match eff11.namespace {
CanonicalNs::Not(set) => {
assert!(set.contains(&Some(target_11)));
assert!(!set.contains(&None), "XSD 1.1 ##other admits absent");
}
_ => panic!("expected Not"),
}
}
#[test]
fn test_normalize_other_absent_target_namespace() {
let schema_10 = SchemaSet::new();
let schema_11 = SchemaSet::xsd11();
let wc = wildcard_with_ns(WildcardNamespace::Other);
let eff10 = normalize_attribute_wildcard(&schema_10, &wc, None);
let eff11 = normalize_attribute_wildcard(&schema_11, &wc, None);
for (label, eff) in [("XSD 1.0", eff10), ("XSD 1.1", eff11)] {
match eff.namespace {
CanonicalNs::Not(set) => {
assert!(
set.contains(&None),
"{}: ##other with absent target MUST exclude the absent namespace",
label,
);
}
other => panic!("{}: expected Not, got {:?}", label, other),
}
}
}
#[test]
fn test_effective_wildcard_restricts_defined_covers_declared_qname() {
use crate::arenas::AttributeDeclData;
use crate::parser::frames::NotQNameItem;
let mut schema_set = SchemaSet::new();
let declared_name = schema_set.name_table.add("declared_attr");
let attr_data = AttributeDeclData {
name: Some(declared_name),
target_namespace: None,
ref_name: None,
type_ref: None,
inline_type: None,
default_value: None,
fixed_value: None,
use_kind: None,
form: None,
inheritable: false,
id: None,
annotation: None,
source: None,
resolved_type: None,
resolved_ref: None,
};
let attr_key = schema_set.arenas.alloc_attribute(attr_data);
schema_set
.get_or_create_namespace(None)
.register_attribute(declared_name, attr_key);
let base = EffectiveAttributeWildcard {
namespace: CanonicalNs::Any,
not_qname: vec![NotQNameItem::QName {
namespace: None,
local_name: declared_name,
}],
process_contents: ProcessContents::Strict,
};
let derived = EffectiveAttributeWildcard {
namespace: CanonicalNs::Any,
not_qname: vec![NotQNameItem::Defined],
process_contents: ProcessContents::Strict,
};
assert!(
effective_attribute_wildcard_restricts(&schema_set, &derived, &base).is_ok(),
"derived ##defined must cover a base literal QName exclusion \
when the attribute is globally declared"
);
let undeclared = schema_set.name_table.add("undeclared_attr");
let base_undeclared = EffectiveAttributeWildcard {
namespace: CanonicalNs::Any,
not_qname: vec![NotQNameItem::QName {
namespace: None,
local_name: undeclared,
}],
process_contents: ProcessContents::Strict,
};
assert!(
effective_attribute_wildcard_restricts(&schema_set, &derived, &base_undeclared)
.is_err(),
"derived ##defined must NOT cover a base QName exclusion \
when the attribute is not globally declared"
);
}
#[test]
fn test_normalize_folds_not_namespace() {
use crate::parser::frames::NamespaceToken;
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let mut wc = wildcard_with_ns(WildcardNamespace::Any);
wc.not_namespace = vec![NamespaceToken::Uri(ns_a)];
let eff = normalize_attribute_wildcard(&schema_set, &wc, None);
match eff.namespace {
CanonicalNs::Not(set) => {
assert_eq!(set.len(), 1);
assert!(set.contains(&Some(ns_a)));
}
_ => panic!("expected Not"),
}
}
#[test]
fn test_intersect_any_is_identity() {
let mut s = std::collections::HashSet::new();
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
s.insert(Some(ns_a));
let enum_a = CanonicalNs::Enum(s.clone());
let result = intersect_canonical_ns(&CanonicalNs::Any, &enum_a);
assert_eq!(result, enum_a);
let result2 = intersect_canonical_ns(&enum_a, &CanonicalNs::Any);
assert_eq!(result2, enum_a);
}
#[test]
fn test_intersect_enum_enum_is_set_intersection() {
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let ns_b = schema_set.name_table.add("http://b");
let ns_c = schema_set.name_table.add("http://c");
let mut s1 = std::collections::HashSet::new();
s1.insert(Some(ns_a));
s1.insert(Some(ns_b));
let mut s2 = std::collections::HashSet::new();
s2.insert(Some(ns_b));
s2.insert(Some(ns_c));
let result = intersect_canonical_ns(&CanonicalNs::Enum(s1), &CanonicalNs::Enum(s2));
match result {
CanonicalNs::Enum(set) => {
assert_eq!(set.len(), 1);
assert!(set.contains(&Some(ns_b)));
}
_ => panic!("expected Enum"),
}
}
#[test]
fn test_intersect_enum_not_is_set_difference() {
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let ns_b = schema_set.name_table.add("http://b");
let mut s = std::collections::HashSet::new();
s.insert(Some(ns_a));
s.insert(Some(ns_b));
let mut n = std::collections::HashSet::new();
n.insert(Some(ns_b));
let result = intersect_canonical_ns(&CanonicalNs::Enum(s), &CanonicalNs::Not(n));
match result {
CanonicalNs::Enum(set) => {
assert_eq!(set.len(), 1);
assert!(set.contains(&Some(ns_a)));
}
_ => panic!("expected Enum"),
}
}
#[test]
fn test_intersect_not_not_is_union_of_exclusions() {
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let ns_b = schema_set.name_table.add("http://b");
let mut n1 = std::collections::HashSet::new();
n1.insert(Some(ns_a));
let mut n2 = std::collections::HashSet::new();
n2.insert(Some(ns_b));
let result = intersect_canonical_ns(&CanonicalNs::Not(n1), &CanonicalNs::Not(n2));
match result {
CanonicalNs::Not(set) => {
assert_eq!(set.len(), 2);
assert!(set.contains(&Some(ns_a)));
assert!(set.contains(&Some(ns_b)));
}
_ => panic!("expected Not"),
}
}
#[test]
fn test_canonical_ns_subset_various() {
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let ns_b = schema_set.name_table.add("http://b");
let empty_set = std::collections::HashSet::new();
let mut s_a = std::collections::HashSet::new();
s_a.insert(Some(ns_a));
let mut s_ab = std::collections::HashSet::new();
s_ab.insert(Some(ns_a));
s_ab.insert(Some(ns_b));
assert!(canonical_ns_subset(&CanonicalNs::Any, &CanonicalNs::Any));
assert!(canonical_ns_subset(
&CanonicalNs::Enum(s_a.clone()),
&CanonicalNs::Any
));
assert!(canonical_ns_subset(
&CanonicalNs::Not(s_a.clone()),
&CanonicalNs::Any
));
assert!(!canonical_ns_subset(
&CanonicalNs::Any,
&CanonicalNs::Enum(s_a.clone())
));
assert!(canonical_ns_subset(
&CanonicalNs::Enum(s_a.clone()),
&CanonicalNs::Enum(s_ab.clone()),
));
assert!(!canonical_ns_subset(
&CanonicalNs::Enum(s_ab.clone()),
&CanonicalNs::Enum(s_a.clone()),
));
assert!(canonical_ns_subset(
&CanonicalNs::Enum(s_a.clone()),
&CanonicalNs::Not(empty_set.clone()),
));
assert!(!canonical_ns_subset(
&CanonicalNs::Enum(s_a.clone()),
&CanonicalNs::Not(s_a.clone()),
));
assert!(canonical_ns_subset(
&CanonicalNs::Not(s_ab.clone()),
&CanonicalNs::Not(s_a.clone()),
));
assert!(!canonical_ns_subset(
&CanonicalNs::Not(s_a.clone()),
&CanonicalNs::Not(s_ab.clone()),
));
assert!(!canonical_ns_subset(
&CanonicalNs::Not(empty_set),
&CanonicalNs::Enum(s_a),
));
}
#[test]
fn test_effective_attribute_wildcard_absent_no_groups_returns_none() {
let schema_set = SchemaSet::new();
let result = effective_attribute_wildcard(&schema_set, None, None, &[]);
assert!(matches!(result, Ok(None)));
}
#[test]
fn test_effective_attribute_wildcard_local_only() {
let schema_set = SchemaSet::new();
let wc = wildcard_with_ns(WildcardNamespace::Any);
let result = effective_attribute_wildcard(&schema_set, Some(&wc), None, &[]).unwrap();
let eff = result.expect("expected Some");
assert!(matches!(eff.namespace, CanonicalNs::Any));
}
#[test]
fn test_effective_attribute_wildcard_intersects_across_group_and_local() {
use crate::arenas::AttributeGroupData;
use crate::parser::frames::NamespaceToken;
let mut schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let ns_b = schema_set.name_table.add("http://b");
let group_wc = WildcardResult {
namespace: WildcardNamespace::List(vec![
NamespaceToken::Uri(ns_a),
NamespaceToken::Uri(ns_b),
]),
process_contents: ProcessContents::Strict,
not_namespace: Vec::new(),
not_qname: Vec::new(),
id: None,
annotation: None,
source: None,
};
let group = AttributeGroupData {
name: None,
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: Some(group_wc),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let group_key = schema_set.arenas.alloc_attribute_group(group);
let local = WildcardResult {
namespace: WildcardNamespace::List(vec![NamespaceToken::Uri(ns_a)]),
process_contents: ProcessContents::Strict,
not_namespace: Vec::new(),
not_qname: Vec::new(),
id: None,
annotation: None,
source: None,
};
let result =
effective_attribute_wildcard(&schema_set, Some(&local), None, &[group_key]).unwrap();
let eff = result.expect("expected Some");
match eff.namespace {
CanonicalNs::Enum(set) => {
assert_eq!(set.len(), 1);
assert!(set.contains(&Some(ns_a)));
}
other => panic!("expected Enum({{ns_a}}), got {:?}", other),
}
}
#[test]
fn test_effective_attribute_wildcard_no_local_uses_first_group_pc() {
use crate::arenas::AttributeGroupData;
let mut schema_set = SchemaSet::new();
let group_wc = WildcardResult {
namespace: WildcardNamespace::Any,
process_contents: ProcessContents::Lax,
not_namespace: Vec::new(),
not_qname: Vec::new(),
id: None,
annotation: None,
source: None,
};
let group = AttributeGroupData {
name: None,
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: Some(group_wc),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let group_key = schema_set.arenas.alloc_attribute_group(group);
let result = effective_attribute_wildcard(&schema_set, None, None, &[group_key]).unwrap();
let eff = result.expect("expected Some");
assert_eq!(eff.process_contents, ProcessContents::Lax);
assert!(matches!(eff.namespace, CanonicalNs::Any));
}
#[test]
fn test_effective_wildcard_allows_attribute_basic() {
let schema_set = SchemaSet::new();
let name = schema_set.name_table.add("foo");
let ns_a = schema_set.name_table.add("http://a");
let any_eff = EffectiveAttributeWildcard {
namespace: CanonicalNs::Any,
not_qname: Vec::new(),
process_contents: ProcessContents::Strict,
};
assert!(effective_wildcard_allows_attribute(
&schema_set,
&any_eff,
Some(ns_a),
name,
));
let mut s = std::collections::HashSet::new();
s.insert(Some(ns_a));
let enum_eff = EffectiveAttributeWildcard {
namespace: CanonicalNs::Enum(s),
not_qname: Vec::new(),
process_contents: ProcessContents::Strict,
};
assert!(effective_wildcard_allows_attribute(
&schema_set,
&enum_eff,
Some(ns_a),
name,
));
assert!(!effective_wildcard_allows_attribute(
&schema_set,
&enum_eff,
None,
name,
));
}
#[test]
fn test_effective_wildcard_restricts_enforces_subset() {
let schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let ns_b = schema_set.name_table.add("http://b");
let mut s_a = std::collections::HashSet::new();
s_a.insert(Some(ns_a));
let mut s_ab = std::collections::HashSet::new();
s_ab.insert(Some(ns_a));
s_ab.insert(Some(ns_b));
let derived_narrow = EffectiveAttributeWildcard {
namespace: CanonicalNs::Enum(s_a.clone()),
not_qname: Vec::new(),
process_contents: ProcessContents::Strict,
};
let base_wide = EffectiveAttributeWildcard {
namespace: CanonicalNs::Enum(s_ab.clone()),
not_qname: Vec::new(),
process_contents: ProcessContents::Strict,
};
assert!(
effective_attribute_wildcard_restricts(&schema_set, &derived_narrow, &base_wide)
.is_ok()
);
assert!(
effective_attribute_wildcard_restricts(&schema_set, &base_wide, &derived_narrow)
.is_err()
);
}
#[test]
fn test_effective_wildcard_restricts_enforces_process_contents() {
let schema_set = SchemaSet::new();
let skip_eff = EffectiveAttributeWildcard {
namespace: CanonicalNs::Any,
not_qname: Vec::new(),
process_contents: ProcessContents::Skip,
};
let strict_eff = EffectiveAttributeWildcard {
namespace: CanonicalNs::Any,
not_qname: Vec::new(),
process_contents: ProcessContents::Strict,
};
assert!(
effective_attribute_wildcard_restricts(&schema_set, &strict_eff, &skip_eff).is_ok()
);
assert!(
effective_attribute_wildcard_restricts(&schema_set, &skip_eff, &strict_eff).is_err()
);
}
#[test]
fn test_validate_attribute_restriction_rejects_added_wildcard() {
let mut schema_set = SchemaSet::new();
let base = create_complex_type_data(None);
let base_key = schema_set.arenas.alloc_complex_type(base);
let mut derived = create_complex_type_data(None);
derived.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::Any));
derived.derivation_method = Some(DerivationMethod::Restriction);
derived.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived);
let derived_ref = schema_set.arenas.complex_types.get(derived_key).unwrap();
let base_ref = schema_set.arenas.complex_types.get(base_key).unwrap();
let result = validate_attribute_restriction(&schema_set, derived_ref, base_ref);
assert!(result.is_err());
if let Err(SchemaError::StructuralError {
constraint,
message,
..
}) = result
{
assert_eq!(constraint, "derivation-ok-restriction");
assert!(
message.contains("wildcard"),
"message should mention wildcard, got: {}",
message
);
} else {
panic!("expected StructuralError");
}
}
#[test]
fn test_validate_attribute_restriction_accepts_narrower_wildcard() {
use crate::parser::frames::NamespaceToken;
let mut schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let mut base = create_complex_type_data(None);
base.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::Any));
let base_key = schema_set.arenas.alloc_complex_type(base);
let mut derived = create_complex_type_data(None);
derived.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::List(vec![
NamespaceToken::Uri(ns_a),
])));
derived.derivation_method = Some(DerivationMethod::Restriction);
derived.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived);
let derived_ref = schema_set.arenas.complex_types.get(derived_key).unwrap();
let base_ref = schema_set.arenas.complex_types.get(base_key).unwrap();
assert!(validate_attribute_restriction(&schema_set, derived_ref, base_ref).is_ok());
}
#[test]
fn test_validate_attribute_restriction_allows_removing_wildcard() {
let mut schema_set = SchemaSet::new();
let mut base = create_complex_type_data(None);
base.attribute_wildcard = Some(wildcard_with_ns(WildcardNamespace::Any));
let base_key = schema_set.arenas.alloc_complex_type(base);
let mut derived = create_complex_type_data(None);
derived.derivation_method = Some(DerivationMethod::Restriction);
derived.resolved_base_type = Some(TypeKey::Complex(base_key));
let derived_key = schema_set.arenas.alloc_complex_type(derived);
let derived_ref = schema_set.arenas.complex_types.get(derived_key).unwrap();
let base_ref = schema_set.arenas.complex_types.get(base_key).unwrap();
assert!(validate_attribute_restriction(&schema_set, derived_ref, base_ref).is_ok());
}
#[test]
fn test_redefine_attribute_group_rejects_broader_wildcard() {
use crate::arenas::AttributeGroupData;
use crate::parser::frames::NamespaceToken;
let mut schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let mut original_wc = wildcard_with_ns(WildcardNamespace::Any);
original_wc.not_namespace = vec![NamespaceToken::Uri(ns_a)];
let original = AttributeGroupData {
name: None,
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: Some(original_wc),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let original_key = schema_set.arenas.alloc_attribute_group(original);
let derived = AttributeGroupData {
name: None,
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: Some(wildcard_with_ns(WildcardNamespace::Any)),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: Some(original_key),
redefine_requires_restriction_check: true,
};
schema_set.arenas.alloc_attribute_group(derived);
let mut errors = Vec::new();
let mut stats = DerivationStats::default();
validate_all_redefine_attribute_group_restrictions(&schema_set, &mut errors, &mut stats);
assert!(
!errors.is_empty(),
"expected a src-redefine.7.2.2 error for broader derived wildcard"
);
let msg = match &errors[0] {
SchemaError::StructuralError {
constraint,
message,
..
} => {
assert_eq!(*constraint, "src-redefine.7.2.2");
message.clone()
}
_ => panic!("expected StructuralError"),
};
assert!(
msg.contains("wildcard") || msg.contains("restriction"),
"error should mention wildcard restriction, got: {}",
msg
);
}
#[test]
fn test_redefine_attribute_group_effective_wildcard_admits_inherited_attr() {
use crate::arenas::{AttributeGroupData, ResolvedAttributeUse};
use crate::parser::frames::{
AttributeFrameResult, AttributeUseKind as AuK, AttributeUseResult, NamespaceToken,
};
let mut schema_set = SchemaSet::new();
let ns_a = schema_set.name_table.add("http://a");
let attr_name = schema_set.name_table.add("foo");
let nested_wc = WildcardResult {
namespace: WildcardNamespace::List(vec![NamespaceToken::Uri(ns_a)]),
process_contents: ProcessContents::Strict,
not_namespace: Vec::new(),
not_qname: Vec::new(),
id: None,
annotation: None,
source: None,
};
let nested = AttributeGroupData {
name: None,
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: Some(nested_wc),
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: Vec::new(),
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let nested_key = schema_set.arenas.alloc_attribute_group(nested);
let original = AttributeGroupData {
name: None,
target_namespace: None,
ref_name: None,
attributes: Vec::new(),
attribute_groups: Vec::new(),
attribute_wildcard: None,
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: vec![nested_key],
resolved_attributes: Vec::new(),
redefine_original: None,
redefine_requires_restriction_check: false,
};
let original_key = schema_set.arenas.alloc_attribute_group(original);
let attr_use = AttributeUseResult {
attribute: AttributeFrameResult {
name: Some(attr_name),
ref_name: None,
target_namespace: Some(ns_a),
type_ref: None,
inline_type: None,
default_value: None,
fixed_value: None,
use_kind: None,
form: None,
inheritable: false,
id: None,
annotation: None,
source: None,
},
use_kind: AuK::Optional,
};
let derived = AttributeGroupData {
name: None,
target_namespace: None,
ref_name: None,
attributes: vec![attr_use],
attribute_groups: Vec::new(),
attribute_wildcard: None,
id: None,
annotation: None,
source: None,
resolved_ref: None,
resolved_attribute_groups: vec![nested_key],
resolved_attributes: vec![ResolvedAttributeUse {
resolved_type: None,
resolved_ref: None,
}],
redefine_original: Some(original_key),
redefine_requires_restriction_check: true,
};
schema_set.arenas.alloc_attribute_group(derived);
let mut errors = Vec::new();
let mut stats = DerivationStats::default();
validate_all_redefine_attribute_group_restrictions(&schema_set, &mut errors, &mut stats);
assert!(
errors.is_empty(),
"attribute admitted by inherited effective wildcard should not error; got: {:?}",
errors
.iter()
.map(|e| format!("{:?}", e))
.collect::<Vec<_>>()
);
}
}