orinium_browser/engine/html/
util.rs1use entities::{Codepoints, ENTITIES};
18use std::collections::HashMap;
19use std::sync::LazyLock;
20
21pub const MAX_ENTITY_NAME_LEN: usize = 31;
22
23static NAMED_ENTITIES: LazyLock<HashMap<&'static str, String>> = LazyLock::new(|| {
24 let mut map = HashMap::new();
25 for ent in ENTITIES.iter() {
26 let key = ent.entity.trim_start_matches('&').trim_end_matches(';');
27 let value = match ent.codepoints {
29 Codepoints::Single(cp) => char::from_u32(cp)
30 .map(|c| c.to_string())
31 .unwrap_or_default(),
32 Codepoints::Double(cp1, cp2) => {
33 let mut s = String::new();
34 if let Some(c1) = char::from_u32(cp1) {
35 s.push(c1);
36 }
37 if let Some(c2) = char::from_u32(cp2) {
38 s.push(c2);
39 }
40 s
41 }
42 };
43 map.insert(key, value);
44 }
45 map
46});
47
48pub fn decode_entity(entity: &str) -> Option<String> {
49 if let Some(val) = NAMED_ENTITIES.get(entity) {
50 return Some(val.clone());
51 }
52
53 if entity.starts_with("#x") || entity.starts_with("#X") {
54 return u32::from_str_radix(&entity[2..], 16)
55 .ok()
56 .and_then(char::from_u32)
57 .map(|c| c.to_string());
58 }
59
60 if let Some(entity_number) = entity.strip_prefix('#') {
61 return entity_number
62 .parse::<u32>()
63 .ok()
64 .and_then(char::from_u32)
65 .map(|c| c.to_string());
66 }
67
68 None
69}
70
71fn normalize(tag_name: &str) -> String {
72 tag_name.trim().to_ascii_lowercase()
73}
74
75const BLOCK_TAGS: &[&str] = &[
82 "html",
84 "body",
85 "main",
86 "header",
87 "footer",
88 "section",
89 "nav",
90 "article",
91 "aside",
92 "h1",
94 "h2",
95 "h3",
96 "h4",
97 "h5",
98 "h6",
99 "p",
101 "pre",
102 "blockquote",
103 "address",
104 "hr",
105 "div",
107 "fieldset",
108 "figure",
109 "figcaption",
110 "details",
111 "summary",
112 "ul",
114 "ol",
115 "li",
116 "dl",
117 "dt",
118 "dd",
119 "form",
121 "textarea",
122 "iframe",
124 "canvas",
125 "object",
126 "embed",
127];
128
129const INLINE_TAGS: &[&str] = &[
130 "a", "span", "em", "strong", "b", "i", "u", "small", "sub", "sup", "mark", "code", "q", "cite",
132 "time", "var", "samp", "kbd", "dfn",
133 "img", "br", "wbr", "input", "label",
136];
137
138const INLINE_BLOCK_TAGS: &[&str] = &[
139 "button", "select", "option",
142];
143
144const TABLEISH_TAGS: &[&str] = &[
145 "table", "thead", "tbody", "tfoot", "tr", "td", "th", "caption", "colgroup", "col",
147];
148
149const OTHER_TAGS: &[&str] = &[
150 "svg", ];
153
154pub fn is_html_space(character: char) -> bool {
160 matches!(character, '\t' | '\n' | '\x0C' | '\r' | ' ')
161}
162
163pub fn split_html_space(value: &str) -> impl Iterator<Item = &str> {
165 value.split(is_html_space).filter(|token| !token.is_empty())
166}
167
168pub fn element_category(tag_name: &str) -> &'static str {
171 let tag = normalize(tag_name);
172 let t = tag.as_str();
173 if BLOCK_TAGS.contains(&t) {
174 "block"
175 } else if INLINE_TAGS.contains(&t) {
176 "inline"
177 } else if INLINE_BLOCK_TAGS.contains(&t) {
178 "inline-block"
179 } else if TABLEISH_TAGS.contains(&t) {
180 "table"
181 } else if OTHER_TAGS.contains(&t) {
182 "other"
183 } else {
184 "unknown"
185 }
186}
187
188pub fn is_block_level_element(tag_name: &str) -> bool {
191 matches!(element_category(tag_name), "block" | "table")
192}
193
194pub fn is_inline_element(tag_name: &str) -> bool {
196 matches!(element_category(tag_name), "inline")
197}