Merge pull request dart-lang/markdown#97 from srawlins/better-block-html

Improve the block HTML identification
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart
index 566a9d6..ee8ada4 100644
--- a/pkgs/markdown/lib/src/block_parser.dart
+++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -34,10 +34,6 @@
 /// SETEXT should win.
 final _hrPattern = new RegExp(r'^ {0,3}([-*_]) *\1 *\1(?:\1| )*$');
 
-/// Really hacky way to detect block-level embedded HTML. Just looks for
-/// "<somename".
-final _htmlPattern = new RegExp(r'^<[ ]*\w+[ >]');
-
 /// A line starting with one of these markers: `-`, `*`, `+`. May have up to
 /// three leading spaces before the marker and any number of spaces or tabs
 /// after.
@@ -65,9 +61,17 @@
   int _pos = 0;
 
   /// The collection of built-in block parsers.
-  final List<BlockSyntax> standardBlockSyntaxes = const [
+  final List<BlockSyntax> standardBlockSyntaxes = [
     const EmptyBlockSyntax(),
-    const BlockHtmlSyntax(),
+    const BlockTagBlockHtmlSyntax(),
+    new LongBlockHtmlSyntax(r'^ {0,3}<pre(?:\s|>|$)', '</pre>'),
+    new LongBlockHtmlSyntax(r'^ {0,3}<script(?:\s|>|$)', '</script>'),
+    new LongBlockHtmlSyntax(r'^ {0,3}<style(?:\s|>|$)', '</style>'),
+    new LongBlockHtmlSyntax('^ {0,3}<!--', '-->'),
+    new LongBlockHtmlSyntax('^ {0,3}<\\?', '\\?>'),
+    new LongBlockHtmlSyntax('^ {0,3}<![A-Z]', '>'),
+    new LongBlockHtmlSyntax('^ {0,3}<!\\[CDATA\\[', '\\]\\]>'),
+    const OtherTagBlockHtmlSyntax(),
     const SetextHeaderSyntax(),
     const HeaderSyntax(),
     const CodeBlockSyntax(),
@@ -387,18 +391,25 @@
 /// implementations in several ways:
 ///
 /// 1.  This one is way way WAY simpler.
-/// 2.  All HTML tags at the block level will be treated as blocks. If you
-///     start a paragraph with `<em>`, it will not wrap it in a `<p>` for you.
-///     As soon as it sees something like HTML, it stops mucking with it until
-///     it hits the next block.
-/// 3.  Absolutely no HTML parsing or validation is done. We're a Markdown
+/// 2.  Essentially no HTML parsing or validation is done. We're a Markdown
 ///     parser, not an HTML parser!
-class BlockHtmlSyntax extends BlockSyntax {
-  RegExp get pattern => _htmlPattern;
-
-  bool get canEndBlock => false;
+abstract class BlockHtmlSyntax extends BlockSyntax {
+  bool get canEndBlock => true;
 
   const BlockHtmlSyntax();
+}
+
+class BlockTagBlockHtmlSyntax extends BlockHtmlSyntax {
+  RegExp get pattern => new RegExp(
+      r'^ {0,3}</?(?:address|article|aside|base|basefont|blockquote|body|'
+      r'caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|'
+      r'figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|'
+      r'iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|'
+      r'option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|'
+      'title|tr|track|ul)'
+      r'(?:\s|>|/>|$)');
+
+  const BlockTagBlockHtmlSyntax();
 
   Node parse(BlockParser parser) {
     var childLines = <String>[];
@@ -413,6 +424,52 @@
   }
 }
 
+class OtherTagBlockHtmlSyntax extends BlockTagBlockHtmlSyntax {
+  bool get canEndBlock => false;
+
+  // Really hacky way to detect "other" HTML. This matches:
+  //
+  // * any opening spaces
+  // * open bracket and maybe a slash ("<" or "</")
+  // * some word characters
+  // * either:
+  //   * a close bracket, or
+  //   * whitespace followed by not-brackets follwed by a close bracket
+  // * possible whitespace and the end of the line.
+  RegExp get pattern => new RegExp(r'^ {0,3}</?\w+(?:>|\s+[^>]*>)\s*$');
+
+  const OtherTagBlockHtmlSyntax();
+}
+
+/// A BlockHtmlSyntax that has a specific [endPattern].
+///
+/// In practice this means that the syntax dominates; it is allowed to eat
+/// many lines, including blank lines, before matching its [endPattern].
+class LongBlockHtmlSyntax extends BlockHtmlSyntax {
+  RegExp _pattern;
+  RegExp _endPattern;
+
+  LongBlockHtmlSyntax(pattern, endPattern) {
+    _pattern = new RegExp(pattern);
+    _endPattern = new RegExp(endPattern);
+  }
+
+  RegExp get pattern => _pattern;
+
+  Node parse(BlockParser parser) {
+    var childLines = <String>[];
+    // Eat until we hit [endPattern].
+    while (!parser.isDone) {
+      childLines.add(parser.current);
+      if (parser.matches(_endPattern)) break;
+      parser.advance();
+    }
+
+    parser.advance();
+    return new Text(childLines.join('\n'));
+  }
+}
+
 class ListItem {
   bool forceBlock = false;
   final List<String> lines;