1use rustc_hash::FxHashMap;
2use serde::{
3 Serialize,
4 ser::{SerializeMap, Serializer},
5};
6
7pub const MAX_TOC_LEVELS: u8 = 5;
8pub const MAX_SECTION_LEVELS: u8 = 5;
9
10#[derive(Debug, PartialEq, Clone)]
15struct AttributeMap {
16 all: FxHashMap<AttributeName, AttributeValue>,
18 explicit: FxHashMap<AttributeName, AttributeValue>,
20}
21
22impl Default for AttributeMap {
23 fn default() -> Self {
24 AttributeMap {
25 all: crate::constants::default_attributes(),
26 explicit: FxHashMap::default(), }
28 }
29}
30
31impl AttributeMap {
32 fn empty() -> Self {
33 AttributeMap {
34 all: FxHashMap::default(),
35 explicit: FxHashMap::default(),
36 }
37 }
38
39 fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
40 self.all.iter()
41 }
42
43 fn is_empty(&self) -> bool {
44 self.explicit.is_empty()
47 }
48
49 fn insert(&mut self, name: AttributeName, value: AttributeValue) {
50 if !self.contains_key(&name) {
51 self.all.insert(name.clone(), value.clone());
52 self.explicit.insert(name, value); }
54 }
55
56 fn set(&mut self, name: AttributeName, value: AttributeValue) {
57 self.all.insert(name.clone(), value.clone());
58 self.explicit.insert(name, value); }
60
61 fn get(&self, name: &str) -> Option<&AttributeValue> {
62 self.all.get(name)
63 }
64
65 fn contains_key(&self, name: &str) -> bool {
66 self.all.contains_key(name)
67 }
68
69 fn remove(&mut self, name: &str) -> Option<AttributeValue> {
70 self.explicit.remove(name);
71 self.all.remove(name)
72 }
73
74 fn merge(&mut self, other: AttributeMap) {
75 for (key, value) in other.all {
76 self.insert(key, value);
77 }
78 }
79}
80
81impl Serialize for AttributeMap {
82 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83 where
84 S: Serializer,
85 {
86 let mut sorted_keys: Vec<_> = self.explicit.keys().collect();
88 sorted_keys.sort();
89
90 let mut state = serializer.serialize_map(Some(self.explicit.len()))?;
91 for key in sorted_keys {
92 if let Some(value) = &self.explicit.get(key) {
93 match value {
94 AttributeValue::Bool(true) => {
95 if key == "toc" {
96 state.serialize_entry(key, "")?;
97 } else {
98 state.serialize_entry(key, &true)?;
99 }
100 }
101 value @ (AttributeValue::Bool(false)
102 | AttributeValue::String(_)
103 | AttributeValue::None) => {
104 state.serialize_entry(key, value)?;
105 }
106 }
107 }
108 }
109 state.end()
110 }
111}
112
113fn validate_bounded_attribute(key: &str, value: &AttributeValue) {
118 let AttributeValue::String(s) = value else {
119 return;
120 };
121
122 match key {
123 "sectnumlevels" => {
124 if let Ok(level) = s.parse::<u8>()
125 && level > MAX_SECTION_LEVELS
126 {
127 tracing::warn!(
128 attribute = "sectnumlevels",
129 value = level,
130 "sectnumlevels must be between 0 and {MAX_SECTION_LEVELS}, got {level}. \
131 Values above {MAX_SECTION_LEVELS} will be treated as {MAX_SECTION_LEVELS}."
132 );
133 }
134 }
135 "toclevels" => {
136 if let Ok(level) = s.parse::<u8>()
137 && level > MAX_TOC_LEVELS
138 {
139 tracing::warn!(
140 attribute = "toclevels",
141 value = level,
142 "toclevels must be between 0 and {MAX_TOC_LEVELS}, got {level}. \
143 Values above {MAX_TOC_LEVELS} will be treated as {MAX_TOC_LEVELS}."
144 );
145 }
146 }
147 _ => {}
148 }
149}
150
151#[derive(Debug, PartialEq, Clone, Default)]
158pub struct DocumentAttributes(AttributeMap);
159
160impl DocumentAttributes {
161 pub fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
163 self.0.iter()
164 }
165
166 #[must_use]
168 pub fn is_empty(&self) -> bool {
169 self.0.is_empty()
170 }
171
172 pub fn insert(&mut self, name: AttributeName, value: AttributeValue) {
176 validate_bounded_attribute(&name, &value);
177 self.0.insert(name, value);
178 }
179
180 pub fn set(&mut self, name: AttributeName, value: AttributeValue) {
182 validate_bounded_attribute(&name, &value);
183 self.0.set(name, value);
184 }
185
186 #[must_use]
188 pub fn get(&self, name: &str) -> Option<&AttributeValue> {
189 self.0.get(name)
190 }
191
192 #[must_use]
194 pub fn contains_key(&self, name: &str) -> bool {
195 self.0.contains_key(name)
196 }
197
198 pub fn remove(&mut self, name: &str) -> Option<AttributeValue> {
200 self.0.remove(name)
201 }
202
203 pub fn merge(&mut self, other: Self) {
205 self.0.merge(other.0);
206 }
207
208 #[must_use]
212 pub fn get_string(&self, name: &str) -> Option<String> {
213 self.get(name).and_then(|v| match v {
214 AttributeValue::String(s) => {
215 let trimmed = s.trim_matches('"');
217 Some(trimmed.to_string())
218 }
219 AttributeValue::None | AttributeValue::Bool(_) => None,
220 })
221 }
222}
223
224impl Serialize for DocumentAttributes {
225 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
226 where
227 S: Serializer,
228 {
229 self.0.serialize(serializer)
230 }
231}
232
233#[derive(Debug, PartialEq, Clone)]
239pub struct ElementAttributes(AttributeMap);
240
241impl Default for ElementAttributes {
242 fn default() -> Self {
243 ElementAttributes(AttributeMap::empty())
244 }
245}
246
247impl ElementAttributes {
248 pub fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
250 self.0.iter()
251 }
252
253 #[must_use]
255 pub fn is_empty(&self) -> bool {
256 self.0.is_empty()
257 }
258
259 pub fn insert(&mut self, name: AttributeName, value: AttributeValue) {
263 self.0.insert(name, value);
264 }
265
266 pub fn set(&mut self, name: AttributeName, value: AttributeValue) {
268 self.0.set(name, value);
269 }
270
271 #[must_use]
273 pub fn get(&self, name: &str) -> Option<&AttributeValue> {
274 self.0.get(name)
275 }
276
277 #[must_use]
279 pub fn contains_key(&self, name: &str) -> bool {
280 self.0.contains_key(name)
281 }
282
283 pub fn remove(&mut self, name: &str) -> Option<AttributeValue> {
285 self.0.remove(name)
286 }
287
288 pub fn merge(&mut self, other: Self) {
290 self.0.merge(other.0);
291 }
292
293 #[must_use]
297 pub fn get_string(&self, name: &str) -> Option<String> {
298 self.get(name).and_then(|v| match v {
299 AttributeValue::String(s) => {
300 let trimmed = s.trim_matches('"');
302 Some(trimmed.to_string())
303 }
304 AttributeValue::None | AttributeValue::Bool(_) => None,
305 })
306 }
307}
308
309impl Serialize for ElementAttributes {
310 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
311 where
312 S: Serializer,
313 {
314 self.0.serialize(serializer)
315 }
316}
317
318pub type AttributeName = String;
320
321#[derive(Clone, Debug, PartialEq, Serialize)]
325#[serde(untagged)]
326#[non_exhaustive]
327pub enum AttributeValue {
328 String(String),
330 Bool(bool),
332 None,
334}
335
336impl std::fmt::Display for AttributeValue {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 match self {
339 AttributeValue::String(value) => write!(f, "{value}"),
340 AttributeValue::Bool(value) => write!(f, "{value}"),
341 AttributeValue::None => write!(f, "null"),
342 }
343 }
344}
345
346impl From<&str> for AttributeValue {
347 fn from(value: &str) -> Self {
348 AttributeValue::String(value.to_string())
349 }
350}
351
352impl From<String> for AttributeValue {
353 fn from(value: String) -> Self {
354 AttributeValue::String(value)
355 }
356}
357
358impl From<bool> for AttributeValue {
359 fn from(value: bool) -> Self {
360 AttributeValue::Bool(value)
361 }
362}
363
364impl From<()> for AttributeValue {
365 fn from((): ()) -> Self {
366 AttributeValue::None
367 }
368}