use serde::{
Serialize,
ser::{Error as _, SerializeMap, Serializer},
};
mod macros;
mod text;
pub use macros::*;
pub use text::*;
use crate::{Anchor, ElementAttributes, Image, Location, Source};
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum InlineNode<'a> {
PlainText(Plain<'a>),
RawText(Raw<'a>),
VerbatimText(Verbatim<'a>),
BoldText(Bold<'a>),
ItalicText(Italic<'a>),
MonospaceText(Monospace<'a>),
HighlightText(Highlight<'a>),
SubscriptText(Subscript<'a>),
SuperscriptText(Superscript<'a>),
CurvedQuotationText(CurvedQuotation<'a>),
CurvedApostropheText(CurvedApostrophe<'a>),
StandaloneCurvedApostrophe(StandaloneCurvedApostrophe),
LineBreak(LineBreak),
InlineAnchor(Anchor<'a>),
Macro(InlineMacro<'a>),
CalloutRef(CalloutRef),
}
impl InlineNode<'_> {
#[must_use]
pub fn location(&self) -> &Location {
match self {
InlineNode::PlainText(t) => &t.location,
InlineNode::RawText(t) => &t.location,
InlineNode::VerbatimText(t) => &t.location,
InlineNode::BoldText(t) => &t.location,
InlineNode::ItalicText(t) => &t.location,
InlineNode::MonospaceText(t) => &t.location,
InlineNode::HighlightText(t) => &t.location,
InlineNode::SubscriptText(t) => &t.location,
InlineNode::SuperscriptText(t) => &t.location,
InlineNode::CurvedQuotationText(t) => &t.location,
InlineNode::CurvedApostropheText(t) => &t.location,
InlineNode::StandaloneCurvedApostrophe(t) => &t.location,
InlineNode::LineBreak(l) => &l.location,
InlineNode::InlineAnchor(a) => &a.location,
InlineNode::Macro(m) => m.location(),
InlineNode::CalloutRef(c) => &c.location,
}
}
}
impl InlineNode<'_> {
pub(crate) fn location_mut(&mut self) -> &mut Location {
match self {
InlineNode::PlainText(t) => &mut t.location,
InlineNode::RawText(t) => &mut t.location,
InlineNode::VerbatimText(t) => &mut t.location,
InlineNode::BoldText(t) => &mut t.location,
InlineNode::ItalicText(t) => &mut t.location,
InlineNode::MonospaceText(t) => &mut t.location,
InlineNode::HighlightText(t) => &mut t.location,
InlineNode::SubscriptText(t) => &mut t.location,
InlineNode::SuperscriptText(t) => &mut t.location,
InlineNode::CurvedQuotationText(t) => &mut t.location,
InlineNode::CurvedApostropheText(t) => &mut t.location,
InlineNode::StandaloneCurvedApostrophe(t) => &mut t.location,
InlineNode::LineBreak(l) => &mut l.location,
InlineNode::InlineAnchor(a) => &mut a.location,
InlineNode::Macro(m) => m.location_mut(),
InlineNode::CalloutRef(c) => &mut c.location,
}
}
}
impl InlineMacro<'_> {
pub(crate) fn location_mut(&mut self) -> &mut Location {
match self {
Self::Footnote(f) => &mut f.location,
Self::Icon(i) => &mut i.location,
Self::Image(img) => &mut img.location,
Self::Keyboard(k) => &mut k.location,
Self::Button(b) => &mut b.location,
Self::Menu(m) => &mut m.location,
Self::Url(u) => &mut u.location,
Self::Mailto(m) => &mut m.location,
Self::Link(l) => &mut l.location,
Self::Autolink(a) => &mut a.location,
Self::CrossReference(x) => &mut x.location,
Self::Pass(p) => &mut p.location,
Self::Stem(s) => &mut s.location,
Self::IndexTerm(i) => &mut i.location,
}
}
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum InlineMacro<'a> {
Footnote(Footnote<'a>),
Icon(Icon<'a>),
Image(Box<Image<'a>>),
Keyboard(Keyboard<'a>),
Button(Button<'a>),
Menu(Menu<'a>),
Url(Url<'a>),
Link(Link<'a>),
Mailto(Mailto<'a>),
Autolink(Autolink<'a>),
CrossReference(CrossReference<'a>),
Pass(Pass<'a>),
Stem(Stem<'a>),
IndexTerm(Box<IndexTerm<'a>>),
}
impl InlineMacro<'_> {
#[must_use]
pub fn location(&self) -> &Location {
match self {
Self::Footnote(f) => &f.location,
Self::Icon(i) => &i.location,
Self::Image(img) => &img.location,
Self::Keyboard(k) => &k.location,
Self::Button(b) => &b.location,
Self::Menu(m) => &m.location,
Self::Url(u) => &u.location,
Self::Mailto(m) => &m.location,
Self::Link(l) => &l.location,
Self::Autolink(a) => &a.location,
Self::CrossReference(x) => &x.location,
Self::Pass(p) => &p.location,
Self::Stem(s) => &s.location,
Self::IndexTerm(i) => &i.location,
}
}
}
macro_rules! serialize_inline_format {
($map:expr, $value:expr, $variant:literal) => {{
$map.serialize_entry("name", "span")?;
$map.serialize_entry("type", "inline")?;
$map.serialize_entry("variant", $variant)?;
$map.serialize_entry("form", &$value.form)?;
if let Some(role) = &$value.role {
$map.serialize_entry("role", role)?;
}
if let Some(id) = &$value.id {
$map.serialize_entry("id", id)?;
}
$map.serialize_entry("inlines", &$value.content)?;
$map.serialize_entry("location", &$value.location)?;
}};
}
impl Serialize for InlineNode<'_> {
#[allow(clippy::too_many_lines)]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
match self {
InlineNode::PlainText(plain) => {
map.serialize_entry("name", "text")?;
map.serialize_entry("type", "string")?;
map.serialize_entry("value", &plain.content)?;
map.serialize_entry("location", &plain.location)?;
}
InlineNode::RawText(raw) => {
map.serialize_entry("name", "raw")?;
map.serialize_entry("type", "string")?;
map.serialize_entry("value", &raw.content)?;
map.serialize_entry("location", &raw.location)?;
}
InlineNode::VerbatimText(verbatim) => {
map.serialize_entry("name", "text")?;
map.serialize_entry("type", "string")?;
map.serialize_entry("value", &verbatim.content)?;
map.serialize_entry("location", &verbatim.location)?;
}
InlineNode::HighlightText(highlight) => {
serialize_inline_format!(map, highlight, "mark");
}
InlineNode::ItalicText(italic) => {
serialize_inline_format!(map, italic, "emphasis");
}
InlineNode::BoldText(bold) => {
serialize_inline_format!(map, bold, "strong");
}
InlineNode::MonospaceText(monospace) => {
serialize_inline_format!(map, monospace, "code");
}
InlineNode::SubscriptText(subscript) => {
serialize_inline_format!(map, subscript, "subscript");
}
InlineNode::SuperscriptText(superscript) => {
serialize_inline_format!(map, superscript, "superscript");
}
InlineNode::CurvedQuotationText(curved_quotation) => {
serialize_inline_format!(map, curved_quotation, "curved_quotation");
}
InlineNode::CurvedApostropheText(curved_apostrophe) => {
serialize_inline_format!(map, curved_apostrophe, "curved_apostrophe");
}
InlineNode::StandaloneCurvedApostrophe(standalone) => {
map.serialize_entry("name", "curved_apostrophe")?;
map.serialize_entry("type", "string")?;
map.serialize_entry("location", &standalone.location)?;
}
InlineNode::LineBreak(line_break) => {
map.serialize_entry("name", "break")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("location", &line_break.location)?;
}
InlineNode::InlineAnchor(anchor) => {
map.serialize_entry("name", "anchor")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("id", &anchor.id)?;
if let Some(xreflabel) = &anchor.xreflabel {
map.serialize_entry("xreflabel", xreflabel)?;
}
map.serialize_entry("location", &anchor.location)?;
}
InlineNode::Macro(macro_node) => {
serialize_inline_macro::<S>(macro_node, &mut map)?;
}
InlineNode::CalloutRef(callout_ref) => {
map.serialize_entry("name", "callout_reference")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("variant", &callout_ref.kind)?;
map.serialize_entry("number", &callout_ref.number)?;
map.serialize_entry("location", &callout_ref.location)?;
}
}
map.end()
}
}
fn serialize_inline_macro<S>(
macro_node: &InlineMacro<'_>,
map: &mut S::SerializeMap,
) -> Result<(), S::Error>
where
S: Serializer,
{
match macro_node {
InlineMacro::Footnote(f) => serialize_footnote::<S>(f, map),
InlineMacro::Icon(i) => serialize_icon::<S>(i, map),
InlineMacro::Image(i) => serialize_image::<S>(i, map),
InlineMacro::Keyboard(k) => serialize_keyboard::<S>(k, map),
InlineMacro::Button(b) => serialize_button::<S>(b, map),
InlineMacro::Menu(m) => serialize_menu::<S>(m, map),
InlineMacro::Url(u) => {
serialize_ref::<S>(map, "link", &u.target, &u.text, &u.attributes, &u.location)
}
InlineMacro::Mailto(m) => serialize_ref::<S>(
map,
"mailto",
&m.target,
&m.text,
&m.attributes,
&m.location,
),
InlineMacro::Link(l) => {
serialize_ref::<S>(map, "link", &l.target, &l.text, &l.attributes, &l.location)
}
InlineMacro::Autolink(a) => serialize_autolink::<S>(a, map),
InlineMacro::CrossReference(x) => serialize_xref::<S>(x, map),
InlineMacro::Stem(s) => serialize_stem::<S>(s, map),
InlineMacro::IndexTerm(i) => serialize_indexterm::<S>(i, map),
InlineMacro::Pass(_) => Err(S::Error::custom(
"inline passthrough macros are not part of the ASG specification and cannot be serialized",
)),
}
}
fn serialize_footnote<S>(f: &Footnote<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "footnote")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("id", &f.id)?;
map.serialize_entry("inlines", &f.content)?;
map.serialize_entry("location", &f.location)
}
fn serialize_icon<S>(i: &Icon<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "icon")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("target", &i.target)?;
if !i.attributes.is_empty() {
map.serialize_entry("attributes", &i.attributes)?;
}
map.serialize_entry("location", &i.location)
}
fn serialize_image<S>(i: &Image<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "image")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("title", &i.title)?;
map.serialize_entry("target", &i.source)?;
map.serialize_entry("location", &i.location)
}
fn serialize_keyboard<S>(k: &Keyboard<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "keyboard")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("keys", &k.keys)?;
map.serialize_entry("location", &k.location)
}
fn serialize_button<S>(b: &Button<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "button")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("label", &b.label)?;
map.serialize_entry("location", &b.location)
}
fn serialize_menu<S>(m: &Menu<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "menu")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("target", &m.target)?;
if !m.items.is_empty() {
map.serialize_entry("items", &m.items)?;
}
map.serialize_entry("location", &m.location)
}
fn serialize_ref<S>(
map: &mut S::SerializeMap,
variant: &str,
target: &Source<'_>,
text: &[InlineNode<'_>],
attributes: &ElementAttributes<'_>,
location: &Location,
) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "ref")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("variant", variant)?;
map.serialize_entry("target", target)?;
if !text.is_empty() {
map.serialize_entry("inlines", text)?;
}
map.serialize_entry("location", location)?;
map.serialize_entry("attributes", attributes)
}
fn serialize_autolink<S>(a: &Autolink<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "ref")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("variant", "autolink")?;
map.serialize_entry("target", &a.url)?;
map.serialize_entry("location", &a.location)
}
fn serialize_xref<S>(x: &CrossReference<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "xref")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("target", &x.target)?;
if !x.text.is_empty() {
map.serialize_entry("inlines", &x.text)?;
}
map.serialize_entry("location", &x.location)
}
fn serialize_stem<S>(s: &Stem<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "stem")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("content", &s.content)?;
map.serialize_entry("notation", &s.notation)?;
map.serialize_entry("location", &s.location)
}
fn serialize_indexterm<S>(i: &IndexTerm<'_>, map: &mut S::SerializeMap) -> Result<(), S::Error>
where
S: Serializer,
{
map.serialize_entry("name", "indexterm")?;
map.serialize_entry("type", "inline")?;
map.serialize_entry("term", i.term())?;
if let Some(secondary) = i.secondary() {
map.serialize_entry("secondary", secondary)?;
}
if let Some(tertiary) = i.tertiary() {
map.serialize_entry("tertiary", tertiary)?;
}
if let Some(relationship) = &i.relationship {
map.serialize_entry("relationship", relationship)?;
}
if let Some(catalog) = &i.catalog {
map.serialize_entry("catalog", catalog)?;
}
map.serialize_entry("visible", &i.is_visible())?;
map.serialize_entry("location", &i.location)
}