azul_layout/xml/
mod.rs

1#![allow(unused_variables)]
2
3use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec};
4use core::fmt;
5#[cfg(feature = "std")]
6use std::path::Path;
7
8#[cfg(feature = "svg")]
9pub mod svg;
10
11pub use azul_core::xml::*;
12use azul_core::{dom::Dom, impl_from, styled_dom::StyledDom, window::StringPairVec};
13use azul_css::{
14    parser::{CssApiWrapper, CssParseError},
15    AzString, Css, OptionAzString, U8Vec,
16};
17use xmlparser::Tokenizer;
18
19#[cfg(feature = "xml")]
20pub fn domxml_from_str(xml: &str, component_map: &mut XmlComponentMap) -> DomXml {
21    let mut error_css = CssApiWrapper::empty();
22
23    let parsed = match parse_xml_string(&xml) {
24        Ok(parsed) => parsed,
25        Err(e) => {
26            return DomXml {
27                parsed_dom: Dom::body()
28                    .with_children(vec![Dom::text(format!("{}", e))].into())
29                    .style(error_css.clone()),
30            };
31        }
32    };
33
34    let parsed_dom = match str_to_dom(parsed.as_ref(), component_map, None) {
35        Ok(o) => o,
36        Err(e) => {
37            return DomXml {
38                parsed_dom: Dom::body()
39                    .with_children(vec![Dom::text(format!("{}", e))].into())
40                    .style(error_css.clone()),
41            };
42        }
43    };
44
45    DomXml { parsed_dom }
46}
47
48/// Loads, parses and builds a DOM from an XML file
49///
50/// **Warning**: The file is reloaded from disk on every function call - do not
51/// use this in release builds! This function deliberately never fails: In an error case,
52/// the error gets rendered as a `NodeType::Label`.
53#[cfg(all(feature = "std", feature = "xml"))]
54pub fn domxml_from_file<I: AsRef<Path>>(
55    file_path: I,
56    component_map: &mut XmlComponentMap,
57) -> DomXml {
58    use std::fs;
59
60    let mut error_css = CssApiWrapper::empty();
61
62    let xml = match fs::read_to_string(file_path.as_ref()) {
63        Ok(xml) => xml,
64        Err(e) => {
65            return DomXml {
66                parsed_dom: Dom::body()
67                    .with_children(
68                        vec![Dom::text(format!(
69                            "Error reading: \"{}\": {}",
70                            file_path.as_ref().to_string_lossy(),
71                            e
72                        ))]
73                        .into(),
74                    )
75                    .style(error_css.clone()),
76            };
77        }
78    };
79
80    domxml_from_str(&xml, component_map)
81}
82
83/// Parses the XML string into an XML tree, returns
84/// the root `<app></app>` node, with the children attached to it.
85///
86/// Since the XML allows multiple root nodes, this function returns
87/// a `Vec<XmlNode>` - which are the "root" nodes, containing all their
88/// children recursively.
89///
90/// # Example
91///
92/// ```rust
93/// # use azul_layout::xml::{XmlNode, parse_xml_string};
94/// assert_eq!(
95///     parse_xml_string("<app><p /><div id='thing' /></app>").unwrap(),
96///     vec![XmlNode::new("app").with_children(vec![
97///         XmlNode::new("p"),
98///         XmlNode::new("div").with_attribute("id", "thing"),
99///     ])]
100/// )
101/// ```
102#[cfg(feature = "xml")]
103pub fn parse_xml_string(xml: &str) -> Result<Vec<XmlNode>, XmlError> {
104    use xmlparser::{ElementEnd::*, Token::*, Tokenizer};
105
106    use self::XmlParseError::*;
107
108    let mut root_node = XmlNode::default();
109
110    // Search for "<?xml" and "?>" tags and delete them from the XML
111    let mut xml = xml.trim();
112    if xml.starts_with("<?") {
113        let pos = xml
114            .find("?>")
115            .ok_or(XmlError::MalformedHierarchy("<?xml".into(), "?>".into()))?;
116        xml = &xml[(pos + 2)..];
117    }
118
119    // Delete <!doctype if necessary
120    let mut xml = xml.trim();
121    if xml.starts_with("<!") {
122        let pos = xml
123            .find(">")
124            .ok_or(XmlError::MalformedHierarchy("<!doctype".into(), ">".into()))?;
125        xml = &xml[(pos + 1)..];
126    }
127
128    let tokenizer = Tokenizer::from_fragment(xml, 0..xml.len());
129
130    // In order to insert where the item is, let's say
131    // [0 -> 1st element, 5th-element -> node]
132    // we need to trach the index of the item in the parent.
133    let mut current_hierarchy: Vec<usize> = Vec::new();
134
135    for token in tokenizer {
136        let token = token.map_err(|e| XmlError::ParserError(translate_xmlparser_error(e)))?;
137        match token {
138            ElementStart { local, .. } => {
139                if let Some(current_parent) = get_item(&current_hierarchy, &mut root_node) {
140                    let children_len = current_parent.children.len();
141                    current_parent.children.push(XmlNode {
142                        node_type: local.to_string().into(),
143                        attributes: StringPairVec::new(),
144                        children: Vec::new().into(),
145                        text: None.into(),
146                    });
147                    current_hierarchy.push(children_len);
148                }
149            }
150            ElementEnd { end: Empty, .. } => {
151                current_hierarchy.pop();
152            }
153            ElementEnd {
154                end: Close(_, close_value),
155                ..
156            } => {
157                let i = get_item(&current_hierarchy, &mut root_node);
158                if let Some(last) = i {
159                    if last.node_type.as_str() != close_value.as_str() {
160                        return Err(XmlError::MalformedHierarchy(
161                            close_value.to_string().into(),
162                            last.node_type.clone(),
163                        ));
164                    }
165                }
166                current_hierarchy.pop();
167            }
168            Attribute { local, value, .. } => {
169                if let Some(last) = get_item(&current_hierarchy, &mut root_node) {
170                    // NOTE: Only lowercase the key ("local"), not the value!
171                    last.attributes.push(azul_core::window::AzStringPair {
172                        key: local.to_string().into(),
173                        value: value.as_str().to_string().into(),
174                    });
175                }
176            }
177            Text { text } => {
178                let text = text.trim();
179                if !text.is_empty() {
180                    if let Some(last) = get_item(&current_hierarchy, &mut root_node) {
181                        if let Some(s) = last.text.as_mut() {
182                            let mut newstr = s.as_str().to_string();
183                            newstr.push_str(text);
184                            *s = newstr.into();
185                        }
186                        if last.text.is_none() {
187                            last.text = Some(text.to_string().into()).into();
188                        }
189                    }
190                }
191            }
192            _ => {}
193        }
194    }
195
196    Ok(root_node.children.into())
197}
198
199#[cfg(feature = "xml")]
200pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
201    Ok(Xml {
202        root: parse_xml_string(s)?.into(),
203    })
204}
205
206#[cfg(not(feature = "xml"))]
207pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
208    Err(XmlError::NoParserAvailable)
209}
210
211// to_string(&self) -> String
212
213#[cfg(feature = "xml")]
214pub fn translate_roxmltree_expandedname<'a, 'b>(
215    e: roxmltree::ExpandedName<'a, 'b>,
216) -> XmlQualifiedName {
217    let ns: Option<AzString> = e.namespace().map(|e| e.to_string().into());
218    XmlQualifiedName {
219        name: e.name().to_string().into(),
220        namespace: ns.into(),
221    }
222}
223
224#[cfg(feature = "xml")]
225fn translate_roxmltree_attribute<'a>(e: roxmltree::Attribute<'a>) -> XmlQualifiedName {
226    XmlQualifiedName {
227        name: e.name().to_string().into(),
228        namespace: e.namespace().map(|e| e.to_string().into()).into(),
229    }
230}
231
232#[cfg(feature = "xml")]
233fn translate_xmlparser_streamerror(e: xmlparser::StreamError) -> XmlStreamError {
234    match e {
235        xmlparser::StreamError::UnexpectedEndOfStream => XmlStreamError::UnexpectedEndOfStream,
236        xmlparser::StreamError::InvalidName => XmlStreamError::InvalidName,
237        xmlparser::StreamError::InvalidReference => XmlStreamError::InvalidReference,
238        xmlparser::StreamError::InvalidExternalID => XmlStreamError::InvalidExternalID,
239        xmlparser::StreamError::InvalidCommentData => XmlStreamError::InvalidCommentData,
240        xmlparser::StreamError::InvalidCommentEnd => XmlStreamError::InvalidCommentEnd,
241        xmlparser::StreamError::InvalidCharacterData => XmlStreamError::InvalidCharacterData,
242        xmlparser::StreamError::NonXmlChar(c, tp) => XmlStreamError::NonXmlChar(NonXmlCharError {
243            ch: c.into(),
244            pos: translate_roxml_textpos(tp),
245        }),
246        xmlparser::StreamError::InvalidChar(a, b, tp) => {
247            XmlStreamError::InvalidChar(InvalidCharError {
248                expected: a,
249                got: b,
250                pos: translate_roxml_textpos(tp),
251            })
252        }
253        xmlparser::StreamError::InvalidCharMultiple(a, b, tp) => {
254            XmlStreamError::InvalidCharMultiple(InvalidCharMultipleError {
255                expected: a,
256                got: b.to_vec().into(),
257                pos: translate_roxml_textpos(tp),
258            })
259        }
260        xmlparser::StreamError::InvalidQuote(a, tp) => {
261            XmlStreamError::InvalidQuote(InvalidQuoteError {
262                got: a.into(),
263                pos: translate_roxml_textpos(tp),
264            })
265        }
266        xmlparser::StreamError::InvalidSpace(a, tp) => {
267            XmlStreamError::InvalidSpace(InvalidSpaceError {
268                got: a.into(),
269                pos: translate_roxml_textpos(tp),
270            })
271        }
272        xmlparser::StreamError::InvalidString(a, tp) => {
273            XmlStreamError::InvalidString(InvalidStringError {
274                got: a.to_string().into(),
275                pos: translate_roxml_textpos(tp),
276            })
277        }
278    }
279}
280
281#[cfg(feature = "xml")]
282fn translate_xmlparser_error(e: xmlparser::Error) -> XmlParseError {
283    match e {
284        xmlparser::Error::InvalidDeclaration(se, tp) => {
285            XmlParseError::InvalidDeclaration(XmlTextError {
286                stream_error: translate_xmlparser_streamerror(se),
287                pos: translate_roxml_textpos(tp),
288            })
289        }
290        xmlparser::Error::InvalidComment(se, tp) => XmlParseError::InvalidComment(XmlTextError {
291            stream_error: translate_xmlparser_streamerror(se),
292            pos: translate_roxml_textpos(tp),
293        }),
294        xmlparser::Error::InvalidPI(se, tp) => XmlParseError::InvalidPI(XmlTextError {
295            stream_error: translate_xmlparser_streamerror(se),
296            pos: translate_roxml_textpos(tp),
297        }),
298        xmlparser::Error::InvalidDoctype(se, tp) => XmlParseError::InvalidDoctype(XmlTextError {
299            stream_error: translate_xmlparser_streamerror(se),
300            pos: translate_roxml_textpos(tp),
301        }),
302        xmlparser::Error::InvalidEntity(se, tp) => XmlParseError::InvalidEntity(XmlTextError {
303            stream_error: translate_xmlparser_streamerror(se),
304            pos: translate_roxml_textpos(tp),
305        }),
306        xmlparser::Error::InvalidElement(se, tp) => XmlParseError::InvalidElement(XmlTextError {
307            stream_error: translate_xmlparser_streamerror(se),
308            pos: translate_roxml_textpos(tp),
309        }),
310        xmlparser::Error::InvalidAttribute(se, tp) => {
311            XmlParseError::InvalidAttribute(XmlTextError {
312                stream_error: translate_xmlparser_streamerror(se),
313                pos: translate_roxml_textpos(tp),
314            })
315        }
316        xmlparser::Error::InvalidCdata(se, tp) => XmlParseError::InvalidCdata(XmlTextError {
317            stream_error: translate_xmlparser_streamerror(se),
318            pos: translate_roxml_textpos(tp),
319        }),
320        xmlparser::Error::InvalidCharData(se, tp) => XmlParseError::InvalidCharData(XmlTextError {
321            stream_error: translate_xmlparser_streamerror(se),
322            pos: translate_roxml_textpos(tp),
323        }),
324        xmlparser::Error::UnknownToken(tp) => {
325            XmlParseError::UnknownToken(translate_roxml_textpos(tp))
326        }
327    }
328}
329
330#[cfg(feature = "xml")]
331pub(crate) fn translate_roxmltree_error(e: roxmltree::Error) -> XmlError {
332    match e {
333        roxmltree::Error::InvalidXmlPrefixUri(s) => {
334            XmlError::InvalidXmlPrefixUri(translate_roxml_textpos(s))
335        }
336        roxmltree::Error::UnexpectedXmlUri(s) => {
337            XmlError::UnexpectedXmlUri(translate_roxml_textpos(s))
338        }
339        roxmltree::Error::UnexpectedXmlnsUri(s) => {
340            XmlError::UnexpectedXmlnsUri(translate_roxml_textpos(s))
341        }
342        roxmltree::Error::InvalidElementNamePrefix(s) => {
343            XmlError::InvalidElementNamePrefix(translate_roxml_textpos(s))
344        }
345        roxmltree::Error::DuplicatedNamespace(s, tp) => {
346            XmlError::DuplicatedNamespace(DuplicatedNamespaceError {
347                ns: s.into(),
348                pos: translate_roxml_textpos(tp),
349            })
350        }
351        roxmltree::Error::UnknownNamespace(s, tp) => {
352            XmlError::UnknownNamespace(UnknownNamespaceError {
353                ns: s.into(),
354                pos: translate_roxml_textpos(tp),
355            })
356        }
357        roxmltree::Error::UnexpectedCloseTag {
358            expected,
359            actual,
360            pos,
361        } => XmlError::UnexpectedCloseTag(UnexpectedCloseTagError {
362            expected: expected.into(),
363            actual: actual.into(),
364            pos: translate_roxml_textpos(pos),
365        }),
366        roxmltree::Error::UnexpectedEntityCloseTag(s) => {
367            XmlError::UnexpectedEntityCloseTag(translate_roxml_textpos(s))
368        }
369        roxmltree::Error::UnknownEntityReference(s, tp) => {
370            XmlError::UnknownEntityReference(UnknownEntityReferenceError {
371                entity: s.into(),
372                pos: translate_roxml_textpos(tp),
373            })
374        }
375        roxmltree::Error::MalformedEntityReference(s) => {
376            XmlError::MalformedEntityReference(translate_roxml_textpos(s))
377        }
378        roxmltree::Error::EntityReferenceLoop(s) => {
379            XmlError::EntityReferenceLoop(translate_roxml_textpos(s))
380        }
381        roxmltree::Error::InvalidAttributeValue(s) => {
382            XmlError::InvalidAttributeValue(translate_roxml_textpos(s))
383        }
384        roxmltree::Error::DuplicatedAttribute(s, tp) => {
385            XmlError::DuplicatedAttribute(DuplicatedAttributeError {
386                attribute: s.into(),
387                pos: translate_roxml_textpos(tp),
388            })
389        }
390        roxmltree::Error::NoRootNode => XmlError::NoRootNode,
391        roxmltree::Error::SizeLimit => XmlError::SizeLimit,
392        roxmltree::Error::DtdDetected => XmlError::DtdDetected,
393        roxmltree::Error::ParserError(s) => XmlError::ParserError(translate_xmlparser_error(s)),
394    }
395}
396
397#[cfg(feature = "xml")]
398#[inline(always)]
399const fn translate_roxml_textpos(o: roxmltree::TextPos) -> XmlTextPos {
400    XmlTextPos {
401        row: o.row,
402        col: o.col,
403    }
404}