Skip to main content

orinium_browser/engine/html/
parser.rs

1//! HTMLパーサー。トークンストリームをDOMツリーに変換する。
2
3use 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    /// Shadow root attached to a host element.
30    /// Children live here and are not part of the host's normal children.
31    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    /// Processing instruction (e.g. `<?xml-stylesheet ...?>`).
42    ProcessingInstruction {
43        target: String,
44        data: String,
45    },
46    InvalidNode(Token, String), // 不正なトークン用
47}
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/// Source of a classic JavaScript script in document order.
101#[derive(Debug, Clone, PartialEq, Eq)]
102pub enum ClassicScriptSource {
103    Inline(String),
104    External(String),
105}
106
107/// Whether scripting is enabled while parsing.
108///
109/// Browsers run scripts by default, so the default mode is [`ScriptingMode::Enabled`].
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
111pub enum ScriptingMode {
112    #[default]
113    Enabled,
114    Disabled,
115}
116
117/// Scheduling mode selected by attributes on a classic script element.
118#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
119pub enum ClassicScriptExecution {
120    #[default]
121    Default,
122    Defer,
123    Async,
124}
125
126/// A classic script source together with its requested scheduling mode.
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub struct ClassicScriptDescriptor {
129    pub source: ClassicScriptSource,
130    pub execution: ClassicScriptExecution,
131}
132
133impl DomTree {
134    /// Returns all elements with the given tag name
135    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    /// Returns the element with the given id
146    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    /// Returns all elements that have the given class
161    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    /// Returns the concatenated text content of this node (including children).
174    /// Shadow root children are skipped (they are not part of light DOM text).
175    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                // Skip shadow root children — not part of light DOM.
183                .filter(|c| !matches!(c.borrow().value, HtmlNodeType::ShadowRoot { .. }))
184                .map(DomTree::inner_text)
185                .collect(),
186            HtmlNodeType::ShadowRoot { .. } => {
187                // Inside a shadow root, traverse its children.
188                n.children().iter().map(DomTree::inner_text).collect()
189            }
190            _ => "".to_string(),
191        }
192    }
193
194    /// Replace all text content of this node with the given string
195    pub fn set_text_content(node: &NodeRef<HtmlNodeType>, new_text: &str) {
196        // Do not hold a borrow across child mutations (would double-borrow).
197        if let HtmlNodeType::Text(content) = &mut node.borrow_mut().value {
198            *content = new_text.to_string();
199            return;
200        }
201
202        // `node.borrow()` must end before mutating below, so evaluate the
203        // condition in its own statement.
204        let is_element = matches!(node.borrow().value, HtmlNodeType::Element { .. });
205        if is_element {
206            // remove all children and add a single Text node
207            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    /// 指定したタグ名の要素のテキストノードをすべて集める
214    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    /// Collects classic scripts in document order.
243    ///
244    /// Module scripts and data blocks with a non-JavaScript MIME type are not
245    /// classic scripts and are ignored here.
246    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    /// Returns the first element matching `selector` in document order.
254    pub fn query_selector(&self, selector: &str) -> Option<NodeRef<HtmlNodeType>> {
255        self.query_selector_all(selector).into_iter().next()
256    }
257
258    /// Returns all elements matching `selector` in document order.
259    pub fn query_selector_all(&self, selector: &str) -> Vec<NodeRef<HtmlNodeType>> {
260        query_selector_all_from(&self.root, selector, true)
261    }
262
263    /// Returns the first matching descendant of `scope` in document order.
264    ///
265    /// The scope element itself is not considered, matching Element's DOM API.
266    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    /// Returns all matching descendants of `scope` in document order.
276    ///
277    /// The scope element itself is not included in the result.
278    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    /// Returns `true` if the element itself matches the given CSS selector.
286    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    /// Walks ancestors starting from `node` and returns the first ancestor
296    /// (including the node itself) that matches the given CSS selector.
297    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            // Stop at shadow boundary — closest() should not cross it.
309            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    /// Collects classic scripts and their scheduling attributes in document order.
323    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                        // `async` and `defer` have no effect on inline classic scripts.
348                        execution: ClassicScriptExecution::Default,
349                    }),
350                }
351            })
352            .collect()
353    }
354
355    /// Collects only inline classic scripts.
356    ///
357    /// Kept for callers that do not yet fetch external script resources.
358    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    // Shadow root children are inside the shadow tree — only
428    // reachable through the shadow root, not through the host element's
429    // light DOM traversal.  Skip them when traversing light DOM.
430    if !is_shadow {
431        for child in children {
432            // Skip shadow root children during light DOM traversal.
433            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        // We're inside a shadow root — do traverse its children
440        // (shadow tree is traversable from within).
441        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        // Stop at shadow boundary — element chains should not cross it.
452        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>, // script/style/noscript 用
591    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    /// Sets the scripting mode used while parsing `<noscript>` contents.
609    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                // TODO:
640                // attributes, self_closing
641                TreeNode::add_child_value(&parent, HtmlNodeType::Text(format!("<{}>", name)));
642                return;
643            }
644
645            // Table-context auto-insertion: a <tbody> is implied around
646            // <tr>/<td>/<th> (and sections around <caption>/<col>/<colgroup>)
647            // inserted directly into a <table>.
648            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            // noscript は scripting フラグに応じて特別な処理を行う
669            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            // script/style は special mode に
693            if name == "script" || name == "style" {
694                self.special_text_mode = Some(name.clone());
695            }
696
697            // HTML の void 要素は自行終了扱い(stack に push しない)
698            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            // Self-closing タグは stack に push しない
716            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    /// 現時点では body と同じ扱い(scripting 有効なら raw text)で、
739    /// spec の "in head noscript" 挿入モード(link/meta/style の処理など)は
740    /// head の挿入モードを導入した際に実装する。
741    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    /// scripting 有効なら raw text
749    /// scripting 無効なら 通常の HTML
750    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    /// `<noscript>` の内容を raw text としてパースする
758    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    /// `<noscript>` の内容を通常の HTML としてパースする
764    fn parse_noscript_as_html(&mut self, attributes: Vec<Attribute>) {
765        self.push_element("noscript", attributes);
766    }
767
768    /// 要素を生成して stack に push する。
769    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            // special mode を解除
785            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            // special mode 中はそのままテキスト追加
827            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            // <html> 以外の中に <body> が来た場合、そのタグを閉じる
870            if tag_name != "html" && name == "body" {
871                println!("here we can see 「お行儀の悪いコード」");
872                return true;
873            }
874            // <p> の中に <p> が来た場合、前の <p> を閉じる
875            if tag_name == "p" && name == "p" {
876                return true;
877            }
878            // <li> の中に <li> が来た場合、前の <li> を閉じる
879            if tag_name == "li" && name == "li" {
880                return true;
881            }
882            // <a> の中に <a> が来た場合、前の <a> を閉じる
883            if tag_name == "a" && name == "a" {
884                return true;
885            }
886            // <dt> の中に <dt> または <dd> が来た場合、前の <dt> を閉じる
887            if tag_name == "dt" && (name == "dt" || name == "dd") {
888                return true;
889            }
890            // <dd> の中に <dt> または <dd> が来た場合、前の <dd> を閉じる
891            if tag_name == "dd" && (name == "dt" || name == "dd") {
892                return true;
893            }
894            // <option> の中に <option> が来た場合、前の <option> を閉じる
895            if tag_name == "option" && name == "option" {
896                return true;
897            }
898            // <p> の中にブロック要素が来た場合、前の <p> を閉じる
899            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    /// DOCTYPE宣言、html, head, body 要素が存在しない場合に補完する
911    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}