acdc_parser/model/inlines/
text.rs

1use serde::{
2    Deserialize, Serialize,
3    de::{self, MapAccess, Visitor},
4    ser::{SerializeMap, Serializer},
5};
6
7use crate::{Location, Role};
8
9use super::InlineNode;
10
11/// The form of an inline formatting element (how it was expressed in the source)
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14pub enum Form {
15    Constrained,
16    Unconstrained,
17}
18
19/// A `Subscript` represents a subscript section of text in a document.
20#[derive(Clone, Debug, PartialEq, Deserialize)]
21pub struct Subscript {
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub role: Option<Role>,
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub id: Option<String>,
26    pub form: Form,
27    #[serde(rename = "inlines")]
28    pub content: Vec<InlineNode>,
29    pub location: Location,
30}
31
32/// A `Superscript` represents a superscript section of text in a document.
33#[derive(Clone, Debug, PartialEq, Deserialize)]
34pub struct Superscript {
35    #[serde(default, skip_serializing_if = "Option::is_none")]
36    pub role: Option<Role>,
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub id: Option<String>,
39    pub form: Form,
40    #[serde(rename = "inlines")]
41    pub content: Vec<InlineNode>,
42    pub location: Location,
43}
44
45/// A `CurvedQuotation` represents a curved quotation section of text in a document.
46#[derive(Clone, Debug, PartialEq, Deserialize)]
47pub struct CurvedQuotation {
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub role: Option<Role>,
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub id: Option<String>,
52    pub form: Form,
53    #[serde(rename = "inlines")]
54    pub content: Vec<InlineNode>,
55    pub location: Location,
56}
57
58/// A `CurvedApostrophe` represents a curved apostrophe section of text in a document.
59#[derive(Clone, Debug, PartialEq, Deserialize)]
60pub struct CurvedApostrophe {
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub role: Option<Role>,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub id: Option<String>,
65    pub form: Form,
66    #[serde(rename = "inlines")]
67    pub content: Vec<InlineNode>,
68    pub location: Location,
69}
70
71/// A `StandaloneCurvedApostrophe` represents a standalone curved apostrophe character.
72#[derive(Clone, Debug, PartialEq, Deserialize)]
73pub struct StandaloneCurvedApostrophe {
74    pub location: Location,
75}
76
77/// A `Monospace` represents a monospace section of text in a document.
78#[derive(Clone, Debug, PartialEq, Deserialize)]
79pub struct Monospace {
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub role: Option<Role>,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub id: Option<String>,
84    pub form: Form,
85    #[serde(rename = "inlines")]
86    pub content: Vec<InlineNode>,
87    pub location: Location,
88}
89
90/// A `Highlight` represents a highlighted section of text in a document.
91#[derive(Clone, Debug, PartialEq, Deserialize)]
92pub struct Highlight {
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub role: Option<Role>,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub id: Option<String>,
97    pub form: Form,
98    #[serde(rename = "inlines")]
99    pub content: Vec<InlineNode>,
100    pub location: Location,
101}
102
103/// A `Bold` represents a bold section of text in a document.
104#[derive(Clone, Debug, PartialEq, Deserialize)]
105pub struct Bold {
106    #[serde(default, skip_serializing_if = "Option::is_none")]
107    pub role: Option<Role>,
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub id: Option<String>,
110    pub form: Form,
111    #[serde(rename = "inlines")]
112    pub content: Vec<InlineNode>,
113    pub location: Location,
114}
115
116/// An `Italic` represents an italic section of text in a document.
117#[derive(Clone, Debug, PartialEq, Deserialize)]
118pub struct Italic {
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub role: Option<Role>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub id: Option<String>,
123    pub form: Form,
124    #[serde(rename = "inlines")]
125    pub content: Vec<InlineNode>,
126    pub location: Location,
127}
128
129/// A `LineBreak` represents a line break (inline).
130#[derive(Clone, Debug, PartialEq, Deserialize)]
131pub struct LineBreak {
132    pub location: Location,
133}
134
135impl Serialize for LineBreak {
136    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137    where
138        S: Serializer,
139    {
140        let mut state = serializer.serialize_map(Some(3))?;
141        state.serialize_entry("name", "linebreak")?;
142        state.serialize_entry("type", "string")?;
143        state.serialize_entry("location", &self.location)?;
144        state.end()
145    }
146}
147
148/// A `Plain` represents a plain text section in a document.
149///
150/// This is the most basic form of text in a document.
151#[derive(Clone, Debug, PartialEq, Deserialize)]
152pub struct Plain {
153    #[serde(rename = "value")]
154    pub content: String,
155    pub location: Location,
156}
157
158impl Serialize for Plain {
159    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
160    where
161        S: Serializer,
162    {
163        let mut state = serializer.serialize_map(Some(4))?;
164        state.serialize_entry("name", "text")?;
165        state.serialize_entry("type", "string")?;
166        state.serialize_entry("value", &self.content)?;
167        state.serialize_entry("location", &self.location)?;
168        state.end()
169    }
170}
171
172/// A `Raw` represents a raw text section in a document.
173///
174/// This is the most basic form of text in a document and it should note that its contents
175/// must be rendered as they are (e.g: "\<h1>" should not end up being a \<h1> tag, it
176/// should be "\<h1>" text in html, very likely \&lt;h1\&gt;).
177#[derive(Clone, Debug, PartialEq, Deserialize)]
178pub struct Raw {
179    #[serde(rename = "value")]
180    pub content: String,
181    pub location: Location,
182}
183
184/// A `Verbatim` represents verbatim text section in a document.
185///
186/// This is the most basic form of text in a document and it should note that its contents
187/// must be rendered as they are (e.g: "\<h1>" should not end up being a \<h1> tag, it
188/// should be "\<h1>" text in html, very likely \&lt;h1\&gt;).
189///
190/// It is similar to `Raw`, but is intended for use in contexts where verbatim text is
191/// used, and some substitutions are done, namely converting callouts.
192#[derive(Clone, Debug, PartialEq, Deserialize)]
193pub struct Verbatim {
194    #[serde(rename = "value")]
195    pub content: String,
196    pub location: Location,
197}
198
199impl Serialize for StandaloneCurvedApostrophe {
200    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
201    where
202        S: Serializer,
203    {
204        let mut state = serializer.serialize_map(Some(3))?;
205        state.serialize_entry("name", "curved_apostrophe")?;
206        state.serialize_entry("type", "string")?;
207        state.serialize_entry("location", &self.location)?;
208        state.end()
209    }
210}
211
212/// The kind of callout reference marker (how it was expressed in the source).
213#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
214#[serde(rename_all = "lowercase")]
215pub enum CalloutRefKind {
216    /// Explicit callout: `<1>`, `<2>`, etc. - the number was specified directly.
217    Explicit,
218    /// Auto-numbered callout: `<.>` - the number was resolved automatically.
219    Auto,
220}
221
222/// A `CalloutRef` represents a callout reference marker within verbatim content.
223///
224/// Callout references appear at the end of lines in source/listing blocks and
225/// link to explanatory text in a subsequent callout list.
226///
227/// # Examples
228///
229/// ```asciidoc
230/// [source,ruby]
231/// ----
232/// def main <1>
233///   puts 'hello' <.>
234/// end
235/// ----
236/// <1> Defines the main function
237/// <.> Prints a greeting
238/// ```
239///
240/// The `<1>` marker creates an `Explicit` callout ref, while `<.>` creates an
241/// `Auto` callout ref that gets resolved to the next available number.
242#[derive(Clone, Debug, PartialEq, Eq)]
243pub struct CalloutRef {
244    /// The kind of callout (explicit number vs auto-numbered).
245    pub kind: CalloutRefKind,
246    /// The resolved callout number (1-indexed).
247    pub number: usize,
248    /// Source location of this callout reference.
249    pub location: Location,
250}
251
252impl CalloutRef {
253    /// Creates a new explicit callout reference with the given number.
254    #[must_use]
255    pub fn explicit(number: usize, location: Location) -> Self {
256        Self {
257            kind: CalloutRefKind::Explicit,
258            number,
259            location,
260        }
261    }
262
263    /// Creates a new auto-numbered callout reference with the resolved number.
264    #[must_use]
265    pub fn auto(number: usize, location: Location) -> Self {
266        Self {
267            kind: CalloutRefKind::Auto,
268            number,
269            location,
270        }
271    }
272}
273
274impl Serialize for CalloutRef {
275    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
276    where
277        S: Serializer,
278    {
279        let mut state = serializer.serialize_map(Some(5))?;
280        state.serialize_entry("name", "callout_reference")?;
281        state.serialize_entry("type", "inline")?;
282        state.serialize_entry("variant", &self.kind)?;
283        state.serialize_entry("number", &self.number)?;
284        state.serialize_entry("location", &self.location)?;
285        state.end()
286    }
287}
288
289impl<'de> Deserialize<'de> for CalloutRef {
290    fn deserialize<D>(deserializer: D) -> Result<CalloutRef, D::Error>
291    where
292        D: serde::Deserializer<'de>,
293    {
294        struct CalloutRefVisitor;
295
296        impl<'de> Visitor<'de> for CalloutRefVisitor {
297            type Value = CalloutRef;
298
299            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
300                formatter.write_str("a CalloutRef object")
301            }
302
303            fn visit_map<V>(self, mut map: V) -> Result<CalloutRef, V::Error>
304            where
305                V: MapAccess<'de>,
306            {
307                let mut kind = None;
308                let mut number = None;
309                let mut location = None;
310
311                while let Some(key) = map.next_key::<String>()? {
312                    match key.as_str() {
313                        // "variant" in JSON maps to "kind" in struct
314                        "variant" => {
315                            if kind.is_some() {
316                                return Err(de::Error::duplicate_field("variant"));
317                            }
318                            kind = Some(map.next_value()?);
319                        }
320                        "number" => {
321                            if number.is_some() {
322                                return Err(de::Error::duplicate_field("number"));
323                            }
324                            number = Some(map.next_value()?);
325                        }
326                        "location" => {
327                            if location.is_some() {
328                                return Err(de::Error::duplicate_field("location"));
329                            }
330                            location = Some(map.next_value()?);
331                        }
332                        // Skip unknown fields (name, type, etc.)
333                        _ => {
334                            let _: serde_json::Value = map.next_value()?;
335                        }
336                    }
337                }
338
339                let kind = kind.ok_or_else(|| de::Error::missing_field("variant"))?;
340                let number = number.ok_or_else(|| de::Error::missing_field("number"))?;
341                let location = location.ok_or_else(|| de::Error::missing_field("location"))?;
342
343                Ok(CalloutRef {
344                    kind,
345                    number,
346                    location,
347                })
348            }
349        }
350
351        deserializer.deserialize_map(CalloutRefVisitor)
352    }
353}