atmos/os_lib/
custom_elements.rs1use alloc::collections::BTreeMap;
7use alloc::string::String;
8use alloc::vec::Vec;
9
10const RESERVED_NAMES: &[&str] = &[
12 "annotation-xml",
13 "color-profile",
14 "font-face",
15 "font-face-src",
16 "font-face-uri",
17 "font-face-format",
18 "font-face-name",
19 "missing-glyph",
20];
21
22pub fn is_valid_custom_element_name(name: &str) -> bool {
31 if name.is_empty() {
32 return false;
33 }
34 let bytes = name.as_bytes();
35 if !bytes[0].is_ascii_lowercase() {
37 return false;
38 }
39 if !name.contains('-') {
41 return false;
42 }
43 if name.bytes().any(|b| b.is_ascii_uppercase()) {
45 return false;
46 }
47 if RESERVED_NAMES.contains(&name) {
49 return false;
50 }
51 for &b in bytes {
54 let valid = b.is_ascii_lowercase()
55 || b.is_ascii_digit()
56 || b == b'-'
57 || b == b'.'
58 || b == b'_';
59 if !valid {
60 return false;
61 }
62 }
63 true
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct CustomElementDefinition {
69 pub name: String,
70 pub observed_attributes: Vec<String>,
71}
72
73impl CustomElementDefinition {
74 pub fn new(name: String, observed_attributes: Vec<String>) -> Self {
75 Self {
76 name,
77 observed_attributes,
78 }
79 }
80
81 pub fn observes_attribute(&self, attr: &str) -> bool {
83 self.observed_attributes.iter().any(|a| a == attr)
84 }
85}
86
87#[derive(Debug, Clone, Default)]
89pub struct CustomElementRegistry {
90 definitions: BTreeMap<String, CustomElementDefinition>,
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub enum DefineError {
95 InvalidName,
96 AlreadyDefined,
97}
98
99impl CustomElementRegistry {
100 pub fn new() -> Self {
101 Self {
102 definitions: BTreeMap::new(),
103 }
104 }
105
106 pub fn define(
108 &mut self,
109 name: &str,
110 observed_attributes: Vec<String>,
111 ) -> Result<(), DefineError> {
112 if !is_valid_custom_element_name(name) {
113 return Err(DefineError::InvalidName);
114 }
115 if self.definitions.contains_key(name) {
116 return Err(DefineError::AlreadyDefined);
117 }
118 self.definitions.insert(
119 String::from(name),
120 CustomElementDefinition::new(String::from(name), observed_attributes),
121 );
122 Ok(())
123 }
124
125 pub fn get(&self, name: &str) -> Option<&CustomElementDefinition> {
127 self.definitions.get(name)
128 }
129
130 pub fn is_defined(&self, name: &str) -> bool {
132 self.definitions.contains_key(name)
133 }
134
135 pub fn matches_defined_pseudo_class(&self, tag_name: &str) -> bool {
140 if !tag_name.contains('-') {
141 return true;
143 }
144 self.is_defined(tag_name)
146 }
147
148 pub fn len(&self) -> usize {
150 self.definitions.len()
151 }
152
153 pub fn is_empty(&self) -> bool {
154 self.definitions.is_empty()
155 }
156}