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 & 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  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 /// "",
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 /// ")",
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, <i>venus</i>!</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 /// "<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;