Skip to main content

muffy_document/
xml.rs

1//! XML documents.
2
3use crate::document::Document;
4use markup5ever_rcdom::RcDom;
5use std::io;
6use xml5ever::{driver::parse_document, tendril::TendrilSink};
7
8/// Parses an XML document.
9pub fn parse(source: &str) -> Result<Document, io::Error> {
10    parse_bytes(source.as_bytes())
11}
12
13/// Parses an XML document from bytes.
14pub fn parse_bytes(mut source: &[u8]) -> Result<Document, io::Error> {
15    parse_document(RcDom::default(), Default::default())
16        .from_utf8()
17        .read_from(&mut source)
18        .map(|dom| {
19            Document::from_markup5ever(&dom.document).set_errors(
20                dom.errors
21                    .borrow()
22                    .iter()
23                    .map(ToString::to_string)
24                    .collect(),
25            )
26        })
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32    use crate::document::{Element, Node};
33    use alloc::sync::Arc;
34    use pretty_assertions::assert_eq;
35
36    const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
37
38    fn element(
39        namespace: Option<&str>,
40        name: &str,
41        attributes: Vec<(&str, &str)>,
42        children: Vec<Arc<Node>>,
43    ) -> Arc<Node> {
44        Arc::new(Node::Element(
45            Element::new(
46                name.into(),
47                attributes
48                    .into_iter()
49                    .map(|(name, value)| (name.into(), value.into()))
50                    .collect(),
51                children,
52            )
53            .set_namespace(namespace.map(Into::into)),
54        ))
55    }
56
57    #[test]
58    fn parse_empty_string() {
59        assert_eq!(
60            parse("").unwrap(),
61            Document::new(vec![]).set_errors(vec!["Unexpected EOF in start phase".into()])
62        );
63    }
64
65    #[test]
66    fn parse_svg_document() {
67        assert_eq!(
68            parse(concat!(
69                r#"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"#,
70                r#"<a href="/foo"><rect/></a>"#,
71                r#"<image xlink:href="/bar.png"/>"#,
72                "</svg>"
73            ))
74            .unwrap(),
75            Document::new(vec![element(
76                Some(SVG_NAMESPACE),
77                "svg",
78                vec![],
79                vec![
80                    element(
81                        Some(SVG_NAMESPACE),
82                        "a",
83                        vec![("href", "/foo")],
84                        vec![element(Some(SVG_NAMESPACE), "rect", vec![], vec![])],
85                    ),
86                    element(
87                        Some(SVG_NAMESPACE),
88                        "image",
89                        vec![("xlink:href", "/bar.png")],
90                        vec![],
91                    ),
92                ],
93            )])
94        );
95    }
96
97    #[test]
98    fn collect_error_on_multiple_root_elements() {
99        let document = parse("<svg/><svg/>").unwrap();
100
101        assert_eq!(document.children().count(), 1);
102        assert_eq!(
103            document.errors().collect::<Vec<_>>(),
104            vec!["Unexpected element in end phase"]
105        );
106    }
107
108    #[test]
109    fn parse_element_without_namespace() {
110        assert_eq!(
111            parse("<svg><foreignObject/></svg>").unwrap(),
112            Document::new(vec![element(
113                None,
114                "svg",
115                vec![],
116                vec![element(None, "foreignObject", vec![], vec![])],
117            )])
118        );
119    }
120
121    #[test]
122    fn parse_prefixed_element() {
123        assert_eq!(
124            parse(concat!(
125                r#"<svg xmlns="http://www.w3.org/2000/svg">"#,
126                r#"<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>"#,
127                "</svg>"
128            ))
129            .unwrap(),
130            Document::new(vec![element(
131                Some(SVG_NAMESPACE),
132                "svg",
133                vec![],
134                vec![element(
135                    Some("http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
136                    "rdf:RDF",
137                    vec![],
138                    vec![],
139                )],
140            )])
141        );
142    }
143
144    #[test]
145    fn keep_html_element_in_svg_element() {
146        assert_eq!(
147            parse(r#"<svg xmlns="http://www.w3.org/2000/svg"><p>foo</p></svg>"#).unwrap(),
148            Document::new(vec![element(
149                Some(SVG_NAMESPACE),
150                "svg",
151                vec![],
152                vec![element(
153                    Some(SVG_NAMESPACE),
154                    "p",
155                    vec![],
156                    vec![Arc::new(Node::Text("foo".into()))],
157                )],
158            )])
159        );
160    }
161
162    #[test]
163    fn ignore_processing_instructions() {
164        assert_eq!(
165            parse(r#"<?xml version="1.0"?><svg/>"#).unwrap(),
166            Document::new(vec![element(None, "svg", vec![], vec![])])
167        );
168    }
169}