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