1use std::collections::HashSet;
2
3use serde::{
4 Deserialize, Serialize,
5 de::{self, Deserializer},
6 ser::{Error as _, SerializeMap, Serializer},
7};
8
9pub(crate) mod converter;
10mod macros;
11mod text;
12
13pub use converter::inlines_to_string;
14pub use macros::*;
15pub use text::*;
16
17use crate::{Anchor, BlockMetadata, ElementAttributes, Image, Source, StemNotation, Title};
18
19#[non_exhaustive]
24#[derive(Clone, Debug, PartialEq)]
25pub enum InlineNode {
26 PlainText(Plain),
28 RawText(Raw),
30 VerbatimText(Verbatim),
32 BoldText(Bold),
33 ItalicText(Italic),
34 MonospaceText(Monospace),
35 HighlightText(Highlight),
36 SubscriptText(Subscript),
37 SuperscriptText(Superscript),
38 CurvedQuotationText(CurvedQuotation),
39 CurvedApostropheText(CurvedApostrophe),
40 StandaloneCurvedApostrophe(StandaloneCurvedApostrophe),
41 LineBreak(LineBreak),
42 InlineAnchor(Anchor),
43 Macro(InlineMacro),
44}
45
46#[non_exhaustive]
48#[derive(Clone, Debug, PartialEq, Serialize)]
49pub enum InlineMacro {
50 Footnote(Footnote),
51 Icon(Icon),
52 Image(Box<Image>),
53 Keyboard(Keyboard),
54 Button(Button),
55 Menu(Menu),
56 Url(Url),
57 Link(Link),
58 Mailto(Mailto),
59 Autolink(Autolink),
60 CrossReference(CrossReference),
61 Pass(Pass),
62 Stem(Stem),
63}
64
65macro_rules! serialize_inline_format {
68 ($map:expr, $value:expr, $variant:literal) => {{
69 $map.serialize_entry("name", "span")?;
70 $map.serialize_entry("type", "inline")?;
71 $map.serialize_entry("variant", $variant)?;
72 $map.serialize_entry("form", &$value.form)?;
73 if let Some(role) = &$value.role {
74 $map.serialize_entry("role", role)?;
75 }
76 if let Some(id) = &$value.id {
77 $map.serialize_entry("id", id)?;
78 }
79 $map.serialize_entry("inlines", &$value.content)?;
80 $map.serialize_entry("location", &$value.location)?;
81 }};
82}
83
84impl Serialize for InlineNode {
85 #[allow(clippy::too_many_lines)]
86 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87 where
88 S: Serializer,
89 {
90 let mut map = serializer.serialize_map(None)?;
91
92 match self {
93 InlineNode::PlainText(plain) => {
94 map.serialize_entry("name", "text")?;
95 map.serialize_entry("type", "string")?;
96 map.serialize_entry("value", &plain.content)?;
97 map.serialize_entry("location", &plain.location)?;
98 }
99 InlineNode::RawText(raw) => {
100 map.serialize_entry("name", "raw")?;
101 map.serialize_entry("type", "string")?;
102 map.serialize_entry("value", &raw.content)?;
103 map.serialize_entry("location", &raw.location)?;
104 }
105 InlineNode::VerbatimText(verbatim) => {
106 map.serialize_entry("name", "text")?;
109 map.serialize_entry("type", "string")?;
110 map.serialize_entry("value", &verbatim.content)?;
111 map.serialize_entry("location", &verbatim.location)?;
112 }
113 InlineNode::HighlightText(highlight) => {
114 serialize_inline_format!(map, highlight, "mark");
115 }
116 InlineNode::ItalicText(italic) => {
117 serialize_inline_format!(map, italic, "emphasis");
118 }
119 InlineNode::BoldText(bold) => {
120 serialize_inline_format!(map, bold, "strong");
121 }
122 InlineNode::MonospaceText(monospace) => {
123 serialize_inline_format!(map, monospace, "code");
124 }
125 InlineNode::SubscriptText(subscript) => {
126 serialize_inline_format!(map, subscript, "subscript");
127 }
128 InlineNode::SuperscriptText(superscript) => {
129 serialize_inline_format!(map, superscript, "superscript");
130 }
131 InlineNode::CurvedQuotationText(curved_quotation) => {
132 serialize_inline_format!(map, curved_quotation, "curved_quotation");
133 }
134 InlineNode::CurvedApostropheText(curved_apostrophe) => {
135 serialize_inline_format!(map, curved_apostrophe, "curved_apostrophe");
136 }
137 InlineNode::StandaloneCurvedApostrophe(standalone) => {
138 map.serialize_entry("name", "curved_apostrophe")?;
139 map.serialize_entry("type", "string")?;
140 map.serialize_entry("location", &standalone.location)?;
141 }
142 InlineNode::LineBreak(line_break) => {
143 map.serialize_entry("name", "break")?;
144 map.serialize_entry("type", "inline")?;
145 map.serialize_entry("location", &line_break.location)?;
146 }
147 InlineNode::InlineAnchor(anchor) => {
148 map.serialize_entry("name", "anchor")?;
149 map.serialize_entry("type", "inline")?;
150 map.serialize_entry("id", &anchor.id)?;
151 if let Some(xreflabel) = &anchor.xreflabel {
152 map.serialize_entry("xreflabel", xreflabel)?;
153 }
154 map.serialize_entry("location", &anchor.location)?;
155 }
156 InlineNode::Macro(macro_node) => {
157 serialize_inline_macro::<S>(macro_node, &mut map)?;
158 }
159 }
160 map.end()
161 }
162}
163
164fn serialize_inline_macro<S>(
165 macro_node: &InlineMacro,
166 map: &mut S::SerializeMap,
167) -> Result<(), S::Error>
168where
169 S: Serializer,
170{
171 match macro_node {
172 InlineMacro::Footnote(footnote) => {
173 map.serialize_entry("name", "footnote")?;
174 map.serialize_entry("type", "inline")?;
175 map.serialize_entry("id", &footnote.id)?;
176 map.serialize_entry("inlines", &footnote.content)?;
177 map.serialize_entry("location", &footnote.location)?;
178 }
179 InlineMacro::Icon(icon) => {
180 map.serialize_entry("name", "icon")?;
181 map.serialize_entry("type", "inline")?;
182 map.serialize_entry("target", &icon.target)?;
183 if !icon.attributes.is_empty() {
184 map.serialize_entry("attributes", &icon.attributes)?;
185 }
186 map.serialize_entry("location", &icon.location)?;
187 }
188 InlineMacro::Image(image) => {
189 map.serialize_entry("name", "image")?;
190 map.serialize_entry("type", "inline")?;
191 map.serialize_entry("title", &image.title)?;
192 map.serialize_entry("target", &image.source)?;
193 map.serialize_entry("location", &image.location)?;
194 }
195 InlineMacro::Keyboard(keyboard) => {
196 map.serialize_entry("name", "keyboard")?;
197 map.serialize_entry("type", "inline")?;
198 map.serialize_entry("keys", &keyboard.keys)?;
199 map.serialize_entry("location", &keyboard.location)?;
200 }
201 InlineMacro::Button(button) => {
202 map.serialize_entry("name", "button")?;
203 map.serialize_entry("type", "inline")?;
204 map.serialize_entry("label", &button.label)?;
205 map.serialize_entry("location", &button.location)?;
206 }
207 InlineMacro::Menu(menu) => {
208 map.serialize_entry("name", "menu")?;
209 map.serialize_entry("type", "inline")?;
210 map.serialize_entry("target", &menu.target)?;
211 if !menu.items.is_empty() {
212 map.serialize_entry("items", &menu.items)?;
213 }
214 map.serialize_entry("location", &menu.location)?;
215 }
216 InlineMacro::Url(url) => {
217 map.serialize_entry("name", "ref")?;
218 map.serialize_entry("type", "inline")?;
219 map.serialize_entry("variant", "link")?;
220 map.serialize_entry("target", &url.target)?;
221 map.serialize_entry("location", &url.location)?;
222 map.serialize_entry("attributes", &url.attributes)?;
223 }
224 InlineMacro::Mailto(mailto) => {
225 map.serialize_entry("name", "ref")?;
226 map.serialize_entry("type", "inline")?;
227 map.serialize_entry("variant", "mailto")?;
228 map.serialize_entry("target", &mailto.target)?;
229 map.serialize_entry("location", &mailto.location)?;
230 map.serialize_entry("attributes", &mailto.attributes)?;
231 }
232 InlineMacro::Link(link) => {
233 map.serialize_entry("name", "ref")?;
234 map.serialize_entry("type", "inline")?;
235 map.serialize_entry("variant", "link")?;
236 map.serialize_entry("target", &link.target)?;
237 map.serialize_entry("location", &link.location)?;
238 map.serialize_entry("attributes", &link.attributes)?;
239 }
240 InlineMacro::Autolink(autolink) => {
241 map.serialize_entry("name", "ref")?;
242 map.serialize_entry("type", "inline")?;
243 map.serialize_entry("variant", "autolink")?;
244 map.serialize_entry("target", &autolink.url)?;
245 map.serialize_entry("location", &autolink.location)?;
246 }
247 InlineMacro::CrossReference(xref) => {
248 map.serialize_entry("name", "xref")?;
249 map.serialize_entry("type", "inline")?;
250 map.serialize_entry("target", &xref.target)?;
251 if let Some(text) = &xref.text {
252 map.serialize_entry("text", text)?;
253 }
254 map.serialize_entry("location", &xref.location)?;
255 }
256 InlineMacro::Pass(_) => {
257 return Err(S::Error::custom(
258 "inline passthrough macros are not part of the ASG specification and cannot be serialized",
259 ));
260 }
261 InlineMacro::Stem(stem) => {
262 map.serialize_entry("name", "stem")?;
263 map.serialize_entry("type", "inline")?;
264 map.serialize_entry("content", &stem.content)?;
265 map.serialize_entry("notation", &stem.notation)?;
266 map.serialize_entry("location", &stem.location)?;
267 }
268 }
269 Ok(())
270}
271
272#[derive(Default, Deserialize)]
278#[serde(default)]
279struct RawInlineFields {
280 name: Option<String>,
281 r#type: Option<String>,
282 value: Option<String>,
283 variant: Option<String>,
284 form: Option<Form>,
285 location: Option<crate::Location>,
286 inlines: Option<Vec<InlineNode>>,
287 title: Option<Vec<InlineNode>>,
288 target: Option<serde_json::Value>,
289 attributes: Option<ElementAttributes>,
290 role: Option<String>,
291 id: Option<String>,
292 text: Option<String>,
293 items: Option<Vec<String>>,
294 keys: Option<Vec<String>>,
295 label: Option<String>,
296 content: Option<String>,
297 notation: Option<StemNotation>,
298 substitutions: Option<HashSet<crate::Substitution>>,
299 xreflabel: Option<String>,
300 bracketed: Option<bool>,
301}
302
303fn construct_plain_text<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
308 Ok(InlineNode::PlainText(Plain {
309 content: raw.value.ok_or_else(|| E::missing_field("value"))?,
310 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
311 }))
312}
313
314fn construct_raw_text<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
315 Ok(InlineNode::RawText(Raw {
316 content: raw.value.ok_or_else(|| E::missing_field("value"))?,
317 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
318 }))
319}
320
321fn construct_verbatim_text<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
322 Ok(InlineNode::VerbatimText(Verbatim {
323 content: raw.value.ok_or_else(|| E::missing_field("value"))?,
324 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
325 }))
326}
327
328fn construct_standalone_curved_apostrophe<E: de::Error>(
329 raw: RawInlineFields,
330) -> Result<InlineNode, E> {
331 Ok(InlineNode::StandaloneCurvedApostrophe(
332 StandaloneCurvedApostrophe {
333 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
334 },
335 ))
336}
337
338fn construct_line_break<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
339 Ok(InlineNode::LineBreak(LineBreak {
340 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
341 }))
342}
343
344fn construct_anchor<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
345 Ok(InlineNode::InlineAnchor(Anchor {
346 id: raw.id.ok_or_else(|| E::missing_field("id"))?,
347 xreflabel: raw.xreflabel,
348 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
349 }))
350}
351
352fn construct_icon<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
353 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
354 let target: Source = serde_json::from_value(target_val).map_err(E::custom)?;
355 Ok(InlineNode::Macro(InlineMacro::Icon(Icon {
356 attributes: raw.attributes.unwrap_or_default(),
357 target,
358 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
359 })))
360}
361
362fn construct_image<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
363 let title = Title::new(raw.title.ok_or_else(|| E::missing_field("title"))?);
364 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
365 let source: Source = serde_json::from_value(target_val).map_err(E::custom)?;
366 Ok(InlineNode::Macro(InlineMacro::Image(Box::new(Image {
367 title,
368 source,
369 metadata: BlockMetadata::default(),
370 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
371 }))))
372}
373
374fn construct_footnote<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
375 let inlines = raw.inlines.ok_or_else(|| E::missing_field("inlines"))?;
376 Ok(InlineNode::Macro(InlineMacro::Footnote(Footnote {
377 id: raw.id,
378 content: inlines,
379 number: 0,
380 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
381 })))
382}
383
384fn construct_keyboard<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
385 Ok(InlineNode::Macro(InlineMacro::Keyboard(Keyboard {
386 keys: raw.keys.ok_or_else(|| E::missing_field("keys"))?,
387 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
388 })))
389}
390
391fn construct_button<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
392 Ok(InlineNode::Macro(InlineMacro::Button(Button {
393 label: raw.label.ok_or_else(|| E::missing_field("label"))?,
394 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
395 })))
396}
397
398fn construct_menu<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
399 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
400 let target: String = serde_json::from_value(target_val).map_err(E::custom)?;
401 Ok(InlineNode::Macro(InlineMacro::Menu(Menu {
402 target,
403 items: raw.items.unwrap_or_default(),
404 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
405 })))
406}
407
408fn construct_stem<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
409 Ok(InlineNode::Macro(InlineMacro::Stem(Stem {
410 content: raw.content.ok_or_else(|| E::missing_field("content"))?,
411 notation: raw.notation.ok_or_else(|| E::missing_field("notation"))?,
412 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
413 })))
414}
415
416fn construct_xref<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
417 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
418 let target: String = serde_json::from_value(target_val).map_err(E::custom)?;
419 Ok(InlineNode::Macro(InlineMacro::CrossReference(
420 crate::model::CrossReference {
421 target,
422 text: raw.text,
423 location: raw.location.ok_or_else(|| E::missing_field("location"))?,
424 },
425 )))
426}
427
428fn construct_ref<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
429 let variant = raw.variant.ok_or_else(|| E::missing_field("variant"))?;
430 let target_val = raw.target.ok_or_else(|| E::missing_field("target"))?;
431 let target: Source = serde_json::from_value(target_val).map_err(E::custom)?;
432 let location = raw.location.ok_or_else(|| E::missing_field("location"))?;
433
434 match variant.as_str() {
435 "url" => Ok(InlineNode::Macro(InlineMacro::Url(Url {
436 text: vec![],
437 attributes: raw.attributes.unwrap_or_default(),
438 target,
439 location,
440 }))),
441 "link" => Ok(InlineNode::Macro(InlineMacro::Link(Link {
442 text: None,
443 attributes: raw.attributes.unwrap_or_default(),
444 target,
445 location,
446 }))),
447 "mailto" => Ok(InlineNode::Macro(InlineMacro::Mailto(Mailto {
448 text: vec![],
449 attributes: raw.attributes.unwrap_or_default(),
450 target,
451 location,
452 }))),
453 "autolink" => Ok(InlineNode::Macro(InlineMacro::Autolink(Autolink {
454 url: target,
455 bracketed: raw.bracketed.unwrap_or(false),
456 location,
457 }))),
458 "pass" => Ok(InlineNode::Macro(InlineMacro::Pass(Pass {
459 text: raw.text,
460 substitutions: raw.substitutions.unwrap_or_default(),
461 location,
462 kind: PassthroughKind::default(),
463 }))),
464 _ => {
465 tracing::error!(variant = %variant, "invalid inline macro variant");
466 Err(E::custom("invalid inline macro variant"))
467 }
468 }
469}
470
471fn construct_span<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
472 let variant = raw.variant.ok_or_else(|| E::missing_field("variant"))?;
473 let inlines = raw.inlines.ok_or_else(|| E::missing_field("inlines"))?;
474 let location = raw.location.ok_or_else(|| E::missing_field("location"))?;
475 let role = raw.role;
476 let id = raw.id;
477
478 match variant.as_str() {
479 "strong" => Ok(InlineNode::BoldText(Bold {
480 role,
481 id,
482 form: raw.form.unwrap_or(Form::Constrained),
483 content: inlines,
484 location,
485 })),
486 "emphasis" => Ok(InlineNode::ItalicText(Italic {
487 role,
488 id,
489 form: raw.form.unwrap_or(Form::Constrained),
490 content: inlines,
491 location,
492 })),
493 "code" => Ok(InlineNode::MonospaceText(Monospace {
494 role,
495 id,
496 form: raw.form.unwrap_or(Form::Constrained),
497 content: inlines,
498 location,
499 })),
500 "mark" => Ok(InlineNode::HighlightText(Highlight {
501 role,
502 id,
503 form: raw.form.unwrap_or(Form::Constrained),
504 content: inlines,
505 location,
506 })),
507 "subscript" => Ok(InlineNode::SubscriptText(Subscript {
508 role,
509 id,
510 form: raw.form.unwrap_or(Form::Unconstrained),
511 content: inlines,
512 location,
513 })),
514 "superscript" => Ok(InlineNode::SuperscriptText(Superscript {
515 role,
516 id,
517 form: raw.form.unwrap_or(Form::Unconstrained),
518 content: inlines,
519 location,
520 })),
521 "curved_quotation" => Ok(InlineNode::CurvedQuotationText(CurvedQuotation {
522 role,
523 id,
524 form: raw.form.unwrap_or(Form::Unconstrained),
525 content: inlines,
526 location,
527 })),
528 "curved_apostrophe" => Ok(InlineNode::CurvedApostropheText(CurvedApostrophe {
529 role,
530 id,
531 form: raw.form.unwrap_or(Form::Unconstrained),
532 content: inlines,
533 location,
534 })),
535 _ => {
536 tracing::error!(variant = %variant, "invalid inline node variant");
537 Err(E::custom("invalid inline node variant"))
538 }
539 }
540}
541
542fn dispatch_inline<E: de::Error>(raw: RawInlineFields) -> Result<InlineNode, E> {
544 let name = raw.name.clone().ok_or_else(|| E::missing_field("name"))?;
545 let ty = raw.r#type.clone().ok_or_else(|| E::missing_field("type"))?;
546
547 match (name.as_str(), ty.as_str()) {
548 ("text", "string") => construct_plain_text(raw),
549 ("raw", "string") => construct_raw_text(raw),
550 ("verbatim", "string") => construct_verbatim_text(raw),
551 ("curved_apostrophe", "string") => construct_standalone_curved_apostrophe(raw),
552 ("break", "inline") => construct_line_break(raw),
553 ("anchor", "inline") => construct_anchor(raw),
554 ("icon", "inline") => construct_icon(raw),
555 ("image", "inline") => construct_image(raw),
556 ("footnote", "inline") => construct_footnote(raw),
557 ("keyboard", "inline") => construct_keyboard(raw),
558 ("btn" | "button", "inline") => construct_button(raw),
559 ("menu", "inline") => construct_menu(raw),
560 ("stem", "inline") => construct_stem(raw),
561 ("xref", "inline") => construct_xref(raw),
562 ("ref", "inline") => construct_ref(raw),
563 ("span", "inline") => construct_span(raw),
564 _ => {
565 tracing::error!(name = %name, r#type = %ty, "invalid inline node");
566 Err(E::custom("invalid inline node"))
567 }
568 }
569}
570
571impl<'de> Deserialize<'de> for InlineNode {
572 fn deserialize<D>(deserializer: D) -> Result<InlineNode, D::Error>
573 where
574 D: Deserializer<'de>,
575 {
576 let raw: RawInlineFields = RawInlineFields::deserialize(deserializer)?;
577 dispatch_inline(raw)
578 }
579}