use crate::md_elem::elem::*;
use std::borrow::Borrow;
#[derive(Default, Copy, Clone)]
pub(crate) struct InlineToStringOpts {
pub(crate) footnotes: FootnoteToString,
}
#[derive(Default, Copy, Clone)]
pub(crate) enum FootnoteToString {
#[default]
IncludeMarkdown,
OnlyFootnoteId,
}
pub(crate) fn inlines_to_plain_string<N: Borrow<Inline>>(inlines: &[N], opts: InlineToStringOpts) -> String {
let mut result = String::with_capacity(inlines.len() * 5); build_inlines(&mut result, inlines, opts);
result
}
fn build_inlines<N: Borrow<Inline>>(out: &mut String, inlines: &[N], opts: InlineToStringOpts) {
for inline in inlines {
build_inline(out, inline.borrow(), opts);
}
}
fn build_inline(out: &mut String, elem: &Inline, opts: InlineToStringOpts) {
match elem {
Inline::Span(Span { children, .. }) => build_inlines(out, children, opts),
Inline::Text(Text { value, .. }) => out.push_str(value),
Inline::Link(Link::Standard(standard_link)) => build_inlines(out, &standard_link.display, opts),
Inline::Link(Link::Autolink(autolink)) => out.push_str(&autolink.url),
Inline::Image(Image { alt, .. }) => out.push_str(alt),
Inline::Footnote(footnote) => {
if matches!(opts.footnotes, FootnoteToString::IncludeMarkdown) {
out.push_str("[^");
}
out.push_str(footnote.as_str());
if matches!(opts.footnotes, FootnoteToString::IncludeMarkdown) {
out.push(']');
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::md_elem::*;
use indoc::indoc;
use crate::util::utils_for_test::*;
variants_checker!(VARIANTS_CHECKER = Inline {
Span(Span{ variant: SpanVariant::Delete, .. }),
Span(Span{ variant: SpanVariant::Emphasis, .. }),
Span(Span{ variant: SpanVariant::Strong, .. }),
Text(Text { variant: TextVariant::Plain, .. }),
Text(Text { variant: TextVariant::Code, .. }),
Text(Text { variant: TextVariant::Math, .. }),
Text(Text { variant: TextVariant::InlineHtml, .. }),
Link { .. },
Image { .. },
Footnote(_),
});
#[test]
fn spans() {
check("_hello world_", "hello world");
check("**hello world**", "hello world");
check("~~hello world~~", "hello world");
}
#[test]
fn texts() {
check("hello world", "hello world");
check("`hello world`", "hello world");
check("$hello world$", "hello world");
}
#[test]
fn inline_html() {
let md_elems = MdDoc::parse("Hello <foo> world", &ParseOptions::gfm()).unwrap().roots;
unwrap!(&md_elems[0], MdElem::Paragraph(contents));
unwrap!(&contents.body[1], inline @ Inline::Text(_));
VARIANTS_CHECKER.see(inline);
let actual = inlines_to_plain_string(&contents.body, Default::default());
assert_eq!(&actual, "Hello <foo> world");
}
#[test]
fn links() {
check("[foo](https://example.com)", "foo");
check("[foo _with emphasis_](https://example.com)", "foo with emphasis");
check(
indoc! {r#"
[foo][1]
[1]: https://example.com"#},
"foo",
)
}
#[test]
fn images() {
check("", "foo");
check("", "foo with emphasis"); check(
indoc! {r#"
![foo][1]
[1]: https://example.com"#},
"foo",
)
}
#[test]
fn footnote() {
check(
indoc! {r#"
[^1]
[^1]: my footnote"#},
"[^1]",
)
}
fn check(md: &str, expect: &str) {
let mut options = ParseOptions::gfm();
options.mdast_options.constructs.math_text = true;
let md_elems = MdDoc::parse(md, &options).unwrap().roots;
unwrap!(&md_elems[0], MdElem::Paragraph(p));
p.body.iter().for_each(|inline| VARIANTS_CHECKER.see(inline));
let actual = inlines_to_plain_string(&p.body, Default::default());
assert_eq!(&actual, expect);
}
}