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
21static NAMED_ENTITIES: Lazy<HashMap<&'static str, String>> = Lazy::new(|| {
22    let mut map = HashMap::new();
23    for ent in ENTITIES.iter() {
24        let key = ent.entity.trim_start_matches('&').trim_end_matches(';');
25        // Codepoints をマッチさせて String に変換
26        let value = match ent.codepoints {
27            Codepoints::Single(cp) => char::from_u32(cp)
28                .map(|c| c.to_string())
29                .unwrap_or_default(),
30            Codepoints::Double(cp1, cp2) => {
31                let mut s = String::new();
32                if let Some(c1) = char::from_u32(cp1) {
33                    s.push(c1);
34                }
35                if let Some(c2) = char::from_u32(cp2) {
36                    s.push(c2);
37                }
38                s
39            }
40        };
41        map.insert(key, value);
42    }
43    map
44});
45
46pub fn decode_entity(entity: &str) -> Option<String> {
47    if let Some(val) = NAMED_ENTITIES.get(entity) {
48        return Some(val.clone());
49    }
50
51    if entity.starts_with("#x") || entity.starts_with("#X") {
52        return u32::from_str_radix(&entity[2..], 16)
53            .ok()
54            .and_then(char::from_u32)
55            .map(|c| c.to_string());
56    }
57
58    if let Some(entity_number) = entity.strip_prefix('#') {
59        return entity_number
60            .parse::<u32>()
61            .ok()
62            .and_then(char::from_u32)
63            .map(|c| c.to_string());
64    }
65
66    None
67}
68
69fn normalize(tag_name: &str) -> String {
70    tag_name.trim().to_ascii_lowercase()
71}
72
73/// 内部カテゴリ配列(重複なし)
74/// - block: 通常 `display:block` またはブロックに準ずる振る舞い(p, div, h1.. など)
75/// - inline: 通常 `display:inline`(a, span, em, img 等)
76/// - inline_block: 通常 `display:inline-block` / replaced inline-block(button, select など)
77/// - tableish: table 系(display: table / table-row / table-cell など)
78/// - other: 上のどれにも該当しない雑多な要素
79const BLOCK_TAGS: &[&str] = &[
80    // セクショナル / グループ
81    "html",
82    "body",
83    "main",
84    "header",
85    "footer",
86    "section",
87    "nav",
88    "article",
89    "aside",
90    // 見出し
91    "h1",
92    "h2",
93    "h3",
94    "h4",
95    "h5",
96    "h6",
97    // 段落系
98    "p",
99    "pre",
100    "blockquote",
101    "address",
102    "hr",
103    // レイアウト・グループ
104    "div",
105    "fieldset",
106    "figure",
107    "figcaption",
108    "details",
109    "summary",
110    // リスト系(li/dt/dd は list-item / block-like)
111    "ul",
112    "ol",
113    "li",
114    "dl",
115    "dt",
116    "dd",
117    // フォーム系(幅取りがある要素をブロック扱いしたい場合に含めるがここでは block扱い)
118    "form",
119    "textarea",
120    // 埋め込み(ブロック的に扱われることが多いが厳密には元の display を参照)
121    "iframe",
122    "canvas",
123    "object",
124    "embed",
125];
126
127const INLINE_TAGS: &[&str] = &[
128    // テキスト系
129    "a", "span", "em", "strong", "b", "i", "u", "small", "sub", "sup", "mark", "code", "q", "cite",
130    "time", "var", "samp", "kbd", "dfn",
131    // 画像・改行等(img は UA stylesheet では inline と定義される)
132    "img", "br", "wbr", // フォーム系の一部(input は通常 inline)
133    "input", "label",
134];
135
136const INLINE_BLOCK_TAGS: &[&str] = &[
137    // ボタンやセレクト類はブラウザによって inline-block 規定が多い
138    // 明確に inline-block として扱いたい要素をここに分離
139    "button", "select", "option",
140];
141
142const TABLEISH_TAGS: &[&str] = &[
143    // 表関連は table 系独特の display を持つため別カテゴリ
144    "table", "thead", "tbody", "tfoot", "tr", "td", "th", "caption", "colgroup", "col",
145];
146
147const OTHER_TAGS: &[&str] = &[
148    // 上のどれにも入れなかった代表的要素
149    "svg", // svg は通常 inline だが独自挙動のため other に分離してもよい
150];
151
152/// 要素の「カテゴリ文字列」を返すユーティリティ(テスト・デバッグ用)
153/// 戻り値: "block" | "inline" | "inline-block" | "table" | "other" | "unknown"
154pub fn element_category(tag_name: &str) -> &'static str {
155    let tag = normalize(tag_name);
156    let t = tag.as_str();
157    if BLOCK_TAGS.contains(&t) {
158        "block"
159    } else if INLINE_TAGS.contains(&t) {
160        "inline"
161    } else if INLINE_BLOCK_TAGS.contains(&t) {
162        "inline-block"
163    } else if TABLEISH_TAGS.contains(&t) {
164        "table"
165    } else if OTHER_TAGS.contains(&t) {
166        "other"
167    } else {
168        "unknown"
169    }
170}
171
172// 互換性のための関数:
173/// - is_block_level_element は "block" と "table" を block-like として true を返す
174pub fn is_block_level_element(tag_name: &str) -> bool {
175    matches!(element_category(tag_name), "block" | "table")
176}
177
178/// - is_inline_element は "inline" のみ true を返す(inline-block は false)
179pub fn is_inline_element(tag_name: &str) -> bool {
180    matches!(element_category(tag_name), "inline")
181}