Skip to main content

iced_markdown_editor/markdown/
mod.rs

1//! CommonMark import and export.
2//!
3//! Import uses `pulldown-cmark`, a CommonMark 0.31.2 parser, with three GFM
4//! extensions: `~~strikethrough~~`, task lists (`- [ ]` / `- [x]`) and
5//! tables.
6//! Export writes Markdown that parses back to the same document; the round
7//! trip is tested against all 652 examples of the CommonMark 0.31.2
8//! specification and against random documents (see `tests.rs`).
9//!
10//! Inline formatting is written with Markdown delimiters (`**`, `*`, `~~`)
11//! when they survive a round trip. CommonMark's delimiter rules cannot
12//! express every combination (e.g. bold text that starts with punctuation
13//! directly after a letter), so such paragraphs fall back to
14//! `<strong>`, `<em>` and `<del>` tags, which the importer recognises.
15//!
16//! * `import`: Markdown to a document.
17//! * `inline`: inline content to Markdown.
18//! * `export`: blocks and containers to Markdown.
19//! * `table`: GFM tables to Markdown.
20
21mod export;
22mod import;
23mod inline;
24mod table;
25#[cfg(test)]
26mod tests;
27
28pub use export::serialize;
29pub use import::{parse, parse_with};
30pub(crate) use inline::image_markdown;
31
32use crate::doc::{Flag, SOFT};
33use crate::features::Features;
34use pulldown_cmark::Options;
35
36fn options(features: Features) -> Options {
37    let mut options = Options::empty();
38    options.set(Options::ENABLE_STRIKETHROUGH, features.strikethrough);
39    options.set(Options::ENABLE_TASKLISTS, features.task_lists);
40    options.set(Options::ENABLE_TABLES, features.tables);
41    options
42}
43
44fn is_break(c: char) -> bool {
45    c == '\n' || c == SOFT
46}
47
48/// Inline HTML tags that map to formatting (the export fallback).
49fn html_flag(tag: &str) -> Option<(Flag, bool)> {
50    match tag {
51        "<strong>" => Some((Flag::Bold, false)),
52        "</strong>" => Some((Flag::Bold, true)),
53        "<em>" => Some((Flag::Italic, false)),
54        "</em>" => Some((Flag::Italic, true)),
55        "<del>" => Some((Flag::Strike, false)),
56        "</del>" => Some((Flag::Strike, true)),
57        _ => None,
58    }
59}
60
61fn flag_bit(flag: Flag) -> u8 {
62    match flag {
63        Flag::Bold => 1,
64        Flag::Italic => 2,
65        Flag::Strike => 4,
66        Flag::Code => 8,
67    }
68}
69
70fn flag_tag(flag: Flag, closing: bool) -> &'static str {
71    match (flag, closing) {
72        (Flag::Bold, false) => "<strong>",
73        (Flag::Bold, true) => "</strong>",
74        (Flag::Italic, false) => "<em>",
75        (Flag::Italic, true) => "</em>",
76        (Flag::Strike, false) => "<del>",
77        (Flag::Strike, true) => "</del>",
78        (Flag::Code, false) => "<code>",
79        (Flag::Code, true) => "</code>",
80    }
81}
82
83/// Parses and writes Markdown again, in the form the editor saves it.
84pub fn normalize(markdown: &str) -> String {
85    serialize(&parse(markdown))
86}