Add an `enableTagfilter` option to `HtmlRenderer` to eanble GFM `tagfilter` extension (dart-lang/markdown#447)

* Add GFM Disallowed Raw HTML support

* Fix errors caused by rebase.

* Update CHANGELOG.md
diff --git a/pkgs/markdown/CHANGELOG.md b/pkgs/markdown/CHANGELOG.md
index 47cb1a5..469bf8b 100644
--- a/pkgs/markdown/CHANGELOG.md
+++ b/pkgs/markdown/CHANGELOG.md
@@ -10,6 +10,8 @@
 * **Breaking change**: Change the `lines` properties of type `List<String>` to
   `List<Line>`.
 * Add a new syntax `HtmlBlockSyntax` to parse HTML blocks.
+* Add an `enableTagfilter` option to `HtmlRenderer` to eanble GFM `tagfilter`
+  extension.
 * Add a new syntax `DecodeHtmlSyntax` to decode HTML entity and numeric
   character references.
 * Add a new syntax `SoftLineBreakSyntax` to remove the single space before the
diff --git a/pkgs/markdown/lib/src/html_renderer.dart b/pkgs/markdown/lib/src/html_renderer.dart
index 6ff803d..404d870 100644
--- a/pkgs/markdown/lib/src/html_renderer.dart
+++ b/pkgs/markdown/lib/src/html_renderer.dart
@@ -20,6 +20,7 @@
   Resolver? imageLinkResolver,
   bool inlineOnly = false,
   bool encodeHtml = true,
+  bool enableTagfilter = false,
   bool withDefaultBlockSyntaxes = true,
   bool withDefaultInlineSyntaxes = true,
 }) {
@@ -38,11 +39,14 @@
 
   final nodes = document.parse(markdown);
 
-  return '${renderToHtml(nodes)}\n';
+  return '${renderToHtml(nodes, enableTagfilter: enableTagfilter)}\n';
 }
 
 /// Renders [nodes] to HTML.
-String renderToHtml(List<Node> nodes) => HtmlRenderer().render(nodes);
+String renderToHtml(List<Node> nodes, {bool enableTagfilter = false}) =>
+    HtmlRenderer(
+      enableTagfilter: enableTagfilter,
+    ).render(nodes);
 
 const _blockTags = [
   'blockquote',
@@ -89,8 +93,11 @@
 
   final _elementStack = <Element>[];
   String? _lastVisitedTag;
+  final bool _tagfilterEnabled;
 
-  HtmlRenderer();
+  HtmlRenderer({
+    bool enableTagfilter = false,
+  }) : _tagfilterEnabled = enableTagfilter;
 
   String render(List<Node> nodes) {
     buffer = StringBuffer();
@@ -106,6 +113,10 @@
   @override
   void visitText(Text text) {
     var content = text.textContent;
+
+    if (_tagfilterEnabled) {
+      content = _filterTags(content);
+    }
     if (const ['br', 'p', 'li'].contains(_lastVisitedTag)) {
       final lines = LineSplitter.split(content);
       content = content.contains('<pre>')
@@ -190,4 +201,18 @@
     uniqueIds.add(suffixedId);
     return suffixedId;
   }
+
+  /// Filters some particular tags, see:
+  /// https://github.github.com/gfm/#disallowed-raw-html-extension-
+  // As said in the specification, this process should happen when rendering
+  // HTML output, so there should not be a dedicated syntax for this extension.
+  String _filterTags(String content) => content.replaceAll(
+      RegExp(
+        '<(?=(?:'
+        'title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext'
+        ')>)',
+        caseSensitive: false,
+        multiLine: true,
+      ),
+      '&lt;');
 }
diff --git a/pkgs/markdown/test/gfm/disallowed_raw_html_extension.unit b/pkgs/markdown/test/gfm/disallowed_raw_html_extension.unit
index cef4e62..f39b4b7 100644
--- a/pkgs/markdown/test/gfm/disallowed_raw_html_extension.unit
+++ b/pkgs/markdown/test/gfm/disallowed_raw_html_extension.unit
@@ -5,6 +5,6 @@
   <xmp> is disallowed.  <XMP> is also disallowed.
 </blockquote>
 <<<
-<p><strong> <title> <style> <em></p><blockquote>
-<xmp> is disallowed.  <XMP> is also disallowed.
+<p><strong> &lt;title> &lt;style> <em></p><blockquote>
+&lt;xmp> is disallowed.  &lt;XMP> is also disallowed.
 </blockquote>
diff --git a/pkgs/markdown/test/util.dart b/pkgs/markdown/test/util.dart
index fba598d..2c2cb88 100644
--- a/pkgs/markdown/test/util.dart
+++ b/pkgs/markdown/test/util.dart
@@ -17,6 +17,7 @@
 
     final inlineSyntaxes = <InlineSyntax>[];
     final blockSyntaxes = <BlockSyntax>[];
+    var enableTagfilter = false;
 
     if (dataCase.file.endsWith('_extension')) {
       final extension = dataCase.file.substring(
@@ -34,7 +35,7 @@
           blockSyntaxes.add(const TableSyntax());
           break;
         case 'disallowed_raw_html':
-          // TODO(Zhiguang): https://github.com/dart-lang/markdown/pull/447
+          enableTagfilter = true;
           break;
         default:
           throw UnimplementedError('Unimplemented extension "$extension"');
@@ -48,6 +49,7 @@
       extensionSet: extensionSet,
       inlineSyntaxes: inlineSyntaxes,
       blockSyntaxes: blockSyntaxes,
+      enableTagfilter: enableTagfilter,
     );
   }
 }
@@ -81,6 +83,7 @@
   Resolver? linkResolver,
   Resolver? imageLinkResolver,
   bool inlineOnly = false,
+  bool enableTagfilter = false,
 }) {
   test(description, () {
     final result = markdownToHtml(
@@ -91,6 +94,7 @@
       linkResolver: linkResolver,
       imageLinkResolver: imageLinkResolver,
       inlineOnly: inlineOnly,
+      enableTagfilter: enableTagfilter,
     );
 
     markdownPrintOnFailure(markdown, html, result);
diff --git a/pkgs/markdown/tool/stats_lib.dart b/pkgs/markdown/tool/stats_lib.dart
index c1298f1..a747216 100644
--- a/pkgs/markdown/tool/stats_lib.dart
+++ b/pkgs/markdown/tool/stats_lib.dart
@@ -128,6 +128,8 @@
   bool verboseLooseMatch = false,
   Set<String> extensions = const {},
 }) {
+  var enabletagfilter = false;
+
   String output;
   final inlineSyntaxes = <InlineSyntax>[];
   final blockSyntaxes = <BlockSyntax>[];
@@ -144,7 +146,7 @@
         blockSyntaxes.add(const TableSyntax());
         break;
       case 'tagfilter':
-        // TODO(Zhiguang): https://github.com/dart-lang/markdown/pull/447
+        enabletagfilter = true;
         break;
       default:
         throw UnimplementedError('Unimplemented extension "$extension"');
@@ -156,6 +158,7 @@
       testCase.markdown,
       inlineSyntaxes: inlineSyntaxes,
       blockSyntaxes: blockSyntaxes,
+      enableTagfilter: enabletagfilter,
     );
   } catch (err, stackTrace) {
     if (throwOnError) {