Skip to main content

comrak/
nodes.rs

1//! The CommonMark AST.
2
3use crate::arena_tree::Node;
4use std::cell::RefCell;
5use std::convert::TryFrom;
6
7#[cfg(feature = "shortcodes")]
8pub use crate::parser::shortcodes::NodeShortCode;
9
10pub use crate::parser::math::NodeMath;
11pub use crate::parser::multiline_block_quote::NodeMultilineBlockQuote;
12
13/// The core AST node enum.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum NodeValue {
16    /// The root of every CommonMark document.  Contains **blocks**.
17    Document,
18
19    /// Non-Markdown front matter.  Treated as an opaque blob.
20    FrontMatter(String),
21
22    /// **Block**. A [block quote](https://github.github.com/gfm/#block-quotes).  Contains other
23    /// **blocks**.
24    ///
25    /// ``` md
26    /// > A block quote.
27    /// ```
28    BlockQuote,
29
30    /// **Block**.  A [list](https://github.github.com/gfm/#lists).  Contains
31    /// [list items](https://github.github.com/gfm/#list-items).
32    ///
33    /// ``` md
34    /// * An unordered list
35    /// * Another item
36    ///
37    /// 1. An ordered list
38    /// 2. Another item
39    /// ```
40    List(NodeList),
41
42    /// **Block**.  A [list item](https://github.github.com/gfm/#list-items).  Contains other
43    /// **blocks**.
44    Item(NodeList),
45
46    /// **Block**. A description list, enabled with `ext_description_lists` option.  Contains
47    /// description items.
48    ///
49    /// It is required to put a blank line between terms and details.
50    ///
51    /// ``` md
52    /// Term 1
53    ///
54    /// : Details 1
55    ///
56    /// Term 2
57    ///
58    /// : Details 2
59    /// ```
60    DescriptionList,
61
62    /// *Block**. An item of a description list.  Contains a term and one details block.
63    DescriptionItem(NodeDescriptionItem),
64
65    /// **Block**. Term of an item in a definition list.
66    DescriptionTerm,
67
68    /// **Block**. Details of an item in a definition list.
69    DescriptionDetails,
70
71    /// **Block**. A code block; may be [fenced](https://github.github.com/gfm/#fenced-code-blocks)
72    /// or [indented](https://github.github.com/gfm/#indented-code-blocks).  Contains raw text
73    /// which is not parsed as Markdown, although is HTML escaped.
74    CodeBlock(NodeCodeBlock),
75
76    /// **Block**. A [HTML block](https://github.github.com/gfm/#html-blocks).  Contains raw text
77    /// which is neither parsed as Markdown nor HTML escaped.
78    HtmlBlock(NodeHtmlBlock),
79
80    /// **Block**. A [paragraph](https://github.github.com/gfm/#paragraphs).  Contains **inlines**.
81    Paragraph,
82
83    /// **Block**. A heading; may be an [ATX heading](https://github.github.com/gfm/#atx-headings)
84    /// or a [setext heading](https://github.github.com/gfm/#setext-headings). Contains
85    /// **inlines**.
86    Heading(NodeHeading),
87
88    /// **Block**. A [thematic break](https://github.github.com/gfm/#thematic-breaks).  Has no
89    /// children.
90    ThematicBreak,
91
92    /// **Block**. A footnote definition.  The `String` is the footnote's name.
93    /// Contains other **blocks**.
94    FootnoteDefinition(NodeFootnoteDefinition),
95
96    /// **Block**. A [table](https://github.github.com/gfm/#tables-extension-) per the GFM spec.
97    /// Contains table rows.
98    Table(NodeTable),
99
100    /// **Block**. A table row.  The `bool` represents whether the row is the header row or not.
101    /// Contains table cells.
102    TableRow(bool),
103
104    /// **Block**.  A table cell.  Contains **inlines**.
105    TableCell,
106
107    /// **Inline**.  [Textual content](https://github.github.com/gfm/#textual-content).  All text
108    /// in a document will be contained in a `Text` node.
109    Text(String),
110
111    /// **Block**. [Task list item](https://github.github.com/gfm/#task-list-items-extension-).
112    /// The value is the symbol that was used in the brackets to mark a task item as checked, or
113    /// None if the item is unchecked.
114    TaskItem(Option<char>),
115
116    /// **Inline**.  A [soft line break](https://github.github.com/gfm/#soft-line-breaks).  If
117    /// the `hardbreaks` option is set in `Options` during formatting, it will be formatted
118    /// as a `LineBreak`.
119    SoftBreak,
120
121    /// **Inline**.  A [hard line break](https://github.github.com/gfm/#hard-line-breaks).
122    LineBreak,
123
124    /// **Inline**.  A [code span](https://github.github.com/gfm/#code-spans).
125    Code(NodeCode),
126
127    /// **Inline**.  [Raw HTML](https://github.github.com/gfm/#raw-html) contained inline.
128    HtmlInline(String),
129
130    /// **Block/Inline**.  A Raw output node. This will be inserted verbatim into CommonMark and
131    /// HTML output. It can only be created programmatically, and is never parsed from input.
132    Raw(String),
133
134    /// **Inline**.  [Emphasized](https://github.github.com/gfm/#emphasis-and-strong-emphasis)
135    /// text.
136    Emph,
137
138    /// **Inline**.  [Strong](https://github.github.com/gfm/#emphasis-and-strong-emphasis) text.
139    Strong,
140
141    /// **Inline**.  [Strikethrough](https://github.github.com/gfm/#strikethrough-extension-) text
142    /// per the GFM spec.
143    Strikethrough,
144
145    /// **Inline**.  Superscript.  Enabled with `ext_superscript` option.
146    Superscript,
147
148    /// **Inline**.  A [link](https://github.github.com/gfm/#links) to some URL, with possible
149    /// title.
150    Link(NodeLink),
151
152    /// **Inline**.  An [image](https://github.github.com/gfm/#images).
153    Image(NodeLink),
154
155    /// **Inline**.  A footnote reference.
156    FootnoteReference(NodeFootnoteReference),
157
158    #[cfg(feature = "shortcodes")]
159    /// **Inline**. An Emoji character generated from a shortcode. Enable with feature "shortcodes".
160    ShortCode(NodeShortCode),
161
162    /// **Inline**. A math span. Contains raw text which is not parsed as Markdown.
163    /// Dollar math or code math
164    ///
165    /// Inline math $1 + 2$ and $`1 + 2`$
166    ///
167    /// Display math $$1 + 2$$ and
168    /// $$
169    /// 1 + 2
170    /// $$
171    ///
172    Math(NodeMath),
173
174    /// **Block**. A [multiline block quote](https://github.github.com/gfm/#block-quotes).  Spans multiple
175    /// lines and contains other **blocks**.
176    ///
177    /// ``` md
178    /// >>>
179    /// A paragraph.
180    ///
181    /// - item one
182    /// - item two
183    /// >>>
184    /// ```
185    MultilineBlockQuote(NodeMultilineBlockQuote),
186
187    /// **Inline**.  A character that has been [escaped](https://github.github.com/gfm/#backslash-escapes)
188    ///
189    /// Enabled with [`escaped_char_spans`](crate::RenderOptionsBuilder::escaped_char_spans).
190    Escaped,
191
192    /// **Inline**.  A wikilink to some URL.
193    WikiLink(NodeWikiLink),
194
195    /// **Inline**.  Underline. Enabled with `underline` option.
196    Underline,
197
198    /// **Inline**.  Subscript. Enabled with `subscript` options.
199    Subscript,
200
201    /// **Inline**.  Spoilered text.  Enabled with `spoiler` option.
202    SpoileredText,
203
204    /// **Inline**. Text surrounded by escaped markup. Enabled with `spoiler` option.
205    /// The `String` is the tag to be escaped.
206    EscapedTag(String),
207}
208
209/// Alignment of a single table cell.
210#[derive(Debug, Copy, Clone, PartialEq, Eq)]
211pub enum TableAlignment {
212    /// Cell content is unaligned.
213    None,
214
215    /// Cell content is aligned left.
216    Left,
217
218    /// Cell content is centered.
219    Center,
220
221    /// Cell content is aligned right.
222    Right,
223}
224
225impl TableAlignment {
226    pub(crate) fn xml_name(&self) -> Option<&'static str> {
227        match *self {
228            TableAlignment::None => None,
229            TableAlignment::Left => Some("left"),
230            TableAlignment::Center => Some("center"),
231            TableAlignment::Right => Some("right"),
232        }
233    }
234}
235
236/// The metadata of a table
237#[derive(Debug, Default, Clone, PartialEq, Eq)]
238pub struct NodeTable {
239    /// The table alignments
240    pub alignments: Vec<TableAlignment>,
241
242    /// Number of columns of the table
243    pub num_columns: usize,
244
245    /// Number of rows of the table
246    pub num_rows: usize,
247
248    /// Number of non-empty, non-autocompleted cells
249    pub num_nonempty_cells: usize,
250}
251
252/// An inline [code span](https://github.github.com/gfm/#code-spans).
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct NodeCode {
255    /// The number of backticks
256    pub num_backticks: usize,
257
258    /// The content of the inline code span.
259    /// As the contents are not interpreted as Markdown at all,
260    /// they are contained within this structure,
261    /// rather than inserted into a child inline of any kind.
262    pub literal: String,
263}
264
265/// The details of a link's destination, or an image's source.
266#[derive(Debug, Clone, PartialEq, Eq)]
267pub struct NodeLink {
268    /// The URL for the link destination or image source.
269    pub url: String,
270
271    /// The title for the link or image.
272    ///
273    /// Note this field is used for the `title` attribute by the HTML formatter even for images;
274    /// `alt` text is supplied in the image inline text.
275    pub title: String,
276}
277
278/// The details of a wikilink's destination.
279#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct NodeWikiLink {
281    /// The URL for the link destination.
282    pub url: String,
283}
284
285/// The metadata of a list; the kind of list, the delimiter used and so on.
286#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
287pub struct NodeList {
288    /// The kind of list (bullet (unordered) or ordered).
289    pub list_type: ListType,
290
291    /// Number of spaces before the list marker.
292    pub marker_offset: usize,
293
294    /// Number of characters between the start of the list marker and the item text (including the list marker(s)).
295    pub padding: usize,
296
297    /// For ordered lists, the ordinal the list starts at.
298    pub start: usize,
299
300    /// For ordered lists, the delimiter after each number.
301    pub delimiter: ListDelimType,
302
303    /// For bullet lists, the character used for each bullet.
304    pub bullet_char: u8,
305
306    /// Whether the list is [tight](https://github.github.com/gfm/#tight), i.e. whether the
307    /// paragraphs are wrapped in `<p>` tags when formatted as HTML.
308    pub tight: bool,
309
310    /// Whether the list contains tasks (checkbox items)
311    pub is_task_list: bool,
312}
313
314/// The metadata of a description list
315#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
316pub struct NodeDescriptionItem {
317    /// Number of spaces before the list marker.
318    pub marker_offset: usize,
319
320    /// Number of characters between the start of the list marker and the item text (including the list marker(s)).
321    pub padding: usize,
322
323    /// Whether the list is [tight](https://github.github.com/gfm/#tight), i.e. whether the
324    /// paragraphs are wrapped in `<p>` tags when formatted as HTML.
325    pub tight: bool,
326}
327
328/// The type of list.
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
330pub enum ListType {
331    /// A bullet list, i.e. an unordered list.
332    #[default]
333    Bullet,
334
335    /// An ordered list.
336    Ordered,
337}
338
339/// The delimiter for ordered lists, i.e. the character which appears after each number.
340#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
341pub enum ListDelimType {
342    /// A period character `.`.
343    #[default]
344    Period,
345
346    /// A paren character `)`.
347    Paren,
348}
349
350impl ListDelimType {
351    pub(crate) fn xml_name(&self) -> &'static str {
352        match *self {
353            ListDelimType::Period => "period",
354            ListDelimType::Paren => "paren",
355        }
356    }
357}
358
359/// The metadata and data of a code block (fenced or indented).
360#[derive(Default, Debug, Clone, PartialEq, Eq)]
361pub struct NodeCodeBlock {
362    /// Whether the code block is fenced.
363    pub fenced: bool,
364
365    /// For fenced code blocks, the fence character itself (`` ` `` or `~`).
366    pub fence_char: u8,
367
368    /// For fenced code blocks, the length of the fence.
369    pub fence_length: usize,
370
371    /// For fenced code blocks, the indentation level of the code within the block.
372    pub fence_offset: usize,
373
374    /// For fenced code blocks, the [info string](https://github.github.com/gfm/#info-string) after
375    /// the opening fence, if any.
376    pub info: String,
377
378    /// The literal contents of the code block.  As the contents are not interpreted as Markdown at
379    /// all, they are contained within this structure, rather than inserted into a child inline of
380    /// any kind.
381    pub literal: String,
382}
383
384/// The metadata of a heading.
385#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
386pub struct NodeHeading {
387    /// The level of the header; from 1 to 6 for ATX headings, 1 or 2 for setext headings.
388    pub level: u8,
389
390    /// Whether the heading is setext (if not, ATX).
391    pub setext: bool,
392}
393
394/// The metadata of an included HTML block.
395#[derive(Debug, Default, Clone, PartialEq, Eq)]
396pub struct NodeHtmlBlock {
397    /// The HTML block's type
398    pub block_type: u8,
399
400    /// The literal contents of the HTML block.  Per NodeCodeBlock, the content is included here
401    /// rather than in any inline.
402    pub literal: String,
403}
404
405/// The metadata of a footnote definition.
406#[derive(Debug, Default, Clone, PartialEq, Eq)]
407pub struct NodeFootnoteDefinition {
408    /// The name of the footnote.
409    pub name: String,
410
411    /// Total number of references to this footnote
412    pub total_references: u32,
413}
414
415/// The metadata of a footnote reference.
416#[derive(Debug, Default, Clone, PartialEq, Eq)]
417pub struct NodeFootnoteReference {
418    /// The name of the footnote.
419    pub name: String,
420
421    /// The index of reference to the same footnote
422    pub ref_num: u32,
423
424    /// The index of the footnote in the document.
425    pub ix: u32,
426}
427
428impl NodeValue {
429    /// Indicates whether this node is a block node or inline node.
430    pub fn block(&self) -> bool {
431        matches!(
432            *self,
433            NodeValue::Document
434                | NodeValue::BlockQuote
435                | NodeValue::FootnoteDefinition(_)
436                | NodeValue::List(..)
437                | NodeValue::DescriptionList
438                | NodeValue::DescriptionItem(_)
439                | NodeValue::DescriptionTerm
440                | NodeValue::DescriptionDetails
441                | NodeValue::Item(..)
442                | NodeValue::CodeBlock(..)
443                | NodeValue::HtmlBlock(..)
444                | NodeValue::Paragraph
445                | NodeValue::Heading(..)
446                | NodeValue::ThematicBreak
447                | NodeValue::Table(..)
448                | NodeValue::TableRow(..)
449                | NodeValue::TableCell
450                | NodeValue::TaskItem(..)
451                | NodeValue::MultilineBlockQuote(_)
452        )
453    }
454
455    /// Whether the type the node is of can contain inline nodes.
456    pub fn contains_inlines(&self) -> bool {
457        matches!(
458            *self,
459            NodeValue::Paragraph | NodeValue::Heading(..) | NodeValue::TableCell
460        )
461    }
462
463    /// Return a reference to the text of a `Text` inline, if this node is one.
464    ///
465    /// Convenience method.
466    pub fn text(&self) -> Option<&String> {
467        match *self {
468            NodeValue::Text(ref t) => Some(t),
469            _ => None,
470        }
471    }
472
473    /// Return a mutable reference to the text of a `Text` inline, if this node is one.
474    ///
475    /// Convenience method.
476    pub fn text_mut(&mut self) -> Option<&mut String> {
477        match *self {
478            NodeValue::Text(ref mut t) => Some(t),
479            _ => None,
480        }
481    }
482
483    pub(crate) fn accepts_lines(&self) -> bool {
484        matches!(
485            *self,
486            NodeValue::Paragraph | NodeValue::Heading(..) | NodeValue::CodeBlock(..)
487        )
488    }
489
490    pub(crate) fn xml_node_name(&self) -> &'static str {
491        match *self {
492            NodeValue::Document => "document",
493            NodeValue::BlockQuote => "block_quote",
494            NodeValue::FootnoteDefinition(_) => "footnote_definition",
495            NodeValue::List(..) => "list",
496            NodeValue::DescriptionList => "description_list",
497            NodeValue::DescriptionItem(_) => "description_item",
498            NodeValue::DescriptionTerm => "description_term",
499            NodeValue::DescriptionDetails => "description_details",
500            NodeValue::Item(..) => "item",
501            NodeValue::CodeBlock(..) => "code_block",
502            NodeValue::HtmlBlock(..) => "html_block",
503            NodeValue::Paragraph => "paragraph",
504            NodeValue::Heading(..) => "heading",
505            NodeValue::ThematicBreak => "thematic_break",
506            NodeValue::Table(..) => "table",
507            NodeValue::TableRow(..) => "table_row",
508            NodeValue::TableCell => "table_cell",
509            NodeValue::Text(..) => "text",
510            NodeValue::SoftBreak => "softbreak",
511            NodeValue::LineBreak => "linebreak",
512            NodeValue::Image(..) => "image",
513            NodeValue::Link(..) => "link",
514            NodeValue::Emph => "emph",
515            NodeValue::Strong => "strong",
516            NodeValue::Code(..) => "code",
517            NodeValue::HtmlInline(..) => "html_inline",
518            NodeValue::Raw(..) => "raw",
519            NodeValue::Strikethrough => "strikethrough",
520            NodeValue::FrontMatter(_) => "frontmatter",
521            NodeValue::TaskItem { .. } => "taskitem",
522            NodeValue::Superscript => "superscript",
523            NodeValue::FootnoteReference(..) => "footnote_reference",
524            #[cfg(feature = "shortcodes")]
525            NodeValue::ShortCode(_) => "shortcode",
526            NodeValue::MultilineBlockQuote(_) => "multiline_block_quote",
527            NodeValue::Escaped => "escaped",
528            NodeValue::Math(..) => "math",
529            NodeValue::WikiLink(..) => "wikilink",
530            NodeValue::Underline => "underline",
531            NodeValue::Subscript => "subscript",
532            NodeValue::SpoileredText => "spoiler",
533            NodeValue::EscapedTag(_) => "escaped_tag",
534        }
535    }
536}
537
538/// A single node in the CommonMark AST.
539///
540/// The struct contains metadata about the node's position in the original document, and the core
541/// enum, `NodeValue`.
542#[derive(Debug, Clone, PartialEq, Eq)]
543pub struct Ast {
544    /// The node value itself.
545    pub value: NodeValue,
546
547    /// The positions in the source document this node comes from.
548    pub sourcepos: Sourcepos,
549    pub(crate) internal_offset: usize,
550
551    pub(crate) content: String,
552    pub(crate) open: bool,
553    pub(crate) last_line_blank: bool,
554    pub(crate) table_visited: bool,
555    pub(crate) line_offsets: Vec<usize>,
556}
557
558/// Represents the position in the source Markdown this node was rendered from.
559#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
560pub struct Sourcepos {
561    /// The line and column of the first character of this node.
562    pub start: LineColumn,
563    /// The line and column of the last character of this node.
564    pub end: LineColumn,
565}
566
567impl std::fmt::Display for Sourcepos {
568    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
569        write!(
570            f,
571            "{}:{}-{}:{}",
572            self.start.line, self.start.column, self.end.line, self.end.column,
573        )
574    }
575}
576
577impl From<(usize, usize, usize, usize)> for Sourcepos {
578    fn from(sp: (usize, usize, usize, usize)) -> Sourcepos {
579        Sourcepos {
580            start: LineColumn {
581                line: sp.0,
582                column: sp.1,
583            },
584            end: LineColumn {
585                line: sp.2,
586                column: sp.3,
587            },
588        }
589    }
590}
591
592/// Represents the 1-based line and column positions of a given character.
593#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
594pub struct LineColumn {
595    /// The 1-based line number of the character.
596    pub line: usize,
597    /// The 1-based column number of the character.
598    pub column: usize,
599}
600
601impl From<(usize, usize)> for LineColumn {
602    fn from(lc: (usize, usize)) -> LineColumn {
603        LineColumn {
604            line: lc.0,
605            column: lc.1,
606        }
607    }
608}
609
610impl LineColumn {
611    /// Return a new LineColumn based on this one, with the column adjusted by offset.
612    pub fn column_add(&self, offset: isize) -> LineColumn {
613        LineColumn {
614            line: self.line,
615            column: usize::try_from((self.column as isize) + offset).unwrap(),
616        }
617    }
618}
619
620impl Ast {
621    /// Create a new AST node with the given value.
622    pub fn new(value: NodeValue, start: LineColumn) -> Self {
623        Ast {
624            value,
625            content: String::new(),
626            sourcepos: (start.line, start.column, start.line, 0).into(),
627            internal_offset: 0,
628            open: true,
629            last_line_blank: false,
630            table_visited: false,
631            line_offsets: Vec::with_capacity(0),
632        }
633    }
634}
635
636/// The type of a node within the document.
637///
638/// It is bound by the lifetime `'a`, which corresponds to the `Arena` nodes are
639/// allocated in. Child `Ast`s are wrapped in `RefCell` for interior mutability.
640///
641/// You can construct a new `AstNode` from a `NodeValue` using the `From` trait:
642///
643/// ```no_run
644/// # use comrak::nodes::{AstNode, NodeValue};
645/// let root = AstNode::from(NodeValue::Document);
646/// ```
647///
648/// Note that no sourcepos information is given to the created node. If you wish
649/// to assign sourcepos information, use the `From` trait to create an `AstNode`
650/// from an `Ast`:
651///
652/// ```no_run
653/// # use comrak::nodes::{Ast, AstNode, NodeValue};
654/// let root = AstNode::from(Ast::new(
655///     NodeValue::Paragraph,
656///     (4, 1).into(), // start_line, start_col
657/// ));
658/// ```
659///
660/// Adjust the `end` position manually.
661///
662/// For practical use, you'll probably need it allocated in an `Arena`, in which
663/// case you can use `.into()` to simplify creation:
664///
665/// ```no_run
666/// # use comrak::{nodes::{AstNode, NodeValue}, Arena};
667/// # let arena = Arena::<AstNode>::new();
668/// let node_in_arena = arena.alloc(NodeValue::Document.into());
669/// ```
670pub type AstNode<'a> = Node<'a, RefCell<Ast>>;
671
672impl<'a> From<NodeValue> for AstNode<'a> {
673    /// Create a new AST node with the given value. The sourcepos is set to (0,0)-(0,0).
674    fn from(value: NodeValue) -> Self {
675        Node::new(RefCell::new(Ast::new(value, LineColumn::default())))
676    }
677}
678
679impl<'a> From<Ast> for AstNode<'a> {
680    /// Create a new AST node with the given Ast.
681    fn from(ast: Ast) -> Self {
682        Node::new(RefCell::new(ast))
683    }
684}
685
686/// Validation errors produced by [Node::validate].
687#[derive(Debug, Clone)]
688pub enum ValidationError<'a> {
689    /// The type of a child node is not allowed in the parent node. This can happen when an inline
690    /// node is found in a block container, a block is found in an inline node, etc.
691    InvalidChildType {
692        /// The parent node.
693        parent: &'a AstNode<'a>,
694        /// The child node.
695        child: &'a AstNode<'a>,
696    },
697}
698
699impl<'a> Node<'a, RefCell<Ast>> {
700    /// The comrak representation of a markdown node in Rust isn't strict enough to rule out
701    /// invalid trees according to the CommonMark specification. One simple example is that block
702    /// containers, such as lists, should only contain blocks, but it's possible to put naked
703    /// inline text in a list item. Such invalid trees can lead comrak to generate incorrect output
704    /// if rendered.
705    ///
706    /// This method performs additional structural checks to ensure that a markdown AST is valid
707    /// according to the CommonMark specification.
708    ///
709    /// Note that those invalid trees can only be generated programmatically. Parsing markdown with
710    /// comrak, on the other hand, should always produce a valid tree.
711    pub fn validate(&'a self) -> Result<(), ValidationError<'a>> {
712        let mut stack = vec![self];
713
714        while let Some(node) = stack.pop() {
715            // Check that this node type is valid wrt to the type of its parent.
716            if let Some(parent) = node.parent() {
717                if !can_contain_type(parent, &node.data.borrow().value) {
718                    return Err(ValidationError::InvalidChildType {
719                        parent,
720                        child: node,
721                    });
722                }
723            }
724
725            stack.extend(node.children());
726        }
727
728        Ok(())
729    }
730}
731
732pub(crate) fn last_child_is_open<'a>(node: &'a AstNode<'a>) -> bool {
733    node.last_child().map_or(false, |n| n.data.borrow().open)
734}
735
736/// Returns true if the given node can contain a node with the given value.
737pub fn can_contain_type<'a>(node: &'a AstNode<'a>, child: &NodeValue) -> bool {
738    match *child {
739        NodeValue::Document => {
740            return false;
741        }
742        NodeValue::FrontMatter(_) => {
743            return matches!(node.data.borrow().value, NodeValue::Document);
744        }
745        _ => {}
746    }
747
748    match node.data.borrow().value {
749        NodeValue::Document
750        | NodeValue::BlockQuote
751        | NodeValue::FootnoteDefinition(_)
752        | NodeValue::DescriptionTerm
753        | NodeValue::DescriptionDetails
754        | NodeValue::Item(..)
755        | NodeValue::TaskItem(..) => {
756            child.block() && !matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..))
757        }
758
759        NodeValue::List(..) => matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..)),
760
761        NodeValue::DescriptionList => matches!(*child, NodeValue::DescriptionItem(_)),
762
763        NodeValue::DescriptionItem(_) => matches!(
764            *child,
765            NodeValue::DescriptionTerm | NodeValue::DescriptionDetails
766        ),
767
768        #[cfg(feature = "shortcodes")]
769        NodeValue::ShortCode(..) => !child.block(),
770
771        NodeValue::Paragraph
772        | NodeValue::Heading(..)
773        | NodeValue::Emph
774        | NodeValue::Strong
775        | NodeValue::Link(..)
776        | NodeValue::Image(..)
777        | NodeValue::WikiLink(..)
778        | NodeValue::Strikethrough
779        | NodeValue::Superscript
780        | NodeValue::SpoileredText
781        | NodeValue::Underline
782        | NodeValue::Subscript
783        // XXX: this is quite a hack: the EscapedTag _contains_ whatever was
784        // possibly going to fall into the spoiler. This should be fixed in
785        // inlines.
786        | NodeValue::EscapedTag(_)
787        => !child.block(),
788
789        NodeValue::Table(..) => matches!(*child, NodeValue::TableRow(..)),
790
791        NodeValue::TableRow(..) => matches!(*child, NodeValue::TableCell),
792
793        #[cfg(not(feature = "shortcodes"))]
794        NodeValue::TableCell => matches!(
795            *child,
796            NodeValue::Text(..)
797                | NodeValue::Code(..)
798                | NodeValue::Emph
799                | NodeValue::Strong
800                | NodeValue::Link(..)
801                | NodeValue::Image(..)
802                | NodeValue::Strikethrough
803                | NodeValue::HtmlInline(..)
804                | NodeValue::Math(..)
805                | NodeValue::WikiLink(..)
806                | NodeValue::FootnoteReference(..)
807                | NodeValue::Superscript
808                | NodeValue::SpoileredText
809                | NodeValue::Underline
810                | NodeValue::Subscript
811        ),
812
813        #[cfg(feature = "shortcodes")]
814        NodeValue::TableCell => matches!(
815            *child,
816            NodeValue::Text(..)
817            | NodeValue::Code(..)
818            | NodeValue::Emph
819            | NodeValue::Strong
820            | NodeValue::Link(..)
821            | NodeValue::Image(..)
822            | NodeValue::Strikethrough
823            | NodeValue::HtmlInline(..)
824            | NodeValue::Math(..)
825            | NodeValue::WikiLink(..)
826            | NodeValue::FootnoteReference(..)
827            | NodeValue::Superscript
828            | NodeValue::SpoileredText
829            | NodeValue::Underline
830            | NodeValue::Subscript
831            | NodeValue::ShortCode(..)
832        ),
833
834        NodeValue::MultilineBlockQuote(_) => {
835            child.block() && !matches!(*child, NodeValue::Item(..) | NodeValue::TaskItem(..))
836        }
837
838        _ => false,
839    }
840}
841
842pub(crate) fn ends_with_blank_line<'a>(node: &'a AstNode<'a>) -> bool {
843    let mut it = Some(node);
844    while let Some(cur) = it {
845        if cur.data.borrow().last_line_blank {
846            return true;
847        }
848        match cur.data.borrow().value {
849            NodeValue::List(..) | NodeValue::Item(..) | NodeValue::TaskItem(..) => {
850                it = cur.last_child()
851            }
852            _ => it = None,
853        };
854    }
855    false
856}
857
858pub(crate) fn containing_block<'a>(node: &'a AstNode<'a>) -> Option<&'a AstNode<'a>> {
859    let mut ch = Some(node);
860    while let Some(n) = ch {
861        if n.data.borrow().value.block() {
862            return Some(n);
863        }
864        ch = n.parent();
865    }
866    None
867}