Repository: raphlinus/pulldown-cmark (rust)

Issue:
Parsing `"<!e -hhl"` gives text, not HTML
Events from pulldown-cmark:

```rust
"<!e -hhl" -> [
  Start(Paragraph)
    Text(Boxed("<!e -hhl"))
  End(Paragraph)
]
```

Events from commonmark.js:

```rust
"<!e -hhl" -> [
  Html(Boxed("<!e -hhl"))
]
```

XML from commonmark.js:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE document SYSTEM "CommonMark.dtd">
<document xmlns="http://commonmark.org/xml/1.0">
  <html_block>&lt;!e -hhl</html_block>
</document>
```

This looks like [start condition 4](https://spec.commonmark.org/0.30/#html-blocks) in the specification. 

I found this by testing against revision 20b52e5 of https://github.com/commonmark/commonmark.js/ — I get the same result as pulldown-cmark when I test against the code in the [dingus](https://spec.commonmark.org/dingus/?text=%3C!e%20-hhl).

Pull request description:
Allow lowercase letters in HTML declarations
This was changed in the commonmark spec in https://github.com/commonmark/commonmark-spec/issues/620 (included in commonmark 0.30)

Fixes #659

Patch:
diff --git a/specs/regression.txt b/specs/regression.txt
index 7a3ce74..b3f3642 100644
--- a/specs/regression.txt
+++ b/specs/regression.txt
@@ -1054,3 +1054,12 @@ ISSUE 656
 →|-----|------|
 | baz | alef |</p>
 ````````````````````````````````
+
+
+ISSUE 659
+
+```````````````````````````````` example
+<!doctype html>
+.
+<!doctype html>
+````````````````````````````````
diff --git a/src/firstpass.rs b/src/firstpass.rs
index 091fd4d..96c73f1 100644
--- a/src/firstpass.rs
+++ b/src/firstpass.rs
@@ -1466,8 +1466,7 @@ fn get_html_end_tag(text_bytes: &[u8]) -> Option<&'static str> {
 
     if text_bytes.len() > 1
         && text_bytes[0] == b'!'
-        && text_bytes[1] >= b'A'
-        && text_bytes[1] <= b'Z'
+        && text_bytes[1].is_ascii_alphabetic()
     {
         Some(">")
     } else {
diff --git a/src/scanners.rs b/src/scanners.rs
index 6141f32..5a04bfb 100644
--- a/src/scanners.rs
+++ b/src/scanners.rs
@@ -1312,6 +1312,8 @@ pub(crate) fn scan_inline_html_comment(
     let c = *bytes.get(ix)?;
     ix += 1;
     match c {
+        // An HTML comment consists of `<!--` + text + `-->`, where text does not start with `>`
+        // or `->`, does not end with -, and does not contain --.
         b'-' => {
             let dashes = scan_ch_repeat(&bytes[ix..], b'-');
             if dashes < 1 {
@@ -1336,6 +1338,8 @@ pub(crate) fn scan_inline_html_comment(
             }
             None
         }
+        // A CDATA section consists of the string `<![CDATA[`, a string of characters not
+        // including the string `]]>`, and the string `]]>`.
         b'[' if bytes[ix..].starts_with(b"CDATA[") && ix > scan_guard.cdata => {
             ix += b"CDATA[".len();
             ix = memchr(b']', &bytes[ix..]).map_or(bytes.len(), |x| ix + x);
@@ -1349,14 +1353,9 @@ pub(crate) fn scan_inline_html_comment(
                 Some(ix + 1)
             }
         }
-        b'A'..=b'Z' if ix > scan_guard.declaration => {
-            // Scan declaration.
-            ix += scan_while(&bytes[ix..], |c| c >= b'A' && c <= b'Z');
-            let whitespace = scan_while(&bytes[ix..], is_ascii_whitespace);
-            if whitespace == 0 {
-                return None;
-            }
-            ix += whitespace;
+        // A declaration consists of the string `<!`, an ASCII letter, zero or more characters not
+        // including the character >, and the character >.
+        _ if c.is_ascii_alphabetic() && ix > scan_guard.declaration => {
             ix = memchr(b'>', &bytes[ix..]).map_or(bytes.len(), |x| ix + x);
             if scan_ch(&bytes[ix..], b'>') == 0 {
                 scan_guard.declaration = ix;


Test patch:
diff --git a/tests/suite/regression.rs b/tests/suite/regression.rs
index 950de79..8411550 100644
--- a/tests/suite/regression.rs
+++ b/tests/suite/regression.rs
@@ -1241,3 +1241,13 @@ fn regression_test_83() {
 
     test_markdown_html(original, expected, false, false, false);
 }
+
+#[test]
+fn regression_test_84() {
+    let original = r##"<!doctype html>
+"##;
+    let expected = r##"<!doctype html>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}