Linter Demo Errors: 4Warnings: 0File: /home/fstrocco/Dart/dart/benchmark/markdown/lib/src/inline_parser.dart library markdown.inline_parser; import 'ast.dart'; import 'document.dart'; import 'util.dart'; class InlineParser { static final List _defaultSyntaxes = [new TextSyntax(r'\s*[A-Za-z0-9]+'), new AutolinkSyntax(), new LinkSyntax(), new ImageLinkSyntax(), new TextSyntax(r' \* '), new TextSyntax(r' _ '), new TextSyntax(r'&[#a-zA-Z0-9]*;'), new TextSyntax(r'&', sub: '&'), new TextSyntax(r'<', sub: '<'), new TagSyntax(r'\*\*', tag: 'strong'), new TagSyntax(r'__', tag: 'strong'), new TagSyntax(r'\*', tag: 'em'), new TagSyntax(r'_', tag: 'em'), new CodeSyntax(r'``\s?((?:.|\n)*?)\s?``'), new CodeSyntax(r'`([^`]*)`')]; final String source; final Document document; final List syntaxes = []; int pos = 0; int start = 0; final List _stack; InlineParser(this.source, this.document) : _stack = [] { if (document.inlineSyntaxes != null) { syntaxes.addAll(document.inlineSyntaxes); } syntaxes.addAll(_defaultSyntaxes); syntaxes.insertAll(1, [new LinkSyntax(linkResolver: document.linkResolver), new ImageLinkSyntax(linkResolver: document.imageLinkResolver)]); } List parse() { _stack.add(new TagState(0, 0, null)); while (!isDone) { bool matched = false; for (int i = _stack.length - 1; i > 0; i--) { if (_stack[i].tryMatch(this)) { matched = true; break; } } if (matched) continue; for (final syntax in syntaxes) { if (syntax.tryMatch(this)) { matched = true; break; } } if (matched) continue; advanceBy(1); } return _stack[0].close(this, null); } void writeText() { writeTextRange(start, pos); start = pos; } void writeTextRange(int start, int end) { if (end > start) { final text = source.substring(start, end); final nodes = _stack.last.children; 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)); } } } void addNode(Node node) { _stack.last.children.add(node); } String get currentSource => source.substring(pos, source.length); bool get isDone => pos == source.length; void advanceBy(int length) { pos += length; } void consume(int length) { pos += length; start = pos; } } abstract class InlineSyntax { final RegExp pattern; 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)) { parser.writeText(); if (onMatch(parser, startMatch)) { parser.consume(startMatch[0].length); } return true; } return false; } bool onMatch(InlineParser parser, Match match); } class TextSyntax extends InlineSyntax { final String substitute; TextSyntax(String pattern, {String sub}) : super(pattern), substitute = sub; bool onMatch(InlineParser parser, Match match) { if (substitute == null) { parser.advanceBy(match[0].length); return false; } parser.addNode(new Text(substitute)); return true; } } class AutolinkSyntax extends InlineSyntax { AutolinkSyntax() : super(r'<((http|https|ftp)://[^>]*)>'); bool onMatch(InlineParser parser, Match match) { final url = match[1]; final anchor = new Element.text('a', escapeHtml(url))..attributes['href'] = url; parser.addNode(anchor); return true; } } class TagSyntax extends InlineSyntax { final RegExp endPattern; final String tag; TagSyntax(String pattern, {String tag, String end}) : super(pattern), endPattern = new RegExp((end != null) ? end : pattern, multiLine: true), tag = tag; bool onMatch(InlineParser parser, Match match) { parser._stack.add(new TagState(parser.pos, parser.pos + match[0].length, this)); return true; } bool onMatchEnd(InlineParser parser, Match match, TagState state) { parser.addNode(new Element(tag, state.children)); return true; } } class LinkSyntax extends TagSyntax { final Resolver linkResolver; bool resolved = false; static get linkPattern { final refLink = r'\s?\[([^\]]*)\]'; final title = r'(?:[ ]*"([^"]+)"|)'; final inlineLink = '\\s?\\(([^ )]+)$title\\)'; return '\](?:($refLink|$inlineLink)|)'; } LinkSyntax({this.linkResolver, String pattern: r'\['}) : super(pattern, end: linkPattern); Node createNode(InlineParser parser, Match match, TagState state) { if (isNullOrEmpty(match[1])) { if (linkResolver == null) return null; if (state.children.any((child) => child is! Text)) return null; var textToResolve = state.children.fold('', (oldVal, child) => oldVal + child.text); resolved = true; return linkResolver(textToResolve); } else { Link 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); cleanMap(node.attributes); return node; } } Link getLink(InlineParser parser, Match match, TagState state) { if ((match[3] != null) && (match[3] != '')) { var url = match[3]; var title = match[4]; if (url.startsWith('<') && url.endsWith('>')) { url = url.substring(1, url.length - 1); } return new Link(null, url, title); } else { var id; if (match[2] == '') id = parser.source.substring(state.startPos + 1, parser.pos); else id = match[2]; id = id.toLowerCase(); return parser.document.refLinks[id]; } } bool onMatchEnd(InlineParser parser, Match match, TagState state) { Node node = createNode(parser, match, state); if (node == null) return false; parser.addNode(node); return true; } } 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(' '); cleanMap(imageElement.attributes); node.children ..clear() ..add(imageElement); return node; } } class CodeSyntax extends InlineSyntax { CodeSyntax(String pattern) : super(pattern); bool onMatch(InlineParser parser, Match match) { parser.addNode(new Element.text('code', escapeHtml(match[1]))); return true; } } class TagState { final int startPos; final int endPos; final TagSyntax syntax; final List children; TagState(this.startPos, this.endPos, this.syntax) : children = []; bool tryMatch(InlineParser parser) { Match endMatch = syntax.endPattern.firstMatch(parser.currentSource); if ((endMatch != null) && (endMatch.start == 0)) { close(parser, endMatch); return true; } return false; } List close(InlineParser parser, Match endMatch) { int index = parser._stack.indexOf(this); final unmatchedTags = parser._stack.sublist(index + 1); parser._stack.removeRange(index + 1, parser._stack.length); for (final unmatched in unmatchedTags) { parser.writeTextRange(unmatched.startPos, unmatched.endPos); children.addAll(unmatched.children); } parser.writeText(); parser._stack.removeLast(); if (parser._stack.length == 0) return children; if (syntax.onMatchEnd(parser, endMatch, this)) { parser.consume(endMatch[0].length); } else { parser.start = startPos; parser.advanceBy(endMatch[0].length); } return null; } }