Skip to main content

rumdl_lib/utils/
html_block.rs

1//! Shared HTML block-start classification.
2//!
3//! A line whose first tag names one of these elements starts an HTML block in
4//! rumdl's parser (`lint_context::heading_detection::detect_html_blocks`).
5//! Reflow consults the same predicate so a wrapped line can never introduce a
6//! construct the parser would classify differently: the two must agree, and
7//! sharing the list keeps them from drifting apart.
8
9/// Type-1 tags per CommonMark: blank lines inside these blocks do not
10/// terminate them — only a matching end tag (or EOF) does.
11pub const TYPE_1_BLOCK_ELEMENTS: &[&str] = &["pre", "script", "style", "textarea"];
12
13/// HTML elements whose tags open an HTML block at line start (CommonMark
14/// type-1 and type-6 conditions, as recognized by rumdl's parser).
15pub const BLOCK_ELEMENTS: &[&str] = &[
16    "address",
17    "article",
18    "aside",
19    "audio",
20    "blockquote",
21    "canvas",
22    "details",
23    "dialog",
24    "dd",
25    "div",
26    "dl",
27    "dt",
28    "embed",
29    "fieldset",
30    "figcaption",
31    "figure",
32    "footer",
33    "form",
34    "h1",
35    "h2",
36    "h3",
37    "h4",
38    "h5",
39    "h6",
40    "header",
41    "hr",
42    "iframe",
43    "li",
44    "main",
45    "menu",
46    "nav",
47    "noscript",
48    "object",
49    "ol",
50    "p",
51    "picture",
52    "pre",
53    "script",
54    "search",
55    "section",
56    "source",
57    "style",
58    "summary",
59    "svg",
60    "table",
61    "tbody",
62    "td",
63    "template",
64    "textarea",
65    "tfoot",
66    "th",
67    "thead",
68    "tr",
69    "track",
70    "ul",
71    "video",
72];
73
74/// If `trimmed` (a line with leading whitespace already stripped) opens an
75/// HTML block per rumdl's parser, return the lowercased tag name and whether
76/// it is a closing tag. Returns `None` for text, autolinks, and inline-level
77/// tags (`<span>`, `<b>`, ...), which cannot interrupt a paragraph.
78pub fn parse_html_block_start(trimmed: &str) -> Option<(String, bool)> {
79    let after_bracket = trimmed.strip_prefix('<')?;
80    if after_bracket.is_empty() {
81        return None;
82    }
83    let is_closing = after_bracket.starts_with('/');
84    let tag_start = if is_closing { &after_bracket[1..] } else { after_bracket };
85
86    let tag_name = tag_start
87        .chars()
88        .take_while(|c| c.is_ascii_alphabetic() || *c == '-' || c.is_ascii_digit())
89        .collect::<String>()
90        .to_lowercase();
91
92    if !tag_name.is_empty() && BLOCK_ELEMENTS.contains(&tag_name.as_str()) {
93        Some((tag_name, is_closing))
94    } else {
95        None
96    }
97}