diff --git a/bench/benches/html_rendering.rs b/bench/benches/html_rendering.rs index 788866eb..a440c324 100644 --- a/bench/benches/html_rendering.rs +++ b/bench/benches/html_rendering.rs @@ -101,6 +101,74 @@ This is a [link](example.com). **Cool!** b.iter(|| Parser::new_ext(input, Options::empty()).count()); }); + + c.bench_function("html_block_type_1", |b| { + let input = concat!( + "", + ); + + b.iter(|| Parser::new_ext(input, Options::empty()).count()); + }); + + c.bench_function("html_block_type_2", |b| { + let input = concat!( + "", + ); + + b.iter(|| Parser::new_ext(input, Options::empty()).count()); + }); + + c.bench_function("html_block_type_3", |b| { + let input = concat!( + "", + ); + + b.iter(|| Parser::new_ext(input, Options::empty()).count()); + }); + + c.bench_function("html_block_type_4", |b| { + let input = concat!( + "", + ); + + b.iter(|| Parser::new_ext(input, Options::empty()).count()); + }); + + c.bench_function("html_block_type_5", |b| { + let input = concat!( + "", + ); + + b.iter(|| Parser::new_ext(input, Options::empty()).count()); + }); + + c.bench_function("html_block_type_6", |b| { + let input = concat!( + "\n", + include_str!("../third_party/markdown-it/lorem1.md"), + ); + + b.iter(|| Parser::new_ext(input, Options::empty()).count()); + }); } criterion_group!(benches, criterion_benchmark); diff --git a/pulldown-cmark/specs/regression.txt b/pulldown-cmark/specs/regression.txt index f30ce13b..d1dc42f3 100644 --- a/pulldown-cmark/specs/regression.txt +++ b/pulldown-cmark/specs/regression.txt @@ -3338,3 +3338,73 @@ Bug reported in , bu ```````````````````````````````` + +The end condition of an HTML block that starts with ` +alert(1) + + +# Title + +
text
+*emphasis* +. + +

Title

+
text
+

emphasis

+```````````````````````````````` + +The end tag need not match the start tag: any of ``, ``, ``, +`` ends a block started by any of the four. + +```````````````````````````````` example + +*b* +. +", "", ""]; + +/// Whether `line` meets the end condition of an HTML block whose start gave `html_end_tag`. +/// For blocks started by ``, `?>`, `]]>`, `>`) have no letters. +fn ends_html_block(line: &[u8], block_type: HtmlBlockType1Through5) -> bool { + let end_condition_without_gt: &[u8] = match block_type { + HtmlBlockType1Through5::Type1 => { + // Every type 1 end tag starts with `<`: try all four at each `<` in one pass. + return memchr::memchr_iter(b'<', line).any(|i| { + HTML_BLOCK_TYPE_1_END_TAGS.iter().any(|tag| { + line.get(i..i + tag.len()).map_or(false, |candidate| { + candidate.eq_ignore_ascii_case(tag.as_bytes()) + }) + }) + }); + } + HtmlBlockType1Through5::Type2 => b"--", + HtmlBlockType1Through5::Type3 => b"?", + HtmlBlockType1Through5::Type4 => b"", + HtmlBlockType1Through5::Type5 => b"]]", + }; + memchr::memchr_iter(b'>', line).any(|i| { + line[..i].ends_with(end_condition_without_gt) + }) +} + +/// +#[derive(Clone, Copy)] +pub(crate) enum HtmlBlockType1Through5 { + /// Start condition: line begins with the string , or the end of the line. + /// + /// End condition: line contains an end tag , , , or + /// (case-insensitive; it need not match the start tag). + Type1, + /// Start condition: line begins with the string . + Type2, + /// Start condition: line begins with the string . + Type3, + /// Start condition: line begins with the string . + Type4, + /// Start condition: line begins with the string . + Type5, +} + /// Assumes `text_bytes` is preceded by `<`. -fn get_html_end_tag(text_bytes: &[u8]) -> Option<&'static str> { +fn get_html_block_type_1_through_5(text_bytes: &[u8]) -> Option { + use HtmlBlockType1Through5::*; static BEGIN_TAGS: &[&[u8]; 4] = &[b"pre", b"style", b"script", b"textarea"]; static ST_BEGIN_TAGS: &[&[u8]; 3] = &[b"!--", b"?", b"![CDATA["]; - for (beg_tag, end_tag) in BEGIN_TAGS - .iter() - .zip(["", "", "", ""].iter()) - { + for beg_tag in BEGIN_TAGS { let tag_len = beg_tag.len(); if text_bytes.len() < tag_len { @@ -2420,24 +2473,24 @@ fn get_html_end_tag(text_bytes: &[u8]) -> Option<&'static str> { // Must either be the end of the line... if text_bytes.len() == tag_len { - return Some(end_tag); + return Some(Type1); } // ...or be followed by whitespace, newline, or '>'. let s = text_bytes[tag_len]; if is_ascii_whitespace(s) || s == b'>' { - return Some(end_tag); + return Some(Type1); } } - for (beg_tag, end_tag) in ST_BEGIN_TAGS.iter().zip(["-->", "?>", "]]>"].iter()) { + for (beg_tag, block_type) in ST_BEGIN_TAGS.iter().zip([Type2, Type3, Type5].iter()) { if text_bytes.starts_with(beg_tag) { - return Some(end_tag); + return Some(*block_type); } } if text_bytes.len() > 1 && text_bytes[0] == b'!' && text_bytes[1].is_ascii_alphabetic() { - Some(">") + Some(Type4) } else { None } diff --git a/pulldown-cmark/tests/suite/regression.rs b/pulldown-cmark/tests/suite/regression.rs index c703fbad..55f6f5bb 100644 --- a/pulldown-cmark/tests/suite/regression.rs +++ b/pulldown-cmark/tests/suite/regression.rs @@ -3965,3 +3965,85 @@ fn regression_test_249() { test_markdown_html(original, expected, false, false, false, false, false, false, false, false, false); } + +#[test] +fn regression_test_250() { + let original = r##" + +# Title + +
text
+*emphasis* +"##; + let expected = r##" +

Title

+
text
+

emphasis

+"##; + + test_markdown_html(original, expected, false, false, false, false, false, false, false, false, false); +} + +#[test] +fn regression_test_251() { + let original = r##" +*b* +"##; + let expected = r##"