Markdown parser fork with extended syntax for personal use.
at main 1543 lines 54 kB view raw
1use crate::util::{ 2 line_ending::LineEnding, 3 mdx::{EsmParse as MdxEsmParse, ExpressionParse as MdxExpressionParse}, 4}; 5use alloc::{boxed::Box, fmt, string::String}; 6 7/// Control which constructs are enabled. 8/// 9/// Not all constructs can be configured. 10/// Notably, blank lines and paragraphs cannot be turned off. 11/// 12/// ## Examples 13/// 14/// ``` 15/// use markdown::Constructs; 16/// # fn main() { 17/// 18/// // Use the default trait to get `CommonMark` constructs: 19/// let commonmark = Constructs::default(); 20/// 21/// // To turn on all of GFM, use the `gfm` method: 22/// let gfm = Constructs::gfm(); 23/// 24/// // Or, mix and match: 25/// let custom = Constructs { 26/// math_flow: true, 27/// math_text: true, 28/// ..Constructs::gfm() 29/// }; 30/// # } 31/// ``` 32#[allow(clippy::struct_excessive_bools)] 33#[derive(Clone, Debug, Eq, PartialEq)] 34#[cfg_attr( 35 feature = "serde", 36 derive(serde::Serialize, serde::Deserialize), 37 serde(rename_all = "camelCase") 38)] 39pub struct Constructs { 40 /// Attention. 41 /// 42 /// ```markdown 43 /// > | a *b* c **d**. 44 /// ^^^ ^^^^^ 45 /// ``` 46 pub attention: bool, 47 /// Autolink. 48 /// 49 /// ```markdown 50 /// > | a <https://example.com> b <user@example.org>. 51 /// ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^ 52 /// ``` 53 pub autolink: bool, 54 /// Block quote. 55 /// 56 /// ```markdown 57 /// > | > a 58 /// ^^^ 59 /// ``` 60 pub block_quote: bool, 61 /// Character escape. 62 /// 63 /// ```markdown 64 /// > | a \* b 65 /// ^^ 66 /// ``` 67 pub character_escape: bool, 68 /// Character reference. 69 /// 70 /// ```markdown 71 /// > | a &amp; b 72 /// ^^^^^ 73 /// ``` 74 pub character_reference: bool, 75 /// Code (indented). 76 /// 77 /// ```markdown 78 /// > | a 79 /// ^^^^^ 80 /// ``` 81 pub code_indented: bool, 82 /// Code (fenced). 83 /// 84 /// ```markdown 85 /// > | ~~~js 86 /// ^^^^^ 87 /// > | console.log(1) 88 /// ^^^^^^^^^^^^^^ 89 /// > | ~~~ 90 /// ^^^ 91 /// ``` 92 pub code_fenced: bool, 93 /// Code (text). 94 /// 95 /// ```markdown 96 /// > | a `b` c 97 /// ^^^ 98 /// ``` 99 pub code_text: bool, 100 /// Definition. 101 /// 102 /// ```markdown 103 /// > | [a]: b "c" 104 /// ^^^^^^^^^^ 105 /// ``` 106 pub definition: bool, 107 /// Frontmatter. 108 /// 109 /// ````markdown 110 /// > | --- 111 /// ^^^ 112 /// > | title: Neptune 113 /// ^^^^^^^^^^^^^^ 114 /// > | --- 115 /// ^^^ 116 /// ```` 117 pub frontmatter: bool, 118 /// GFM: autolink literal. 119 /// 120 /// ```markdown 121 /// > | https://example.com 122 /// ^^^^^^^^^^^^^^^^^^^ 123 /// ``` 124 pub gfm_autolink_literal: bool, 125 /// GFM: footnote definition. 126 /// 127 /// ```markdown 128 /// > | [^a]: b 129 /// ^^^^^^^ 130 /// ``` 131 pub gfm_footnote_definition: bool, 132 /// GFM: footnote label start. 133 /// 134 /// ```markdown 135 /// > | a[^b] 136 /// ^^ 137 /// ``` 138 pub gfm_label_start_footnote: bool, 139 /// 140 /// ```markdown 141 /// > | a ~b~ c. 142 /// ^^^ 143 /// ``` 144 pub gfm_strikethrough: bool, 145 /// GFM: table. 146 /// 147 /// ```markdown 148 /// > | | a | 149 /// ^^^^^ 150 /// > | | - | 151 /// ^^^^^ 152 /// > | | b | 153 /// ^^^^^ 154 /// ``` 155 pub gfm_table: bool, 156 /// GFM: task list item. 157 /// 158 /// ```markdown 159 /// > | * [x] y. 160 /// ^^^ 161 /// ``` 162 pub gfm_task_list_item: bool, 163 /// Hard break (escape). 164 /// 165 /// ```markdown 166 /// > | a\ 167 /// ^ 168 /// | b 169 /// ``` 170 pub hard_break_escape: bool, 171 /// Hard break (trailing). 172 /// 173 /// ```markdown 174 /// > | a␠␠ 175 /// ^^ 176 /// | b 177 /// ``` 178 pub hard_break_trailing: bool, 179 /// Heading (atx). 180 /// 181 /// ```markdown 182 /// > | # a 183 /// ^^^ 184 /// ``` 185 pub heading_atx: bool, 186 /// Heading (setext). 187 /// 188 /// ```markdown 189 /// > | a 190 /// ^^ 191 /// > | == 192 /// ^^ 193 /// ``` 194 pub heading_setext: bool, 195 /// HTML (flow). 196 /// 197 /// ```markdown 198 /// > | <div> 199 /// ^^^^^ 200 /// ``` 201 pub html_flow: bool, 202 /// HTML (text). 203 /// 204 /// ```markdown 205 /// > | a <b> c 206 /// ^^^ 207 /// ``` 208 pub html_text: bool, 209 /// Label start (image). 210 /// 211 /// ```markdown 212 /// > | a ![b](c) d 213 /// ^^ 214 /// ``` 215 pub label_start_image: bool, 216 /// Label start (link). 217 /// 218 /// ```markdown 219 /// > | a [b](c) d 220 /// ^ 221 /// ``` 222 pub label_start_link: bool, 223 /// Label end. 224 /// 225 /// ```markdown 226 /// > | a [b](c) d 227 /// ^^^^ 228 /// ``` 229 pub label_end: bool, 230 /// List items. 231 /// 232 /// ```markdown 233 /// > | * a 234 /// ^^^ 235 /// ``` 236 pub list_item: bool, 237 /// Math (flow). 238 /// 239 /// ```markdown 240 /// > | $$ 241 /// ^^ 242 /// > | \frac{1}{2} 243 /// ^^^^^^^^^^^ 244 /// > | $$ 245 /// ^^ 246 /// ``` 247 pub math_flow: bool, 248 /// Math (text). 249 /// 250 /// ```markdown 251 /// > | a $b$ c 252 /// ^^^ 253 /// ``` 254 pub math_text: bool, 255 /// MDX: ESM. 256 /// 257 /// ```markdown 258 /// > | import a from 'b' 259 /// ^^^^^^^^^^^^^^^^^ 260 /// ``` 261 /// 262 /// > 👉 **Note**: to support ESM, you *must* pass 263 /// > [`mdx_esm_parse`][MdxEsmParse] in [`ParseOptions`][] too. 264 /// > Otherwise, ESM is treated as normal markdown. 265 pub mdx_esm: bool, 266 /// MDX: expression (flow). 267 /// 268 /// ```markdown 269 /// > | {Math.PI} 270 /// ^^^^^^^^^ 271 /// ``` 272 /// 273 /// > 👉 **Note**: You *can* pass 274 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][] 275 /// > too, to parse expressions according to a certain grammar (typically, 276 /// > a programming language). 277 /// > Otherwise, expressions are parsed with a basic algorithm that only 278 /// > cares about braces. 279 pub mdx_expression_flow: bool, 280 /// MDX: expression (text). 281 /// 282 /// ```markdown 283 /// > | a {Math.PI} c 284 /// ^^^^^^^^^ 285 /// ``` 286 /// 287 /// > 👉 **Note**: You *can* pass 288 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][] 289 /// > too, to parse expressions according to a certain grammar (typically, 290 /// > a programming language). 291 /// > Otherwise, expressions are parsed with a basic algorithm that only 292 /// > cares about braces. 293 pub mdx_expression_text: bool, 294 /// MDX: JSX (flow). 295 /// 296 /// ```markdown 297 /// > | <Component /> 298 /// ^^^^^^^^^^^^^ 299 /// ``` 300 /// 301 /// > 👉 **Note**: You *must* pass `html_flow: false` to use this, 302 /// > as it’s preferred when on over `mdx_jsx_flow`. 303 /// 304 /// > 👉 **Note**: You *can* pass 305 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][] 306 /// > too, to parse expressions in JSX according to a certain grammar 307 /// > (typically, a programming language). 308 /// > Otherwise, expressions are parsed with a basic algorithm that only 309 /// > cares about braces. 310 pub mdx_jsx_flow: bool, 311 /// MDX: JSX (text). 312 /// 313 /// ```markdown 314 /// > | a <Component /> c 315 /// ^^^^^^^^^^^^^ 316 /// ``` 317 /// 318 /// > 👉 **Note**: You *must* pass `html_text: false` to use this, 319 /// > as it’s preferred when on over `mdx_jsx_text`. 320 /// 321 /// > 👉 **Note**: You *can* pass 322 /// > [`mdx_expression_parse`][MdxExpressionParse] in [`ParseOptions`][] 323 /// > too, to parse expressions in JSX according to a certain grammar 324 /// > (typically, a programming language). 325 /// > Otherwise, expressions are parsed with a basic algorithm that only 326 /// > cares about braces. 327 pub mdx_jsx_text: bool, 328 /// Thematic break. 329 /// 330 /// ```markdown 331 /// > | *** 332 /// ^^^ 333 /// ``` 334 pub thematic_break: bool, 335} 336 337impl Default for Constructs { 338 /// `CommonMark`. 339 /// 340 /// `CommonMark` is a relatively strong specification of how markdown 341 /// works. 342 /// Most markdown parsers try to follow it. 343 /// 344 /// For more information, see the `CommonMark` specification: 345 /// <https://spec.commonmark.org>. 346 fn default() -> Self { 347 Self { 348 attention: true, 349 autolink: true, 350 block_quote: true, 351 character_escape: true, 352 character_reference: true, 353 code_indented: true, 354 code_fenced: true, 355 code_text: true, 356 definition: true, 357 frontmatter: false, 358 gfm_autolink_literal: false, 359 gfm_label_start_footnote: false, 360 gfm_footnote_definition: false, 361 gfm_strikethrough: false, 362 gfm_table: false, 363 gfm_task_list_item: false, 364 hard_break_escape: true, 365 hard_break_trailing: true, 366 heading_atx: true, 367 heading_setext: true, 368 html_flow: true, 369 html_text: true, 370 label_start_image: true, 371 label_start_link: true, 372 label_end: true, 373 list_item: true, 374 math_flow: false, 375 math_text: false, 376 mdx_esm: false, 377 mdx_expression_flow: false, 378 mdx_expression_text: false, 379 mdx_jsx_flow: false, 380 mdx_jsx_text: false, 381 thematic_break: true, 382 } 383 } 384} 385 386impl Constructs { 387 /// GFM. 388 /// 389 /// GFM stands for **GitHub flavored markdown**. 390 /// GFM extends `CommonMark` and adds support for autolink literals, 391 /// footnotes, strikethrough, tables, and tasklists. 392 /// 393 /// For more information, see the GFM specification: 394 /// <https://github.github.com/gfm/>. 395 pub fn gfm() -> Self { 396 Self { 397 gfm_autolink_literal: true, 398 gfm_footnote_definition: true, 399 gfm_label_start_footnote: true, 400 gfm_strikethrough: true, 401 gfm_table: true, 402 gfm_task_list_item: true, 403 ..Self::default() 404 } 405 } 406 407 /// MDX. 408 /// 409 /// This turns on `CommonMark`, turns off some conflicting constructs 410 /// (autolinks, code (indented), and HTML), and turns on MDX (ESM, 411 /// expressions, and JSX). 412 /// 413 /// For more information, see the MDX website: 414 /// <https://mdxjs.com>. 415 /// 416 /// > 👉 **Note**: to support ESM, you *must* pass 417 /// > [`mdx_esm_parse`][MdxEsmParse] in [`ParseOptions`][] too. 418 /// > Otherwise, ESM is treated as normal markdown. 419 /// > 420 /// > You *can* pass 421 /// > [`mdx_expression_parse`][MdxExpressionParse] 422 /// > to parse expressions according to a certain grammar (typically, a 423 /// > programming language). 424 /// > Otherwise, expressions are parsed with a basic algorithm that only 425 /// > cares about braces. 426 pub fn mdx() -> Self { 427 Self { 428 autolink: false, 429 code_indented: false, 430 html_flow: false, 431 html_text: false, 432 mdx_esm: true, 433 mdx_expression_flow: true, 434 mdx_expression_text: true, 435 mdx_jsx_flow: true, 436 mdx_jsx_text: true, 437 ..Self::default() 438 } 439 } 440} 441 442/// Configuration that describes how to compile to HTML. 443/// 444/// You likely either want to turn on the dangerous options 445/// (`allow_dangerous_html`, `allow_dangerous_protocol`) when dealing with 446/// input you trust, or want to customize how GFM footnotes are compiled 447/// (typically because the input markdown is not in English). 448/// 449/// ## Examples 450/// 451/// ``` 452/// use markdown::CompileOptions; 453/// # fn main() { 454/// 455/// // Use the default trait to get safe defaults: 456/// let safe = CompileOptions::default(); 457/// 458/// // Live dangerously / trust the author: 459/// let danger = CompileOptions { 460/// allow_dangerous_html: true, 461/// allow_dangerous_protocol: true, 462/// ..CompileOptions::default() 463/// }; 464/// 465/// // In French: 466/// let enFrançais = CompileOptions { 467/// gfm_footnote_back_label: Some("Arrière".into()), 468/// gfm_footnote_label: Some("Notes de bas de page".into()), 469/// ..CompileOptions::default() 470/// }; 471/// # } 472/// ``` 473#[allow(clippy::struct_excessive_bools)] 474#[derive(Clone, Debug, Default)] 475#[cfg_attr( 476 feature = "serde", 477 derive(serde::Serialize, serde::Deserialize), 478 serde(default, rename_all = "camelCase") 479)] 480pub struct CompileOptions { 481 /// Whether to allow all values in images. 482 /// 483 /// The default is `false`, 484 /// which lets `allow_dangerous_protocol` control protocol safety for 485 /// both links and images. 486 /// 487 /// Pass `true` to allow all values as `src` on images, 488 /// regardless of `allow_dangerous_protocol`. 489 /// This is safe because the 490 /// [HTML specification][whatwg-html-image-processing] 491 /// does not allow executable code in images. 492 /// 493 /// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model 494 /// 495 /// ## Examples 496 /// 497 /// ``` 498 /// use markdown::{to_html_with_options, CompileOptions, Options}; 499 /// # fn main() -> Result<(), markdown::message::Message> { 500 /// 501 /// // By default, some protocols in image sources are dropped: 502 /// assert_eq!( 503 /// to_html_with_options( 504 /// "![](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)", 505 /// &Options::default() 506 /// )?, 507 /// "<p><img src=\"\" alt=\"\" /></p>" 508 /// ); 509 /// 510 /// // Turn `allow_any_img_src` on to allow all values as `src` on images. 511 /// // This is safe because browsers do not execute code in images. 512 /// assert_eq!( 513 /// to_html_with_options( 514 /// "![](javascript:alert(1))", 515 /// &Options { 516 /// compile: CompileOptions { 517 /// allow_any_img_src: true, 518 /// ..CompileOptions::default() 519 /// }, 520 /// ..Options::default() 521 /// } 522 /// )?, 523 /// "<p><img src=\"javascript:alert(1)\" alt=\"\" /></p>" 524 /// ); 525 /// # Ok(()) 526 /// # } 527 /// ``` 528 pub allow_any_img_src: bool, 529 530 /// Whether to allow (dangerous) HTML. 531 /// 532 /// The default is `false`, which still parses the HTML according to 533 /// `CommonMark` but shows the HTML as text instead of as elements. 534 /// 535 /// Pass `true` for trusted content to get actual HTML elements. 536 /// 537 /// When using GFM, make sure to also turn off `gfm_tagfilter`. 538 /// Otherwise, some dangerous HTML is still ignored. 539 /// 540 /// ## Examples 541 /// 542 /// ``` 543 /// use markdown::{to_html, to_html_with_options, CompileOptions, Options}; 544 /// # fn main() -> Result<(), markdown::message::Message> { 545 /// 546 /// // `markdown-rs` is safe by default: 547 /// assert_eq!( 548 /// to_html("Hi, <i>venus</i>!"), 549 /// "<p>Hi, &lt;i&gt;venus&lt;/i&gt;!</p>" 550 /// ); 551 /// 552 /// // Turn `allow_dangerous_html` on to allow potentially dangerous HTML: 553 /// assert_eq!( 554 /// to_html_with_options( 555 /// "Hi, <i>venus</i>!", 556 /// &Options { 557 /// compile: CompileOptions { 558 /// allow_dangerous_html: true, 559 /// ..CompileOptions::default() 560 /// }, 561 /// ..Options::default() 562 /// } 563 /// )?, 564 /// "<p>Hi, <i>venus</i>!</p>" 565 /// ); 566 /// # Ok(()) 567 /// # } 568 /// ``` 569 pub allow_dangerous_html: bool, 570 571 /// Whether to allow dangerous protocols in links and images. 572 /// 573 /// The default is `false`, which drops URLs in links and images that use 574 /// dangerous protocols. 575 /// 576 /// Pass `true` for trusted content to support all protocols. 577 /// 578 /// URLs that have no protocol (which means it’s relative to the current 579 /// page, such as `./some/page.html`) and URLs that have a safe protocol 580 /// (for images: `http`, `https`; for links: `http`, `https`, `irc`, 581 /// `ircs`, `mailto`, `xmpp`), are safe. 582 /// All other URLs are dangerous and dropped. 583 /// 584 /// When the option `allow_all_protocols_in_img` is enabled, 585 /// `allow_dangerous_protocol` only applies to links. 586 /// 587 /// This is safe because the 588 /// [HTML specification][whatwg-html-image-processing] 589 /// does not allow executable code in images. 590 /// All modern browsers respect this. 591 /// 592 /// [whatwg-html-image-processing]: https://html.spec.whatwg.org/multipage/images.html#images-processing-model 593 /// 594 /// ## Examples 595 /// 596 /// ``` 597 /// use markdown::{to_html, to_html_with_options, CompileOptions, Options}; 598 /// # fn main() -> Result<(), markdown::message::Message> { 599 /// 600 /// // `markdown-rs` is safe by default: 601 /// assert_eq!( 602 /// to_html("<javascript:alert(1)>"), 603 /// "<p><a href=\"\">javascript:alert(1)</a></p>" 604 /// ); 605 /// 606 /// // Turn `allow_dangerous_protocol` on to allow potentially dangerous protocols: 607 /// assert_eq!( 608 /// to_html_with_options( 609 /// "<javascript:alert(1)>", 610 /// &Options { 611 /// compile: CompileOptions { 612 /// allow_dangerous_protocol: true, 613 /// ..CompileOptions::default() 614 /// }, 615 /// ..Options::default() 616 /// } 617 /// )?, 618 /// "<p><a href=\"javascript:alert(1)\">javascript:alert(1)</a></p>" 619 /// ); 620 /// # Ok(()) 621 /// # } 622 /// ``` 623 pub allow_dangerous_protocol: bool, 624 625 // To do: `doc_markdown` is broken. 626 #[allow(clippy::doc_markdown)] 627 /// Default line ending to use when compiling to HTML, for line endings not 628 /// in `value`. 629 /// 630 /// Generally, `markdown-rs` copies line endings (`\r`, `\n`, `\r\n`) in 631 /// the markdown document over to the compiled HTML. 632 /// In some cases, such as `> a`, CommonMark requires that extra line 633 /// endings are added: `<blockquote>\n<p>a</p>\n</blockquote>`. 634 /// 635 /// To create that line ending, the document is checked for the first line 636 /// ending that is used. 637 /// If there is no line ending, `default_line_ending` is used. 638 /// If that isn’t configured, `\n` is used. 639 /// 640 /// ## Examples 641 /// 642 /// ``` 643 /// use markdown::{to_html, to_html_with_options, CompileOptions, LineEnding, Options}; 644 /// # fn main() -> Result<(), markdown::message::Message> { 645 /// 646 /// // `markdown-rs` uses `\n` by default: 647 /// assert_eq!( 648 /// to_html("> a"), 649 /// "<blockquote>\n<p>a</p>\n</blockquote>" 650 /// ); 651 /// 652 /// // Define `default_line_ending` to configure the default: 653 /// assert_eq!( 654 /// to_html_with_options( 655 /// "> a", 656 /// &Options { 657 /// compile: CompileOptions { 658 /// default_line_ending: LineEnding::CarriageReturnLineFeed, 659 /// ..CompileOptions::default() 660 /// }, 661 /// ..Options::default() 662 /// } 663 /// )?, 664 /// "<blockquote>\r\n<p>a</p>\r\n</blockquote>" 665 /// ); 666 /// # Ok(()) 667 /// # } 668 /// ``` 669 pub default_line_ending: LineEnding, 670 671 /// Textual label to describe the backreference back to footnote calls. 672 /// 673 /// The default value is `"Back to content"`. 674 /// Change it when the markdown is not in English. 675 /// 676 /// This label is used in the `aria-label` attribute on each backreference 677 /// (the `↩` links). 678 /// It affects users of assistive technology. 679 /// 680 /// ## Examples 681 /// 682 /// ``` 683 /// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions}; 684 /// # fn main() -> Result<(), markdown::message::Message> { 685 /// 686 /// // `"Back to content"` is used by default: 687 /// assert_eq!( 688 /// to_html_with_options( 689 /// "[^a]\n\n[^a]: b", 690 /// &Options::gfm() 691 /// )?, 692 /// "<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" 693 /// ); 694 /// 695 /// // Pass `gfm_footnote_back_label` to use something else: 696 /// assert_eq!( 697 /// to_html_with_options( 698 /// "[^a]\n\n[^a]: b", 699 /// &Options { 700 /// parse: ParseOptions::gfm(), 701 /// compile: CompileOptions { 702 /// gfm_footnote_back_label: Some("Arrière".into()), 703 /// ..CompileOptions::gfm() 704 /// } 705 /// } 706 /// )?, 707 /// "<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" 708 /// ); 709 /// # Ok(()) 710 /// # } 711 /// ``` 712 pub gfm_footnote_back_label: Option<String>, 713 714 /// Prefix to use before the `id` attribute on footnotes to prevent them 715 /// from *clobbering*. 716 /// 717 /// The default is `"user-content-"`. 718 /// Pass `Some("".into())` for trusted markdown and when you are careful 719 /// with polyfilling. 720 /// You could pass a different prefix. 721 /// 722 /// DOM clobbering is this: 723 /// 724 /// ```html 725 /// <p id="x"></p> 726 /// <script>alert(x) // `x` now refers to the `p#x` DOM element</script> 727 /// ``` 728 /// 729 /// The above example shows that elements are made available by browsers, 730 /// by their ID, on the `window` object. 731 /// This is a security risk because you might be expecting some other 732 /// variable at that place. 733 /// It can also break polyfills. 734 /// Using a prefix solves these problems. 735 /// 736 /// ## Examples 737 /// 738 /// ``` 739 /// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions}; 740 /// # fn main() -> Result<(), markdown::message::Message> { 741 /// 742 /// // `"user-content-"` is used by default: 743 /// assert_eq!( 744 /// to_html_with_options( 745 /// "[^a]\n\n[^a]: b", 746 /// &Options::gfm() 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\"><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" 749 /// ); 750 /// 751 /// // Pass `gfm_footnote_clobber_prefix` to use something else: 752 /// assert_eq!( 753 /// to_html_with_options( 754 /// "[^a]\n\n[^a]: b", 755 /// &Options { 756 /// parse: ParseOptions::gfm(), 757 /// compile: CompileOptions { 758 /// gfm_footnote_clobber_prefix: Some("".into()), 759 /// ..CompileOptions::gfm() 760 /// } 761 /// } 762 /// )?, 763 /// "<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" 764 /// ); 765 /// # Ok(()) 766 /// # } 767 /// ``` 768 pub gfm_footnote_clobber_prefix: Option<String>, 769 770 /// Attributes to use on the footnote label. 771 /// 772 /// The default value is `"class=\"sr-only\""`. 773 /// Change it to show the label and add other attributes. 774 /// 775 /// This label is typically hidden visually (assuming a `sr-only` CSS class 776 /// is defined that does that), and thus affects screen readers only. 777 /// If you do have such a class, but want to show this section to everyone, 778 /// pass an empty string. 779 /// You can also add different attributes. 780 /// 781 /// > 👉 **Note**: `id="footnote-label"` is always added, because footnote 782 /// > calls use it with `aria-describedby` to provide an accessible label. 783 /// 784 /// ## Examples 785 /// 786 /// ``` 787 /// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions}; 788 /// # fn main() -> Result<(), markdown::message::Message> { 789 /// 790 /// // `"class=\"sr-only\""` is used by default: 791 /// assert_eq!( 792 /// to_html_with_options( 793 /// "[^a]\n\n[^a]: b", 794 /// &Options::gfm() 795 /// )?, 796 /// "<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" 797 /// ); 798 /// 799 /// // Pass `gfm_footnote_label_attributes` to use something else: 800 /// assert_eq!( 801 /// to_html_with_options( 802 /// "[^a]\n\n[^a]: b", 803 /// &Options { 804 /// parse: ParseOptions::gfm(), 805 /// compile: CompileOptions { 806 /// gfm_footnote_label_attributes: Some("class=\"footnote-heading\"".into()), 807 /// ..CompileOptions::gfm() 808 /// } 809 /// } 810 /// )?, 811 /// "<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" 812 /// ); 813 /// # Ok(()) 814 /// # } 815 /// ``` 816 pub gfm_footnote_label_attributes: Option<String>, 817 818 /// HTML tag name to use for the footnote label element. 819 /// 820 /// The default value is `"h2"`. 821 /// Change it to match your document structure. 822 /// 823 /// This label is typically hidden visually (assuming a `sr-only` CSS class 824 /// is defined that does that), and thus affects screen readers only. 825 /// If you do have such a class, but want to show this section to everyone, 826 /// pass different attributes with the `gfm_footnote_label_attributes` 827 /// option. 828 /// 829 /// ## Examples 830 /// 831 /// ``` 832 /// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions}; 833 /// # fn main() -> Result<(), markdown::message::Message> { 834 /// 835 /// // `"h2"` is used by default: 836 /// assert_eq!( 837 /// to_html_with_options( 838 /// "[^a]\n\n[^a]: b", 839 /// &Options::gfm() 840 /// )?, 841 /// "<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" 842 /// ); 843 /// 844 /// // Pass `gfm_footnote_label_tag_name` to use something else: 845 /// assert_eq!( 846 /// to_html_with_options( 847 /// "[^a]\n\n[^a]: b", 848 /// &Options { 849 /// parse: ParseOptions::gfm(), 850 /// compile: CompileOptions { 851 /// gfm_footnote_label_tag_name: Some("h1".into()), 852 /// ..CompileOptions::gfm() 853 /// } 854 /// } 855 /// )?, 856 /// "<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" 857 /// ); 858 /// # Ok(()) 859 /// # } 860 /// ``` 861 pub gfm_footnote_label_tag_name: Option<String>, 862 863 /// Textual label to use for the footnotes section. 864 /// 865 /// The default value is `"Footnotes"`. 866 /// Change it when the markdown is not in English. 867 /// 868 /// This label is typically hidden visually (assuming a `sr-only` CSS class 869 /// is defined that does that), and thus affects screen readers only. 870 /// If you do have such a class, but want to show this section to everyone, 871 /// pass different attributes with the `gfm_footnote_label_attributes` 872 /// option. 873 /// 874 /// ## Examples 875 /// 876 /// ``` 877 /// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions}; 878 /// # fn main() -> Result<(), markdown::message::Message> { 879 /// 880 /// // `"Footnotes"` is used by default: 881 /// assert_eq!( 882 /// to_html_with_options( 883 /// "[^a]\n\n[^a]: b", 884 /// &Options::gfm() 885 /// )?, 886 /// "<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" 887 /// ); 888 /// 889 /// // Pass `gfm_footnote_label` to use something else: 890 /// assert_eq!( 891 /// to_html_with_options( 892 /// "[^a]\n\n[^a]: b", 893 /// &Options { 894 /// parse: ParseOptions::gfm(), 895 /// compile: CompileOptions { 896 /// gfm_footnote_label: Some("Notes de bas de page".into()), 897 /// ..CompileOptions::gfm() 898 /// } 899 /// } 900 /// )?, 901 /// "<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" 902 /// ); 903 /// # Ok(()) 904 /// # } 905 /// ``` 906 pub gfm_footnote_label: Option<String>, 907 908 /// Whether or not GFM task list html `<input>` items are enabled. 909 /// 910 /// This determines whether or not the user of the browser is able 911 /// to click and toggle generated checkbox items. The default is false. 912 /// 913 /// ## Examples 914 /// 915 /// ``` 916 /// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions}; 917 /// # fn main() -> Result<(), markdown::message::Message> { 918 /// 919 /// // With `gfm_task_list_item_checkable`, generated `<input type="checkbox" />` 920 /// // tags do not contain the attribute `disabled=""` and are thus toggleable by 921 /// // browser users. 922 /// assert_eq!( 923 /// to_html_with_options( 924 /// "* [x] y.", 925 /// &Options { 926 /// parse: ParseOptions::gfm(), 927 /// compile: CompileOptions { 928 /// gfm_task_list_item_checkable: true, 929 /// ..CompileOptions::gfm() 930 /// } 931 /// } 932 /// )?, 933 /// "<ul>\n<li><input type=\"checkbox\" checked=\"\" /> y.</li>\n</ul>" 934 /// ); 935 /// # Ok(()) 936 /// # } 937 /// ``` 938 pub gfm_task_list_item_checkable: bool, 939 940 /// Whether to support the GFM tagfilter. 941 /// 942 /// This option does nothing if `allow_dangerous_html` is not turned on. 943 /// The default is `false`, which does not apply the GFM tagfilter to HTML. 944 /// Pass `true` for output that is a bit closer to GitHub’s actual output. 945 /// 946 /// The tagfilter is kinda weird and kinda useless. 947 /// The tag filter is a naïve attempt at XSS protection. 948 /// You should use a proper HTML sanitizing algorithm instead. 949 /// 950 /// ## Examples 951 /// 952 /// ``` 953 /// use markdown::{to_html_with_options, CompileOptions, Options, ParseOptions}; 954 /// # fn main() -> Result<(), markdown::message::Message> { 955 /// 956 /// // With `allow_dangerous_html`, `markdown-rs` passes HTML through untouched: 957 /// assert_eq!( 958 /// to_html_with_options( 959 /// "<iframe>", 960 /// &Options { 961 /// parse: ParseOptions::gfm(), 962 /// compile: CompileOptions { 963 /// allow_dangerous_html: true, 964 /// ..CompileOptions::default() 965 /// } 966 /// } 967 /// )?, 968 /// "<iframe>" 969 /// ); 970 /// 971 /// // Pass `gfm_tagfilter: true` to make some of that safe: 972 /// assert_eq!( 973 /// to_html_with_options( 974 /// "<iframe>", 975 /// &Options { 976 /// parse: ParseOptions::gfm(), 977 /// compile: CompileOptions { 978 /// allow_dangerous_html: true, 979 /// gfm_tagfilter: true, 980 /// ..CompileOptions::default() 981 /// } 982 /// } 983 /// )?, 984 /// "&lt;iframe>" 985 /// ); 986 /// # Ok(()) 987 /// # } 988 /// ``` 989 /// 990 /// ## References 991 /// 992 /// * [*§ 6.1 Disallowed Raw HTML (extension)* in GFM](https://github.github.com/gfm/#disallowed-raw-html-extension-) 993 /// * [`cmark-gfm#extensions/tagfilter.c`](https://github.com/github/cmark-gfm/blob/master/extensions/tagfilter.c) 994 pub gfm_tagfilter: bool, 995} 996 997impl CompileOptions { 998 /// GFM. 999 /// 1000 /// GFM stands for **GitHub flavored markdown**. 1001 /// On the compilation side, GFM turns on the GFM tag filter. 1002 /// The tagfilter is useless, but it’s included here for consistency, and 1003 /// this method exists for parity to parse options. 1004 /// 1005 /// For more information, see the GFM specification: 1006 /// <https://github.github.com/gfm/>. 1007 pub fn gfm() -> Self { 1008 Self { 1009 gfm_tagfilter: true, 1010 ..Self::default() 1011 } 1012 } 1013} 1014 1015/// Configuration that describes how to parse from markdown. 1016/// 1017/// You can use this: 1018/// 1019/// * To control what markdown constructs are turned on and off 1020/// * To control some of those constructs 1021/// * To add support for certain programming languages when parsing MDX 1022/// 1023/// In most cases, you will want to use the default trait or `gfm` method. 1024/// 1025/// ## Examples 1026/// 1027/// ``` 1028/// use markdown::ParseOptions; 1029/// # fn main() { 1030/// 1031/// // Use the default trait to parse markdown according to `CommonMark`: 1032/// let commonmark = ParseOptions::default(); 1033/// 1034/// // Use the `gfm` method to parse markdown according to GFM: 1035/// let gfm = ParseOptions::gfm(); 1036/// # } 1037/// ``` 1038#[allow(clippy::struct_excessive_bools)] 1039#[cfg_attr( 1040 feature = "serde", 1041 derive(serde::Serialize, serde::Deserialize), 1042 serde(default, rename_all = "camelCase") 1043)] 1044pub struct ParseOptions { 1045 // Note: when adding fields, don’t forget to add them to `fmt::Debug` below. 1046 /// Which constructs to enable and disable. 1047 /// 1048 /// The default is to follow `CommonMark`. 1049 /// 1050 /// ## Examples 1051 /// 1052 /// ``` 1053 /// use markdown::{to_html, to_html_with_options, Constructs, Options, ParseOptions}; 1054 /// # fn main() -> Result<(), markdown::message::Message> { 1055 /// 1056 /// // `markdown-rs` follows CommonMark by default: 1057 /// assert_eq!( 1058 /// to_html(" indented code?"), 1059 /// "<pre><code>indented code?\n</code></pre>" 1060 /// ); 1061 /// 1062 /// // Pass `constructs` to choose what to enable and disable: 1063 /// assert_eq!( 1064 /// to_html_with_options( 1065 /// " indented code?", 1066 /// &Options { 1067 /// parse: ParseOptions { 1068 /// constructs: Constructs { 1069 /// code_indented: false, 1070 /// ..Constructs::default() 1071 /// }, 1072 /// ..ParseOptions::default() 1073 /// }, 1074 /// ..Options::default() 1075 /// } 1076 /// )?, 1077 /// "<p>indented code?</p>" 1078 /// ); 1079 /// # Ok(()) 1080 /// # } 1081 /// ``` 1082 #[cfg_attr(feature = "serde", serde(default))] 1083 pub constructs: Constructs, 1084 1085 /// Whether to support GFM strikethrough with a single tilde 1086 /// 1087 /// This option does nothing if `gfm_strikethrough` is not turned on in 1088 /// `constructs`. 1089 /// This option does not affect strikethrough with double tildes. 1090 /// 1091 /// The default is `true`, which follows how markdown on `github.com` 1092 /// works, as strikethrough with single tildes is supported. 1093 /// Pass `false`, to follow the GFM spec more strictly, by not allowing 1094 /// strikethrough with single tildes. 1095 /// 1096 /// ## Examples 1097 /// 1098 /// ``` 1099 /// use markdown::{to_html_with_options, Constructs, Options, ParseOptions}; 1100 /// # fn main() -> Result<(), markdown::message::Message> { 1101 /// 1102 /// // `markdown-rs` supports single tildes by default: 1103 /// assert_eq!( 1104 /// to_html_with_options( 1105 /// "~a~", 1106 /// &Options { 1107 /// parse: ParseOptions { 1108 /// constructs: Constructs::gfm(), 1109 /// ..ParseOptions::default() 1110 /// }, 1111 /// ..Options::default() 1112 /// } 1113 /// )?, 1114 /// "<p><del>a</del></p>" 1115 /// ); 1116 /// 1117 /// // Pass `gfm_strikethrough_single_tilde: false` to turn that off: 1118 /// assert_eq!( 1119 /// to_html_with_options( 1120 /// "~a~", 1121 /// &Options { 1122 /// parse: ParseOptions { 1123 /// constructs: Constructs::gfm(), 1124 /// gfm_strikethrough_single_tilde: false, 1125 /// ..ParseOptions::default() 1126 /// }, 1127 /// ..Options::default() 1128 /// } 1129 /// )?, 1130 /// "<p>~a~</p>" 1131 /// ); 1132 /// # Ok(()) 1133 /// # } 1134 /// ``` 1135 #[cfg_attr(feature = "serde", serde(default))] 1136 pub gfm_strikethrough_single_tilde: bool, 1137 1138 /// Whether to support math (text) with a single dollar 1139 /// 1140 /// This option does nothing if `math_text` is not turned on in 1141 /// `constructs`. 1142 /// This option does not affect math (text) with two or more dollars. 1143 /// 1144 /// The default is `true`, which is more close to how code (text) and 1145 /// Pandoc work, as it allows math with a single dollar to form. 1146 /// However, single dollars can interfere with “normal” dollars in text. 1147 /// Pass `false`, to only allow math (text) to form when two or more 1148 /// dollars are used. 1149 /// If you pass `false`, you can still use two or more dollars for text 1150 /// math. 1151 /// 1152 /// ## Examples 1153 /// 1154 /// ``` 1155 /// use markdown::{to_html_with_options, Constructs, Options, ParseOptions}; 1156 /// # fn main() -> Result<(), markdown::message::Message> { 1157 /// 1158 /// // `markdown-rs` supports single dollars by default: 1159 /// assert_eq!( 1160 /// to_html_with_options( 1161 /// "$a$", 1162 /// &Options { 1163 /// parse: ParseOptions { 1164 /// constructs: Constructs { 1165 /// math_text: true, 1166 /// ..Constructs::default() 1167 /// }, 1168 /// ..ParseOptions::default() 1169 /// }, 1170 /// ..Options::default() 1171 /// } 1172 /// )?, 1173 /// "<p><code class=\"language-math math-inline\">a</code></p>" 1174 /// ); 1175 /// 1176 /// // Pass `math_text_single_dollar: false` to turn that off: 1177 /// assert_eq!( 1178 /// to_html_with_options( 1179 /// "$a$", 1180 /// &Options { 1181 /// parse: ParseOptions { 1182 /// constructs: Constructs { 1183 /// math_text: true, 1184 /// ..Constructs::default() 1185 /// }, 1186 /// math_text_single_dollar: false, 1187 /// ..ParseOptions::default() 1188 /// }, 1189 /// ..Options::default() 1190 /// } 1191 /// )?, 1192 /// "<p>$a$</p>" 1193 /// ); 1194 /// # Ok(()) 1195 /// # } 1196 /// ``` 1197 #[cfg_attr(feature = "serde", serde(default))] 1198 pub math_text_single_dollar: bool, 1199 1200 /// Function to parse expressions with. 1201 /// 1202 /// This function can be used to add support for arbitrary programming 1203 /// languages within expressions. 1204 /// 1205 /// It only makes sense to pass this when compiling to a syntax tree 1206 /// with [`to_mdast()`][crate::to_mdast()]. 1207 /// 1208 /// For an example that adds support for JavaScript with SWC, see 1209 /// `tests/test_utils/mod.rs`. 1210 #[cfg_attr(feature = "serde", serde(skip))] 1211 pub mdx_expression_parse: Option<Box<MdxExpressionParse>>, 1212 1213 /// Function to parse ESM with. 1214 /// 1215 /// This function can be used to add support for arbitrary programming 1216 /// languages within ESM blocks, however, the keywords (`export`, 1217 /// `import`) are currently hardcoded JavaScript-specific. 1218 /// 1219 /// > 👉 **Note**: please raise an issue if you’re interested in working on 1220 /// > MDX that is aware of, say, Rust, or other programming languages. 1221 /// 1222 /// It only makes sense to pass this when compiling to a syntax tree 1223 /// with [`to_mdast()`][crate::to_mdast()]. 1224 /// 1225 /// For an example that adds support for JavaScript with SWC, see 1226 /// `tests/test_utils/mod.rs`. 1227 #[cfg_attr(feature = "serde", serde(skip))] 1228 pub mdx_esm_parse: Option<Box<MdxEsmParse>>, 1229 // Note: when adding fields, don’t forget to add them to `fmt::Debug` below. 1230} 1231 1232impl fmt::Debug for ParseOptions { 1233 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1234 f.debug_struct("ParseOptions") 1235 .field("constructs", &self.constructs) 1236 .field( 1237 "gfm_strikethrough_single_tilde", 1238 &self.gfm_strikethrough_single_tilde, 1239 ) 1240 .field("math_text_single_dollar", &self.math_text_single_dollar) 1241 .field( 1242 "mdx_expression_parse", 1243 &self.mdx_expression_parse.as_ref().map(|_d| "[Function]"), 1244 ) 1245 .field( 1246 "mdx_esm_parse", 1247 &self.mdx_esm_parse.as_ref().map(|_d| "[Function]"), 1248 ) 1249 .finish() 1250 } 1251} 1252 1253impl Default for ParseOptions { 1254 /// `CommonMark` defaults. 1255 fn default() -> Self { 1256 Self { 1257 constructs: Constructs::default(), 1258 gfm_strikethrough_single_tilde: true, 1259 math_text_single_dollar: true, 1260 mdx_expression_parse: None, 1261 mdx_esm_parse: None, 1262 } 1263 } 1264} 1265 1266impl ParseOptions { 1267 /// GFM. 1268 /// 1269 /// GFM stands for GitHub flavored markdown. 1270 /// GFM extends `CommonMark` and adds support for autolink literals, 1271 /// footnotes, strikethrough, tables, and tasklists. 1272 /// 1273 /// For more information, see the GFM specification: 1274 /// <https://github.github.com/gfm/> 1275 pub fn gfm() -> Self { 1276 Self { 1277 constructs: Constructs::gfm(), 1278 ..Self::default() 1279 } 1280 } 1281 1282 /// MDX. 1283 /// 1284 /// This turns on `CommonMark`, turns off some conflicting constructs 1285 /// (autolinks, code (indented), and HTML), and turns on MDX (ESM, 1286 /// expressions, and JSX). 1287 /// 1288 /// For more information, see the MDX website: 1289 /// <https://mdxjs.com>. 1290 /// 1291 /// > 👉 **Note**: to support ESM, you *must* pass 1292 /// > [`mdx_esm_parse`][MdxEsmParse] in [`ParseOptions`][] too. 1293 /// > Otherwise, ESM is treated as normal markdown. 1294 /// > 1295 /// > You *can* pass 1296 /// > [`mdx_expression_parse`][MdxExpressionParse] 1297 /// > to parse expressions according to a certain grammar (typically, a 1298 /// > programming language). 1299 /// > Otherwise, expressions are parsed with a basic algorithm that only 1300 /// > cares about braces. 1301 pub fn mdx() -> Self { 1302 Self { 1303 constructs: Constructs::mdx(), 1304 ..Self::default() 1305 } 1306 } 1307} 1308 1309/// Configuration that describes how to parse from markdown and compile to 1310/// HTML. 1311/// 1312/// In most cases, you will want to use the default trait or `gfm` method. 1313/// 1314/// ## Examples 1315/// 1316/// ``` 1317/// use markdown::Options; 1318/// # fn main() { 1319/// 1320/// // Use the default trait to compile markdown to HTML according to `CommonMark`: 1321/// let commonmark = Options::default(); 1322/// 1323/// // Use the `gfm` method to compile markdown to HTML according to GFM: 1324/// let gfm = Options::gfm(); 1325/// # } 1326/// ``` 1327#[allow(clippy::struct_excessive_bools)] 1328#[derive(Debug, Default)] 1329#[cfg_attr( 1330 feature = "serde", 1331 derive(serde::Serialize, serde::Deserialize), 1332 serde(default) 1333)] 1334pub struct Options { 1335 /// Configuration that describes how to parse from markdown. 1336 pub parse: ParseOptions, 1337 /// Configuration that describes how to compile to HTML. 1338 pub compile: CompileOptions, 1339} 1340 1341impl Options { 1342 /// GFM. 1343 /// 1344 /// GFM stands for GitHub flavored markdown. 1345 /// GFM extends `CommonMark` and adds support for autolink literals, 1346 /// footnotes, strikethrough, tables, and tasklists. 1347 /// On the compilation side, GFM turns on the GFM tag filter. 1348 /// The tagfilter is useless, but it’s included here for consistency. 1349 /// 1350 /// For more information, see the GFM specification: 1351 /// <https://github.github.com/gfm/> 1352 pub fn gfm() -> Self { 1353 Self { 1354 parse: ParseOptions::gfm(), 1355 compile: CompileOptions::gfm(), 1356 } 1357 } 1358} 1359 1360#[cfg(test)] 1361mod tests { 1362 use super::*; 1363 use crate::util::mdx::Signal; 1364 use alloc::format; 1365 1366 #[test] 1367 fn test_constructs() { 1368 Constructs::default(); 1369 Constructs::gfm(); 1370 Constructs::mdx(); 1371 1372 let constructs = Constructs::default(); 1373 assert!(constructs.attention, "should default to `CommonMark` (1)"); 1374 assert!( 1375 !constructs.gfm_autolink_literal, 1376 "should default to `CommonMark` (2)" 1377 ); 1378 assert!( 1379 !constructs.mdx_jsx_flow, 1380 "should default to `CommonMark` (3)" 1381 ); 1382 assert!( 1383 !constructs.frontmatter, 1384 "should default to `CommonMark` (4)" 1385 ); 1386 1387 let constructs = Constructs::gfm(); 1388 assert!(constructs.attention, "should support `gfm` shortcut (1)"); 1389 assert!( 1390 constructs.gfm_autolink_literal, 1391 "should support `gfm` shortcut (2)" 1392 ); 1393 assert!( 1394 !constructs.mdx_jsx_flow, 1395 "should support `gfm` shortcut (3)" 1396 ); 1397 assert!(!constructs.frontmatter, "should support `gfm` shortcut (4)"); 1398 1399 let constructs = Constructs::mdx(); 1400 assert!(constructs.attention, "should support `gfm` shortcut (1)"); 1401 assert!( 1402 !constructs.gfm_autolink_literal, 1403 "should support `mdx` shortcut (2)" 1404 ); 1405 assert!(constructs.mdx_jsx_flow, "should support `mdx` shortcut (3)"); 1406 assert!(!constructs.frontmatter, "should support `mdx` shortcut (4)"); 1407 } 1408 1409 #[test] 1410 fn test_parse_options() { 1411 ParseOptions::default(); 1412 ParseOptions::gfm(); 1413 ParseOptions::mdx(); 1414 1415 let options = ParseOptions::default(); 1416 assert!( 1417 options.constructs.attention, 1418 "should default to `CommonMark` (1)" 1419 ); 1420 assert!( 1421 !options.constructs.gfm_autolink_literal, 1422 "should default to `CommonMark` (2)" 1423 ); 1424 assert!( 1425 !options.constructs.mdx_jsx_flow, 1426 "should default to `CommonMark` (3)" 1427 ); 1428 1429 let options = ParseOptions::gfm(); 1430 assert!( 1431 options.constructs.attention, 1432 "should support `gfm` shortcut (1)" 1433 ); 1434 assert!( 1435 options.constructs.gfm_autolink_literal, 1436 "should support `gfm` shortcut (2)" 1437 ); 1438 assert!( 1439 !options.constructs.mdx_jsx_flow, 1440 "should support `gfm` shortcut (3)" 1441 ); 1442 1443 let options = ParseOptions::mdx(); 1444 assert!( 1445 options.constructs.attention, 1446 "should support `mdx` shortcut (1)" 1447 ); 1448 assert!( 1449 !options.constructs.gfm_autolink_literal, 1450 "should support `mdx` shortcut (2)" 1451 ); 1452 assert!( 1453 options.constructs.mdx_jsx_flow, 1454 "should support `mdx` shortcut (3)" 1455 ); 1456 1457 assert_eq!( 1458 format!("{:?}", ParseOptions::default()), 1459 "ParseOptions { constructs: Constructs { attention: true, autolink: true, block_quote: true, character_escape: true, character_reference: true, code_indented: true, code_fenced: true, code_text: true, definition: true, frontmatter: false, gfm_autolink_literal: false, gfm_footnote_definition: false, gfm_label_start_footnote: false, gfm_strikethrough: false, gfm_table: false, gfm_task_list_item: false, hard_break_escape: true, hard_break_trailing: true, heading_atx: true, heading_setext: true, html_flow: true, html_text: true, label_start_image: true, label_start_link: true, label_end: true, list_item: true, math_flow: false, math_text: false, mdx_esm: false, mdx_expression_flow: false, mdx_expression_text: false, mdx_jsx_flow: false, mdx_jsx_text: false, thematic_break: true }, gfm_strikethrough_single_tilde: true, math_text_single_dollar: true, mdx_expression_parse: None, mdx_esm_parse: None }", 1460 "should support `Debug` trait" 1461 ); 1462 assert_eq!( 1463 format!("{:?}", ParseOptions { 1464 mdx_esm_parse: Some(Box::new(|_value| { 1465 Signal::Ok 1466 })), 1467 mdx_expression_parse: Some(Box::new(|_value, _kind| { 1468 Signal::Ok 1469 })), 1470 ..Default::default() 1471 }), 1472 "ParseOptions { constructs: Constructs { attention: true, autolink: true, block_quote: true, character_escape: true, character_reference: true, code_indented: true, code_fenced: true, code_text: true, definition: true, frontmatter: false, gfm_autolink_literal: false, gfm_footnote_definition: false, gfm_label_start_footnote: false, gfm_strikethrough: false, gfm_table: false, gfm_task_list_item: false, hard_break_escape: true, hard_break_trailing: true, heading_atx: true, heading_setext: true, html_flow: true, html_text: true, label_start_image: true, label_start_link: true, label_end: true, list_item: true, math_flow: false, math_text: false, mdx_esm: false, mdx_expression_flow: false, mdx_expression_text: false, mdx_jsx_flow: false, mdx_jsx_text: false, thematic_break: true }, gfm_strikethrough_single_tilde: true, math_text_single_dollar: true, mdx_expression_parse: Some(\"[Function]\"), mdx_esm_parse: Some(\"[Function]\") }", 1473 "should support `Debug` trait on mdx functions" 1474 ); 1475 } 1476 1477 #[test] 1478 fn test_compile_options() { 1479 CompileOptions::default(); 1480 CompileOptions::gfm(); 1481 1482 let options = CompileOptions::default(); 1483 assert!( 1484 !options.allow_dangerous_html, 1485 "should default to safe `CommonMark` (1)" 1486 ); 1487 assert!( 1488 !options.gfm_tagfilter, 1489 "should default to safe `CommonMark` (2)" 1490 ); 1491 1492 let options = CompileOptions::gfm(); 1493 assert!( 1494 !options.allow_dangerous_html, 1495 "should support safe `gfm` shortcut (1)" 1496 ); 1497 assert!( 1498 options.gfm_tagfilter, 1499 "should support safe `gfm` shortcut (1)" 1500 ); 1501 } 1502 1503 #[test] 1504 fn test_options() { 1505 Options::default(); 1506 1507 let options = Options::default(); 1508 assert!( 1509 options.parse.constructs.attention, 1510 "should default to safe `CommonMark` (1)" 1511 ); 1512 assert!( 1513 !options.parse.constructs.gfm_autolink_literal, 1514 "should default to safe `CommonMark` (2)" 1515 ); 1516 assert!( 1517 !options.parse.constructs.mdx_jsx_flow, 1518 "should default to safe `CommonMark` (3)" 1519 ); 1520 assert!( 1521 !options.compile.allow_dangerous_html, 1522 "should default to safe `CommonMark` (4)" 1523 ); 1524 1525 let options = Options::gfm(); 1526 assert!( 1527 options.parse.constructs.attention, 1528 "should support safe `gfm` shortcut (1)" 1529 ); 1530 assert!( 1531 options.parse.constructs.gfm_autolink_literal, 1532 "should support safe `gfm` shortcut (2)" 1533 ); 1534 assert!( 1535 !options.parse.constructs.mdx_jsx_flow, 1536 "should support safe `gfm` shortcut (3)" 1537 ); 1538 assert!( 1539 !options.compile.allow_dangerous_html, 1540 "should support safe `gfm` shortcut (4)" 1541 ); 1542 } 1543}