1use rusty_xml_tree::{NodeId, NodeKind, XmlDoc};
4
5use crate::error::XmlError;
6use crate::parse::default_parse_options;
7
8pub const HTML_PARSE_NOIMPLIED: i32 = 1 << 13;
10pub const HTML_PARSE_NONET: i32 = 1 << 11;
11
12const VOID: &[&str] = &[
13 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
14 "source", "track", "wbr",
15];
16
17fn is_void(name: &str) -> bool {
18 VOID.contains(&name)
19}
20
21#[doc(alias = "htmlReadMemory")]
23pub fn html_read_memory(
24 buffer: &[u8],
25 url: Option<&str>,
26 encoding: Option<&str>,
27 options: i32,
28) -> Result<XmlDoc, XmlError> {
29 let (utf8, _) = crate::encoding::xml_convert_to_utf8(buffer, encoding)?;
30 html_parse_utf8(&utf8, url, options)
31}
32
33#[doc(alias = "htmlReadDoc")]
35pub fn html_read_doc(
36 cur: &str,
37 url: Option<&str>,
38 encoding: Option<&str>,
39 options: i32,
40) -> Result<XmlDoc, XmlError> {
41 html_read_memory(cur.as_bytes(), url, encoding, options)
42}
43
44#[doc(alias = "htmlReadFile")]
46pub fn html_read_file(filename: &str, encoding: Option<&str>, options: i32) -> Result<XmlDoc, XmlError> {
47 let b = std::fs::read(filename).map_err(|e| XmlError::new(4, e.to_string(), 0, 0))?;
48 html_read_memory(&b, Some(filename), encoding, options)
49}
50
51fn html_parse_utf8(bytes: &[u8], _url: Option<&str>, options: i32) -> Result<XmlDoc, XmlError> {
52 let text = String::from_utf8_lossy(bytes);
53 let mut p = HtmlParser {
54 src: text.as_ref(),
55 pos: 0,
56 doc: XmlDoc::xml_new_doc(Some("1.0")),
57 stack: Vec::new(),
58 noimplied: (options & HTML_PARSE_NOIMPLIED) != 0,
59 html: None,
60 head: None,
61 body: None,
62 };
63 p.doc.encoding = Some("HTML".into());
64 p.parse()?;
65 while p.stack.len() > 1 {
66 p.stack.pop();
67 }
68 let _ = options | HTML_PARSE_NONET | default_parse_options();
69 Ok(p.doc)
70}
71
72struct HtmlParser<'a> {
73 src: &'a str,
74 pos: usize,
75 doc: XmlDoc,
76 stack: Vec<NodeId>,
77 noimplied: bool,
78 html: Option<NodeId>,
79 head: Option<NodeId>,
80 body: Option<NodeId>,
81}
82
83impl<'a> HtmlParser<'a> {
84 fn rest(&self) -> &'a str {
85 &self.src[self.pos..]
86 }
87 fn eof(&self) -> bool {
88 self.pos >= self.src.len()
89 }
90 fn bump(&mut self, n: usize) {
91 self.pos += n;
92 }
93 fn parent(&self) -> NodeId {
94 *self.stack.last().unwrap_or(&NodeId::DOCUMENT)
95 }
96 fn ensure_html(&mut self) -> NodeId {
97 if let Some(h) = self.html {
98 return h;
99 }
100 let html = self.doc.xml_new_node(None, "html");
101 self.doc.xml_doc_set_root_element(html);
102 self.html = Some(html);
103 html
104 }
105 fn ensure_head(&mut self) -> NodeId {
106 if let Some(h) = self.head {
107 return h;
108 }
109 let html = self.ensure_html();
110 let head = self.doc.xml_new_node(None, "head");
111 self.doc.xml_add_child(html, head);
112 self.head = Some(head);
113 head
114 }
115 fn ensure_body(&mut self) -> NodeId {
116 if let Some(b) = self.body {
117 return b;
118 }
119 let html = self.ensure_html();
120 let body = self.doc.xml_new_node(None, "body");
121 self.doc.xml_add_child(html, body);
122 self.body = Some(body);
123 body
124 }
125 fn ensure_html_body(&mut self) -> NodeId {
126 if self.noimplied {
127 return self.stack.last().copied().unwrap_or(NodeId::DOCUMENT);
128 }
129 self.ensure_body()
130 }
131 fn parse(&mut self) -> Result<(), XmlError> {
132 while !self.eof() {
133 if self.rest().starts_with("<!--") {
134 self.parse_comment()?;
135 } else if self.rest().starts_with("<!") {
136 self.skip_decl();
137 } else if self.rest().starts_with("</") {
138 self.parse_end_tag();
139 } else if self.rest().starts_with('<') {
140 self.parse_start_tag()?;
141 } else {
142 self.parse_text();
143 }
144 }
145 Ok(())
146 }
147 fn parse_comment(&mut self) -> Result<(), XmlError> {
148 self.bump(4);
149 if let Some(end) = self.rest().find("-->") {
150 let body = self.rest()[..end].to_string();
151 self.bump(end + 3);
152 let n = self.doc.alloc(NodeKind::Comment, "#comment");
153 self.doc.node_mut(n).content = body;
154 self.doc.xml_add_child(self.parent(), n);
155 } else {
156 self.pos = self.src.len();
157 }
158 Ok(())
159 }
160 fn skip_decl(&mut self) {
161 if let Some(i) = self.rest().find('>') {
162 self.bump(i + 1);
163 } else {
164 self.pos = self.src.len();
165 }
166 }
167 fn parse_text(&mut self) {
168 let mut i = 0;
169 let r = self.rest();
170 for (off, c) in r.char_indices() {
171 if c == '<' {
172 i = off;
173 break;
174 }
175 i = off + c.len_utf8();
176 }
177 if i == 0 {
178 return;
179 }
180 let t = r[..i].to_string();
181 self.bump(i);
182 if t.chars().all(|c| c.is_whitespace()) && self.stack.is_empty() {
183 return;
184 }
185 let n = self.doc.alloc(NodeKind::Text, "#text");
186 self.doc.node_mut(n).content = t;
187 let parent = if self.stack.is_empty() {
188 self.ensure_html_body()
189 } else {
190 self.parent()
191 };
192 self.doc.xml_add_child(parent, n);
193 }
194 fn parse_start_tag(&mut self) -> Result<(), XmlError> {
195 self.bump(1);
196 let name = self.read_name().to_ascii_lowercase();
197 if name.is_empty() {
198 return Ok(());
199 }
200 let mut attrs: Vec<(String, String)> = Vec::new();
201 loop {
202 self.skip_ws();
203 if self.rest().starts_with('>') {
204 self.bump(1);
205 break;
206 }
207 if self.rest().starts_with("/>") {
208 self.bump(2);
209 break;
210 }
211 if self.eof() {
212 break;
213 }
214 let an = self.read_name().to_ascii_lowercase();
215 if an.is_empty() {
216 self.bump(1);
217 continue;
218 }
219 self.skip_ws();
220 let av = if self.rest().starts_with('=') {
221 self.bump(1);
222 self.skip_ws();
223 self.read_attr_value()
224 } else {
225 an.clone()
226 };
227 attrs.push((an, av));
228 }
229 if name == "p" || name == "li" || name == "tr" || name == "td" || name == "th" {
231 while let Some(&top) = self.stack.last() {
232 if self.doc.name(top) == name {
233 self.stack.pop();
234 } else {
235 break;
236 }
237 }
238 }
239 let parent = if self.noimplied {
240 self.stack.last().copied().unwrap_or(NodeId::DOCUMENT)
241 } else if name == "html" {
242 NodeId::DOCUMENT
243 } else if name == "head" {
244 self.ensure_html()
245 } else if name == "body" || name == "frameset" {
246 self.ensure_html()
247 } else if matches!(name.as_str(), "title" | "meta" | "link" | "style" | "base") {
248 self.ensure_head()
249 } else {
250 self.ensure_body()
251 };
252 let elem = self.doc.xml_new_node(None, &name);
253 for (k, v) in attrs {
254 self.doc.xml_set_prop(elem, &k, &v);
255 }
256 if name == "html" {
257 self.doc.xml_doc_set_root_element(elem);
258 self.html = Some(elem);
259 } else {
260 self.doc.xml_add_child(parent, elem);
261 }
262 if name == "head" {
263 self.head = Some(elem);
264 }
265 if name == "body" || name == "frameset" {
266 self.body = Some(elem);
267 }
268 if !is_void(&name) {
269 self.stack.push(elem);
270 }
271 Ok(())
272 }
273 fn parse_end_tag(&mut self) {
274 self.bump(2);
275 let name = self.read_name().to_ascii_lowercase();
276 self.skip_ws();
277 if self.rest().starts_with('>') {
278 self.bump(1);
279 }
280 if let Some(idx) = self.stack.iter().rposition(|&id| self.doc.name(id) == name) {
281 self.stack.truncate(idx);
282 }
283 }
284 fn skip_ws(&mut self) {
285 while let Some(c) = self.rest().chars().next() {
286 if c.is_whitespace() {
287 self.bump(c.len_utf8());
288 } else {
289 break;
290 }
291 }
292 }
293 fn read_name(&mut self) -> String {
294 let r = self.rest();
295 let mut n = 0;
296 for (i, c) in r.char_indices() {
297 if i == 0 {
298 if !(c.is_ascii_alphabetic() || c == '_' || c == ':') {
299 return String::new();
300 }
301 } else if !(c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == ':' || c == '.') {
302 n = i;
303 break;
304 }
305 n = i + c.len_utf8();
306 }
307 let s = r[..n].to_string();
308 self.bump(n);
309 s
310 }
311 fn read_attr_value(&mut self) -> String {
312 let r = self.rest();
313 if r.starts_with('"') || r.starts_with('\'') {
314 let q = r.as_bytes()[0] as char;
315 self.bump(1);
316 if let Some(end) = self.rest().find(q) {
317 let v = self.rest()[..end].to_string();
318 self.bump(end + 1);
319 return v;
320 }
321 }
322 let mut n = 0;
323 for (i, c) in self.rest().char_indices() {
324 if c.is_whitespace() || c == '>' {
325 n = i;
326 break;
327 }
328 n = i + c.len_utf8();
329 }
330 let v = self.rest()[..n].to_string();
331 self.bump(n);
332 v
333 }
334}