Skip to main content

rusty_xml_parser/
xinclude.rs

1//! XInclude 1.0. Resource fetch is caller-supplied; the library never opens the network.
2
3use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
4use crate::error::XmlError;
5use crate::parse::{default_parse_options, xml_read_memory};
6
7const XI: &str = "http://www.w3.org/2001/XInclude";
8
9/// `xmlXIncludeProcess` with a caller resource loader.
10#[doc(alias = "xmlXIncludeProcess")]
11pub fn xml_xinclude_process<F>(doc: &mut XmlDoc, mut loader: F) -> Result<i32, XmlError>
12where
13    F: FnMut(&str) -> Result<Vec<u8>, String>,
14{
15    xml_xinclude_process_tree(doc, NodeId::DOCUMENT, &mut loader)
16}
17
18fn xml_xinclude_process_tree<F>(
19    doc: &mut XmlDoc,
20    start: NodeId,
21    loader: &mut F,
22) -> Result<i32, XmlError>
23where
24    F: FnMut(&str) -> Result<Vec<u8>, String>,
25{
26    let mut subs: Vec<NodeId> = Vec::new();
27    collect_includes(doc, start, &mut subs);
28    let mut n = 0i32;
29    for id in subs {
30        if replace_include(doc, id, loader)? {
31            n += 1;
32        }
33    }
34    Ok(n)
35}
36
37fn collect_includes(doc: &XmlDoc, id: NodeId, out: &mut Vec<NodeId>) {
38    if doc.kind(id) == NodeKind::Element
39        && doc.name(id) == "include"
40        && (doc.ns_uri(id) == Some(XI) || doc.prefix(id) == Some("xi"))
41    {
42        out.push(id);
43        return; // do not recurse into include (fallback stays nested)
44    }
45    let mut c = doc.first_child(id);
46    while let Some(x) = c {
47        collect_includes(doc, x, out);
48        c = doc.next_sibling(x);
49    }
50}
51
52fn replace_include<F>(doc: &mut XmlDoc, id: NodeId, loader: &mut F) -> Result<bool, XmlError>
53where
54    F: FnMut(&str) -> Result<Vec<u8>, String>,
55{
56    let href = doc.xml_get_prop(id, "href");
57    let parse = doc.xml_get_prop(id, "parse").unwrap_or_else(|| "xml".into());
58    let parent = doc.parent(id).unwrap_or(NodeId::DOCUMENT);
59    if let Some(h) = href {
60        match loader(&h) {
61            Ok(bytes) => {
62                if parse == "text" {
63                    let text = String::from_utf8_lossy(&bytes).into_owned();
64                    let n = doc.alloc(NodeKind::Text, "#text");
65                    doc.node_mut(n).content = text;
66                    doc.xml_add_prev_sibling(id, n);
67                    doc.xml_unlink_node(id);
68                    return Ok(true);
69                }
70                let included = xml_read_memory(&bytes, Some(&h), None, default_parse_options())?;
71                if let Some(root) = included.xml_doc_get_root_element() {
72                    graft(doc, parent, id, &included, root);
73                    doc.xml_unlink_node(id);
74                    return Ok(true);
75                }
76            }
77            Err(_) => {
78                if let Some(fb) = find_fallback(doc, id) {
79                    let mut kids = Vec::new();
80                    let mut c = doc.first_child(fb);
81                    while let Some(x) = c {
82                        kids.push(x);
83                        c = doc.next_sibling(x);
84                    }
85                    for k in kids {
86                        doc.xml_unlink_node(k);
87                        doc.xml_add_prev_sibling(id, k);
88                    }
89                    doc.xml_unlink_node(id);
90                    return Ok(true);
91                }
92                return Err(XmlError::new(
93                    crate::error::XML_ERR_DOCUMENT_EMPTY,
94                    format!("XInclude failed to load {h}"),
95                    0,
96                    0,
97                ));
98            }
99        }
100    }
101    Ok(false)
102}
103
104fn find_fallback(doc: &XmlDoc, include: NodeId) -> Option<NodeId> {
105    let mut c = doc.first_child(include);
106    while let Some(x) = c {
107        if doc.kind(x) == NodeKind::Element && doc.name(x) == "fallback" {
108            return Some(x);
109        }
110        c = doc.next_sibling(x);
111    }
112    None
113}
114
115fn graft(dst: &mut XmlDoc, _parent: NodeId, before: NodeId, src: &XmlDoc, src_id: NodeId) {
116    let new = copy_node(dst, src, src_id);
117    dst.xml_add_prev_sibling(before, new);
118}
119
120fn copy_node(dst: &mut XmlDoc, src: &XmlDoc, id: NodeId) -> NodeId {
121    let kind = src.kind(id);
122    let n = dst.alloc(kind, src.name(id));
123    {
124        let node = dst.node_mut(n);
125        node.prefix = src.prefix(id).map(str::to_string);
126        node.ns_uri = src.ns_uri(id).map(str::to_string);
127        node.content = src.content(id).to_string();
128        node.ns_defs = src.ns_defs(id).to_vec();
129    }
130    let mut a = src.first_attr(id);
131    while let Some(x) = a {
132        let an = copy_node(dst, src, x);
133        dst.xml_set_prop(n, src.name(x), src.content(x));
134        let _ = an;
135        a = src.next_sibling(x);
136    }
137    let mut c = src.first_child(id);
138    while let Some(x) = c {
139        let ch = copy_node(dst, src, x);
140        dst.xml_add_child(n, ch);
141        c = src.next_sibling(x);
142    }
143    n
144}