Skip to main content

orinium_browser/engine/html/
util.rs

1//! # HTML関連のユーティリティ関数群
2//! ## タグのカテゴリ分けと判定
3//! - タグを明確にカテゴリ分け(block / inline / inline-block / table-ish / other)
4//! - 重複が起きないように定義し、判定関数は既存の名前で使えるようにしている
5//!   - element_category, is_block_level_element, is_inline_element
6//!
7//! 注:
8//! - 「デフォルトのUA stylesheet による display の振る舞い」を基準に簡易判定しています。
9//! - CSS による display の上書きやカスタム要素は考慮していません。
10//! - 必要に応じてカテゴリやタグの追加・調整をしてください。
11//!
12//! ## htmlエスケープ処理
13//! - 基本的なHTMLエスケープ文字列をデコードする関数を提供
14//!   - decode_entity
15//!
16
17use 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        // Codepoints をマッチさせて String に変換
28        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
75/// 内部カテゴリ配列(重複なし)
76/// - block: 通常 `display:block` またはブロックに準ずる振る舞い(p, div, h1.. など)
77/// - inline: 通常 `display:inline`(a, span, em, img 等)
78/// - inline_block: 通常 `display:inline-block` / replaced inline-block(button, select など)
79/// - tableish: table 系(display: table / table-row / table-cell など)
80/// - other: 上のどれにも該当しない雑多な要素
81const BLOCK_TAGS: &[&str] = &[
82    // セクショナル / グループ
83    "html",
84    "body",
85    "main",
86    "header",
87    "footer",
88    "section",
89    "nav",
90    "article",
91    "aside",
92    // 見出し
93    "h1",
94    "h2",
95    "h3",
96    "h4",
97    "h5",
98    "h6",
99    // 段落系
100    "p",
101    "pre",
102    "blockquote",
103    "address",
104    "hr",
105    // レイアウト・グループ
106    "div",
107    "fieldset",
108    "figure",
109    "figcaption",
110    "details",
111    "summary",
112    // リスト系(li/dt/dd は list-item / block-like)
113    "ul",
114    "ol",
115    "li",
116    "dl",
117    "dt",
118    "dd",
119    // フォーム系(幅取りがある要素をブロック扱いしたい場合に含めるがここでは block扱い)
120    "form",
121    "textarea",
122    // 埋め込み(ブロック的に扱われることが多いが厳密には元の display を参照)
123    "iframe",
124    "canvas",
125    "object",
126    "embed",
127];
128
129const INLINE_TAGS: &[&str] = &[
130    // テキスト系
131    "a", "span", "em", "strong", "b", "i", "u", "small", "sub", "sup", "mark", "code", "q", "cite",
132    "time", "var", "samp", "kbd", "dfn",
133    // 画像・改行等(img は UA stylesheet では inline と定義される)
134    "img", "br", "wbr", // フォーム系の一部(input は通常 inline)
135    "input", "label",
136];
137
138const INLINE_BLOCK_TAGS: &[&str] = &[
139    // ボタンやセレクト類はブラウザによって inline-block 規定が多い
140    // 明確に inline-block として扱いたい要素をここに分離
141    "button", "select", "option",
142];
143
144const TABLEISH_TAGS: &[&str] = &[
145    // 表関連は table 系独特の display を持つため別カテゴリ
146    "table", "thead", "tbody", "tfoot", "tr", "td", "th", "caption", "colgroup", "col",
147];
148
149const OTHER_TAGS: &[&str] = &[
150    // 上のどれにも入れなかった代表的要素
151    "svg", // svg は通常 inline だが独自挙動のため other に分離してもよい
152];
153
154/// 要素の「カテゴリ文字列」を返すユーティリティ(テスト・デバッグ用)
155/// 戻り値: "block" | "inline" | "inline-block" | "table" | "other" | "unknown"
156pub 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
174// 互換性のための関数:
175/// - is_block_level_element は "block" と "table" を block-like として true を返す
176pub fn is_block_level_element(tag_name: &str) -> bool {
177    matches!(element_category(tag_name), "block" | "table")
178}
179
180/// - is_inline_element は "inline" のみ true を返す(inline-block は false)
181pub fn is_inline_element(tag_name: &str) -> bool {
182    matches!(element_category(tag_name), "inline")
183}