#![cfg(feature = "xsd11")]
use crate::error::{SchemaError, SchemaResult};
use crate::ids::TypeKey;
use crate::parser::frames::AlternativeResult;
use crate::schema::model::DerivationSet;
use crate::schema::SchemaSet;
use crate::xpath::api::XPathExpr;
use crate::xpath::ast::{AstNode, ItemTypeNode, TypeExprNode};
use crate::xpath::error::XPathError;
use crate::xpath::XPathContext;
pub fn validate_cta_xpath(schema_set: &SchemaSet) -> SchemaResult<()> {
if !schema_set.is_xsd11() {
return Ok(());
}
for (_key, elem) in schema_set.arenas.elements.iter() {
for alt in &elem.alternatives {
let Some(test) = alt.test.as_deref() else {
continue;
};
let ctx = XPathContext::new(&schema_set.name_table)
.with_namespaces(alt.ns_snapshot.clone())
.with_schema_set(schema_set);
let ctx = if let Some(default_ns) = resolve_alt_default_ns(alt, schema_set) {
ctx.with_default_element_ns(default_ns)
} else {
ctx
};
let expr = match XPathExpr::compile(test, &ctx) {
Ok(e) => e,
Err(err) => {
if is_schema_invalidating_error(&err) {
let location = schema_set.locate(alt.source.as_ref());
let elem_name = elem
.name
.map(|n| schema_set.name_table.resolve_ref(n))
.unwrap_or("(anonymous)");
return Err(SchemaError::structural(
"src-ct-alternative",
format!(
"Element '{}' <xs:alternative test=\"{}\"> failed CTA \
static analysis: {}",
elem_name, test, err
),
location,
));
}
continue;
}
};
check_no_user_defined_types(&expr, alt, elem, test, schema_set)?;
}
}
Ok(())
}
fn is_schema_invalidating_error(err: &XPathError) -> bool {
matches!(
err,
XPathError::XPST0003 { .. }
| XPathError::XPST0008 { .. }
| XPathError::XPST0051 { .. }
| XPathError::XPST0081 { .. }
)
}
fn resolve_alt_default_ns(
alt: &AlternativeResult,
schema_set: &SchemaSet,
) -> Option<crate::ids::NameId> {
let raw = alt.xpath_default_namespace.as_deref()?;
match raw {
"##defaultNamespace" => alt.ns_snapshot.default_ns,
"##targetNamespace" => None, "##local" => None,
uri => Some(schema_set.name_table.add(uri)),
}
}
fn check_no_user_defined_types(
expr: &XPathExpr,
alt: &AlternativeResult,
elem: &crate::arenas::ElementDeclData,
test: &str,
schema_set: &SchemaSet,
) -> SchemaResult<()> {
let arena = expr.arena();
let xs_ns = schema_set
.name_table
.add("http://www.w3.org/2001/XMLSchema");
for (_id, node) in arena.iter() {
let AstNode::TypeExpr(type_expr) = node else {
continue;
};
let Some(ItemTypeNode::Atomic(_)) = type_expr.target_type.item_type else {
continue;
};
let Some(qn) = type_expr.resolved_atomic_type.as_ref() else {
continue;
};
if qn.namespace_uri != Some(xs_ns) {
return Err(report_user_defined_type(
alt, elem, type_expr, test, schema_set,
));
}
if let Some(local) = schema_set.name_table.try_resolve_ref(qn.local_name) {
if crate::types::XmlTypeCode::from_local_name(local).is_none() {
return Err(report_user_defined_type(
alt, elem, type_expr, test, schema_set,
));
}
}
}
Ok(())
}
pub fn validate_cta_substitutability(schema_set: &SchemaSet) -> SchemaResult<()> {
if !schema_set.is_xsd11() {
return Ok(());
}
for (_key, elem) in schema_set.arenas.elements.iter() {
let Some(declared) = elem.resolved_type else {
continue;
};
if elem.alternatives.is_empty() {
continue;
}
if !type_is_named(schema_set, declared) {
continue;
}
for alt in &elem.alternatives {
let Some(alt_type) = alt.resolved_type else {
continue;
};
if is_xs_error(schema_set, alt_type) {
continue;
}
let blocked = elem.block;
if !is_substitutable(schema_set, alt_type, declared, blocked) {
let elem_name = elem
.name
.map(|n| schema_set.name_table.resolve_ref(n))
.unwrap_or("(anonymous)");
let location = schema_set.locate(alt.source.as_ref());
return Err(SchemaError::structural(
"cos-ct-alternative-substitutable",
format!(
"Element '{}': type alternative is not validly substitutable \
for the element's declared type — the alternative type does \
not derive from the declared type by a non-blocked method \
(§3.12.6)",
elem_name
),
location,
));
}
}
}
Ok(())
}
fn type_is_named(schema_set: &SchemaSet, type_key: TypeKey) -> bool {
match type_key {
TypeKey::Simple(sk) => schema_set
.arenas
.simple_types
.get(sk)
.and_then(|t| t.name)
.is_some(),
TypeKey::Complex(ck) => schema_set
.arenas
.complex_types
.get(ck)
.and_then(|t| t.name)
.is_some(),
}
}
fn is_substitutable(
schema_set: &SchemaSet,
alt_type: TypeKey,
declared: TypeKey,
blocked: DerivationSet,
) -> bool {
schema_set.is_type_derived_from(alt_type, declared, blocked)
}
fn is_xs_error(schema_set: &SchemaSet, type_key: TypeKey) -> bool {
let TypeKey::Simple(key) = type_key else {
return false;
};
let Some(error_key) = schema_set
.builtin_types()
.get_by_type_code(crate::types::XmlTypeCode::Error)
else {
return false;
};
key == error_key
}
fn report_user_defined_type(
alt: &AlternativeResult,
elem: &crate::arenas::ElementDeclData,
_type_expr: &TypeExprNode,
test: &str,
schema_set: &SchemaSet,
) -> SchemaError {
let location = schema_set.locate(alt.source.as_ref());
let elem_name = elem
.name
.map(|n| schema_set.name_table.resolve_ref(n))
.unwrap_or("(anonymous)");
SchemaError::structural(
"src-ct-alternative",
format!(
"Element '{}' <xs:alternative test=\"{}\"> references a user-defined \
type in a type expression; the CTA static context only exposes \
built-in primitive types from the XML Schema namespace (§3.12.4)",
elem_name, test
),
location,
)
}