satteri_pulldown_cmark/lib.rs
1// Copyright 2015 Google Inc. All rights reserved.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21//! Pull parser for [CommonMark](https://commonmark.org). This crate provides a [Parser](struct.Parser.html) struct
22//! which is an iterator over [Event](enum.Event.html)s. This iterator can be used
23//! directly, or to build an arena representation via [`parse()`].
24//!
25//! By default, only CommonMark features are enabled. To use extensions like tables,
26//! footnotes or task lists, enable them by setting the corresponding flags in the
27//! [Options](struct.Options.html) struct.
28//!
29//! # Example
30//! ```rust
31//! use satteri_pulldown_cmark::{parse, Options};
32//!
33//! let markdown_input = "Hello world, this is a ~~complicated~~ *very simple* example.";
34//!
35//! let mut options = Options::empty();
36//! options.insert(Options::ENABLE_STRIKETHROUGH);
37//! let (arena, _) = parse(markdown_input, options);
38//! let html = satteri_ast::mdast_to_html(&arena);
39//!
40//! let expected_html = "<p>Hello world, this is a <del>complicated</del> <em>very simple</em> example.</p>\n";
41//! assert_eq!(expected_html, &html);
42//! ```
43//!
44//! Note that consecutive text events can happen due to the manner in which the
45//! parser evaluates the source. A utility `TextMergeStream` exists to improve
46//! the comfort of iterating the events:
47//!
48//! ```rust
49//! use satteri_pulldown_cmark::{Event, Parser, TextMergeStream};
50//!
51//! let markdown_input = "Hello world, this is a ~~complicated~~ *very simple* example.";
52//!
53//! let iterator = TextMergeStream::new(Parser::new(markdown_input));
54//!
55//! for event in iterator {
56//! match event {
57//! Event::Text(text) => println!("{}", text),
58//! _ => {}
59//! }
60//! }
61//! ```
62//!
63#![warn(
64 clippy::alloc_instead_of_core,
65 clippy::std_instead_of_alloc,
66 clippy::std_instead_of_core
67)]
68#![forbid(unsafe_code)]
69#![warn(missing_debug_implementations)]
70#![cfg_attr(not(feature = "std"), no_std)]
71
72#[macro_use]
73extern crate alloc;
74
75#[cfg(feature = "std")]
76extern crate std;
77
78#[cfg(not(feature = "std"))]
79compile_error!("This crate requires the \"std\" feature.");
80
81use alloc::vec::Vec;
82
83#[cfg(feature = "serde")]
84use serde::{Deserialize, Serialize};
85
86pub mod utils;
87
88pub mod arena_build;
89mod entities;
90mod firstpass;
91mod linklabel;
92#[cfg(feature = "mdx")]
93mod mdx;
94mod parse;
95pub(crate) mod post_passes;
96mod puncttable;
97mod scanners;
98mod strings;
99mod tree;
100
101use core::fmt::Display;
102
103#[cfg(feature = "mdx")]
104pub use crate::arena_build::MDX_OPTIONS;
105pub use crate::{
106 arena_build::{
107 DEFAULT_OPTIONS, parse, parse_into, parse_no_positions, parse_no_positions_into,
108 },
109 parse::{
110 BrokenLink, BrokenLinkCallback, DefaultParserCallbacks, OffsetIter, Parser,
111 ParserCallbacks, RefDefs,
112 },
113 strings::{CowStr, InlineStr},
114 utils::*,
115};
116
117/// Codeblock kind.
118#[derive(Clone, Debug, PartialEq)]
119#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
120pub enum CodeBlockKind<'a> {
121 Indented,
122 /// The value contained in the tag describes the language of the code, which may be empty.
123 #[cfg_attr(feature = "serde", serde(borrow))]
124 Fenced(CowStr<'a>),
125}
126
127impl<'a> CodeBlockKind<'a> {
128 pub fn is_indented(&self) -> bool {
129 matches!(*self, CodeBlockKind::Indented)
130 }
131
132 pub fn is_fenced(&self) -> bool {
133 matches!(*self, CodeBlockKind::Fenced(_))
134 }
135
136 pub fn into_static(self) -> CodeBlockKind<'static> {
137 match self {
138 CodeBlockKind::Indented => CodeBlockKind::Indented,
139 CodeBlockKind::Fenced(s) => CodeBlockKind::Fenced(s.into_static()),
140 }
141 }
142}
143
144/// BlockQuote kind (Note, Tip, Important, Warning, Caution).
145#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
146#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
147pub enum BlockQuoteKind {
148 Note,
149 Tip,
150 Important,
151 Warning,
152 Caution,
153}
154
155/// Directive kind.
156#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
157#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
158pub enum DirectiveKind {
159 Container,
160 Leaf,
161 Text,
162}
163
164/// Metadata block kind.
165#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
166#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
167pub enum MetadataBlockKind {
168 YamlStyle,
169 PlusesStyle,
170}
171
172/// Tags for elements that can contain other elements.
173#[derive(Clone, Debug, PartialEq)]
174#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
175pub enum Tag<'a> {
176 /// A paragraph of text and other inline elements.
177 Paragraph,
178
179 /// A heading, with optional identifier, classes and custom attributes.
180 /// The identifier is prefixed with `#` and the last one in the attributes
181 /// list is chosen, classes are prefixed with `.` and custom attributes
182 /// have no prefix and can optionally have a value (`myattr` or `myattr=myvalue`).
183 ///
184 /// `id`, `classes` and `attrs` are only parsed and populated with [`Options::ENABLE_HEADING_ATTRIBUTES`], `None` or empty otherwise.
185 Heading {
186 level: HeadingLevel,
187 id: Option<CowStr<'a>>,
188 classes: Vec<CowStr<'a>>,
189 /// The first item of the tuple is the attr and second one the value.
190 attrs: Vec<(CowStr<'a>, Option<CowStr<'a>>)>,
191 },
192
193 /// A block quote.
194 ///
195 /// The `BlockQuoteKind` is only parsed & populated with [`Options::ENABLE_GFM`], `None` otherwise.
196 ///
197 /// ```markdown
198 /// > regular quote
199 ///
200 /// > [!NOTE]
201 /// > note quote
202 /// ```
203 BlockQuote(Option<BlockQuoteKind>),
204 /// A code block.
205 CodeBlock(CodeBlockKind<'a>),
206 /// A directive (container, leaf, or text).
207 /// Only parsed and emitted with [`Options::ENABLE_DIRECTIVE`].
208 Directive {
209 kind: DirectiveKind,
210 name: CowStr<'a>,
211 attributes: Vec<(CowStr<'a>, CowStr<'a>)>,
212 },
213
214 /// An HTML block.
215 ///
216 /// A line that begins with some predefined tags (HTML block tags) (see [CommonMark Spec](https://spec.commonmark.org/0.31.2/#html-blocks) for more details) or any tag that is followed only by whitespace.
217 ///
218 /// Most HTML blocks end on an empty line, though some e.g. `<pre>` like `<script>` or `<!-- Comments -->` don't.
219 /// ```markdown
220 /// <body> Is HTML block even though here is non-whitespace.
221 /// Block ends on an empty line.
222 ///
223 /// <some-random-tag>
224 /// This is HTML block.
225 ///
226 /// <pre> Doesn't end on empty lines.
227 ///
228 /// This is still the same block.</pre>
229 /// ```
230 HtmlBlock,
231
232 /// A list. If the list is ordered the first field indicates the number of the first item.
233 /// The second field is `true` when the list is tight (no blank lines between items).
234 /// Contains only list items.
235 List(Option<u64>, bool),
236 /// A list item.
237 Item,
238 /// A footnote definition. The value contained is the footnote's label by which it can
239 /// be referred to.
240 ///
241 /// Only parsed and emitted with [`Options::ENABLE_FOOTNOTES`].
242 #[cfg_attr(feature = "serde", serde(borrow))]
243 FootnoteDefinition(CowStr<'a>),
244
245 /// Only parsed and emitted with [`Options::ENABLE_DEFINITION_LIST`].
246 DefinitionList,
247 /// Only parsed and emitted with [`Options::ENABLE_DEFINITION_LIST`].
248 DefinitionListTitle,
249 /// Only parsed and emitted with [`Options::ENABLE_DEFINITION_LIST`].
250 DefinitionListDefinition,
251
252 /// A table. Contains a vector describing the text-alignment for each of its columns.
253 /// Only parsed and emitted with [`Options::ENABLE_TABLES`].
254 Table(Vec<Alignment>),
255 /// A table header. Contains only `TableCell`s. Note that the table body starts immediately
256 /// after the closure of the `TableHead` tag. There is no `TableBody` tag.
257 /// Only parsed and emitted with [`Options::ENABLE_TABLES`].
258 TableHead,
259 /// A table row. Is used both for header rows as body rows. Contains only `TableCell`s.
260 /// Only parsed and emitted with [`Options::ENABLE_TABLES`].
261 TableRow,
262 /// Only parsed and emitted with [`Options::ENABLE_TABLES`].
263 TableCell,
264
265 // span-level tags
266 /// [Emphasis](https://spec.commonmark.org/0.31.2/#emphasis-and-strong-emphasis).
267 /// ```markdown
268 /// half*emph* _strong_ _multi _level__
269 /// ```
270 Emphasis,
271 /// [Strong emphasis](https://spec.commonmark.org/0.31.2/#emphasis-and-strong-emphasis).
272 /// ```markdown
273 /// half**strong** __strong__ __multi __level____
274 /// ```
275 Strong,
276 /// Only parsed and emitted with [`Options::ENABLE_STRIKETHROUGH`].
277 ///
278 /// ```markdown
279 /// ~strike through~
280 /// ```
281 Strikethrough,
282 /// Only parsed and emitted with [`Options::ENABLE_SUPERSCRIPT`].
283 ///
284 /// ```markdown
285 /// ^superscript^
286 /// ```
287 Superscript,
288 /// Only parsed and emitted with [`Options::ENABLE_SUBSCRIPT`], if disabled `~something~` is parsed as [`Strikethrough`](Self::Strikethrough).
289 /// ```markdown
290 /// ~subscript~ ~~if also enabled this is strikethrough~~
291 /// ```
292 Subscript,
293
294 /// A link.
295 Link {
296 link_type: LinkType,
297 dest_url: CowStr<'a>,
298 title: CowStr<'a>,
299 /// Identifier of reference links, e.g. `world` in the link `[hello][world]`.
300 id: CowStr<'a>,
301 },
302
303 /// An image. The first field is the link type, the second the destination URL and the third is a title,
304 /// the fourth is the link identifier.
305 Image {
306 link_type: LinkType,
307 dest_url: CowStr<'a>,
308 title: CowStr<'a>,
309 /// Identifier of reference links, e.g. `world` in the link `[hello][world]`.
310 id: CowStr<'a>,
311 },
312
313 /// A metadata block.
314 /// Only parsed and emitted with [`Options::ENABLE_YAML_STYLE_METADATA_BLOCKS`]
315 /// or [`Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS`].
316 MetadataBlock(MetadataBlockKind),
317
318 /// An MDX JSX element (flow-level, i.e. block).
319 /// Only parsed and emitted with [`Options::ENABLE_MDX`].
320 /// The `CowStr` is the raw JSX tag content (e.g. `Component x={1}`).
321 /// ```mdx
322 /// <Component x={1}>
323 /// children
324 /// </Component>
325 /// ```
326 #[cfg(feature = "mdx")]
327 #[cfg_attr(feature = "serde", serde(borrow))]
328 MdxJsxFlowElement(CowStr<'a>),
329
330 /// An MDX JSX element (text-level, i.e. inline).
331 /// Only parsed and emitted with [`Options::ENABLE_MDX`].
332 #[cfg(feature = "mdx")]
333 #[cfg_attr(feature = "serde", serde(borrow))]
334 MdxJsxTextElement(CowStr<'a>),
335}
336
337impl<'a> Tag<'a> {
338 pub fn to_end(&self) -> TagEnd {
339 match self {
340 Tag::Paragraph => TagEnd::Paragraph,
341 Tag::Heading { level, .. } => TagEnd::Heading(*level),
342 Tag::BlockQuote(kind) => TagEnd::BlockQuote(*kind),
343 Tag::CodeBlock(_) => TagEnd::CodeBlock,
344 Tag::Directive { kind, .. } => TagEnd::Directive(*kind),
345 Tag::HtmlBlock => TagEnd::HtmlBlock,
346 Tag::List(number, _) => TagEnd::List(number.is_some()),
347 Tag::Item => TagEnd::Item,
348 Tag::FootnoteDefinition(_) => TagEnd::FootnoteDefinition,
349 Tag::Table(_) => TagEnd::Table,
350 Tag::TableHead => TagEnd::TableHead,
351 Tag::TableRow => TagEnd::TableRow,
352 Tag::TableCell => TagEnd::TableCell,
353 Tag::Subscript => TagEnd::Subscript,
354 Tag::Superscript => TagEnd::Superscript,
355 Tag::Emphasis => TagEnd::Emphasis,
356 Tag::Strong => TagEnd::Strong,
357 Tag::Strikethrough => TagEnd::Strikethrough,
358 Tag::Link { .. } => TagEnd::Link,
359 Tag::Image { .. } => TagEnd::Image,
360 Tag::MetadataBlock(kind) => TagEnd::MetadataBlock(*kind),
361 Tag::DefinitionList => TagEnd::DefinitionList,
362 Tag::DefinitionListTitle => TagEnd::DefinitionListTitle,
363 Tag::DefinitionListDefinition => TagEnd::DefinitionListDefinition,
364 #[cfg(feature = "mdx")]
365 Tag::MdxJsxFlowElement(_) => TagEnd::MdxJsxFlowElement,
366 #[cfg(feature = "mdx")]
367 Tag::MdxJsxTextElement(_) => TagEnd::MdxJsxTextElement,
368 }
369 }
370
371 pub fn into_static(self) -> Tag<'static> {
372 match self {
373 Tag::Paragraph => Tag::Paragraph,
374 Tag::Heading {
375 level,
376 id,
377 classes,
378 attrs,
379 } => Tag::Heading {
380 level,
381 id: id.map(|s| s.into_static()),
382 classes: classes.into_iter().map(|s| s.into_static()).collect(),
383 attrs: attrs
384 .into_iter()
385 .map(|(k, v)| (k.into_static(), v.map(|s| s.into_static())))
386 .collect(),
387 },
388 Tag::BlockQuote(k) => Tag::BlockQuote(k),
389 Tag::CodeBlock(kb) => Tag::CodeBlock(kb.into_static()),
390 Tag::Directive {
391 kind,
392 name,
393 attributes,
394 } => Tag::Directive {
395 kind,
396 name: name.into_static(),
397 attributes: attributes
398 .into_iter()
399 .map(|(k, v)| (k.into_static(), v.into_static()))
400 .collect(),
401 },
402 Tag::HtmlBlock => Tag::HtmlBlock,
403 Tag::List(v, t) => Tag::List(v, t),
404 Tag::Item => Tag::Item,
405 Tag::FootnoteDefinition(a) => Tag::FootnoteDefinition(a.into_static()),
406 Tag::Table(v) => Tag::Table(v),
407 Tag::TableHead => Tag::TableHead,
408 Tag::TableRow => Tag::TableRow,
409 Tag::TableCell => Tag::TableCell,
410 Tag::Emphasis => Tag::Emphasis,
411 Tag::Strong => Tag::Strong,
412 Tag::Strikethrough => Tag::Strikethrough,
413 Tag::Superscript => Tag::Superscript,
414 Tag::Subscript => Tag::Subscript,
415 Tag::Link {
416 link_type,
417 dest_url,
418 title,
419 id,
420 } => Tag::Link {
421 link_type,
422 dest_url: dest_url.into_static(),
423 title: title.into_static(),
424 id: id.into_static(),
425 },
426 Tag::Image {
427 link_type,
428 dest_url,
429 title,
430 id,
431 } => Tag::Image {
432 link_type,
433 dest_url: dest_url.into_static(),
434 title: title.into_static(),
435 id: id.into_static(),
436 },
437 Tag::MetadataBlock(v) => Tag::MetadataBlock(v),
438 Tag::DefinitionList => Tag::DefinitionList,
439 Tag::DefinitionListTitle => Tag::DefinitionListTitle,
440 Tag::DefinitionListDefinition => Tag::DefinitionListDefinition,
441 #[cfg(feature = "mdx")]
442 Tag::MdxJsxFlowElement(s) => Tag::MdxJsxFlowElement(s.into_static()),
443 #[cfg(feature = "mdx")]
444 Tag::MdxJsxTextElement(s) => Tag::MdxJsxTextElement(s.into_static()),
445 }
446 }
447}
448
449/// The end of a `Tag`.
450#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
451#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
452pub enum TagEnd {
453 Paragraph,
454 Heading(HeadingLevel),
455
456 BlockQuote(Option<BlockQuoteKind>),
457 CodeBlock,
458 Directive(DirectiveKind),
459
460 HtmlBlock,
461
462 /// A list, `true` for ordered lists.
463 List(bool),
464 Item,
465 FootnoteDefinition,
466
467 DefinitionList,
468 DefinitionListTitle,
469 DefinitionListDefinition,
470
471 Table,
472 TableHead,
473 TableRow,
474 TableCell,
475
476 Emphasis,
477 Strong,
478 Strikethrough,
479 Superscript,
480 Subscript,
481
482 Link,
483 Image,
484
485 MetadataBlock(MetadataBlockKind),
486
487 #[cfg(feature = "mdx")]
488 MdxJsxFlowElement,
489 #[cfg(feature = "mdx")]
490 MdxJsxTextElement,
491}
492
493/// Make sure `TagEnd` is no more than two bytes in size.
494/// This is why it's used instead of just using `Tag`.
495#[cfg(target_pointer_width = "64")]
496const _STATIC_ASSERT_TAG_END_SIZE: [(); 2] = [(); core::mem::size_of::<TagEnd>()];
497
498impl<'a> From<Tag<'a>> for TagEnd {
499 fn from(value: Tag) -> Self {
500 value.to_end()
501 }
502}
503
504#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
505#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
506pub enum HeadingLevel {
507 H1 = 1,
508 H2,
509 H3,
510 H4,
511 H5,
512 H6,
513}
514
515impl Display for HeadingLevel {
516 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
517 match self {
518 Self::H1 => write!(f, "h1"),
519 Self::H2 => write!(f, "h2"),
520 Self::H3 => write!(f, "h3"),
521 Self::H4 => write!(f, "h4"),
522 Self::H5 => write!(f, "h5"),
523 Self::H6 => write!(f, "h6"),
524 }
525 }
526}
527
528/// Returned when trying to convert a `usize` into a `Heading` but it fails
529/// because the usize isn't a valid heading level
530#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
531pub struct InvalidHeadingLevel(usize);
532
533impl TryFrom<usize> for HeadingLevel {
534 type Error = InvalidHeadingLevel;
535
536 fn try_from(value: usize) -> Result<Self, Self::Error> {
537 match value {
538 1 => Ok(Self::H1),
539 2 => Ok(Self::H2),
540 3 => Ok(Self::H3),
541 4 => Ok(Self::H4),
542 5 => Ok(Self::H5),
543 6 => Ok(Self::H6),
544 _ => Err(InvalidHeadingLevel(value)),
545 }
546 }
547}
548
549/// Type specifier for inline links. See [the Tag::Link](enum.Tag.html#variant.Link) for more information.
550#[derive(Clone, Debug, PartialEq, Copy)]
551#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
552pub enum LinkType {
553 /// Inline link like `[foo](bar)`
554 Inline,
555 /// Reference link like `[foo][bar]`
556 Reference,
557 /// Reference without destination in the document, but resolved by the broken_link_callback
558 ReferenceUnknown,
559 /// Collapsed link like `[foo][]`
560 Collapsed,
561 /// Collapsed link without destination in the document, but resolved by the broken_link_callback
562 CollapsedUnknown,
563 /// Shortcut link like `[foo]`
564 Shortcut,
565 /// Shortcut without destination in the document, but resolved by the broken_link_callback
566 ShortcutUnknown,
567 /// Autolink like `<http://foo.bar/baz>`
568 Autolink,
569 /// Email address in autolink like `<john@example.org>`
570 Email,
571 /// Wikilink link like `[[foo]]` or `[[foo|bar]]`
572 WikiLink {
573 /// `true` if the wikilink was piped.
574 ///
575 /// * `true` - `[[foo|bar]]`
576 /// * `false` - `[[foo]]`
577 has_pothole: bool,
578 },
579}
580
581impl LinkType {
582 /// Map the link type to an equivalent _Unknown link type.
583 fn to_unknown(self) -> Self {
584 match self {
585 LinkType::Reference => LinkType::ReferenceUnknown,
586 LinkType::Collapsed => LinkType::CollapsedUnknown,
587 LinkType::Shortcut => LinkType::ShortcutUnknown,
588 _ => unreachable!(),
589 }
590 }
591}
592
593/// Markdown events that are generated in a preorder traversal of the document
594/// tree, with additional `End` events whenever all of an inner node's children
595/// have been visited.
596#[derive(Clone, Debug, PartialEq)]
597#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
598pub enum Event<'a> {
599 /// Start of a tagged element. Events that are yielded after this event
600 /// and before its corresponding `End` event are inside this element.
601 /// Start and end events are guaranteed to be balanced.
602 #[cfg_attr(feature = "serde", serde(borrow))]
603 Start(Tag<'a>),
604 /// End of a tagged element.
605 End(TagEnd),
606 /// A text node.
607 ///
608 /// All text, outside and inside [`Tag`]s.
609 #[cfg_attr(feature = "serde", serde(borrow))]
610 Text(CowStr<'a>),
611 /// An [inline code node](https://spec.commonmark.org/0.31.2/#code-spans).
612 ///
613 /// ```markdown
614 /// `code`
615 /// ```
616 #[cfg_attr(feature = "serde", serde(borrow))]
617 Code(CowStr<'a>),
618 /// An inline math environment node.
619 /// Requires [`Options::ENABLE_MATH`].
620 ///
621 /// ```markdown
622 /// $math$
623 /// ```
624 #[cfg_attr(feature = "serde", serde(borrow))]
625 InlineMath(CowStr<'a>),
626 /// A display math environment node.
627 /// Requires [`Options::ENABLE_MATH`].
628 ///
629 /// ```markdown
630 /// $$math$$
631 /// ```
632 #[cfg_attr(feature = "serde", serde(borrow))]
633 DisplayMath(CowStr<'a>),
634 /// An HTML node.
635 ///
636 /// A line of HTML inside [`Tag::HtmlBlock`] includes the line break.
637 #[cfg_attr(feature = "serde", serde(borrow))]
638 Html(CowStr<'a>),
639 /// An [inline HTML node](https://spec.commonmark.org/0.31.2/#raw-html).
640 ///
641 /// Contains only the tag itself, e.g. `<open-tag>`, `</close-tag>` or `<!-- comment -->`.
642 ///
643 /// **Note**: Under some conditions HTML can also be parsed as an HTML Block, see [`Tag::HtmlBlock`] for details.
644 #[cfg_attr(feature = "serde", serde(borrow))]
645 InlineHtml(CowStr<'a>),
646 /// A reference to a footnote with given label, defined
647 /// by an event with a [`Tag::FootnoteDefinition`] tag. Definitions and references to them may
648 /// occur in any order. Only parsed and emitted with [`Options::ENABLE_FOOTNOTES`].
649 ///
650 /// ```markdown
651 /// [^1]
652 /// ```
653 #[cfg_attr(feature = "serde", serde(borrow))]
654 FootnoteReference(CowStr<'a>),
655 /// A [soft line break](https://spec.commonmark.org/0.31.2/#soft-line-breaks).
656 ///
657 /// Any line break that isn't a [`HardBreak`](Self::HardBreak), or the end of e.g. a paragraph.
658 SoftBreak,
659 /// A [hard line break](https://spec.commonmark.org/0.31.2/#hard-line-breaks).
660 ///
661 /// A line ending that is either preceded by at least two spaces or `\`.
662 ///
663 /// ```markdown
664 /// hard··
665 /// line\
666 /// breaks
667 /// ```
668 /// *`·` is a space*
669 HardBreak,
670 /// A horizontal ruler.
671 ///
672 /// ```markdown
673 /// ***
674 /// ···---
675 /// _·_··_····_··
676 /// ```
677 /// *`·` is any whitespace*
678 Rule,
679 /// A task list marker, rendered as a checkbox in HTML. Contains a true when it is checked.
680 /// Only parsed and emitted with [`Options::ENABLE_TASKLISTS`].
681 /// ```markdown
682 /// - [ ] unchecked
683 /// - [x] checked
684 /// ```
685 TaskListMarker(bool),
686
687 /// An MDX flow expression (block-level).
688 /// Only parsed and emitted with [`Options::ENABLE_MDX`].
689 /// ```mdx
690 /// {1 + 1}
691 /// ```
692 #[cfg(feature = "mdx")]
693 #[cfg_attr(feature = "serde", serde(borrow))]
694 MdxFlowExpression(CowStr<'a>),
695
696 /// An MDX text expression (inline).
697 /// Only parsed and emitted with [`Options::ENABLE_MDX`].
698 /// ```mdx
699 /// a]n {expression} here
700 /// ```
701 #[cfg(feature = "mdx")]
702 #[cfg_attr(feature = "serde", serde(borrow))]
703 MdxTextExpression(CowStr<'a>),
704
705 /// An MDX ESM block (import/export at document level).
706 /// Only parsed and emitted with [`Options::ENABLE_MDX`].
707 /// ```mdx
708 /// import {Chart} from './chart.js'
709 /// export const meta = {}
710 /// ```
711 #[cfg(feature = "mdx")]
712 #[cfg_attr(feature = "serde", serde(borrow))]
713 MdxEsm(CowStr<'a>),
714}
715
716impl<'a> Event<'a> {
717 pub fn into_static(self) -> Event<'static> {
718 match self {
719 Event::Start(t) => Event::Start(t.into_static()),
720 Event::End(e) => Event::End(e),
721 Event::Text(s) => Event::Text(s.into_static()),
722 Event::Code(s) => Event::Code(s.into_static()),
723 Event::InlineMath(s) => Event::InlineMath(s.into_static()),
724 Event::DisplayMath(s) => Event::DisplayMath(s.into_static()),
725 Event::Html(s) => Event::Html(s.into_static()),
726 Event::InlineHtml(s) => Event::InlineHtml(s.into_static()),
727 Event::FootnoteReference(s) => Event::FootnoteReference(s.into_static()),
728 Event::SoftBreak => Event::SoftBreak,
729 Event::HardBreak => Event::HardBreak,
730 Event::Rule => Event::Rule,
731 Event::TaskListMarker(b) => Event::TaskListMarker(b),
732 #[cfg(feature = "mdx")]
733 Event::MdxFlowExpression(s) => Event::MdxFlowExpression(s.into_static()),
734 #[cfg(feature = "mdx")]
735 Event::MdxTextExpression(s) => Event::MdxTextExpression(s.into_static()),
736 #[cfg(feature = "mdx")]
737 Event::MdxEsm(s) => Event::MdxEsm(s.into_static()),
738 }
739 }
740}
741
742/// Table column text alignment.
743#[derive(Copy, Clone, Debug, PartialEq)]
744#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
745pub enum Alignment {
746 /// Default text alignment.
747 None,
748 Left,
749 Center,
750 Right,
751}
752
753bitflags::bitflags! {
754 /// Option struct containing flags for enabling extra features
755 /// that are not part of the CommonMark spec.
756 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
757 pub struct Options: u32 {
758 const ENABLE_TABLES = 1 << 1;
759 /// GitHub-compatible footnote syntax.
760 ///
761 /// Footnotes are referenced with the syntax `[^IDENT]`,
762 /// and defined with an identifier followed by a colon at top level.
763 ///
764 /// ---
765 ///
766 /// ```markdown
767 /// Footnote referenced [^1].
768 ///
769 /// [^1]: footnote defined
770 /// ```
771 ///
772 /// Footnote referenced [^1].
773 ///
774 /// [^1]: footnote defined
775 const ENABLE_FOOTNOTES = 1 << 2;
776 const ENABLE_STRIKETHROUGH = 1 << 3;
777 const ENABLE_TASKLISTS = 1 << 4;
778 /// Enables replacement of ASCII punctuation characters with
779 /// Unicode ligatures and smart quotes.
780 ///
781 /// This includes replacing `--` with `–`, `---` with `—`, `...` with `…`,
782 /// `”quote”` with `\u{201c}quote\u{201d}`, and `’quote’` with `\u{2018}quote\u{2019}`.
783 ///
784 /// Equivalent to enabling all of `ENABLE_SMART_QUOTES`,
785 /// `ENABLE_SMART_DASHES`, and `ENABLE_SMART_ELLIPSES`.
786 const ENABLE_SMART_PUNCTUATION = 1 << 5;
787 /// Replace straight quotes (`”`, `’`) with curly/smart quotes.
788 const ENABLE_SMART_QUOTES = 1 << 18;
789 /// Replace `--` with en-dash and `---` with em-dash.
790 const ENABLE_SMART_DASHES = 1 << 19;
791 /// Replace `...` with ellipsis (`…`).
792 const ENABLE_SMART_ELLIPSES = 1 << 20;
793 /// Extension to allow headings to have ID and classes.
794 ///
795 /// `# text { #id .class1 .class2 myattr other_attr=myvalue }`
796 /// is interpreted as a level 1 heading
797 /// with the content `text`, ID `id`, classes `class1` and `class2` and
798 /// custom attributes `myattr` (without value) and
799 /// `other_attr` with value `myvalue`.
800 /// Note that ID, classes, and custom attributes should be space-separated.
801 const ENABLE_HEADING_ATTRIBUTES = 1 << 6;
802 /// Metadata blocks in YAML style, i.e.:
803 /// - starting with a `---` line
804 /// - ending with a `---` or `...` line
805 const ENABLE_YAML_STYLE_METADATA_BLOCKS = 1 << 7;
806 /// Metadata blocks delimited by:
807 /// - `+++` line at start
808 /// - `+++` line at end
809 const ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS = 1 << 8;
810 /// Emits `Event::InlineMath` and `Event::DisplayMath` for TeX formulas.
811 /// Umbrella over [`ENABLE_MATH_SINGLE_DOLLAR`](Self::ENABLE_MATH_SINGLE_DOLLAR)
812 /// and [`ENABLE_MATH_MULTI_DOLLAR`](Self::ENABLE_MATH_MULTI_DOLLAR).
813 const ENABLE_MATH = 1 << 10;
814 /// Single-dollar inline math (`$x$`).
815 const ENABLE_MATH_SINGLE_DOLLAR = 1 << 22;
816 /// Multi-dollar math: inline `$$x$$` and `$$` block fences.
817 const ENABLE_MATH_MULTI_DOLLAR = 1 << 23;
818 /// Misc GitHub Flavored Markdown features not supported in CommonMark.
819 const ENABLE_GFM = 1 << 11;
820 /// GitHub-style blockquote alerts ([!NOTE], [!TIP], [!IMPORTANT], [!WARNING], [!CAUTION]).
821 /// Not part of the GFM spec — this is a GitHub-specific feature.
822 const ENABLE_GITHUB_ALERTS = 1 << 21;
823 /// Commonmark-HS-Extensions compatible definition lists.
824 ///
825 /// ```markdown
826 /// title 1
827 /// : definition 1
828 ///
829 /// title 2
830 /// : definition 2a
831 /// : definition 2b
832 /// ```
833 const ENABLE_DEFINITION_LIST = 1 << 12;
834 const ENABLE_SUPERSCRIPT = 1 << 13;
835 const ENABLE_SUBSCRIPT = 1 << 14;
836 /// Obsidian-style Wikilinks.
837 const ENABLE_WIKILINKS = 1 << 15;
838 /// Directives: container (:::), leaf (::), and text (:) directives.
839 const ENABLE_DIRECTIVE = 1 << 16;
840 /// MDX: enables JSX elements, expressions, and ESM import/export.
841 const ENABLE_MDX = 1 << 17;
842 }
843}
844
845impl Options {
846 pub(crate) fn has_smart_quotes(&self) -> bool {
847 self.contains(Options::ENABLE_SMART_PUNCTUATION)
848 || self.contains(Options::ENABLE_SMART_QUOTES)
849 }
850
851 pub(crate) fn has_smart_dashes(&self) -> bool {
852 self.contains(Options::ENABLE_SMART_PUNCTUATION)
853 || self.contains(Options::ENABLE_SMART_DASHES)
854 }
855
856 pub(crate) fn has_smart_ellipses(&self) -> bool {
857 self.contains(Options::ENABLE_SMART_PUNCTUATION)
858 || self.contains(Options::ENABLE_SMART_ELLIPSES)
859 }
860
861 pub(crate) fn has_math(&self) -> bool {
862 self.intersects(
863 Options::ENABLE_MATH
864 | Options::ENABLE_MATH_SINGLE_DOLLAR
865 | Options::ENABLE_MATH_MULTI_DOLLAR,
866 )
867 }
868}