Skip to main content

rusty_xml_valid/
xsd.rs

1//! XML Schema (XSD) subset: elements, attributes, sequence, choice, simple types.
2
3use rusty_xml_parser::{default_parse_options, xml_read_memory};
4use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
5use std::collections::HashMap;
6
7const XS: &str = "http://www.w3.org/2001/XMLSchema";
8
9#[derive(Clone, Debug)]
10struct ElementDecl {
11    name: String,
12    min: u32,
13    max: u32,
14    attrs: Vec<AttrDecl>,
15    content: Content,
16}
17
18#[derive(Clone, Debug)]
19struct AttrDecl {
20    name: String,
21    required: bool,
22}
23
24#[derive(Clone, Debug)]
25enum Content {
26    Any,
27    Empty,
28    Text,
29    Sequence(Vec<ElementDecl>),
30    Choice(Vec<ElementDecl>),
31}
32
33struct Schema {
34    elements: HashMap<String, ElementDecl>,
35}
36
37/// `xmlSchemaValidateDoc`.
38#[doc(alias = "xmlSchemaValidateDoc")]
39pub fn xml_schema_validate_doc(xsd: &[u8], doc: &XmlDoc) -> Result<(), String> {
40    let sdoc = xml_read_memory(xsd, None, None, default_parse_options()).map_err(|e| e.to_string())?;
41    let schema = compile(&sdoc)?;
42    let root = doc.xml_doc_get_root_element().ok_or("no root")?;
43    let name = doc.name(root);
44    let decl = schema
45        .elements
46        .get(name)
47        .ok_or_else(|| format!("element {name} not declared"))?;
48    check_element(doc, root, decl, &schema)
49}
50
51fn is_xs(doc: &XmlDoc, id: NodeId, name: &str) -> bool {
52    doc.kind(id) == NodeKind::Element
53        && doc.name(id) == name
54        && (doc.ns_uri(id) == Some(XS) || doc.ns_uri(id).is_none())
55}
56
57fn compile(doc: &XmlDoc) -> Result<Schema, String> {
58    let root = doc.xml_doc_get_root_element().ok_or("empty schema")?;
59    let mut elements = HashMap::new();
60    let mut c = doc.first_child(root);
61    while let Some(x) = c {
62        if is_xs(doc, x, "element") {
63            if let Some(d) = compile_element(doc, x) {
64                elements.insert(d.name.clone(), d);
65            }
66        }
67        c = doc.next_sibling(x);
68    }
69    Ok(Schema { elements })
70}
71
72fn compile_element(doc: &XmlDoc, id: NodeId) -> Option<ElementDecl> {
73    let name = doc.xml_get_prop(id, "name")?;
74    let min = doc
75        .xml_get_prop(id, "minOccurs")
76        .and_then(|s| s.parse().ok())
77        .unwrap_or(1);
78    let max = match doc.xml_get_prop(id, "maxOccurs").as_deref() {
79        Some("unbounded") => u32::MAX,
80        Some(s) => s.parse().unwrap_or(1),
81        None => 1,
82    };
83    let mut attrs = Vec::new();
84    let mut content = Content::Text;
85    if let Some(ct) = find(doc, id, "complexType") {
86        content = Content::Empty;
87        let mut ch = doc.first_child(ct);
88        while let Some(x) = ch {
89            if is_xs(doc, x, "sequence") {
90                content = Content::Sequence(child_elements(doc, x));
91            } else if is_xs(doc, x, "choice") {
92                content = Content::Choice(child_elements(doc, x));
93            } else if is_xs(doc, x, "all") {
94                content = Content::Sequence(child_elements(doc, x));
95            } else if is_xs(doc, x, "attribute") {
96                if let Some(n) = doc.xml_get_prop(x, "name") {
97                    let required = doc.xml_get_prop(x, "use").as_deref() == Some("required");
98                    attrs.push(AttrDecl { name: n, required });
99                }
100            } else if is_xs(doc, x, "simpleContent") || is_xs(doc, x, "complexContent") {
101                content = Content::Any;
102            }
103            ch = doc.next_sibling(x);
104        }
105    } else if find(doc, id, "simpleType").is_some() {
106        content = Content::Text;
107    }
108    Some(ElementDecl {
109        name,
110        min,
111        max,
112        attrs,
113        content,
114    })
115}
116
117fn child_elements(doc: &XmlDoc, id: NodeId) -> Vec<ElementDecl> {
118    let mut v = Vec::new();
119    let mut c = doc.first_child(id);
120    while let Some(x) = c {
121        if is_xs(doc, x, "element") {
122            if let Some(d) = compile_element(doc, x) {
123                v.push(d);
124            } else if let Some(r) = doc.xml_get_prop(x, "ref") {
125                let local = r.rsplit(':').next().unwrap_or(&r).to_string();
126                let min = doc
127                    .xml_get_prop(x, "minOccurs")
128                    .and_then(|s| s.parse().ok())
129                    .unwrap_or(1);
130                let max = match doc.xml_get_prop(x, "maxOccurs").as_deref() {
131                    Some("unbounded") => u32::MAX,
132                    Some(s) => s.parse().unwrap_or(1),
133                    None => 1,
134                };
135                v.push(ElementDecl {
136                    name: local,
137                    min,
138                    max,
139                    attrs: vec![],
140                    content: Content::Any,
141                });
142            }
143        }
144        c = doc.next_sibling(x);
145    }
146    v
147}
148
149fn find(doc: &XmlDoc, id: NodeId, name: &str) -> Option<NodeId> {
150    let mut c = doc.first_child(id);
151    while let Some(x) = c {
152        if is_xs(doc, x, name) {
153            return Some(x);
154        }
155        c = doc.next_sibling(x);
156    }
157    None
158}
159
160fn check_element(doc: &XmlDoc, id: NodeId, decl: &ElementDecl, schema: &Schema) -> Result<(), String> {
161    for a in &decl.attrs {
162        if a.required && doc.xml_get_prop(id, &a.name).is_none() {
163            return Err(format!("required attribute {} missing", a.name));
164        }
165    }
166    let kids: Vec<NodeId> = {
167        let mut v = Vec::new();
168        let mut c = doc.first_child(id);
169        while let Some(x) = c {
170            if doc.kind(x) == NodeKind::Element {
171                v.push(x);
172            }
173            c = doc.next_sibling(x);
174        }
175        v
176    };
177    match &decl.content {
178        Content::Any | Content::Text => {}
179        Content::Empty => {
180            if !kids.is_empty() {
181                return Err(format!("{} must be empty", decl.name));
182            }
183        }
184        Content::Sequence(seq) => {
185            let mut i = 0;
186            for part in seq {
187                let mut seen = 0u32;
188                while i < kids.len() && doc.name(kids[i]) == part.name {
189                    let sub = schema.elements.get(&part.name).unwrap_or(part);
190                    check_element(doc, kids[i], sub, schema)?;
191                    i += 1;
192                    seen += 1;
193                    if seen == part.max {
194                        break;
195                    }
196                }
197                if seen < part.min {
198                    return Err(format!("need {} of {}", part.min, part.name));
199                }
200            }
201            if i != kids.len() {
202                return Err("extra children in sequence".into());
203            }
204        }
205        Content::Choice(alts) => {
206            if kids.len() != 1 {
207                return Err("choice expects one child".into());
208            }
209            let n = doc.name(kids[0]);
210            let part = alts.iter().find(|a| a.name == n).ok_or("choice mismatch")?;
211            let sub = schema.elements.get(&part.name).unwrap_or(part);
212            check_element(doc, kids[0], sub, schema)?;
213        }
214    }
215    Ok(())
216}