aboutsummaryrefslogtreecommitdiffstats
path: root/src/compiler.rs
diff options
context:
space:
mode:
authorLibravatar Titus Wormer <tituswormer@gmail.com>2022-09-19 17:29:59 +0200
committerLibravatar Titus Wormer <tituswormer@gmail.com>2022-09-19 17:29:59 +0200
commit9cb9e37c33173c16cbafd345f43e43b5a550537d (patch)
tree6b02d2ea2c3ba904887b01f88aee369bd1a026d8 /src/compiler.rs
parentdfe1f2da522afa3e86680fa763b2f3c75e86b3ca (diff)
downloadmarkdown-rs-9cb9e37c33173c16cbafd345f43e43b5a550537d.tar.gz
markdown-rs-9cb9e37c33173c16cbafd345f43e43b5a550537d.tar.bz2
markdown-rs-9cb9e37c33173c16cbafd345f43e43b5a550537d.zip
Add structs, enums for `mdast`
Diffstat (limited to 'src/compiler.rs')
-rw-r--r--src/compiler.rs1924
1 files changed, 0 insertions, 1924 deletions
diff --git a/src/compiler.rs b/src/compiler.rs
deleted file mode 100644
index eaa15ee..0000000
--- a/src/compiler.rs
+++ /dev/null
@@ -1,1924 +0,0 @@
-//! Turn events into a string of HTML.
-use crate::event::{Event, Kind, Name};
-use crate::util::{
- constant::{SAFE_PROTOCOL_HREF, SAFE_PROTOCOL_SRC},
- decode_character_reference::{decode_named, decode_numeric},
- encode::encode,
- gfm_tagfilter::gfm_tagfilter,
- normalize_identifier::normalize_identifier,
- sanitize_uri::{sanitize, sanitize_with_protocols},
- skip,
- slice::{Position, Slice},
-};
-use crate::{LineEnding, Options};
-use alloc::{
- format,
- string::{String, ToString},
- vec,
- vec::Vec,
-};
-use core::str;
-
-/// Link, image, or footnote call.
-/// Resource or reference.
-/// Reused for temporary definitions as well, in the first pass.
-#[derive(Debug)]
-struct Media {
- /// Whether this represents an image (`true`) or a link or definition
- /// (`false`).
- image: bool,
- /// The text between the brackets (`x` in `![x]()` and `[x]()`).
- ///
- /// Not interpreted.
- label_id: Option<(usize, usize)>,
- /// The result of interpreting the text between the brackets
- /// (`x` in `![x]()` and `[x]()`).
- ///
- /// When this is a link, it contains further text content and thus HTML
- /// tags.
- /// Otherwise, when an image, text content is also allowed, but resulting
- /// tags are ignored.
- label: Option<String>,
- /// The string between the explicit brackets of the reference (`y` in
- /// `[x][y]`), as content.
- ///
- /// Not interpreted.
- reference_id: Option<(usize, usize)>,
- /// The destination (url).
- ///
- /// Interpreted string content.
- destination: Option<String>,
- /// The destination (url).
- ///
- /// Interpreted string content.
- title: Option<String>,
-}
-
-/// Representation of a definition.
-#[derive(Debug)]
-struct Definition {
- /// Identifier.
- id: String,
- /// The destination (url).
- ///
- /// Interpreted string content.
- destination: Option<String>,
- /// The title.
- ///
- /// Interpreted string content.
- title: Option<String>,
-}
-
-/// GFM table: column alignment.
-#[derive(Debug, PartialEq, Eq, Copy, Clone)]
-enum GfmTableAlign {
- /// No alignment.
- ///
- /// ```markdown
- /// | | aaa |
- /// > | | --- |
- /// ^^^
- /// ```
- None,
- /// Left alignment.
- ///
- /// ```markdown
- /// | | aaa |
- /// > | | :-- |
- /// ^^^
- /// ```
- Left,
- /// Center alignment.
- ///
- /// ```markdown
- /// | | aaa |
- /// > | | :-: |
- /// ^^^
- /// ```
- Center,
- /// Right alignment.
- ///
- /// ```markdown
- /// | | aaa |
- /// > | | --: |
- /// ^^^
- /// ```
- Right,
-}
-
-/// Context used to compile markdown.
-#[allow(clippy::struct_excessive_bools)]
-#[derive(Debug)]
-struct CompileContext<'a> {
- // Static info.
- /// List of events.
- pub events: &'a [Event],
- /// List of bytes.
- pub bytes: &'a [u8],
- /// Configuration.
- pub options: &'a Options,
- // Fields used by handlers to track the things they need to track to
- // compile markdown.
- /// Rank of heading (atx).
- pub heading_atx_rank: Option<usize>,
- /// Buffer of heading (setext) text.
- pub heading_setext_buffer: Option<String>,
- /// Whether raw (flow) (code (fenced), math (flow)) or code (indented) contains data.
- pub raw_flow_seen_data: Option<bool>,
- /// Number of raw (flow) fences.
- pub raw_flow_fences_count: Option<usize>,
- /// Whether we are in code (text).
- pub raw_text_inside: bool,
- /// Whether we are in image text.
- pub image_alt_inside: bool,
- /// Marker of character reference.
- pub character_reference_marker: Option<u8>,
- /// Whether we are expecting the first list item marker.
- pub list_expect_first_marker: Option<bool>,
- /// Stack of media (link, image).
- pub media_stack: Vec<Media>,
- /// Stack of containers.
- pub tight_stack: Vec<bool>,
- /// List of definitions.
- pub definitions: Vec<Definition>,
- /// List of definitions.
- pub gfm_footnote_definitions: Vec<(String, String)>,
- pub gfm_footnote_definition_calls: Vec<(String, usize)>,
- pub gfm_footnote_definition_stack: Vec<(usize, usize)>,
- /// Whether we are in a GFM table head.
- pub gfm_table_in_head: bool,
- /// Current GFM table alignment.
- pub gfm_table_align: Option<Vec<GfmTableAlign>>,
- /// Current GFM table column.
- pub gfm_table_column: usize,
- // Fields used to influance the current compilation.
- /// Ignore the next line ending.
- pub slurp_one_line_ending: bool,
- /// Whether to encode HTML.
- pub encode_html: bool,
- // Configuration
- /// Line ending to use.
- pub line_ending_default: LineEnding,
- // Intermediate results.
- /// Stack of buffers.
- pub buffers: Vec<String>,
- /// Current event index.
- pub index: usize,
-}
-
-impl<'a> CompileContext<'a> {
- /// Create a new compile context.
- pub fn new(
- events: &'a [Event],
- bytes: &'a [u8],
- options: &'a Options,
- line_ending: LineEnding,
- ) -> CompileContext<'a> {
- CompileContext {
- events,
- bytes,
- heading_atx_rank: None,
- heading_setext_buffer: None,
- raw_flow_seen_data: None,
- raw_flow_fences_count: None,
- raw_text_inside: false,
- character_reference_marker: None,
- list_expect_first_marker: None,
- media_stack: vec![],
- definitions: vec![],
- gfm_footnote_definitions: vec![],
- gfm_footnote_definition_calls: vec![],
- gfm_footnote_definition_stack: vec![],
- gfm_table_in_head: false,
- gfm_table_align: None,
- gfm_table_column: 0,
- tight_stack: vec![],
- slurp_one_line_ending: false,
- image_alt_inside: false,
- encode_html: true,
- line_ending_default: line_ending,
- buffers: vec![String::new()],
- index: 0,
- options,
- }
- }
-
- /// Push a buffer.
- pub fn buffer(&mut self) {
- self.buffers.push(String::new());
- }
-
- /// Pop a buffer, returning its value.
- pub fn resume(&mut self) -> String {
- self.buffers.pop().expect("Cannot resume w/o buffer")
- }
-
- /// Push a str to the last buffer.
- pub fn push(&mut self, value: &str) {
- self.buffers
- .last_mut()
- .expect("Cannot push w/o buffer")
- .push_str(value);
- }
-
- /// Add a line ending.
- pub fn line_ending(&mut self) {
- let eol = self.line_ending_default.as_str().to_string();
- self.push(&eol);
- }
-
- /// Add a line ending if needed (as in, there’s no eol/eof already).
- pub fn line_ending_if_needed(&mut self) {
- let tail = self
- .buffers
- .last()
- .expect("at least one buffer should exist")
- .as_bytes()
- .last();
-
- if !matches!(tail, None | Some(b'\n' | b'\r')) {
- self.line_ending();
- }
- }
-}
-
-/// Turn events and codes into a string of HTML.
-pub fn compile(events: &[Event], bytes: &[u8], options: &Options) -> String {
- let mut index = 0;
- let mut line_ending_inferred = None;
-
- // First, we figure out what the used line ending style is.
- // Stop when we find a line ending.
- while index < events.len() {
- let event = &events[index];
-
- if event.kind == Kind::Exit
- && (event.name == Name::BlankLineEnding || event.name == Name::LineEnding)
- {
- line_ending_inferred = Some(LineEnding::from_str(
- Slice::from_position(bytes, &Position::from_exit_event(events, index)).as_str(),
- ));
- break;
- }
-
- index += 1;
- }
-
- // Figure out which line ending style we’ll use.
- let line_ending_default = if let Some(value) = line_ending_inferred {
- value
- } else {
- options.default_line_ending.clone()
- };
-
- let mut context = CompileContext::new(events, bytes, options, line_ending_default);
- let mut definition_indices = vec![];
- let mut index = 0;
- let mut definition_inside = false;
-
- // Handle all definitions first.
- // We must do two passes because we need to compile the events in
- // definitions which come after references already.
- //
- // To speed things up, we collect the places we can jump over for the
- // second pass.
- //
- // We don’t need to handle GFM footnote definitions like this, because
- // unlike normal definitions, what they produce is not used in calls.
- // It would also get very complex, because footnote definitions can be
- // nested.
- while index < events.len() {
- let event = &events[index];
-
- if definition_inside {
- handle(&mut context, index);
- }
-
- if event.kind == Kind::Enter {
- if event.name == Name::Definition {
- handle(&mut context, index); // Also handle start.
- definition_inside = true;
- definition_indices.push((index, index));
- }
- } else if event.name == Name::Definition {
- definition_inside = false;
- definition_indices.last_mut().unwrap().1 = index;
- }
-
- index += 1;
- }
-
- index = 0;
- let jump_default = (events.len(), events.len());
- let mut definition_index = 0;
- let mut jump = definition_indices
- .get(definition_index)
- .unwrap_or(&jump_default);
-
- while index < events.len() {
- if index == jump.0 {
- index = jump.1 + 1;
- definition_index += 1;
- jump = definition_indices
- .get(definition_index)
- .unwrap_or(&jump_default);
- } else {
- handle(&mut context, index);
- index += 1;
- }
- }
-
- // No section to generate.
- if !context.gfm_footnote_definition_calls.is_empty() {
- generate_footnote_section(&mut context);
- }
-
- debug_assert_eq!(context.buffers.len(), 1, "expected 1 final buffer");
- context
- .buffers
- .get(0)
- .expect("expected 1 final buffer")
- .to_string()
-}
-
-/// Handle the event at `index`.
-fn handle(context: &mut CompileContext, index: usize) {
- context.index = index;
-
- if context.events[index].kind == Kind::Enter {
- enter(context);
- } else {
- exit(context);
- }
-}
-
-/// Handle [`Enter`][Kind::Enter].
-fn enter(context: &mut CompileContext) {
- match context.events[context.index].name {
- Name::CodeFencedFenceInfo
- | Name::CodeFencedFenceMeta
- | Name::MathFlowFenceMeta
- | Name::DefinitionLabelString
- | Name::DefinitionTitleString
- | Name::GfmFootnoteDefinitionPrefix
- | Name::HeadingAtxText
- | Name::HeadingSetextText
- | Name::Label
- | Name::MdxEsm
- | Name::MdxFlowExpression
- | Name::MdxTextExpression
- | Name::MdxJsxFlowTag
- | Name::MdxJsxTextTag
- | Name::ReferenceString
- | Name::ResourceTitleString => on_enter_buffer(context),
-
- Name::BlockQuote => on_enter_block_quote(context),
- Name::CodeIndented => on_enter_code_indented(context),
- Name::CodeFenced | Name::MathFlow => on_enter_raw_flow(context),
- Name::CodeText | Name::MathText => on_enter_raw_text(context),
- Name::Definition => on_enter_definition(context),
- Name::DefinitionDestinationString => on_enter_definition_destination_string(context),
- Name::Emphasis => on_enter_emphasis(context),
- Name::Frontmatter => on_enter_frontmatter(context),
- Name::GfmFootnoteDefinition => on_enter_gfm_footnote_definition(context),
- Name::GfmFootnoteCall => on_enter_gfm_footnote_call(context),
- Name::GfmStrikethrough => on_enter_gfm_strikethrough(context),
- Name::GfmTable => on_enter_gfm_table(context),
- Name::GfmTableBody => on_enter_gfm_table_body(context),
- Name::GfmTableCell => on_enter_gfm_table_cell(context),
- Name::GfmTableHead => on_enter_gfm_table_head(context),
- Name::GfmTableRow => on_enter_gfm_table_row(context),
- Name::GfmTaskListItemCheck => on_enter_gfm_task_list_item_check(context),
- Name::HtmlFlow => on_enter_html_flow(context),
- Name::HtmlText => on_enter_html_text(context),
- Name::Image => on_enter_image(context),
- Name::Link => on_enter_link(context),
- Name::ListItemMarker => on_enter_list_item_marker(context),
- Name::ListOrdered | Name::ListUnordered => on_enter_list(context),
- Name::Paragraph => on_enter_paragraph(context),
- Name::Resource => on_enter_resource(context),
- Name::ResourceDestinationString => on_enter_resource_destination_string(context),
- Name::Strong => on_enter_strong(context),
- _ => {}
- }
-}
-
-/// Handle [`Exit`][Kind::Exit].
-fn exit(context: &mut CompileContext) {
- match context.events[context.index].name {
- Name::CodeFencedFenceMeta
- | Name::MathFlowFenceMeta
- | Name::MdxJsxTextTag
- | Name::MdxTextExpression
- | Name::Resource => {
- on_exit_drop(context);
- }
- Name::MdxEsm | Name::MdxFlowExpression | Name::MdxJsxFlowTag => on_exit_drop_slurp(context),
- Name::CharacterEscapeValue | Name::CodeTextData | Name::Data | Name::MathTextData => {
- on_exit_data(context);
- }
- Name::AutolinkEmail => on_exit_autolink_email(context),
- Name::AutolinkProtocol => on_exit_autolink_protocol(context),
- Name::BlankLineEnding => on_exit_blank_line_ending(context),
- Name::BlockQuote => on_exit_block_quote(context),
- Name::CharacterReferenceMarker => on_exit_character_reference_marker(context),
- Name::CharacterReferenceMarkerNumeric => {
- on_exit_character_reference_marker_numeric(context);
- }
- Name::CharacterReferenceMarkerHexadecimal => {
- on_exit_character_reference_marker_hexadecimal(context);
- }
- Name::CharacterReferenceValue => on_exit_character_reference_value(context),
- Name::CodeFenced | Name::CodeIndented | Name::MathFlow => on_exit_raw_flow(context),
- Name::CodeFencedFence | Name::MathFlowFence => on_exit_raw_flow_fence(context),
- Name::CodeFencedFenceInfo => on_exit_raw_flow_fence_info(context),
- Name::CodeFlowChunk | Name::MathFlowChunk => on_exit_raw_flow_chunk(context),
- Name::CodeText | Name::MathText => on_exit_raw_text(context),
- Name::Definition => on_exit_definition(context),
- Name::DefinitionDestinationString => on_exit_definition_destination_string(context),
- Name::DefinitionLabelString => on_exit_definition_label_string(context),
- Name::DefinitionTitleString => on_exit_definition_title_string(context),
- Name::Emphasis => on_exit_emphasis(context),
- Name::Frontmatter => on_exit_frontmatter(context),
- Name::GfmAutolinkLiteralEmail => on_exit_gfm_autolink_literal_email(context),
- Name::GfmAutolinkLiteralMailto => on_exit_gfm_autolink_literal_mailto(context),
- Name::GfmAutolinkLiteralProtocol => on_exit_gfm_autolink_literal_protocol(context),
- Name::GfmAutolinkLiteralWww => on_exit_gfm_autolink_literal_www(context),
- Name::GfmAutolinkLiteralXmpp => on_exit_gfm_autolink_literal_xmpp(context),
- Name::GfmFootnoteCall => on_exit_gfm_footnote_call(context),
- Name::GfmFootnoteDefinitionLabelString => {
- on_exit_gfm_footnote_definition_label_string(context);
- }
- Name::GfmFootnoteDefinitionPrefix => on_exit_gfm_footnote_definition_prefix(context),
- Name::GfmFootnoteDefinition => on_exit_gfm_footnote_definition(context),
- Name::GfmStrikethrough => on_exit_gfm_strikethrough(context),
- Name::GfmTable => on_exit_gfm_table(context),
- Name::GfmTableBody => on_exit_gfm_table_body(context),
- Name::GfmTableCell => on_exit_gfm_table_cell(context),
- Name::GfmTableHead => on_exit_gfm_table_head(context),
- Name::GfmTableRow => on_exit_gfm_table_row(context),
- Name::GfmTaskListItemCheck => on_exit_gfm_task_list_item_check(context),
- Name::GfmTaskListItemValueChecked => on_exit_gfm_task_list_item_value_checked(context),
- Name::HardBreakEscape | Name::HardBreakTrailing => on_exit_break(context),
- Name::HeadingAtx => on_exit_heading_atx(context),
- Name::HeadingAtxSequence => on_exit_heading_atx_sequence(context),
- Name::HeadingAtxText => on_exit_heading_atx_text(context),
- Name::HeadingSetextText => on_exit_heading_setext_text(context),
- Name::HeadingSetextUnderlineSequence => on_exit_heading_setext_underline_sequence(context),
- Name::HtmlFlow | Name::HtmlText => on_exit_html(context),
- Name::HtmlFlowData | Name::HtmlTextData => on_exit_html_data(context),
- Name::Image | Name::Link => on_exit_media(context),
- Name::Label => on_exit_label(context),
- Name::LabelText => on_exit_label_text(context),
- Name::LineEnding => on_exit_line_ending(context),
- Name::ListOrdered | Name::ListUnordered => on_exit_list(context),
- Name::ListItem => on_exit_list_item(context),
- Name::ListItemValue => on_exit_list_item_value(context),
- Name::Paragraph => on_exit_paragraph(context),
- Name::ReferenceString => on_exit_reference_string(context),
- Name::ResourceDestinationString => on_exit_resource_destination_string(context),
- Name::ResourceTitleString => on_exit_resource_title_string(context),
- Name::Strong => on_exit_strong(context),
- Name::ThematicBreak => on_exit_thematic_break(context),
- _ => {}
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:`*`.
-///
-/// Buffers data.
-fn on_enter_buffer(context: &mut CompileContext) {
- context.buffer();
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`BlockQuote`][Name::BlockQuote].
-fn on_enter_block_quote(context: &mut CompileContext) {
- context.tight_stack.push(false);
- context.line_ending_if_needed();
- context.push("<blockquote>");
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`CodeIndented`][Name::CodeIndented].
-fn on_enter_code_indented(context: &mut CompileContext) {
- context.raw_flow_seen_data = Some(false);
- context.line_ending_if_needed();
- context.push("<pre><code>");
-}
-
-/// Handle [`Enter`][Kind::Enter]:{[`CodeFenced`][Name::CodeFenced],[`MathFlow`][Name::MathFlow]}.
-fn on_enter_raw_flow(context: &mut CompileContext) {
- context.raw_flow_seen_data = Some(false);
- context.line_ending_if_needed();
- // Note that no `>` is used, which is added later (due to info)
- context.push("<pre><code");
- context.raw_flow_fences_count = Some(0);
-
- if context.events[context.index].name == Name::MathFlow {
- context.push(" class=\"language-math math-display\"");
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:{[`CodeText`][Name::CodeText],[`MathText`][Name::MathText]}.
-fn on_enter_raw_text(context: &mut CompileContext) {
- context.raw_text_inside = true;
- if !context.image_alt_inside {
- context.push("<code");
- if context.events[context.index].name == Name::MathText {
- context.push(" class=\"language-math math-inline\"");
- }
- context.push(">");
- }
- context.buffer();
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Definition`][Name::Definition].
-fn on_enter_definition(context: &mut CompileContext) {
- context.buffer();
- context.media_stack.push(Media {
- image: false,
- label: None,
- label_id: None,
- reference_id: None,
- destination: None,
- title: None,
- });
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`DefinitionDestinationString`][Name::DefinitionDestinationString].
-fn on_enter_definition_destination_string(context: &mut CompileContext) {
- context.buffer();
- context.encode_html = false;
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Emphasis`][Name::Emphasis].
-fn on_enter_emphasis(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("<em>");
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Frontmatter`][Name::Frontmatter].
-fn on_enter_frontmatter(context: &mut CompileContext) {
- context.buffer();
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmFootnoteDefinition`][Name::GfmFootnoteDefinition].
-fn on_enter_gfm_footnote_definition(context: &mut CompileContext) {
- context.tight_stack.push(false);
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmFootnoteCall`][Name::GfmFootnoteCall].
-fn on_enter_gfm_footnote_call(context: &mut CompileContext) {
- context.media_stack.push(Media {
- image: false,
- label_id: None,
- label: None,
- reference_id: None,
- destination: None,
- title: None,
- });
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmStrikethrough`][Name::GfmStrikethrough].
-fn on_enter_gfm_strikethrough(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("<del>");
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmTable`][Name::GfmTable].
-fn on_enter_gfm_table(context: &mut CompileContext) {
- // Find the alignment.
- let mut index = context.index;
- let mut in_delimiter_row = false;
- let mut align = vec![];
-
- while index < context.events.len() {
- let event = &context.events[index];
-
- if in_delimiter_row {
- if event.kind == Kind::Enter {
- // Start of alignment value: set a new column.
- if event.name == Name::GfmTableDelimiterCellValue {
- align.push(
- if context.events[index + 1].name == Name::GfmTableDelimiterMarker {
- GfmTableAlign::Left
- } else {
- GfmTableAlign::None
- },
- );
- }
- } else {
- // End of alignment value: change the column.
- if event.name == Name::GfmTableDelimiterCellValue {
- if context.events[index - 1].name == Name::GfmTableDelimiterMarker {
- let align_index = align.len() - 1;
- align[align_index] = if align[align_index] == GfmTableAlign::Left {
- GfmTableAlign::Center
- } else {
- GfmTableAlign::Right
- }
- }
- }
- // Done!
- else if event.name == Name::GfmTableDelimiterRow {
- break;
- }
- }
- } else if event.kind == Kind::Enter && event.name == Name::GfmTableDelimiterRow {
- in_delimiter_row = true;
- }
-
- index += 1;
- }
-
- // Generate.
- context.gfm_table_align = Some(align);
- context.line_ending_if_needed();
- context.push("<table>");
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmTableBody`][Name::GfmTableBody].
-fn on_enter_gfm_table_body(context: &mut CompileContext) {
- context.push("<tbody>");
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmTableCell`][Name::GfmTableCell].
-fn on_enter_gfm_table_cell(context: &mut CompileContext) {
- let column = context.gfm_table_column;
- let align = context.gfm_table_align.as_ref().unwrap();
-
- if column >= align.len() {
- // Capture cell to ignore it.
- context.buffer();
- } else {
- let value = align[column];
- context.line_ending_if_needed();
-
- if context.gfm_table_in_head {
- context.push("<th");
- } else {
- context.push("<td");
- }
-
- match value {
- GfmTableAlign::Left => context.push(" align=\"left\""),
- GfmTableAlign::Right => context.push(" align=\"right\""),
- GfmTableAlign::Center => context.push(" align=\"center\""),
- GfmTableAlign::None => {}
- }
-
- context.push(">");
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmTableHead`][Name::GfmTableHead].
-fn on_enter_gfm_table_head(context: &mut CompileContext) {
- context.line_ending_if_needed();
- context.push("<thead>");
- context.gfm_table_in_head = true;
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmTableRow`][Name::GfmTableRow].
-fn on_enter_gfm_table_row(context: &mut CompileContext) {
- context.line_ending_if_needed();
- context.push("<tr>");
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`GfmTaskListItemCheck`][Name::GfmTaskListItemCheck].
-fn on_enter_gfm_task_list_item_check(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("<input type=\"checkbox\" disabled=\"\" ");
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`HtmlFlow`][Name::HtmlFlow].
-fn on_enter_html_flow(context: &mut CompileContext) {
- context.line_ending_if_needed();
- if context.options.allow_dangerous_html {
- context.encode_html = false;
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`HtmlText`][Name::HtmlText].
-fn on_enter_html_text(context: &mut CompileContext) {
- if context.options.allow_dangerous_html {
- context.encode_html = false;
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Image`][Name::Image].
-fn on_enter_image(context: &mut CompileContext) {
- context.media_stack.push(Media {
- image: true,
- label_id: None,
- label: None,
- reference_id: None,
- destination: None,
- title: None,
- });
- context.image_alt_inside = true; // Disallow tags.
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Link`][Name::Link].
-fn on_enter_link(context: &mut CompileContext) {
- context.media_stack.push(Media {
- image: false,
- label_id: None,
- label: None,
- reference_id: None,
- destination: None,
- title: None,
- });
-}
-
-/// Handle [`Enter`][Kind::Enter]:{[`ListOrdered`][Name::ListOrdered],[`ListUnordered`][Name::ListUnordered]}.
-fn on_enter_list(context: &mut CompileContext) {
- let events = &context.events;
- let mut index = context.index;
- let mut balance = 0;
- let mut loose = false;
- let name = &events[index].name;
-
- while index < events.len() {
- let event = &events[index];
-
- if event.kind == Kind::Enter {
- balance += 1;
- } else {
- balance -= 1;
-
- if balance < 3 && event.name == Name::BlankLineEnding {
- // Blank line directly after a prefix:
- //
- // ```markdown
- // > | -␊
- // ^
- // | a
- // ```
- let mut at_prefix = false;
- // Blank line directly after item, which is just a prefix.
- //
- // ```markdown
- // > | -␊
- // ^
- // | - a
- // ```
- let mut at_empty_list_item = false;
- // Blank line at block quote prefix:
- //
- // ```markdown
- // > | * >␊
- // ^
- // | * a
- // ```
- let mut at_empty_block_quote = false;
-
- if balance == 1 {
- let mut before = index - 2;
-
- if events[before].name == Name::ListItem {
- before -= 1;
-
- if events[before].name == Name::SpaceOrTab {
- before -= 2;
- }
-
- if events[before].name == Name::BlockQuote
- && events[before - 1].name == Name::BlockQuotePrefix
- {
- at_empty_block_quote = true;
- } else if events[before].name == Name::ListItemPrefix {
- at_empty_list_item = true;
- }
- }
- } else {
- let mut before = index - 2;
-
- if events[before].name == Name::SpaceOrTab {
- before -= 2;
- }
-
- if events[before].name == Name::ListItemPrefix {
- at_prefix = true;
- }
- }
-
- if !at_prefix && !at_empty_list_item && !at_empty_block_quote {
- loose = true;
- break;
- }
- }
-
- // Done.
- if balance == 0 && event.name == *name {
- break;
- }
- }
-
- index += 1;
- }
-
- context.tight_stack.push(!loose);
- context.line_ending_if_needed();
- // Note: no `>`.
- context.push(if *name == Name::ListOrdered {
- "<ol"
- } else {
- "<ul"
- });
- context.list_expect_first_marker = Some(true);
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`ListItemMarker`][Name::ListItemMarker].
-fn on_enter_list_item_marker(context: &mut CompileContext) {
- if context.list_expect_first_marker.take().unwrap() {
- context.push(">");
- }
-
- context.line_ending_if_needed();
-
- context.push("<li>");
- context.list_expect_first_marker = Some(false);
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Paragraph`][Name::Paragraph].
-fn on_enter_paragraph(context: &mut CompileContext) {
- let tight = context.tight_stack.last().unwrap_or(&false);
-
- if !tight {
- context.line_ending_if_needed();
- context.push("<p>");
- }
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Resource`][Name::Resource].
-fn on_enter_resource(context: &mut CompileContext) {
- context.buffer(); // We can have line endings in the resource, ignore them.
- context.media_stack.last_mut().unwrap().destination = Some("".to_string());
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`ResourceDestinationString`][Name::ResourceDestinationString].
-fn on_enter_resource_destination_string(context: &mut CompileContext) {
- context.buffer();
- // Ignore encoding the result, as we’ll first percent encode the url and
- // encode manually after.
- context.encode_html = false;
-}
-
-/// Handle [`Enter`][Kind::Enter]:[`Strong`][Name::Strong].
-fn on_enter_strong(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("<strong>");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`AutolinkEmail`][Name::AutolinkEmail].
-fn on_exit_autolink_email(context: &mut CompileContext) {
- generate_autolink(
- context,
- Some("mailto:"),
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- false,
- );
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`AutolinkProtocol`][Name::AutolinkProtocol].
-fn on_exit_autolink_protocol(context: &mut CompileContext) {
- generate_autolink(
- context,
- None,
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- false,
- );
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`HardBreakEscape`][Name::HardBreakEscape],[`HardBreakTrailing`][Name::HardBreakTrailing]}.
-fn on_exit_break(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("<br />");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`BlankLineEnding`][Name::BlankLineEnding].
-fn on_exit_blank_line_ending(context: &mut CompileContext) {
- if context.index == context.events.len() - 1 {
- context.line_ending_if_needed();
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`BlockQuote`][Name::BlockQuote].
-fn on_exit_block_quote(context: &mut CompileContext) {
- context.tight_stack.pop();
- context.line_ending_if_needed();
- context.slurp_one_line_ending = false;
- context.push("</blockquote>");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarker`][Name::CharacterReferenceMarker].
-fn on_exit_character_reference_marker(context: &mut CompileContext) {
- context.character_reference_marker = Some(b'&');
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarkerHexadecimal`][Name::CharacterReferenceMarkerHexadecimal].
-fn on_exit_character_reference_marker_hexadecimal(context: &mut CompileContext) {
- context.character_reference_marker = Some(b'x');
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceMarkerNumeric`][Name::CharacterReferenceMarkerNumeric].
-fn on_exit_character_reference_marker_numeric(context: &mut CompileContext) {
- context.character_reference_marker = Some(b'#');
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`CharacterReferenceValue`][Name::CharacterReferenceValue].
-fn on_exit_character_reference_value(context: &mut CompileContext) {
- let marker = context
- .character_reference_marker
- .take()
- .expect("expected `character_reference_kind` to be set");
- let slice = Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- );
- let value = slice.as_str();
-
- let value = match marker {
- b'#' => decode_numeric(value, 10),
- b'x' => decode_numeric(value, 16),
- b'&' => decode_named(value),
- _ => panic!("impossible"),
- };
-
- context.push(&encode(&value, context.encode_html));
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`CodeFlowChunk`][Name::CodeFlowChunk],[`MathFlowChunk`][Name::MathFlowChunk]}.
-fn on_exit_raw_flow_chunk(context: &mut CompileContext) {
- context.raw_flow_seen_data = Some(true);
- context.push(&encode(
- &Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- // Must serialize to get virtual spaces.
- .serialize(),
- context.encode_html,
- ));
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`CodeFencedFence`][Name::CodeFencedFence],[`MathFlowFence`][Name::MathFlowFence]}.
-fn on_exit_raw_flow_fence(context: &mut CompileContext) {
- let count = if let Some(count) = context.raw_flow_fences_count {
- count
- } else {
- 0
- };
-
- if count == 0 {
- context.push(">");
- context.slurp_one_line_ending = true;
- }
-
- context.raw_flow_fences_count = Some(count + 1);
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`CodeFencedFenceInfo`][Name::CodeFencedFenceInfo].
-///
-/// Note: math (flow) does not support `info`.
-fn on_exit_raw_flow_fence_info(context: &mut CompileContext) {
- let value = context.resume();
- context.push(" class=\"language-");
- context.push(&value);
- context.push("\"");
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`CodeFenced`][Name::CodeFenced],[`CodeIndented`][Name::CodeIndented],[`MathFlow`][Name::MathFlow]}.
-fn on_exit_raw_flow(context: &mut CompileContext) {
- // One special case is if we are inside a container, and the raw (flow) was
- // not closed (meaning it runs to the end).
- // In that case, the following line ending, is considered *outside* the
- // fenced code and block quote by micromark, but CM wants to treat that
- // ending as part of the code.
- if let Some(count) = context.raw_flow_fences_count {
- // No closing fence.
- if count == 1
- // In a container.
- && !context.tight_stack.is_empty()
- // Empty (as the closing is right at the opening fence)
- && !matches!(context.events[context.index - 1].name, Name::CodeFencedFence | Name::MathFlowFence)
- {
- context.line_ending();
- }
- }
-
- // But in most cases, it’s simpler: when we’ve seen some data, emit an extra
- // line ending when needed.
- if context
- .raw_flow_seen_data
- .take()
- .expect("`raw_flow_seen_data` must be defined")
- {
- context.line_ending_if_needed();
- }
-
- context.push("</code></pre>");
-
- if let Some(count) = context.raw_flow_fences_count.take() {
- if count < 2 {
- context.line_ending_if_needed();
- }
- }
-
- context.slurp_one_line_ending = false;
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`CodeText`][Name::CodeText],[`MathText`][Name::MathText]}.
-fn on_exit_raw_text(context: &mut CompileContext) {
- let result = context.resume();
- let mut bytes = result.as_bytes().to_vec();
-
- // If we are in a GFM table, we need to decode escaped pipes.
- // This is a rather weird GFM feature.
- if context.gfm_table_align.is_some() {
- let mut index = 0;
- let mut len = bytes.len();
-
- while index < len {
- if index + 1 < len && bytes[index] == b'\\' && bytes[index + 1] == b'|' {
- bytes.remove(index);
- len -= 1;
- }
-
- index += 1;
- }
- }
-
- let mut trim = false;
- let mut index = 0;
- let mut end = bytes.len();
-
- if end > 2 && bytes[index] == b' ' && bytes[end - 1] == b' ' {
- index += 1;
- end -= 1;
- while index < end && !trim {
- if bytes[index] != b' ' {
- trim = true;
- break;
- }
- index += 1;
- }
- }
-
- if trim {
- bytes.remove(0);
- bytes.pop();
- }
-
- context.raw_text_inside = false;
- context.push(str::from_utf8(&bytes).unwrap());
-
- if !context.image_alt_inside {
- context.push("</code>");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:*.
-///
-/// Resumes, and ignores what was resumed.
-fn on_exit_drop(context: &mut CompileContext) {
- context.resume();
-}
-
-/// Handle [`Exit`][Kind::Exit]:*.
-///
-/// Resumes, ignores what was resumed, and slurps the following line ending.
-fn on_exit_drop_slurp(context: &mut CompileContext) {
- context.resume();
- context.slurp_one_line_ending = true;
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`CodeTextData`][Name::CodeTextData],[`Data`][Name::Data],[`CharacterEscapeValue`][Name::CharacterEscapeValue]}.
-fn on_exit_data(context: &mut CompileContext) {
- context.push(&encode(
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- context.encode_html,
- ));
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`Definition`][Name::Definition].
-fn on_exit_definition(context: &mut CompileContext) {
- context.resume();
- let media = context.media_stack.pop().unwrap();
- let indices = media.reference_id.unwrap();
- let id =
- normalize_identifier(Slice::from_indices(context.bytes, indices.0, indices.1).as_str());
-
- context.definitions.push(Definition {
- id,
- destination: media.destination,
- title: media.title,
- });
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`DefinitionDestinationString`][Name::DefinitionDestinationString].
-fn on_exit_definition_destination_string(context: &mut CompileContext) {
- let buf = context.resume();
- context.media_stack.last_mut().unwrap().destination = Some(buf);
- context.encode_html = true;
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`DefinitionLabelString`][Name::DefinitionLabelString].
-fn on_exit_definition_label_string(context: &mut CompileContext) {
- // Discard label, use the source content instead.
- context.resume();
- context.media_stack.last_mut().unwrap().reference_id =
- Some(Position::from_exit_event(context.events, context.index).to_indices());
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`DefinitionTitleString`][Name::DefinitionTitleString].
-fn on_exit_definition_title_string(context: &mut CompileContext) {
- let buf = context.resume();
- context.media_stack.last_mut().unwrap().title = Some(buf);
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`Emphasis`][Name::Emphasis].
-fn on_exit_emphasis(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("</em>");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`Frontmatter`][Name::Frontmatter].
-fn on_exit_frontmatter(context: &mut CompileContext) {
- context.resume();
- context.slurp_one_line_ending = true;
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralEmail`][Name::GfmAutolinkLiteralEmail].
-fn on_exit_gfm_autolink_literal_email(context: &mut CompileContext) {
- generate_autolink(
- context,
- Some("mailto:"),
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- true,
- );
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralMailto`][Name::GfmAutolinkLiteralMailto].
-fn on_exit_gfm_autolink_literal_mailto(context: &mut CompileContext) {
- generate_autolink(
- context,
- None,
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- true,
- );
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralProtocol`][Name::GfmAutolinkLiteralProtocol].
-fn on_exit_gfm_autolink_literal_protocol(context: &mut CompileContext) {
- generate_autolink(
- context,
- None,
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- true,
- );
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralWww`][Name::GfmAutolinkLiteralWww].
-fn on_exit_gfm_autolink_literal_www(context: &mut CompileContext) {
- generate_autolink(
- context,
- Some("http://"),
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- true,
- );
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmAutolinkLiteralXmpp`][Name::GfmAutolinkLiteralXmpp].
-fn on_exit_gfm_autolink_literal_xmpp(context: &mut CompileContext) {
- generate_autolink(
- context,
- None,
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- true,
- );
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteCall`][Name::GfmFootnoteCall].
-fn on_exit_gfm_footnote_call(context: &mut CompileContext) {
- let indices = context.media_stack.pop().unwrap().label_id.unwrap();
- let id =
- normalize_identifier(Slice::from_indices(context.bytes, indices.0, indices.1).as_str());
- let safe_id = sanitize(&id.to_lowercase());
- let mut call_index = 0;
-
- // See if this has been called before.
- while call_index < context.gfm_footnote_definition_calls.len() {
- if context.gfm_footnote_definition_calls[call_index].0 == id {
- break;
- }
- call_index += 1;
- }
-
- // New.
- if call_index == context.gfm_footnote_definition_calls.len() {
- context.gfm_footnote_definition_calls.push((id, 0));
- }
-
- // Increment.
- context.gfm_footnote_definition_calls[call_index].1 += 1;
-
- // No call is output in an image alt, though the definition and
- // backreferences are generated as if it was the case.
- if context.image_alt_inside {
- return;
- }
-
- context.push("<sup><a href=\"#");
- if let Some(ref value) = context.options.gfm_footnote_clobber_prefix {
- context.push(&encode(value, context.encode_html));
- } else {
- context.push("user-content-");
- }
- context.push("fn-");
- context.push(&safe_id);
- context.push("\" id=\"");
- if let Some(ref value) = context.options.gfm_footnote_clobber_prefix {
- context.push(&encode(value, context.encode_html));
- } else {
- context.push("user-content-");
- }
- context.push("fnref-");
- context.push(&safe_id);
- if context.gfm_footnote_definition_calls[call_index].1 > 1 {
- context.push("-");
- context.push(
- &context.gfm_footnote_definition_calls[call_index]
- .1
- .to_string(),
- );
- }
- context.push("\" data-footnote-ref=\"\" aria-describedby=\"footnote-label\">");
-
- context.push(&(call_index + 1).to_string());
- context.push("</a></sup>");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteDefinitionLabelString`][Name::GfmFootnoteDefinitionLabelString].
-fn on_exit_gfm_footnote_definition_label_string(context: &mut CompileContext) {
- context
- .gfm_footnote_definition_stack
- .push(Position::from_exit_event(context.events, context.index).to_indices());
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteDefinitionPrefix`][Name::GfmFootnoteDefinitionPrefix].
-fn on_exit_gfm_footnote_definition_prefix(context: &mut CompileContext) {
- // Drop the prefix.
- context.resume();
- // Capture everything until end of definition.
- context.buffer();
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmFootnoteDefinition`][Name::GfmFootnoteDefinition].
-fn on_exit_gfm_footnote_definition(context: &mut CompileContext) {
- let value = context.resume();
- let indices = context.gfm_footnote_definition_stack.pop().unwrap();
- context.tight_stack.pop();
- context.gfm_footnote_definitions.push((
- normalize_identifier(Slice::from_indices(context.bytes, indices.0, indices.1).as_str()),
- value,
- ));
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmStrikethrough`][Name::GfmStrikethrough].
-fn on_exit_gfm_strikethrough(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("</del>");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmTable`][Name::GfmTable].
-fn on_exit_gfm_table(context: &mut CompileContext) {
- context.gfm_table_align = None;
- context.line_ending_if_needed();
- context.push("</table>");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmTableBody`][Name::GfmTableBody].
-fn on_exit_gfm_table_body(context: &mut CompileContext) {
- context.line_ending_if_needed();
- context.push("</tbody>");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmTableCell`][Name::GfmTableCell].
-fn on_exit_gfm_table_cell(context: &mut CompileContext) {
- let align = context.gfm_table_align.as_ref().unwrap();
-
- if context.gfm_table_column < align.len() {
- if context.gfm_table_in_head {
- context.push("</th>");
- } else {
- context.push("</td>");
- }
- } else {
- // Stop capturing.
- context.resume();
- }
-
- context.gfm_table_column += 1;
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmTableHead`][Name::GfmTableHead].
-fn on_exit_gfm_table_head(context: &mut CompileContext) {
- context.gfm_table_in_head = false;
- context.line_ending_if_needed();
- context.push("</thead>");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmTableRow`][Name::GfmTableRow].
-fn on_exit_gfm_table_row(context: &mut CompileContext) {
- let mut column = context.gfm_table_column;
- let len = context.gfm_table_align.as_ref().unwrap().len();
-
- // Add “phantom” cells, for body rows that are shorter than the delimiter
- // row (which is equal to the head row).
- while column < len {
- on_enter_gfm_table_cell(context);
- on_exit_gfm_table_cell(context);
- column += 1;
- }
-
- context.gfm_table_column = 0;
- context.line_ending_if_needed();
- context.push("</tr>");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmTaskListItemCheck`][Name::GfmTaskListItemCheck].
-fn on_exit_gfm_task_list_item_check(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("/>");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`GfmTaskListItemValueChecked`][Name::GfmTaskListItemValueChecked].
-fn on_exit_gfm_task_list_item_value_checked(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("checked=\"\" ");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`HeadingAtx`][Name::HeadingAtx].
-fn on_exit_heading_atx(context: &mut CompileContext) {
- let rank = context
- .heading_atx_rank
- .take()
- .expect("`heading_atx_rank` must be set in headings");
-
- context.push("</h");
- context.push(&rank.to_string());
- context.push(">");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`HeadingAtxSequence`][Name::HeadingAtxSequence].
-fn on_exit_heading_atx_sequence(context: &mut CompileContext) {
- // First fence we see.
- if context.heading_atx_rank.is_none() {
- let rank = Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .len();
- context.line_ending_if_needed();
- context.heading_atx_rank = Some(rank);
- context.push("<h");
- context.push(&rank.to_string());
- context.push(">");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`HeadingAtxText`][Name::HeadingAtxText].
-fn on_exit_heading_atx_text(context: &mut CompileContext) {
- let value = context.resume();
- context.push(&value);
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`HeadingSetextText`][Name::HeadingSetextText].
-fn on_exit_heading_setext_text(context: &mut CompileContext) {
- let buf = context.resume();
- context.heading_setext_buffer = Some(buf);
- context.slurp_one_line_ending = true;
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`HeadingSetextUnderlineSequence`][Name::HeadingSetextUnderlineSequence].
-fn on_exit_heading_setext_underline_sequence(context: &mut CompileContext) {
- let text = context
- .heading_setext_buffer
- .take()
- .expect("`heading_atx_rank` must be set in headings");
- let head = Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .head();
- let rank = if head == Some(b'-') { "2" } else { "1" };
-
- context.line_ending_if_needed();
- context.push("<h");
- context.push(rank);
- context.push(">");
- context.push(&text);
- context.push("</h");
- context.push(rank);
- context.push(">");
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`HtmlFlow`][Name::HtmlFlow],[`HtmlText`][Name::HtmlText]}.
-fn on_exit_html(context: &mut CompileContext) {
- context.encode_html = true;
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`HtmlFlowData`][Name::HtmlFlowData],[`HtmlTextData`][Name::HtmlTextData]}.
-fn on_exit_html_data(context: &mut CompileContext) {
- let slice = Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- );
- let value = slice.as_str();
-
- let encoded = if context.options.gfm_tagfilter && context.options.allow_dangerous_html {
- encode(&gfm_tagfilter(value), context.encode_html)
- } else {
- encode(value, context.encode_html)
- };
-
- context.push(&encoded);
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`Label`][Name::Label].
-fn on_exit_label(context: &mut CompileContext) {
- let buf = context.resume();
- context.media_stack.last_mut().unwrap().label = Some(buf);
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`LabelText`][Name::LabelText].
-fn on_exit_label_text(context: &mut CompileContext) {
- context.media_stack.last_mut().unwrap().label_id =
- Some(Position::from_exit_event(context.events, context.index).to_indices());
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`LineEnding`][Name::LineEnding].
-fn on_exit_line_ending(context: &mut CompileContext) {
- if context.raw_text_inside {
- context.push(" ");
- } else if context.slurp_one_line_ending
- // Ignore line endings after definitions.
- || (context.index > 1
- && (context.events[context.index - 2].name == Name::Definition
- || context.events[context.index - 2].name == Name::GfmFootnoteDefinition))
- {
- context.slurp_one_line_ending = false;
- } else {
- context.push(&encode(
- Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- )
- .as_str(),
- context.encode_html,
- ));
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`ListOrdered`][Name::ListOrdered],[`ListUnordered`][Name::ListUnordered]}.
-fn on_exit_list(context: &mut CompileContext) {
- context.tight_stack.pop();
- context.line_ending();
- context.push(if context.events[context.index].name == Name::ListOrdered {
- "</ol>"
- } else {
- "</ul>"
- });
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`ListItem`][Name::ListItem].
-fn on_exit_list_item(context: &mut CompileContext) {
- let tight = context.tight_stack.last().unwrap_or(&false);
- let before_item = skip::opt_back(
- context.events,
- context.index - 1,
- &[
- Name::BlankLineEnding,
- Name::BlockQuotePrefix,
- Name::LineEnding,
- Name::SpaceOrTab,
- // Also ignore things that don’t contribute to the document.
- Name::Definition,
- Name::GfmFootnoteDefinition,
- ],
- );
- let previous = &context.events[before_item];
- let tight_paragraph = *tight && previous.name == Name::Paragraph;
- let empty_item = previous.name == Name::ListItemPrefix;
-
- context.slurp_one_line_ending = false;
-
- if !tight_paragraph && !empty_item {
- context.line_ending_if_needed();
- }
-
- context.push("</li>");
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`ListItemValue`][Name::ListItemValue].
-fn on_exit_list_item_value(context: &mut CompileContext) {
- if context.list_expect_first_marker.unwrap() {
- let slice = Slice::from_position(
- context.bytes,
- &Position::from_exit_event(context.events, context.index),
- );
- let value = slice.as_str().parse::<u32>().ok().unwrap();
-
- if value != 1 {
- context.push(" start=\"");
- context.push(&value.to_string());
- context.push("\"");
- }
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:{[`Image`][Name::Image],[`Link`][Name::Link]}.
-fn on_exit_media(context: &mut CompileContext) {
- let mut is_in_image = false;
- let mut index = 0;
-
- // Skip current.
- let end = context.media_stack.len() - 1;
- while index < end {
- if context.media_stack[index].image {
- is_in_image = true;
- break;
- }
- index += 1;
- }
-
- context.image_alt_inside = is_in_image;
-
- let media = context.media_stack.pop().unwrap();
- let label = media.label.unwrap();
- let id = media.reference_id.or(media.label_id).map(|indices| {
- normalize_identifier(Slice::from_indices(context.bytes, indices.0, indices.1).as_str())
- });
-
- let definition_index = if media.destination.is_none() {
- id.and_then(|id| {
- let mut index = 0;
-
- while index < context.definitions.len() {
- if context.definitions[index].id == id {
- return Some(index);
- }
-
- index += 1;
- }
-
- None
- })
- } else {
- None
- };
-
- if !is_in_image {
- if media.image {
- context.push("<img src=\"");
- } else {
- context.push("<a href=\"");
- };
-
- let destination = if let Some(index) = definition_index {
- context.definitions[index].destination.as_ref()
- } else {
- media.destination.as_ref()
- };
-
- if let Some(destination) = destination {
- let url = if context.options.allow_dangerous_protocol {
- sanitize(destination)
- } else {
- sanitize_with_protocols(
- destination,
- if media.image {
- &SAFE_PROTOCOL_SRC
- } else {
- &SAFE_PROTOCOL_HREF
- },
- )
- };
- context.push(&url);
- }
-
- if media.image {
- context.push("\" alt=\"");
- };
- }
-
- if media.image {
- context.push(&label);
- }
-
- if !is_in_image {
- context.push("\"");
-
- let title = if let Some(index) = definition_index {
- context.definitions[index].title.clone()
- } else {
- media.title
- };
-
- if let Some(title) = title {
- context.push(" title=\"");
- context.push(&title);
- context.push("\"");
- };
-
- if media.image {
- context.push(" /");
- }
-
- context.push(">");
- }
-
- if !media.image {
- context.push(&label);
-
- if !is_in_image {
- context.push("</a>");
- }
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`Paragraph`][Name::Paragraph].
-fn on_exit_paragraph(context: &mut CompileContext) {
- let tight = context.tight_stack.last().unwrap_or(&false);
-
- if *tight {
- context.slurp_one_line_ending = true;
- } else {
- context.push("</p>");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`ReferenceString`][Name::ReferenceString].
-fn on_exit_reference_string(context: &mut CompileContext) {
- // Drop stuff.
- context.resume();
-
- context.media_stack.last_mut().unwrap().reference_id =
- Some(Position::from_exit_event(context.events, context.index).to_indices());
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`ResourceDestinationString`][Name::ResourceDestinationString].
-fn on_exit_resource_destination_string(context: &mut CompileContext) {
- let buf = context.resume();
- context.media_stack.last_mut().unwrap().destination = Some(buf);
- context.encode_html = true;
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`ResourceTitleString`][Name::ResourceTitleString].
-fn on_exit_resource_title_string(context: &mut CompileContext) {
- let buf = context.resume();
- context.media_stack.last_mut().unwrap().title = Some(buf);
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`Strong`][Name::Strong].
-fn on_exit_strong(context: &mut CompileContext) {
- if !context.image_alt_inside {
- context.push("</strong>");
- }
-}
-
-/// Handle [`Exit`][Kind::Exit]:[`ThematicBreak`][Name::ThematicBreak].
-fn on_exit_thematic_break(context: &mut CompileContext) {
- context.line_ending_if_needed();
- context.push("<hr />");
-}
-
-/// Generate a footnote section.
-fn generate_footnote_section(context: &mut CompileContext) {
- context.line_ending_if_needed();
- context.push("<section data-footnotes=\"\" class=\"footnotes\"><");
- if let Some(ref value) = context.options.gfm_footnote_label_tag_name {
- context.push(&encode(value, context.encode_html));
- } else {
- context.push("h2");
- }
- context.push(" id=\"footnote-label\" ");
- if let Some(ref value) = context.options.gfm_footnote_label_attributes {
- context.push(value);
- } else {
- context.push("class=\"sr-only\"");
- }
- context.push(">");
- if let Some(ref value) = context.options.gfm_footnote_label {
- context.push(&encode(value, context.encode_html));
- } else {
- context.push("Footnotes");
- }
- context.push("</");
- if let Some(ref value) = context.options.gfm_footnote_label_tag_name {
- context.push(&encode(value, context.encode_html));
- } else {
- context.push("h2");
- }
- context.push(">");
- context.line_ending();
- context.push("<ol>");
-
- let mut index = 0;
- while index < context.gfm_footnote_definition_calls.len() {
- generate_footnote_item(context, index);
- index += 1;
- }
-
- context.line_ending();
- context.push("</ol>");
- context.line_ending();
- context.push("</section>");
- context.line_ending();
-}
-
-/// Generate a footnote item from a call.
-fn generate_footnote_item(context: &mut CompileContext, index: usize) {
- let id = &context.gfm_footnote_definition_calls[index].0;
- let safe_id = sanitize(&id.to_lowercase());
-
- // Find definition: we’ll always find it.
- let mut definition_index = 0;
- while definition_index < context.gfm_footnote_definitions.len() {
- if &context.gfm_footnote_definitions[definition_index].0 == id {
- break;
- }
- definition_index += 1;
- }
-
- debug_assert_ne!(
- definition_index,
- context.gfm_footnote_definitions.len(),
- "expected definition"
- );
-
- context.line_ending();
- context.push("<li id=\"");
- if let Some(ref value) = context.options.gfm_footnote_clobber_prefix {
- context.push(&encode(value, context.encode_html));
- } else {
- context.push("user-content-");
- }
- context.push("fn-");
- context.push(&safe_id);
- context.push("\">");
- context.line_ending();
-
- // Create one or more backreferences.
- let mut reference_index = 0;
- let mut backreferences = String::new();
- while reference_index < context.gfm_footnote_definition_calls[index].1 {
- if reference_index != 0 {
- backreferences.push(' ');
- }
- backreferences.push_str("<a href=\"#");
- if let Some(ref value) = context.options.gfm_footnote_clobber_prefix {
- backreferences.push_str(&encode(value, context.encode_html));
- } else {
- backreferences.push_str("user-content-");
- }
- backreferences.push_str("fnref-");
- backreferences.push_str(&safe_id);
- if reference_index != 0 {
- backreferences.push('-');
- backreferences.push_str(&(reference_index + 1).to_string());
- }
- backreferences.push_str("\" data-footnote-backref=\"\" aria-label=\"");
- if let Some(ref value) = context.options.gfm_footnote_back_label {
- backreferences.push_str(&encode(value, context.encode_html));
- } else {
- backreferences.push_str("Back to content");
- }
- backreferences.push_str("\" class=\"data-footnote-backref\">↩");
- if reference_index != 0 {
- backreferences.push_str("<sup>");
- backreferences.push_str(&(reference_index + 1).to_string());
- backreferences.push_str("</sup>");
- }
- backreferences.push_str("</a>");
-
- reference_index += 1;
- }
-
- let value = context.gfm_footnote_definitions[definition_index].1.clone();
- let bytes = value.as_bytes();
- let mut byte_index = bytes.len();
- // Move back past EOL.
- while byte_index > 0 && matches!(bytes[byte_index - 1], b'\n' | b'\r') {
- byte_index -= 1;
- }
- // Check if it ends in `</p>`.
- // This is a bit funky if someone wrote a safe paragraph by hand in
- // there.
- // But in all other cases, `<` and `>` would be encoded, so we can be
- // sure that this is generated by our compiler.
- if byte_index > 3
- && bytes[byte_index - 4] == b'<'
- && bytes[byte_index - 3] == b'/'
- && bytes[byte_index - 2] == b'p'
- && bytes[byte_index - 1] == b'>'
- {
- let (before, after) = bytes.split_at(byte_index - 4);
- let mut result = String::new();
- result.push_str(str::from_utf8(before).unwrap());
- result.push(' ');
- result.push_str(&backreferences);
- result.push_str(str::from_utf8(after).unwrap());
- context.push(&result);
- } else {
- context.push(&value);
- context.line_ending_if_needed();
- context.push(&backreferences);
- }
- context.line_ending_if_needed();
- context.push("</li>");
-}
-
-/// Generate an autolink (used by unicode autolinks and GFM autolink literals).
-fn generate_autolink(
- context: &mut CompileContext,
- protocol: Option<&str>,
- value: &str,
- is_gfm_literal: bool,
-) {
- let mut is_in_link = false;
- let mut index = 0;
-
- while index < context.media_stack.len() {
- if !context.media_stack[index].image {
- is_in_link = true;
- break;
- }
- index += 1;
- }
-
- if !context.image_alt_inside && (!is_in_link || !is_gfm_literal) {
- context.push("<a href=\"");
- let url = if let Some(protocol) = protocol {
- format!("{}{}", protocol, value)
- } else {
- value.to_string()
- };
-
- let url = if context.options.allow_dangerous_protocol {
- sanitize(&url)
- } else {
- sanitize_with_protocols(&url, &SAFE_PROTOCOL_HREF)
- };
-
- context.push(&url);
- context.push("\">");
- }
-
- context.push(&encode(value, context.encode_html));
-
- if !context.image_alt_inside && (!is_in_link || !is_gfm_literal) {
- context.push("</a>");
- }
-}