Skip to main content

base_markdown/
configuration.rs

1// Released under the MIT License.
2// Copyright, 2022, by Bernhard Berger.
3// Copyright, 2022-2025, by Titus Wormer.
4// Copyright, 2023, by cel.
5// Copyright, 2025, by Ophir Lojkine.
6// Copyright, 2026, by Samuel Williams.
7
8use crate::util::line_ending::LineEnding;
9use alloc::string::String;
10
11/// Control which constructs are enabled.
12///
13/// Not all constructs can be configured.
14/// Notably, blank lines and paragraphs cannot be turned off.
15///
16/// ## Examples
17///
18/// ```
19/// use base_markdown::Constructs;
20/// # fn main() {
21///
22/// // Use the default trait to get `CommonMark` constructs:
23/// let commonmark = Constructs::default();
24///
25/// // To turn on all of GFM, use the `gfm` method:
26/// let gfm = Constructs::gfm();
27///
28/// // Or, mix and match:
29/// let custom = Constructs {
30///   math_flow: true,
31///   math_text: true,
32///   ..Constructs::gfm()
33/// };
34/// # }
35/// ```
36#[allow(clippy::struct_excessive_bools)]
37#[derive(Clone, Debug, Eq, PartialEq)]
38#[cfg_attr(
39    feature = "serde",
40    derive(serde::Serialize, serde::Deserialize),
41    serde(rename_all = "camelCase")
42)]
43pub struct Constructs {
44    /// Attention.
45    ///
46    /// ```markdown
47    /// > | a *b* c **d**.
48    ///       ^^^   ^^^^^
49    /// ```
50    pub attention: bool,
51    /// Autolink.
52    ///
53    /// ```markdown
54    /// > | a <https://example.com> b <user@example.org>.
55    ///       ^^^^^^^^^^^^^^^^^^^^^   ^^^^^^^^^^^^^^^^^^
56    /// ```
57    pub autolink: bool,
58    /// Block quote.
59    ///
60    /// ```markdown
61    /// > | > a
62    ///     ^^^
63    /// ```
64    pub block_quote: bool,
65    /// Character escape.
66    ///
67    /// ```markdown
68    /// > | a \* b
69    ///       ^^
70    /// ```
71    pub character_escape: bool,
72    /// Character reference.
73    ///
74    /// ```markdown
75    /// > | a &amp; b
76    ///       ^^^^^
77    /// ```
78    pub character_reference: bool,
79    /// Code (indented).
80    ///
81    /// ```markdown
82    /// > |     a
83    ///     ^^^^^
84    /// ```
85    pub code_indented: bool,
86    /// Code (fenced).
87    ///
88    /// ```markdown
89    /// > | ~~~js
90    ///     ^^^^^
91    /// > | console.log(1)
92    ///     ^^^^^^^^^^^^^^
93    /// > | ~~~
94    ///     ^^^
95    /// ```
96    pub code_fenced: bool,
97    /// Code (text).
98    ///
99    /// ```markdown
100    /// > | a `b` c
101    ///       ^^^
102    /// ```
103    pub code_text: bool,
104    /// Definition.
105    ///
106    /// ```markdown
107    /// > | [a]: b "c"
108    ///     ^^^^^^^^^^
109    /// ```
110    pub definition: bool,
111    /// Frontmatter with an unindented opening fence on the first document line,
112    /// outside list items and blockquotes, and a matching closing fence.
113    ///
114    /// Frontmatter is language-agnostic, following [Markly](https://github.com/socketry/markly) and [cmarkly](https://github.com/socketry/cmarkly). All forms produce a source-backed opaque node with [`NodeSyntax::Frontmatter`][crate::NodeSyntax::Frontmatter] metadata. Untagged `---` and `+++` imply YAML and TOML, with an empty info string. A format hint after either delimiter (such as `--- json` or `---yaml`), or a language-tagged backtick/tilde code fence, supplies an explicit language. The parser preserves the raw body and the full authored info string without parsing the named format; [`Document::frontmatter_value`][crate::Document::frontmatter_value] returns the body. HTML omits the frontmatter. Backtick/tilde fences without a language, fences later in a document, and unclosed fences remain ordinary Markdown.
115    ///
116    /// Markdown serialization retains the stored info string and body while normalizing spacing around the info string. It can lengthen fences or change their marker when edited content would close them prematurely, writing an implied language explicitly when replacing an untagged dash/plus delimiter, and adds a line ending before the closing fence when an edited body needs one. Round trips preserve the body and effective language.
117    ///
118    /// ````
119    /// use base_markdown::{Constructs, Document, NodeSyntax, ParseOptions};
120    ///
121    /// let options = ParseOptions {
122    ///     constructs: Constructs { frontmatter: true, ..Constructs::default() },
123    ///     ..ParseOptions::default()
124    /// };
125    /// let document = Document::parse("```json title=example\n{\"draft\": true}\n```\n\n# Hello", &options).unwrap();
126    /// let node = document.children(document.root()).next().unwrap();
127    /// let Some(NodeSyntax::Frontmatter { info, .. }) = document.syntax(node) else { panic!() };
128    /// assert_eq!(info.resolve(document.source()).unwrap(), "json title=example");
129    /// assert_eq!(document.frontmatter_value(node), Some("{\"draft\": true}\n"));
130    /// ````
131    ///
132    /// ````markdown
133    /// > | ---
134    ///     ^^^
135    /// > | title: Neptune
136    ///     ^^^^^^^^^^^^^^
137    /// > | ---
138    ///     ^^^
139    /// ````
140    pub frontmatter: bool,
141    /// Autolink literal.
142    ///
143    /// ```markdown
144    /// > | https://example.com
145    ///     ^^^^^^^^^^^^^^^^^^^
146    /// ```
147    pub autolink_literal: bool,
148    /// Footnote definition.
149    ///
150    /// ```markdown
151    /// > | [^a]: b
152    ///     ^^^^^^^
153    /// ```
154    pub footnote_definition: bool,
155    /// Footnote label start.
156    ///
157    /// ```markdown
158    /// > | a[^b]
159    ///      ^^
160    /// ```
161    pub label_start_footnote: bool,
162    /// Strikethrough.
163    ///
164    /// ```markdown
165    /// > | a ~b~ c.
166    ///       ^^^
167    /// ```
168    pub strikethrough: bool,
169    /// Table.
170    ///
171    /// ```markdown
172    /// > | | a |
173    ///     ^^^^^
174    /// > | | - |
175    ///     ^^^^^
176    /// > | | b |
177    ///     ^^^^^
178    /// ```
179    pub table: bool,
180    /// Task list item.
181    ///
182    /// ```markdown
183    /// > | * [x] y.
184    ///       ^^^
185    /// ```
186    pub task_list_item: bool,
187    /// Hard break (escape).
188    ///
189    /// ```markdown
190    /// > | a\
191    ///      ^
192    ///   | b
193    /// ```
194    pub hard_break_escape: bool,
195    /// Hard break (trailing).
196    ///
197    /// ```markdown
198    /// > | a␠␠
199    ///      ^^
200    ///   | b
201    /// ```
202    pub hard_break_trailing: bool,
203    /// Heading (atx).
204    ///
205    /// ```markdown
206    /// > | # a
207    ///     ^^^
208    /// ```
209    pub heading_atx: bool,
210    /// Heading (setext).
211    ///
212    /// ```markdown
213    /// > | a
214    ///     ^^
215    /// > | ==
216    ///     ^^
217    /// ```
218    pub heading_setext: bool,
219    /// HTML (flow).
220    ///
221    /// ```markdown
222    /// > | <div>
223    ///     ^^^^^
224    /// ```
225    pub html_flow: bool,
226    /// HTML (text).
227    ///
228    /// ```markdown
229    /// > | a <b> c
230    ///       ^^^
231    /// ```
232    pub html_text: bool,
233    /// Label start (image).
234    ///
235    /// ```markdown
236    /// > | a ![b](c) d
237    ///       ^^
238    /// ```
239    pub label_start_image: bool,
240    /// Label start (link).
241    ///
242    /// ```markdown
243    /// > | a [b](c) d
244    ///       ^
245    /// ```
246    pub label_start_link: bool,
247    /// Label end.
248    ///
249    /// ```markdown
250    /// > | a [b](c) d
251    ///         ^^^^
252    /// ```
253    pub label_end: bool,
254    /// List items.
255    ///
256    /// ```markdown
257    /// > | * a
258    ///     ^^^
259    /// ```
260    pub list_item: bool,
261    /// Math (flow).
262    ///
263    /// ```markdown
264    /// > | $$
265    ///     ^^
266    /// > | \frac{1}{2}
267    ///     ^^^^^^^^^^^
268    /// > | $$
269    ///     ^^
270    /// ```
271    pub math_flow: bool,
272    /// Math (text).
273    ///
274    /// ```markdown
275    /// > | a $b$ c
276    ///       ^^^
277    /// ```
278    pub math_text: bool,
279
280    /// Thematic break.
281    ///
282    /// ```markdown
283    /// > | ***
284    ///     ^^^
285    /// ```
286    pub thematic_break: bool,
287}
288
289impl Default for Constructs {
290    /// `CommonMark`.
291    ///
292    /// `CommonMark` is a relatively strong specification of how markdown
293    /// works.
294    /// Most markdown parsers try to follow it.
295    ///
296    /// For more information, see the `CommonMark` specification:
297    /// <https://spec.commonmark.org>.
298    fn default() -> Self {
299        Self {
300            attention: true,
301            autolink: true,
302            block_quote: true,
303            character_escape: true,
304            character_reference: true,
305            code_indented: true,
306            code_fenced: true,
307            code_text: true,
308            definition: true,
309            frontmatter: false,
310            autolink_literal: false,
311            label_start_footnote: false,
312            footnote_definition: false,
313            strikethrough: false,
314            table: false,
315            task_list_item: false,
316            hard_break_escape: true,
317            hard_break_trailing: true,
318            heading_atx: true,
319            heading_setext: true,
320            html_flow: true,
321            html_text: true,
322            label_start_image: true,
323            label_start_link: true,
324            label_end: true,
325            list_item: true,
326            math_flow: false,
327            math_text: false,
328
329            thematic_break: true,
330        }
331    }
332}
333
334impl Constructs {
335    /// GFM.
336    ///
337    /// GFM stands for **GitHub flavored markdown**.
338    /// GFM extends `CommonMark` and adds support for autolink literals,
339    /// footnotes, strikethrough, tables, and tasklists.
340    ///
341    /// For more information, see the GFM specification:
342    /// <https://github.github.com/gfm/>.
343    pub fn gfm() -> Self {
344        Self {
345            autolink_literal: true,
346            footnote_definition: true,
347            label_start_footnote: true,
348            strikethrough: true,
349            table: true,
350            task_list_item: true,
351            ..Self::default()
352        }
353    }
354}
355
356/// Configuration that describes how to compile to HTML.
357///
358/// You likely either want to turn on the dangerous options
359/// (`allow_dangerous_html`, `allow_dangerous_protocol`) when dealing with
360/// input you trust, or want to customize how footnotes are compiled
361/// (typically because the input markdown is not in English).
362///
363/// ## Examples
364///
365/// ```
366/// use base_markdown::CompileOptions;
367/// # fn main() {
368///
369/// // Use the default trait to get safe defaults:
370/// let safe = CompileOptions::default();
371///
372/// // Live dangerously / trust the author:
373/// let danger = CompileOptions {
374///   allow_dangerous_html: true,
375///   allow_dangerous_protocol: true,
376///   ..CompileOptions::default()
377/// };
378///
379/// // In French:
380/// let enFrançais = CompileOptions {
381///   footnote_back_label: Some("Arrière".into()),
382///   footnote_label: Some("Notes de bas de page".into()),
383///   ..CompileOptions::default()
384/// };
385/// # }
386/// ```
387#[allow(clippy::struct_excessive_bools)]
388#[derive(Clone, Debug, Default)]
389#[cfg_attr(
390    feature = "serde",
391    derive(serde::Serialize, serde::Deserialize),
392    serde(default, rename_all = "camelCase")
393)]
394pub struct CompileOptions {
395    /// Whether to allow all values in images.
396    ///
397    /// The default is `false`,
398    /// which lets `allow_dangerous_protocol` control protocol safety for
399    /// both links and images.
400    ///
401    /// Pass `true` to allow all values as `src` on images,
402    /// regardless of `allow_dangerous_protocol`.
403    /// This is safe because the
404    /// [HTML specification][whatwg-html-image-processing]
405    /// does not allow executable code in images.
406    ///
407    /// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model
408    ///
409    /// ## Examples
410    ///
411    /// ```
412    /// use base_markdown::{to_html_with_options, CompileOptions, Options};
413    ///
414    /// // By default, some protocols in image sources are dropped:
415    /// assert_eq!(
416    ///     to_html_with_options(
417    ///         "![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)",
418    ///         &Options::default()
419    ///     ),
420    ///     "<p><img src=\"\" alt=\"\" /></p>"
421    /// );
422    ///
423    /// // Turn `allow_any_img_src` on to allow all values as `src` on images.
424    /// // This is safe because browsers do not execute code in images.
425    /// assert_eq!(
426    ///     to_html_with_options(
427    ///         "![](javascript:alert(1))",
428    ///         &Options {
429    ///             compile: CompileOptions {
430    ///               allow_any_img_src: true,
431    ///               ..CompileOptions::default()
432    ///             },
433    ///             ..Options::default()
434    ///         }
435    ///     ),
436    ///     "<p><img src=\"javascript:alert(1)\" alt=\"\" /></p>"
437    /// );
438    /// ```
439    pub allow_any_img_src: bool,
440
441    /// Whether to allow (dangerous) HTML.
442    ///
443    /// The default is `false`, which still parses the HTML according to
444    /// `CommonMark` but shows the HTML as text instead of as elements.
445    ///
446    /// Pass `true` for trusted content to get actual HTML elements.
447    ///
448    /// When using GFM, make sure to also turn off `tagfilter`.
449    /// Otherwise, some dangerous HTML is still ignored.
450    ///
451    /// ## Examples
452    ///
453    /// ```
454    /// use base_markdown::{to_html, to_html_with_options, CompileOptions, Options};
455    ///
456    /// // `markdown-rs` is safe by default:
457    /// assert_eq!(
458    ///     to_html("Hi, <i>venus</i>!"),
459    ///     "<p>Hi, &lt;i&gt;venus&lt;/i&gt;!</p>"
460    /// );
461    ///
462    /// // Turn `allow_dangerous_html` on to allow potentially dangerous HTML:
463    /// assert_eq!(
464    ///     to_html_with_options(
465    ///         "Hi, <i>venus</i>!",
466    ///         &Options {
467    ///             compile: CompileOptions {
468    ///               allow_dangerous_html: true,
469    ///               ..CompileOptions::default()
470    ///             },
471    ///             ..Options::default()
472    ///         }
473    ///     ),
474    ///     "<p>Hi, <i>venus</i>!</p>"
475    /// );
476    /// ```
477    pub allow_dangerous_html: bool,
478
479    /// Whether to allow dangerous protocols in links and images.
480    ///
481    /// The default is `false`, which drops URLs in links and images that use
482    /// dangerous protocols.
483    ///
484    /// Pass `true` for trusted content to support all protocols.
485    ///
486    /// URLs that have no protocol (which means it’s relative to the current
487    /// page, such as `./some/page.html`) and URLs that have a safe protocol
488    /// (for images: `http`, `https`; for links: `http`, `https`, `irc`,
489    /// `ircs`, `mailto`, `xmpp`), are safe.
490    /// All other URLs are dangerous and dropped.
491    ///
492    /// When the option `allow_all_protocols_in_img` is enabled,
493    /// `allow_dangerous_protocol` only applies to links.
494    ///
495    /// This is safe because the
496    /// [HTML specification][whatwg-html-image-processing]
497    /// does not allow executable code in images.
498    /// All modern browsers respect this.
499    ///
500    /// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model
501    ///
502    /// ## Examples
503    ///
504    /// ```
505    /// use base_markdown::{to_html, to_html_with_options, CompileOptions, Options};
506    ///
507    /// // `markdown-rs` is safe by default:
508    /// assert_eq!(
509    ///     to_html("<javascript:alert(1)>"),
510    ///     "<p><a href=\"\">javascript:alert(1)</a></p>"
511    /// );
512    ///
513    /// // Turn `allow_dangerous_protocol` on to allow potentially dangerous protocols:
514    /// assert_eq!(
515    ///     to_html_with_options(
516    ///         "<javascript:alert(1)>",
517    ///         &Options {
518    ///             compile: CompileOptions {
519    ///               allow_dangerous_protocol: true,
520    ///               ..CompileOptions::default()
521    ///             },
522    ///             ..Options::default()
523    ///         }
524    ///     ),
525    ///     "<p><a href=\"javascript:alert(1)\">javascript:alert(1)</a></p>"
526    /// );
527    /// ```
528    pub allow_dangerous_protocol: bool,
529
530    // To do: `doc_markdown` is broken.
531    #[allow(clippy::doc_markdown)]
532    /// Default line ending to use when compiling to HTML, for line endings not
533    /// in `value`.
534    ///
535    /// Generally, `markdown-rs` copies line endings (`\r`, `\n`, `\r\n`) in
536    /// the markdown document over to the compiled HTML.
537    /// In some cases, such as `> a`, CommonMark requires that extra line
538    /// endings are added: `<blockquote>\n<p>a</p>\n</blockquote>`.
539    ///
540    /// To create that line ending, the document is checked for the first line
541    /// ending that is used.
542    /// If there is no line ending, `default_line_ending` is used.
543    /// If that isn’t configured, `\n` is used.
544    ///
545    /// ## Examples
546    ///
547    /// ```
548    /// use base_markdown::{to_html, to_html_with_options, CompileOptions, LineEnding, Options};
549    ///
550    /// // `markdown-rs` uses `\n` by default:
551    /// assert_eq!(
552    ///     to_html("> a"),
553    ///     "<blockquote>\n<p>a</p>\n</blockquote>"
554    /// );
555    ///
556    /// // Define `default_line_ending` to configure the default:
557    /// assert_eq!(
558    ///     to_html_with_options(
559    ///         "> a",
560    ///         &Options {
561    ///             compile: CompileOptions {
562    ///               default_line_ending: LineEnding::CarriageReturnLineFeed,
563    ///               ..CompileOptions::default()
564    ///             },
565    ///             ..Options::default()
566    ///         }
567    ///     ),
568    ///     "<blockquote>\r\n<p>a</p>\r\n</blockquote>"
569    /// );
570    /// ```
571    pub default_line_ending: LineEnding,
572
573    /// Textual label to describe the backreference back to footnote calls.
574    ///
575    /// The default value is `"Back to content"`.
576    /// Change it when the markdown is not in English.
577    ///
578    /// This label is used in the `aria-label` attribute on each backreference
579    /// (the `↩` links).
580    /// It affects users of assistive technology.
581    ///
582    /// ## Examples
583    ///
584    /// ```
585    /// use base_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
586    ///
587    /// // `"Back to content"` is used by default:
588    /// assert_eq!(
589    ///     to_html_with_options(
590    ///         "[^a]\n\n[^a]: b",
591    ///         &Options::gfm()
592    ///     ),
593    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
594    /// );
595    ///
596    /// // Pass `footnote_back_label` to use something else:
597    /// assert_eq!(
598    ///     to_html_with_options(
599    ///         "[^a]\n\n[^a]: b",
600    ///         &Options {
601    ///             parse: ParseOptions::gfm(),
602    ///             compile: CompileOptions {
603    ///               footnote_back_label: Some("Arrière".into()),
604    ///               ..CompileOptions::gfm()
605    ///             }
606    ///         }
607    ///     ),
608    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Arrière\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
609    /// );
610    /// ```
611    pub footnote_back_label: Option<String>,
612
613    /// Prefix to use before the `id` attribute on footnotes to prevent them
614    /// from *clobbering*.
615    ///
616    /// The default is `"user-content-"`.
617    /// Pass `Some("".into())` for trusted markdown and when you are careful
618    /// with polyfilling.
619    /// You could pass a different prefix.
620    ///
621    /// DOM clobbering is this:
622    ///
623    /// ```html
624    /// <p id="x"></p>
625    /// <script>alert(x) // `x` now refers to the `p#x` DOM element</script>
626    /// ```
627    ///
628    /// The above example shows that elements are made available by browsers,
629    /// by their ID, on the `window` object.
630    /// This is a security risk because you might be expecting some other
631    /// variable at that place.
632    /// It can also break polyfills.
633    /// Using a prefix solves these problems.
634    ///
635    /// ## Examples
636    ///
637    /// ```
638    /// use base_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
639    ///
640    /// // `"user-content-"` is used by default:
641    /// assert_eq!(
642    ///     to_html_with_options(
643    ///         "[^a]\n\n[^a]: b",
644    ///         &Options::gfm()
645    ///     ),
646    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
647    /// );
648    ///
649    /// // Pass `footnote_clobber_prefix` to use something else:
650    /// assert_eq!(
651    ///     to_html_with_options(
652    ///         "[^a]\n\n[^a]: b",
653    ///         &Options {
654    ///             parse: ParseOptions::gfm(),
655    ///             compile: CompileOptions {
656    ///               footnote_clobber_prefix: Some("".into()),
657    ///               ..CompileOptions::gfm()
658    ///             }
659    ///         }
660    ///     ),
661    ///     "<p><sup><a href=\"#fn-a\" id=\"fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"fn-a\">\n<p>b <a href=\"#fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
662    /// );
663    /// ```
664    pub footnote_clobber_prefix: Option<String>,
665
666    /// Attributes to use on the footnote label.
667    ///
668    /// The default value is `"class=\"sr-only\""`.
669    /// Change it to show the label and add other attributes.
670    ///
671    /// This label is typically hidden visually (assuming a `sr-only` CSS class
672    /// is defined that does that), and thus affects screen readers only.
673    /// If you do have such a class, but want to show this section to everyone,
674    /// pass an empty string.
675    /// You can also add different attributes.
676    ///
677    /// > 👉 **Note**: `id="footnote-label"` is always added, because footnote
678    /// > calls use it with `aria-describedby` to provide an accessible label.
679    ///
680    /// ## Examples
681    ///
682    /// ```
683    /// use base_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
684    ///
685    /// // `"class=\"sr-only\""` is used by default:
686    /// assert_eq!(
687    ///     to_html_with_options(
688    ///         "[^a]\n\n[^a]: b",
689    ///         &Options::gfm()
690    ///     ),
691    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
692    /// );
693    ///
694    /// // Pass `footnote_label_attributes` to use something else:
695    /// assert_eq!(
696    ///     to_html_with_options(
697    ///         "[^a]\n\n[^a]: b",
698    ///         &Options {
699    ///             parse: ParseOptions::gfm(),
700    ///             compile: CompileOptions {
701    ///               footnote_label_attributes: Some("class=\"footnote-heading\"".into()),
702    ///               ..CompileOptions::gfm()
703    ///             }
704    ///         }
705    ///     ),
706    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"footnote-heading\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
707    /// );
708    /// ```
709    pub footnote_label_attributes: Option<String>,
710
711    /// HTML tag name to use for the footnote label element.
712    ///
713    /// The default value is `"h2"`.
714    /// Change it to match your document structure.
715    ///
716    /// This label is typically hidden visually (assuming a `sr-only` CSS class
717    /// is defined that does that), and thus affects screen readers only.
718    /// If you do have such a class, but want to show this section to everyone,
719    /// pass different attributes with the `footnote_label_attributes`
720    /// option.
721    ///
722    /// ## Examples
723    ///
724    /// ```
725    /// use base_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
726    ///
727    /// // `"h2"` is used by default:
728    /// assert_eq!(
729    ///     to_html_with_options(
730    ///         "[^a]\n\n[^a]: b",
731    ///         &Options::gfm()
732    ///     ),
733    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
734    /// );
735    ///
736    /// // Pass `footnote_label_tag_name` to use something else:
737    /// assert_eq!(
738    ///     to_html_with_options(
739    ///         "[^a]\n\n[^a]: b",
740    ///         &Options {
741    ///             parse: ParseOptions::gfm(),
742    ///             compile: CompileOptions {
743    ///               footnote_label_tag_name: Some("h1".into()),
744    ///               ..CompileOptions::gfm()
745    ///             }
746    ///         }
747    ///     ),
748    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h1 id=\"footnote-label\" class=\"sr-only\">Footnotes</h1>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
749    /// );
750    /// ```
751    pub footnote_label_tag_name: Option<String>,
752
753    /// Textual label to use for the footnotes section.
754    ///
755    /// The default value is `"Footnotes"`.
756    /// Change it when the markdown is not in English.
757    ///
758    /// This label is typically hidden visually (assuming a `sr-only` CSS class
759    /// is defined that does that), and thus affects screen readers only.
760    /// If you do have such a class, but want to show this section to everyone,
761    /// pass different attributes with the `footnote_label_attributes`
762    /// option.
763    ///
764    /// ## Examples
765    ///
766    /// ```
767    /// use base_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
768    ///
769    /// // `"Footnotes"` is used by default:
770    /// assert_eq!(
771    ///     to_html_with_options(
772    ///         "[^a]\n\n[^a]: b",
773    ///         &Options::gfm()
774    ///     ),
775    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Footnotes</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
776    /// );
777    ///
778    /// // Pass `footnote_label` to use something else:
779    /// assert_eq!(
780    ///     to_html_with_options(
781    ///         "[^a]\n\n[^a]: b",
782    ///         &Options {
783    ///             parse: ParseOptions::gfm(),
784    ///             compile: CompileOptions {
785    ///               footnote_label: Some("Notes de bas de page".into()),
786    ///               ..CompileOptions::gfm()
787    ///             }
788    ///         }
789    ///     ),
790    ///     "<p><sup><a href=\"#user-content-fn-a\" id=\"user-content-fnref-a\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">1</a></sup></p>\n<section data-footnotes=\"\" class=\"footnotes\"><h2 id=\"footnote-label\" class=\"sr-only\">Notes de bas de page</h2>\n<ol>\n<li id=\"user-content-fn-a\">\n<p>b <a href=\"#user-content-fnref-a\" data-footnote-backref=\"\" aria-label=\"Back to content\" class=\"data-footnote-backref\">↩</a></p>\n</li>\n</ol>\n</section>\n"
791    /// );
792    /// ```
793    pub footnote_label: Option<String>,
794
795    /// Whether or not task list HTML `<input>` items are enabled.
796    ///
797    /// This determines whether or not the user of the browser is able
798    /// to click and toggle generated checkbox items. The default is false.
799    ///
800    /// ## Examples
801    ///
802    /// ```
803    /// use base_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
804    ///
805    /// // With `task_list_item_checkable`, generated `<input type="checkbox" />`
806    /// // tags do not contain the attribute `disabled=""` and are thus toggleable by
807    /// // browser users.
808    /// assert_eq!(
809    ///     to_html_with_options(
810    ///         "* [x] y.",
811    ///         &Options {
812    ///             parse: ParseOptions::gfm(),
813    ///             compile: CompileOptions {
814    ///                 task_list_item_checkable: true,
815    ///                 ..CompileOptions::gfm()
816    ///             }
817    ///         }
818    ///     ),
819    ///     "<ul>\n<li><input type=\"checkbox\" checked=\"\" /> y.</li>\n</ul>"
820    /// );
821    /// ```
822    pub task_list_item_checkable: bool,
823
824    /// Whether to filter disallowed raw HTML tags.
825    ///
826    /// This option does nothing if `allow_dangerous_html` is not turned on.
827    /// The default is `false`, which does not filter raw HTML tags.
828    /// Pass `true` for output that is a bit closer to GitHub’s actual output.
829    ///
830    /// The tagfilter is kinda weird and kinda useless.
831    /// The tag filter is a naïve attempt at XSS protection.
832    /// You should use a proper HTML sanitizing algorithm instead.
833    ///
834    /// ## Examples
835    ///
836    /// ```
837    /// use base_markdown::{to_html_with_options, CompileOptions, Options, ParseOptions};
838    ///
839    /// // With `allow_dangerous_html`, `markdown-rs` passes HTML through untouched:
840    /// assert_eq!(
841    ///     to_html_with_options(
842    ///         "<iframe>",
843    ///         &Options {
844    ///             parse: ParseOptions::gfm(),
845    ///             compile: CompileOptions {
846    ///               allow_dangerous_html: true,
847    ///               ..CompileOptions::default()
848    ///             }
849    ///         }
850    ///     ),
851    ///     "<iframe>"
852    /// );
853    ///
854    /// // Pass `tagfilter: true` to make some of that safe:
855    /// assert_eq!(
856    ///     to_html_with_options(
857    ///         "<iframe>",
858    ///         &Options {
859    ///             parse: ParseOptions::gfm(),
860    ///             compile: CompileOptions {
861    ///               allow_dangerous_html: true,
862    ///               tagfilter: true,
863    ///               ..CompileOptions::default()
864    ///             }
865    ///         }
866    ///     ),
867    ///     "&lt;iframe>"
868    /// );
869    /// ```
870    ///
871    /// ## References
872    ///
873    /// * [*§ 6.1 Disallowed Raw HTML (extension)* in GFM](https://github.github.com/gfm/#disallowed-raw-html-extension-)
874    /// * [`cmark-gfm#extensions/tagfilter.c`](https://github.com/github/cmark-gfm/blob/master/extensions/tagfilter.c)
875    pub tagfilter: bool,
876
877    /// Whether to add unique `id` attributes to headings for in-page links
878    /// and tables of contents.
879    ///
880    /// IDs use the same text normalization and duplicate suffixes as
881    /// [`Document::headings`][crate::Document::headings].
882    ///
883    /// The default is `false`.
884    pub heading_ids: bool,
885}
886
887impl CompileOptions {
888    /// GFM.
889    ///
890    /// GFM stands for **GitHub flavored markdown**.
891    /// On the compilation side, the GFM preset turns on the tagfilter.
892    /// The tagfilter is useless, but it’s included here for consistency, and
893    /// this method exists for parity to parse options.
894    ///
895    /// For more information, see the GFM specification:
896    /// <https://github.github.com/gfm/>.
897    pub fn gfm() -> Self {
898        Self {
899            tagfilter: true,
900            ..Self::default()
901        }
902    }
903}
904
905/// Configuration that describes how to parse from markdown.
906///
907/// You can use this:
908///
909/// * To control what markdown constructs are turned on and off
910/// * To control some of those constructs
911///
912/// In most cases, you will want to use the default trait or `gfm` method.
913///
914/// ## Examples
915///
916/// ```
917/// use base_markdown::ParseOptions;
918/// # fn main() {
919///
920/// // Use the default trait to parse markdown according to `CommonMark`:
921/// let commonmark = ParseOptions::default();
922///
923/// // Use the `gfm` method to parse markdown according to GFM:
924/// let gfm = ParseOptions::gfm();
925/// # }
926/// ```
927#[allow(clippy::struct_excessive_bools)]
928#[cfg_attr(
929    feature = "serde",
930    derive(serde::Serialize, serde::Deserialize),
931    serde(default, rename_all = "camelCase")
932)]
933#[derive(Debug)]
934pub struct ParseOptions {
935    /// Which constructs to enable and disable.
936    ///
937    /// The default is to follow `CommonMark`.
938    ///
939    /// ## Examples
940    ///
941    /// ```
942    /// use base_markdown::{to_html, to_html_with_options, Constructs, Options, ParseOptions};
943    ///
944    /// // `markdown-rs` follows CommonMark by default:
945    /// assert_eq!(
946    ///     to_html("    indented code?"),
947    ///     "<pre><code>indented code?\n</code></pre>"
948    /// );
949    ///
950    /// // Pass `constructs` to choose what to enable and disable:
951    /// assert_eq!(
952    ///     to_html_with_options(
953    ///         "    indented code?",
954    ///         &Options {
955    ///             parse: ParseOptions {
956    ///               constructs: Constructs {
957    ///                 code_indented: false,
958    ///                 ..Constructs::default()
959    ///               },
960    ///               ..ParseOptions::default()
961    ///             },
962    ///             ..Options::default()
963    ///         }
964    ///     ),
965    ///     "<p>indented code?</p>"
966    /// );
967    /// ```
968    #[cfg_attr(feature = "serde", serde(default))]
969    pub constructs: Constructs,
970
971    /// Whether to parse a language prefix, such as `ruby:`, before inline code.
972    ///
973    /// When enabled, the prefix is removed from the surrounding text and stored as the inline code's `language`, and HTML rendering emits a `language-*` class. The default is `false`, which retains `CommonMark` behavior.
974    ///
975    /// ```
976    /// use base_markdown::{to_html_with_options, Options, ParseOptions};
977    ///
978    /// let options = Options {
979    ///     parse: ParseOptions { inline_code_info: true, ..ParseOptions::default() },
980    ///     ..Options::default()
981    /// };
982    /// assert_eq!(
983    ///     to_html_with_options("Call rust:`main()`.", &options),
984    ///     "<p>Call <code class=\"language-rust\">main()</code>.</p>"
985    /// );
986    /// ```
987    #[cfg_attr(feature = "serde", serde(default))]
988    pub inline_code_info: bool,
989
990    /// Whether indented type 6 and type 7 HTML blocks continue across blank lines when later content keeps a consistent indentation.
991    ///
992    /// This keeps indented HTML content together instead of ending the block at the first blank line. The default is `false`, which follows `CommonMark`'s blank-line rule.
993    #[cfg_attr(feature = "serde", serde(default))]
994    pub html_block_blank_lines: bool,
995
996    /// Whether `:` is allowed in HTML tag names for namespace prefixes such as `<svg:circle />`, in both block and inline HTML.
997    ///
998    /// The default is `false` to preserve `CommonMark` parsing of text such as `<m:abc>`.
999    #[cfg_attr(feature = "serde", serde(default))]
1000    pub html_tag_namespaces: bool,
1001
1002    /// Whether to support strikethrough with a single tilde
1003    ///
1004    /// This option does nothing if `strikethrough` is not turned on in
1005    /// `constructs`.
1006    /// This option does not affect strikethrough with double tildes.
1007    ///
1008    /// The default is `true`, which follows how markdown on `github.com`
1009    /// works, as strikethrough with single tildes is supported.
1010    /// Pass `false`, to follow the GFM spec more strictly, by not allowing
1011    /// strikethrough with single tildes.
1012    ///
1013    /// ## Examples
1014    ///
1015    /// ```
1016    /// use base_markdown::{to_html_with_options, Constructs, Options, ParseOptions};
1017    ///
1018    /// // `markdown-rs` supports single tildes by default:
1019    /// assert_eq!(
1020    ///     to_html_with_options(
1021    ///         "~a~",
1022    ///         &Options {
1023    ///             parse: ParseOptions {
1024    ///               constructs: Constructs::gfm(),
1025    ///               ..ParseOptions::default()
1026    ///             },
1027    ///             ..Options::default()
1028    ///         }
1029    ///     ),
1030    ///     "<p><del>a</del></p>"
1031    /// );
1032    ///
1033    /// // Pass `strikethrough_single_tilde: false` to turn that off:
1034    /// assert_eq!(
1035    ///     to_html_with_options(
1036    ///         "~a~",
1037    ///         &Options {
1038    ///             parse: ParseOptions {
1039    ///               constructs: Constructs::gfm(),
1040    ///               strikethrough_single_tilde: false,
1041    ///               ..ParseOptions::default()
1042    ///             },
1043    ///             ..Options::default()
1044    ///         }
1045    ///     ),
1046    ///     "<p>~a~</p>"
1047    /// );
1048    /// ```
1049    #[cfg_attr(feature = "serde", serde(default))]
1050    pub strikethrough_single_tilde: bool,
1051
1052    /// Whether to support math (text) with a single dollar
1053    ///
1054    /// This option does nothing if `math_text` is not turned on in
1055    /// `constructs`.
1056    /// This option does not affect math (text) with two or more dollars.
1057    ///
1058    /// The default is `true`, which is more close to how code (text) and
1059    /// Pandoc work, as it allows math with a single dollar to form.
1060    /// However, single dollars can interfere with “normal” dollars in text.
1061    /// Pass `false`, to only allow math (text) to form when two or more
1062    /// dollars are used.
1063    /// If you pass `false`, you can still use two or more dollars for text
1064    /// math.
1065    ///
1066    /// ## Examples
1067    ///
1068    /// ```
1069    /// use base_markdown::{to_html_with_options, Constructs, Options, ParseOptions};
1070    ///
1071    /// // `markdown-rs` supports single dollars by default:
1072    /// assert_eq!(
1073    ///     to_html_with_options(
1074    ///         "$a$",
1075    ///         &Options {
1076    ///             parse: ParseOptions {
1077    ///               constructs: Constructs {
1078    ///                 math_text: true,
1079    ///                 ..Constructs::default()
1080    ///               },
1081    ///               ..ParseOptions::default()
1082    ///             },
1083    ///             ..Options::default()
1084    ///         }
1085    ///     ),
1086    ///     "<p><code class=\"language-math math-inline\">a</code></p>"
1087    /// );
1088    ///
1089    /// // Pass `math_text_single_dollar: false` to turn that off:
1090    /// assert_eq!(
1091    ///     to_html_with_options(
1092    ///         "$a$",
1093    ///         &Options {
1094    ///             parse: ParseOptions {
1095    ///               constructs: Constructs {
1096    ///                 math_text: true,
1097    ///                 ..Constructs::default()
1098    ///               },
1099    ///               math_text_single_dollar: false,
1100    ///               ..ParseOptions::default()
1101    ///             },
1102    ///             ..Options::default()
1103    ///         }
1104    ///     ),
1105    ///     "<p>$a$</p>"
1106    /// );
1107    /// ```
1108    #[cfg_attr(feature = "serde", serde(default))]
1109    pub math_text_single_dollar: bool,
1110}
1111
1112impl Default for ParseOptions {
1113    /// `CommonMark` defaults.
1114    fn default() -> Self {
1115        Self {
1116            constructs: Constructs::default(),
1117            inline_code_info: false,
1118            html_block_blank_lines: false,
1119            html_tag_namespaces: false,
1120            strikethrough_single_tilde: true,
1121            math_text_single_dollar: true,
1122        }
1123    }
1124}
1125
1126impl ParseOptions {
1127    /// GFM.
1128    ///
1129    /// GFM stands for GitHub flavored markdown.
1130    /// GFM extends `CommonMark` and adds support for autolink literals,
1131    /// footnotes, strikethrough, tables, and tasklists.
1132    ///
1133    /// For more information, see the GFM specification:
1134    /// <https://github.github.com/gfm/>
1135    pub fn gfm() -> Self {
1136        Self {
1137            constructs: Constructs::gfm(),
1138            ..Self::default()
1139        }
1140    }
1141}
1142
1143/// Configuration that describes how to parse from markdown and compile to
1144/// HTML.
1145///
1146/// In most cases, you will want to use the default trait or `gfm` method.
1147///
1148/// ## Examples
1149///
1150/// ```
1151/// use base_markdown::Options;
1152/// # fn main() {
1153///
1154/// // Use the default trait to compile markdown to HTML according to `CommonMark`:
1155/// let commonmark = Options::default();
1156///
1157/// // Use the `gfm` method to compile markdown to HTML according to GFM:
1158/// let gfm = Options::gfm();
1159/// # }
1160/// ```
1161#[allow(clippy::struct_excessive_bools)]
1162#[derive(Debug, Default)]
1163#[cfg_attr(
1164    feature = "serde",
1165    derive(serde::Serialize, serde::Deserialize),
1166    serde(default)
1167)]
1168pub struct Options {
1169    /// Configuration that describes how to parse from markdown.
1170    pub parse: ParseOptions,
1171    /// Configuration that describes how to compile to HTML.
1172    pub compile: CompileOptions,
1173}
1174
1175impl Options {
1176    /// GFM.
1177    ///
1178    /// GFM stands for GitHub flavored markdown.
1179    /// GFM extends `CommonMark` and adds support for autolink literals,
1180    /// footnotes, strikethrough, tables, and tasklists.
1181    /// On the compilation side, the GFM preset turns on the tagfilter.
1182    /// The tagfilter is useless, but it’s included here for consistency.
1183    ///
1184    /// For more information, see the GFM specification:
1185    /// <https://github.github.com/gfm/>
1186    pub fn gfm() -> Self {
1187        Self {
1188            parse: ParseOptions::gfm(),
1189            compile: CompileOptions::gfm(),
1190        }
1191    }
1192}
1193
1194#[cfg(test)]
1195#[path = "configuration/tests.rs"]
1196mod tests;