Skip to main content

openbim_dt/
document.rs

1//! Lossless, namespace-aware XML tree used by ISO 23387 documents.
2
3use std::{borrow::Cow, error::Error, fmt, io::Cursor, sync::Arc};
4
5use quick_xml::{
6    events::{
7        attributes::Attribute as XmlAttribute, BytesCData, BytesDecl, BytesEnd, BytesPI,
8        BytesStart, BytesText, Event,
9    },
10    name::QName,
11    Writer,
12};
13
14use crate::parser::{parse_document, ParseError, ParseOptions};
15
16/// XML declaration values retained by the document model.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct XmlDeclaration {
19    pub version: String,
20    pub encoding: Option<String>,
21    pub standalone: Option<String>,
22}
23
24/// One XML attribute with both lexical and resolved namespace identity.
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct Attribute {
27    qname: String,
28    prefix: Option<String>,
29    local_name: String,
30    namespace_uri: Option<Arc<str>>,
31    value: String,
32}
33
34impl Attribute {
35    pub(crate) fn parsed(
36        qname: String,
37        prefix: Option<String>,
38        local_name: String,
39        namespace_uri: Option<Arc<str>>,
40        value: String,
41    ) -> Self {
42        Self {
43            qname,
44            prefix,
45            local_name,
46            namespace_uri,
47            value,
48        }
49    }
50
51    #[must_use]
52    pub fn qname(&self) -> &str {
53        &self.qname
54    }
55
56    #[must_use]
57    pub fn prefix(&self) -> Option<&str> {
58        self.prefix.as_deref()
59    }
60
61    #[must_use]
62    pub fn local_name(&self) -> &str {
63        &self.local_name
64    }
65
66    #[must_use]
67    pub fn namespace_uri(&self) -> Option<&str> {
68        self.namespace_uri.as_deref()
69    }
70
71    #[must_use]
72    pub fn value(&self) -> &str {
73        &self.value
74    }
75}
76
77/// An XML element retaining names, namespace resolution, attributes, and order.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct Element {
80    qname: String,
81    prefix: Option<String>,
82    local_name: String,
83    namespace_uri: Option<Arc<str>>,
84    attributes: Vec<Attribute>,
85    nodes: Vec<Node>,
86    empty_style: bool,
87}
88
89impl Element {
90    pub(crate) fn parsed(
91        qname: String,
92        prefix: Option<String>,
93        local_name: String,
94        namespace_uri: Option<Arc<str>>,
95        attributes: Vec<Attribute>,
96        empty_style: bool,
97    ) -> Self {
98        Self {
99            qname,
100            prefix,
101            local_name,
102            namespace_uri,
103            attributes,
104            nodes: Vec::new(),
105            empty_style,
106        }
107    }
108
109    #[must_use]
110    pub fn qname(&self) -> &str {
111        &self.qname
112    }
113
114    #[must_use]
115    pub fn prefix(&self) -> Option<&str> {
116        self.prefix.as_deref()
117    }
118
119    #[must_use]
120    pub fn local_name(&self) -> &str {
121        &self.local_name
122    }
123
124    #[must_use]
125    pub fn namespace_uri(&self) -> Option<&str> {
126        self.namespace_uri.as_deref()
127    }
128
129    #[must_use]
130    pub fn attributes(&self) -> &[Attribute] {
131        &self.attributes
132    }
133
134    #[must_use]
135    pub fn nodes(&self) -> &[Node] {
136        &self.nodes
137    }
138
139    /// Whether the source used an empty-element tag such as `<dt:Property/>`.
140    #[must_use]
141    pub const fn was_empty_element(&self) -> bool {
142        self.empty_style
143    }
144
145    /// Direct child elements in document order.
146    pub fn children(&self) -> impl Iterator<Item = &Element> {
147        self.nodes.iter().filter_map(Node::as_element)
148    }
149
150    /// Finds an attribute by resolved namespace URI and local name.
151    #[must_use]
152    pub fn attribute_ns(&self, namespace_uri: Option<&str>, local_name: &str) -> Option<&str> {
153        self.attributes
154            .iter()
155            .find(|attribute| {
156                attribute.namespace_uri() == namespace_uri && attribute.local_name() == local_name
157            })
158            .map(Attribute::value)
159    }
160
161    /// Direct semantic text and CDATA content, preserving order.
162    #[must_use]
163    pub fn direct_text(&self) -> String {
164        let mut result = String::new();
165        for node in &self.nodes {
166            match node {
167                Node::Text(value) | Node::CData(value) => result.push_str(value),
168                _ => {}
169            }
170        }
171        result
172    }
173
174    pub(crate) fn push(&mut self, node: Node) {
175        push_coalescing_text(&mut self.nodes, node);
176    }
177}
178
179pub(crate) fn push_coalescing_text(nodes: &mut Vec<Node>, node: Node) {
180    if let Node::Text(value) = node {
181        if let Some(Node::Text(existing)) = nodes.last_mut() {
182            existing.push_str(&value);
183        } else {
184            nodes.push(Node::Text(value));
185        }
186    } else {
187        nodes.push(node);
188    }
189}
190
191/// XML node kinds retained by the document model.
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub enum Node {
194    Element(Element),
195    Text(String),
196    CData(String),
197    Comment(String),
198    /// Complete processing-instruction content: target plus optional data.
199    ProcessingInstruction(String),
200}
201
202impl Node {
203    #[must_use]
204    pub fn as_element(&self) -> Option<&Element> {
205        match self {
206            Self::Element(element) => Some(element),
207            _ => None,
208        }
209    }
210}
211
212/// A well-formed XML document with one root element.
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct Document {
215    declaration: Option<XmlDeclaration>,
216    prolog: Vec<Node>,
217    root: Element,
218    epilog: Vec<Node>,
219}
220
221impl Document {
222    /// Parses with conservative production defaults.
223    pub fn parse(xml: &str) -> Result<Self, ParseError> {
224        Self::parse_with_options(xml, ParseOptions::default())
225    }
226
227    /// Parses with explicit resource limits.
228    pub fn parse_with_options(xml: &str, options: ParseOptions) -> Result<Self, ParseError> {
229        parse_document(xml, options)
230    }
231
232    pub(crate) fn parsed(
233        declaration: Option<XmlDeclaration>,
234        prolog: Vec<Node>,
235        root: Element,
236        epilog: Vec<Node>,
237    ) -> Self {
238        Self {
239            declaration,
240            prolog,
241            root,
242            epilog,
243        }
244    }
245
246    #[must_use]
247    pub const fn declaration(&self) -> Option<&XmlDeclaration> {
248        self.declaration.as_ref()
249    }
250
251    #[must_use]
252    pub fn prolog(&self) -> &[Node] {
253        &self.prolog
254    }
255
256    #[must_use]
257    pub const fn root(&self) -> &Element {
258        &self.root
259    }
260
261    #[must_use]
262    pub fn epilog(&self) -> &[Node] {
263        &self.epilog
264    }
265
266    /// Serializes the semantic XML tree without dropping retained content.
267    pub fn to_xml_string(&self) -> Result<String, WriteError> {
268        let mut writer = Writer::new(Cursor::new(Vec::new()));
269        if let Some(declaration) = &self.declaration {
270            writer.write_event(Event::Decl(BytesDecl::new(
271                &declaration.version,
272                declaration.encoding.as_deref(),
273                declaration.standalone.as_deref(),
274            )))?;
275        }
276        for node in &self.prolog {
277            write_node(&mut writer, node)?;
278        }
279        write_element(&mut writer, &self.root)?;
280        for node in &self.epilog {
281            write_node(&mut writer, node)?;
282        }
283        String::from_utf8(writer.into_inner().into_inner()).map_err(WriteError::Utf8)
284    }
285
286    pub(crate) fn take_root(self) -> Element {
287        self.root
288    }
289}
290
291fn write_element(
292    writer: &mut Writer<Cursor<Vec<u8>>>,
293    element: &Element,
294) -> Result<(), WriteError> {
295    let mut start = BytesStart::new(element.qname());
296    for attribute in element.attributes() {
297        start.push_attribute(XmlAttribute {
298            key: QName(attribute.qname().as_bytes()),
299            value: Cow::Owned(escape_attribute_value(attribute.value())),
300        });
301    }
302    if element.empty_style && element.nodes.is_empty() {
303        writer.write_event(Event::Empty(start))?;
304        return Ok(());
305    }
306    writer.write_event(Event::Start(start))?;
307    for node in element.nodes() {
308        write_node(writer, node)?;
309    }
310    writer.write_event(Event::End(BytesEnd::new(element.qname())))?;
311    Ok(())
312}
313
314fn escape_attribute_value(value: &str) -> Vec<u8> {
315    let mut output = String::with_capacity(value.len());
316    for character in value.chars() {
317        match character {
318            '&' => output.push_str("&amp;"),
319            '<' => output.push_str("&lt;"),
320            '"' => output.push_str("&quot;"),
321            '\t' => output.push_str("&#x9;"),
322            '\n' => output.push_str("&#xA;"),
323            '\r' => output.push_str("&#xD;"),
324            _ => output.push(character),
325        }
326    }
327    output.into_bytes()
328}
329
330fn escape_text_value(value: &str) -> String {
331    let mut output = String::with_capacity(value.len());
332    for character in value.chars() {
333        match character {
334            '&' => output.push_str("&amp;"),
335            '<' => output.push_str("&lt;"),
336            '>' => output.push_str("&gt;"),
337            '\r' => output.push_str("&#13;"),
338            _ => output.push(character),
339        }
340    }
341    output
342}
343
344fn write_node(writer: &mut Writer<Cursor<Vec<u8>>>, node: &Node) -> Result<(), WriteError> {
345    match node {
346        Node::Element(element) => write_element(writer, element),
347        Node::Text(value) => writer
348            .write_event(Event::Text(BytesText::from_escaped(escape_text_value(
349                value,
350            ))))
351            .map_err(Into::into),
352        Node::CData(value) => writer
353            .write_event(Event::CData(BytesCData::new(value)))
354            .map_err(Into::into),
355        Node::Comment(value) => writer
356            .write_event(Event::Comment(BytesText::new(value)))
357            .map_err(Into::into),
358        Node::ProcessingInstruction(value) => writer
359            .write_event(Event::PI(BytesPI::new(value)))
360            .map_err(Into::into),
361    }
362}
363
364/// XML serialization failure.
365#[derive(Debug)]
366pub enum WriteError {
367    Xml(std::io::Error),
368    Utf8(std::string::FromUtf8Error),
369}
370
371impl fmt::Display for WriteError {
372    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
373        match self {
374            Self::Xml(error) => write!(formatter, "could not write XML: {error}"),
375            Self::Utf8(error) => write!(formatter, "XML writer produced invalid UTF-8: {error}"),
376        }
377    }
378}
379
380impl Error for WriteError {
381    fn source(&self) -> Option<&(dyn Error + 'static)> {
382        match self {
383            Self::Xml(error) => Some(error),
384            Self::Utf8(error) => Some(error),
385        }
386    }
387}
388
389impl From<std::io::Error> for WriteError {
390    fn from(value: std::io::Error) -> Self {
391        Self::Xml(value)
392    }
393}