Skip to main content

comrak/parser/
mod.rs

1mod autolink;
2mod inlines;
3#[cfg(feature = "shortcodes")]
4pub mod shortcodes;
5mod table;
6
7pub mod math;
8pub mod multiline_block_quote;
9
10use crate::adapters::SyntaxHighlighterAdapter;
11use crate::arena_tree::Node;
12use crate::ctype::{isdigit, isspace};
13use crate::entity;
14use crate::nodes::{self, NodeFootnoteDefinition, Sourcepos};
15use crate::nodes::{
16    Ast, AstNode, ListDelimType, ListType, NodeCodeBlock, NodeDescriptionItem, NodeHeading,
17    NodeHtmlBlock, NodeList, NodeValue,
18};
19use crate::scanners::{self, SetextChar};
20use crate::strings::{self, split_off_front_matter, Case};
21use bon::Builder;
22use std::cell::RefCell;
23use std::cmp::min;
24use std::collections::HashMap;
25use std::fmt::{self, Debug, Formatter};
26use std::mem;
27use std::panic::RefUnwindSafe;
28use std::str;
29use std::sync::Arc;
30use typed_arena::Arena;
31
32use crate::adapters::HeadingAdapter;
33use crate::parser::multiline_block_quote::NodeMultilineBlockQuote;
34
35use self::inlines::RefMap;
36
37const TAB_STOP: usize = 4;
38const CODE_INDENT: usize = 4;
39
40// Very deeply nested lists can cause quadratic performance issues.
41// This constant is used in open_new_blocks() to limit the nesting
42// depth. It is unlikely that a non-contrived markdown document will
43// be nested this deeply.
44const MAX_LIST_DEPTH: usize = 100;
45
46macro_rules! node_matches {
47    ($node:expr, $( $pat:pat )|+) => {{
48        matches!(
49            $node.data.borrow().value,
50            $( $pat )|+
51        )
52    }};
53}
54
55/// Parse a Markdown document to an AST.
56///
57/// See the documentation of the crate root for an example.
58pub fn parse_document<'a>(
59    arena: &'a Arena<AstNode<'a>>,
60    buffer: &str,
61    options: &Options,
62) -> &'a AstNode<'a> {
63    let root: &'a AstNode<'a> = arena.alloc(Node::new(RefCell::new(Ast {
64        value: NodeValue::Document,
65        content: String::new(),
66        sourcepos: (1, 1, 1, 1).into(),
67        internal_offset: 0,
68        open: true,
69        last_line_blank: false,
70        table_visited: false,
71        line_offsets: Vec::with_capacity(0),
72    })));
73    let mut parser = Parser::new(arena, root, options);
74    let mut linebuf = Vec::with_capacity(buffer.len());
75    parser.feed(&mut linebuf, buffer, true);
76    parser.finish(linebuf)
77}
78
79/// Parse a Markdown document to an AST, specifying
80/// [`ParseOptions::broken_link_callback`].
81#[deprecated(
82    since = "0.25.0",
83    note = "The broken link callback has been moved into ParseOptions."
84)]
85pub fn parse_document_with_broken_link_callback<'a, 'c>(
86    arena: &'a Arena<AstNode<'a>>,
87    buffer: &str,
88    options: &Options,
89    callback: Arc<dyn BrokenLinkCallback + 'c>,
90) -> &'a AstNode<'a> {
91    let mut options_with_callback = options.clone();
92    options_with_callback.parse.broken_link_callback = Some(callback);
93    parse_document(arena, buffer, &options_with_callback)
94}
95
96/// The type of the callback used when a reference link is encountered with no
97/// matching reference.
98///
99/// The details of the broken reference are passed in the
100/// [`BrokenLinkReference`] argument. If a [`ResolvedReference`] is returned, it
101/// is used as the link; otherwise, no link is made and the reference text is
102/// preserved in its entirety.
103pub trait BrokenLinkCallback: RefUnwindSafe + Send + Sync {
104    /// Potentially resolve a single broken link reference.
105    fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option<ResolvedReference>;
106}
107
108impl<'c> Debug for dyn BrokenLinkCallback + 'c {
109    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), fmt::Error> {
110        formatter.write_str("<dyn BrokenLinkCallback>")
111    }
112}
113
114impl<F> BrokenLinkCallback for F
115where
116    F: Fn(BrokenLinkReference) -> Option<ResolvedReference>,
117    F: RefUnwindSafe + Send + Sync,
118{
119    fn resolve(&self, broken_link_reference: BrokenLinkReference) -> Option<ResolvedReference> {
120        self(broken_link_reference)
121    }
122}
123
124/// Struct to the broken link callback, containing details on the link reference
125/// which failed to find a match.
126#[derive(Debug)]
127pub struct BrokenLinkReference<'l> {
128    /// The normalized reference link label. Unicode case folding is applied;
129    /// see <https://github.com/commonmark/commonmark-spec/issues/695> for a
130    /// discussion on the details of what this exactly means.
131    pub normalized: &'l str,
132
133    /// The original text in the link label.
134    pub original: &'l str,
135}
136
137pub struct Parser<'a, 'o, 'c> {
138    arena: &'a Arena<AstNode<'a>>,
139    refmap: RefMap,
140    root: &'a AstNode<'a>,
141    current: &'a AstNode<'a>,
142    line_number: usize,
143    offset: usize,
144    column: usize,
145    thematic_break_kill_pos: usize,
146    first_nonspace: usize,
147    first_nonspace_column: usize,
148    indent: usize,
149    blank: bool,
150    partially_consumed_tab: bool,
151    curline_len: usize,
152    curline_end_col: usize,
153    last_line_length: usize,
154    last_buffer_ended_with_cr: bool,
155    total_size: usize,
156    options: &'o Options<'c>,
157}
158
159#[derive(Default, Debug, Clone)]
160#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
161/// Umbrella options struct.
162pub struct Options<'c> {
163    /// Enable CommonMark extensions.
164    pub extension: ExtensionOptions<'c>,
165
166    /// Configure parse-time options.
167    pub parse: ParseOptions<'c>,
168
169    /// Configure render-time options.
170    pub render: RenderOptions,
171}
172
173/// Trait for link and image URL rewrite extensions.
174pub trait URLRewriter: RefUnwindSafe + Send + Sync {
175    /// Converts the given URL from Markdown to its representation when output as HTML.
176    fn to_html(&self, url: &str) -> String;
177}
178
179impl<'c> Debug for dyn URLRewriter + 'c {
180    fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
181        formatter.write_str("<dyn URLRewriter>")
182    }
183}
184
185impl<F> URLRewriter for F
186where
187    F: for<'a> Fn(&'a str) -> String,
188    F: RefUnwindSafe + Send + Sync,
189{
190    fn to_html(&self, url: &str) -> String {
191        self(url)
192    }
193}
194
195#[non_exhaustive]
196#[derive(Debug, Clone, PartialEq, Eq, Copy)]
197#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
198/// Selects between wikilinks with the title first or the URL first.
199pub(crate) enum WikiLinksMode {
200    /// Indicates that the URL precedes the title. For example: `[[http://example.com|link
201    /// title]]`.
202    UrlFirst,
203
204    /// Indicates that the title precedes the URL. For example: `[[link title|http://example.com]]`.
205    TitleFirst,
206}
207
208#[non_exhaustive]
209#[derive(Default, Debug, Clone, Builder)]
210#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
211/// Options to select extensions.
212pub struct ExtensionOptions<'c> {
213    /// Enables the
214    /// [strikethrough extension](https://github.github.com/gfm/#strikethrough-extension-)
215    /// from the GFM spec.
216    ///
217    /// ```
218    /// # use comrak::{markdown_to_html, Options};
219    /// let mut options = Options::default();
220    /// options.extension.strikethrough = true;
221    /// assert_eq!(markdown_to_html("Hello ~world~ there.\n", &options),
222    ///            "<p>Hello <del>world</del> there.</p>\n");
223    /// ```
224    #[builder(default)]
225    pub strikethrough: bool,
226
227    /// Enables the
228    /// [tagfilter extension](https://github.github.com/gfm/#disallowed-raw-html-extension-)
229    /// from the GFM spec.
230    ///
231    /// ```
232    /// # use comrak::{markdown_to_html, Options};
233    /// let mut options = Options::default();
234    /// options.extension.tagfilter = true;
235    /// options.render.unsafe_ = true;
236    /// assert_eq!(markdown_to_html("Hello <xmp>.\n\n<xmp>", &options),
237    ///            "<p>Hello &lt;xmp>.</p>\n&lt;xmp>\n");
238    /// ```
239    #[builder(default)]
240    pub tagfilter: bool,
241
242    /// Enables the [table extension](https://github.github.com/gfm/#tables-extension-)
243    /// from the GFM spec.
244    ///
245    /// ```
246    /// # use comrak::{markdown_to_html, Options};
247    /// let mut options = Options::default();
248    /// options.extension.table = true;
249    /// assert_eq!(markdown_to_html("| a | b |\n|---|---|\n| c | d |\n", &options),
250    ///            "<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b</th>\n</tr>\n</thead>\n\
251    ///             <tbody>\n<tr>\n<td>c</td>\n<td>d</td>\n</tr>\n</tbody>\n</table>\n");
252    /// ```
253    #[builder(default)]
254    pub table: bool,
255
256    /// Enables the [autolink extension](https://github.github.com/gfm/#autolinks-extension-)
257    /// from the GFM spec.
258    ///
259    /// ```
260    /// # use comrak::{markdown_to_html, Options};
261    /// let mut options = Options::default();
262    /// options.extension.autolink = true;
263    /// assert_eq!(markdown_to_html("Hello www.github.com.\n", &options),
264    ///            "<p>Hello <a href=\"http://www.github.com\">www.github.com</a>.</p>\n");
265    /// ```
266    #[builder(default)]
267    pub autolink: bool,
268
269    /// Enables the
270    /// [task list items extension](https://github.github.com/gfm/#task-list-items-extension-)
271    /// from the GFM spec.
272    ///
273    /// Note that the spec does not define the precise output, so only the bare essentials are
274    /// rendered.
275    ///
276    /// ```
277    /// # use comrak::{markdown_to_html, Options};
278    /// let mut options = Options::default();
279    /// options.extension.tasklist = true;
280    /// options.render.unsafe_ = true;
281    /// assert_eq!(markdown_to_html("* [x] Done\n* [ ] Not done\n", &options),
282    ///            "<ul>\n<li><input type=\"checkbox\" checked=\"\" disabled=\"\" /> Done</li>\n\
283    ///            <li><input type=\"checkbox\" disabled=\"\" /> Not done</li>\n</ul>\n");
284    /// ```
285    #[builder(default)]
286    pub tasklist: bool,
287
288    /// Enables the superscript Comrak extension.
289    ///
290    /// ```
291    /// # use comrak::{markdown_to_html, Options};
292    /// let mut options = Options::default();
293    /// options.extension.superscript = true;
294    /// assert_eq!(markdown_to_html("e = mc^2^.\n", &options),
295    ///            "<p>e = mc<sup>2</sup>.</p>\n");
296    /// ```
297    #[builder(default)]
298    pub superscript: bool,
299
300    /// Enables the header IDs Comrak extension.
301    ///
302    /// ```
303    /// # use comrak::{markdown_to_html, Options};
304    /// let mut options = Options::default();
305    /// options.extension.header_ids = Some("user-content-".to_string());
306    /// assert_eq!(markdown_to_html("# README\n", &options),
307    ///            "<h1><a href=\"#readme\" aria-hidden=\"true\" class=\"anchor\" id=\"user-content-readme\"></a>README</h1>\n");
308    /// ```
309    pub header_ids: Option<String>,
310
311    /// Enables the footnotes extension per `cmark-gfm`.
312    ///
313    /// For usage, see `src/tests.rs`.  The extension is modelled after
314    /// [Kramdown](https://kramdown.gettalong.org/syntax.html#footnotes).
315    ///
316    /// ```
317    /// # use comrak::{markdown_to_html, Options};
318    /// let mut options = Options::default();
319    /// options.extension.footnotes = true;
320    /// assert_eq!(markdown_to_html("Hi[^x].\n\n[^x]: A greeting.\n", &options),
321    ///            "<p>Hi<sup class=\"footnote-ref\"><a href=\"#fn-x\" id=\"fnref-x\" data-footnote-ref>1</a></sup>.</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-x\">\n<p>A greeting. <a href=\"#fnref-x\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\"1\" aria-label=\"Back to reference 1\">↩</a></p>\n</li>\n</ol>\n</section>\n");
322    /// ```
323    #[builder(default)]
324    pub footnotes: bool,
325
326    /// Enables the description lists extension.
327    ///
328    /// Each term must be defined in one paragraph, followed by a blank line,
329    /// and then by the details.  Details begins with a colon.
330    ///
331    /// Not (yet) compatible with render.sourcepos.
332    ///
333    /// ``` md
334    /// First term
335    ///
336    /// : Details for the **first term**
337    ///
338    /// Second term
339    ///
340    /// : Details for the **second term**
341    ///
342    ///     More details in second paragraph.
343    /// ```
344    ///
345    /// ```
346    /// # use comrak::{markdown_to_html, Options};
347    /// let mut options = Options::default();
348    /// options.extension.description_lists = true;
349    /// assert_eq!(markdown_to_html("Term\n\n: Definition", &options),
350    ///            "<dl>\n<dt>Term</dt>\n<dd>\n<p>Definition</p>\n</dd>\n</dl>\n");
351    /// ```
352    #[builder(default)]
353    pub description_lists: bool,
354
355    /// Enables the front matter extension.
356    ///
357    /// Front matter, which begins with the delimiter string at the beginning of the file and ends
358    /// at the end of the next line that contains only the delimiter, is passed through unchanged
359    /// in markdown output and omitted from HTML output.
360    ///
361    /// ``` md
362    /// ---
363    /// layout: post
364    /// title: Formatting Markdown with Comrak
365    /// ---
366    ///
367    /// # Shorter Title
368    ///
369    /// etc.
370    /// ```
371    ///
372    /// ```
373    /// # use comrak::{markdown_to_html, Options};
374    /// let mut options = Options::default();
375    /// options.extension.front_matter_delimiter = Some("---".to_owned());
376    /// assert_eq!(
377    ///     markdown_to_html("---\nlayout: post\n---\nText\n", &options),
378    ///     markdown_to_html("Text\n", &Options::default()));
379    /// ```
380    ///
381    /// ```
382    /// # use comrak::{format_commonmark, Arena, Options};
383    /// use comrak::parse_document;
384    /// let mut options = Options::default();
385    /// options.extension.front_matter_delimiter = Some("---".to_owned());
386    /// let arena = Arena::new();
387    /// let input ="---\nlayout: post\n---\nText\n";
388    /// let root = parse_document(&arena, input, &options);
389    /// let mut buf = Vec::new();
390    /// format_commonmark(&root, &options, &mut buf);
391    /// assert_eq!(&String::from_utf8(buf).unwrap(), input);
392    /// ```
393    pub front_matter_delimiter: Option<String>,
394
395    /// Enables the multiline block quote extension.
396    ///
397    /// Place `>>>` before and after text to make it into
398    /// a block quote.
399    ///
400    /// ``` md
401    /// Paragraph one
402    ///
403    /// >>>
404    /// Paragraph two
405    ///
406    /// - one
407    /// - two
408    /// >>>
409    /// ```
410    ///
411    /// ```
412    /// # use comrak::{markdown_to_html, Options};
413    /// let mut options = Options::default();
414    /// options.extension.multiline_block_quotes = true;
415    /// assert_eq!(markdown_to_html(">>>\nparagraph\n>>>", &options),
416    ///            "<blockquote>\n<p>paragraph</p>\n</blockquote>\n");
417    /// ```
418    #[builder(default)]
419    pub multiline_block_quotes: bool,
420
421    /// Enables math using dollar syntax.
422    ///
423    /// ``` md
424    /// Inline math $1 + 2$ and display math $$x + y$$
425    ///
426    /// $$
427    /// x^2
428    /// $$
429    /// ```
430    ///
431    /// ```
432    /// # use comrak::{markdown_to_html, Options};
433    /// let mut options = Options::default();
434    /// options.extension.math_dollars = true;
435    /// assert_eq!(markdown_to_html("$1 + 2$ and $$x = y$$", &options),
436    ///            "<p><span data-math-style=\"inline\">1 + 2</span> and <span data-math-style=\"display\">x = y</span></p>\n");
437    /// assert_eq!(markdown_to_html("$$\nx^2\n$$\n", &options),
438    ///            "<p><span data-math-style=\"display\">\nx^2\n</span></p>\n");
439    /// ```
440    #[builder(default)]
441    pub math_dollars: bool,
442
443    /// Enables math using code syntax.
444    ///
445    /// ```` md
446    /// Inline math $`1 + 2`$
447    ///
448    /// ```math
449    /// x^2
450    /// ```
451    /// ````
452    ///
453    /// ```
454    /// # use comrak::{markdown_to_html, Options};
455    /// let mut options = Options::default();
456    /// options.extension.math_code = true;
457    /// assert_eq!(markdown_to_html("$`1 + 2`$", &options),
458    ///            "<p><code data-math-style=\"inline\">1 + 2</code></p>\n");
459    /// assert_eq!(markdown_to_html("```math\nx^2\n```\n", &options),
460    ///            "<pre><code class=\"language-math\" data-math-style=\"display\">x^2\n</code></pre>\n");
461    /// ```
462    #[builder(default)]
463    pub math_code: bool,
464
465    #[cfg(feature = "shortcodes")]
466    #[cfg_attr(docsrs, doc(cfg(feature = "shortcodes")))]
467    /// Phrases wrapped inside of ':' blocks will be replaced with emojis.
468    ///
469    /// ```
470    /// # use comrak::{markdown_to_html, Options};
471    /// let mut options = Options::default();
472    /// assert_eq!(markdown_to_html("Happy Friday! :smile:", &options),
473    ///            "<p>Happy Friday! :smile:</p>\n");
474    ///
475    /// options.extension.shortcodes = true;
476    /// assert_eq!(markdown_to_html("Happy Friday! :smile:", &options),
477    ///            "<p>Happy Friday! 😄</p>\n");
478    /// ```
479    #[builder(default)]
480    pub shortcodes: bool,
481
482    /// Enables wikilinks using title after pipe syntax
483    ///
484    /// ```` md
485    /// [[url|link label]]
486    /// ````
487    ///
488    /// When both this option and [`wikilinks_title_before_pipe`][0] are enabled, this option takes
489    /// precedence.
490    ///
491    /// [0]: Self::wikilinks_title_before_pipe
492    ///
493    /// ```
494    /// # use comrak::{markdown_to_html, Options};
495    /// let mut options = Options::default();
496    /// options.extension.wikilinks_title_after_pipe = true;
497    /// assert_eq!(markdown_to_html("[[url|link label]]", &options),
498    ///            "<p><a href=\"url\" data-wikilink=\"true\">link label</a></p>\n");
499    /// ```
500    #[builder(default)]
501    pub wikilinks_title_after_pipe: bool,
502
503    /// Enables wikilinks using title before pipe syntax
504    ///
505    /// ```` md
506    /// [[link label|url]]
507    /// ````
508    /// When both this option and [`wikilinks_title_after_pipe`][0] are enabled,
509    /// [`wikilinks_title_after_pipe`][0] takes precedence.
510    ///
511    /// [0]: Self::wikilinks_title_after_pipe
512    ///
513    /// ```
514    /// # use comrak::{markdown_to_html, Options};
515    /// let mut options = Options::default();
516    /// options.extension.wikilinks_title_before_pipe = true;
517    /// assert_eq!(markdown_to_html("[[link label|url]]", &options),
518    ///            "<p><a href=\"url\" data-wikilink=\"true\">link label</a></p>\n");
519    /// ```
520    #[builder(default)]
521    pub wikilinks_title_before_pipe: bool,
522
523    /// Enables underlines using double underscores
524    ///
525    /// ```md
526    /// __underlined text__
527    /// ```
528    ///
529    /// ```
530    /// # use comrak::{markdown_to_html, Options};
531    /// let mut options = Options::default();
532    /// options.extension.underline = true;
533    ///
534    /// assert_eq!(markdown_to_html("__underlined text__", &options),
535    ///            "<p><u>underlined text</u></p>\n");
536    /// ```
537    #[builder(default)]
538    pub underline: bool,
539
540    /// Enables subscript text using single tildes.
541    ///
542    /// If the strikethrough option is also enabled, this overrides the single
543    /// tilde case to output subscript text.
544    ///
545    /// ```md
546    /// H~2~O
547    /// ```
548    ///
549    /// ```
550    /// # use comrak::{markdown_to_html, Options};
551    /// let mut options = Options::default();
552    /// options.extension.subscript = true;
553    ///
554    /// assert_eq!(markdown_to_html("H~2~O", &options),
555    ///            "<p>H<sub>2</sub>O</p>\n");
556    /// ```
557    #[builder(default)]
558    pub subscript: bool,
559
560    /// Enables spoilers using double vertical bars
561    ///
562    /// ```md
563    /// Darth Vader is ||Luke's father||
564    /// ```
565    ///
566    /// ```
567    /// # use comrak::{markdown_to_html, Options};
568    /// let mut options = Options::default();
569    /// options.extension.spoiler = true;
570    ///
571    /// assert_eq!(markdown_to_html("Darth Vader is ||Luke's father||", &options),
572    ///            "<p>Darth Vader is <span class=\"spoiler\">Luke's father</span></p>\n");
573    /// ```
574    #[builder(default)]
575    pub spoiler: bool,
576
577    /// Requires at least one space after a `>` character to generate a blockquote,
578    /// and restarts blockquote nesting across unique lines of input
579    ///
580    /// ```md
581    /// >implying implications
582    ///
583    /// > one
584    /// > > two
585    /// > three
586    /// ```
587    ///
588    /// ```
589    /// # use comrak::{markdown_to_html, Options};
590    /// let mut options = Options::default();
591    /// options.extension.greentext = true;
592    ///
593    /// assert_eq!(markdown_to_html(">implying implications", &options),
594    ///            "<p>&gt;implying implications</p>\n");
595    ///
596    /// assert_eq!(markdown_to_html("> one\n> > two\n> three", &options),
597    ///            concat!(
598    ///             "<blockquote>\n",
599    ///             "<p>one</p>\n",
600    ///             "<blockquote>\n<p>two</p>\n</blockquote>\n",
601    ///             "<p>three</p>\n",
602    ///             "</blockquote>\n"));
603    /// ```
604    #[builder(default)]
605    pub greentext: bool,
606
607    /// Wraps embedded image URLs using a function or custom trait object.
608    ///
609    /// ```
610    /// # use std::sync::Arc;
611    /// # use comrak::{markdown_to_html, ComrakOptions};
612    /// let mut options = ComrakOptions::default();
613    ///
614    /// options.extension.image_url_rewriter = Some(Arc::new(
615    ///     |url: &str| format!("https://safe.example.com?url={}", url)
616    /// ));
617    ///
618    /// assert_eq!(markdown_to_html("![](http://unsafe.example.com/bad.png)", &options),
619    ///            "<p><img src=\"https://safe.example.com?url=http://unsafe.example.com/bad.png\" alt=\"\" /></p>\n");
620    /// ```
621    #[cfg_attr(feature = "arbitrary", arbitrary(value = None))]
622    pub image_url_rewriter: Option<Arc<dyn URLRewriter + 'c>>,
623
624    /// Wraps link URLs using a function or custom trait object.
625    ///
626    /// ```
627    /// # use std::sync::Arc;
628    /// # use comrak::{markdown_to_html, ComrakOptions};
629    /// let mut options = ComrakOptions::default();
630    ///
631    /// options.extension.link_url_rewriter = Some(Arc::new(
632    ///     |url: &str| format!("https://safe.example.com/norefer?url={}", url)
633    /// ));
634    ///
635    /// assert_eq!(markdown_to_html("[my link](http://unsafe.example.com/bad)", &options),
636    ///            "<p><a href=\"https://safe.example.com/norefer?url=http://unsafe.example.com/bad\">my link</a></p>\n");
637    /// ```
638    #[cfg_attr(feature = "arbitrary", arbitrary(value = None))]
639    pub link_url_rewriter: Option<Arc<dyn URLRewriter + 'c>>,
640}
641
642impl<'c> ExtensionOptions<'c> {
643    pub(crate) fn wikilinks(&self) -> Option<WikiLinksMode> {
644        match (
645            self.wikilinks_title_before_pipe,
646            self.wikilinks_title_after_pipe,
647        ) {
648            (false, false) => None,
649            (true, false) => Some(WikiLinksMode::TitleFirst),
650            (_, _) => Some(WikiLinksMode::UrlFirst),
651        }
652    }
653}
654
655#[non_exhaustive]
656#[derive(Default, Clone, Debug, Builder)]
657#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
658/// Options for parser functions.
659pub struct ParseOptions<'c> {
660    /// Punctuation (quotes, full-stops and hyphens) are converted into 'smart' punctuation.
661    ///
662    /// ```
663    /// # use comrak::{markdown_to_html, Options};
664    /// let mut options = Options::default();
665    /// assert_eq!(markdown_to_html("'Hello,' \"world\" ...", &options),
666    ///            "<p>'Hello,' &quot;world&quot; ...</p>\n");
667    ///
668    /// options.parse.smart = true;
669    /// assert_eq!(markdown_to_html("'Hello,' \"world\" ...", &options),
670    ///            "<p>‘Hello,’ “world” …</p>\n");
671    /// ```
672    #[builder(default)]
673    pub smart: bool,
674
675    /// The default info string for fenced code blocks.
676    ///
677    /// ```
678    /// # use comrak::{markdown_to_html, Options};
679    /// let mut options = Options::default();
680    /// assert_eq!(markdown_to_html("```\nfn hello();\n```\n", &options),
681    ///            "<pre><code>fn hello();\n</code></pre>\n");
682    ///
683    /// options.parse.default_info_string = Some("rust".into());
684    /// assert_eq!(markdown_to_html("```\nfn hello();\n```\n", &options),
685    ///            "<pre><code class=\"language-rust\">fn hello();\n</code></pre>\n");
686    /// ```
687    pub default_info_string: Option<String>,
688
689    /// Whether or not a simple `x` or `X` is used for tasklist or any other symbol is allowed.
690    #[builder(default)]
691    pub relaxed_tasklist_matching: bool,
692
693    /// Relax parsing of autolinks, allow links to be detected inside brackets
694    /// and allow all url schemes. It is intended to allow a very specific type of autolink
695    /// detection, such as `[this http://and.com that]` or `{http://foo.com}`, on a best can basis.
696    ///
697    /// ```
698    /// # use comrak::{markdown_to_html, Options};
699    /// let mut options = Options::default();
700    /// options.extension.autolink = true;
701    /// assert_eq!(markdown_to_html("[https://foo.com]", &options),
702    ///            "<p>[https://foo.com]</p>\n");
703    ///
704    /// options.parse.relaxed_autolinks = true;
705    /// assert_eq!(markdown_to_html("[https://foo.com]", &options),
706    ///            "<p>[<a href=\"https://foo.com\">https://foo.com</a>]</p>\n");
707    /// ```
708    #[builder(default)]
709    pub relaxed_autolinks: bool,
710
711    /// In case the parser encounters any potential links that have a broken
712    /// reference (e.g `[foo]` when there is no `[foo]: url` entry at the
713    /// bottom) the provided callback will be called with the reference name,
714    /// both in normalized form and unmodified, and the returned pair will be
715    /// used as the link destination and title if not [`None`].
716    ///
717    /// ```
718    /// # use std::{str, sync::Arc};
719    /// # use comrak::{markdown_to_html, BrokenLinkReference, Options, ResolvedReference};
720    /// let cb = |link_ref: BrokenLinkReference| match link_ref.normalized {
721    ///     "foo" => Some(ResolvedReference {
722    ///         url: "https://www.rust-lang.org/".to_string(),
723    ///         title: "The Rust Language".to_string(),
724    ///     }),
725    ///     _ => None,
726    /// };
727    ///
728    /// let mut options = Options::default();
729    /// options.parse.broken_link_callback = Some(Arc::new(cb));
730    ///
731    /// let output = markdown_to_html(
732    ///     "# Cool input!\nWow look at this cool [link][foo]. A [broken link] renders as text.",
733    ///     &options,
734    /// );
735    ///
736    /// assert_eq!(output,
737    ///            "<h1>Cool input!</h1>\n<p>Wow look at this cool \
738    ///            <a href=\"https://www.rust-lang.org/\" title=\"The Rust Language\">link</a>. \
739    ///            A [broken link] renders as text.</p>\n");
740    #[cfg_attr(feature = "arbitrary", arbitrary(default))]
741    pub broken_link_callback: Option<Arc<dyn BrokenLinkCallback + 'c>>,
742}
743
744#[non_exhaustive]
745#[derive(Default, Debug, Clone, Copy, Builder)]
746#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
747/// Options for formatter functions.
748pub struct RenderOptions {
749    /// [Soft line breaks](http://spec.commonmark.org/0.27/#soft-line-breaks) in the input
750    /// translate into hard line breaks in the output.
751    ///
752    /// ```
753    /// # use comrak::{markdown_to_html, Options};
754    /// let mut options = Options::default();
755    /// assert_eq!(markdown_to_html("Hello.\nWorld.\n", &options),
756    ///            "<p>Hello.\nWorld.</p>\n");
757    ///
758    /// options.render.hardbreaks = true;
759    /// assert_eq!(markdown_to_html("Hello.\nWorld.\n", &options),
760    ///            "<p>Hello.<br />\nWorld.</p>\n");
761    /// ```
762    #[builder(default)]
763    pub hardbreaks: bool,
764
765    /// GitHub-style `<pre lang="xyz">` is used for fenced code blocks with info tags.
766    ///
767    /// ```
768    /// # use comrak::{markdown_to_html, Options};
769    /// let mut options = Options::default();
770    /// assert_eq!(markdown_to_html("``` rust\nfn hello();\n```\n", &options),
771    ///            "<pre><code class=\"language-rust\">fn hello();\n</code></pre>\n");
772    ///
773    /// options.render.github_pre_lang = true;
774    /// assert_eq!(markdown_to_html("``` rust\nfn hello();\n```\n", &options),
775    ///            "<pre lang=\"rust\"><code>fn hello();\n</code></pre>\n");
776    /// ```
777    #[builder(default)]
778    pub github_pre_lang: bool,
779
780    /// Enable full info strings for code blocks
781    ///
782    /// ```
783    /// # use comrak::{markdown_to_html, Options};
784    /// let mut options = Options::default();
785    /// assert_eq!(markdown_to_html("``` rust extra info\nfn hello();\n```\n", &options),
786    ///            "<pre><code class=\"language-rust\">fn hello();\n</code></pre>\n");
787    ///
788    /// options.render.full_info_string = true;
789    /// let html = markdown_to_html("``` rust extra info\nfn hello();\n```\n", &options);
790    /// let re = regex::Regex::new(r#"data-meta="extra info""#).unwrap();
791    /// assert!(re.is_match(&html));
792    /// ```
793    #[builder(default)]
794    pub full_info_string: bool,
795
796    /// The wrap column when outputting CommonMark.
797    ///
798    /// ```
799    /// # use comrak::{parse_document, Options, format_commonmark};
800    /// # fn main() {
801    /// # let arena = typed_arena::Arena::new();
802    /// let mut options = Options::default();
803    /// let node = parse_document(&arena, "hello hello hello hello hello hello", &options);
804    /// let mut output = vec![];
805    /// format_commonmark(node, &options, &mut output).unwrap();
806    /// assert_eq!(String::from_utf8(output).unwrap(),
807    ///            "hello hello hello hello hello hello\n");
808    ///
809    /// options.render.width = 20;
810    /// let mut output = vec![];
811    /// format_commonmark(node, &options, &mut output).unwrap();
812    /// assert_eq!(String::from_utf8(output).unwrap(),
813    ///            "hello hello hello\nhello hello hello\n");
814    /// # }
815    /// ```
816    #[builder(default)]
817    pub width: usize,
818
819    /// Allow rendering of raw HTML and potentially dangerous links.
820    ///
821    /// ```
822    /// # use comrak::{markdown_to_html, Options};
823    /// let mut options = Options::default();
824    /// let input = "<script>\nalert('xyz');\n</script>\n\n\
825    ///              Possibly <marquee>annoying</marquee>.\n\n\
826    ///              [Dangerous](javascript:alert(document.cookie)).\n\n\
827    ///              [Safe](http://commonmark.org).\n";
828    ///
829    /// assert_eq!(markdown_to_html(input, &options),
830    ///            "<!-- raw HTML omitted -->\n\
831    ///             <p>Possibly <!-- raw HTML omitted -->annoying<!-- raw HTML omitted -->.</p>\n\
832    ///             <p><a href=\"\">Dangerous</a>.</p>\n\
833    ///             <p><a href=\"http://commonmark.org\">Safe</a>.</p>\n");
834    ///
835    /// options.render.unsafe_ = true;
836    /// assert_eq!(markdown_to_html(input, &options),
837    ///            "<script>\nalert(\'xyz\');\n</script>\n\
838    ///             <p>Possibly <marquee>annoying</marquee>.</p>\n\
839    ///             <p><a href=\"javascript:alert(document.cookie)\">Dangerous</a>.</p>\n\
840    ///             <p><a href=\"http://commonmark.org\">Safe</a>.</p>\n");
841    /// ```
842    #[builder(default)]
843    pub unsafe_: bool,
844
845    /// Escape raw HTML instead of clobbering it.
846    /// ```
847    /// # use comrak::{markdown_to_html, Options};
848    /// let mut options = Options::default();
849    /// let input = "<i>italic text</i>";
850    ///
851    /// assert_eq!(markdown_to_html(input, &options),
852    ///            "<p><!-- raw HTML omitted -->italic text<!-- raw HTML omitted --></p>\n");
853    ///
854    /// options.render.escape = true;
855    /// assert_eq!(markdown_to_html(input, &options),
856    ///            "<p>&lt;i&gt;italic text&lt;/i&gt;</p>\n");
857    /// ```
858    #[builder(default)]
859    pub escape: bool,
860
861    /// Set the type of [bullet list marker](https://spec.commonmark.org/0.30/#bullet-list-marker) to use. Options are:
862    ///
863    /// * [`ListStyleType::Dash`] to use `-` (default)
864    /// * [`ListStyleType::Plus`] to use `+`
865    /// * [`ListStyleType::Star`] to use `*`
866    ///
867    /// ```rust
868    /// # use comrak::{markdown_to_commonmark, Options, ListStyleType};
869    /// let mut options = Options::default();
870    /// let input = "- one\n- two\n- three";
871    /// assert_eq!(markdown_to_commonmark(input, &options),
872    ///            "- one\n- two\n- three\n"); // default is Dash
873    ///
874    /// options.render.list_style = ListStyleType::Plus;
875    /// assert_eq!(markdown_to_commonmark(input, &options),
876    ///            "+ one\n+ two\n+ three\n");
877    ///
878    /// options.render.list_style = ListStyleType::Star;
879    /// assert_eq!(markdown_to_commonmark(input, &options),
880    ///            "* one\n* two\n* three\n");
881    /// ```
882    #[builder(default)]
883    pub list_style: ListStyleType,
884
885    /// Include source position attributes in HTML and XML output.
886    ///
887    /// Sourcepos information is reliable for all core block items, and most
888    /// extensions. The description lists extension still has issues; see
889    /// <https://github.com/kivikakk/comrak/blob/3bb6d4ce/src/tests/description_lists.rs#L60-L125>.
890    ///
891    /// Sourcepos information is **not** reliable for inlines, and is not
892    /// included in HTML without also setting [`experimental_inline_sourcepos`].
893    /// See <https://github.com/kivikakk/comrak/pull/439> for a discussion.
894    ///
895    /// ```rust
896    /// # use comrak::{markdown_to_commonmark_xml, Options};
897    /// let mut options = Options::default();
898    /// options.render.sourcepos = true;
899    /// let input = "## Hello world!";
900    /// let xml = markdown_to_commonmark_xml(input, &options);
901    /// assert!(xml.contains("<text sourcepos=\"1:4-1:15\" xml:space=\"preserve\">"));
902    /// ```
903    ///
904    /// [`experimental_inline_sourcepos`]: crate::RenderOptionsBuilder::experimental_inline_sourcepos
905    #[builder(default)]
906    pub sourcepos: bool,
907
908    /// Include inline sourcepos in HTML output, which is known to have issues.
909    /// See <https://github.com/kivikakk/comrak/pull/439> for a discussion.
910    /// ```rust
911    /// # use comrak::{markdown_to_html, Options};
912    /// let mut options = Options::default();
913    /// options.render.sourcepos = true;
914    /// let input = "Hello *world*!";
915    /// assert_eq!(markdown_to_html(input, &options),
916    ///            "<p data-sourcepos=\"1:1-1:14\">Hello <em>world</em>!</p>\n");
917    /// options.render.experimental_inline_sourcepos = true;
918    /// assert_eq!(markdown_to_html(input, &options),
919    ///            "<p data-sourcepos=\"1:1-1:14\">Hello <em data-sourcepos=\"1:7-1:13\">world</em>!</p>\n");
920    /// ```
921    #[builder(default)]
922    pub experimental_inline_sourcepos: bool,
923
924    /// Wrap escaped characters in a `<span>` to allow any
925    /// post-processing to recognize them.
926    ///
927    /// ```rust
928    /// # use comrak::{markdown_to_html, Options};
929    /// let mut options = Options::default();
930    /// let input = "Notify user \\@example";
931    ///
932    /// assert_eq!(markdown_to_html(input, &options),
933    ///            "<p>Notify user @example</p>\n");
934    ///
935    /// options.render.escaped_char_spans = true;
936    /// assert_eq!(markdown_to_html(input, &options),
937    ///            "<p>Notify user <span data-escaped-char>@</span>example</p>\n");
938    /// ```
939    #[builder(default)]
940    pub escaped_char_spans: bool,
941
942    /// Ignore setext headings in input.
943    ///
944    /// ```rust
945    /// # use comrak::{markdown_to_html, Options};
946    /// let mut options = Options::default();
947    /// let input = "setext heading\n---";
948    ///
949    /// assert_eq!(markdown_to_html(input, &options),
950    ///            "<h2>setext heading</h2>\n");
951    ///
952    /// options.render.ignore_setext = true;
953    /// assert_eq!(markdown_to_html(input, &options),
954    ///            "<p>setext heading</p>\n<hr />\n");
955    /// ```
956    #[builder(default)]
957    pub ignore_setext: bool,
958
959    /// Ignore empty links in input.
960    ///
961    /// ```rust
962    /// # use comrak::{markdown_to_html, Options};
963    /// let mut options = Options::default();
964    /// let input = "[]()";
965    ///
966    /// assert_eq!(markdown_to_html(input, &options),
967    ///            "<p><a href=\"\"></a></p>\n");
968    ///
969    /// options.render.ignore_empty_links = true;
970    /// assert_eq!(markdown_to_html(input, &options), "<p>[]()</p>\n");
971    /// ```
972    #[builder(default)]
973    pub ignore_empty_links: bool,
974
975    /// Enables GFM quirks in HTML output which break CommonMark compatibility.
976    ///
977    /// ```rust
978    /// # use comrak::{markdown_to_html, Options};
979    /// let mut options = Options::default();
980    /// let input = "****abcd**** *_foo_*";
981    ///
982    /// assert_eq!(markdown_to_html(input, &options),
983    ///            "<p><strong><strong>abcd</strong></strong> <em><em>foo</em></em></p>\n");
984    ///
985    /// options.render.gfm_quirks = true;
986    /// assert_eq!(markdown_to_html(input, &options),
987    ///            "<p><strong>abcd</strong> <em><em>foo</em></em></p>\n");
988    /// ```
989    #[builder(default)]
990    pub gfm_quirks: bool,
991
992    /// Prefer fenced code blocks when outputting CommonMark.
993    ///
994    /// ```rust
995    /// # use std::str;
996    /// # use comrak::{Arena, Options, format_commonmark, parse_document};
997    /// let arena = Arena::new();
998    /// let mut options = Options::default();
999    /// let input = "```\nhello\n```\n";
1000    /// let root = parse_document(&arena, input, &options);
1001    ///
1002    /// let mut buf = Vec::new();
1003    /// format_commonmark(&root, &options, &mut buf);
1004    /// assert_eq!(str::from_utf8(&buf).unwrap(), "    hello\n");
1005    ///
1006    /// buf.clear();
1007    /// options.render.prefer_fenced = true;
1008    /// format_commonmark(&root, &options, &mut buf);
1009    /// assert_eq!(str::from_utf8(&buf).unwrap(), "```\nhello\n```\n");
1010    /// ```
1011    #[builder(default)]
1012    pub prefer_fenced: bool,
1013
1014    /// Render the image as a figure element with the title as its caption.
1015    ///
1016    /// ```rust
1017    /// # use comrak::{markdown_to_html, Options};
1018    /// let mut options = Options::default();
1019    /// let input = "![image](https://example.com/image.png \"this is an image\")";
1020    ///
1021    /// assert_eq!(markdown_to_html(input, &options),
1022    ///            "<p><img src=\"https://example.com/image.png\" alt=\"image\" title=\"this is an image\" /></p>\n");
1023    ///
1024    /// options.render.figure_with_caption = true;
1025    /// assert_eq!(markdown_to_html(input, &options),
1026    ///            "<p><figure><img src=\"https://example.com/image.png\" alt=\"image\" title=\"this is an image\" /><figcaption>this is an image</figcaption></figure></p>\n");
1027    /// ```
1028    #[builder(default)]
1029    pub figure_with_caption: bool,
1030
1031    /// Add classes to the output of the tasklist extension. This allows tasklists to be styled.
1032    ///
1033    /// ```rust
1034    /// # use comrak::{markdown_to_html, Options};
1035    /// let mut options = Options::default();
1036    /// options.extension.tasklist = true;
1037    /// let input = "- [ ] Foo";
1038    ///
1039    /// assert_eq!(markdown_to_html(input, &options),
1040    ///            "<ul>\n<li><input type=\"checkbox\" disabled=\"\" /> Foo</li>\n</ul>\n");
1041    ///
1042    /// options.render.tasklist_classes = true;
1043    /// assert_eq!(markdown_to_html(input, &options),
1044    ///            "<ul class=\"contains-task-list\">\n<li class=\"task-list-item\"><input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled=\"\" /> Foo</li>\n</ul>\n");
1045    /// ```
1046    #[builder(default)]
1047    pub tasklist_classes: bool,
1048
1049    /// Render ordered list with a minimum marker width.
1050    /// Having a width lower than 3 doesn't do anything.
1051    ///
1052    /// ```rust
1053    /// # use comrak::{markdown_to_commonmark, Options};
1054    /// let mut options = Options::default();
1055    /// let input = "1. Something";
1056    ///
1057    /// assert_eq!(markdown_to_commonmark(input, &options),
1058    ///            "1. Something\n");
1059    ///
1060    /// options.render.ol_width = 5;
1061    /// assert_eq!(markdown_to_commonmark(input, &options),
1062    ///            "1.   Something\n");
1063    /// ```
1064    #[builder(default)]
1065    pub ol_width: usize,
1066}
1067
1068#[non_exhaustive]
1069#[derive(Default, Debug, Clone, Builder)]
1070/// Umbrella plugins struct.
1071pub struct Plugins<'p> {
1072    /// Configure render-time plugins.
1073    #[builder(default)]
1074    pub render: RenderPlugins<'p>,
1075}
1076
1077#[non_exhaustive]
1078#[derive(Default, Clone, Builder)]
1079/// Plugins for alternative rendering.
1080pub struct RenderPlugins<'p> {
1081    /// Provide a syntax highlighter adapter implementation for syntax
1082    /// highlighting of codefence blocks.
1083    /// ```
1084    /// # use comrak::{markdown_to_html, Options, Plugins, markdown_to_html_with_plugins};
1085    /// # use comrak::adapters::SyntaxHighlighterAdapter;
1086    /// use std::collections::HashMap;
1087    /// use std::io::{self, Write};
1088    /// let options = Options::default();
1089    /// let mut plugins = Plugins::default();
1090    /// let input = "```rust\nfn main<'a>();\n```";
1091    ///
1092    /// assert_eq!(markdown_to_html_with_plugins(input, &options, &plugins),
1093    ///            "<pre><code class=\"language-rust\">fn main&lt;'a&gt;();\n</code></pre>\n");
1094    ///
1095    /// pub struct MockAdapter {}
1096    /// impl SyntaxHighlighterAdapter for MockAdapter {
1097    ///     fn write_highlighted(&self, output: &mut dyn Write, lang: Option<&str>, code: &str) -> io::Result<()> {
1098    ///         write!(output, "<span class=\"lang-{}\">{}</span>", lang.unwrap(), code)
1099    ///     }
1100    ///
1101    ///     fn write_pre_tag(&self, output: &mut dyn Write, _attributes: HashMap<String, String>) -> io::Result<()> {
1102    ///         output.write_all(b"<pre lang=\"rust\">")
1103    ///     }
1104    ///
1105    ///     fn write_code_tag(&self, output: &mut dyn Write, _attributes: HashMap<String, String>) -> io::Result<()> {
1106    ///         output.write_all(b"<code class=\"language-rust\">")
1107    ///     }
1108    /// }
1109    ///
1110    /// let adapter = MockAdapter {};
1111    /// plugins.render.codefence_syntax_highlighter = Some(&adapter);
1112    ///
1113    /// assert_eq!(markdown_to_html_with_plugins(input, &options, &plugins),
1114    ///            "<pre lang=\"rust\"><code class=\"language-rust\"><span class=\"lang-rust\">fn main<'a>();\n</span></code></pre>\n");
1115    /// ```
1116    pub codefence_syntax_highlighter: Option<&'p dyn SyntaxHighlighterAdapter>,
1117
1118    /// Optional heading adapter
1119    pub heading_adapter: Option<&'p dyn HeadingAdapter>,
1120}
1121
1122impl Debug for RenderPlugins<'_> {
1123    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1124        f.debug_struct("RenderPlugins")
1125            .field(
1126                "codefence_syntax_highlighter",
1127                &"impl SyntaxHighlighterAdapter",
1128            )
1129            .finish()
1130    }
1131}
1132
1133/// A reference link's resolved details.
1134#[derive(Clone, Debug)]
1135pub struct ResolvedReference {
1136    /// The destination URL of the reference link.
1137    pub url: String,
1138
1139    /// The text of the link.
1140    pub title: String,
1141}
1142
1143struct FootnoteDefinition<'a> {
1144    ix: Option<u32>,
1145    node: &'a AstNode<'a>,
1146    name: String,
1147    total_references: u32,
1148}
1149
1150impl<'a, 'o, 'c> Parser<'a, 'o, 'c>
1151where
1152    'c: 'o,
1153{
1154    fn new(arena: &'a Arena<AstNode<'a>>, root: &'a AstNode<'a>, options: &'o Options<'c>) -> Self {
1155        Parser {
1156            arena,
1157            refmap: RefMap::new(),
1158            root,
1159            current: root,
1160            line_number: 0,
1161            offset: 0,
1162            column: 0,
1163            thematic_break_kill_pos: 0,
1164            first_nonspace: 0,
1165            first_nonspace_column: 0,
1166            indent: 0,
1167            blank: false,
1168            partially_consumed_tab: false,
1169            curline_len: 0,
1170            curline_end_col: 0,
1171            last_line_length: 0,
1172            last_buffer_ended_with_cr: false,
1173            total_size: 0,
1174            options,
1175        }
1176    }
1177
1178    fn feed(&mut self, linebuf: &mut Vec<u8>, mut s: &str, eof: bool) {
1179        if let (0, Some(delimiter)) = (
1180            self.total_size,
1181            &self.options.extension.front_matter_delimiter,
1182        ) {
1183            if let Some((front_matter, rest)) = split_off_front_matter(s, delimiter) {
1184                let lines = front_matter
1185                    .as_bytes()
1186                    .iter()
1187                    .filter(|b| **b == b'\n')
1188                    .count();
1189
1190                let mut stripped_front_matter = front_matter.to_string();
1191                strings::remove_trailing_blank_lines(&mut stripped_front_matter);
1192                let stripped_lines = stripped_front_matter
1193                    .as_bytes()
1194                    .iter()
1195                    .filter(|b| **b == b'\n')
1196                    .count();
1197
1198                let node = self.add_child(
1199                    self.root,
1200                    NodeValue::FrontMatter(front_matter.to_string()),
1201                    1,
1202                );
1203                s = rest;
1204                self.finalize(node).unwrap();
1205
1206                node.data.borrow_mut().sourcepos = Sourcepos {
1207                    start: nodes::LineColumn { line: 1, column: 1 },
1208                    end: nodes::LineColumn {
1209                        line: 1 + stripped_lines,
1210                        column: delimiter.len(),
1211                    },
1212                };
1213                self.line_number += lines;
1214            }
1215        }
1216
1217        let s = s.as_bytes();
1218
1219        if s.len() > usize::MAX - self.total_size {
1220            self.total_size = usize::MAX;
1221        } else {
1222            self.total_size += s.len();
1223        }
1224
1225        let mut buffer = 0;
1226        if self.last_buffer_ended_with_cr && !s.is_empty() && s[0] == b'\n' {
1227            buffer += 1;
1228        }
1229        self.last_buffer_ended_with_cr = false;
1230
1231        let end = s.len();
1232
1233        while buffer < end {
1234            let mut process = false;
1235            let mut eol = buffer;
1236            while eol < end {
1237                if strings::is_line_end_char(s[eol]) {
1238                    process = true;
1239                    break;
1240                }
1241                if s[eol] == 0 {
1242                    break;
1243                }
1244                eol += 1;
1245            }
1246
1247            if eol >= end && eof {
1248                process = true;
1249            }
1250
1251            if process {
1252                if !linebuf.is_empty() {
1253                    linebuf.extend_from_slice(&s[buffer..eol]);
1254                    self.process_line(linebuf);
1255                    linebuf.truncate(0);
1256                } else {
1257                    self.process_line(&s[buffer..eol]);
1258                }
1259            } else if eol < end && s[eol] == b'\0' {
1260                linebuf.extend_from_slice(&s[buffer..eol]);
1261                linebuf.extend_from_slice(&"\u{fffd}".to_string().into_bytes());
1262            } else {
1263                linebuf.extend_from_slice(&s[buffer..eol]);
1264            }
1265
1266            buffer = eol;
1267            if buffer < end {
1268                if s[buffer] == b'\0' {
1269                    buffer += 1;
1270                } else {
1271                    if s[buffer] == b'\r' {
1272                        buffer += 1;
1273                        if buffer == end {
1274                            self.last_buffer_ended_with_cr = true;
1275                        }
1276                    }
1277                    if buffer < end && s[buffer] == b'\n' {
1278                        buffer += 1;
1279                    }
1280                }
1281            }
1282        }
1283    }
1284
1285    fn scan_thematic_break_inner(&mut self, line: &[u8]) -> (usize, bool) {
1286        let mut i = self.first_nonspace;
1287
1288        if i >= line.len() {
1289            return (i, false);
1290        }
1291
1292        let c = line[i];
1293        if c != b'*' && c != b'_' && c != b'-' {
1294            return (i, false);
1295        }
1296
1297        let mut count = 1;
1298        let mut nextc;
1299        loop {
1300            i += 1;
1301            if i >= line.len() {
1302                return (i, false);
1303            }
1304            nextc = line[i];
1305
1306            if nextc == c {
1307                count += 1;
1308            } else if nextc != b' ' && nextc != b'\t' {
1309                break;
1310            }
1311        }
1312
1313        if count >= 3 && (nextc == b'\r' || nextc == b'\n') {
1314            ((i - self.first_nonspace) + 1, true)
1315        } else {
1316            (i, false)
1317        }
1318    }
1319
1320    fn scan_thematic_break(&mut self, line: &[u8]) -> Option<usize> {
1321        let (offset, found) = self.scan_thematic_break_inner(line);
1322        if !found {
1323            self.thematic_break_kill_pos = offset;
1324            None
1325        } else {
1326            Some(offset)
1327        }
1328    }
1329
1330    fn find_first_nonspace(&mut self, line: &[u8]) {
1331        let mut chars_to_tab = TAB_STOP - (self.column % TAB_STOP);
1332
1333        if self.first_nonspace <= self.offset {
1334            self.first_nonspace = self.offset;
1335            self.first_nonspace_column = self.column;
1336
1337            loop {
1338                if self.first_nonspace >= line.len() {
1339                    break;
1340                }
1341                match line[self.first_nonspace] {
1342                    32 => {
1343                        self.first_nonspace += 1;
1344                        self.first_nonspace_column += 1;
1345                        chars_to_tab -= 1;
1346                        if chars_to_tab == 0 {
1347                            chars_to_tab = TAB_STOP;
1348                        }
1349                    }
1350                    9 => {
1351                        self.first_nonspace += 1;
1352                        self.first_nonspace_column += chars_to_tab;
1353                        chars_to_tab = TAB_STOP;
1354                    }
1355                    _ => break,
1356                }
1357            }
1358        }
1359
1360        self.indent = self.first_nonspace_column - self.column;
1361        self.blank = self.first_nonspace < line.len()
1362            && strings::is_line_end_char(line[self.first_nonspace]);
1363    }
1364
1365    fn process_line(&mut self, line: &[u8]) {
1366        let mut new_line: Vec<u8>;
1367        let line = if line.is_empty() || !strings::is_line_end_char(*line.last().unwrap()) {
1368            new_line = line.into();
1369            new_line.push(b'\n');
1370            &new_line
1371        } else {
1372            line
1373        };
1374
1375        self.curline_len = line.len();
1376        self.curline_end_col = line.len();
1377        if self.curline_end_col > 0 && line[self.curline_end_col - 1] == b'\n' {
1378            self.curline_end_col -= 1;
1379        }
1380        if self.curline_end_col > 0 && line[self.curline_end_col - 1] == b'\r' {
1381            self.curline_end_col -= 1;
1382        }
1383
1384        self.offset = 0;
1385        self.column = 0;
1386        self.first_nonspace = 0;
1387        self.first_nonspace_column = 0;
1388        self.indent = 0;
1389        self.thematic_break_kill_pos = 0;
1390        self.blank = false;
1391        self.partially_consumed_tab = false;
1392
1393        if self.line_number == 0
1394            && line.len() >= 3
1395            && unsafe { str::from_utf8_unchecked(line) }.starts_with('\u{feff}')
1396        {
1397            self.offset += 3;
1398        }
1399
1400        self.line_number += 1;
1401
1402        let mut all_matched = true;
1403        if let Some(last_matched_container) = self.check_open_blocks(line, &mut all_matched) {
1404            let mut container = last_matched_container;
1405            let current = self.current;
1406            self.open_new_blocks(&mut container, line, all_matched);
1407
1408            if current.same_node(self.current) {
1409                self.add_text_to_container(container, last_matched_container, line);
1410            }
1411        }
1412
1413        self.last_line_length = self.curline_end_col;
1414
1415        self.curline_len = 0;
1416        self.curline_end_col = 0;
1417    }
1418
1419    fn check_open_blocks(
1420        &mut self,
1421        line: &[u8],
1422        all_matched: &mut bool,
1423    ) -> Option<&'a AstNode<'a>> {
1424        let (new_all_matched, mut container, should_continue) =
1425            self.check_open_blocks_inner(self.root, line);
1426
1427        *all_matched = new_all_matched;
1428        if !*all_matched {
1429            container = container.parent().unwrap();
1430        }
1431
1432        if !should_continue {
1433            None
1434        } else {
1435            Some(container)
1436        }
1437    }
1438
1439    fn check_open_blocks_inner(
1440        &mut self,
1441        mut container: &'a AstNode<'a>,
1442        line: &[u8],
1443    ) -> (bool, &'a AstNode<'a>, bool) {
1444        let mut should_continue = true;
1445
1446        while nodes::last_child_is_open(container) {
1447            container = container.last_child().unwrap();
1448            let ast = &mut *container.data.borrow_mut();
1449
1450            self.find_first_nonspace(line);
1451
1452            match ast.value {
1453                NodeValue::BlockQuote => {
1454                    if !self.parse_block_quote_prefix(line) {
1455                        return (false, container, should_continue);
1456                    }
1457                }
1458                NodeValue::Item(ref nl) => {
1459                    if !self.parse_node_item_prefix(line, container, nl) {
1460                        return (false, container, should_continue);
1461                    }
1462                }
1463                NodeValue::DescriptionItem(ref di) => {
1464                    if !self.parse_description_item_prefix(line, container, di) {
1465                        return (false, container, should_continue);
1466                    }
1467                }
1468                NodeValue::CodeBlock(..) => {
1469                    if !self.parse_code_block_prefix(line, container, ast, &mut should_continue) {
1470                        return (false, container, should_continue);
1471                    }
1472                }
1473                NodeValue::HtmlBlock(ref nhb) => {
1474                    if !self.parse_html_block_prefix(nhb.block_type) {
1475                        return (false, container, should_continue);
1476                    }
1477                }
1478                NodeValue::Paragraph => {
1479                    if self.blank {
1480                        return (false, container, should_continue);
1481                    }
1482                }
1483                NodeValue::Table(..) => {
1484                    if !table::matches(&line[self.first_nonspace..], self.options.extension.spoiler)
1485                    {
1486                        return (false, container, should_continue);
1487                    }
1488                    continue;
1489                }
1490                NodeValue::Heading(..) | NodeValue::TableRow(..) | NodeValue::TableCell => {
1491                    return (false, container, should_continue);
1492                }
1493                NodeValue::FootnoteDefinition(..) => {
1494                    if !self.parse_footnote_definition_block_prefix(line) {
1495                        return (false, container, should_continue);
1496                    }
1497                }
1498                NodeValue::MultilineBlockQuote(..) => {
1499                    if !self.parse_multiline_block_quote_prefix(
1500                        line,
1501                        container,
1502                        ast,
1503                        &mut should_continue,
1504                    ) {
1505                        return (false, container, should_continue);
1506                    }
1507                }
1508                _ => {}
1509            }
1510        }
1511
1512        (true, container, should_continue)
1513    }
1514
1515    fn is_not_greentext(&mut self, line: &[u8]) -> bool {
1516        !self.options.extension.greentext || strings::is_space_or_tab(line[self.first_nonspace + 1])
1517    }
1518
1519    fn setext_heading_line(&mut self, s: &[u8]) -> Option<SetextChar> {
1520        match self.options.render.ignore_setext {
1521            false => scanners::setext_heading_line(s),
1522            true => None,
1523        }
1524    }
1525
1526    fn detect_multiline_blockquote(
1527        &mut self,
1528        line: &[u8],
1529        indented: bool,
1530        matched: &mut usize,
1531    ) -> bool {
1532        !indented
1533            && self.options.extension.multiline_block_quotes
1534            && unwrap_into(
1535                scanners::open_multiline_block_quote_fence(&line[self.first_nonspace..]),
1536                matched,
1537            )
1538    }
1539
1540    fn handle_multiline_blockquote(
1541        &mut self,
1542        container: &mut &'a Node<'a, RefCell<Ast>>,
1543        line: &[u8],
1544        indented: bool,
1545        matched: &mut usize,
1546    ) -> bool {
1547        if !self.detect_multiline_blockquote(line, indented, matched) {
1548            return false;
1549        }
1550
1551        let first_nonspace = self.first_nonspace;
1552        let offset = self.offset;
1553        let nmbc = NodeMultilineBlockQuote {
1554            fence_length: *matched,
1555            fence_offset: first_nonspace - offset,
1556        };
1557
1558        *container = self.add_child(
1559            container,
1560            NodeValue::MultilineBlockQuote(nmbc),
1561            self.first_nonspace + 1,
1562        );
1563
1564        self.advance_offset(line, first_nonspace + *matched - offset, false);
1565
1566        true
1567    }
1568
1569    fn detect_blockquote(&mut self, line: &[u8], indented: bool) -> bool {
1570        !indented && line[self.first_nonspace] == b'>' && self.is_not_greentext(line)
1571    }
1572
1573    fn handle_blockquote(
1574        &mut self,
1575        container: &mut &'a Node<'a, RefCell<Ast>>,
1576        line: &[u8],
1577        indented: bool,
1578    ) -> bool {
1579        if !self.detect_blockquote(line, indented) {
1580            return false;
1581        }
1582
1583        let blockquote_startpos = self.first_nonspace;
1584
1585        let offset = self.first_nonspace + 1 - self.offset;
1586        self.advance_offset(line, offset, false);
1587        if strings::is_space_or_tab(line[self.offset]) {
1588            self.advance_offset(line, 1, true);
1589        }
1590        *container = self.add_child(container, NodeValue::BlockQuote, blockquote_startpos + 1);
1591
1592        true
1593    }
1594
1595    fn detect_atx_heading(&mut self, line: &[u8], indented: bool, matched: &mut usize) -> bool {
1596        !indented
1597            && unwrap_into(
1598                scanners::atx_heading_start(&line[self.first_nonspace..]),
1599                matched,
1600            )
1601    }
1602
1603    fn handle_atx_heading(
1604        &mut self,
1605        container: &mut &'a Node<'a, RefCell<Ast>>,
1606        line: &[u8],
1607        indented: bool,
1608        matched: &mut usize,
1609    ) -> bool {
1610        if !self.detect_atx_heading(line, indented, matched) {
1611            return false;
1612        }
1613
1614        let heading_startpos = self.first_nonspace;
1615        let offset = self.offset;
1616        self.advance_offset(line, heading_startpos + *matched - offset, false);
1617        *container = self.add_child(
1618            container,
1619            NodeValue::Heading(NodeHeading::default()),
1620            heading_startpos + 1,
1621        );
1622
1623        let mut hashpos = line[self.first_nonspace..]
1624            .iter()
1625            .position(|&c| c == b'#')
1626            .unwrap()
1627            + self.first_nonspace;
1628        let mut level = 0;
1629        while line[hashpos] == b'#' {
1630            level += 1;
1631            hashpos += 1;
1632        }
1633
1634        let container_ast = &mut container.data.borrow_mut();
1635        container_ast.value = NodeValue::Heading(NodeHeading {
1636            level,
1637            setext: false,
1638        });
1639        container_ast.internal_offset = *matched;
1640
1641        true
1642    }
1643
1644    fn detect_code_fence(&mut self, line: &[u8], indented: bool, matched: &mut usize) -> bool {
1645        !indented
1646            && unwrap_into(
1647                scanners::open_code_fence(&line[self.first_nonspace..]),
1648                matched,
1649            )
1650    }
1651
1652    fn handle_code_fence(
1653        &mut self,
1654        container: &mut &'a Node<'a, RefCell<Ast>>,
1655        line: &[u8],
1656        indented: bool,
1657        matched: &mut usize,
1658    ) -> bool {
1659        if !self.detect_code_fence(line, indented, matched) {
1660            return false;
1661        }
1662
1663        let first_nonspace = self.first_nonspace;
1664        let offset = self.offset;
1665        let ncb = NodeCodeBlock {
1666            fenced: true,
1667            fence_char: line[first_nonspace],
1668            fence_length: *matched,
1669            fence_offset: first_nonspace - offset,
1670            info: String::with_capacity(10),
1671            literal: String::new(),
1672        };
1673        *container = self.add_child(
1674            container,
1675            NodeValue::CodeBlock(ncb),
1676            self.first_nonspace + 1,
1677        );
1678        self.advance_offset(line, first_nonspace + *matched - offset, false);
1679
1680        true
1681    }
1682
1683    fn detect_html_block(
1684        &mut self,
1685        container: &AstNode,
1686        line: &[u8],
1687        indented: bool,
1688        matched: &mut usize,
1689    ) -> bool {
1690        !indented
1691            && (unwrap_into(
1692                scanners::html_block_start(&line[self.first_nonspace..]),
1693                matched,
1694            ) || (!node_matches!(container, NodeValue::Paragraph)
1695                && unwrap_into(
1696                    scanners::html_block_start_7(&line[self.first_nonspace..]),
1697                    matched,
1698                )))
1699    }
1700
1701    fn handle_html_block(
1702        &mut self,
1703        container: &mut &'a Node<'a, RefCell<Ast>>,
1704        line: &[u8],
1705        indented: bool,
1706        matched: &mut usize,
1707    ) -> bool {
1708        if !self.detect_html_block(container, line, indented, matched) {
1709            return false;
1710        }
1711
1712        let nhb = NodeHtmlBlock {
1713            block_type: *matched as u8,
1714            literal: String::new(),
1715        };
1716
1717        *container = self.add_child(
1718            container,
1719            NodeValue::HtmlBlock(nhb),
1720            self.first_nonspace + 1,
1721        );
1722
1723        true
1724    }
1725
1726    fn detect_setext_heading(
1727        &mut self,
1728        container: &AstNode,
1729        line: &[u8],
1730        indented: bool,
1731        sc: &mut scanners::SetextChar,
1732    ) -> bool {
1733        !indented
1734            && node_matches!(container, NodeValue::Paragraph)
1735            && unwrap_into(self.setext_heading_line(&line[self.first_nonspace..]), sc)
1736    }
1737
1738    fn handle_setext_heading(
1739        &mut self,
1740        container: &mut &'a Node<'a, RefCell<Ast>>,
1741        line: &[u8],
1742        indented: bool,
1743        sc: &mut scanners::SetextChar,
1744    ) -> bool {
1745        if !self.detect_setext_heading(container, line, indented, sc) {
1746            return false;
1747        }
1748
1749        let has_content = {
1750            let mut ast = container.data.borrow_mut();
1751            self.resolve_reference_link_definitions(&mut ast.content)
1752        };
1753        if has_content {
1754            container.data.borrow_mut().value = NodeValue::Heading(NodeHeading {
1755                level: match sc {
1756                    scanners::SetextChar::Equals => 1,
1757                    scanners::SetextChar::Hyphen => 2,
1758                },
1759                setext: true,
1760            });
1761            let adv = line.len() - 1 - self.offset;
1762            self.advance_offset(line, adv, false);
1763        }
1764
1765        true
1766    }
1767
1768    fn detect_thematic_break(
1769        &mut self,
1770        container: &AstNode,
1771        line: &[u8],
1772        indented: bool,
1773        matched: &mut usize,
1774        all_matched: bool,
1775    ) -> bool {
1776        !indented
1777            && !matches!(
1778                (&container.data.borrow().value, all_matched),
1779                (&NodeValue::Paragraph, false)
1780            )
1781            && self.thematic_break_kill_pos <= self.first_nonspace
1782            && unwrap_into(self.scan_thematic_break(line), matched)
1783    }
1784
1785    fn handle_thematic_break(
1786        &mut self,
1787        container: &mut &'a Node<'a, RefCell<Ast>>,
1788        line: &[u8],
1789        indented: bool,
1790        matched: &mut usize,
1791        all_matched: bool,
1792    ) -> bool {
1793        if !self.detect_thematic_break(container, line, indented, matched, all_matched) {
1794            return false;
1795        }
1796
1797        *container = self.add_child(container, NodeValue::ThematicBreak, self.first_nonspace + 1);
1798
1799        let adv = line.len() - 1 - self.offset;
1800        self.advance_offset(line, adv, false);
1801
1802        true
1803    }
1804
1805    fn detect_footnote(
1806        &mut self,
1807        line: &[u8],
1808        indented: bool,
1809        matched: &mut usize,
1810        depth: usize,
1811    ) -> bool {
1812        !indented
1813            && self.options.extension.footnotes
1814            && depth < MAX_LIST_DEPTH
1815            && unwrap_into(
1816                scanners::footnote_definition(&line[self.first_nonspace..]),
1817                matched,
1818            )
1819    }
1820
1821    fn handle_footnote(
1822        &mut self,
1823        container: &mut &'a Node<'a, RefCell<Ast>>,
1824        line: &[u8],
1825        indented: bool,
1826        matched: &mut usize,
1827        depth: usize,
1828    ) -> bool {
1829        if !self.detect_footnote(line, indented, matched, depth) {
1830            return false;
1831        }
1832
1833        let mut c = &line[self.first_nonspace + 2..self.first_nonspace + *matched];
1834        c = c.split(|&e| e == b']').next().unwrap();
1835        let offset = self.first_nonspace + *matched - self.offset;
1836        self.advance_offset(line, offset, false);
1837        *container = self.add_child(
1838            container,
1839            NodeValue::FootnoteDefinition(NodeFootnoteDefinition {
1840                name: str::from_utf8(c).unwrap().to_string(),
1841                total_references: 0,
1842            }),
1843            self.first_nonspace + 1,
1844        );
1845        container.data.borrow_mut().internal_offset = *matched;
1846
1847        true
1848    }
1849
1850    fn detect_description_list(
1851        &mut self,
1852        container: &mut &'a Node<'a, RefCell<Ast>>,
1853        line: &[u8],
1854        indented: bool,
1855        matched: &mut usize,
1856    ) -> bool {
1857        !indented
1858            && self.options.extension.description_lists
1859            && unwrap_into(
1860                scanners::description_item_start(&line[self.first_nonspace..]),
1861                matched,
1862            )
1863            && self.parse_desc_list_details(container, *matched)
1864    }
1865
1866    fn handle_description_list(
1867        &mut self,
1868        container: &mut &'a Node<'a, RefCell<Ast>>,
1869        line: &[u8],
1870        indented: bool,
1871        matched: &mut usize,
1872    ) -> bool {
1873        if !self.detect_description_list(container, line, indented, matched) {
1874            return false;
1875        }
1876
1877        let offset = self.first_nonspace + *matched - self.offset;
1878        self.advance_offset(line, offset, false);
1879        if strings::is_space_or_tab(line[self.offset]) {
1880            self.advance_offset(line, 1, true);
1881        }
1882
1883        true
1884    }
1885
1886    fn detect_list(
1887        &mut self,
1888        container: &AstNode,
1889        line: &[u8],
1890        indented: bool,
1891        matched: &mut usize,
1892        depth: usize,
1893        nl: &mut NodeList,
1894    ) -> bool {
1895        (!indented || node_matches!(container, NodeValue::List(..)))
1896            && self.indent < 4
1897            && depth < MAX_LIST_DEPTH
1898            && unwrap_into_2(
1899                parse_list_marker(
1900                    line,
1901                    self.first_nonspace,
1902                    node_matches!(container, NodeValue::Paragraph),
1903                ),
1904                matched,
1905                nl,
1906            )
1907    }
1908
1909    fn handle_list(
1910        &mut self,
1911        container: &mut &'a Node<'a, RefCell<Ast>>,
1912        line: &[u8],
1913        indented: bool,
1914        matched: &mut usize,
1915        depth: usize,
1916        nl: &mut NodeList,
1917    ) -> bool {
1918        if !self.detect_list(container, line, indented, matched, depth, nl) {
1919            return false;
1920        }
1921
1922        let offset = self.first_nonspace + *matched - self.offset;
1923        self.advance_offset(line, offset, false);
1924        let (save_partially_consumed_tab, save_offset, save_column) =
1925            (self.partially_consumed_tab, self.offset, self.column);
1926
1927        while self.column - save_column <= 5 && strings::is_space_or_tab(line[self.offset]) {
1928            self.advance_offset(line, 1, true);
1929        }
1930
1931        let i = self.column - save_column;
1932        if !(1..5).contains(&i) || strings::is_line_end_char(line[self.offset]) {
1933            nl.padding = *matched + 1;
1934            self.offset = save_offset;
1935            self.column = save_column;
1936            self.partially_consumed_tab = save_partially_consumed_tab;
1937            if i > 0 {
1938                self.advance_offset(line, 1, true);
1939            }
1940        } else {
1941            nl.padding = *matched + i;
1942        }
1943
1944        nl.marker_offset = self.indent;
1945
1946        if match container.data.borrow().value {
1947            NodeValue::List(ref mnl) => !lists_match(nl, mnl),
1948            _ => true,
1949        } {
1950            *container = self.add_child(container, NodeValue::List(*nl), self.first_nonspace + 1);
1951        }
1952
1953        *container = self.add_child(container, NodeValue::Item(*nl), self.first_nonspace + 1);
1954
1955        true
1956    }
1957
1958    fn detect_code_block(&mut self, indented: bool, maybe_lazy: bool) -> bool {
1959        indented && !maybe_lazy && !self.blank
1960    }
1961
1962    fn handle_code_block(
1963        &mut self,
1964        container: &mut &'a Node<'a, RefCell<Ast>>,
1965        line: &[u8],
1966        indented: bool,
1967        maybe_lazy: bool,
1968    ) -> bool {
1969        if !self.detect_code_block(indented, maybe_lazy) {
1970            return false;
1971        }
1972
1973        self.advance_offset(line, CODE_INDENT, true);
1974        let ncb = NodeCodeBlock {
1975            fenced: false,
1976            fence_char: 0,
1977            fence_length: 0,
1978            fence_offset: 0,
1979            info: String::new(),
1980            literal: String::new(),
1981        };
1982        *container = self.add_child(container, NodeValue::CodeBlock(ncb), self.offset + 1);
1983
1984        true
1985    }
1986
1987    fn open_new_blocks(&mut self, container: &mut &'a AstNode<'a>, line: &[u8], all_matched: bool) {
1988        let mut matched: usize = 0;
1989        let mut nl: NodeList = NodeList::default();
1990        let mut sc: scanners::SetextChar = scanners::SetextChar::Equals;
1991        let mut maybe_lazy = node_matches!(self.current, NodeValue::Paragraph);
1992        let mut depth = 0;
1993
1994        while !node_matches!(
1995            container,
1996            NodeValue::CodeBlock(..) | NodeValue::HtmlBlock(..)
1997        ) {
1998            depth += 1;
1999            self.find_first_nonspace(line);
2000            let indented = self.indent >= CODE_INDENT;
2001
2002            if self.handle_multiline_blockquote(container, line, indented, &mut matched)
2003                || self.handle_blockquote(container, line, indented)
2004                || self.handle_atx_heading(container, line, indented, &mut matched)
2005                || self.handle_code_fence(container, line, indented, &mut matched)
2006                || self.handle_html_block(container, line, indented, &mut matched)
2007                || self.handle_setext_heading(container, line, indented, &mut sc)
2008                || self.handle_thematic_break(container, line, indented, &mut matched, all_matched)
2009                || self.handle_footnote(container, line, indented, &mut matched, depth)
2010                || self.handle_description_list(container, line, indented, &mut matched)
2011                || self.handle_list(container, line, indented, &mut matched, depth, &mut nl)
2012                || self.handle_code_block(container, line, indented, maybe_lazy)
2013            {
2014                // block handled
2015            } else {
2016                let new_container = if !indented && self.options.extension.table {
2017                    table::try_opening_block(self, container, line)
2018                } else {
2019                    None
2020                };
2021
2022                match new_container {
2023                    Some((new_container, replace, mark_visited)) => {
2024                        if replace {
2025                            container.insert_after(new_container);
2026                            container.detach();
2027                            *container = new_container;
2028                        } else {
2029                            *container = new_container;
2030                        }
2031                        if mark_visited {
2032                            container.data.borrow_mut().table_visited = true;
2033                        }
2034                    }
2035                    _ => break,
2036                }
2037            }
2038
2039            if container.data.borrow().value.accepts_lines() {
2040                break;
2041            }
2042
2043            maybe_lazy = false;
2044        }
2045    }
2046
2047    fn advance_offset(&mut self, line: &[u8], mut count: usize, columns: bool) {
2048        while count > 0 {
2049            match line[self.offset] {
2050                9 => {
2051                    let chars_to_tab = TAB_STOP - (self.column % TAB_STOP);
2052                    if columns {
2053                        self.partially_consumed_tab = chars_to_tab > count;
2054                        let chars_to_advance = min(count, chars_to_tab);
2055                        self.column += chars_to_advance;
2056                        self.offset += if self.partially_consumed_tab { 0 } else { 1 };
2057                        count -= chars_to_advance;
2058                    } else {
2059                        self.partially_consumed_tab = false;
2060                        self.column += chars_to_tab;
2061                        self.offset += 1;
2062                        count -= 1;
2063                    }
2064                }
2065                _ => {
2066                    self.partially_consumed_tab = false;
2067                    self.offset += 1;
2068                    self.column += 1;
2069                    count -= 1;
2070                }
2071            }
2072        }
2073    }
2074
2075    fn parse_block_quote_prefix(&mut self, line: &[u8]) -> bool {
2076        let indent = self.indent;
2077        if indent <= 3 && line[self.first_nonspace] == b'>' && self.is_not_greentext(line) {
2078            self.advance_offset(line, indent + 1, true);
2079
2080            if strings::is_space_or_tab(line[self.offset]) {
2081                self.advance_offset(line, 1, true);
2082            }
2083
2084            return true;
2085        }
2086
2087        false
2088    }
2089
2090    fn parse_footnote_definition_block_prefix(&mut self, line: &[u8]) -> bool {
2091        if self.indent >= 4 {
2092            self.advance_offset(line, 4, true);
2093            true
2094        } else {
2095            line == b"\n" || line == b"\r\n"
2096        }
2097    }
2098
2099    fn parse_node_item_prefix(
2100        &mut self,
2101        line: &[u8],
2102        container: &'a AstNode<'a>,
2103        nl: &NodeList,
2104    ) -> bool {
2105        if self.indent >= nl.marker_offset + nl.padding {
2106            self.advance_offset(line, nl.marker_offset + nl.padding, true);
2107            true
2108        } else if self.blank && container.first_child().is_some() {
2109            let offset = self.first_nonspace - self.offset;
2110            self.advance_offset(line, offset, false);
2111            true
2112        } else {
2113            false
2114        }
2115    }
2116
2117    fn parse_description_item_prefix(
2118        &mut self,
2119        line: &[u8],
2120        container: &'a AstNode<'a>,
2121        di: &NodeDescriptionItem,
2122    ) -> bool {
2123        if self.indent >= di.marker_offset + di.padding {
2124            self.advance_offset(line, di.marker_offset + di.padding, true);
2125            true
2126        } else if self.blank && container.first_child().is_some() {
2127            let offset = self.first_nonspace - self.offset;
2128            self.advance_offset(line, offset, false);
2129            true
2130        } else {
2131            false
2132        }
2133    }
2134
2135    fn parse_code_block_prefix(
2136        &mut self,
2137        line: &[u8],
2138        container: &'a AstNode<'a>,
2139        ast: &mut Ast,
2140        should_continue: &mut bool,
2141    ) -> bool {
2142        let (fenced, fence_char, fence_length, fence_offset) = match ast.value {
2143            NodeValue::CodeBlock(ref ncb) => (
2144                ncb.fenced,
2145                ncb.fence_char,
2146                ncb.fence_length,
2147                ncb.fence_offset,
2148            ),
2149            _ => unreachable!(),
2150        };
2151
2152        if !fenced {
2153            if self.indent >= CODE_INDENT {
2154                self.advance_offset(line, CODE_INDENT, true);
2155                return true;
2156            } else if self.blank {
2157                let offset = self.first_nonspace - self.offset;
2158                self.advance_offset(line, offset, false);
2159                return true;
2160            }
2161            return false;
2162        }
2163
2164        let matched = if self.indent <= 3 && line[self.first_nonspace] == fence_char {
2165            scanners::close_code_fence(&line[self.first_nonspace..]).unwrap_or(0)
2166        } else {
2167            0
2168        };
2169
2170        if matched >= fence_length {
2171            *should_continue = false;
2172            self.advance_offset(line, matched, false);
2173            self.current = self.finalize_borrowed(container, ast).unwrap();
2174            return false;
2175        }
2176
2177        let mut i = fence_offset;
2178        while i > 0 && strings::is_space_or_tab(line[self.offset]) {
2179            self.advance_offset(line, 1, true);
2180            i -= 1;
2181        }
2182        true
2183    }
2184
2185    fn parse_html_block_prefix(&mut self, t: u8) -> bool {
2186        match t {
2187            1..=5 => true,
2188            6 | 7 => !self.blank,
2189            _ => unreachable!(),
2190        }
2191    }
2192
2193    fn parse_desc_list_details(&mut self, container: &mut &'a AstNode<'a>, matched: usize) -> bool {
2194        let mut tight = false;
2195        let last_child = match container.last_child() {
2196            Some(lc) => lc,
2197            None => {
2198                // Happens when the detail line is directly after the term,
2199                // without a blank line between.
2200                if !node_matches!(container, NodeValue::Paragraph) {
2201                    // If the container is not a paragraph, then this can't
2202                    // be a description list item.
2203                    return false;
2204                }
2205
2206                let parent = container.parent();
2207                if parent.is_none() {
2208                    return false;
2209                }
2210
2211                tight = true;
2212                *container = parent.unwrap();
2213                container.last_child().unwrap()
2214            }
2215        };
2216
2217        if node_matches!(last_child, NodeValue::Paragraph) {
2218            // We have found the details after the paragraph for the term.
2219            //
2220            // This paragraph is moved as a child of a new DescriptionTerm node.
2221            //
2222            // If the node before the paragraph is a description list, the item
2223            // is added to it. If not, create a new list.
2224
2225            last_child.detach();
2226            let last_child_sourcepos = last_child.data.borrow().sourcepos;
2227
2228            // TODO: description list sourcepos has issues.
2229            //
2230            // DescriptionItem:
2231            //   For all but the last, the end line/col is wrong.
2232            //   Where it should be l:c, it gives (l+1):0.
2233            //
2234            // DescriptionTerm:
2235            //   All are incorrect; they all give the start line/col of
2236            //   the DescriptionDetails, and the end line/col is completely off.
2237            //
2238            // DescriptionDetails:
2239            //   Same as the DescriptionItem.  All but last, the end line/col
2240            //   is (l+1):0.
2241            //
2242            // See crate::tests::description_lists::sourcepos.
2243            let list = match container.last_child() {
2244                Some(lc) if node_matches!(lc, NodeValue::DescriptionList) => {
2245                    reopen_ast_nodes(lc);
2246                    lc
2247                }
2248                _ => {
2249                    let list = self.add_child(
2250                        container,
2251                        NodeValue::DescriptionList,
2252                        self.first_nonspace + 1,
2253                    );
2254                    list.data.borrow_mut().sourcepos.start = last_child_sourcepos.start;
2255                    list
2256                }
2257            };
2258
2259            let metadata = NodeDescriptionItem {
2260                marker_offset: self.indent,
2261                padding: matched,
2262                tight,
2263            };
2264
2265            let item = self.add_child(
2266                list,
2267                NodeValue::DescriptionItem(metadata),
2268                self.first_nonspace + 1,
2269            );
2270            item.data.borrow_mut().sourcepos.start = last_child_sourcepos.start;
2271            let term = self.add_child(item, NodeValue::DescriptionTerm, self.first_nonspace + 1);
2272            let details =
2273                self.add_child(item, NodeValue::DescriptionDetails, self.first_nonspace + 1);
2274
2275            term.append(last_child);
2276
2277            *container = details;
2278
2279            true
2280        } else if node_matches!(last_child, NodeValue::DescriptionItem(..)) {
2281            let parent = last_child.parent().unwrap();
2282            let tight = match last_child.data.borrow().value {
2283                NodeValue::DescriptionItem(ref ndi) => ndi.tight,
2284                _ => false,
2285            };
2286
2287            let metadata = NodeDescriptionItem {
2288                marker_offset: self.indent,
2289                padding: matched,
2290                tight,
2291            };
2292
2293            let item = self.add_child(
2294                parent,
2295                NodeValue::DescriptionItem(metadata),
2296                self.first_nonspace + 1,
2297            );
2298
2299            let details =
2300                self.add_child(item, NodeValue::DescriptionDetails, self.first_nonspace + 1);
2301
2302            *container = details;
2303
2304            true
2305        } else {
2306            false
2307        }
2308    }
2309
2310    fn parse_multiline_block_quote_prefix(
2311        &mut self,
2312        line: &[u8],
2313        container: &'a AstNode<'a>,
2314        ast: &mut Ast,
2315        should_continue: &mut bool,
2316    ) -> bool {
2317        let (fence_length, fence_offset) = match ast.value {
2318            NodeValue::MultilineBlockQuote(ref node_value) => {
2319                (node_value.fence_length, node_value.fence_offset)
2320            }
2321            _ => unreachable!(),
2322        };
2323
2324        let matched = if self.indent <= 3 && line[self.first_nonspace] == b'>' {
2325            scanners::close_multiline_block_quote_fence(&line[self.first_nonspace..]).unwrap_or(0)
2326        } else {
2327            0
2328        };
2329
2330        if matched >= fence_length {
2331            *should_continue = false;
2332            self.advance_offset(line, matched, false);
2333
2334            // The last child, like an indented codeblock, could be left open.
2335            // Make sure it's finalized.
2336            if nodes::last_child_is_open(container) {
2337                let child = container.last_child().unwrap();
2338                let child_ast = &mut *child.data.borrow_mut();
2339
2340                self.finalize_borrowed(child, child_ast).unwrap();
2341            }
2342
2343            self.current = self.finalize_borrowed(container, ast).unwrap();
2344            return false;
2345        }
2346
2347        let mut i = fence_offset;
2348        while i > 0 && strings::is_space_or_tab(line[self.offset]) {
2349            self.advance_offset(line, 1, true);
2350            i -= 1;
2351        }
2352        true
2353    }
2354
2355    fn add_child(
2356        &mut self,
2357        mut parent: &'a AstNode<'a>,
2358        value: NodeValue,
2359        start_column: usize,
2360    ) -> &'a AstNode<'a> {
2361        while !nodes::can_contain_type(parent, &value) {
2362            parent = self.finalize(parent).unwrap();
2363        }
2364
2365        assert!(start_column > 0);
2366
2367        let child = Ast::new(value, (self.line_number, start_column).into());
2368        let node = self.arena.alloc(Node::new(RefCell::new(child)));
2369        parent.append(node);
2370        node
2371    }
2372
2373    fn add_text_to_container(
2374        &mut self,
2375        mut container: &'a AstNode<'a>,
2376        last_matched_container: &'a AstNode<'a>,
2377        line: &[u8],
2378    ) {
2379        self.find_first_nonspace(line);
2380
2381        if self.blank {
2382            if let Some(last_child) = container.last_child() {
2383                last_child.data.borrow_mut().last_line_blank = true;
2384            }
2385        }
2386
2387        container.data.borrow_mut().last_line_blank = self.blank
2388            && match container.data.borrow().value {
2389                NodeValue::BlockQuote | NodeValue::Heading(..) | NodeValue::ThematicBreak => false,
2390                NodeValue::CodeBlock(ref ncb) => !ncb.fenced,
2391                NodeValue::Item(..) => {
2392                    container.first_child().is_some()
2393                        || container.data.borrow().sourcepos.start.line != self.line_number
2394                }
2395                NodeValue::MultilineBlockQuote(..) => false,
2396                _ => true,
2397            };
2398
2399        let mut tmp = container;
2400        while let Some(parent) = tmp.parent() {
2401            parent.data.borrow_mut().last_line_blank = false;
2402            tmp = parent;
2403        }
2404
2405        if !self.current.same_node(last_matched_container)
2406            && container.same_node(last_matched_container)
2407            && !self.blank
2408            && (!self.options.extension.greentext
2409                || !matches!(
2410                    container.data.borrow().value,
2411                    NodeValue::BlockQuote | NodeValue::Document
2412                ))
2413            && node_matches!(self.current, NodeValue::Paragraph)
2414        {
2415            self.add_line(self.current, line);
2416        } else {
2417            while !self.current.same_node(last_matched_container) {
2418                self.current = self.finalize(self.current).unwrap();
2419            }
2420
2421            let add_text_result = match container.data.borrow().value {
2422                NodeValue::CodeBlock(..) => AddTextResult::LiteralText,
2423                NodeValue::HtmlBlock(ref nhb) => AddTextResult::HtmlBlock(nhb.block_type),
2424                _ => AddTextResult::Otherwise,
2425            };
2426
2427            match add_text_result {
2428                AddTextResult::LiteralText => {
2429                    self.add_line(container, line);
2430                }
2431                AddTextResult::HtmlBlock(block_type) => {
2432                    self.add_line(container, line);
2433
2434                    let matches_end_condition = match block_type {
2435                        1 => scanners::html_block_end_1(&line[self.first_nonspace..]),
2436                        2 => scanners::html_block_end_2(&line[self.first_nonspace..]),
2437                        3 => scanners::html_block_end_3(&line[self.first_nonspace..]),
2438                        4 => scanners::html_block_end_4(&line[self.first_nonspace..]),
2439                        5 => scanners::html_block_end_5(&line[self.first_nonspace..]),
2440                        _ => false,
2441                    };
2442
2443                    if matches_end_condition {
2444                        container = self.finalize(container).unwrap();
2445                    }
2446                }
2447                _ => {
2448                    if self.blank {
2449                        // do nothing
2450                    } else if container.data.borrow().value.accepts_lines() {
2451                        let mut line: Vec<u8> = line.into();
2452                        if let NodeValue::Heading(ref nh) = container.data.borrow().value {
2453                            if !nh.setext {
2454                                strings::chop_trailing_hashtags(&mut line);
2455                            }
2456                        };
2457                        let count = self.first_nonspace - self.offset;
2458
2459                        // In a rare case the above `chop` operation can leave
2460                        // the line shorter than the recorded `first_nonspace`
2461                        // This happens with ATX headers containing no header
2462                        // text, multiple spaces and trailing hashes, e.g
2463                        //
2464                        // ###     ###
2465                        //
2466                        // In this case `first_nonspace` indexes into the second
2467                        // set of hashes, while `chop_trailing_hashtags` truncates
2468                        // `line` to just `###` (the first three hashes).
2469                        // In this case there's no text to add, and no further
2470                        // processing to be done.
2471                        let have_line_text = self.first_nonspace <= line.len();
2472
2473                        if have_line_text {
2474                            self.advance_offset(&line, count, false);
2475                            self.add_line(container, &line);
2476                        }
2477                    } else {
2478                        container = self.add_child(
2479                            container,
2480                            NodeValue::Paragraph,
2481                            self.first_nonspace + 1,
2482                        );
2483                        let count = self.first_nonspace - self.offset;
2484                        self.advance_offset(line, count, false);
2485                        self.add_line(container, line);
2486                    }
2487                }
2488            }
2489
2490            self.current = container;
2491        }
2492    }
2493
2494    fn add_line(&mut self, node: &'a AstNode<'a>, line: &[u8]) {
2495        let mut ast = node.data.borrow_mut();
2496        assert!(ast.open);
2497        if self.partially_consumed_tab {
2498            self.offset += 1;
2499            let chars_to_tab = TAB_STOP - (self.column % TAB_STOP);
2500            for _ in 0..chars_to_tab {
2501                ast.content.push(' ');
2502            }
2503        }
2504        if self.offset < line.len() {
2505            // since whitespace is stripped off the beginning of lines, we need to keep
2506            // track of how much was stripped off. This allows us to properly calculate
2507            // inline sourcepos during inline processing.
2508            ast.line_offsets.push(self.offset);
2509
2510            ast.content
2511                .push_str(str::from_utf8(&line[self.offset..]).unwrap());
2512        }
2513    }
2514
2515    fn finish(&mut self, remaining: Vec<u8>) -> &'a AstNode<'a> {
2516        if !remaining.is_empty() {
2517            self.process_line(&remaining);
2518        }
2519
2520        self.finalize_document();
2521        self.postprocess_text_nodes(self.root);
2522        self.root
2523    }
2524
2525    fn finalize_document(&mut self) {
2526        while !self.current.same_node(self.root) {
2527            self.current = self.finalize(self.current).unwrap();
2528        }
2529
2530        self.finalize(self.root);
2531
2532        self.refmap.max_ref_size = if self.total_size > 100000 {
2533            self.total_size
2534        } else {
2535            100000
2536        };
2537
2538        self.process_inlines();
2539        if self.options.extension.footnotes {
2540            self.process_footnotes();
2541        }
2542    }
2543
2544    fn finalize(&mut self, node: &'a AstNode<'a>) -> Option<&'a AstNode<'a>> {
2545        self.finalize_borrowed(node, &mut node.data.borrow_mut())
2546    }
2547
2548    fn resolve_reference_link_definitions(&mut self, content: &mut String) -> bool {
2549        let mut seeked = 0;
2550        {
2551            let mut pos = 0;
2552            let mut seek: &[u8] = content.as_bytes();
2553            while !seek.is_empty()
2554                && seek[0] == b'['
2555                && unwrap_into(self.parse_reference_inline(seek), &mut pos)
2556            {
2557                seek = &seek[pos..];
2558                seeked += pos;
2559            }
2560        }
2561
2562        if seeked != 0 {
2563            *content = content[seeked..].to_string();
2564        }
2565
2566        !strings::is_blank(content.as_bytes())
2567    }
2568
2569    fn finalize_borrowed(
2570        &mut self,
2571        node: &'a AstNode<'a>,
2572        ast: &mut Ast,
2573    ) -> Option<&'a AstNode<'a>> {
2574        assert!(ast.open);
2575        ast.open = false;
2576
2577        let content = &mut ast.content;
2578        let parent = node.parent();
2579
2580        if self.curline_len == 0 {
2581            ast.sourcepos.end = (self.line_number, self.last_line_length).into();
2582        } else if match ast.value {
2583            NodeValue::Document => true,
2584            NodeValue::CodeBlock(ref ncb) => ncb.fenced,
2585            NodeValue::MultilineBlockQuote(..) => true,
2586            _ => false,
2587        } {
2588            ast.sourcepos.end = (self.line_number, self.curline_end_col).into();
2589        } else {
2590            ast.sourcepos.end = (self.line_number - 1, self.last_line_length).into();
2591        }
2592
2593        match ast.value {
2594            NodeValue::Paragraph => {
2595                let has_content = self.resolve_reference_link_definitions(content);
2596                if !has_content {
2597                    node.detach();
2598                }
2599            }
2600            NodeValue::CodeBlock(ref mut ncb) => {
2601                if !ncb.fenced {
2602                    strings::remove_trailing_blank_lines(content);
2603                    content.push('\n');
2604                } else {
2605                    let mut pos = 0;
2606                    while pos < content.len() {
2607                        if strings::is_line_end_char(content.as_bytes()[pos]) {
2608                            break;
2609                        }
2610                        pos += 1;
2611                    }
2612                    assert!(pos < content.len());
2613
2614                    let mut tmp = entity::unescape_html(&content.as_bytes()[..pos]);
2615                    strings::trim(&mut tmp);
2616                    strings::unescape(&mut tmp);
2617                    if tmp.is_empty() {
2618                        ncb.info = self
2619                            .options
2620                            .parse
2621                            .default_info_string
2622                            .as_ref()
2623                            .map_or(String::new(), |s| s.clone());
2624                    } else {
2625                        ncb.info = String::from_utf8(tmp).unwrap();
2626                    }
2627
2628                    if content.as_bytes()[pos] == b'\r' {
2629                        pos += 1;
2630                    }
2631                    if content.as_bytes()[pos] == b'\n' {
2632                        pos += 1;
2633                    }
2634
2635                    content.drain(..pos);
2636                }
2637                mem::swap(&mut ncb.literal, content);
2638            }
2639            NodeValue::HtmlBlock(ref mut nhb) => {
2640                mem::swap(&mut nhb.literal, content);
2641            }
2642            NodeValue::List(ref mut nl) => {
2643                nl.tight = true;
2644                let mut ch = node.first_child();
2645
2646                while let Some(item) = ch {
2647                    if item.data.borrow().last_line_blank && item.next_sibling().is_some() {
2648                        nl.tight = false;
2649                        break;
2650                    }
2651
2652                    let mut subch = item.first_child();
2653                    while let Some(subitem) = subch {
2654                        if (item.next_sibling().is_some() || subitem.next_sibling().is_some())
2655                            && nodes::ends_with_blank_line(subitem)
2656                        {
2657                            nl.tight = false;
2658                            break;
2659                        }
2660                        subch = subitem.next_sibling();
2661                    }
2662
2663                    if !nl.tight {
2664                        break;
2665                    }
2666
2667                    ch = item.next_sibling();
2668                }
2669            }
2670            _ => (),
2671        }
2672
2673        parent
2674    }
2675
2676    fn process_inlines(&mut self) {
2677        self.process_inlines_node(self.root);
2678    }
2679
2680    fn process_inlines_node(&mut self, node: &'a AstNode<'a>) {
2681        for node in node.descendants() {
2682            if node.data.borrow().value.contains_inlines() {
2683                self.parse_inlines(node);
2684            }
2685        }
2686    }
2687
2688    fn parse_inlines(&mut self, node: &'a AstNode<'a>) {
2689        let delimiter_arena = Arena::new();
2690        let node_data = node.data.borrow();
2691        let content = strings::rtrim_slice(node_data.content.as_bytes());
2692        let mut subj = inlines::Subject::new(
2693            self.arena,
2694            self.options,
2695            content,
2696            node_data.sourcepos.start.line,
2697            &mut self.refmap,
2698            &delimiter_arena,
2699        );
2700
2701        while subj.parse_inline(node) {}
2702
2703        subj.process_emphasis(0);
2704
2705        while subj.pop_bracket() {}
2706    }
2707
2708    fn process_footnotes(&mut self) {
2709        let mut map = HashMap::new();
2710        Self::find_footnote_definitions(self.root, &mut map);
2711
2712        let mut ix = 0;
2713        Self::find_footnote_references(self.root, &mut map, &mut ix);
2714
2715        if !map.is_empty() {
2716            // In order for references to be found inside footnote definitions,
2717            // such as `[^1]: another reference[^2]`,
2718            // the node needed to remain in the AST. Now we can remove them.
2719            Self::cleanup_footnote_definitions(self.root);
2720        }
2721
2722        if ix > 0 {
2723            let mut v = map.into_values().collect::<Vec<_>>();
2724            v.sort_unstable_by(|a, b| a.ix.cmp(&b.ix));
2725            for f in v {
2726                if f.ix.is_some() {
2727                    match f.node.data.borrow_mut().value {
2728                        NodeValue::FootnoteDefinition(ref mut nfd) => {
2729                            nfd.name = f.name.to_string();
2730                            nfd.total_references = f.total_references;
2731                        }
2732                        _ => unreachable!(),
2733                    }
2734                    self.root.append(f.node);
2735                }
2736            }
2737        }
2738    }
2739
2740    fn find_footnote_definitions(
2741        node: &'a AstNode<'a>,
2742        map: &mut HashMap<String, FootnoteDefinition<'a>>,
2743    ) {
2744        match node.data.borrow().value {
2745            NodeValue::FootnoteDefinition(ref nfd) => {
2746                map.insert(
2747                    strings::normalize_label(&nfd.name, Case::Fold),
2748                    FootnoteDefinition {
2749                        ix: None,
2750                        node,
2751                        name: strings::normalize_label(&nfd.name, Case::Preserve),
2752                        total_references: 0,
2753                    },
2754                );
2755            }
2756            _ => {
2757                for n in node.children() {
2758                    Self::find_footnote_definitions(n, map);
2759                }
2760            }
2761        }
2762    }
2763
2764    fn find_footnote_references(
2765        node: &'a AstNode<'a>,
2766        map: &mut HashMap<String, FootnoteDefinition>,
2767        ixp: &mut u32,
2768    ) {
2769        let mut ast = node.data.borrow_mut();
2770        let mut replace = None;
2771        match ast.value {
2772            NodeValue::FootnoteReference(ref mut nfr) => {
2773                let normalized = strings::normalize_label(&nfr.name, Case::Fold);
2774                if let Some(ref mut footnote) = map.get_mut(&normalized) {
2775                    let ix = match footnote.ix {
2776                        Some(ix) => ix,
2777                        None => {
2778                            *ixp += 1;
2779                            footnote.ix = Some(*ixp);
2780                            *ixp
2781                        }
2782                    };
2783                    footnote.total_references += 1;
2784                    nfr.ref_num = footnote.total_references;
2785                    nfr.ix = ix;
2786                    nfr.name = strings::normalize_label(&footnote.name, Case::Preserve);
2787                } else {
2788                    replace = Some(nfr.name.clone());
2789                }
2790            }
2791            _ => {
2792                for n in node.children() {
2793                    Self::find_footnote_references(n, map, ixp);
2794                }
2795            }
2796        }
2797
2798        if let Some(mut label) = replace {
2799            label.insert_str(0, "[^");
2800            label.push(']');
2801            ast.value = NodeValue::Text(label);
2802        }
2803    }
2804
2805    fn cleanup_footnote_definitions(node: &'a AstNode<'a>) {
2806        match node.data.borrow().value {
2807            NodeValue::FootnoteDefinition(_) => {
2808                node.detach();
2809            }
2810            _ => {
2811                for n in node.children() {
2812                    Self::cleanup_footnote_definitions(n);
2813                }
2814            }
2815        }
2816    }
2817
2818    fn postprocess_text_nodes(&mut self, node: &'a AstNode<'a>) {
2819        let mut stack = vec![node];
2820        let mut children = vec![];
2821
2822        while let Some(node) = stack.pop() {
2823            let mut nch = node.first_child();
2824
2825            while let Some(n) = nch {
2826                let mut this_bracket = false;
2827                let n_ast = &mut n.data.borrow_mut();
2828                let mut sourcepos = n_ast.sourcepos;
2829
2830                loop {
2831                    match n_ast.value {
2832                        // Join adjacent text nodes together
2833                        NodeValue::Text(ref mut root) => {
2834                            let ns = match n.next_sibling() {
2835                                Some(ns) => ns,
2836                                _ => {
2837                                    // Post-process once we are finished joining text nodes
2838                                    self.postprocess_text_node(n, root, &mut sourcepos);
2839                                    break;
2840                                }
2841                            };
2842
2843                            match ns.data.borrow().value {
2844                                NodeValue::Text(ref adj) => {
2845                                    root.push_str(adj);
2846                                    sourcepos.end.column = ns.data.borrow().sourcepos.end.column;
2847                                    ns.detach();
2848                                }
2849                                _ => {
2850                                    // Post-process once we are finished joining text nodes
2851                                    self.postprocess_text_node(n, root, &mut sourcepos);
2852                                    break;
2853                                }
2854                            }
2855                        }
2856                        NodeValue::Link(..) | NodeValue::Image(..) | NodeValue::WikiLink(..) => {
2857                            this_bracket = true;
2858                            break;
2859                        }
2860                        _ => break,
2861                    }
2862                }
2863
2864                n_ast.sourcepos = sourcepos;
2865
2866                if !this_bracket {
2867                    children.push(n);
2868                }
2869
2870                nch = n.next_sibling();
2871            }
2872
2873            // Push children onto work stack in reverse order so they are
2874            // traversed in order
2875            stack.extend(children.drain(..).rev());
2876        }
2877    }
2878
2879    fn postprocess_text_node(
2880        &mut self,
2881        node: &'a AstNode<'a>,
2882        text: &mut String,
2883        sourcepos: &mut Sourcepos,
2884    ) {
2885        if self.options.extension.tasklist {
2886            self.process_tasklist(node, text, sourcepos);
2887        }
2888
2889        if self.options.extension.autolink {
2890            autolink::process_autolinks(
2891                self.arena,
2892                node,
2893                text,
2894                self.options.parse.relaxed_autolinks,
2895            );
2896        }
2897    }
2898
2899    fn process_tasklist(
2900        &mut self,
2901        node: &'a AstNode<'a>,
2902        text: &mut String,
2903        sourcepos: &mut Sourcepos,
2904    ) {
2905        let (end, symbol) = match scanners::tasklist(text.as_bytes()) {
2906            Some(p) => p,
2907            None => return,
2908        };
2909
2910        let symbol = symbol as char;
2911
2912        if !self.options.parse.relaxed_tasklist_matching && !matches!(symbol, ' ' | 'x' | 'X') {
2913            return;
2914        }
2915
2916        let parent = node.parent().unwrap();
2917        if node.previous_sibling().is_some() || parent.previous_sibling().is_some() {
2918            return;
2919        }
2920
2921        if !node_matches!(parent, NodeValue::Paragraph) {
2922            return;
2923        }
2924
2925        let grandparent = parent.parent().unwrap();
2926        if !node_matches!(grandparent, NodeValue::Item(..)) {
2927            return;
2928        }
2929
2930        let great_grandparent = grandparent.parent().unwrap();
2931        if !node_matches!(great_grandparent, NodeValue::List(..)) {
2932            return;
2933        }
2934
2935        text.drain(..end);
2936
2937        // These are sound only because the exact text that we've matched and
2938        // the count thereof (i.e. "end") will precisely map to characters in
2939        // the source document.
2940        sourcepos.start.column += end;
2941        parent.data.borrow_mut().sourcepos.start.column += end;
2942
2943        grandparent.data.borrow_mut().value =
2944            NodeValue::TaskItem(if symbol == ' ' { None } else { Some(symbol) });
2945
2946        if let NodeValue::List(ref mut list) = &mut great_grandparent.data.borrow_mut().value {
2947            list.is_task_list = true;
2948        }
2949    }
2950
2951    fn parse_reference_inline(&mut self, content: &[u8]) -> Option<usize> {
2952        // In this case reference inlines rarely have delimiters
2953        // so we often just need the minimal case
2954        let delimiter_arena = Arena::with_capacity(0);
2955        let mut subj = inlines::Subject::new(
2956            self.arena,
2957            self.options,
2958            content,
2959            0, // XXX -1 in upstream; never used?
2960            &mut self.refmap,
2961            &delimiter_arena,
2962        );
2963
2964        let mut lab: String = match subj.link_label() {
2965            Some(lab) if !lab.is_empty() => lab.to_string(),
2966            _ => return None,
2967        };
2968
2969        if subj.peek_char() != Some(&(b':')) {
2970            return None;
2971        }
2972
2973        subj.pos += 1;
2974        subj.spnl();
2975        let (url, matchlen) = match inlines::manual_scan_link_url(&subj.input[subj.pos..]) {
2976            Some((url, matchlen)) => (url, matchlen),
2977            None => return None,
2978        };
2979        subj.pos += matchlen;
2980
2981        let beforetitle = subj.pos;
2982        subj.spnl();
2983        let title_search = if subj.pos == beforetitle {
2984            None
2985        } else {
2986            scanners::link_title(&subj.input[subj.pos..])
2987        };
2988        let title = match title_search {
2989            Some(matchlen) => {
2990                let t = &subj.input[subj.pos..subj.pos + matchlen];
2991                subj.pos += matchlen;
2992                t.to_vec()
2993            }
2994            _ => {
2995                subj.pos = beforetitle;
2996                vec![]
2997            }
2998        };
2999
3000        subj.skip_spaces();
3001        if !subj.skip_line_end() {
3002            if !title.is_empty() {
3003                subj.pos = beforetitle;
3004                subj.skip_spaces();
3005                if !subj.skip_line_end() {
3006                    return None;
3007                }
3008            } else {
3009                return None;
3010            }
3011        }
3012
3013        lab = strings::normalize_label(&lab, Case::Fold);
3014        if !lab.is_empty() {
3015            subj.refmap.map.entry(lab).or_insert(ResolvedReference {
3016                url: String::from_utf8(strings::clean_url(url)).unwrap(),
3017                title: String::from_utf8(strings::clean_title(&title)).unwrap(),
3018            });
3019        }
3020        Some(subj.pos)
3021    }
3022}
3023
3024enum AddTextResult {
3025    LiteralText,
3026    HtmlBlock(u8),
3027    Otherwise,
3028}
3029
3030fn parse_list_marker(
3031    line: &[u8],
3032    mut pos: usize,
3033    interrupts_paragraph: bool,
3034) -> Option<(usize, NodeList)> {
3035    let mut c = line[pos];
3036    let startpos = pos;
3037
3038    if c == b'*' || c == b'-' || c == b'+' {
3039        pos += 1;
3040        if !isspace(line[pos]) {
3041            return None;
3042        }
3043
3044        if interrupts_paragraph {
3045            let mut i = pos;
3046            while strings::is_space_or_tab(line[i]) {
3047                i += 1;
3048            }
3049            if line[i] == b'\n' {
3050                return None;
3051            }
3052        }
3053
3054        return Some((
3055            pos - startpos,
3056            NodeList {
3057                list_type: ListType::Bullet,
3058                marker_offset: 0,
3059                padding: 0,
3060                start: 1,
3061                delimiter: ListDelimType::Period,
3062                bullet_char: c,
3063                tight: false,
3064                is_task_list: false,
3065            },
3066        ));
3067    } else if isdigit(c) {
3068        let mut start: usize = 0;
3069        let mut digits = 0;
3070
3071        loop {
3072            start = (10 * start) + (line[pos] - b'0') as usize;
3073            pos += 1;
3074            digits += 1;
3075
3076            if !(digits < 9 && isdigit(line[pos])) {
3077                break;
3078            }
3079        }
3080
3081        if interrupts_paragraph && start != 1 {
3082            return None;
3083        }
3084
3085        c = line[pos];
3086        if c != b'.' && c != b')' {
3087            return None;
3088        }
3089
3090        pos += 1;
3091
3092        if !isspace(line[pos]) {
3093            return None;
3094        }
3095
3096        if interrupts_paragraph {
3097            let mut i = pos;
3098            while strings::is_space_or_tab(line[i]) {
3099                i += 1;
3100            }
3101            if strings::is_line_end_char(line[i]) {
3102                return None;
3103            }
3104        }
3105
3106        return Some((
3107            pos - startpos,
3108            NodeList {
3109                list_type: ListType::Ordered,
3110                marker_offset: 0,
3111                padding: 0,
3112                start,
3113                delimiter: if c == b'.' {
3114                    ListDelimType::Period
3115                } else {
3116                    ListDelimType::Paren
3117                },
3118                bullet_char: 0,
3119                tight: false,
3120                is_task_list: false,
3121            },
3122        ));
3123    }
3124
3125    None
3126}
3127
3128pub fn unwrap_into<T>(t: Option<T>, out: &mut T) -> bool {
3129    match t {
3130        Some(v) => {
3131            *out = v;
3132            true
3133        }
3134        _ => false,
3135    }
3136}
3137
3138pub fn unwrap_into_copy<T: Copy>(t: Option<&T>, out: &mut T) -> bool {
3139    match t {
3140        Some(v) => {
3141            *out = *v;
3142            true
3143        }
3144        _ => false,
3145    }
3146}
3147
3148fn unwrap_into_2<T, U>(tu: Option<(T, U)>, out_t: &mut T, out_u: &mut U) -> bool {
3149    match tu {
3150        Some((t, u)) => {
3151            *out_t = t;
3152            *out_u = u;
3153            true
3154        }
3155        _ => false,
3156    }
3157}
3158
3159fn lists_match(list_data: &NodeList, item_data: &NodeList) -> bool {
3160    list_data.list_type == item_data.list_type
3161        && list_data.delimiter == item_data.delimiter
3162        && list_data.bullet_char == item_data.bullet_char
3163}
3164
3165fn reopen_ast_nodes<'a>(mut ast: &'a AstNode<'a>) {
3166    loop {
3167        ast.data.borrow_mut().open = true;
3168        ast = match ast.parent() {
3169            Some(p) => p,
3170            None => return,
3171        }
3172    }
3173}
3174
3175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3176pub enum AutolinkType {
3177    Uri,
3178    Email,
3179}
3180
3181#[derive(Debug, Clone, Copy, Default)]
3182#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
3183/// Options for bulleted list redering in markdown. See `link_style` in [`RenderOptions`] for more details.
3184pub enum ListStyleType {
3185    /// The `-` character
3186    #[default]
3187    Dash = 45,
3188    /// The `+` character
3189    Plus = 43,
3190    /// The `*` character
3191    Star = 42,
3192}