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