use crate::document::buffer::BufferDocument;
use crate::document::navigator::BufferDocNavigator;
use crate::ids::{ComplexTypeKey, NameId, TypeKey};
use crate::navigator::{DomNavigator, TypedValue};
use crate::parser::frames::{AssertResult, ComplexContentResult};
use crate::parser::location::SourceLocation;
use crate::schema::SchemaSet;
use crate::validation::errors::{self, ValidationError};
use crate::validation::simple::validate_simple_type;
use crate::xpath::api::XPathExpr;
use crate::xpath::functions::{effective_boolean_value, XPathValue};
use crate::xpath::XPathContext;
use crate::arenas::SchemaArenas;
pub(crate) struct AssertionBufferFrame {
pub element_ref: u32,
pub complex_type_key: ComplexTypeKey,
pub element_path: String,
pub location: Option<SourceLocation>,
}
pub(crate) fn has_inherited_assertions(ct_key: ComplexTypeKey, arenas: &SchemaArenas) -> bool {
let ct = &arenas.complex_types[ct_key];
if !ct.assertions.is_empty() {
return true;
}
let mut current = ct.resolved_base_type;
while let Some(TypeKey::Complex(base_key)) = current {
let base = &arenas.complex_types[base_key];
if !base.assertions.is_empty() {
return true;
}
current = base.resolved_base_type;
}
false
}
pub(crate) fn collect_inherited_assertions(
ct_key: ComplexTypeKey,
arenas: &SchemaArenas,
) -> Vec<(&AssertResult, ComplexTypeKey)> {
let mut chain = vec![ct_key];
let mut current = arenas.complex_types[ct_key].resolved_base_type;
while let Some(TypeKey::Complex(base_key)) = current {
chain.push(base_key);
current = arenas.complex_types[base_key].resolved_base_type;
}
let mut result = Vec::new();
for &key in chain.iter().rev() {
let ct = &arenas.complex_types[key];
for assertion in &ct.assertions {
result.push((assertion, key));
}
}
result
}
fn resolve_ct_assertion_default_ns(
assertion: &AssertResult,
owner_ct_key: ComplexTypeKey,
schema_set: &SchemaSet,
) -> Option<NameId> {
let ct = &schema_set.arenas.complex_types[owner_ct_key];
let doc = ct
.source
.as_ref()
.and_then(|s| schema_set.documents.get(s.doc_id as usize));
let effective = if let Some(raw) = &assertion.xpath_default_namespace {
Some(raw.clone())
} else if let Some(raw) = &ct.xpath_default_namespace {
Some(raw.clone())
} else {
doc.and_then(|d| d.xpath_default_namespace)
.map(|id| schema_set.name_table.resolve(id))
};
match effective.as_deref() {
Some("##defaultNamespace") => assertion.ns_snapshot.default_ns,
Some("##targetNamespace") => doc.and_then(|d| d.target_namespace),
Some("##local") | None => None,
Some(uri) => Some(schema_set.name_table.add(uri)),
}
}
fn compute_dollar_value<'doc>(
doc: &'doc BufferDocument<'doc>,
element_ref: u32,
governing_ct_key: ComplexTypeKey,
schema_set: &SchemaSet,
) -> XPathValue<BufferDocNavigator<'doc>> {
use crate::types::value::{XmlValue, XmlValueKind};
use crate::xpath::iterator::XmlItem;
let ct = &schema_set.arenas.complex_types[governing_ct_key];
if !matches!(ct.content, ComplexContentResult::Simple(_)) {
return XPathValue::empty();
}
let nav = BufferDocNavigator::new(doc, element_ref);
if matches!(nav.typed_value(), TypedValue::Nilled) {
return XPathValue::empty();
}
match validate_simple_type(&nav.value(), TypeKey::Complex(governing_ct_key), schema_set) {
Ok(result) => {
if let XmlValueKind::List { item_type, items } = &result.typed_value.value {
let item_type_code = *item_type;
let xpath_items: Vec<XmlItem<BufferDocNavigator<'doc>>> = items
.iter()
.cloned()
.map(|atom| {
XmlItem::Atomic(XmlValue::new(item_type_code, XmlValueKind::Atomic(atom)))
})
.collect();
return XPathValue::from_sequence(xpath_items);
}
XPathValue::from_atomic(result.typed_value)
}
Err(_) => XPathValue::empty(),
}
}
pub(crate) fn evaluate_complex_type_assertions(
doc: &BufferDocument<'_>,
element_ref: u32,
ct_key: ComplexTypeKey,
schema_set: &SchemaSet,
) -> Vec<ValidationError> {
let assertions = collect_inherited_assertions(ct_key, &schema_set.arenas);
let mut errors = Vec::new();
let dollar_value = compute_dollar_value(doc, element_ref, ct_key, schema_set);
for (assertion, owner_key) in assertions {
if assertion.test.is_empty() {
continue;
}
let ctx = XPathContext::new(&schema_set.name_table)
.with_namespaces(assertion.ns_snapshot.clone())
.with_schema_set(schema_set);
let ctx = if let Some(default_ns) =
resolve_ct_assertion_default_ns(assertion, owner_key, schema_set)
{
ctx.with_default_element_ns(default_ns)
} else {
ctx
};
let expr = match XPathExpr::compile_with_vars(&assertion.test, &ctx, &["value"]) {
Ok(e) => e,
Err(e) => {
errors.push(errors::error(
"cvc-assertion",
format!(
"Failed to compile assertion test '{}': {}",
assertion.test, e
),
None,
));
continue;
}
};
let nav = BufferDocNavigator::new_assertion(doc, element_ref);
let value_for_eval = dollar_value.clone();
let result = match expr
.evaluator(&ctx)
.run_with_node_and_setup(Some(nav), |eval| {
eval.set_variable_by_name("value", value_for_eval)
.expect("$value declared via compile_with_vars");
}) {
Ok(r) => r,
Err(e) => {
errors.push(errors::error(
"cvc-assertion",
format!(
"Failed to evaluate assertion test '{}': {}",
assertion.test, e
),
None,
));
continue;
}
};
match effective_boolean_value(&result) {
Ok(true) => { }
Ok(false) => {
errors.push(errors::error(
"cvc-assertion",
format!("Assertion '{}' failed", assertion.test),
None,
));
}
Err(e) => {
errors.push(errors::error(
"cvc-assertion",
format!(
"Failed to compute boolean value for assertion '{}': {}",
assertion.test, e
),
None,
));
}
}
}
errors
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pipeline::load_and_process_schema;
fn load_schema(xsd: &str) -> SchemaSet {
let mut schema_set = SchemaSet::xsd11();
load_and_process_schema(xsd.as_bytes(), "test.xsd", &mut schema_set, None)
.expect("failed to load schema");
schema_set
}
fn find_ct_key(schema_set: &SchemaSet, name: &str) -> ComplexTypeKey {
let name_id = schema_set.name_table.add(name);
for (key, ct) in &schema_set.arenas.complex_types {
if ct.name == Some(name_id) {
return key;
}
}
panic!("Complex type '{}' not found", name);
}
#[test]
fn test_has_inherited_assertions_none() {
let schema_set = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="plain">
<xs:sequence>
<xs:element name="x" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>"#,
);
let key = find_ct_key(&schema_set, "plain");
assert!(!has_inherited_assertions(key, &schema_set.arenas));
}
#[test]
fn test_has_inherited_assertions_own() {
let schema_set = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="withAssert">
<xs:attribute name="val" type="xs:integer"/>
<xs:assert test="@val >= 0"/>
</xs:complexType>
</xs:schema>"#,
);
let key = find_ct_key(&schema_set, "withAssert");
assert!(has_inherited_assertions(key, &schema_set.arenas));
}
#[test]
fn test_has_inherited_assertions_from_base() {
let schema_set = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="base">
<xs:attribute name="val" type="xs:integer"/>
<xs:assert test="@val >= 0"/>
</xs:complexType>
<xs:complexType name="derived">
<xs:complexContent>
<xs:restriction base="base">
<xs:attribute name="val" type="xs:integer"/>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
</xs:schema>"#,
);
let key = find_ct_key(&schema_set, "derived");
assert!(has_inherited_assertions(key, &schema_set.arenas));
}
#[test]
fn test_collect_inherited_assertions_ordering() {
let schema_set = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="base">
<xs:attribute name="val" type="xs:integer"/>
<xs:assert test="@val >= 0"/>
</xs:complexType>
<xs:complexType name="derived">
<xs:complexContent>
<xs:restriction base="base">
<xs:attribute name="val" type="xs:integer"/>
<xs:assert test="@val < 100"/>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
</xs:schema>"#,
);
let derived_key = find_ct_key(&schema_set, "derived");
let base_key = find_ct_key(&schema_set, "base");
let assertions = collect_inherited_assertions(derived_key, &schema_set.arenas);
assert_eq!(assertions.len(), 2);
assert_eq!(
assertions[0].1, base_key,
"first assertion should be from base"
);
assert_eq!(
assertions[1].1, derived_key,
"second assertion should be from derived"
);
assert!(assertions[0].0.test.contains(">= 0"));
assert!(assertions[1].0.test.contains("< 100"));
}
#[test]
fn test_collect_inherited_assertions_no_assertions() {
let schema_set = load_schema(
r#"<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="plain">
<xs:sequence>
<xs:element name="x" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>"#,
);
let key = find_ct_key(&schema_set, "plain");
let assertions = collect_inherited_assertions(key, &schema_set.arenas);
assert!(assertions.is_empty());
}
}