acdc_parser/model/
anchor.rs

1//! Anchor and reference types for `AsciiDoc` documents.
2
3use serde::{
4    Deserialize, Serialize,
5    de::{self, Deserializer, MapAccess, Visitor},
6    ser::{SerializeMap, Serializer},
7};
8
9use super::inlines::InlineNode;
10use super::location::Location;
11use super::title::Title;
12
13/// An `Anchor` represents an anchor in a document.
14///
15/// An anchor is a reference point in a document that can be linked to.
16#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
17#[non_exhaustive]
18pub struct Anchor {
19    pub id: String,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub xreflabel: Option<String>,
22    pub location: Location,
23}
24
25impl Anchor {
26    /// Create a new anchor with the given ID and location.
27    #[must_use]
28    pub fn new(id: String, location: Location) -> Self {
29        Self {
30            id,
31            xreflabel: None,
32            location,
33        }
34    }
35
36    /// Set the cross-reference label.
37    #[must_use]
38    pub fn with_xreflabel(mut self, xreflabel: Option<String>) -> Self {
39        self.xreflabel = xreflabel;
40        self
41    }
42}
43
44/// A `TocEntry` represents a table of contents entry.
45///
46/// This is collected during parsing from Section.
47#[derive(Clone, Debug, PartialEq)]
48#[non_exhaustive]
49pub struct TocEntry {
50    /// Unique identifier for this section (used for anchor links)
51    pub id: String,
52    /// Title of the section
53    pub title: Title,
54    /// Section level (1 for top-level, 2 for subsection, etc.)
55    pub level: u8,
56    /// Optional cross-reference label (from `[[id,xreflabel]]` syntax)
57    pub xreflabel: Option<String>,
58}
59
60impl Serialize for TocEntry {
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: Serializer,
64    {
65        let mut state = serializer.serialize_map(None)?;
66        state.serialize_entry("id", &self.id)?;
67        state.serialize_entry("title", &self.title)?;
68        state.serialize_entry("level", &self.level)?;
69        if self.xreflabel.is_some() {
70            state.serialize_entry("xreflabel", &self.xreflabel)?;
71        }
72        state.end()
73    }
74}
75
76impl<'de> Deserialize<'de> for TocEntry {
77    fn deserialize<D>(deserializer: D) -> Result<TocEntry, D::Error>
78    where
79        D: Deserializer<'de>,
80    {
81        struct TocEntryVisitor;
82
83        impl<'de> Visitor<'de> for TocEntryVisitor {
84            type Value = TocEntry;
85
86            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
87                formatter.write_str("struct TocEntry")
88            }
89
90            fn visit_map<V>(self, mut map: V) -> Result<TocEntry, V::Error>
91            where
92                V: MapAccess<'de>,
93            {
94                let mut id = None;
95                let mut title: Option<Vec<InlineNode>> = None;
96                let mut level = None;
97                let mut xreflabel = None;
98
99                while let Some(key) = map.next_key::<String>()? {
100                    match key.as_str() {
101                        "id" => id = Some(map.next_value()?),
102                        "title" => title = Some(map.next_value()?),
103                        "level" => level = Some(map.next_value()?),
104                        "xreflabel" => xreflabel = Some(map.next_value()?),
105                        _ => {
106                            let _ = map.next_value::<de::IgnoredAny>()?;
107                        }
108                    }
109                }
110
111                Ok(TocEntry {
112                    id: id.ok_or_else(|| de::Error::missing_field("id"))?,
113                    title: title.unwrap_or_default().into(),
114                    level: level.ok_or_else(|| de::Error::missing_field("level"))?,
115                    xreflabel,
116                })
117            }
118        }
119
120        deserializer.deserialize_map(TocEntryVisitor)
121    }
122}