1use crate::engine::html::tokenizer::{Attribute, Token, Tokenizer};
4use crate::engine::html::util as html_util;
5use crate::engine::{
6 css::{
7 matcher::{ElementChain, ElementInfo},
8 parser::{CssNodeType, Parser as CssParser},
9 },
10 tree::{NodeRef, Tree, TreeNode},
11};
12use std::cell::RefCell;
13use std::collections::HashMap;
14use std::rc::Rc;
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum ShadowRootMode {
17 Open,
18 Closed,
19}
20
21#[derive(Debug, Clone)]
22pub enum HtmlNodeType {
23 Document,
24 DocumentFragment,
25 Element {
26 tag_name: String,
27 attributes: Vec<Attribute>,
28 },
29 ShadowRoot {
32 mode: ShadowRootMode,
33 },
34 Text(String),
35 Comment(String),
36 Doctype {
37 name: Option<String>,
38 public_id: Option<String>,
39 system_id: Option<String>,
40 },
41 ProcessingInstruction {
43 target: String,
44 data: String,
45 },
46 InvalidNode(Token, String), }
48
49impl HtmlNodeType {
50 pub fn tag_name(&self) -> Option<&str> {
51 match self {
52 HtmlNodeType::Element { tag_name, .. } => Some(tag_name),
53 _ => None,
54 }
55 }
56
57 pub fn get_attr(&self, name: &str) -> Option<&str> {
58 match self {
59 HtmlNodeType::Element { attributes, .. } => attributes
60 .iter()
61 .find(|attr| attr.name == name)
62 .map(|attr| attr.value.as_str()),
63 _ => None,
64 }
65 }
66 pub fn set_attr(&mut self, name: &str, value: String) {
67 if let HtmlNodeType::Element { attributes, .. } = self {
68 if let Some(attr) = attributes.iter_mut().find(|attr| attr.name == name) {
69 attr.value = value;
70 } else {
71 attributes.push(Attribute {
72 name: name.to_string(),
73 value,
74 });
75 }
76 }
77 }
78 pub fn remove_attr(&mut self, name: &str) -> Option<String> {
79 if let HtmlNodeType::Element { attributes, .. } = self {
80 attributes
81 .iter()
82 .position(|attr| attr.name == name)
83 .map(|pos| attributes.remove(pos).value)
84 } else {
85 None
86 }
87 }
88 pub fn has_attr(&self, name: &str) -> bool {
89 match self {
90 HtmlNodeType::Element { attributes, .. } => {
91 attributes.iter().any(|attr| attr.name == name)
92 }
93 _ => false,
94 }
95 }
96}
97
98pub type DomTree = Tree<HtmlNodeType>;
99
100#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum ClassicScriptSource {
103 Inline(String),
104 External(String),
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
111pub enum ScriptingMode {
112 #[default]
113 Enabled,
114 Disabled,
115}
116
117#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
119pub enum ClassicScriptExecution {
120 #[default]
121 Default,
122 Defer,
123 Async,
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct ClassicScriptDescriptor {
129 pub source: ClassicScriptSource,
130 pub execution: ClassicScriptExecution,
131}
132
133impl DomTree {
134 pub fn get_elements_by_tag_name(&self, tag_name: &str) -> Vec<NodeRef<HtmlNodeType>> {
136 self.find_all(|n| {
137 if let HtmlNodeType::Element { tag_name: t, .. } = n {
138 t.eq_ignore_ascii_case(tag_name)
139 } else {
140 false
141 }
142 })
143 }
144
145 pub fn get_element_by_id(&self, id: &str) -> Option<NodeRef<HtmlNodeType>> {
147 self.find_all(|n| {
148 if let HtmlNodeType::Element { attributes, .. } = n {
149 attributes
150 .iter()
151 .any(|attr| attr.name == "id" && attr.value == id)
152 } else {
153 false
154 }
155 })
156 .into_iter()
157 .next()
158 }
159
160 pub fn get_elements_by_class_name(&self, class_name: &str) -> Vec<NodeRef<HtmlNodeType>> {
162 self.find_all(|n| {
163 if let HtmlNodeType::Element { attributes, .. } = n {
164 attributes.iter().any(|attr| {
165 attr.name == "class" && attr.value.split_whitespace().any(|c| c == class_name)
166 })
167 } else {
168 false
169 }
170 })
171 }
172
173 pub fn inner_text(node: &NodeRef<HtmlNodeType>) -> String {
176 let n = node.borrow();
177 match &n.value {
178 HtmlNodeType::Text(content) => content.clone(),
179 HtmlNodeType::Element { .. } => n
180 .children()
181 .iter()
182 .filter(|c| !matches!(c.borrow().value, HtmlNodeType::ShadowRoot { .. }))
184 .map(DomTree::inner_text)
185 .collect(),
186 HtmlNodeType::ShadowRoot { .. } => {
187 n.children().iter().map(DomTree::inner_text).collect()
189 }
190 _ => "".to_string(),
191 }
192 }
193
194 pub fn set_text_content(node: &NodeRef<HtmlNodeType>, new_text: &str) {
196 if let HtmlNodeType::Text(content) = &mut node.borrow_mut().value {
198 *content = new_text.to_string();
199 return;
200 }
201
202 let is_element = matches!(node.borrow().value, HtmlNodeType::Element { .. });
205 if is_element {
206 node.borrow_mut().clear_children();
208 let text_node = TreeNode::new(HtmlNodeType::Text(new_text.to_string()));
209 TreeNode::add_child(node, text_node);
210 }
211 }
212
213 pub fn collect_text_by_tag(&self, tag_name: &str) -> Vec<String> {
215 let mut texts = Vec::new();
216
217 self.traverse(|node| {
218 let n = node.borrow();
219 if let HtmlNodeType::Element { tag_name: t, .. } = &n.value
220 && t.eq_ignore_ascii_case(tag_name)
221 {
222 let text_of_this_node: String = n
223 .children()
224 .iter()
225 .filter_map(|child| {
226 let child_ref = child.borrow();
227 if let HtmlNodeType::Text(content) = &child_ref.value {
228 Some(content.clone())
229 } else {
230 None
231 }
232 })
233 .collect();
234
235 texts.push(text_of_this_node);
236 }
237 });
238
239 texts
240 }
241
242 pub fn collect_classic_scripts(&self) -> Vec<ClassicScriptSource> {
247 self.collect_classic_script_descriptors()
248 .into_iter()
249 .map(|script| script.source)
250 .collect()
251 }
252
253 pub fn query_selector(&self, selector: &str) -> Option<NodeRef<HtmlNodeType>> {
255 self.query_selector_all(selector).into_iter().next()
256 }
257
258 pub fn query_selector_all(&self, selector: &str) -> Vec<NodeRef<HtmlNodeType>> {
260 query_selector_all_from(&self.root, selector, true)
261 }
262
263 pub fn query_selector_within(
267 scope: &NodeRef<HtmlNodeType>,
268 selector: &str,
269 ) -> Option<NodeRef<HtmlNodeType>> {
270 Self::query_selector_all_within(scope, selector)
271 .into_iter()
272 .next()
273 }
274
275 pub fn query_selector_all_within(
279 scope: &NodeRef<HtmlNodeType>,
280 selector: &str,
281 ) -> Vec<NodeRef<HtmlNodeType>> {
282 query_selector_all_from(scope, selector, false)
283 }
284
285 pub fn element_matches_selector(node: &NodeRef<HtmlNodeType>, selector: &str) -> bool {
287 let selectors = parse_query_selectors(selector);
288 if selectors.is_empty() {
289 return false;
290 }
291 let chain = element_chain(node);
292 selectors.iter().any(|s| s.matches(&chain))
293 }
294
295 pub fn element_closest(
298 node: &NodeRef<HtmlNodeType>,
299 selector: &str,
300 ) -> Option<NodeRef<HtmlNodeType>> {
301 let selectors = parse_query_selectors(selector);
302 if selectors.is_empty() {
303 return None;
304 }
305
306 let mut current = Some(Rc::clone(node));
307 while let Some(n) = current.clone() {
308 if matches!(n.borrow().value, HtmlNodeType::ShadowRoot { .. }) {
310 break;
311 }
312 let chain = element_chain(&n);
313 if selectors.iter().any(|s| s.matches(&chain)) {
314 return Some(Rc::clone(&n));
315 }
316 current = n.borrow().parent();
317 }
318
319 None
320 }
321
322 pub fn collect_classic_script_descriptors(&self) -> Vec<ClassicScriptDescriptor> {
324 self.get_elements_by_tag_name("script")
325 .into_iter()
326 .filter_map(|node| {
327 let n = node.borrow();
328 let script_type = n.value.get_attr("type").unwrap_or("").trim();
329 if !is_classic_javascript_type(script_type) {
330 return None;
331 }
332
333 match n.value.get_attr("src").map(str::trim) {
334 Some(src) if !src.is_empty() => Some(ClassicScriptDescriptor {
335 source: ClassicScriptSource::External(src.to_string()),
336 execution: if n.value.has_attr("async") {
337 ClassicScriptExecution::Async
338 } else if n.value.has_attr("defer") {
339 ClassicScriptExecution::Defer
340 } else {
341 ClassicScriptExecution::Default
342 },
343 }),
344 Some(_) => None,
345 None => Some(ClassicScriptDescriptor {
346 source: ClassicScriptSource::Inline(DomTree::inner_text(&node)),
347 execution: ClassicScriptExecution::Default,
349 }),
350 }
351 })
352 .collect()
353 }
354
355 pub fn collect_inline_scripts(&self) -> Vec<String> {
359 self.collect_classic_scripts()
360 .into_iter()
361 .filter_map(|script| match script {
362 ClassicScriptSource::Inline(source) => Some(source),
363 ClassicScriptSource::External(_) => None,
364 })
365 .collect()
366 }
367}
368
369fn query_selector_all_from(
370 scope: &NodeRef<HtmlNodeType>,
371 selector: &str,
372 include_scope: bool,
373) -> Vec<NodeRef<HtmlNodeType>> {
374 let selectors = parse_query_selectors(selector);
375 if selectors.is_empty() {
376 return Vec::new();
377 }
378
379 let mut candidates = Vec::new();
380 collect_element_nodes(scope, include_scope, &mut candidates);
381 candidates
382 .into_iter()
383 .filter(|node| {
384 let chain = element_chain(node);
385 selectors.iter().any(|selector| selector.matches(&chain))
386 })
387 .collect()
388}
389
390pub(crate) fn parse_query_selectors(
391 selector: &str,
392) -> Vec<crate::engine::css::parser::ComplexSelector> {
393 if selector.trim().is_empty() {
394 return Vec::new();
395 }
396
397 let source = format!("{selector} {{}} ");
398 let Ok(stylesheet) = CssParser::new(&source).parse() else {
399 return Vec::new();
400 };
401 stylesheet
402 .children()
403 .iter()
404 .find_map(|node| match node.node() {
405 CssNodeType::Rule { selectors } => Some(selectors.clone()),
406 _ => None,
407 })
408 .unwrap_or_default()
409}
410
411pub(crate) fn collect_element_nodes(
412 node: &NodeRef<HtmlNodeType>,
413 include_node: bool,
414 output: &mut Vec<NodeRef<HtmlNodeType>>,
415) {
416 let (is_element, is_shadow, children) = {
417 let node = node.borrow();
418 (
419 matches!(node.value, HtmlNodeType::Element { .. }),
420 matches!(node.value, HtmlNodeType::ShadowRoot { .. }),
421 node.children().to_vec(),
422 )
423 };
424 if include_node && is_element {
425 output.push(Rc::clone(node));
426 }
427 if !is_shadow {
431 for child in children {
432 let is_child_shadow = matches!(child.borrow().value, HtmlNodeType::ShadowRoot { .. });
434 if !is_child_shadow {
435 collect_element_nodes(&child, true, output);
436 }
437 }
438 } else {
439 for child in children {
442 collect_element_nodes(&child, true, output);
443 }
444 }
445}
446
447pub(crate) fn element_chain(node: &NodeRef<HtmlNodeType>) -> ElementChain {
448 let mut chain = Vec::new();
449 let mut current = Some(Rc::clone(node));
450 while let Some(node) = current {
451 if matches!(node.borrow().value, HtmlNodeType::ShadowRoot { .. }) {
453 break;
454 }
455 if let Some(info) = element_info(&node) {
456 chain.push(info);
457 }
458 current = node.borrow().parent();
459 }
460 ElementChain::from_vec(chain)
461}
462
463fn element_info(node: &NodeRef<HtmlNodeType>) -> Option<ElementInfo> {
464 let (tag_name, attributes, parent) = {
465 let node = node.borrow();
466 let HtmlNodeType::Element {
467 tag_name,
468 attributes,
469 } = &node.value
470 else {
471 return None;
472 };
473 (tag_name.clone(), attributes.clone(), node.parent())
474 };
475
476 let siblings = parent
477 .map(|parent| parent.borrow().children().to_vec())
478 .unwrap_or_else(|| vec![Rc::clone(node)]);
479 let sibling_elements: Vec<_> = siblings
480 .into_iter()
481 .filter_map(|sibling| basic_element_info(&sibling).map(|info| (sibling, info)))
482 .collect();
483 let element_count = sibling_elements.len();
484 let mut type_counts = HashMap::<String, usize>::new();
485 for (_, sibling) in &sibling_elements {
486 *type_counts.entry(sibling.tag_name.clone()).or_default() += 1;
487 }
488
489 let position = sibling_elements
490 .iter()
491 .position(|(sibling, _)| Rc::ptr_eq(sibling, node))?;
492 let element_index = position + 1;
493 let type_index = sibling_elements[..=position]
494 .iter()
495 .filter(|(_, sibling)| sibling.tag_name == tag_name)
496 .count();
497 let previous_siblings = ElementChain::from_document_order(
498 sibling_elements[..position]
499 .iter()
500 .map(|(_, sibling)| sibling.clone()),
501 );
502
503 Some(ElementInfo {
504 tag_name: tag_name.clone(),
505 id: attributes
506 .iter()
507 .find(|attribute| attribute.name.eq_ignore_ascii_case("id"))
508 .map(|attribute| attribute.value.clone()),
509 classes: attributes
510 .iter()
511 .find(|attribute| attribute.name.eq_ignore_ascii_case("class"))
512 .map(|attribute| {
513 attribute
514 .value
515 .split_whitespace()
516 .map(str::to_string)
517 .collect()
518 })
519 .unwrap_or_default(),
520 attributes: attributes
521 .into_iter()
522 .map(|attribute| (attribute.name, attribute.value))
523 .collect(),
524 element_index,
525 element_count,
526 type_index,
527 type_count: type_counts[&tag_name],
528 previous_siblings,
529 })
530}
531
532fn basic_element_info(node: &NodeRef<HtmlNodeType>) -> Option<ElementInfo> {
533 let node = node.borrow();
534 let HtmlNodeType::Element {
535 tag_name,
536 attributes,
537 } = &node.value
538 else {
539 return None;
540 };
541 Some(ElementInfo {
542 tag_name: tag_name.clone(),
543 id: attributes
544 .iter()
545 .find(|attribute| attribute.name.eq_ignore_ascii_case("id"))
546 .map(|attribute| attribute.value.clone()),
547 classes: attributes
548 .iter()
549 .find(|attribute| attribute.name.eq_ignore_ascii_case("class"))
550 .map(|attribute| {
551 attribute
552 .value
553 .split_whitespace()
554 .map(str::to_string)
555 .collect()
556 })
557 .unwrap_or_default(),
558 attributes: attributes
559 .iter()
560 .map(|attribute| (attribute.name.clone(), attribute.value.clone()))
561 .collect(),
562 element_index: 1,
563 element_count: 1,
564 type_index: 1,
565 type_count: 1,
566 previous_siblings: ElementChain::default(),
567 })
568}
569
570fn is_classic_javascript_type(script_type: &str) -> bool {
571 if script_type.is_empty() {
572 return true;
573 }
574
575 matches!(
576 script_type.to_ascii_lowercase().as_str(),
577 "text/javascript"
578 | "application/javascript"
579 | "text/ecmascript"
580 | "application/ecmascript"
581 | "application/x-javascript"
582 )
583}
584
585pub struct Parser<'a> {
586 tokenizer: Tokenizer<'a>,
587 tree: DomTree,
588 stack: Vec<Rc<RefCell<TreeNode<HtmlNodeType>>>>,
589 tag_stack: Vec<String>,
590 special_text_mode: Option<String>, scripting_mode: ScriptingMode,
592}
593
594impl<'a> Parser<'a> {
595 pub fn new(input: &'a str) -> Self {
596 let document = Tree::new(HtmlNodeType::Document);
597
598 Self {
599 tokenizer: Tokenizer::new(input),
600 tree: document.clone(),
601 stack: vec![document.root],
602 tag_stack: vec![],
603 special_text_mode: None,
604 scripting_mode: ScriptingMode::default(),
605 }
606 }
607
608 pub fn with_scripting_mode(mut self, mode: ScriptingMode) -> Self {
610 self.scripting_mode = mode;
611 self
612 }
613
614 pub fn parse(&mut self) -> DomTree {
615 while let Some(token) = self.tokenizer.next_token() {
616 log::debug!(target:"HtmlParser::Token" ,"Processing token: {token:?}");
617 match token {
618 Token::StartTag { .. } => self.handle_start_tag(token),
619 Token::EndTag { .. } => self.handle_end_tag(token),
620 Token::Doctype { .. } => self.handle_doctype(token),
621 Token::Comment(_) => self.handle_comment(token),
622 Token::Text(_) => self.handle_text(token),
623 }
624 }
625 self.autofill_elements();
626
627 self.tree.clone()
628 }
629
630 fn handle_start_tag(&mut self, token: Token) {
631 if let Token::StartTag {
632 name,
633 attributes,
634 self_closing,
635 } = token
636 {
637 let mut parent = Rc::clone(self.stack.last().unwrap());
638 if self.special_text_mode.is_some() {
639 TreeNode::add_child_value(&parent, HtmlNodeType::Text(format!("<{}>", name)));
642 return;
643 }
644
645 if matches!(name.as_str(), "tr" | "td" | "th")
649 && let Some(top) = self.stack.last()
650 && top
651 .borrow()
652 .value
653 .tag_name()
654 .is_some_and(|t| t.eq_ignore_ascii_case("table"))
655 {
656 let tbody = TreeNode::add_child_value(
657 &parent,
658 HtmlNodeType::Element {
659 tag_name: "tbody".to_string(),
660 attributes: Vec::new(),
661 },
662 );
663 self.tag_stack.push("tbody".to_string());
664 self.stack.push(tbody);
665 parent = Rc::clone(self.stack.last().unwrap());
666 }
667
668 if name == "noscript" {
670 self.handle_noscript(attributes);
671 return;
672 }
673
674 while self.check_start_tag_with_invalid_nesting(&name, &parent) {
675 if let HtmlNodeType::Element { tag_name, .. } = &parent.borrow().value {
676 log::info!(target:"HtmlParser::AutoClosing" ,"Auto-closing tag: <{}> to allow <{}> inside it.", tag_name, name);
677 self.handle_end_tag(Token::EndTag {
678 name: tag_name.clone(),
679 });
680 }
681 parent = Rc::clone(self.stack.last().unwrap());
682 }
683
684 let new_node = TreeNode::add_child_value(
685 &parent,
686 HtmlNodeType::Element {
687 tag_name: name.clone(),
688 attributes,
689 },
690 );
691
692 if name == "script" || name == "style" {
694 self.special_text_mode = Some(name.clone());
695 }
696
697 let is_void = matches!(
699 name.as_str(),
700 "area"
701 | "base"
702 | "br"
703 | "col"
704 | "embed"
705 | "hr"
706 | "img"
707 | "input"
708 | "link"
709 | "meta"
710 | "param"
711 | "source"
712 | "track"
713 | "wbr"
714 );
715 if !self_closing && !is_void {
717 self.tag_stack.push(name.clone());
718 self.stack.push(new_node);
719 log::debug!(target:"HtmlParser::Stack" ,"Stack len: {}, +Pushed <{}> to stack.", self.stack.len(), name);
720 }
721 }
722 }
723
724 fn handle_noscript(&mut self, attributes: Vec<Attribute>) {
725 let in_head = self
726 .stack
727 .last()
728 .and_then(|node| node.borrow().value.tag_name().map(str::to_string))
729 .is_some_and(|tag| tag.eq_ignore_ascii_case("head"));
730
731 if in_head {
732 self.handle_noscript_in_head(attributes);
733 } else {
734 self.handle_noscript_in_body(attributes);
735 }
736 }
737
738 fn handle_noscript_in_head(&mut self, attributes: Vec<Attribute>) {
742 match self.scripting_mode {
743 ScriptingMode::Enabled => self.parse_noscript_as_raw_text(attributes),
744 ScriptingMode::Disabled => self.parse_noscript_as_html(attributes),
745 }
746 }
747
748 fn handle_noscript_in_body(&mut self, attributes: Vec<Attribute>) {
751 match self.scripting_mode {
752 ScriptingMode::Enabled => self.parse_noscript_as_raw_text(attributes),
753 ScriptingMode::Disabled => self.parse_noscript_as_html(attributes),
754 }
755 }
756
757 fn parse_noscript_as_raw_text(&mut self, attributes: Vec<Attribute>) {
759 self.push_element("noscript", attributes);
760 self.special_text_mode = Some("noscript".to_string());
761 }
762
763 fn parse_noscript_as_html(&mut self, attributes: Vec<Attribute>) {
765 self.push_element("noscript", attributes);
766 }
767
768 fn push_element(&mut self, name: &str, attributes: Vec<Attribute>) {
770 let parent = Rc::clone(self.stack.last().unwrap());
771 let node = TreeNode::add_child_value(
772 &parent,
773 HtmlNodeType::Element {
774 tag_name: name.to_string(),
775 attributes,
776 },
777 );
778 self.tag_stack.push(name.to_string());
779 self.stack.push(node);
780 }
781
782 fn handle_end_tag(&mut self, token: Token) {
783 if let Token::EndTag { ref name } = token {
784 if self.special_text_mode.as_deref() == Some(name.as_str()) {
786 self.special_text_mode = None;
787 }
788
789 if self.special_text_mode.is_some() {
790 let parent = Rc::clone(self.stack.last().unwrap());
791 TreeNode::add_child_value(&parent, HtmlNodeType::Text(format!("</{}>", name)));
792 return;
793 }
794
795 let name = name.clone();
796 if self.tag_stack.contains(&name) {
797 while let Some(top) = self.stack.pop() {
798 if let HtmlNodeType::Element { tag_name, .. } = &top.borrow().value {
799 self.tag_stack.pop();
800 if tag_name == &name {
801 log::debug!(target:"HtmlParser::Stack" ,"Stack len: {}, -Popped </{}> from stack.", self.stack.len(), name);
802 break;
803 } else {
804 log::debug!(target:"HtmlParser::Stack" ,"Stack len: {}, Unmatched end tag: </{}>, Find <{}>", self.stack.len(), name, tag_name);
805 }
806 }
807 }
808 } else {
809 let parent = Rc::clone(self.stack.last().unwrap());
810 TreeNode::add_child_value(
811 &parent,
812 HtmlNodeType::InvalidNode(
813 token,
814 format!("No matching start tag for </{}>", name),
815 ),
816 );
817 log::debug!(target:"HtmlParser::Invalid" ,"Invalid end tag: </{}>", name);
818 }
819 }
820 }
821
822 fn handle_text(&mut self, token: Token) {
823 if let Token::Text(data) = token {
824 let parent = Rc::clone(self.stack.last().unwrap());
825
826 if self.special_text_mode.is_some() {
828 TreeNode::add_child_value(&parent, HtmlNodeType::Text(data));
829 return;
830 }
831
832 TreeNode::add_child_value(&parent, HtmlNodeType::Text(data));
833 }
834 }
835
836 fn handle_comment(&mut self, token: Token) {
837 if let Token::Comment(data) = token {
838 let parent = Rc::clone(self.stack.last().unwrap());
839 TreeNode::add_child_value(&parent, HtmlNodeType::Comment(data));
840 }
841 }
842
843 fn handle_doctype(&mut self, token: Token) {
844 if let Token::Doctype {
845 name,
846 public_id,
847 system_id,
848 ..
849 } = token
850 {
851 let parent = Rc::clone(self.stack.last().unwrap());
852 TreeNode::add_child_value(
853 &parent,
854 HtmlNodeType::Doctype {
855 name,
856 public_id,
857 system_id,
858 },
859 );
860 }
861 }
862
863 fn check_start_tag_with_invalid_nesting(
864 &self,
865 name: &String,
866 parent: &Rc<RefCell<TreeNode<HtmlNodeType>>>,
867 ) -> bool {
868 if let HtmlNodeType::Element { tag_name, .. } = &parent.borrow().value {
869 if tag_name != "html" && name == "body" {
871 println!("here we can see 「お行儀の悪いコード」");
872 return true;
873 }
874 if tag_name == "p" && name == "p" {
876 return true;
877 }
878 if tag_name == "li" && name == "li" {
880 return true;
881 }
882 if tag_name == "a" && name == "a" {
884 return true;
885 }
886 if tag_name == "dt" && (name == "dt" || name == "dd") {
888 return true;
889 }
890 if tag_name == "dd" && (name == "dt" || name == "dd") {
892 return true;
893 }
894 if tag_name == "option" && name == "option" {
896 return true;
897 }
898 if matches!(
900 tag_name.as_str(),
901 "p" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
902 ) && html_util::is_block_level_element(name)
903 {
904 return true;
905 }
906 }
907 false
908 }
909
910 fn autofill_elements(&mut self) {
912 let root = Rc::clone(&self.stack[0]);
913 let has_html = root
914 .borrow()
915 .children()
916 .iter()
917 .any(|c| matches!(&c.borrow().value, HtmlNodeType::Element { tag_name, .. } if tag_name.to_lowercase() == "html"));
918
919 if !has_html {
920 let mut doctype_node = None;
921 let mut orphan_nodes = Vec::new();
922 for child in root.borrow().children() {
923 match &child.borrow().value {
924 HtmlNodeType::Doctype { .. } => {
925 doctype_node = Some(Rc::clone(child));
926 }
927 _ => orphan_nodes.push(Rc::clone(child)),
928 }
929 }
930
931 root.borrow_mut().clear_children();
932
933 if let Some(dt) = doctype_node {
934 TreeNode::add_child(&root, dt);
935 } else {
936 TreeNode::add_child_value(
937 &root,
938 HtmlNodeType::Doctype {
939 name: Some("html".to_string()),
940 public_id: None,
941 system_id: None,
942 },
943 );
944 }
945
946 let html_node = TreeNode::add_child_value(
947 &root,
948 HtmlNodeType::Element {
949 tag_name: "html".to_string(),
950 attributes: vec![],
951 },
952 );
953
954 TreeNode::add_child_value(
955 &html_node,
956 HtmlNodeType::Element {
957 tag_name: "head".to_string(),
958 attributes: vec![],
959 },
960 );
961
962 let body_node = TreeNode::add_child_value(
963 &html_node,
964 HtmlNodeType::Element {
965 tag_name: "body".to_string(),
966 attributes: vec![],
967 },
968 );
969
970 for orphan in orphan_nodes {
971 TreeNode::add_child(&body_node, orphan);
972 }
973 }
974 }
975}