Skip to main content

acdc_parser/model/
attributes.rs

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/// Strip surrounding single or double quotes from a string.
11///
12/// Attribute values in `AsciiDoc` can be quoted with either single or double quotes.
13/// This function strips the outermost matching quotes from both ends.
14#[must_use]
15pub fn strip_quotes(s: &str) -> &str {
16    s.trim_start_matches(['"', '\''])
17        .trim_end_matches(['"', '\''])
18}
19
20/// Internal shared implementation for both document and element attributes.
21///
22/// This type is not exported directly. Use `DocumentAttributes` for document-level
23/// attributes or `ElementAttributes` for element-level attributes.
24#[derive(Debug, PartialEq, Clone)]
25struct AttributeMap {
26    /// All attributes including defaults
27    all: FxHashMap<AttributeName, AttributeValue>,
28    /// Only explicitly set attributes (not defaults) - used for serialization
29    explicit: FxHashMap<AttributeName, AttributeValue>,
30}
31
32impl Default for AttributeMap {
33    fn default() -> Self {
34        AttributeMap {
35            all: crate::constants::default_attributes(),
36            explicit: FxHashMap::default(), // Defaults are not explicit
37        }
38    }
39}
40
41impl AttributeMap {
42    fn empty() -> Self {
43        AttributeMap {
44            all: FxHashMap::default(),
45            explicit: FxHashMap::default(),
46        }
47    }
48
49    fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
50        self.all.iter()
51    }
52
53    fn is_empty(&self) -> bool {
54        // We only consider explicit attributes for emptiness because defaults are always
55        // present.
56        self.explicit.is_empty()
57    }
58
59    fn insert(&mut self, name: AttributeName, value: AttributeValue) {
60        if !self.contains_key(&name) {
61            self.all.insert(name.clone(), value.clone());
62            self.explicit.insert(name, value); // Track as explicit
63        }
64    }
65
66    fn set(&mut self, name: AttributeName, value: AttributeValue) {
67        self.all.insert(name.clone(), value.clone());
68        self.explicit.insert(name, value); // Track as explicit
69    }
70
71    fn get(&self, name: &str) -> Option<&AttributeValue> {
72        self.all.get(name)
73    }
74
75    fn contains_key(&self, name: &str) -> bool {
76        self.all.contains_key(name)
77    }
78
79    fn remove(&mut self, name: &str) -> Option<AttributeValue> {
80        self.explicit.remove(name);
81        self.all.remove(name)
82    }
83
84    fn merge(&mut self, other: AttributeMap) {
85        for (key, value) in other.all {
86            self.insert(key, value);
87        }
88    }
89}
90
91impl Serialize for AttributeMap {
92    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
93    where
94        S: Serializer,
95    {
96        // Only serialize explicitly set attributes, not defaults
97        let mut sorted_keys: Vec<_> = self.explicit.keys().collect();
98        sorted_keys.sort();
99
100        let mut state = serializer.serialize_map(Some(self.explicit.len()))?;
101        for key in sorted_keys {
102            if let Some(value) = &self.explicit.get(key) {
103                match value {
104                    AttributeValue::Bool(true) => {
105                        if key == "toc" {
106                            state.serialize_entry(key, "")?;
107                        } else {
108                            state.serialize_entry(key, &true)?;
109                        }
110                    }
111                    value @ (AttributeValue::Bool(false)
112                    | AttributeValue::String(_)
113                    | AttributeValue::None) => {
114                        state.serialize_entry(key, value)?;
115                    }
116                }
117            }
118        }
119        state.end()
120    }
121}
122
123/// Validate bounded attributes and emit warnings for out-of-range values.
124///
125/// Some attributes like `sectnumlevels` and `toclevels` have valid ranges.
126/// This function emits a warning if the value is outside the valid range.
127fn validate_bounded_attribute(key: &str, value: &AttributeValue) {
128    let AttributeValue::String(s) = value else {
129        return;
130    };
131
132    match key {
133        "sectnumlevels" => {
134            if let Ok(level) = s.parse::<u8>()
135                && level > MAX_SECTION_LEVELS
136            {
137                tracing::warn!(
138                    attribute = "sectnumlevels",
139                    value = level,
140                    "sectnumlevels must be between 0 and {MAX_SECTION_LEVELS}, got {level}. \
141                         Values above {MAX_SECTION_LEVELS} will be treated as {MAX_SECTION_LEVELS}."
142                );
143            }
144        }
145        "toclevels" => {
146            if let Ok(level) = s.parse::<u8>()
147                && level > MAX_TOC_LEVELS
148            {
149                tracing::warn!(
150                    attribute = "toclevels",
151                    value = level,
152                    "toclevels must be between 0 and {MAX_TOC_LEVELS}, got {level}. \
153                         Values above {MAX_TOC_LEVELS} will be treated as {MAX_TOC_LEVELS}."
154                );
155            }
156        }
157        _ => {}
158    }
159}
160
161/// Document-level attributes with universal defaults.
162///
163/// These attributes apply to the entire document and include defaults for
164/// admonition captions, TOC settings, structural settings, etc.
165///
166/// Use `DocumentAttributes::default()` to get a map with universal defaults applied.
167#[derive(Debug, PartialEq, Clone, Default)]
168pub struct DocumentAttributes(AttributeMap);
169
170impl DocumentAttributes {
171    /// Iterate over all attributes.
172    pub fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
173        self.0.iter()
174    }
175
176    /// Check if the attribute map is empty.
177    #[must_use]
178    pub fn is_empty(&self) -> bool {
179        self.0.is_empty()
180    }
181
182    /// Insert a new attribute.
183    ///
184    /// NOTE: This will *NOT* overwrite an existing attribute with the same name.
185    pub fn insert(&mut self, name: AttributeName, value: AttributeValue) {
186        validate_bounded_attribute(&name, &value);
187        self.0.insert(name, value);
188    }
189
190    /// Set an attribute, overwriting any existing value.
191    pub fn set(&mut self, name: AttributeName, value: AttributeValue) {
192        validate_bounded_attribute(&name, &value);
193        self.0.set(name, value);
194    }
195
196    /// Get an attribute value by name.
197    #[must_use]
198    pub fn get(&self, name: &str) -> Option<&AttributeValue> {
199        self.0.get(name)
200    }
201
202    /// Check if an attribute exists.
203    #[must_use]
204    pub fn contains_key(&self, name: &str) -> bool {
205        self.0.contains_key(name)
206    }
207
208    /// Remove an attribute by name.
209    pub fn remove(&mut self, name: &str) -> Option<AttributeValue> {
210        self.0.remove(name)
211    }
212
213    /// Merge another attribute map into this one.
214    pub fn merge(&mut self, other: Self) {
215        self.0.merge(other.0);
216    }
217
218    /// Helper to get a string value.
219    ///
220    /// Strips surrounding quotes from the value if present (parser quirk workaround).
221    #[must_use]
222    pub fn get_string(&self, name: &str) -> Option<String> {
223        self.get(name).and_then(|v| match v {
224            AttributeValue::String(s) => Some(strip_quotes(s).to_string()),
225            AttributeValue::None | AttributeValue::Bool(_) => None,
226        })
227    }
228}
229
230impl Serialize for DocumentAttributes {
231    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232    where
233        S: Serializer,
234    {
235        self.0.serialize(serializer)
236    }
237}
238
239/// Element-level attributes (for blocks, sections, etc.).
240///
241/// These attributes are specific to individual elements and start empty.
242///
243/// Use `ElementAttributes::default()` to get an empty attribute map.
244#[derive(Debug, PartialEq, Clone)]
245pub struct ElementAttributes(AttributeMap);
246
247impl Default for ElementAttributes {
248    fn default() -> Self {
249        ElementAttributes(AttributeMap::empty())
250    }
251}
252
253impl ElementAttributes {
254    /// Iterate over all attributes.
255    pub fn iter(&self) -> impl Iterator<Item = (&AttributeName, &AttributeValue)> {
256        self.0.iter()
257    }
258
259    /// Check if the attribute map is empty.
260    #[must_use]
261    pub fn is_empty(&self) -> bool {
262        self.0.is_empty()
263    }
264
265    /// Insert a new attribute.
266    ///
267    /// NOTE: This will *NOT* overwrite an existing attribute with the same name.
268    pub fn insert(&mut self, name: AttributeName, value: AttributeValue) {
269        self.0.insert(name, value);
270    }
271
272    /// Set an attribute, overwriting any existing value.
273    pub fn set(&mut self, name: AttributeName, value: AttributeValue) {
274        self.0.set(name, value);
275    }
276
277    /// Get an attribute value by name.
278    #[must_use]
279    pub fn get(&self, name: &str) -> Option<&AttributeValue> {
280        self.0.get(name)
281    }
282
283    /// Check if an attribute exists.
284    #[must_use]
285    pub fn contains_key(&self, name: &str) -> bool {
286        self.0.contains_key(name)
287    }
288
289    /// Remove an attribute by name.
290    pub fn remove(&mut self, name: &str) -> Option<AttributeValue> {
291        self.0.remove(name)
292    }
293
294    /// Merge another attribute map into this one.
295    pub fn merge(&mut self, other: Self) {
296        self.0.merge(other.0);
297    }
298
299    /// Get a string attribute value as an owned `String`.
300    ///
301    /// Strips surrounding quotes from the value if present.
302    #[must_use]
303    pub fn get_string(&self, name: &str) -> Option<String> {
304        self.get(name).and_then(|v| match v {
305            AttributeValue::String(s) => Some(strip_quotes(s).to_string()),
306            AttributeValue::None | AttributeValue::Bool(_) => None,
307        })
308    }
309}
310
311impl Serialize for ElementAttributes {
312    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
313    where
314        S: Serializer,
315    {
316        self.0.serialize(serializer)
317    }
318}
319
320/// An `AttributeName` represents the name of an attribute in a document.
321pub type AttributeName = String;
322
323/// An `AttributeValue` represents the value of an attribute in a document.
324///
325/// An attribute value can be a string, a boolean, or nothing
326#[derive(Clone, Debug, PartialEq, Serialize)]
327#[serde(untagged)]
328#[non_exhaustive]
329pub enum AttributeValue {
330    /// A string attribute value.
331    String(String),
332    /// A boolean attribute value. `false` means it is unset.
333    Bool(bool),
334    /// No value (or it was unset)
335    None,
336}
337
338impl std::fmt::Display for AttributeValue {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        match self {
341            AttributeValue::String(value) => write!(f, "{value}"),
342            AttributeValue::Bool(value) => write!(f, "{value}"),
343            AttributeValue::None => write!(f, "null"),
344        }
345    }
346}
347
348impl From<&str> for AttributeValue {
349    fn from(value: &str) -> Self {
350        AttributeValue::String(value.to_string())
351    }
352}
353
354impl From<String> for AttributeValue {
355    fn from(value: String) -> Self {
356        AttributeValue::String(value)
357    }
358}
359
360impl From<bool> for AttributeValue {
361    fn from(value: bool) -> Self {
362        AttributeValue::Bool(value)
363    }
364}
365
366impl From<()> for AttributeValue {
367    fn from((): ()) -> Self {
368        AttributeValue::None
369    }
370}