use std::io::BufRead;
use quick_xml::{
events::{BytesStart, Event},
Reader,
};
use super::types::{
Attribute, ChoiceVariant, ComplexContent, ComplexType, Element, Facet, MaxOccurs, Restriction,
Schema, SequenceElement, SimpleType,
};
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("xml error: {0}")]
Xml(#[from] quick_xml::Error),
#[error("missing required attribute '{attr}' on <{element}>")]
MissingAttribute {
element: &'static str,
attr: &'static str,
},
#[error("invalid value '{value}' for attribute '{attr}': {reason}")]
InvalidAttributeValue {
value: String,
attr: &'static str,
reason: &'static str,
},
#[error("missing <xs:schema> root element")]
MissingSchemaRoot,
#[error("utf-8 error in attribute value: {0}")]
Utf8(#[from] std::str::Utf8Error),
}
#[derive(Debug)]
enum Context {
Schema,
SimpleType(SimpleTypeBuilder),
ComplexType(ComplexTypeBuilder),
}
#[derive(Debug, Default)]
struct SimpleTypeBuilder {
name: String,
base: String,
facets: Vec<Facet>,
nesting_depth: u32,
}
#[derive(Debug, Default)]
struct ComplexTypeBuilder {
name: String,
content: ComplexContentBuilder,
nesting_depth: u32,
}
#[derive(Debug, Default)]
enum ComplexContentBuilder {
#[default]
Empty,
Sequence(Vec<SequenceElement>),
Choice(Vec<ChoiceVariant>),
SimpleContent {
base: String,
attributes: Vec<Attribute>,
in_extension: bool,
},
Any {
namespace: Option<String>,
},
}
pub fn parse<R: BufRead>(reader: R) -> Result<Schema, ParseError> {
parse_internal(Reader::from_reader(reader))
}
pub fn parse_str(xml: &str) -> Result<Schema, ParseError> {
parse_internal(Reader::from_str(xml))
}
fn parse_internal<R: BufRead>(mut reader: Reader<R>) -> Result<Schema, ParseError> {
reader.config_mut().trim_text(true);
let mut schema = Schema {
target_namespace: String::new(),
elements: Vec::new(),
simple_types: Vec::new(),
complex_types: Vec::new(),
};
let mut buf = Vec::new();
let mut ctx_stack: Vec<Context> = Vec::new();
let mut found_schema_root = false;
loop {
match reader.read_event_into(&mut buf)? {
Event::Start(ref e) => {
on_start(e, &mut ctx_stack, &mut schema, &mut found_schema_root)?;
}
Event::Empty(ref e) => {
on_empty(e, &mut ctx_stack, &mut schema, &mut found_schema_root)?;
}
Event::End(_) => {
on_end(&mut ctx_stack, &mut schema);
}
Event::Eof => break,
_ => {}
}
buf.clear();
}
if !found_schema_root {
return Err(ParseError::MissingSchemaRoot);
}
Ok(schema)
}
fn on_start(
e: &BytesStart<'_>,
ctx_stack: &mut Vec<Context>,
schema: &mut Schema,
found_schema_root: &mut bool,
) -> Result<(), ParseError> {
let name_binding = e.name();
let local = local_name(name_binding.as_ref());
match ctx_stack.last_mut() {
None => {
if local == "schema" {
*found_schema_root = true;
schema.target_namespace = attr_value(e, "targetNamespace")?.unwrap_or_default();
ctx_stack.push(Context::Schema);
}
}
Some(Context::Schema) => match local {
"simpleType" => {
let name = require_attr(e, "name", "xs:simpleType")?;
ctx_stack.push(Context::SimpleType(SimpleTypeBuilder {
name,
..Default::default()
}));
}
"complexType" => {
let name = require_attr(e, "name", "xs:complexType")?;
ctx_stack.push(Context::ComplexType(ComplexTypeBuilder {
name,
..Default::default()
}));
}
"element" => {
on_top_level_element(e, schema)?;
}
_ => {
}
},
Some(Context::SimpleType(ref mut b)) => {
b.nesting_depth += 1;
if b.nesting_depth == 1 && local == "restriction" {
b.base = attr_value(e, "base")?.unwrap_or_default();
}
}
Some(Context::ComplexType(ref mut b)) => {
b.nesting_depth += 1;
match (b.nesting_depth, local) {
(1, "sequence") => {
b.content = ComplexContentBuilder::Sequence(Vec::new());
}
(1, "choice") => {
b.content = ComplexContentBuilder::Choice(Vec::new());
}
(1, "simpleContent") => {
b.content = ComplexContentBuilder::SimpleContent {
base: String::new(),
attributes: Vec::new(),
in_extension: false,
};
}
(2, "extension") => {
if let ComplexContentBuilder::SimpleContent {
ref mut base,
ref mut in_extension,
..
} = b.content
{
*base = attr_value(e, "base")?.unwrap_or_default();
*in_extension = true;
}
}
(2, "element") => {
on_element_inside_complex(e, b)?;
}
_ => {}
}
}
}
Ok(())
}
fn on_empty(
e: &BytesStart<'_>,
ctx_stack: &mut [Context],
schema: &mut Schema,
found_schema_root: &mut bool,
) -> Result<(), ParseError> {
let name_binding = e.name();
let local = local_name(name_binding.as_ref());
match ctx_stack.last_mut() {
None => {
if local == "schema" {
*found_schema_root = true;
schema.target_namespace = attr_value(e, "targetNamespace")?.unwrap_or_default();
}
}
Some(Context::Schema) => {
if local == "element" {
on_top_level_element(e, schema)?;
}
}
Some(Context::SimpleType(ref mut b)) => {
match local {
"restriction" => {
b.base = attr_value(e, "base")?.unwrap_or_default();
}
"enumeration" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets.push(Facet::Enumeration(v));
}
"pattern" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets.push(Facet::Pattern(v));
}
"minLength" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets.push(Facet::MinLength(parse_u64(&v, "minLength")?));
}
"maxLength" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets.push(Facet::MaxLength(parse_u64(&v, "maxLength")?));
}
"minInclusive" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets.push(Facet::MinInclusive(v));
}
"maxInclusive" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets.push(Facet::MaxInclusive(v));
}
"totalDigits" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets
.push(Facet::TotalDigits(parse_u32(&v, "totalDigits")?));
}
"fractionDigits" => {
let v = attr_value(e, "value")?.unwrap_or_default();
b.facets
.push(Facet::FractionDigits(parse_u32(&v, "fractionDigits")?));
}
_ => {}
}
}
Some(Context::ComplexType(ref mut b)) => match local {
"element" => {
on_element_inside_complex(e, b)?;
}
"any" => {
let namespace = attr_value(e, "namespace")?;
b.content = ComplexContentBuilder::Any { namespace };
}
"attribute" => {
on_attribute_inside_complex(e, b)?;
}
_ => {}
},
}
Ok(())
}
fn on_end(ctx_stack: &mut Vec<Context>, schema: &mut Schema) {
match ctx_stack.last_mut() {
Some(Context::SimpleType(ref mut b)) => {
if b.nesting_depth > 0 {
b.nesting_depth -= 1;
return;
}
}
Some(Context::ComplexType(ref mut b)) => {
if b.nesting_depth > 0 {
b.nesting_depth -= 1;
return;
}
}
Some(Context::Schema) => {
ctx_stack.pop();
return;
}
None => return,
}
let ctx = ctx_stack.pop().expect("checked above");
match ctx {
Context::SimpleType(b) => {
schema.simple_types.push(SimpleType {
name: b.name,
restriction: Restriction {
base: b.base,
facets: b.facets,
},
});
}
Context::ComplexType(b) => {
let content = finish_complex_content(b.content);
schema.complex_types.push(ComplexType {
name: b.name,
content,
});
}
Context::Schema => unreachable!("handled above"),
}
}
fn on_top_level_element(e: &BytesStart<'_>, schema: &mut Schema) -> Result<(), ParseError> {
if let (Some(name), Some(type_name)) = (attr_value(e, "name")?, attr_value(e, "type")?) {
schema.elements.push(Element { name, type_name });
}
Ok(())
}
fn on_element_inside_complex(
e: &BytesStart<'_>,
b: &mut ComplexTypeBuilder,
) -> Result<(), ParseError> {
match b.content {
ComplexContentBuilder::Sequence(ref mut seq) => {
if let (Some(name), Some(type_name)) = (attr_value(e, "name")?, attr_value(e, "type")?)
{
let min_occurs = parse_min_occurs(e)?;
let max_occurs = parse_max_occurs(e)?;
seq.push(SequenceElement {
name,
type_name,
min_occurs,
max_occurs,
});
}
}
ComplexContentBuilder::Choice(ref mut variants) => {
if let (Some(name), Some(type_name)) = (attr_value(e, "name")?, attr_value(e, "type")?)
{
variants.push(ChoiceVariant { name, type_name });
}
}
_ => {}
}
Ok(())
}
fn on_attribute_inside_complex(
e: &BytesStart<'_>,
b: &mut ComplexTypeBuilder,
) -> Result<(), ParseError> {
if let ComplexContentBuilder::SimpleContent {
ref mut attributes,
in_extension,
..
} = b.content
{
if in_extension {
if let (Some(name), Some(type_name)) = (attr_value(e, "name")?, attr_value(e, "type")?)
{
let required = attr_value(e, "use")?.is_some_and(|v| v == "required");
attributes.push(Attribute {
name,
type_name,
required,
});
}
}
}
Ok(())
}
fn attr_value(e: &BytesStart<'_>, name: &str) -> Result<Option<String>, ParseError> {
for attr in e.attributes().flatten() {
if local_name(attr.key.as_ref()) == name {
let val = std::str::from_utf8(attr.value.as_ref())?.to_owned();
return Ok(Some(val));
}
}
Ok(None)
}
fn require_attr(
e: &BytesStart<'_>,
attr: &'static str,
element: &'static str,
) -> Result<String, ParseError> {
attr_value(e, attr)?.ok_or(ParseError::MissingAttribute { element, attr })
}
fn parse_min_occurs(e: &BytesStart<'_>) -> Result<u32, ParseError> {
match attr_value(e, "minOccurs")? {
None => Ok(1),
Some(v) => parse_u32(&v, "minOccurs"),
}
}
fn parse_max_occurs(e: &BytesStart<'_>) -> Result<MaxOccurs, ParseError> {
match attr_value(e, "maxOccurs")? {
None => Ok(MaxOccurs::Bounded(1)),
Some(ref v) if v == "unbounded" => Ok(MaxOccurs::Unbounded),
Some(v) => parse_u32(&v, "maxOccurs").map(MaxOccurs::Bounded),
}
}
fn parse_u32(s: &str, attr: &'static str) -> Result<u32, ParseError> {
s.parse::<u32>()
.map_err(|_| ParseError::InvalidAttributeValue {
value: s.to_owned(),
attr,
reason: "expected non-negative integer",
})
}
fn parse_u64(s: &str, attr: &'static str) -> Result<u64, ParseError> {
s.parse::<u64>()
.map_err(|_| ParseError::InvalidAttributeValue {
value: s.to_owned(),
attr,
reason: "expected non-negative integer",
})
}
fn local_name(name: &[u8]) -> &str {
let s = std::str::from_utf8(name).unwrap_or("");
s.rfind(':').map_or(s, |pos| &s[pos + 1..])
}
fn finish_complex_content(b: ComplexContentBuilder) -> ComplexContent {
match b {
ComplexContentBuilder::Sequence(seq) => ComplexContent::Sequence(seq),
ComplexContentBuilder::Choice(ch) => ComplexContent::Choice(ch),
ComplexContentBuilder::SimpleContent {
base, attributes, ..
} => ComplexContent::SimpleContent { base, attributes },
ComplexContentBuilder::Any { namespace } => ComplexContent::Any { namespace },
ComplexContentBuilder::Empty => ComplexContent::Any { namespace: None },
}
}
#[cfg(test)]
mod tests {
use super::*;
fn wrap(body: &str) -> String {
format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:test"
elementFormDefault="qualified">
{body}
</xs:schema>"#
)
}
#[test]
fn empty_schema_parses() {
let xml = wrap("");
let schema = parse_str(&xml).unwrap();
assert_eq!(schema.target_namespace, "urn:test");
assert!(schema.elements.is_empty());
assert!(schema.simple_types.is_empty());
assert!(schema.complex_types.is_empty());
}
#[test]
fn missing_schema_root_errors() {
let err = parse_str("<root/>").unwrap_err();
assert!(matches!(err, ParseError::MissingSchemaRoot));
}
#[test]
fn top_level_element() {
let xml = wrap(r#"<xs:element name="Document" type="Document"/>"#);
let schema = parse_str(&xml).unwrap();
assert_eq!(schema.elements.len(), 1);
let el = &schema.elements[0];
assert_eq!(el.name, "Document");
assert_eq!(el.type_name, "Document");
}
#[test]
fn multiple_top_level_elements() {
let xml = wrap(
r#"
<xs:element name="AppHdr" type="BusinessApplicationHeaderV04"/>
<xs:element name="Document" type="Document"/>
"#,
);
let schema = parse_str(&xml).unwrap();
assert_eq!(schema.elements.len(), 2);
assert_eq!(schema.elements[0].name, "AppHdr");
assert_eq!(schema.elements[1].name, "Document");
}
#[test]
fn simple_type_string_min_max_length() {
let xml = wrap(
r#"
<xs:simpleType name="Max35Text">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="35"/>
</xs:restriction>
</xs:simpleType>
"#,
);
let schema = parse_str(&xml).unwrap();
assert_eq!(schema.simple_types.len(), 1);
let st = &schema.simple_types[0];
assert_eq!(st.name, "Max35Text");
assert_eq!(st.restriction.base, "xs:string");
assert_eq!(
st.restriction.facets,
vec![Facet::MinLength(1), Facet::MaxLength(35)]
);
}
#[test]
fn simple_type_enumeration() {
let xml = wrap(
r#"
<xs:simpleType name="AddressType2Code">
<xs:restriction base="xs:string">
<xs:enumeration value="ADDR"/>
<xs:enumeration value="PBOX"/>
<xs:enumeration value="HOME"/>
</xs:restriction>
</xs:simpleType>
"#,
);
let schema = parse_str(&xml).unwrap();
let st = &schema.simple_types[0];
assert_eq!(
st.restriction.facets,
vec![
Facet::Enumeration("ADDR".into()),
Facet::Enumeration("PBOX".into()),
Facet::Enumeration("HOME".into()),
]
);
}
#[test]
fn simple_type_pattern() {
let xml = wrap(
r#"
<xs:simpleType name="CountryCode">
<xs:restriction base="xs:string">
<xs:pattern value="[A-Z]{2,2}"/>
</xs:restriction>
</xs:simpleType>
"#,
);
let schema = parse_str(&xml).unwrap();
let st = &schema.simple_types[0];
assert_eq!(
st.restriction.facets,
vec![Facet::Pattern("[A-Z]{2,2}".into())]
);
}
#[test]
fn simple_type_decimal_restriction() {
let xml = wrap(
r#"
<xs:simpleType name="ActiveCurrencyAndAmount_SimpleType">
<xs:restriction base="xs:decimal">
<xs:fractionDigits value="5"/>
<xs:totalDigits value="18"/>
<xs:minInclusive value="0"/>
</xs:restriction>
</xs:simpleType>
"#,
);
let schema = parse_str(&xml).unwrap();
let st = &schema.simple_types[0];
assert_eq!(st.restriction.base, "xs:decimal");
assert_eq!(
st.restriction.facets,
vec![
Facet::FractionDigits(5),
Facet::TotalDigits(18),
Facet::MinInclusive("0".into()),
]
);
}
#[test]
fn simple_type_boolean_no_facets() {
let xml = wrap(
r#"
<xs:simpleType name="YesNoIndicator">
<xs:restriction base="xs:boolean"/>
</xs:simpleType>
"#,
);
let schema = parse_str(&xml).unwrap();
let st = &schema.simple_types[0];
assert_eq!(st.name, "YesNoIndicator");
assert_eq!(st.restriction.base, "xs:boolean");
assert!(st.restriction.facets.is_empty());
}
#[test]
fn simple_type_empty_string_restriction() {
let xml = wrap(
r#"
<xs:simpleType name="BusinessMessagePriorityCode">
<xs:restriction base="xs:string"/>
</xs:simpleType>
"#,
);
let schema = parse_str(&xml).unwrap();
let st = &schema.simple_types[0];
assert_eq!(st.restriction.base, "xs:string");
assert!(st.restriction.facets.is_empty());
}
#[test]
fn simple_type_date_restriction() {
let xml = wrap(
r#"
<xs:simpleType name="ISODate">
<xs:restriction base="xs:date"/>
</xs:simpleType>
"#,
);
let schema = parse_str(&xml).unwrap();
let st = &schema.simple_types[0];
assert_eq!(st.restriction.base, "xs:date");
}
#[test]
fn complex_type_sequence_required_optional() {
let xml = wrap(
r#"
<xs:complexType name="BranchData5">
<xs:sequence>
<xs:element name="FinInstnId" type="FinancialInstitutionIdentification23"/>
<xs:element maxOccurs="1" minOccurs="0" name="BrnchId" type="BranchData5"/>
</xs:sequence>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
assert_eq!(schema.complex_types.len(), 1);
let ct = &schema.complex_types[0];
assert_eq!(ct.name, "BranchData5");
if let ComplexContent::Sequence(ref seq) = ct.content {
assert_eq!(seq.len(), 2);
assert_eq!(seq[0].name, "FinInstnId");
assert_eq!(seq[0].min_occurs, 1);
assert_eq!(seq[0].max_occurs, MaxOccurs::Bounded(1));
assert_eq!(seq[1].name, "BrnchId");
assert_eq!(seq[1].min_occurs, 0);
assert_eq!(seq[1].max_occurs, MaxOccurs::Bounded(1));
} else {
panic!("expected Sequence, got {:?}", ct.content);
}
}
#[test]
fn complex_type_sequence_unbounded() {
let xml = wrap(
r#"
<xs:complexType name="BusinessApplicationHeaderV04">
<xs:sequence>
<xs:element name="Fr" type="Party51Choice"/>
<xs:element maxOccurs="unbounded" minOccurs="0" name="Rltd" type="BusinessApplicationHeader8"/>
</xs:sequence>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
let ct = &schema.complex_types[0];
if let ComplexContent::Sequence(ref seq) = ct.content {
assert_eq!(seq[1].max_occurs, MaxOccurs::Unbounded);
} else {
panic!("expected Sequence");
}
}
#[test]
fn complex_type_sequence_bounded_max() {
let xml = wrap(
r#"
<xs:complexType name="PostalAddress27">
<xs:sequence>
<xs:element maxOccurs="7" minOccurs="0" name="AdrLine" type="Max70Text"/>
</xs:sequence>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
let ct = &schema.complex_types[0];
if let ComplexContent::Sequence(ref seq) = ct.content {
assert_eq!(seq[0].max_occurs, MaxOccurs::Bounded(7));
} else {
panic!("expected Sequence");
}
}
#[test]
fn complex_type_choice() {
let xml = wrap(
r#"
<xs:complexType name="Party51Choice">
<xs:choice>
<xs:element name="OrgId" type="PartyIdentification272"/>
<xs:element name="FIId" type="BranchAndFinancialInstitutionIdentification8"/>
</xs:choice>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
let ct = &schema.complex_types[0];
assert_eq!(ct.name, "Party51Choice");
if let ComplexContent::Choice(ref variants) = ct.content {
assert_eq!(variants.len(), 2);
assert_eq!(variants[0].name, "OrgId");
assert_eq!(variants[0].type_name, "PartyIdentification272");
assert_eq!(variants[1].name, "FIId");
} else {
panic!("expected Choice, got {:?}", ct.content);
}
}
#[test]
fn complex_type_simple_content() {
let xml = wrap(
r#"
<xs:complexType name="ActiveCurrencyAndAmount">
<xs:simpleContent>
<xs:extension base="ActiveCurrencyAndAmount_SimpleType">
<xs:attribute name="Ccy" type="ActiveCurrencyCode" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
let ct = &schema.complex_types[0];
assert_eq!(ct.name, "ActiveCurrencyAndAmount");
if let ComplexContent::SimpleContent {
ref base,
ref attributes,
} = ct.content
{
assert_eq!(base, "ActiveCurrencyAndAmount_SimpleType");
assert_eq!(attributes.len(), 1);
assert_eq!(attributes[0].name, "Ccy");
assert_eq!(attributes[0].type_name, "ActiveCurrencyCode");
assert!(attributes[0].required);
} else {
panic!("expected SimpleContent, got {:?}", ct.content);
}
}
#[test]
fn simple_content_optional_attribute() {
let xml = wrap(
r#"
<xs:complexType name="FooAmount">
<xs:simpleContent>
<xs:extension base="FooAmount_SimpleType">
<xs:attribute name="Ccy" type="CurrencyCode" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
let ct = &schema.complex_types[0];
if let ComplexContent::SimpleContent { ref attributes, .. } = ct.content {
assert!(!attributes[0].required);
} else {
panic!("expected SimpleContent");
}
}
#[test]
fn complex_type_any_with_namespace() {
let xml = wrap(
r#"
<xs:complexType name="SignatureEnvelope">
<xs:sequence>
<xs:any namespace="http://www.w3.org/2000/09/xmldsig#" processContents="lax"/>
</xs:sequence>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
let ct = &schema.complex_types[0];
if let ComplexContent::Any { ref namespace } = ct.content {
assert_eq!(
namespace.as_deref(),
Some("http://www.w3.org/2000/09/xmldsig#")
);
} else {
panic!("expected Any, got {:?}", ct.content);
}
}
#[test]
fn mixed_schema_counts() {
let xml = wrap(
r#"
<xs:element name="AppHdr" type="BusinessApplicationHeaderV04"/>
<xs:simpleType name="Max35Text">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="35"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="BranchData5">
<xs:sequence>
<xs:element maxOccurs="1" minOccurs="0" name="Id" type="Max35Text"/>
</xs:sequence>
</xs:complexType>
"#,
);
let schema = parse_str(&xml).unwrap();
assert_eq!(schema.elements.len(), 1);
assert_eq!(schema.simple_types.len(), 1);
assert_eq!(schema.complex_types.len(), 1);
}
#[test]
fn parse_head_001_001_04() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../schemas/head/head.001.001.04.xsd"
);
let file = std::fs::File::open(path)
.expect("head.001.001.04.xsd not found — run scripts/download-schemas.sh");
let schema = parse(std::io::BufReader::new(file)).unwrap();
assert_eq!(
schema.target_namespace,
"urn:iso:std:iso:20022:tech:xsd:head.001.001.04"
);
assert!(!schema.elements.is_empty(), "no top-level elements parsed");
let app_hdr = schema.elements.iter().find(|e| e.name == "AppHdr");
assert!(app_hdr.is_some(), "AppHdr element not found");
assert_eq!(app_hdr.unwrap().type_name, "BusinessApplicationHeaderV04");
assert!(
schema.simple_types.iter().any(|s| s.name == "Max35Text"),
"Max35Text not found"
);
let max35 = schema
.simple_types
.iter()
.find(|s| s.name == "Max35Text")
.unwrap();
assert!(max35.restriction.facets.contains(&Facet::MinLength(1)));
assert!(max35.restriction.facets.contains(&Facet::MaxLength(35)));
let country = schema
.simple_types
.iter()
.find(|s| s.name == "CountryCode")
.unwrap();
assert!(country
.restriction
.facets
.iter()
.any(|f| matches!(f, Facet::Pattern(_))));
let yes_no = schema
.simple_types
.iter()
.find(|s| s.name == "YesNoIndicator")
.unwrap();
assert_eq!(yes_no.restriction.base, "xs:boolean");
let party51 = schema
.complex_types
.iter()
.find(|c| c.name == "Party51Choice")
.unwrap();
assert!(
matches!(party51.content, ComplexContent::Choice(_)),
"Party51Choice should be Choice"
);
let sig_env = schema
.complex_types
.iter()
.find(|c| c.name == "SignatureEnvelope")
.unwrap();
assert!(
matches!(sig_env.content, ComplexContent::Any { .. }),
"SignatureEnvelope should be Any"
);
let branch = schema
.complex_types
.iter()
.find(|c| c.name == "BranchData5")
.unwrap();
if let ComplexContent::Sequence(ref seq) = branch.content {
assert!(!seq.is_empty());
} else {
panic!("BranchData5 should be Sequence");
}
}
#[test]
fn parse_pacs_008_001_10() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../schemas/pacs/pacs.008.001.10.xsd"
);
let file = match std::fs::File::open(path) {
Ok(f) => f,
Err(_) => return,
};
let schema = parse(std::io::BufReader::new(file)).unwrap();
assert_eq!(
schema.target_namespace,
"urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10"
);
let decimal_st = schema
.simple_types
.iter()
.find(|s| s.name == "ActiveCurrencyAndAmount_SimpleType")
.unwrap();
assert_eq!(decimal_st.restriction.base, "xs:decimal");
assert!(decimal_st
.restriction
.facets
.contains(&Facet::FractionDigits(5)));
assert!(decimal_st
.restriction
.facets
.contains(&Facet::TotalDigits(18)));
assert!(decimal_st
.restriction
.facets
.contains(&Facet::MinInclusive("0".into())));
let amount_ct = schema
.complex_types
.iter()
.find(|c| c.name == "ActiveCurrencyAndAmount")
.unwrap();
if let ComplexContent::SimpleContent {
ref base,
ref attributes,
} = amount_ct.content
{
assert_eq!(base, "ActiveCurrencyAndAmount_SimpleType");
assert_eq!(attributes.len(), 1);
assert_eq!(attributes[0].name, "Ccy");
assert!(attributes[0].required);
} else {
panic!(
"ActiveCurrencyAndAmount should be SimpleContent, got {:?}",
amount_ct.content
);
}
assert!(schema.elements.iter().any(|e| e.name == "Document"));
}
}