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};
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
45#[non_exhaustive]
83#[derive(Clone, Debug, PartialEq, Serialize)]
84pub enum InlineMacro {
85 Footnote(Footnote),
87 Icon(Icon),
89 Image(Box<Image>),
91 Keyboard(Keyboard),
93 Button(Button),
95 Menu(Menu),
97 Url(Url),
99 Link(Link),
101 Mailto(Mailto),
103 Autolink(Autolink),
105 CrossReference(CrossReference),
107 Pass(Pass),
109 Stem(Stem),
111 IndexTerm(IndexTerm),
113}
114
115macro_rules! serialize_inline_format {
118 ($map:expr, $value:expr, $variant:literal) => {{
119 $map.serialize_entry("name", "span")?;
120 $map.serialize_entry("type", "inline")?;
121 $map.serialize_entry("variant", $variant)?;
122 $map.serialize_entry("form", &$value.form)?;
123 if let Some(role) = &$value.role {
124 $map.serialize_entry("role", role)?;
125 }
126 if let Some(id) = &$value.id {
127 $map.serialize_entry("id", id)?;
128 }
129 $map.serialize_entry("inlines", &$value.content)?;
130 $map.serialize_entry("location", &$value.location)?;
131 }};
132}
133
134impl Serialize for InlineNode {
135 #[allow(clippy::too_many_lines)]
136 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
137 where
138 S: Serializer,
139 {
140 let mut map = serializer.serialize_map(None)?;
141
142 match self {
143 InlineNode::PlainText(plain) => {
144 map.serialize_entry("name", "text")?;
145 map.serialize_entry("type", "string")?;
146 map.serialize_entry("value", &plain.content)?;
147 map.serialize_entry("location", &plain.location)?;
148 }
149 InlineNode::RawText(raw) => {
150 map.serialize_entry("name", "raw")?;
151 map.serialize_entry("type", "string")?;
152 map.serialize_entry("value", &raw.content)?;
153 map.serialize_entry("location", &raw.location)?;
154 }
155 InlineNode::VerbatimText(verbatim) => {
156 map.serialize_entry("name", "text")?;
159 map.serialize_entry("type", "string")?;
160 map.serialize_entry("value", &verbatim.content)?;
161 map.serialize_entry("location", &verbatim.location)?;
162 }
163 InlineNode::HighlightText(highlight) => {
164 serialize_inline_format!(map, highlight, "mark");
165 }
166 InlineNode::ItalicText(italic) => {
167 serialize_inline_format!(map, italic, "emphasis");
168 }
169 InlineNode::BoldText(bold) => {
170 serialize_inline_format!(map, bold, "strong");
171 }
172 InlineNode::MonospaceText(monospace) => {
173 serialize_inline_format!(map, monospace, "code");
174 }
175 InlineNode::SubscriptText(subscript) => {
176 serialize_inline_format!(map, subscript, "subscript");
177 }
178 InlineNode::SuperscriptText(superscript) => {
179 serialize_inline_format!(map, superscript, "superscript");
180 }
181 InlineNode::CurvedQuotationText(curved_quotation) => {
182 serialize_inline_format!(map, curved_quotation, "curved_quotation");
183 }
184 InlineNode::CurvedApostropheText(curved_apostrophe) => {
185 serialize_inline_format!(map, curved_apostrophe, "curved_apostrophe");
186 }
187 InlineNode::StandaloneCurvedApostrophe(standalone) => {
188 map.serialize_entry("name", "curved_apostrophe")?;
189 map.serialize_entry("type", "string")?;
190 map.serialize_entry("location", &standalone.location)?;
191 }
192 InlineNode::LineBreak(line_break) => {
193 map.serialize_entry("name", "break")?;
194 map.serialize_entry("type", "inline")?;
195 map.serialize_entry("location", &line_break.location)?;
196 }
197 InlineNode::InlineAnchor(anchor) => {
198 map.serialize_entry("name", "anchor")?;
199 map.serialize_entry("type", "inline")?;
200 map.serialize_entry("id", &anchor.id)?;
201 if let Some(xreflabel) = &anchor.xreflabel {
202 map.serialize_entry("xreflabel", xreflabel)?;
203 }
204 map.serialize_entry("location", &anchor.location)?;
205 }
206 InlineNode::Macro(macro_node) => {
207 serialize_inline_macro::<S>(macro_node, &mut map)?;
208 }
209 InlineNode::CalloutRef(callout_ref) => {
210 map.serialize_entry("name", "callout_reference")?;
211 map.serialize_entry("type", "inline")?;
212 map.serialize_entry("variant", &callout_ref.kind)?;
213 map.serialize_entry("number", &callout_ref.number)?;
214 map.serialize_entry("location", &callout_ref.location)?;
215 }
216 }
217 map.end()
218 }
219}
220
221fn serialize_inline_macro<S>(
222 macro_node: &InlineMacro,
223 map: &mut S::SerializeMap,
224) -> Result<(), S::Error>
225where
226 S: Serializer,
227{
228 match macro_node {
229 InlineMacro::Footnote(f) => serialize_footnote::<S>(f, map),
230 InlineMacro::Icon(i) => serialize_icon::<S>(i, map),
231 InlineMacro::Image(i) => serialize_image::<S>(i, map),
232 InlineMacro::Keyboard(k) => serialize_keyboard::<S>(k, map),
233 InlineMacro::Button(b) => serialize_button::<S>(b, map),
234 InlineMacro::Menu(m) => serialize_menu::<S>(m, map),
235 InlineMacro::Url(u) => serialize_url::<S>(u, map),
236 InlineMacro::Mailto(m) => serialize_mailto::<S>(m, map),
237 InlineMacro::Link(l) => serialize_link::<S>(l, map),
238 InlineMacro::Autolink(a) => serialize_autolink::<S>(a, map),
239 InlineMacro::CrossReference(x) => serialize_xref::<S>(x, map),
240 InlineMacro::Stem(s) => serialize_stem::<S>(s, map),
241 InlineMacro::IndexTerm(i) => serialize_indexterm::<S>(i, map),
242 InlineMacro::Pass(_) => Err(S::Error::custom(
243 "inline passthrough macros are not part of the ASG specification and cannot be serialized",
244 )),
245 }
246}
247
248fn serialize_footnote<S>(f: &Footnote, map: &mut S::SerializeMap) -> Result<(), S::Error>
249where
250 S: Serializer,
251{
252 map.serialize_entry("name", "footnote")?;
253 map.serialize_entry("type", "inline")?;
254 map.serialize_entry("id", &f.id)?;
255 map.serialize_entry("inlines", &f.content)?;
256 map.serialize_entry("location", &f.location)
257}
258
259fn serialize_icon<S>(i: &Icon, map: &mut S::SerializeMap) -> Result<(), S::Error>
260where
261 S: Serializer,
262{
263 map.serialize_entry("name", "icon")?;
264 map.serialize_entry("type", "inline")?;
265 map.serialize_entry("target", &i.target)?;
266 if !i.attributes.is_empty() {
267 map.serialize_entry("attributes", &i.attributes)?;
268 }
269 map.serialize_entry("location", &i.location)
270}
271
272fn serialize_image<S>(i: &Image, map: &mut S::SerializeMap) -> Result<(), S::Error>
273where
274 S: Serializer,
275{
276 map.serialize_entry("name", "image")?;
277 map.serialize_entry("type", "inline")?;
278 map.serialize_entry("title", &i.title)?;
279 map.serialize_entry("target", &i.source)?;
280 map.serialize_entry("location", &i.location)
281}
282
283fn serialize_keyboard<S>(k: &Keyboard, map: &mut S::SerializeMap) -> Result<(), S::Error>
284where
285 S: Serializer,
286{
287 map.serialize_entry("name", "keyboard")?;
288 map.serialize_entry("type", "inline")?;
289 map.serialize_entry("keys", &k.keys)?;
290 map.serialize_entry("location", &k.location)
291}
292
293fn serialize_button<S>(b: &Button, map: &mut S::SerializeMap) -> Result<(), S::Error>
294where
295 S: Serializer,
296{
297 map.serialize_entry("name", "button")?;
298 map.serialize_entry("type", "inline")?;
299 map.serialize_entry("label", &b.label)?;
300 map.serialize_entry("location", &b.location)
301}
302
303fn serialize_menu<S>(m: &Menu, map: &mut S::SerializeMap) -> Result<(), S::Error>
304where
305 S: Serializer,
306{
307 map.serialize_entry("name", "menu")?;
308 map.serialize_entry("type", "inline")?;
309 map.serialize_entry("target", &m.target)?;
310 if !m.items.is_empty() {
311 map.serialize_entry("items", &m.items)?;
312 }
313 map.serialize_entry("location", &m.location)
314}
315
316fn serialize_url<S>(u: &Url, map: &mut S::SerializeMap) -> Result<(), S::Error>
317where
318 S: Serializer,
319{
320 map.serialize_entry("name", "ref")?;
321 map.serialize_entry("type", "inline")?;
322 map.serialize_entry("variant", "link")?;
323 map.serialize_entry("target", &u.target)?;
324 map.serialize_entry("location", &u.location)?;
325 map.serialize_entry("attributes", &u.attributes)
326}
327
328fn serialize_mailto<S>(m: &Mailto, map: &mut S::SerializeMap) -> Result<(), S::Error>
329where
330 S: Serializer,
331{
332 map.serialize_entry("name", "ref")?;
333 map.serialize_entry("type", "inline")?;
334 map.serialize_entry("variant", "mailto")?;
335 map.serialize_entry("target", &m.target)?;
336 map.serialize_entry("location", &m.location)?;
337 map.serialize_entry("attributes", &m.attributes)
338}
339
340fn serialize_link<S>(l: &Link, map: &mut S::SerializeMap) -> Result<(), S::Error>
341where
342 S: Serializer,
343{
344 map.serialize_entry("name", "ref")?;
345 map.serialize_entry("type", "inline")?;
346 map.serialize_entry("variant", "link")?;
347 map.serialize_entry("target", &l.target)?;
348 map.serialize_entry("location", &l.location)?;
349 map.serialize_entry("attributes", &l.attributes)
350}
351
352fn serialize_autolink<S>(a: &Autolink, map: &mut S::SerializeMap) -> Result<(), S::Error>
353where
354 S: Serializer,
355{
356 map.serialize_entry("name", "ref")?;
357 map.serialize_entry("type", "inline")?;
358 map.serialize_entry("variant", "autolink")?;
359 map.serialize_entry("target", &a.url)?;
360 map.serialize_entry("location", &a.location)
361}
362
363fn serialize_xref<S>(x: &CrossReference, map: &mut S::SerializeMap) -> Result<(), S::Error>
364where
365 S: Serializer,
366{
367 map.serialize_entry("name", "xref")?;
368 map.serialize_entry("type", "inline")?;
369 map.serialize_entry("target", &x.target)?;
370 if !x.text.is_empty() {
371 map.serialize_entry("inlines", &x.text)?;
372 }
373 map.serialize_entry("location", &x.location)
374}
375
376fn serialize_stem<S>(s: &Stem, map: &mut S::SerializeMap) -> Result<(), S::Error>
377where
378 S: Serializer,
379{
380 map.serialize_entry("name", "stem")?;
381 map.serialize_entry("type", "inline")?;
382 map.serialize_entry("content", &s.content)?;
383 map.serialize_entry("notation", &s.notation)?;
384 map.serialize_entry("location", &s.location)
385}
386
387fn serialize_indexterm<S>(i: &IndexTerm, map: &mut S::SerializeMap) -> Result<(), S::Error>
388where
389 S: Serializer,
390{
391 map.serialize_entry("name", "indexterm")?;
392 map.serialize_entry("type", "inline")?;
393 map.serialize_entry("term", i.term())?;
394 if let Some(secondary) = i.secondary() {
395 map.serialize_entry("secondary", secondary)?;
396 }
397 if let Some(tertiary) = i.tertiary() {
398 map.serialize_entry("tertiary", tertiary)?;
399 }
400 map.serialize_entry("visible", &i.is_visible())?;
401 map.serialize_entry("location", &i.location)
402}