Skip to main content

muffy_document/
html.rs

1//! HTML documents.
2
3use crate::document::Document;
4use html5ever::{parse_document, tendril::TendrilSink};
5use markup5ever_rcdom::RcDom;
6use std::io;
7
8/// Parses an HTML document.
9pub fn parse(source: &str) -> Result<Document, io::Error> {
10    parse_bytes(source.as_bytes())
11}
12
13/// Parses an HTML 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| Document::from_markup5ever(&dom.document))
19}
20
21#[cfg(test)]
22mod tests {
23    use super::*;
24    use crate::document::{Element, Node};
25    use alloc::sync::Arc;
26    use pretty_assertions::assert_eq;
27
28    const XHTML_NAMESPACE: &str = "http://www.w3.org/1999/xhtml";
29    const SVG_NAMESPACE: &str = "http://www.w3.org/2000/svg";
30
31    fn element(
32        namespace: &str,
33        name: &str,
34        attributes: Vec<(&str, &str)>,
35        children: Vec<Arc<Node>>,
36    ) -> Arc<Node> {
37        Arc::new(Node::Element(
38            Element::new(
39                name.into(),
40                attributes
41                    .into_iter()
42                    .map(|(name, value)| (name.into(), value.into()))
43                    .collect(),
44                children,
45            )
46            .set_namespace(Some(namespace.into())),
47        ))
48    }
49
50    fn text(value: &str) -> Arc<Node> {
51        Arc::new(Node::Text(value.into()))
52    }
53
54    #[test]
55    fn parse_empty_string() {
56        assert_eq!(
57            parse("").unwrap(),
58            Document::new(vec![element(
59                XHTML_NAMESPACE,
60                "html",
61                vec![],
62                vec![
63                    element(XHTML_NAMESPACE, "head", vec![], vec![]),
64                    element(XHTML_NAMESPACE, "body", vec![], vec![]),
65                ],
66            )])
67        );
68    }
69
70    #[test]
71    fn parse_simple_html() {
72        assert_eq!(
73            parse("<html><body><p>Hello</p></body></html>").unwrap(),
74            Document::new(vec![element(
75                XHTML_NAMESPACE,
76                "html",
77                vec![],
78                vec![
79                    element(XHTML_NAMESPACE, "head", vec![], vec![]),
80                    element(
81                        XHTML_NAMESPACE,
82                        "body",
83                        vec![],
84                        vec![element(XHTML_NAMESPACE, "p", vec![], vec![text("Hello")])],
85                    ),
86                ],
87            )])
88        );
89    }
90
91    #[test]
92    fn parse_with_attributes() {
93        assert_eq!(
94            parse("<html><body><p class=\"foo\">Hello</p></body></html>").unwrap(),
95            Document::new(vec![element(
96                XHTML_NAMESPACE,
97                "html",
98                vec![],
99                vec![
100                    element(XHTML_NAMESPACE, "head", vec![], vec![]),
101                    element(
102                        XHTML_NAMESPACE,
103                        "body",
104                        vec![],
105                        vec![element(
106                            XHTML_NAMESPACE,
107                            "p",
108                            vec![("class", "foo")],
109                            vec![text("Hello")],
110                        )],
111                    ),
112                ],
113            )])
114        );
115    }
116
117    #[test]
118    fn parse_svg_document() {
119        assert_eq!(
120            parse(concat!(
121                r#"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"#,
122                r#"<a href="/foo"><rect/></a>"#,
123                r#"<image xlink:href="/bar.png"/>"#,
124                "</svg>"
125            ))
126            .unwrap(),
127            Document::new(vec![element(
128                XHTML_NAMESPACE,
129                "html",
130                vec![],
131                vec![
132                    element(XHTML_NAMESPACE, "head", vec![], vec![]),
133                    element(
134                        XHTML_NAMESPACE,
135                        "body",
136                        vec![],
137                        vec![element(
138                            SVG_NAMESPACE,
139                            "svg",
140                            vec![],
141                            vec![
142                                element(
143                                    SVG_NAMESPACE,
144                                    "a",
145                                    vec![("href", "/foo")],
146                                    vec![element(SVG_NAMESPACE, "rect", vec![], vec![])],
147                                ),
148                                element(
149                                    SVG_NAMESPACE,
150                                    "image",
151                                    vec![("xlink:href", "/bar.png")],
152                                    vec![],
153                                ),
154                            ],
155                        )],
156                    ),
157                ],
158            )])
159        );
160    }
161
162    #[test]
163    fn ignore_comments() {
164        assert_eq!(
165            parse("<html><body><!-- comment --><p>Hello</p></body></html>").unwrap(),
166            Document::new(vec![element(
167                XHTML_NAMESPACE,
168                "html",
169                vec![],
170                vec![
171                    element(XHTML_NAMESPACE, "head", vec![], vec![]),
172                    element(
173                        XHTML_NAMESPACE,
174                        "body",
175                        vec![],
176                        vec![element(XHTML_NAMESPACE, "p", vec![], vec![text("Hello")])],
177                    ),
178                ],
179            )])
180        );
181    }
182}