Skip to main content

odox_core/xml/
read.rs

1//! Parsing a part of a package into an [`Element`] tree.
2//
3// Author: David M. Anderson
4// Built with AI assistance (Claude, Anthropic)
5
6use quick_xml::events::{BytesStart, Event};
7use quick_xml::name::{QName, ResolveResult};
8use quick_xml::{NsReader, XmlVersion};
9
10use super::{Attribute, Element, Name, Node, Ns};
11use crate::Error;
12
13/// Parse one part of a package.
14///
15/// `part` names the entry, and is carried into any error so that a window can
16/// say which part of the document it could not read.
17///
18/// # Errors
19///
20/// The part is not UTF-8, or is not well-formed XML.
21pub fn parse(bytes: &[u8], part: &str) -> Result<Element, Error> {
22    // OpenDocument requires UTF-8 or UTF-16 and every writer in practice
23    // produces UTF-8. Decoding here rather than letting the parser do it is what
24    // lets an event borrow the input instead of a scratch buffer, which is the
25    // difference between one copy of the part in memory and two.
26    let text = std::str::from_utf8(bytes).map_err(|e| Error::Xml {
27        part: part.to_owned(),
28        detail: format!("not UTF-8: {e}"),
29    })?;
30    // A UTF-8 byte order mark is legal and the parser does not skip it.
31    let text = text.strip_prefix('\u{feff}').unwrap_or(text);
32
33    let mut reader = NsReader::from_str(text);
34    let config = reader.config_mut();
35    // An empty element is kept as one rather than expanded into a start and an
36    // end, so that writing the tree does not rewrite `<a/>` as `<a></a>` in
37    // every style in the document.
38    config.expand_empty_elements = false;
39    // Whitespace is significant inside a paragraph, and the whitespace between
40    // elements is what makes a part readable after a round trip.
41    config.trim_text_start = false;
42    config.trim_text_end = false;
43    config.check_end_names = true;
44
45    let mut stack: Vec<Element> = Vec::new();
46    let mut root: Option<Element> = None;
47
48    loop {
49        let event = reader.read_event().map_err(|e| Error::Xml {
50            part: part.to_owned(),
51            detail: e.to_string(),
52        })?;
53        match event {
54            Event::Start(ref start) => {
55                let element = element_of(start, &reader, false);
56                stack.push(element);
57            }
58            Event::Empty(ref start) => {
59                let element = element_of(start, &reader, true);
60                push_node(&mut stack, &mut root, Node::Element(element), part)?;
61            }
62            Event::End(_) => {
63                let done = stack.pop().ok_or_else(|| Error::Xml {
64                    part: part.to_owned(),
65                    detail: "an end tag with nothing open".to_owned(),
66                })?;
67                push_node(&mut stack, &mut root, Node::Element(done), part)?;
68            }
69            Event::Text(ref t) => {
70                push_text(&mut stack, &t.xml10_content());
71            }
72            Event::GeneralRef(ref r) => {
73                let text = reference(r, part)?;
74                push_text(&mut stack, &text);
75            }
76            Event::CData(ref c) => {
77                let node = Node::CData(c.xml10_content().into_owned());
78                push_node(&mut stack, &mut root, node, part)?;
79            }
80            Event::Comment(ref c) => {
81                let node = Node::Comment(c.xml10_content().into_owned());
82                push_node(&mut stack, &mut root, node, part)?;
83            }
84            Event::PI(p) => {
85                // The target and its content together, as written, so that the
86                // writer can put back the whitespace between them.
87                let node = Node::ProcessingInstruction(p.into_inner().into_owned());
88                push_node(&mut stack, &mut root, node, part)?;
89            }
90            // The declaration is not kept: the writer emits the one every part
91            // of an OpenDocument package carries. A document type declaration
92            // is not kept either, because ODF defines none and writing back
93            // something a reader might resolve entities against would be the
94            // one place this tree stopped being a faithful copy.
95            Event::Decl(_) | Event::DocType(_) => {}
96            Event::Eof => break,
97        }
98    }
99
100    root.ok_or(Error::Xml {
101        part: part.to_owned(),
102        detail: "no root element".to_owned(),
103    })
104}
105
106/// The characters an entity or character reference stands for.
107///
108/// quick-xml reports `&amp;` and `&#10;` as events of their own rather than
109/// unescaping them into the surrounding text, so resolving them is this reader's
110/// job. ODF declares no document type and so has no entities beyond the five XML
111/// defines; a reference to anything else is given back as the characters that
112/// spelled it, which is both lossless and visible.
113///
114/// # Errors
115///
116/// A character reference names no character: `&#0;`, or a number past Unicode.
117fn reference(r: &quick_xml::events::BytesRef<'_>, part: &str) -> Result<String, Error> {
118    let name = r.xml10_content();
119    let resolved = if r.is_char_ref() {
120        r.resolve_char_ref().map_err(|e| Error::Xml {
121            part: part.to_owned(),
122            detail: e.to_string(),
123        })?
124    } else {
125        match &*name {
126            "amp" => Some('&'),
127            "lt" => Some('<'),
128            "gt" => Some('>'),
129            "quot" => Some('"'),
130            "apos" => Some('\''),
131            _ => None,
132        }
133    };
134    Ok(match resolved {
135        Some(c) => c.to_string(),
136        None => format!("&{name};"),
137    })
138}
139
140/// Text outside the root element — the newline after the declaration — has
141/// nowhere to live in a tree with one root, and is dropped. Inside, it is
142/// appended to the open element, merged with any text already there so that a
143/// run split by an entity reference comes back as one node.
144fn push_text(stack: &mut [Element], text: &str) {
145    if let Some(open) = stack.last_mut() {
146        if let Some(Node::Text(existing)) = open.children.last_mut() {
147            existing.push_str(text);
148        } else {
149            open.children.push(Node::Text(text.to_owned()));
150        }
151    }
152}
153
154fn push_node(
155    stack: &mut [Element],
156    root: &mut Option<Element>,
157    node: Node,
158    part: &str,
159) -> Result<(), Error> {
160    if let Some(open) = stack.last_mut() {
161        open.children.push(node);
162        return Ok(());
163    }
164    match node {
165        Node::Element(e) if root.is_none() => {
166            *root = Some(e);
167            Ok(())
168        }
169        Node::Element(_) => Err(Error::Xml {
170            part: part.to_owned(),
171            detail: "a second root element".to_owned(),
172        }),
173        // A comment or a processing instruction beside the root element is
174        // legal and carries nothing this crate reads.
175        _ => Ok(()),
176    }
177}
178
179fn element_of(start: &BytesStart<'_>, reader: &NsReader<&[u8]>, self_closing: bool) -> Element {
180    let name = resolved_name(reader, start.name(), false);
181    let mut attrs = Vec::new();
182    for attr in start.attributes() {
183        let Ok(attr) = attr else { continue };
184        // Normalizing is what the specification asks of an attribute value:
185        // entity references resolved, and a literal tab or newline turned into a
186        // space. The writer re-escapes the ones that have to survive, which is
187        // why `write.rs` spells them as character references.
188        let value = attr
189            .normalized_value(XmlVersion::Implicit1_0)
190            .unwrap_or_default()
191            .into_owned();
192        attrs.push(Attribute {
193            name: resolved_name(reader, attr.key, true),
194            value,
195        });
196    }
197    Element {
198        name,
199        attrs,
200        children: Vec::new(),
201        self_closing,
202    }
203}
204
205fn resolved_name(reader: &NsReader<&[u8]>, qname: QName<'_>, is_attribute: bool) -> Name {
206    let prefix = qname.prefix().map(|p| p.as_ref().into());
207    let local: Box<str> = qname.local_name().as_ref().into();
208
209    // A namespace declaration is an attribute whose own prefix is `xmlns`, or
210    // whose whole name is. The resolver will not place it in a namespace, and
211    // it has to be kept so that writing the tree re-declares what reading it
212    // found.
213    if prefix.as_deref() == Some("xmlns") {
214        return Name {
215            prefix,
216            local,
217            ns: Ns::Xmlns,
218        };
219    }
220    if prefix.is_none() && &*local == "xmlns" {
221        return Name {
222            prefix: None,
223            local,
224            ns: Ns::Xmlns,
225        };
226    }
227
228    let (resolved, _) = if is_attribute {
229        reader.resolver().resolve_attribute(qname)
230    } else {
231        reader.resolver().resolve_element(qname)
232    };
233    let ns = match resolved {
234        ResolveResult::Bound(uri) => Ns::from_uri(uri.as_ref()),
235        // An unprefixed attribute is in no namespace by definition, and a
236        // prefix with nothing in scope to bind it is a broken document that is
237        // still worth showing.
238        ResolveResult::Unbound | ResolveResult::Unknown(_) => Ns::None,
239    };
240    Name { prefix, local, ns }
241}