rusty_xml_valid/
schematron.rs1use rusty_xml_parser::{default_parse_options, xml_read_memory};
4use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
5use rusty_xml_xpath::{xml_xpath_cast_to_boolean, xml_xpath_eval, XmlXPathContext};
6
7const SCH: &[&str] = &[
8 "http://purl.oclc.org/dsdl/schematron",
9 "http://www.ascc.net/xml/schematron",
10];
11
12#[doc(alias = "xmlSchematronValidateDoc")]
14pub fn xml_schematron_validate_doc(sch: &[u8], doc: &XmlDoc) -> Result<(), String> {
15 let sdoc = xml_read_memory(sch, None, None, default_parse_options()).map_err(|e| e.to_string())?;
16 let mut errors = Vec::new();
17 walk_schema(&sdoc, sdoc.xml_doc_get_root_element(), doc, &mut errors);
18 if errors.is_empty() {
19 Ok(())
20 } else {
21 Err(errors.join("; "))
22 }
23}
24
25fn is_sch(doc: &XmlDoc, id: NodeId, name: &str) -> bool {
26 doc.kind(id) == NodeKind::Element
27 && doc.name(id) == name
28 && (doc.ns_uri(id).map(|u| SCH.contains(&u)).unwrap_or(true))
29}
30
31fn walk_schema(sdoc: &XmlDoc, node: Option<NodeId>, doc: &XmlDoc, errors: &mut Vec<String>) {
32 let Some(id) = node else { return };
33 if is_sch(sdoc, id, "rule") {
34 if let Some(ctx) = sdoc.xml_get_prop(id, "context") {
35 let nodes = eval_nodeset(doc, &ctx);
36 let mut c = sdoc.first_child(id);
37 while let Some(x) = c {
38 if is_sch(sdoc, x, "assert") {
39 if let Some(test) = sdoc.xml_get_prop(x, "test") {
40 for n in &nodes {
41 if !eval_bool(doc, *n, &test) {
42 errors.push(sdoc.xml_node_get_content(x));
43 }
44 }
45 }
46 } else if is_sch(sdoc, x, "report") {
47 if let Some(test) = sdoc.xml_get_prop(x, "test") {
48 for n in &nodes {
49 if eval_bool(doc, *n, &test) {
50 errors.push(sdoc.xml_node_get_content(x));
51 }
52 }
53 }
54 }
55 c = sdoc.next_sibling(x);
56 }
57 }
58 }
59 let mut c = sdoc.first_child(id);
60 while let Some(x) = c {
61 walk_schema(sdoc, Some(x), doc, errors);
62 c = sdoc.next_sibling(x);
63 }
64}
65
66fn eval_nodeset(doc: &XmlDoc, expr: &str) -> Vec<NodeId> {
67 let ctx = XmlXPathContext::xml_xpath_new_context(doc);
68 match xml_xpath_eval(expr, &ctx) {
69 Ok(rusty_xml_xpath::XPathObject::NodeSet(v)) => v,
70 _ => vec![],
71 }
72}
73
74fn eval_bool(doc: &XmlDoc, node: NodeId, expr: &str) -> bool {
75 let mut ctx = XmlXPathContext::xml_xpath_new_context(doc);
76 ctx.xml_xpath_set_context_node(node);
77 match xml_xpath_eval(expr, &ctx) {
78 Ok(o) => xml_xpath_cast_to_boolean(&o),
79 Err(_) => false,
80 }
81}