orinium_browser/engine/html/
util.rs1use entities::{Codepoints, ENTITIES};
18use once_cell::sync::Lazy;
19use std::collections::HashMap;
20
21pub const MAX_ENTITY_NAME_LEN: usize = 31;
22
23static NAMED_ENTITIES: Lazy<HashMap<&'static str, String>> = Lazy::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 element_category(tag_name: &str) -> &'static str {
157 let tag = normalize(tag_name);
158 let t = tag.as_str();
159 if BLOCK_TAGS.contains(&t) {
160 "block"
161 } else if INLINE_TAGS.contains(&t) {
162 "inline"
163 } else if INLINE_BLOCK_TAGS.contains(&t) {
164 "inline-block"
165 } else if TABLEISH_TAGS.contains(&t) {
166 "table"
167 } else if OTHER_TAGS.contains(&t) {
168 "other"
169 } else {
170 "unknown"
171 }
172}
173
174pub fn is_block_level_element(tag_name: &str) -> bool {
177 matches!(element_category(tag_name), "block" | "table")
178}
179
180pub fn is_inline_element(tag_name: &str) -> bool {
182 matches!(element_category(tag_name), "inline")
183}