1use crate::scene::{ElementKind, NodeId, SceneGraph};
2use html5ever::parse_document;
3use html5ever::tendril::TendrilSink;
4use markup5ever_rcdom::{Handle, NodeData, RcDom};
5
6pub fn parse_html(html: &str) -> SceneGraph {
7 let (scene, _, _, _, _) = parse_html_full(html);
8 scene
9}
10
11pub fn parse_html_full(
13 html: &str,
14) -> (
15 SceneGraph,
16 Vec<String>,
17 Vec<String>,
18 Vec<String>,
19 Vec<String>,
20) {
21 let dom = parse_document(RcDom::default(), Default::default())
22 .from_utf8()
23 .read_from(&mut html.as_bytes())
24 .expect("failed to parse HTML");
25
26 let mut scene = SceneGraph::new();
27 let mut styles = Vec::new();
28 let mut scripts = Vec::new();
29 let mut ext_styles = Vec::new();
30 let mut ext_scripts = Vec::new();
31
32 walk_node_full(
33 &dom.document,
34 &mut scene,
35 None,
36 &mut styles,
37 &mut scripts,
38 &mut ext_styles,
39 &mut ext_scripts,
40 );
41
42 (scene, styles, scripts, ext_styles, ext_scripts)
43}
44
45fn walk_node_full(
46 handle: &Handle,
47 scene: &mut SceneGraph,
48 parent: Option<NodeId>,
49 styles: &mut Vec<String>,
50 scripts: &mut Vec<String>,
51 ext_styles: &mut Vec<String>,
52 ext_scripts: &mut Vec<String>,
53) {
54 match &handle.data {
55 NodeData::Document => {
56 for child in handle.children.borrow().iter() {
57 walk_node_full(
58 child,
59 scene,
60 parent,
61 styles,
62 scripts,
63 ext_styles,
64 ext_scripts,
65 );
66 }
67 }
68 NodeData::Element { name, attrs, .. } => {
69 let tag = name.local.to_string();
70
71 if tag == "style" {
73 for child in handle.children.borrow().iter() {
74 if let NodeData::Text { contents } = &child.data {
75 styles.push(contents.borrow().to_string());
76 }
77 }
78 return;
79 }
80
81 if tag == "script" {
83 let attrs_ref = attrs.borrow();
84 let src = attrs_ref
85 .iter()
86 .find(|a| a.name.local.to_string() == "src")
87 .map(|a| a.value.to_string());
88 if let Some(src_path) = src {
89 ext_scripts.push(src_path);
90 } else {
91 for child in handle.children.borrow().iter() {
92 if let NodeData::Text { contents } = &child.data {
93 scripts.push(contents.borrow().to_string());
94 }
95 }
96 }
97 return;
98 }
99
100 if tag == "link" {
102 let attrs_ref = attrs.borrow();
103 let is_stylesheet = attrs_ref.iter().any(|a| {
104 a.name.local.to_string() == "rel" && a.value.to_string() == "stylesheet"
105 });
106 if is_stylesheet {
107 if let Some(href) = attrs_ref
108 .iter()
109 .find(|a| a.name.local.to_string() == "href")
110 {
111 ext_styles.push(href.value.to_string());
112 }
113 }
114 return;
115 }
116
117 match tag.as_str() {
119 "head" | "meta" | "title" => {
120 for child in handle.children.borrow().iter() {
121 walk_node_full(
122 child,
123 scene,
124 parent,
125 styles,
126 scripts,
127 ext_styles,
128 ext_scripts,
129 );
130 }
131 return;
132 }
133 _ => {}
134 }
135
136 let kind = ElementKind::from_tag(&tag);
138 let id = scene.add_node(kind, tag.clone());
139
140 let attrs_ref = attrs.borrow();
141 for attr in attrs_ref.iter() {
142 let attr_name = attr.name.local.to_string();
143 let attr_value = attr.value.to_string();
144
145 match attr_name.as_str() {
146 "id" => {
147 scene.get_mut(id).element_id = Some(attr_value.clone());
148 }
149 "class" => {
150 scene.get_mut(id).classes = attr_value
151 .split_whitespace()
152 .map(|s| s.to_string())
153 .collect();
154 }
155 _ => {}
156 }
157
158 if let Some(event) = attr_name.strip_prefix("on") {
159 scene
160 .get_mut(id)
161 .event_handlers
162 .insert(event.to_string(), attr_value.clone());
163 }
164
165 scene.get_mut(id).attributes.insert(attr_name, attr_value);
166 }
167
168 if let Some(parent_id) = parent {
169 scene.add_child(parent_id, id);
170 }
171
172 for child in handle.children.borrow().iter() {
173 walk_node_full(
174 child,
175 scene,
176 Some(id),
177 styles,
178 scripts,
179 ext_styles,
180 ext_scripts,
181 );
182 }
183 }
184 NodeData::Text { contents } => {
185 let text = contents.borrow().to_string();
186 let trimmed = text.trim();
187 if trimmed.is_empty() {
188 return;
189 }
190
191 let id = scene.add_node(ElementKind::Text, "text".to_string());
192 scene.get_mut(id).text_content = Some(trimmed.to_string());
193
194 if let Some(parent_id) = parent {
195 scene.add_child(parent_id, id);
196 }
197 }
198 _ => {}
199 }
200}
201
202#[allow(dead_code)]
204fn walk_node(handle: &Handle, scene: &mut SceneGraph, parent: Option<NodeId>) {
205 let node = handle;
206
207 match &node.data {
208 NodeData::Document => {
209 for child in node.children.borrow().iter() {
210 walk_node(child, scene, parent);
211 }
212 }
213 NodeData::Element { name, attrs, .. } => {
214 let tag = name.local.to_string();
215 let kind = ElementKind::from_tag(&tag);
216
217 match tag.as_str() {
219 "head" | "meta" | "title" | "link" => {
220 for child in node.children.borrow().iter() {
222 if let NodeData::Element { name, .. } = &child.data {
223 let child_tag = name.local.to_string();
224 if child_tag == "style" || child_tag == "script" {
225 walk_node(child, scene, parent);
226 }
227 }
228 }
229 return;
230 }
231 _ => {}
232 }
233
234 let id = scene.add_node(kind, tag.clone());
235
236 let attrs = attrs.borrow();
238 for attr in attrs.iter() {
239 let attr_name = attr.name.local.to_string();
240 let attr_value = attr.value.to_string();
241
242 match attr_name.as_str() {
243 "id" => {
244 scene.get_mut(id).element_id = Some(attr_value.clone());
245 }
246 "class" => {
247 scene.get_mut(id).classes = attr_value
248 .split_whitespace()
249 .map(|s| s.to_string())
250 .collect();
251 }
252 "style" => {
253 }
255 _ => {}
256 }
257
258 if let Some(event) = attr_name.strip_prefix("on") {
260 scene
261 .get_mut(id)
262 .event_handlers
263 .insert(event.to_string(), attr_value.clone());
264 }
265
266 scene.get_mut(id).attributes.insert(attr_name, attr_value);
267 }
268
269 if let Some(parent_id) = parent {
270 scene.add_child(parent_id, id);
271 }
272
273 for child in node.children.borrow().iter() {
275 walk_node(child, scene, Some(id));
276 }
277 }
278 NodeData::Text { contents } => {
279 let text = contents.borrow().to_string();
280 let trimmed = text.trim();
281 if trimmed.is_empty() {
282 return;
283 }
284
285 let id = scene.add_node(ElementKind::Text, "text".to_string());
286 scene.get_mut(id).text_content = Some(trimmed.to_string());
287
288 if let Some(parent_id) = parent {
289 scene.add_child(parent_id, id);
290 }
291 }
292 _ => {
293 }
295 }
296}