Refactor HTML block syntax (dart-lang/markdown#458)
* Refactor HTML blocks syntax
* Update a comment
* Update some comments
* Put some files back and add deprecation annotation
* Update CHANGELOG.md
* Fix some comments
* Update a comment
* Export HtmlBlockSyntax
* Expand some comments
diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md
index 9125fbe..8900c14 100644
--- a/pkgs/markdown/CHANGELOG.md
+++ b/pkgs/markdown/CHANGELOG.md
@@ -2,6 +2,10 @@
* Fix a crash in checkbox lists when mixing checkbox items with
non-checkbox items.
+* Add a new syntax `BlockHtmlSyntax` to parse HTML blocks.
+* Deprecate `BlockTagBlockHtmlSyntax`, `LongBlockHtmlSyntax` and
+ `OtherTagBlockHtmlSyntax`. These syntaxes will be removed from the next major
+ version.
## 6.0.0
diff --git a/pkgs/markdown/lib/markdown.dart b/pkgs/markdown/lib/markdown.dart
index 6155242..de6792d 100644
--- a/pkgs/markdown/lib/markdown.dart
+++ b/pkgs/markdown/lib/markdown.dart
@@ -40,7 +40,6 @@
export 'src/block_parser.dart';
export 'src/block_syntaxes/block_html_syntax.dart';
export 'src/block_syntaxes/block_syntax.dart';
-export 'src/block_syntaxes/block_tag_block_html_syntax.dart';
export 'src/block_syntaxes/blockquote_syntax.dart';
export 'src/block_syntaxes/code_block_syntax.dart';
export 'src/block_syntaxes/dummy_block_syntax.dart';
@@ -50,11 +49,10 @@
export 'src/block_syntaxes/header_syntax.dart';
export 'src/block_syntaxes/header_with_id_syntax.dart';
export 'src/block_syntaxes/horizontal_rule_syntax.dart';
+export 'src/block_syntaxes/html_block_syntax.dart';
export 'src/block_syntaxes/list_syntax.dart';
-export 'src/block_syntaxes/long_block_html_syntax.dart';
export 'src/block_syntaxes/ordered_list_syntax.dart';
export 'src/block_syntaxes/ordered_list_with_checkbox_syntax.dart';
-export 'src/block_syntaxes/other_tag_block_html_syntax.dart';
export 'src/block_syntaxes/paragraph_syntax.dart';
export 'src/block_syntaxes/setext_header_syntax.dart';
export 'src/block_syntaxes/setext_header_with_id_syntax.dart';
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index cb88d8e..beaa46a 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -4,16 +4,14 @@
import 'ast.dart';
import 'block_syntaxes/block_syntax.dart';
-import 'block_syntaxes/block_tag_block_html_syntax.dart';
import 'block_syntaxes/blockquote_syntax.dart';
import 'block_syntaxes/code_block_syntax.dart';
import 'block_syntaxes/dummy_block_syntax.dart';
import 'block_syntaxes/empty_block_syntax.dart';
import 'block_syntaxes/header_syntax.dart';
import 'block_syntaxes/horizontal_rule_syntax.dart';
-import 'block_syntaxes/long_block_html_syntax.dart';
+import 'block_syntaxes/html_block_syntax.dart';
import 'block_syntaxes/ordered_list_syntax.dart';
-import 'block_syntaxes/other_tag_block_html_syntax.dart';
import 'block_syntaxes/paragraph_syntax.dart';
import 'block_syntaxes/setext_header_syntax.dart';
import 'block_syntaxes/unordered_list_syntax.dart';
@@ -43,15 +41,7 @@
/// The collection of built-in block parsers.
final List<BlockSyntax> standardBlockSyntaxes = [
const EmptyBlockSyntax(),
- const BlockTagBlockHtmlSyntax(),
- LongBlockHtmlSyntax(r'^ {0,3}<pre(?:\s|>|$)', '</pre>'),
- LongBlockHtmlSyntax(r'^ {0,3}<script(?:\s|>|$)', '</script>'),
- LongBlockHtmlSyntax(r'^ {0,3}<style(?:\s|>|$)', '</style>'),
- LongBlockHtmlSyntax('^ {0,3}<!--', '-->'),
- LongBlockHtmlSyntax(r'^ {0,3}<\?', r'\?>'),
- LongBlockHtmlSyntax('^ {0,3}<![A-Z]', '>'),
- LongBlockHtmlSyntax(r'^ {0,3}<!\[CDATA\[', r'\]\]>'),
- const OtherTagBlockHtmlSyntax(),
+ const HtmlBlockSyntax(),
const SetextHeaderSyntax(),
const HeaderSyntax(),
const CodeBlockSyntax(),
diff --git a/pkgs/markdown/lib/src/block_syntaxes/block_tag_block_html_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/block_tag_block_html_syntax.dart
index e017696..7e40ac9 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/block_tag_block_html_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/block_tag_block_html_syntax.dart
@@ -7,6 +7,7 @@
import '../patterns.dart';
import 'block_html_syntax.dart';
+@Deprecated('Use HtmlBlockSyntax instead')
class BlockTagBlockHtmlSyntax extends BlockHtmlSyntax {
static final _pattern = RegExp(
'^ {0,3}</?(?:address|article|aside|base|basefont|blockquote|body|'
diff --git a/pkgs/markdown/lib/src/block_syntaxes/html_block_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/html_block_syntax.dart
new file mode 100644
index 0000000..a25da4b
--- /dev/null
+++ b/pkgs/markdown/lib/src/block_syntaxes/html_block_syntax.dart
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../ast.dart';
+import '../block_parser.dart';
+import '../patterns.dart';
+import 'block_syntax.dart';
+
+/// Parse HTML blocks.
+// There are seven kinds of HTML block defined in the CommonMark spec:
+// https://spec.commonmark.org/0.30/#html-blocks.
+// These matching conditions and HTML block types mentioned in this syntax
+// correspond to these ones in the CommonMark spec.
+class HtmlBlockSyntax extends BlockSyntax {
+ @override
+ RegExp get pattern => htmlBlockPattern;
+
+ // All types of HTML blocks except type 7 may interrupt a paragraph, see the
+ // second paragraph after https://spec.commonmark.org/0.30/#example-148 for
+ // more detail.
+ @override
+ bool canEndBlock(BlockParser parser) =>
+ pattern.firstMatch(parser.current)!.namedGroup('condition_7') == null;
+
+ static final _endConditions = [
+ // For condition 1, it does not need to match the start tag, see
+ // https://spec.commonmark.org/0.30/#end-condition
+ RegExp('</(?:pre|script|style|textarea)>', caseSensitive: false),
+ RegExp('-->'),
+ RegExp(r'\?>'),
+ RegExp('>'),
+ RegExp(']]>'),
+ emptyPattern,
+ emptyPattern,
+ ];
+
+ const HtmlBlockSyntax();
+
+ @override
+ List<String> parseChildLines(BlockParser parser) {
+ final lines = <String>[];
+
+ final match = pattern.firstMatch(parser.current);
+ var matchedCondition = 0;
+ for (var i = 0; i < match!.groupCount; i++) {
+ if (match.group(i + 1) != null) {
+ matchedCondition = i;
+ break;
+ }
+ }
+
+ final endCondition = _endConditions[matchedCondition];
+ if (endCondition == emptyPattern) {
+ lines.add(parser.current);
+ parser.advance();
+
+ while (!parser.isDone && !endCondition.hasMatch(parser.current)) {
+ lines.add(parser.current);
+ parser.advance();
+ }
+ } else {
+ while (!parser.isDone) {
+ lines.add(parser.current);
+ if (endCondition.hasMatch(parser.current)) {
+ break;
+ }
+ parser.advance();
+ }
+ parser.advance();
+ }
+
+ // If the following lines start an HTML block again, put them together with
+ // current HTML block.
+ if (!parser.isDone &&
+ parser.next != null &&
+ pattern.hasMatch(parser.next!)) {
+ parser.advance();
+ lines.addAll(parseChildLines(parser));
+ }
+
+ return lines;
+ }
+
+ @override
+ Node parse(BlockParser parser) {
+ final childLines = parseChildLines(parser);
+ return Text(childLines.join('\n').trimRight());
+ }
+}
diff --git a/pkgs/markdown/lib/src/block_syntaxes/long_block_html_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/long_block_html_syntax.dart
index 8332bce..89e294a 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/long_block_html_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/long_block_html_syntax.dart
@@ -10,6 +10,7 @@
///
/// In practice this means that the syntax dominates; it is allowed to eat
/// many lines, including blank lines, before matching its `endPattern`.
+@Deprecated('Use HtmlBlockSyntax instead')
class LongBlockHtmlSyntax extends BlockHtmlSyntax {
@override
final RegExp pattern;
diff --git a/pkgs/markdown/lib/src/block_syntaxes/other_tag_block_html_syntax.dart b/pkgs/markdown/lib/src/block_syntaxes/other_tag_block_html_syntax.dart
index edb5bfb..bcdc5c7 100644
--- a/pkgs/markdown/lib/src/block_syntaxes/other_tag_block_html_syntax.dart
+++ b/pkgs/markdown/lib/src/block_syntaxes/other_tag_block_html_syntax.dart
@@ -5,6 +5,7 @@
import '../block_parser.dart';
import 'block_tag_block_html_syntax.dart';
+@Deprecated('Use HtmlBlockSyntax instead')
class OtherTagBlockHtmlSyntax extends BlockTagBlockHtmlSyntax {
@override
bool canEndBlock(BlockParser parser) => false;
diff --git a/pkgs/markdown/lib/src/patterns.dart b/pkgs/markdown/lib/src/patterns.dart
index 5602630..71b2570 100644
--- a/pkgs/markdown/lib/src/patterns.dart
+++ b/pkgs/markdown/lib/src/patterns.dart
@@ -124,3 +124,77 @@
/// A pattern which should never be used. It just satisfies non-nullability of
/// pattern fields.
final dummyPattern = RegExp('');
+
+/// A [String] pattern to match a named tag like `<table>` or `</table>`.
+const _namedTagDefinition =
+ // Opening tag begins.
+ '<'
+
+ // Tag name.
+ '[a-z][a-z0-9-]*'
+
+ // Attribute begins, see
+ // https://spec.commonmark.org/0.30/#attribute.
+ r'(?:\s+'
+
+ // Attribute name, see
+ // https://spec.commonmark.org/0.30/#attribute-name.
+ '[a-z_:][a-z0-9._:-]*'
+
+ //
+ '(?:'
+ // Attribute value specification, see
+ // https://spec.commonmark.org/0.30/#attribute-value-specification.
+ r'\s*=\s*'
+
+ // Attribute value, see
+ // https://spec.commonmark.org/0.30/#unquoted-attribute-value.
+ r'''(?:[^\s"'=<>`]+?|'[^']*?'|"[^"]*?")'''
+
+ // Attribute ends.
+ ')?)*'
+
+ // Opening tag ends.
+ r'\s*/?>'
+
+ // Or
+ '|'
+
+ // Closing tag, see
+ // https://spec.commonmark.org/0.30/#closing-tag.
+ r'</[a-z][a-z0-9-]*\s*>';
+
+/// A pattern to match the start of an HTML block.
+///
+/// The 7 conditions here correspond to the 7 start conditions in the Commonmark
+/// specification one by one: https://spec.commonmark.org/0.30/#html-block.
+final htmlBlockPattern = RegExp(
+ '^ {0,3}(?:'
+ '<(?<condition_1>pre|script|style|textarea)'
+ r'(?:\s|>|$)'
+ '|'
+ '(?<condition_2><!--)'
+ '|'
+ r'(?<condition_3><\?)'
+ '|'
+ '(?<condition_4><![a-z])'
+ '|'
+ r'(?<condition_5><!\[CDATA\[)'
+ '|'
+ '</?(?<condition_6>address|article|aside|base|basefont|blockquote|body|'
+ 'caption|center|col|colgroup|dd|details|dialog|dir|DIV|dl|dt|fieldset|'
+ 'figcaption|figure|footer|form|frame|frameset|h1|h2|h3|h4|h5|h6|head|'
+ 'header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|'
+ 'optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|'
+ 'thead|title|tr|track|ul)'
+ r'(?:\s|>|/>|$)'
+ '|'
+
+ // Here we are more restrictive than the Commonmark definition (Rule #7).
+ // Otherwise some raw HTML test cases will fail, for example:
+ // https://spec.commonmark.org/0.30/#example-618.
+ // Because if a line is treated as an HTML block, it will output as Text node
+ // directly, the RawHtmlSyntax does not have a chance to validate if this
+ // HTML tag is legal or not.
+ '(?<condition_7>(?:$_namedTagDefinition)\\s*\$))',
+ caseSensitive: false);
diff --git a/pkgs/markdown/test/common_mark/html_blocks.unit b/pkgs/markdown/test/common_mark/html_blocks.unit
index f9afb32..54b229c 100644
--- a/pkgs/markdown/test/common_mark/html_blocks.unit
+++ b/pkgs/markdown/test/common_mark/html_blocks.unit
@@ -210,8 +210,12 @@
</textarea>
<<<
<textarea>
-<p><em>foo</em></p>
-<p><em>bar</em></p></textarea>
+
+*foo*
+
+_bar_
+
+</textarea>
>>> HTML blocks - 172
<style
type="text/css">
@@ -407,9 +411,13 @@
</table>
<<<
-<table><tr><td>
+<table>
+<tr>
+<td>
Hi
-</td></tr></table>
+</td>
+</tr>
+</table>
>>> HTML blocks - 191
<table>
@@ -423,8 +431,10 @@
</table>
<<<
-<table> <tr>
+<table>
+ <tr>
<pre><code><td>
Hi
</td>
-</code></pre> </tr></table>
+</code></pre> </tr>
+</table>
diff --git a/pkgs/markdown/test/common_mark/raw_html.unit b/pkgs/markdown/test/common_mark/raw_html.unit
index f1fa258..a91a33f 100644
--- a/pkgs/markdown/test/common_mark/raw_html.unit
+++ b/pkgs/markdown/test/common_mark/raw_html.unit
@@ -29,7 +29,7 @@
>>> Raw HTML - 618
<a h*#ref="hi">
<<<
-<a h*#ref="hi">
+<p><a h*#ref="hi"></p>
>>> Raw HTML - 619
<a href="hi'> <a href=hi'>
<<<
@@ -47,7 +47,7 @@
>>> Raw HTML - 621
<a href='bar'title=title>
<<<
-<a href='bar'title=title>
+<p><a href='bar'title=title></p>
>>> Raw HTML - 622
</a></foo >
<<<
@@ -55,7 +55,7 @@
>>> Raw HTML - 623
</a href="foo">
<<<
-</a href="foo">
+<p></a href="foo"></p>
>>> Raw HTML - 624
foo <!-- this is a
comment - with hyphen -->
@@ -96,4 +96,4 @@
>>> Raw HTML - 632
<a href="\"">
<<<
-<a href="\"">
+<p><a href="\""></p>
diff --git a/pkgs/markdown/test/gfm/html_blocks.unit b/pkgs/markdown/test/gfm/html_blocks.unit
index 78c47ab..f2562f4 100644
--- a/pkgs/markdown/test/gfm/html_blocks.unit
+++ b/pkgs/markdown/test/gfm/html_blocks.unit
@@ -395,9 +395,13 @@
</table>
<<<
-<table><tr><td>
+<table>
+<tr>
+<td>
Hi
-</td></tr></table>
+</td>
+</tr>
+</table>
>>> HTML blocks - 160
<table>
@@ -411,8 +415,10 @@
</table>
<<<
-<table> <tr>
+<table>
+ <tr>
<pre><code><td>
Hi
</td>
-</code></pre> </tr></table>
+</code></pre> </tr>
+</table>
diff --git a/pkgs/markdown/test/gfm/raw_html.unit b/pkgs/markdown/test/gfm/raw_html.unit
index f693be6..aec88ab 100644
--- a/pkgs/markdown/test/gfm/raw_html.unit
+++ b/pkgs/markdown/test/gfm/raw_html.unit
@@ -29,7 +29,7 @@
>>> Raw HTML - 638
<a h*#ref="hi">
<<<
-<a h*#ref="hi">
+<p><a h*#ref="hi"></p>
>>> Raw HTML - 639
<a href="hi'> <a href=hi'>
<<<
@@ -47,7 +47,7 @@
>>> Raw HTML - 641
<a href='bar'title=title>
<<<
-<a href='bar'title=title>
+<p><a href='bar'title=title></p>
>>> Raw HTML - 642
</a></foo >
<<<
@@ -55,7 +55,7 @@
>>> Raw HTML - 643
</a href="foo">
<<<
-</a href="foo">
+<p></a href="foo"></p>
>>> Raw HTML - 644
foo <!-- this is a
comment - with hyphen -->
@@ -96,4 +96,4 @@
>>> Raw HTML - 652
<a href="\"">
<<<
-<a href="\"">
+<p><a href="\""></p>
diff --git a/pkgs/markdown/tool/common_mark_stats.json b/pkgs/markdown/tool/common_mark_stats.json
index be58d4a..cdaa19a 100644
--- a/pkgs/markdown/tool/common_mark_stats.json
+++ b/pkgs/markdown/tool/common_mark_stats.json
@@ -333,7 +333,7 @@
"168": "strict",
"169": "strict",
"170": "strict",
- "171": "loose",
+ "171": "strict",
"172": "strict",
"173": "strict",
"174": "loose",
@@ -352,7 +352,7 @@
"187": "strict",
"188": "loose",
"189": "strict",
- "190": "loose",
+ "190": "strict",
"191": "loose"
},
"Images": {
@@ -620,7 +620,7 @@
"620": "fail",
"621": "fail",
"622": "strict",
- "623": "fail",
+ "623": "loose",
"624": "loose",
"625": "strict",
"626": "strict",
diff --git a/pkgs/markdown/tool/common_mark_stats.txt b/pkgs/markdown/tool/common_mark_stats.txt
index 023259f..1ab5d03 100644
--- a/pkgs/markdown/tool/common_mark_stats.txt
+++ b/pkgs/markdown/tool/common_mark_stats.txt
@@ -18,11 +18,11 @@
22 of 26 – 84.6% Lists
8 of 8 – 100.0% Paragraphs
1 of 1 – 100.0% Precedence
- 15 of 21 – 71.4% Raw HTML
+ 16 of 21 – 76.2% Raw HTML
26 of 27 – 96.3% Setext headings
2 of 2 – 100.0% Soft line breaks
11 of 11 – 100.0% Tabs
3 of 3 – 100.0% Textual content
19 of 19 – 100.0% Thematic breaks
- 614 of 652 – 94.2% TOTAL
- 547 of 614 – 89.1% TOTAL Strict
+ 615 of 652 – 94.3% TOTAL
+ 549 of 615 – 89.3% TOTAL Strict
diff --git a/pkgs/markdown/tool/gfm_stats.json b/pkgs/markdown/tool/gfm_stats.json
index ffbe9bd..3ef3e95 100644
--- a/pkgs/markdown/tool/gfm_stats.json
+++ b/pkgs/markdown/tool/gfm_stats.json
@@ -367,7 +367,7 @@
"156": "strict",
"157": "loose",
"158": "strict",
- "159": "loose",
+ "159": "strict",
"160": "loose"
},
"Images": {
@@ -633,7 +633,7 @@
"640": "fail",
"641": "fail",
"642": "strict",
- "643": "fail",
+ "643": "loose",
"644": "loose",
"645": "strict",
"646": "strict",
diff --git a/pkgs/markdown/tool/gfm_stats.txt b/pkgs/markdown/tool/gfm_stats.txt
index b82976e..e18f44f 100644
--- a/pkgs/markdown/tool/gfm_stats.txt
+++ b/pkgs/markdown/tool/gfm_stats.txt
@@ -20,7 +20,7 @@
22 of 26 – 84.6% Lists
8 of 8 – 100.0% Paragraphs
1 of 1 – 100.0% Precedence
- 15 of 21 – 71.4% Raw HTML
+ 16 of 21 – 76.2% Raw HTML
26 of 27 – 96.3% Setext headings
2 of 2 – 100.0% Soft line breaks
2 of 2 – 100.0% Strikethrough (extension)
@@ -28,5 +28,5 @@
11 of 11 – 100.0% Tabs
3 of 3 – 100.0% Textual content
19 of 19 – 100.0% Thematic breaks
- 626 of 671 – 93.3% TOTAL
- 551 of 626 – 88.0% TOTAL Strict
+ 627 of 671 – 93.4% TOTAL
+ 552 of 627 – 88.0% TOTAL Strict