use std::sync::Arc;
use fastxml::schema::{Schema, Validator};
const LAX_SCHEMA: &[u8] = br#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="doc">
<xs:complexType>
<xs:sequence>
<xs:any minOccurs="2" maxOccurs="3" processContents="lax"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>"#;
const SKIP_SCHEMA: &[u8] = br#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="doc">
<xs:complexType>
<xs:sequence>
<xs:any processContents="skip"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="known" type="xs:int"/>
</xs:schema>"#;
fn validate_both(schema: &[u8], xml: &str) -> (bool, bool) {
let schema = Arc::new(Schema::from_xsd(schema).expect("schema"));
let doc = fastxml::Parser::from(xml).parse().expect("parse");
let dom_valid = Validator::from(&doc)
.schema(Arc::clone(&schema))
.run()
.expect("dom validate")
.into_entries()
.is_empty();
let stream_valid = Validator::from(xml.as_bytes())
.schema(schema)
.run()
.expect("stream validate")
.into_entries()
.is_empty();
(dom_valid, stream_valid)
}
#[test]
fn lax_wildcard_admits_undeclared_elements() {
let (dom, stream) = validate_both(LAX_SCHEMA, "<doc><a/><b/></doc>");
assert!(dom, "DOM should accept undeclared children under lax");
assert!(
stream,
"streaming should accept undeclared children under lax"
);
}
#[test]
fn wildcard_min_occurs_enforced() {
let (dom, stream) = validate_both(LAX_SCHEMA, "<doc><a/></doc>");
assert!(!dom, "DOM should reject too few wildcard children");
assert!(!stream, "streaming should reject too few wildcard children");
}
#[test]
fn wildcard_max_occurs_enforced() {
let (dom, stream) = validate_both(LAX_SCHEMA, "<doc><a/><b/><c/><d/></doc>");
assert!(!dom, "DOM should reject too many wildcard children");
assert!(
!stream,
"streaming should reject too many wildcard children"
);
}
#[test]
fn skip_wildcard_skips_subtree_even_when_declared() {
let (dom, stream) = validate_both(SKIP_SCHEMA, "<doc><known>not-an-int</known></doc>");
assert!(dom, "DOM must not validate inside a skip wildcard");
assert!(stream, "streaming must not validate inside a skip wildcard");
}
#[test]
fn lax_wildcard_validates_declared_elements() {
const LAX_ONE: &[u8] = br#"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="doc">
<xs:complexType>
<xs:sequence>
<xs:any processContents="lax"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="known" type="xs:int"/>
</xs:schema>"#;
let (dom, stream) = validate_both(LAX_ONE, "<doc><known>not-an-int</known></doc>");
assert!(!dom, "DOM must validate declared elements under lax");
assert!(
!stream,
"streaming must validate declared elements under lax"
);
}
#[test]
fn wildcard_same_local_name_cross_namespace_is_deterministically_valid() {
const NS_SCHEMA: &[u8] = br#"<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://foobar">
<xsd:element name="foo">
<xsd:complexType>
<xsd:sequence>
<xsd:any namespace="A B C D E ##targetNamespace ##local" maxOccurs="unbounded" processContents="lax"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="bar" type="xsd:string"/>
</xsd:schema>"#;
const NO_NS_SCHEMA: &[u8] = br#"<?xml version="1.0"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="foo"/>
<xsd:element name="bar" type="xsd:string"/>
</xsd:schema>"#;
const INSTANCE: &str = r#"<?xml version="1.0"?>
<a:foo xmlns:a="http://foobar">
<a:bar>foo bar</a:bar>
<bar>foo bar</bar>
<foo/>
<A:foo xmlns:A="A"/>
<A:foo xmlns:A="B"/>
<foo xmlns="C"/>
<D:foo xmlns:D="D"/>
<e:foo xmlns:e="E"/>
</a:foo>"#;
for _ in 0..8 {
let schema = Arc::new(
Schema::builder()
.add("ns.xsd", NS_SCHEMA)
.add("nons.xsd", NO_NS_SCHEMA)
.resolve()
.expect("resolve combined schema"),
);
let doc = fastxml::Parser::from(INSTANCE).parse().expect("parse");
let dom = Validator::from(&doc)
.schema(Arc::clone(&schema))
.run()
.expect("dom validate")
.into_entries();
let stream = Validator::from(INSTANCE.as_bytes())
.schema(Arc::clone(&schema))
.run()
.expect("stream validate")
.into_entries();
assert!(dom.is_empty(), "DOM should be valid, got: {dom:?}");
assert!(
stream.is_empty(),
"streaming should be valid, got: {stream:?}"
);
}
}
#[test]
fn streaming_wildcard_namespace_set_enforced() {
const SCHEMA: &[u8] = br###"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:x="http://example.com/t" targetNamespace="http://example.com/t"
elementFormDefault="qualified">
<xs:element name="doc">
<xs:complexType>
<xs:sequence>
<xs:element name="elem" type="x:anyLocal" minOccurs="0" maxOccurs="100"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="anyLocal">
<xs:choice>
<xs:any processContents="lax" namespace="##local"/>
</xs:choice>
</xs:complexType>
</xs:schema>"###;
let schema = std::sync::Arc::new(Schema::from_xsd(SCHEMA).expect("schema"));
let run = |xml: &str| {
let doc = fastxml::Parser::from(xml).parse().expect("parse");
let dom = Validator::from(&doc)
.schema(std::sync::Arc::clone(&schema))
.run()
.expect("dom")
.into_entries()
.is_empty();
let stream = Validator::from(xml.as_bytes())
.schema(std::sync::Arc::clone(&schema))
.run()
.expect("stream")
.into_entries()
.is_empty();
(dom, stream)
};
let (dom, stream) =
run(r#"<x:doc xmlns:x="http://example.com/t"><x:elem><anything/></x:elem></x:doc>"#);
assert!(dom, "DOM should accept a no-namespace child under ##local");
assert!(
stream,
"streaming should accept a no-namespace child under ##local"
);
let (dom, stream) = run(
r#"<x:doc xmlns:x="http://example.com/t"><x:elem><f:foo xmlns:f="urn:other"/></x:elem></x:doc>"#,
);
assert!(!dom, "DOM should reject a namespaced child under ##local");
assert!(
!stream,
"streaming should reject a namespaced child under ##local"
);
let (dom, stream) = run(
r#"<x:doc xmlns:x="http://example.com/t"><x:elem><foo xmlns="urn:other"/></x:elem></x:doc>"#,
);
assert!(
!dom,
"DOM should reject a default-namespaced child under ##local"
);
assert!(
!stream,
"streaming should reject a default-namespaced child under ##local"
);
}
#[test]
fn streaming_wildcard_occurrence_counts_only_matching_children() {
const SCHEMA: &[u8] = br###"<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.com/w" xmlns="http://example.com/w">
<xs:element name="foo">
<xs:complexType>
<xs:sequence>
<xs:any namespace="##other" processContents="lax"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="b" type="xs:string"/>
</xs:schema>"###;
let schema = std::sync::Arc::new(Schema::from_xsd(SCHEMA).expect("schema"));
let run = |xml: &str| {
let doc = fastxml::Parser::from(xml).parse().expect("parse");
let dom = Validator::from(&doc)
.schema(std::sync::Arc::clone(&schema))
.run()
.expect("dom")
.into_entries()
.is_empty();
let stream = Validator::from(xml.as_bytes())
.schema(std::sync::Arc::clone(&schema))
.run()
.expect("stream")
.into_entries()
.is_empty();
(dom, stream)
};
let (dom, stream) = run(r#"<foo xmlns="http://example.com/w"><b>text</b></foo>"#);
assert!(
!dom,
"DOM must reject: no child matches the ##other wildcard"
);
assert!(
!stream,
"streaming must reject: no child matches the ##other wildcard"
);
let (dom, stream) =
run(r#"<foo xmlns="http://example.com/w"><o:x xmlns:o="urn:other"/></foo>"#);
assert!(dom, "DOM should accept an ##other-matching child");
assert!(stream, "streaming should accept an ##other-matching child");
}