1use serde::{
2 Serialize,
3 ser::{Error as _, SerializeMap, Serializer},
4};
5
6pub(crate) mod converter;
7mod macros;
8mod text;
9
10pub use converter::inlines_to_string;
11pub use macros::*;
12pub use text::*;
13
14use crate::{Anchor, Image, Location, model::Locateable};
15
16#[non_exhaustive]
21#[derive(Clone, Debug, PartialEq)]
22pub enum InlineNode<'a> {
23 PlainText(Plain<'a>),
25 RawText(Raw<'a>),
27 VerbatimText(Verbatim<'a>),
29 BoldText(Bold<'a>),
30 ItalicText(Italic<'a>),
31 MonospaceText(Monospace<'a>),
32 HighlightText(Highlight<'a>),
33 SubscriptText(Subscript<'a>),
34 SuperscriptText(Superscript<'a>),
35 CurvedQuotationText(CurvedQuotation<'a>),
36 CurvedApostropheText(CurvedApostrophe<'a>),
37 StandaloneCurvedApostrophe(StandaloneCurvedApostrophe),
38 LineBreak(LineBreak),
39 InlineAnchor(Anchor<'a>),
40 Macro(InlineMacro<'a>),
41 CalloutRef(CalloutRef),
43}
44
45impl InlineNode<'_> {
46 #[must_use]
48 pub fn location(&self) -> &Location {
49 <Self as Locateable>::location(self)
50 }
51}
52
53impl Locateable for InlineNode<'_> {
54 fn location(&self) -> &Location {
55 match self {
56 InlineNode::PlainText(t) => &t.location,
57 InlineNode::RawText(t) => &t.location,
58 InlineNode::VerbatimText(t) => &t.location,
59 InlineNode::BoldText(t) => &t.location,
60 InlineNode::ItalicText(t) => &t.location,
61 InlineNode::MonospaceText(t) => &t.location,
62 InlineNode::HighlightText(t) => &t.location,
63 InlineNode::SubscriptText(t) => &t.location,
64 InlineNode::SuperscriptText(t) => &t.location,
65 InlineNode::CurvedQuotationText(t) => &t.location,
66 InlineNode::CurvedApostropheText(t) => &t.location,
67 InlineNode::StandaloneCurvedApostrophe(t) => &t.location,
68 InlineNode::LineBreak(l) => &l.location,
69 InlineNode::InlineAnchor(a) => &a.location,
70 InlineNode::Macro(m) => m.location(),
71 InlineNode::CalloutRef(c) => &c.location,
72 }
73 }
74}
75
76impl Locateable for InlineMacro<'_> {
77 fn location(&self) -> &Location {
78 match self {
79 Self::Footnote(f) => &f.location,
80 Self::Icon(i) => &i.location,
81 Self::Image(img) => &img.location,
82 Self::Keyboard(k) => &k.location,
83 Self::Button(b) => &b.location,
84 Self::Menu(m) => &m.location,
85 Self::Url(u) => &u.location,
86 Self::Mailto(m) => &m.location,
87 Self::Link(l) => &l.location,
88 Self::Autolink(a) => &a.location,
89 Self::CrossReference(x) => &x.location,
90 Self::Pass(p) => &p.location,
91 Self::Stem(s) => &s.location,
92 Self::IndexTerm(i) => &i.location,
93 }
94 }
95}
96
97#[non_exhaustive]
135#[derive(Clone, Debug, PartialEq, Serialize)]
136pub enum InlineMacro<'a> {
137 Footnote(Footnote<'a>),
139 Icon(Icon<'a>),
141 Image(Box<Image<'a>>),
143 Keyboard(Keyboard<'a>),
145 Button(Button<'a>),
147 Menu(Menu<'a>),
149 Url(Url<'a>),
151 Link(Link<'a>),
153 Mailto(Mailto<'a>),
155 Autolink(Autolink<'a>),
157 CrossReference(CrossReference<'a>),
159 Pass(Pass<'a>),
161 Stem(Stem<'a>),
163 IndexTerm(IndexTerm<'a>),
165}
166
167impl InlineMacro<'_> {
168 #[must_use]
170 pub fn location(&self) -> &Location {
171 <Self as Locateable>::location(self)
172 }
173}
174
175macro_rules! serialize_inline_format {
178 ($map:expr, $value:expr, $variant:literal) => {{
179 $map.serialize_entry("name", "span")?;
180 $map.serialize_entry("type", "inline")?;
181 $map.serialize_entry("variant", $variant)?;
182 $map.serialize_entry("form", &$value.form)?;
183 if let Some(role) = &$value.role {
184 $map.serialize_entry("role", role)?;
185 }
186 if let Some(id) = &$value.id {
187 $map.serialize_entry("id", id)?;
188 }
189 $map.serialize_entry("inlines", &$value.content)?;
190 $map.serialize_entry("location", &$value.location)?;
191 }};
192}
193
194impl Serialize for InlineNode<'_> {
195 #[allow(clippy::too_many_lines)]
196 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
197 where
198 S: Serializer,
199 {
200 let mut map = serializer.serialize_map(None)?;
201
202 match self {
203 InlineNode::PlainText(plain) => {
204 map.serialize_entry("name", "text")?;
205 map.serialize_entry("type", "string")?;
206 map.serialize_entry("value", &plain.content)?;
207 map.serialize_entry("location", &plain.location)?;
208 }
209 InlineNode::RawText(raw) => {
210 map.serialize_entry("name", "raw")?;
211 map.serialize_entry("type", "string")?;
212 map.serialize_entry("value", &raw.content)?;
213 map.serialize_entry("location", &raw.location)?;
214 }
215 InlineNode::VerbatimText(verbatim) => {
216 map.serialize_entry("name", "text")?;
219 map.serialize_entry("type", "string")?;
220 map.serialize_entry("value", &verbatim.content)?;
221 map.serialize_entry("location", &verbatim.location)?;
222 }
223 InlineNode::HighlightText(highlight) => {
224 serialize_inline_format!(map, highlight, "mark");
225 }
226 InlineNode::ItalicText(italic) => {
227 serialize_inline_format!(map, italic, "emphasis");
228 }
229 InlineNode::BoldText(bold) => {
230 serialize_inline_format!(map, bold, "strong");
231 }
232 InlineNode::MonospaceText(monospace) => {
233 serialize_inline_format!(map, monospace, "code");
234 }
235 InlineNode::SubscriptText(subscript) => {
236 serialize_inline_format!(map, subscript, "subscript");
237 }
238 InlineNode::SuperscriptText(superscript) => {
239 serialize_inline_format!(map, superscript, "superscript");
240 }
241 InlineNode::CurvedQuotationText(curved_quotation) => {
242 serialize_inline_format!(map, curved_quotation, "curved_quotation");
243 }
244 InlineNode::CurvedApostropheText(curved_apostrophe) => {
245 serialize_inline_format!(map, curved_apostrophe, "curved_apostrophe");
246 }
247 InlineNode::StandaloneCurvedApostrophe(standalone) => {
248 map.serialize_entry("name", "curved_apostrophe")?;
249 map.serialize_entry("type", "string")?;
250 map.serialize_entry("location", &standalone.location)?;
251 }
252 InlineNode::LineBreak(line_break) => {
253 map.serialize_entry("name", "break")?;
254 map.serialize_entry("type", "inline")?;
255 map.serialize_entry("location", &line_break.location)?;
256 }
257 InlineNode::InlineAnchor(anchor) => {
258 map.serialize_entry("name", "anchor")?;
259 map.serialize_entry("type", "inline")?;
260 map.serialize_entry("id", &anchor.id)?;
261 if let Some(xreflabel) = &anchor.xreflabel {
262 map.serialize_entry("xreflabel", xreflabel)?;
263 }
264 map.serialize_entry("location", &anchor.location)?;
265 }
266 InlineNode::Macro(macro_node) => {
267 serialize_inline_macro::<S>(macro_node, &mut map)?;
268 }
269 InlineNode::CalloutRef(callout_ref) => {
270 map.serialize_entry("name", "callout_reference")?;
271 map.serialize_entry("type", "inline")?;
272 map.serialize_entry("variant", &callout_ref.kind)?;
273 map.serialize_entry("number", &callout_ref.number)?;
274 map.serialize_entry("location", &callout_ref.location)?;
275 }
276 }
277 map.end()
278 }
279}
280
281fn serialize_inline_macro<S>(
282 macro_node: &InlineMacro<'_>,
283 map: &mut S::SerializeMap,
284) -> Result<(), S::Error>
285where
286 S: Serializer,
287{
288 match macro_node {
289 InlineMacro::Footnote(f) => serialize_footnote::<S>(f, map),
290 InlineMacro::Icon(i) => serialize_icon::<S>(i, map),
291 InlineMacro::Image(i) => serialize_image::<S>(i, map),
292 InlineMacro::Keyboard(k) => serialize_keyboard::<S>(k, map),
293 InlineMacro::Button(b) => serialize_button::<S>(b, map),
294 InlineMacro::Menu(m) => serialize_menu::<S>(m, map),
295 InlineMacro::Url(u) => serialize_url::<S>(u, map),
296 InlineMacro::Mailto(m) => serialize_mailto::<S>(m, map),
297 InlineMacro::Link(l) => serialize_link::<S>(l, map),
298 InlineMacro::Autolink(a) => serialize_autolink::<S>(a, map),
299 InlineMacro::CrossReference(x) => serialize_xref::<S>(x, map),
300 InlineMacro::Stem(s) => serialize_stem::<S>(s, map),
301 InlineMacro::IndexTerm(i) => serialize_indexterm::<S>(i, map),
302 InlineMacro::Pass(_) => Err(S::Error::custom(
303 "inline passthrough macros are not part of the ASG specification and cannot be serialized",
304 )),
305 }
306}
307
308fn serialize_footnote<S>(f: &Footnote<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
309where
310 S: Serializer,
311{
312 map.serialize_entry("name", "footnote")?;
313 map.serialize_entry("type", "inline")?;
314 map.serialize_entry("id", &f.id)?;
315 map.serialize_entry("inlines", &f.content)?;
316 map.serialize_entry("location", &f.location)
317}
318
319fn serialize_icon<S>(i: &Icon<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
320where
321 S: Serializer,
322{
323 map.serialize_entry("name", "icon")?;
324 map.serialize_entry("type", "inline")?;
325 map.serialize_entry("target", &i.target)?;
326 if !i.attributes.is_empty() {
327 map.serialize_entry("attributes", &i.attributes)?;
328 }
329 map.serialize_entry("location", &i.location)
330}
331
332fn serialize_image<S>(i: &Image<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
333where
334 S: Serializer,
335{
336 map.serialize_entry("name", "image")?;
337 map.serialize_entry("type", "inline")?;
338 map.serialize_entry("title", &i.title)?;
339 map.serialize_entry("target", &i.source)?;
340 map.serialize_entry("location", &i.location)
341}
342
343fn serialize_keyboard<S>(k: &Keyboard<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
344where
345 S: Serializer,
346{
347 map.serialize_entry("name", "keyboard")?;
348 map.serialize_entry("type", "inline")?;
349 map.serialize_entry("keys", &k.keys)?;
350 map.serialize_entry("location", &k.location)
351}
352
353fn serialize_button<S>(b: &Button<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
354where
355 S: Serializer,
356{
357 map.serialize_entry("name", "button")?;
358 map.serialize_entry("type", "inline")?;
359 map.serialize_entry("label", &b.label)?;
360 map.serialize_entry("location", &b.location)
361}
362
363fn serialize_menu<S>(m: &Menu<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
364where
365 S: Serializer,
366{
367 map.serialize_entry("name", "menu")?;
368 map.serialize_entry("type", "inline")?;
369 map.serialize_entry("target", &m.target)?;
370 if !m.items.is_empty() {
371 map.serialize_entry("items", &m.items)?;
372 }
373 map.serialize_entry("location", &m.location)
374}
375
376fn serialize_url<S>(u: &Url<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
377where
378 S: Serializer,
379{
380 map.serialize_entry("name", "ref")?;
381 map.serialize_entry("type", "inline")?;
382 map.serialize_entry("variant", "link")?;
383 map.serialize_entry("target", &u.target)?;
384 if !u.text.is_empty() {
385 map.serialize_entry("inlines", &u.text)?;
386 }
387 map.serialize_entry("location", &u.location)?;
388 map.serialize_entry("attributes", &u.attributes)
389}
390
391fn serialize_mailto<S>(m: &Mailto<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
392where
393 S: Serializer,
394{
395 map.serialize_entry("name", "ref")?;
396 map.serialize_entry("type", "inline")?;
397 map.serialize_entry("variant", "mailto")?;
398 map.serialize_entry("target", &m.target)?;
399 if !m.text.is_empty() {
400 map.serialize_entry("inlines", &m.text)?;
401 }
402 map.serialize_entry("location", &m.location)?;
403 map.serialize_entry("attributes", &m.attributes)
404}
405
406fn serialize_link<S>(l: &Link<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
407where
408 S: Serializer,
409{
410 map.serialize_entry("name", "ref")?;
411 map.serialize_entry("type", "inline")?;
412 map.serialize_entry("variant", "link")?;
413 map.serialize_entry("target", &l.target)?;
414 if !l.text.is_empty() {
415 map.serialize_entry("inlines", &l.text)?;
416 }
417 map.serialize_entry("location", &l.location)?;
418 map.serialize_entry("attributes", &l.attributes)
419}
420
421fn serialize_autolink<S>(a: &Autolink<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
422where
423 S: Serializer,
424{
425 map.serialize_entry("name", "ref")?;
426 map.serialize_entry("type", "inline")?;
427 map.serialize_entry("variant", "autolink")?;
428 map.serialize_entry("target", &a.url)?;
429 map.serialize_entry("location", &a.location)
430}
431
432fn serialize_xref<S>(x: &CrossReference<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
433where
434 S: Serializer,
435{
436 map.serialize_entry("name", "xref")?;
437 map.serialize_entry("type", "inline")?;
438 map.serialize_entry("target", &x.target)?;
439 if !x.text.is_empty() {
440 map.serialize_entry("inlines", &x.text)?;
441 }
442 map.serialize_entry("location", &x.location)
443}
444
445fn serialize_stem<S>(s: &Stem<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
446where
447 S: Serializer,
448{
449 map.serialize_entry("name", "stem")?;
450 map.serialize_entry("type", "inline")?;
451 map.serialize_entry("content", &s.content)?;
452 map.serialize_entry("notation", &s.notation)?;
453 map.serialize_entry("location", &s.location)
454}
455
456fn serialize_indexterm<S>(i: &IndexTerm<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
457where
458 S: Serializer,
459{
460 map.serialize_entry("name", "indexterm")?;
461 map.serialize_entry("type", "inline")?;
462 map.serialize_entry("term", i.term())?;
463 if let Some(secondary) = i.secondary() {
464 map.serialize_entry("secondary", secondary)?;
465 }
466 if let Some(tertiary) = i.tertiary() {
467 map.serialize_entry("tertiary", tertiary)?;
468 }
469 map.serialize_entry("visible", &i.is_visible())?;
470 map.serialize_entry("location", &i.location)
471}