1use std::borrow::Cow;
2
3use rustc_hash::FxHashMap;
4use serde::{
5 Serialize,
6 ser::{SerializeMap, Serializer},
7};
8
9pub const MAX_TOC_LEVELS: u8 = 5;
10pub const MAX_SECTION_LEVELS: u8 = 5;
11
12#[must_use]
17pub fn strip_quotes(s: &str) -> &str {
18 s.trim_start_matches(['"', '\''])
19 .trim_end_matches(['"', '\''])
20}
21
22#[derive(Debug, PartialEq, Clone)]
27struct AttributeMap<'a> {
28 all: FxHashMap<AttributeName<'a>, AttributeValue<'a>>,
30 explicit: FxHashMap<AttributeName<'a>, AttributeValue<'a>>,
32}
33
34impl Default for AttributeMap<'_> {
35 fn default() -> Self {
36 use std::sync::LazyLock;
37 static DEFAULTS: LazyLock<FxHashMap<AttributeName<'static>, AttributeValue<'static>>> =
43 LazyLock::new(|| {
44 crate::constants::DEFAULT_ATTRIBUTE_ENTRIES
45 .iter()
46 .cloned()
47 .collect()
48 });
49 AttributeMap {
50 all: DEFAULTS.clone(),
51 explicit: FxHashMap::default(), }
53 }
54}
55
56impl<'a> AttributeMap<'a> {
57 fn empty() -> Self {
58 AttributeMap {
59 all: FxHashMap::default(),
60 explicit: FxHashMap::default(),
61 }
62 }
63
64 fn iter(&self) -> impl Iterator<Item = (&AttributeName<'a>, &AttributeValue<'a>)> {
65 self.all.iter()
66 }
67
68 fn is_empty(&self) -> bool {
69 self.explicit.is_empty()
72 }
73
74 fn insert(&mut self, name: AttributeName<'a>, value: AttributeValue<'a>) {
75 if !self.contains_key(&name) {
76 self.all.insert(name.clone(), value.clone());
77 self.explicit.insert(name, value); }
79 }
80
81 fn set(&mut self, name: AttributeName<'a>, value: AttributeValue<'a>) {
82 self.all.insert(name.clone(), value.clone());
83 self.explicit.insert(name, value); }
85
86 fn get(&self, name: &str) -> Option<&AttributeValue<'a>> {
87 self.all.get(name)
88 }
89
90 fn contains_key(&self, name: &str) -> bool {
91 self.all.contains_key(name)
92 }
93
94 fn remove(&mut self, name: &str) -> Option<AttributeValue<'a>> {
95 self.explicit.remove(name);
96 self.all.remove(name)
97 }
98
99 fn merge(&mut self, other: AttributeMap<'a>) {
100 for (key, value) in other.all {
101 self.insert(key, value);
102 }
103 }
104}
105
106impl Serialize for AttributeMap<'_> {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: Serializer,
110 {
111 let mut sorted_keys: Vec<_> = self.explicit.keys().collect();
113 sorted_keys.sort();
114
115 let mut state = serializer.serialize_map(Some(self.explicit.len()))?;
116 for key in sorted_keys {
117 if let Some(value) = &self.explicit.get(key) {
118 match value {
119 AttributeValue::Bool(true) => {
120 if key == "toc" {
121 state.serialize_entry(key, "")?;
122 } else {
123 state.serialize_entry(key, &true)?;
124 }
125 }
126 value @ (AttributeValue::Bool(false)
127 | AttributeValue::String(_)
128 | AttributeValue::None) => {
129 state.serialize_entry(key, value)?;
130 }
131 }
132 }
133 }
134 state.end()
135 }
136}
137
138fn validate_bounded_attribute(key: &str, value: &AttributeValue<'_>) {
143 let AttributeValue::String(s) = value else {
144 return;
145 };
146
147 match key {
148 "sectnumlevels" => {
149 if let Ok(level) = s.parse::<u8>()
150 && level > MAX_SECTION_LEVELS
151 {
152 tracing::warn!(
153 attribute = "sectnumlevels",
154 value = level,
155 "sectnumlevels must be between 0 and {MAX_SECTION_LEVELS}, got {level}. \
156 Values above {MAX_SECTION_LEVELS} will be treated as {MAX_SECTION_LEVELS}."
157 );
158 }
159 }
160 "toclevels" => {
161 if let Ok(level) = s.parse::<u8>()
162 && level > MAX_TOC_LEVELS
163 {
164 tracing::warn!(
165 attribute = "toclevels",
166 value = level,
167 "toclevels must be between 0 and {MAX_TOC_LEVELS}, got {level}. \
168 Values above {MAX_TOC_LEVELS} will be treated as {MAX_TOC_LEVELS}."
169 );
170 }
171 }
172 _ => {}
173 }
174}
175
176#[derive(Debug, PartialEq, Clone, Default)]
183pub struct DocumentAttributes<'a>(AttributeMap<'a>);
184
185impl<'a> DocumentAttributes<'a> {
186 pub(crate) fn empty() -> Self {
190 Self(AttributeMap::empty())
191 }
192
193 pub fn iter(&self) -> impl Iterator<Item = (&AttributeName<'a>, &AttributeValue<'a>)> {
195 self.0.iter()
196 }
197
198 #[must_use]
200 pub fn is_empty(&self) -> bool {
201 self.0.is_empty()
202 }
203
204 pub fn insert(&mut self, name: AttributeName<'a>, value: AttributeValue<'a>) {
208 validate_bounded_attribute(&name, &value);
209 self.0.insert(name, value);
210 }
211
212 pub fn set(&mut self, name: AttributeName<'a>, value: AttributeValue<'a>) {
214 validate_bounded_attribute(&name, &value);
215 self.0.set(name, value);
216 }
217
218 #[must_use]
220 pub fn get(&self, name: &str) -> Option<&AttributeValue<'a>> {
221 self.0.get(name)
222 }
223
224 #[must_use]
226 pub fn contains_key(&self, name: &str) -> bool {
227 self.0.contains_key(name)
228 }
229
230 pub fn remove(&mut self, name: &str) -> Option<AttributeValue<'a>> {
232 self.0.remove(name)
233 }
234
235 pub fn merge(&mut self, other: Self) {
237 self.0.merge(other.0);
238 }
239
240 #[must_use]
244 pub fn get_string(&self, name: &str) -> Option<Cow<'a, str>> {
245 self.get(name).and_then(|v| match v {
246 AttributeValue::String(s) => Some(match s {
247 Cow::Borrowed(b) => Cow::Borrowed(strip_quotes(b)),
248 Cow::Owned(o) => Cow::Owned(strip_quotes(o).to_string()),
249 }),
250 AttributeValue::None | AttributeValue::Bool(_) => None,
251 })
252 }
253
254 #[must_use]
258 pub fn to_static(&self) -> DocumentAttributes<'static> {
259 self.clone().into_static()
260 }
261
262 #[must_use]
264 pub fn into_static(self) -> DocumentAttributes<'static> {
265 let convert_map = |map: FxHashMap<AttributeName<'a>, AttributeValue<'a>>| -> FxHashMap<AttributeName<'static>, AttributeValue<'static>> {
266 map.into_iter()
267 .map(|(k, v)| {
268 let key: AttributeName<'static> = Cow::Owned(k.into_owned());
269 let val = match v {
270 AttributeValue::String(s) => AttributeValue::String(Cow::Owned(s.into_owned())),
271 AttributeValue::Bool(b) => AttributeValue::Bool(b),
272 AttributeValue::None => AttributeValue::None,
273 };
274 (key, val)
275 })
276 .collect()
277 };
278 DocumentAttributes(AttributeMap {
279 all: convert_map(self.0.all),
280 explicit: convert_map(self.0.explicit),
281 })
282 }
283}
284
285impl Serialize for DocumentAttributes<'_> {
286 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
287 where
288 S: Serializer,
289 {
290 self.0.serialize(serializer)
291 }
292}
293
294#[derive(Debug, PartialEq, Clone)]
300pub struct ElementAttributes<'a>(AttributeMap<'a>);
301
302impl Default for ElementAttributes<'_> {
303 fn default() -> Self {
304 ElementAttributes(AttributeMap::empty())
305 }
306}
307
308impl<'a> ElementAttributes<'a> {
309 pub fn iter(&self) -> impl Iterator<Item = (&AttributeName<'a>, &AttributeValue<'a>)> {
311 self.0.iter()
312 }
313
314 #[must_use]
316 pub fn is_empty(&self) -> bool {
317 self.0.is_empty()
318 }
319
320 pub fn insert(&mut self, name: AttributeName<'a>, value: AttributeValue<'a>) {
324 self.0.insert(name, value);
325 }
326
327 pub fn set(&mut self, name: AttributeName<'a>, value: AttributeValue<'a>) {
329 self.0.set(name, value);
330 }
331
332 #[must_use]
334 pub fn get(&self, name: &str) -> Option<&AttributeValue<'a>> {
335 self.0.get(name)
336 }
337
338 #[must_use]
340 pub fn contains_key(&self, name: &str) -> bool {
341 self.0.contains_key(name)
342 }
343
344 pub fn remove(&mut self, name: &str) -> Option<AttributeValue<'a>> {
346 self.0.remove(name)
347 }
348
349 pub fn merge(&mut self, other: Self) {
351 self.0.merge(other.0);
352 }
353
354 #[must_use]
356 pub fn into_static(self) -> ElementAttributes<'static> {
357 let convert_map = |map: FxHashMap<AttributeName<'a>, AttributeValue<'a>>| -> FxHashMap<AttributeName<'static>, AttributeValue<'static>> {
358 map.into_iter()
359 .map(|(k, v)| {
360 let key: AttributeName<'static> = Cow::Owned(k.into_owned());
361 let val = match v {
362 AttributeValue::String(s) => AttributeValue::String(Cow::Owned(s.into_owned())),
363 AttributeValue::Bool(b) => AttributeValue::Bool(b),
364 AttributeValue::None => AttributeValue::None,
365 };
366 (key, val)
367 })
368 .collect()
369 };
370 ElementAttributes(AttributeMap {
371 all: convert_map(self.0.all),
372 explicit: convert_map(self.0.explicit),
373 })
374 }
375
376 #[must_use]
380 pub fn get_string(&self, name: &str) -> Option<Cow<'a, str>> {
381 self.get(name).and_then(|v| match v {
382 AttributeValue::String(s) => Some(match s {
383 Cow::Borrowed(b) => Cow::Borrowed(strip_quotes(b)),
384 Cow::Owned(o) => Cow::Owned(strip_quotes(o).to_string()),
385 }),
386 AttributeValue::None | AttributeValue::Bool(_) => None,
387 })
388 }
389}
390
391impl Serialize for ElementAttributes<'_> {
392 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
393 where
394 S: Serializer,
395 {
396 self.0.serialize(serializer)
397 }
398}
399
400pub type AttributeName<'a> = Cow<'a, str>;
402
403#[derive(Clone, Debug, PartialEq, Serialize)]
407#[serde(untagged)]
408#[non_exhaustive]
409pub enum AttributeValue<'a> {
410 String(Cow<'a, str>),
412 Bool(bool),
414 None,
416}
417
418impl std::fmt::Display for AttributeValue<'_> {
419 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420 match self {
421 AttributeValue::String(value) => write!(f, "{value}"),
422 AttributeValue::Bool(value) => write!(f, "{value}"),
423 AttributeValue::None => write!(f, "null"),
424 }
425 }
426}
427
428impl<'a> From<&'a str> for AttributeValue<'a> {
429 fn from(value: &'a str) -> Self {
430 AttributeValue::String(Cow::Borrowed(value))
431 }
432}
433
434impl From<String> for AttributeValue<'_> {
435 fn from(value: String) -> Self {
436 AttributeValue::String(Cow::Owned(value))
437 }
438}
439
440impl From<bool> for AttributeValue<'_> {
441 fn from(value: bool) -> Self {
442 AttributeValue::Bool(value)
443 }
444}
445
446impl From<()> for AttributeValue<'_> {
447 fn from((): ()) -> Self {
448 AttributeValue::None
449 }
450}