Clean up: 1. Use "var" for locals. 2. Don't use type parameters on temporary collections. 3. Be more strict about null. 4. Reformat. R=sethladd@google.com Review URL: https://codereview.chromium.org//1274763005 .
diff --git a/pkgs/markdown/lib/src/ast.dart b/pkgs/markdown/lib/src/ast.dart index 3290efb..6da7ab4 100644 --- a/pkgs/markdown/lib/src/ast.dart +++ b/pkgs/markdown/lib/src/ast.dart
@@ -2,7 +2,7 @@ // 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. -library markdown.ast; +library markdown.src.ast; typedef Node Resolver(String name); @@ -22,21 +22,21 @@ Element.empty(this.tag) : children = null, - attributes = <String, String>{}; + attributes = {}; Element.withTag(this.tag) : children = [], - attributes = <String, String>{}; + attributes = {}; Element.text(this.tag, String text) : children = [new Text(text)], - attributes = <String, String>{}; + attributes = {}; bool get isEmpty => children == null; void accept(NodeVisitor visitor) { if (visitor.visitElementBefore(this)) { - for (final child in children) child.accept(visitor); + for (var child in children) child.accept(visitor); visitor.visitElementAfter(this); } }
diff --git a/pkgs/markdown/lib/src/block_parser.dart b/pkgs/markdown/lib/src/block_parser.dart index ce92867..504fdbb 100644 --- a/pkgs/markdown/lib/src/block_parser.dart +++ b/pkgs/markdown/lib/src/block_parser.dart
@@ -9,42 +9,42 @@ import 'util.dart'; /// The line contains only whitespace or is empty. -final _RE_EMPTY = new RegExp(r'^([ \t]*)$'); +final _emptyPattern = new RegExp(r'^([ \t]*)$'); /// A series of `=` or `-` (on the next line) define setext-style headers. -final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$'); +final _setextPattern = new RegExp(r'^((=+)|(-+))$'); /// Leading (and trailing) `#` define atx-style headers. -final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$'); +final _headerPattern = new RegExp(r'^(#{1,6})(.*?)#*$'); /// The line starts with `>` with one optional space after. -final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); +final _blockquotePattern = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); /// A line indented four spaces. Used for code blocks and lists. -final _RE_INDENT = new RegExp(r'^(?: |\t)(.*)$'); +final _indentPattern = new RegExp(r'^(?: |\t)(.*)$'); /// Fenced code block. -final _RE_CODE = new RegExp(r'^(`{3,}|~{3,})(.*)$'); +final _codePattern = new RegExp(r'^(`{3,}|~{3,})(.*)$'); /// Three or more hyphens, asterisks or underscores by themselves. Note that /// a line like `----` is valid as both HR and SETEXT. In case of a tie, /// SETEXT should win. -final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' +final _hrPattern = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' r'(_+[ ]{0,2}){3,}|' r'(\*+[ ]{0,2}){3,})$'); /// Really hacky way to detect block-level embedded HTML. Just looks for /// "<somename". -final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]'); +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. -final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$'); +final _ulPattern = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$'); /// A line starting with a number like `123.`. May have up to three leading /// spaces before the marker and any number of spaces or tabs after. -final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$'); +final _olPattern = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$'); /// Maintains the internal state needed to parse a series of lines into blocks /// of markdown suitable for further inline parsing. @@ -120,10 +120,10 @@ List<String> parseChildLines(BlockParser parser) { // Grab all of the lines that form the blockquote, stripping off the ">". - final childLines = <String>[]; + var childLines = <String>[]; while (!parser.isDone) { - final match = pattern.firstMatch(parser.current); + var match = pattern.firstMatch(parser.current); if (match == null) break; childLines.add(match[1]); parser.advance(); @@ -140,7 +140,7 @@ } class EmptyBlockSyntax extends BlockSyntax { - RegExp get pattern => _RE_EMPTY; + RegExp get pattern => _emptyPattern; const EmptyBlockSyntax(); @@ -159,14 +159,14 @@ bool canParse(BlockParser parser) { // Note: matches *next* line, not the current one. We're looking for the // underlining after this line. - return parser.matchesNext(_RE_SETEXT); + return parser.matchesNext(_setextPattern); } Node parse(BlockParser parser) { - final match = _RE_SETEXT.firstMatch(parser.next); + var match = _setextPattern.firstMatch(parser.next); - final tag = (match[1][0] == '=') ? 'h1' : 'h2'; - final contents = parser.document.parseInline(parser.current); + var tag = (match[1][0] == '=') ? 'h1' : 'h2'; + var contents = parser.document.parseInline(parser.current); parser.advance(); parser.advance(); @@ -176,30 +176,30 @@ /// Parses atx-style headers: `## Header ##`. class HeaderSyntax extends BlockSyntax { - RegExp get pattern => _RE_HEADER; + RegExp get pattern => _headerPattern; const HeaderSyntax(); Node parse(BlockParser parser) { - final match = pattern.firstMatch(parser.current); + var match = pattern.firstMatch(parser.current); parser.advance(); - final level = match[1].length; - final contents = parser.document.parseInline(match[2].trim()); + var level = match[1].length; + var contents = parser.document.parseInline(match[2].trim()); return new Element('h$level', contents); } } /// Parses email-style blockquotes: `> quote`. class BlockquoteSyntax extends BlockSyntax { - RegExp get pattern => _RE_BLOCKQUOTE; + RegExp get pattern => _blockquotePattern; const BlockquoteSyntax(); Node parse(BlockParser parser) { - final childLines = parseChildLines(parser); + var childLines = parseChildLines(parser); // Recursively parse the contents of the blockquote. - final children = parser.document.parseLines(childLines); + var children = parser.document.parseLines(childLines); return new Element('blockquote', children); } @@ -207,12 +207,12 @@ /// Parses preformatted code blocks that are indented four spaces. class CodeBlockSyntax extends BlockSyntax { - RegExp get pattern => _RE_INDENT; + RegExp get pattern => _indentPattern; const CodeBlockSyntax(); List<String> parseChildLines(BlockParser parser) { - final childLines = <String>[]; + var childLines = <String>[]; while (!parser.isDone) { var match = pattern.firstMatch(parser.current); @@ -238,30 +238,32 @@ } Node parse(BlockParser parser) { - final childLines = parseChildLines(parser); + var childLines = parseChildLines(parser); // The Markdown tests expect a trailing newline. childLines.add(''); // Escape the code. - final escaped = escapeHtml(childLines.join('\n')); + var escaped = escapeHtml(childLines.join('\n')); return new Element('pre', [new Element.text('code', escaped)]); } } /// Parses preformatted code blocks between two ~~~ or ``` sequences. -/// [Pandoc's markdown documentation](http://johnmacfarlane.net/pandoc/demo/example9/pandocs-markdown.html). +/// +/// See [Pandoc's documentation](http://johnmacfarlane.net/pandoc/demo/example9/pandocs-markdown.html). class FencedCodeBlockSyntax extends BlockSyntax { - RegExp get pattern => _RE_CODE; + RegExp get pattern => _codePattern; const FencedCodeBlockSyntax(); List<String> parseChildLines(BlockParser parser, [String endBlock]) { if (endBlock == null) endBlock = ''; - final childLines = <String>[]; + var childLines = <String>[]; parser.advance(); + while (!parser.isDone) { var match = pattern.firstMatch(parser.current); if (match == null || !match[1].startsWith(endBlock)) { @@ -272,6 +274,7 @@ break; } } + return childLines; } @@ -281,25 +284,24 @@ var endBlock = match.group(1); var syntax = match.group(2); - final childLines = parseChildLines(parser, endBlock); + var childLines = parseChildLines(parser, endBlock); // The Markdown tests expect a trailing newline. childLines.add(''); // Escape the code. - final escaped = escapeHtml(childLines.join('\n')); + var escaped = escapeHtml(childLines.join('\n')); var element = new Element('pre', [new Element.text('code', escaped)]); - if (syntax != '') { - element.attributes['class'] = syntax; - } + if (syntax != '') element.attributes['class'] = syntax; + return element; } } /// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. class HorizontalRuleSyntax extends BlockSyntax { - RegExp get pattern => _RE_HR; + RegExp get pattern => _hrPattern; const HorizontalRuleSyntax(); @@ -320,17 +322,17 @@ /// 3. Absolutely no HTML parsing or validation is done. We're a markdown /// parser not an HTML parser! class BlockHtmlSyntax extends BlockSyntax { - RegExp get pattern => _RE_HTML; + RegExp get pattern => _htmlPattern; bool get canEndBlock => false; const BlockHtmlSyntax(); Node parse(BlockParser parser) { - final childLines = []; + var childLines = <String>[]; // Eat until we hit a blank line. - while (!parser.isDone && !parser.matches(_RE_EMPTY)) { + while (!parser.isDone && !parser.matches(_emptyPattern)) { childLines.add(parser.current); parser.advance(); } @@ -355,7 +357,7 @@ const ListSyntax(); Node parse(BlockParser parser) { - final items = <ListItem>[]; + var items = <ListItem>[]; var childLines = <String>[]; endItem() { @@ -372,14 +374,14 @@ } while (!parser.isDone) { - if (tryMatch(_RE_EMPTY)) { + if (tryMatch(_emptyPattern)) { // Add a blank line to the current list item. childLines.add(''); - } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) { + } else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) { // End the current list item and start a new one. endItem(); childLines.add(match[1]); - } else if (tryMatch(_RE_INDENT)) { + } else if (tryMatch(_indentPattern)) { // Strip off indent and add to current item. childLines.add(match[1]); } else if (BlockSyntax.isAtBlockEnd(parser)) { @@ -436,9 +438,9 @@ // Remove any trailing empty lines and note which items are separated by // empty lines. Do this before seeing which items are single-line so that // trailing empty lines on the last item don't force it into being a block. - for (int i = 0; i < items.length; i++) { - for (int j = items[i].lines.length - 1; j > 0; j--) { - if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) { + for (var i = 0; i < items.length; i++) { + for (var j = items[i].lines.length - 1; j > 0; j--) { + if (_emptyPattern.firstMatch(items[i].lines[j]) != null) { // Found an empty line. Item and one after it are blocks. if (i < items.length - 1) { items[i].forceBlock = true; @@ -452,22 +454,22 @@ } // Convert the list items to Nodes. - final itemNodes = <Node>[]; - for (final item in items) { - bool blockItem = item.forceBlock || (item.lines.length > 1); + var itemNodes = <Node>[]; + for (var item in items) { + var blockItem = item.forceBlock || (item.lines.length > 1); // See if it matches some block parser. - final blocksInList = [ - _RE_BLOCKQUOTE, - _RE_HEADER, - _RE_HR, - _RE_INDENT, - _RE_UL, - _RE_OL + var blocksInList = [ + _blockquotePattern, + _headerPattern, + _hrPattern, + _indentPattern, + _ulPattern, + _olPattern ]; if (!blockItem) { - for (final pattern in blocksInList) { + for (var pattern in blocksInList) { if (pattern.firstMatch(item.lines[0]) != null) { blockItem = true; break; @@ -478,11 +480,11 @@ // Parse the item as a block or inline. if (blockItem) { // Block list item. - final children = parser.document.parseLines(item.lines); + var children = parser.document.parseLines(item.lines); itemNodes.add(new Element('li', children)); } else { // Raw list item. - final contents = parser.document.parseInline(item.lines[0]); + var contents = parser.document.parseInline(item.lines[0]); itemNodes.add(new Element('li', contents)); } } @@ -493,7 +495,7 @@ /// Parses unordered lists. class UnorderedListSyntax extends ListSyntax { - RegExp get pattern => _RE_UL; + RegExp get pattern => _ulPattern; String get listTag => 'ul'; const UnorderedListSyntax(); @@ -501,7 +503,7 @@ /// Parses ordered lists. class OrderedListSyntax extends ListSyntax { - RegExp get pattern => _RE_OL; + RegExp get pattern => _olPattern; String get listTag => 'ol'; const OrderedListSyntax(); @@ -516,7 +518,7 @@ bool canParse(BlockParser parser) => true; Node parse(BlockParser parser) { - final childLines = []; + var childLines = <String>[]; // Eat until we hit something that ends a paragraph. while (!BlockSyntax.isAtBlockEnd(parser)) { @@ -524,7 +526,7 @@ parser.advance(); } - final contents = parser.document.parseInline(childLines.join('\n')); + var contents = parser.document.parseInline(childLines.join('\n')); return new Element('p', contents); } }
diff --git a/pkgs/markdown/lib/src/document.dart b/pkgs/markdown/lib/src/document.dart index 79f7c31..71a29d9 100644 --- a/pkgs/markdown/lib/src/document.dart +++ b/pkgs/markdown/lib/src/document.dart
@@ -1,4 +1,4 @@ -library markdown.document; +library markdown.src.document; import 'ast.dart'; import 'block_parser.dart'; @@ -19,16 +19,16 @@ // [id]: http:foo.com "some title" // Where there may whitespace in there, and where the title may be in // single quotes, double quotes, or parentheses. - final indent = r'^[ ]{0,3}'; // Leading indentation. - final id = r'\[([^\]]+)\]'; // Reference id in [brackets]. - final quote = r'"[^"]+"'; // Title in "double quotes". - final apos = r"'[^']+'"; // Title in 'single quotes'. - final paren = r"\([^)]+\)"; // Title in (parentheses). - final pattern = new RegExp( - '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$'); + var indent = r'^[ ]{0,3}'; // Leading indentation. + var id = r'\[([^\]]+)\]'; // Reference id in [brackets]. + var quote = r'"[^"]+"'; // Title in "double quotes". + var apos = r"'[^']+'"; // Title in 'single quotes'. + var paren = r"\([^)]+\)"; // Title in (parentheses). + var pattern = + new RegExp('$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$'); - for (int i = 0; i < lines.length; i++) { - final match = pattern.firstMatch(lines[i]); + for (var i = 0; i < lines.length; i++) { + var match = pattern.firstMatch(lines[i]); if (match != null) { // Parse the link. var id = match[1]; @@ -57,13 +57,13 @@ /// Parse the given [lines] of markdown to a series of AST nodes. List<Node> parseLines(List<String> lines) { - final parser = new BlockParser(lines, this); + var parser = new BlockParser(lines, this); - final blocks = []; + var blocks = <Node>[]; while (!parser.isDone) { - for (final syntax in BlockSyntax.syntaxes) { + for (var syntax in BlockSyntax.syntaxes) { if (syntax.canParse(parser)) { - final block = syntax.parse(parser); + var block = syntax.parse(parser); if (block != null) blocks.add(block); break; }
diff --git a/pkgs/markdown/lib/src/html_renderer.dart b/pkgs/markdown/lib/src/html_renderer.dart index 835c178..edb09a0 100644 --- a/pkgs/markdown/lib/src/html_renderer.dart +++ b/pkgs/markdown/lib/src/html_renderer.dart
@@ -2,38 +2,37 @@ // 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. -library markdown.html_renderer; +library markdown.src.html_renderer; import 'ast.dart'; import 'document.dart'; import 'inline_parser.dart'; /// Converts the given string of markdown to HTML. -String markdownToHtml(String markdown, {List<InlineSyntax> inlineSyntaxes, - Resolver linkResolver, Resolver imageLinkResolver, +String markdownToHtml(String markdown, + {List<InlineSyntax> inlineSyntaxes, + Resolver linkResolver, + Resolver imageLinkResolver, bool inlineOnly: false}) { var document = new Document( inlineSyntaxes: inlineSyntaxes, imageLinkResolver: imageLinkResolver, linkResolver: linkResolver); - if (inlineOnly) { - return renderToHtml(document.parseInline(markdown)); - } else { - // Replace windows line endings with unix line endings, and split. - var lines = markdown.replaceAll('\r\n', '\n').split('\n'); - document.parseRefLinks(lines); - var blocks = document.parseLines(lines); - return renderToHtml(blocks); - } + if (inlineOnly) return renderToHtml(document.parseInline(markdown)); + + // Replace windows line endings with unix line endings, and split. + var lines = markdown.replaceAll('\r\n', '\n').split('\n'); + document.parseRefLinks(lines); + + return renderToHtml(document.parseLines(lines)); } String renderToHtml(List<Node> nodes) => new HtmlRenderer().render(nodes); /// Translates a parsed AST to HTML. class HtmlRenderer implements NodeVisitor { - static final _BLOCK_TAGS = new RegExp( - 'blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre'); + static final _blockTags = new RegExp('blockquote|h1|h2|h3|h4|h5|h6|hr|p|pre'); StringBuffer buffer; @@ -53,16 +52,17 @@ bool visitElementBefore(Element element) { // Hackish. Separate block-level elements with newlines. - if (!buffer.isEmpty && _BLOCK_TAGS.firstMatch(element.tag) != null) { + if (!buffer.isEmpty && _blockTags.firstMatch(element.tag) != null) { buffer.write('\n'); } buffer.write('<${element.tag}'); // Sort the keys so that we generate stable output. - final attributeNames = element.attributes.keys.toList(); + var attributeNames = element.attributes.keys.toList(); attributeNames.sort((a, b) => a.compareTo(b)); - for (final name in attributeNames) { + + for (var name in attributeNames) { buffer.write(' $name="${element.attributes[name]}"'); }
diff --git a/pkgs/markdown/lib/src/inline_parser.dart b/pkgs/markdown/lib/src/inline_parser.dart index c9cf98a..5c11d7f 100644 --- a/pkgs/markdown/lib/src/inline_parser.dart +++ b/pkgs/markdown/lib/src/inline_parser.dart
@@ -2,7 +2,7 @@ // 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. -library markdown.inline_parser; +library markdown.src.inline_parser; import 'ast.dart'; import 'document.dart'; @@ -26,7 +26,6 @@ new TextSyntax(r'\s*[A-Za-z0-9]+'), // The real syntaxes. - new AutolinkSyntax(), new LinkSyntax(), new ImageLinkSyntax(), @@ -75,13 +74,15 @@ final List<TagState> _stack; InlineParser(this.source, this.document) : _stack = <TagState>[] { - /// User specified syntaxes will be the first syntaxes to be evaluated. + // User specified syntaxes are the first syntaxes to be evaluated. if (document.inlineSyntaxes != null) { syntaxes.addAll(document.inlineSyntaxes); } + syntaxes.addAll(_defaultSyntaxes); + // Custom link resolvers goes after the generic text syntax. - syntaxes.insertAll(1, <InlineSyntax>[ + syntaxes.insertAll(1, [ new LinkSyntax(linkResolver: document.linkResolver), new ImageLinkSyntax(linkResolver: document.imageLinkResolver) ]); @@ -92,25 +93,28 @@ _stack.add(new TagState(0, 0, null)); while (!isDone) { - bool matched = false; + var matched = false; // See if any of the current tags on the stack match. We don't allow tags - // of the same kind to nest, so this takes priority over other possible // matches. - for (int i = _stack.length - 1; i > 0; i--) { + // of the same kind to nest, so this takes priority over other possible + // matches. + for (var i = _stack.length - 1; i > 0; i--) { if (_stack[i].tryMatch(this)) { matched = true; break; } } + if (matched) continue; // See if the current text matches any defined markdown syntax. - for (final syntax in syntaxes) { + for (var syntax in syntaxes) { if (syntax.tryMatch(this)) { matched = true; break; } } + if (matched) continue; // If we got here, it's just text. @@ -127,17 +131,16 @@ } void writeTextRange(int start, int end) { - if (end > start) { - final text = source.substring(start, end); - final nodes = _stack.last.children; + if (end <= start) return; - // If the previous node is text too, just append. - if ((nodes.length > 0) && (nodes.last is Text)) { - final newNode = new Text('${nodes.last.text}$text'); - nodes[nodes.length - 1] = newNode; - } else { - nodes.add(new Text(text)); - } + var text = source.substring(start, end); + var nodes = _stack.last.children; + + // If the previous node is text too, just append. + if (nodes.length > 0 && nodes.last is Text) { + nodes[nodes.length - 1] = new Text('${nodes.last.text}$text'); + } else { + nodes.add(new Text(text)); } } @@ -147,6 +150,7 @@ // TODO(rnystrom): Only need this because RegExp doesn't let you start // searching from a given offset. + @deprecated String get currentSource => source.substring(pos, source.length); bool get isDone => pos == source.length; @@ -168,16 +172,15 @@ InlineSyntax(String pattern) : pattern = new RegExp(pattern, multiLine: true); bool tryMatch(InlineParser parser) { - final startMatch = pattern.firstMatch(parser.currentSource); - if ((startMatch != null) && (startMatch.start == 0)) { + var startMatch = pattern.matchAsPrefix(parser.source, parser.pos); + if (startMatch != null) { // Write any existing plain text up to this point. parser.writeText(); - if (onMatch(parser, startMatch)) { - parser.consume(startMatch[0].length); - } + if (onMatch(parser, startMatch)) parser.consume(startMatch[0].length); return true; } + return false; } @@ -187,6 +190,7 @@ /// Matches stuff that should just be passed through as straight text. class TextSyntax extends InlineSyntax { final String substitute; + TextSyntax(String pattern, {String sub}) : super(pattern), substitute = sub; @@ -210,10 +214,9 @@ // TODO(rnystrom): Make case insensitive. bool onMatch(InlineParser parser, Match match) { - final url = match[1]; - - final anchor = new Element.text('a', escapeHtml(url)) - ..attributes['href'] = url; + var url = match[1]; + var anchor = new Element.text('a', escapeHtml(url)); + anchor.attributes['href'] = url; parser.addNode(anchor); return true; @@ -226,15 +229,13 @@ final RegExp endPattern; final String tag; - TagSyntax(String pattern, {String tag, String end}) + TagSyntax(String pattern, {this.tag, String end}) : super(pattern), - endPattern = new RegExp((end != null) ? end : pattern, multiLine: true), - tag = tag; - // TODO(rnystrom): Doing this.field doesn't seem to work with named args. + endPattern = new RegExp((end != null) ? end : pattern, multiLine: true); bool onMatch(InlineParser parser, Match match) { - parser._stack.add( - new TagState(parser.pos, parser.pos + match[0].length, this)); + parser._stack + .add(new TagState(parser.pos, parser.pos + match[0].length, this)); return true; } @@ -255,9 +256,9 @@ /// inline styles as well as optional titles for inline links. To make that /// a bit more palatable, this breaks it into pieces. static get linkPattern { - final refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id. - final title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes. - final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link. + var refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id. + var title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes. + var inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link. return '\](?:($refLink|$inlineLink)|)'; // The groups matched by this are: @@ -277,7 +278,7 @@ // link at all. Instead, we allow users of the library to specify a special // resolver function ([linkResolver]) that may choose to handle // this. Otherwise, it's just treated as plain text. - if (isNullOrEmpty(match[1])) { + if (match[1] == null) { if (linkResolver == null) return null; // Treat the contents as unparsed text even if they happen to match. This @@ -289,20 +290,20 @@ resolved = true; return linkResolver(textToResolve); } else { - Link link = getLink(parser, match, state); + var link = getLink(parser, match, state); if (link == null) return null; - final Element node = new Element('a', state.children) - ..attributes["href"] = escapeHtml(link.url) - ..attributes['title'] = escapeHtml(link.title); + var node = new Element('a', state.children); - cleanMap(node.attributes); + node.attributes["href"] = escapeHtml(link.url); + if (link.title != null) node.attributes['title'] = escapeHtml(link.title); + return node; } } Link getLink(InlineParser parser, Match match, TagState state) { - if ((match[3] != null) && (match[3] != '')) { + if (match[3] != null && match[3] != '') { // Inline link like [foo](url). var url = match[3]; var title = match[4]; @@ -316,10 +317,12 @@ } else { var id; // Reference link like [foo] [bar]. - if (match[2] == '') - // The id is empty ("[]") so infer it from the contents. - id = parser.source.substring(state.startPos + 1, parser.pos); - else id = match[2]; + if (match[2] == '') { + // The id is empty ("[]") so infer it from the contents. + id = parser.source.substring(state.startPos + 1, parser.pos); + } else { + id = match[2]; + } // References are case-insensitive. id = id.toLowerCase(); @@ -328,8 +331,9 @@ } bool onMatchEnd(InlineParser parser, Match match, TagState state) { - Node node = createNode(parser, match, state); + var node = createNode(parser, match, state); if (node == null) return false; + parser.addNode(node); return true; } @@ -339,21 +343,24 @@ /// `![alternate text][url reference]`. class ImageLinkSyntax extends LinkSyntax { final Resolver linkResolver; + ImageLinkSyntax({this.linkResolver}) : super(pattern: r'!\['); Node createNode(InlineParser parser, Match match, TagState state) { var node = super.createNode(parser, match, state); + if (resolved) return node; if (node == null) return null; - final Element imageElement = new Element.withTag("img") - ..attributes["src"] = node.attributes["href"] - ..attributes["title"] = node.attributes["title"] - ..attributes["alt"] = node.children - .map((e) => isNullOrEmpty(e) || e is! Text ? '' : e.text) - .join(' '); + var imageElement = new Element.withTag("img"); + imageElement.attributes["src"] = node.attributes["href"]; - cleanMap(imageElement.attributes); + if (node.attributes.containsKey("title")) { + imageElement.attributes["title"] = node.attributes["title"]; + } + + var alt = node.children.map((e) => e is! Text ? '' : e.text).join(" "); + if (alt != "") imageElement.attributes["alt"] = alt; node.children ..clear() @@ -393,8 +400,8 @@ /// Attempts to close this tag by matching the current text against its end /// pattern. bool tryMatch(InlineParser parser) { - Match endMatch = syntax.endPattern.firstMatch(parser.currentSource); - if ((endMatch != null) && (endMatch.start == 0)) { + var endMatch = syntax.endPattern.matchAsPrefix(parser.source, parser.pos); + if (endMatch != null) { // Close the tag. close(parser, endMatch); return true; @@ -411,14 +418,14 @@ // means they are mismatched. Mismatched tags are treated as plain text in // markdown. So for each tag above this one, we write its start tag as text // and then adds its children to this one's children. - int index = parser._stack.indexOf(this); + var index = parser._stack.indexOf(this); // Remove the unmatched children. - final unmatchedTags = parser._stack.sublist(index + 1); + var unmatchedTags = parser._stack.sublist(index + 1); parser._stack.removeRange(index + 1, parser._stack.length); // Flatten them out onto this tag. - for (final unmatched in unmatchedTags) { + for (var unmatched in unmatchedTags) { // Write the start tag as text. parser.writeTextRange(unmatched.startPos, unmatched.endPos);
diff --git a/pkgs/markdown/lib/src/util.dart b/pkgs/markdown/lib/src/util.dart index 4b5f6d5..e254d26 100644 --- a/pkgs/markdown/lib/src/util.dart +++ b/pkgs/markdown/lib/src/util.dart
@@ -1,20 +1,9 @@ -library markdown.util; +library markdown.src.util; /// Replaces `<`, `&`, and `>`, with their HTML entity equivalents. String escapeHtml(String html) { - if (html == '' || html == null) return null; return html .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>'); } - -/// Removes null or empty values from [map]. -void cleanMap(Map map) { - map.keys.where((e) => isNullOrEmpty(map[e])).toList().forEach(map.remove); -} - -/// Returns true if an object is null or an empty string. -bool isNullOrEmpty(object) { - return object == null || object == ''; -}
diff --git a/pkgs/markdown/pubspec.yaml b/pkgs/markdown/pubspec.yaml index 9a86724..3049abb 100644 --- a/pkgs/markdown/pubspec.yaml +++ b/pkgs/markdown/pubspec.yaml
@@ -1,5 +1,5 @@ name: markdown -version: 0.7.2-dev +version: 0.7.2 author: Dart Team <misc@dartlang.org> description: A library for converting markdown to HTML. homepage: https://github.com/dart-lang/markdown
diff --git a/pkgs/markdown/test/markdown_test.dart b/pkgs/markdown/test/markdown_test.dart index e2d1b40..e0e8e11 100644 --- a/pkgs/markdown/test/markdown_test.dart +++ b/pkgs/markdown/test/markdown_test.dart
@@ -9,16 +9,19 @@ import 'package:markdown/markdown.dart'; -import 'utils.dart'; +import 'util.dart'; /// Most of these tests are based on observing how showdown behaves: /// http://softwaremaniacs.org/playground/showdown-highlight/ void main() { group('Paragraphs', () { - validate('consecutive lines form a single paragraph', ''' + validate( + 'consecutive lines form a single paragraph', + ''' This is the first line. This is the second line. - ''', ''' + ''', + ''' <p>This is the first line. This is the second line.</p> '''); @@ -28,163 +31,229 @@ // code significantly cleaner, we should consider ourselves free to change // these tests. - validate('are terminated by a header', ''' + validate( + 'are terminated by a header', + ''' para # header - ''', ''' + ''', + ''' <p>para</p> <h1>header</h1> '''); - validate('are terminated by a setext header', ''' + validate( + 'are terminated by a setext header', + ''' para header == - ''', ''' + ''', + ''' <p>para</p> <h1>header</h1> '''); - validate('are terminated by a hr', ''' + validate( + 'are terminated by a hr', + ''' para ___ - ''', ''' + ''', + ''' <p>para</p> <hr /> '''); - validate('consume an unordered list', ''' + validate( + 'consume an unordered list', + ''' para * list - ''', ''' + ''', + ''' <p>para * list</p> '''); - validate('consume an ordered list', ''' + validate( + 'consume an ordered list', + ''' para 1. list - ''', ''' + ''', + ''' <p>para 1. list</p> '''); // Windows line endings have a \r\n format // instead of the unix \n format. - validate('take account of windows line endings', ''' + validate( + 'take account of windows line endings', + ''' line1\r\n\r\n line2\r\n - ''', ''' + ''', + ''' <p>line1</p> <p>line2</p> '''); }); group('Setext headers', () { - validate('h1', ''' + validate( + 'h1', + ''' text === - ''', ''' + ''', + ''' <h1>text</h1> '''); - validate('h2', ''' + validate( + 'h2', + ''' text --- - ''', ''' + ''', + ''' <h2>text</h2> '''); - validate('h1 on first line becomes text', ''' + validate( + 'h1 on first line becomes text', + ''' === - ''', ''' + ''', + ''' <p>===</p> '''); - validate('h2 on first line becomes text', ''' + validate( + 'h2 on first line becomes text', + ''' - - ''', ''' + ''', + ''' <p>-</p> '''); - validate('h1 turns preceding list into text', ''' + validate( + 'h1 turns preceding list into text', + ''' - list === - ''', ''' + ''', + ''' <h1>- list</h1> '''); - validate('h2 turns preceding list into text', ''' + validate( + 'h2 turns preceding list into text', + ''' - list === - ''', ''' + ''', + ''' <h1>- list</h1> '''); - validate('h1 turns preceding blockquote into text', ''' + validate( + 'h1 turns preceding blockquote into text', + ''' > quote === - ''', ''' + ''', + ''' <h1>> quote</h1> '''); - validate('h2 turns preceding blockquote into text', ''' + validate( + 'h2 turns preceding blockquote into text', + ''' > quote === - ''', ''' + ''', + ''' <h1>> quote</h1> '''); }); group('Headers', () { - validate('h1', ''' + validate( + 'h1', + ''' # header - ''', ''' + ''', + ''' <h1>header</h1> '''); - validate('h2', ''' + validate( + 'h2', + ''' ## header - ''', ''' + ''', + ''' <h2>header</h2> '''); - validate('h3', ''' + validate( + 'h3', + ''' ### header - ''', ''' + ''', + ''' <h3>header</h3> '''); - validate('h4', ''' + validate( + 'h4', + ''' #### header - ''', ''' + ''', + ''' <h4>header</h4> '''); - validate('h5', ''' + validate( + 'h5', + ''' ##### header - ''', ''' + ''', + ''' <h5>header</h5> '''); - validate('h6', ''' + validate( + 'h6', + ''' ###### header - ''', ''' + ''', + ''' <h6>header</h6> '''); - validate('trailing "#" are removed', ''' + validate( + 'trailing "#" are removed', + ''' # header ###### - ''', ''' + ''', + ''' <h1>header</h1> '''); }); group('Unordered lists', () { - validate('asterisk, plus and hyphen', ''' + validate( + 'asterisk, plus and hyphen', + ''' * star - dash + plus - ''', ''' + ''', + ''' <ul> <li>star</li> <li>dash</li> @@ -192,22 +261,28 @@ </ul> '''); - validate('allow numbered lines after first', ''' + validate( + 'allow numbered lines after first', + ''' * a 1. b - ''', ''' + ''', + ''' <ul> <li>a</li> <li>b</li> </ul> '''); - validate('allow a tab after the marker', ''' + validate( + 'allow a tab after the marker', + ''' *\ta +\tb -\tc 1.\td - ''', ''' + ''', + ''' <ul> <li>a</li> <li>b</li> @@ -216,23 +291,29 @@ </ul> '''); - validate('wrap items in paragraphs if blank lines separate', ''' + validate( + 'wrap items in paragraphs if blank lines separate', + ''' * one * two - ''', ''' + ''', + ''' <ul> <li><p>one</p></li> <li><p>two</p></li> </ul> '''); - validate('force paragraph on item before and after blank lines', ''' + validate( + 'force paragraph on item before and after blank lines', + ''' * one * two * three - ''', ''' + ''', + ''' <ul> <li>one</li> <li> @@ -244,24 +325,30 @@ </ul> '''); - validate('do not force paragraph if item is already block', ''' + validate( + 'do not force paragraph if item is already block', + ''' * > quote * # header - ''', ''' + ''', + ''' <ul> <li><blockquote><p>quote</p></blockquote></li> <li><h1>header</h1></li> </ul> '''); - validate('can contain multiple paragraphs', ''' + validate( + 'can contain multiple paragraphs', + ''' * one two * three - ''', ''' + ''', + ''' <ul> <li> <p>one</p> @@ -273,11 +360,14 @@ </ul> '''); - validate('can span newlines', ''' + validate( + 'can span newlines', + ''' * one two * three - ''', ''' + ''', + ''' <ul> <li> <p>one @@ -306,11 +396,14 @@ }); group('Ordered lists', () { - validate('start with numbers', ''' + validate( + 'start with numbers', + ''' 1. one 45. two 12345. three - ''', ''' + ''', + ''' <ol> <li>one</li> <li>two</li> @@ -318,10 +411,13 @@ </ol> '''); - validate('allow unordered lines after first', ''' + validate( + 'allow unordered lines after first', + ''' 1. a * b - ''', ''' + ''', + ''' <ol> <li>a</li> <li>b</li> @@ -330,30 +426,39 @@ }); group('Blockquotes', () { - validate('single line', ''' + validate( + 'single line', + ''' > blah - ''', ''' + ''', + ''' <blockquote> <p>blah</p> </blockquote> '''); - validate('with two paragraphs', ''' + validate( + 'with two paragraphs', + ''' > first > > second - ''', ''' + ''', + ''' <blockquote> <p>first</p> <p>second</p> </blockquote> '''); - validate('nested', ''' + validate( + 'nested', + ''' > one >> two > > > three - ''', ''' + ''', + ''' <blockquote> <p>one</p> <blockquote> @@ -367,32 +472,41 @@ }); group('Code blocks', () { - validate('single line', ''' + validate( + 'single line', + ''' code - ''', ''' + ''', + ''' <pre><code>code</code></pre> '''); - validate('include leading whitespace after indentation', ''' + validate( + 'include leading whitespace after indentation', + ''' zero one two three - ''', ''' + ''', + ''' <pre><code>zero one two three</code></pre> '''); - validate('code blocks separated by newlines form one block', ''' + validate( + 'code blocks separated by newlines form one block', + ''' zero one two three - ''', ''' + ''', + ''' <pre><code>zero one @@ -401,7 +515,9 @@ three</code></pre> '''); - validate('code blocks separated by two newlines form multiple blocks', ''' + validate( + 'code blocks separated by two newlines form multiple blocks', + ''' zero one @@ -410,73 +526,95 @@ three - ''', ''' + ''', + ''' <pre><code>zero one</code></pre> <pre><code>two</code></pre> <pre><code>three</code></pre> '''); - validate('escape HTML characters', ''' + validate( + 'escape HTML characters', + ''' <&> - ''', ''' + ''', + ''' <pre><code><&></code></pre> '''); }); group('Fenced code blocks', () { - validate('without an optional language identifier', ''' + validate( + 'without an optional language identifier', + ''' ``` code ``` - ''', ''' + ''', + ''' <pre><code>code </code></pre> '''); - validate('with an optional language identifier', ''' + validate( + 'with an optional language identifier', + ''' ```dart code ``` - ''', ''' + ''', + ''' <pre class="dart"><code>code </code></pre> '''); - validate('escape HTML characters', ''' + validate( + 'escape HTML characters', + ''' ``` <&> ``` - ''', ''' + ''', + ''' <pre><code><&> </code></pre> '''); - validate('Pandoc style without language identifier', ''' + validate( + 'Pandoc style without language identifier', + ''' ~~~~~ code ~~~~~ - ''', ''' + ''', + ''' <pre><code>code </code></pre> '''); - validate('Pandoc style with language identifier', ''' + validate( + 'Pandoc style with language identifier', + ''' ~~~~~dart code ~~~~~ - ''', ''' + ''', + ''' <pre class="dart"><code>code </code></pre> '''); - validate('Pandoc style with inner tildes row', ''' + validate( + 'Pandoc style with inner tildes row', + ''' ~~~~~ ~~~ code ~~~ ~~~~~ - ''', ''' + ''', + ''' <pre><code>~~~ code ~~~ @@ -485,68 +623,92 @@ }); group('Horizontal rules', () { - validate('from dashes', ''' + validate( + 'from dashes', + ''' --- - ''', ''' + ''', + ''' <hr /> '''); - validate('from asterisks', ''' + validate( + 'from asterisks', + ''' *** - ''', ''' + ''', + ''' <hr /> '''); - validate('from underscores', ''' + validate( + 'from underscores', + ''' ___ - ''', ''' + ''', + ''' <hr /> '''); - validate('can include up to two spaces', ''' + validate( + 'can include up to two spaces', + ''' _ _ _ - ''', ''' + ''', + ''' <hr /> '''); }); group('Block-level HTML', () { - validate('single line', ''' + validate( + 'single line', + ''' <table></table> - ''', ''' + ''', + ''' <table></table> '''); - validate('multi-line', ''' + validate( + 'multi-line', + ''' <table> blah </table> - ''', ''' + ''', + ''' <table> blah </table> '''); - validate('blank line ends block', ''' + validate( + 'blank line ends block', + ''' <table> blah </table> para - ''', ''' + ''', + ''' <table> blah </table> <p>para</p> '''); - validate('HTML can be bogus', ''' + validate( + 'HTML can be bogus', + ''' <bogus> blah </weird> para - ''', ''' + ''', + ''' <bogus> blah </weird> @@ -555,319 +717,466 @@ }); group('Strong', () { - validate('using asterisks', ''' + validate( + 'using asterisks', + ''' before **strong** after - ''', ''' + ''', + ''' <p>before <strong>strong</strong> after</p> '''); - validate('using underscores', ''' + validate( + 'using underscores', + ''' before __strong__ after - ''', ''' + ''', + ''' <p>before <strong>strong</strong> after</p> '''); - validate('unmatched asterisks', ''' + validate( + 'unmatched asterisks', + ''' before ** after - ''', ''' + ''', + ''' <p>before ** after</p> '''); - validate('unmatched underscores', ''' + validate( + 'unmatched underscores', + ''' before __ after - ''', ''' + ''', + ''' <p>before __ after</p> '''); - validate('multiple spans in one text', ''' + validate( + 'multiple spans in one text', + ''' a **one** b __two__ c - ''', ''' + ''', + ''' <p>a <strong>one</strong> b <strong>two</strong> c</p> '''); - validate('multi-line', ''' + validate( + 'multi-line', + ''' before **first second** after - ''', ''' + ''', + ''' <p>before <strong>first second</strong> after</p> '''); }); group('Emphasis and strong', () { - validate('single asterisks', ''' + validate( + 'single asterisks', + ''' before *em* after - ''', ''' + ''', + ''' <p>before <em>em</em> after</p> '''); - validate('single underscores', ''' + validate( + 'single underscores', + ''' before _em_ after - ''', ''' + ''', + ''' <p>before <em>em</em> after</p> '''); - validate('double asterisks', ''' + validate( + 'double asterisks', + ''' before **strong** after - ''', ''' + ''', + ''' <p>before <strong>strong</strong> after</p> '''); - validate('double underscores', ''' + validate( + 'double underscores', + ''' before __strong__ after - ''', ''' + ''', + ''' <p>before <strong>strong</strong> after</p> '''); - validate('unmatched asterisk', ''' + validate( + 'unmatched asterisk', + ''' before *after - ''', ''' + ''', + ''' <p>before *after</p> '''); - validate('unmatched underscore', ''' + validate( + 'unmatched underscore', + ''' before _after - ''', ''' + ''', + ''' <p>before _after</p> '''); - validate('multiple spans in one text', ''' + validate( + 'multiple spans in one text', + ''' a *one* b _two_ c - ''', ''' + ''', + ''' <p>a <em>one</em> b <em>two</em> c</p> '''); - validate('multi-line', ''' + validate( + 'multi-line', + ''' before *first second* after - ''', ''' + ''', + ''' <p>before <em>first second</em> after</p> '''); - validate('not processed when surrounded by spaces', ''' + validate( + 'not processed when surrounded by spaces', + ''' a * b * c _ d _ e - ''', ''' + ''', + ''' <p>a * b * c _ d _ e</p> '''); - validate('strong then emphasis', ''' + validate( + 'strong then emphasis', + ''' **strong***em* - ''', ''' + ''', + ''' <p><strong>strong</strong><em>em</em></p> '''); - validate('emphasis then strong', ''' + validate( + 'emphasis then strong', + ''' *em***strong** - ''', ''' + ''', + ''' <p><em>em</em><strong>strong</strong></p> '''); - validate('emphasis inside strong', ''' + validate( + 'emphasis inside strong', + ''' **strong *em*** - ''', ''' + ''', + ''' <p><strong>strong <em>em</em></strong></p> '''); - validate('mismatched in nested', ''' + validate( + 'mismatched in nested', + ''' *a _b* c_ - ''', ''' + ''', + ''' <p><em>a _b</em> c_</p> '''); - validate('cannot nest tags of same type', ''' + validate( + 'cannot nest tags of same type', + ''' *a _b *c* d_ e* - ''', ''' + ''', + ''' <p><em>a _b </em>c<em> d_ e</em></p> '''); }); group('Inline code', () { - validate('simple case', ''' + validate( + 'simple case', + ''' before `source` after - ''', ''' + ''', + ''' <p>before <code>source</code> after</p> '''); - validate('unmatched backtick', ''' + validate( + 'unmatched backtick', + ''' before ` after - ''', ''' + ''', + ''' <p>before ` after</p> '''); - validate('multiple spans in one text', ''' + validate( + 'multiple spans in one text', + ''' a `one` b `two` c - ''', ''' + ''', + ''' <p>a <code>one</code> b <code>two</code> c</p> '''); - validate('multi-line', ''' + validate( + 'multi-line', + ''' before `first second` after - ''', ''' + ''', + ''' <p>before <code>first second</code> after</p> '''); - validate('simple double backticks', ''' + validate( + 'simple double backticks', + ''' before ``source`` after - ''', ''' + ''', + ''' <p>before <code>source</code> after</p> '''); - validate('double backticks', ''' + validate( + 'double backticks', + ''' before ``can `contain` backticks`` after - ''', ''' + ''', + ''' <p>before <code>can `contain` backticks</code> after</p> '''); - validate('double backticks with spaces', ''' + validate( + 'double backticks with spaces', + ''' before `` `tick` `` after - ''', ''' + ''', + ''' <p>before <code>`tick`</code> after</p> '''); - validate('multiline double backticks with spaces', ''' + validate( + 'multiline double backticks with spaces', + ''' before ``in `tick` another`` after - ''', ''' + ''', + ''' <p>before <code>in `tick` another</code> after</p> '''); - validate('ignore markup inside code', ''' + validate( + 'ignore markup inside code', + ''' before `*b* _c_` after - ''', ''' + ''', + ''' <p>before <code>*b* _c_</code> after</p> '''); - validate('escape HTML characters', ''' + validate( + 'escape HTML characters', + ''' `<&>` - ''', ''' + ''', + ''' <p><code><&></code></p> '''); - validate('escape HTML tags', ''' + validate( + 'escape HTML tags', + ''' '*' `<em>` - ''', ''' + ''', + ''' <p>'*' <code><em></code></p> '''); }); group('HTML encoding', () { - validate('less than and ampersand are escaped', ''' + validate( + 'less than and ampersand are escaped', + ''' < & - ''', ''' + ''', + ''' <p>< &</p> '''); - validate('greater than is not escaped', ''' + validate( + 'greater than is not escaped', + ''' not you > - ''', ''' + ''', + ''' <p>not you ></p> '''); - validate('existing entities are untouched', ''' + validate( + 'existing entities are untouched', + ''' & - ''', ''' + ''', + ''' <p>&</p> '''); }); group('Autolinks', () { - validate('basic link', ''' + validate( + 'basic link', + ''' before <http://foo.com/> after - ''', ''' + ''', + ''' <p>before <a href="http://foo.com/">http://foo.com/</a> after</p> '''); - validate('handles ampersand in url', ''' + validate( + 'handles ampersand in url', + ''' <http://foo.com/?a=1&b=2> - ''', ''' + ''', + ''' <p><a href="http://foo.com/?a=1&b=2">http://foo.com/?a=1&b=2</a></p> '''); }); group('Reference links', () { - validate('double quotes for title', ''' + validate( + 'double quotes for title', + ''' links [are] [a] awesome [a]: http://foo.com "woo" - ''', ''' + ''', + ''' <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> '''); - validate('single quoted title', """ + validate( + 'single quoted title', + """ links [are] [a] awesome [a]: http://foo.com 'woo' - """, ''' + """, + ''' <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> '''); - validate('parentheses for title', ''' + validate( + 'parentheses for title', + ''' links [are] [a] awesome [a]: http://foo.com (woo) - ''', ''' + ''', + ''' <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> '''); - validate('no title', ''' + validate( + 'no title', + ''' links [are] [a] awesome [a]: http://foo.com - ''', ''' + ''', + ''' <p>links <a href="http://foo.com">are</a> awesome</p> '''); - validate('unknown link becomes plaintext', ''' + validate( + 'unknown link becomes plaintext', + ''' [not] [known] - ''', ''' + ''', + ''' <p>[not] [known]</p> '''); - validate('can style link contents', ''' + validate( + 'can style link contents', + ''' links [*are*] [a] awesome [a]: http://foo.com - ''', ''' + ''', + ''' <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> '''); - validate('inline styles after a bad link are processed', ''' + validate( + 'inline styles after a bad link are processed', + ''' [bad] `code` - ''', ''' + ''', + ''' <p>[bad] <code>code</code></p> '''); - validate('empty reference uses text from link', ''' + validate( + 'empty reference uses text from link', + ''' links [are][] awesome [are]: http://foo.com - ''', ''' + ''', + ''' <p>links <a href="http://foo.com">are</a> awesome</p> '''); - validate('references are case-insensitive', ''' + validate( + 'references are case-insensitive', + ''' links [ARE][] awesome [are]: http://foo.com - ''', ''' + ''', + ''' <p>links <a href="http://foo.com">ARE</a> awesome</p> '''); }); group('Inline links', () { - validate('double quotes for title', ''' + validate( + 'double quotes for title', + ''' links [are](http://foo.com "woo") awesome - ''', ''' + ''', + ''' <p>links <a href="http://foo.com" title="woo">are</a> awesome</p> '''); - validate('no title', ''' + validate( + 'no title', + ''' links [are] (http://foo.com) awesome - ''', ''' + ''', + ''' <p>links <a href="http://foo.com">are</a> awesome</p> '''); - validate('can style link contents', ''' + validate( + 'can style link contents', + ''' links [*are*](http://foo.com) awesome - ''', ''' + ''', + ''' <p>links <a href="http://foo.com"><em>are</em></a> awesome</p> '''); }); group('Inline Images', () { - validate('image', ''' + validate( + 'image', + '''  - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png"> <img src="http://foo.com/foo.png"></img> @@ -875,9 +1184,12 @@ </p> '''); - validate('alternate text', ''' + validate( + 'alternate text', + '''  - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png"> <img alt="alternate text" src="http://foo.com/foo.png"></img> @@ -885,18 +1197,24 @@ </p> '''); - validate('title', ''' + validate( + 'title', + '''  - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png" title="optional title"> <img src="http://foo.com/foo.png" title="optional title"></img> </a> </p> '''); - validate('invalid alt text', ''' + validate( + 'invalid alt text', + '''  - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png"> <img src="http://foo.com/foo.png"></img> @@ -906,10 +1224,13 @@ }); group('Reference Images', () { - validate('image', ''' + validate( + 'image', + ''' ![][foo] [foo]: http://foo.com/foo.png - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png"> <img src="http://foo.com/foo.png"></img> @@ -917,10 +1238,13 @@ </p> '''); - validate('alternate text', ''' + validate( + 'alternate text', + ''' ![alternate text][foo] [foo]: http://foo.com/foo.png - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png"> <img alt="alternate text" src="http://foo.com/foo.png"></img> @@ -928,10 +1252,13 @@ </p> '''); - validate('title', ''' + validate( + 'title', + ''' ![][foo] [foo]: http://foo.com/foo.png "optional title" - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png" title="optional title"> <img src="http://foo.com/foo.png" title="optional title"></img> @@ -939,10 +1266,13 @@ </p> '''); - validate('invalid alt text', ''' + validate( + 'invalid alt text', + ''' ![`alt`][foo] [foo]: http://foo.com/foo.png "optional title" - ''', ''' + ''', + ''' <p> <a href="http://foo.com/foo.png" title="optional title"> <img src="http://foo.com/foo.png" title="optional title"></img> @@ -954,37 +1284,53 @@ group('Resolver', () { nyanResolver(text) => new Text('~=[,,_${text}_,,]:3'); - validate('simple link resolver', ''' + validate( + 'simple link resolver', + ''' resolve [this] thing - ''', ''' + ''', + ''' <p>resolve ~=[,,_this_,,]:3 thing</p> - ''', linkResolver: nyanResolver); - validate('simple image resolver', ''' + ''', + linkResolver: nyanResolver); + validate( + 'simple image resolver', + ''' resolve ![this] thing - ''', ''' + ''', + ''' <p>resolve ~=[,,_this_,,]:3 thing</p> - ''', imageLinkResolver: nyanResolver); + ''', + imageLinkResolver: nyanResolver); - validate('can resolve link containing inline tags', ''' + validate( + 'can resolve link containing inline tags', + ''' resolve [*star* _underline_] thing - ''', ''' + ''', + ''' <p>resolve ~=[,,_*star* _underline__,,]:3 thing</p> - ''', linkResolver: nyanResolver); + ''', + linkResolver: nyanResolver); }); group('Custom inline syntax', () { var nyanSyntax = [new TextSyntax('nyan', sub: '~=[,,_,,]:3')]; - validate('simple inline syntax', ''' + validate( + 'simple inline syntax', + ''' nyan - ''', ''' + ''', + ''' <p>~=[,,_,,]:3</p> - ''', inlineSyntaxes: nyanSyntax); + ''', + inlineSyntaxes: nyanSyntax); validate('dart custom links', 'links [are<foo>] awesome', '<p>links <a>are<foo></a> awesome</p>', - linkResolver: (text) => new Element.text( - 'a', text.replaceAll('<', '<'))); + linkResolver: (text) => + new Element.text('a', text.replaceAll('<', '<'))); // TODO(amouravski): need more tests here for custom syntaxes, as some // things are not quite working properly. The regexps are sometime a little @@ -992,42 +1338,70 @@ }); group('Inline only', () { - validate('simple line', ''' + validate( + 'simple line', + ''' This would normally create a paragraph. - ''', ''' + ''', + ''' This would normally create a paragraph. - ''', inlineOnly: true); - validate('strong and em', ''' + ''', + inlineOnly: true); + validate( + 'strong and em', + ''' This would _normally_ create a **paragraph**. - ''', ''' + ''', + ''' This would <em>normally</em> create a <strong>paragraph</strong>. - ''', inlineOnly: true); - validate('link', ''' + ''', + inlineOnly: true); + validate( + 'link', + ''' This [link](http://www.example.com/) will work normally. - ''', ''' + ''', + ''' This <a href="http://www.example.com/">link</a> will work normally. - ''', inlineOnly: true); - validate('references do not work', ''' + ''', + inlineOnly: true); + validate( + 'references do not work', + ''' [This][] shouldn't work, though. - ''', ''' + ''', + ''' [This][] shouldn't work, though. - ''', inlineOnly: true); - validate('less than and ampersand are escaped', ''' + ''', + inlineOnly: true); + validate( + 'less than and ampersand are escaped', + ''' < & - ''', ''' + ''', + ''' < & - ''', inlineOnly: true); - validate('keeps newlines', ''' + ''', + inlineOnly: true); + validate( + 'keeps newlines', + ''' This paragraph continues after a newline. - ''', ''' + ''', + ''' This paragraph continues after a newline. - ''', inlineOnly: true); - validate('ignores block-level markdown syntax', ''' + ''', + inlineOnly: true); + validate( + 'ignores block-level markdown syntax', + ''' 1. This will not be an <ol>. - ''', ''' + ''', + ''' 1. This will not be an <ol>. - ''', inlineOnly: true); + ''', + inlineOnly: true); }); }
diff --git a/pkgs/markdown/test/util.dart b/pkgs/markdown/test/util.dart new file mode 100644 index 0000000..fd99cb0 --- /dev/null +++ b/pkgs/markdown/test/util.dart
@@ -0,0 +1,95 @@ +// Copyright (c) 2015, 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. + +library markdown.test.utils; + +import 'package:unittest/unittest.dart'; + +import 'package:markdown/markdown.dart'; + +/// Removes eight spaces of leading indentation from a multiline string. +/// +/// Note that this is very sensitive to how the literals are styled. They should +/// be: +/// ''' +/// Text starts on own line. Lines up with subsequent lines. +/// Lines are indented exactly 8 characters from the left margin.''' +/// +/// This does nothing if text is only a single line. +// TODO(nweiz): Make this auto-detect the indentation level from the first +// non-whitespace line. +String cleanUpLiteral(String text) { + var lines = text.split('\n'); + if (lines.length <= 1) return text; + + for (var j = 0; j < lines.length; j++) { + if (lines[j].length > 8) { + lines[j] = lines[j].substring(8, lines[j].length); + } else { + lines[j] = ''; + } + } + + return lines.join('\n'); +} + +void validate(String description, String markdown, String html, + {List<InlineSyntax> inlineSyntaxes, + Resolver linkResolver, + Resolver imageLinkResolver, + bool inlineOnly: false}) { + test(description, () { + markdown = cleanUpLiteral(markdown); + html = cleanUpLiteral(html); + + var result = markdownToHtml(markdown, + inlineSyntaxes: inlineSyntaxes, + linkResolver: linkResolver, + imageLinkResolver: imageLinkResolver, + inlineOnly: inlineOnly); + var passed = compareOutput(html, result); + + if (!passed) { + // Remove trailing newline. + html = html.substring(0, html.length - 1); + + var sb = new StringBuffer(); + sb.writeln('Expected: ${html.replaceAll("\n", "\n ")}'); + sb.writeln(' Actual: ${result.replaceAll("\n", "\n ")}'); + + fail(sb.toString()); + } + }); +} + +/// Does a loose comparison of the two strings of HTML. Ignores differences in +/// newlines and indentation. +bool compareOutput(String a, String b) { + int i = 0; + int j = 0; + + skipIgnored(String s, int i) { + // Ignore newlines. + while ((i < s.length) && (s[i] == '\n')) { + i++; + // Ignore indentation. + while ((i < s.length) && (s[i] == ' ')) i++; + } + + return i; + } + + while (true) { + i = skipIgnored(a, i); + j = skipIgnored(b, j); + + // If one string runs out of non-ignored strings, the other must too. + if (i == a.length) return j == b.length; + if (j == b.length) return i == a.length; + + if (a[i] != b[j]) return false; + i++; + j++; + } +}