Skip to main content

atmos/os_lib/
dom_dataset.rs

1//! W3C HTML Living Standard §7.3.3 / DOMStringMap 準拠
2//! DOM dataset(data-* 属性 ↔ camelCase プロパティ)相互変換および
3//! 双方向バインディングの純粋ロジックモジュール。
4
5#![allow(dead_code)]
6
7use alloc::collections::BTreeMap;
8use alloc::string::String;
9use alloc::string::ToString;
10use alloc::vec::Vec;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum DatasetError {
14    /// プロパティ名にハイフンの直後の ASCII 小文字が含まれている(SyntaxError DOMException 相当)
15    InvalidHyphenFollowedByLowerCase(String),
16}
17
18/// camelCase の dataset プロパティ名から `data-*` 属性名への変換。
19/// HTML Living Standard §7.3.3
20/// 
21/// 1. プロパティ名に `-` 直後の小文字が含まれている場合は SyntaxError 例外(Err)。
22/// 2. 各 ASCII 大文字の直前に `-` を挿入し、その文字を小文字に変換する。
23/// 3. 先頭に `"data-"` を付加する。
24pub fn camel_to_data_attr(prop_name: &str) -> Result<String, DatasetError> {
25    let bytes = prop_name.as_bytes();
26    for i in 0..bytes.len().saturating_sub(1) {
27        if bytes[i] == b'-' && bytes[i + 1].is_ascii_lowercase() {
28            return Err(DatasetError::InvalidHyphenFollowedByLowerCase(prop_name.to_string()));
29        }
30    }
31
32    let mut out = String::with_capacity(prop_name.len() + 5);
33    out.push_str("data-");
34    for c in prop_name.chars() {
35        if c.is_ascii_uppercase() {
36            out.push('-');
37            out.push(c.to_ascii_lowercase());
38        } else {
39            out.push(c);
40        }
41    }
42    Ok(out)
43}
44
45/// `data-*` 属性名から camelCase の dataset プロパティ名への変換。
46/// HTML Living Standard §7.3.3
47/// 
48/// 1. 先頭が `"data-"` でなければ None。
49/// 2. `"data-"` を取り除く。
50/// 3. ハイフン (`-`) の直後が ASCII 小文字 (`'a'..='z'`) の場合のみ、
51///    ハイフンを取り除き小文字を大文字へ変換する。
52///    数字や大文字、記号、末尾ハイフンの場合はハイフンを維持する。
53pub fn data_attr_to_camel(attr_name: &str) -> Option<String> {
54    let s = attr_name.strip_prefix("data-")?;
55    let chars: Vec<char> = s.chars().collect();
56    let mut out = String::with_capacity(s.len());
57    let mut i = 0;
58    while i < chars.len() {
59        if chars[i] == '-' && i + 1 < chars.len() && chars[i + 1].is_ascii_lowercase() {
60            out.push(chars[i + 1].to_ascii_uppercase());
61            i += 2;
62        } else {
63            out.push(chars[i]);
64            i += 1;
65        }
66    }
67    Some(out)
68}
69
70/// 属性マップから `data-*` 属性を抽出し、camelCase プロパティ名と値のマップを生成する。
71pub fn extract_dataset(attrs: &BTreeMap<String, String>) -> BTreeMap<String, String> {
72    let mut result = BTreeMap::new();
73    for (key, val) in attrs {
74        if let Some(prop) = data_attr_to_camel(key) {
75            result.insert(prop, val.clone());
76        }
77    }
78    result
79}
80
81/// 属性マップに対して dataset プロパティを設定し、対応する `data-*` 属性名を返す。
82pub fn set_dataset_property(
83    attrs: &mut BTreeMap<String, String>,
84    prop_name: &str,
85    value: &str,
86) -> Result<String, DatasetError> {
87    let attr_name = camel_to_data_attr(prop_name)?;
88    attrs.insert(attr_name.clone(), value.to_string());
89    Ok(attr_name)
90}
91
92/// 属性マップから dataset プロパティを削除し、以前の値があれば返す。
93pub fn remove_dataset_property(
94    attrs: &mut BTreeMap<String, String>,
95    prop_name: &str,
96) -> Result<Option<String>, DatasetError> {
97    let attr_name = camel_to_data_attr(prop_name)?;
98    Ok(attrs.remove(&attr_name))
99}