orinium_browser/engine/html/
xml.rs1use super::tokenizer::{Attribute, Token, Tokenizer};
18use crate::engine::tree::{NodeRef, Tree, TreeNode};
19use std::rc::Rc;
20
21#[derive(Debug, Clone, PartialEq)]
24pub struct XmlElement {
25 pub name: String,
26 pub attributes: Vec<Attribute>,
27}
28
29impl XmlElement {
30 pub fn attr(&self, name: &str) -> Option<&str> {
32 self.attributes
33 .iter()
34 .find(|attribute| attribute.name == name)
35 .map(|attribute| attribute.value.as_str())
36 }
37}
38
39pub fn parse(markup: &str) -> Result<Tree<XmlElement>, String> {
46 let mut tokenizer = Tokenizer::new(markup);
47 let mut root: Option<NodeRef<XmlElement>> = None;
48 let mut stack: Vec<NodeRef<XmlElement>> = Vec::new();
49
50 while let Some(token) = tokenizer.next_token() {
51 match token {
52 Token::StartTag {
53 name,
54 attributes,
55 self_closing,
56 } => {
57 let node = TreeNode::new(XmlElement { name, attributes });
58 match stack.last() {
59 Some(parent) => {
60 if self_closing {
61 TreeNode::add_child(parent, node);
62 } else {
63 TreeNode::add_child(parent, Rc::clone(&node));
64 stack.push(Rc::clone(&node));
65 }
66 }
67 None => {
68 if self_closing {
69 root = Some(node);
70 } else {
71 root = Some(Rc::clone(&node));
72 stack.push(node);
73 }
74 }
75 }
76 }
77 Token::EndTag { name } => {
78 if let Some(top) = stack.last() {
79 if top.borrow().value.name == name {
80 stack.pop();
81 } else {
82 return Err(format!(
83 "XML: mismatched closing tag </{name}> for <{}>",
84 top.borrow().value.name
85 ));
86 }
87 }
88 }
89 _ => {}
90 }
91 }
92
93 match root {
94 Some(root) if stack.is_empty() => Ok(Tree::from_root(root)),
95 Some(_) => Err(format!(
96 "XML: unclosed element <{}>",
97 stack.last().unwrap().borrow().value.name
98 )),
99 None => Err("XML: no document element".to_string()),
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn parses_svg_document_with_pi_and_comment() {
109 let tree = parse(
110 r##"<?xml version="1.0" encoding="UTF-8"?>
111 <!-- a comment -->
112 <svg width="12" height="8" xmlns="http://www.w3.org/2000/svg">
113 <g id="layer">
114 <rect width="12" height="8" fill="#ff0000"/>
115 <path d="M 0 0 L 1 1"/>
116 </g>
117 </svg>"##,
118 )
119 .expect("parses");
120 let root = tree.root.borrow();
121 assert_eq!(root.value.name, "svg");
122 assert_eq!(root.value.attr("width"), Some("12"));
123 let children = root.children().to_vec();
124 assert_eq!(children.len(), 1);
125 assert_eq!(children[0].borrow().value.name, "g");
126 let g_children = children[0].borrow().children().to_vec();
127 assert_eq!(g_children.len(), 2);
128 assert_eq!(g_children[0].borrow().value.name, "rect");
129 assert_eq!(g_children[0].borrow().value.attr("fill"), Some("#ff0000"));
130 assert_eq!(g_children[1].borrow().value.name, "path");
131 }
132
133 #[test]
134 fn keeps_namespace_qualified_names() {
135 let tree = parse(
136 r#"<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
137 <inkscape:label foo:bar="1"/>
138 </svg>"#,
139 )
140 .expect("parses");
141 let root = tree.root.borrow();
142 assert_eq!(root.value.name, "svg");
143 let ns = &root.children()[0].borrow().value;
144 assert_eq!(ns.name, "inkscape:label");
145 assert_eq!(ns.attr("foo:bar"), Some("1"));
146 }
147
148 #[test]
149 fn element_names_are_case_sensitive() {
150 let tree = parse(r#"<svg><path d="M0 0"/><PATH d="x"/></svg>"#).expect("parses");
151 let root = tree.root.borrow();
152 let children = root.children().to_vec();
153 assert_eq!(children.len(), 2);
154 assert_eq!(children[0].borrow().value.name, "path");
155 assert_eq!(children[1].borrow().value.name, "PATH");
156 }
157
158 #[test]
159 fn accepts_well_formed_and_rejects_malformed() {
160 assert!(parse("<svg><rect/></svg>").is_ok());
161 assert!(parse("<svg/>").is_ok());
162 assert!(parse("<svg><rect/></svg>trailing").is_ok());
163 assert!(parse("<svg>").is_err());
164 assert!(parse("<svg><rect/></g>").is_err());
165 assert!(parse("plain text").is_err());
166 }
167}