| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /// The line contains only whitespace or is empty. | 5 /// The line contains only whitespace or is empty. |
| 6 const _RE_EMPTY = const RegExp(@'^([ \t]*)$'); | 6 const _RE_EMPTY = const RegExp(@'^([ \t]*)$'); |
| 7 | 7 |
| 8 /// A series of `=` or `-` (on the next line) define setext-style headers. | 8 /// A series of `=` or `-` (on the next line) define setext-style headers. |
| 9 const _RE_SETEXT = const RegExp(@'^((=+)|(-+))$'); | 9 const _RE_SETEXT = const RegExp(@'^((=+)|(-+))$'); |
| 10 | 10 |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 /// The markdown document this parser is parsing. | 45 /// The markdown document this parser is parsing. |
| 46 final Document document; | 46 final Document document; |
| 47 | 47 |
| 48 /// Index of the current line. | 48 /// Index of the current line. |
| 49 int pos; | 49 int pos; |
| 50 | 50 |
| 51 BlockParser(this.lines, this.document) | 51 BlockParser(this.lines, this.document) |
| 52 : pos = 0; | 52 : pos = 0; |
| 53 | 53 |
| 54 /// Gets the current line. | 54 /// Gets the current line. |
| 55 String get current() => lines[pos]; | 55 String get current => lines[pos]; |
| 56 | 56 |
| 57 /// Gets the line after the current one or `null` if there is none. | 57 /// Gets the line after the current one or `null` if there is none. |
| 58 String get next() { | 58 String get next { |
| 59 // Don't read past the end. | 59 // Don't read past the end. |
| 60 if (pos >= lines.length - 1) return null; | 60 if (pos >= lines.length - 1) return null; |
| 61 return lines[pos + 1]; | 61 return lines[pos + 1]; |
| 62 } | 62 } |
| 63 | 63 |
| 64 void advance() { | 64 void advance() { |
| 65 pos++; | 65 pos++; |
| 66 } | 66 } |
| 67 | 67 |
| 68 bool get isDone() => pos >= lines.length; | 68 bool get isDone => pos >= lines.length; |
| 69 | 69 |
| 70 /// Gets whether or not the current line matches the given pattern. | 70 /// Gets whether or not the current line matches the given pattern. |
| 71 bool matches(RegExp regex) { | 71 bool matches(RegExp regex) { |
| 72 if (isDone) return false; | 72 if (isDone) return false; |
| 73 return regex.firstMatch(current) != null; | 73 return regex.firstMatch(current) != null; |
| 74 } | 74 } |
| 75 | 75 |
| 76 /// Gets whether or not the current line matches the given pattern. | 76 /// Gets whether or not the current line matches the given pattern. |
| 77 bool matchesNext(RegExp regex) { | 77 bool matchesNext(RegExp regex) { |
| 78 if (next == null) return false; | 78 if (next == null) return false; |
| 79 return regex.firstMatch(next) != null; | 79 return regex.firstMatch(next) != null; |
| 80 } | 80 } |
| 81 } | 81 } |
| 82 | 82 |
| 83 class BlockSyntax { | 83 class BlockSyntax { |
| 84 /// Gets the collection of built-in block parsers. To turn a series of lines | 84 /// Gets the collection of built-in block parsers. To turn a series of lines |
| 85 /// into blocks, each of these will be tried in turn. Order matters here. | 85 /// into blocks, each of these will be tried in turn. Order matters here. |
| 86 static List<BlockSyntax> get syntaxes() { | 86 static List<BlockSyntax> get syntaxes { |
| 87 // Lazy initialize. | 87 // Lazy initialize. |
| 88 if (_syntaxes == null) { | 88 if (_syntaxes == null) { |
| 89 _syntaxes = [ | 89 _syntaxes = [ |
| 90 new EmptyBlockSyntax(), | 90 new EmptyBlockSyntax(), |
| 91 new BlockHtmlSyntax(), | 91 new BlockHtmlSyntax(), |
| 92 new SetextHeaderSyntax(), | 92 new SetextHeaderSyntax(), |
| 93 new HeaderSyntax(), | 93 new HeaderSyntax(), |
| 94 new CodeBlockSyntax(), | 94 new CodeBlockSyntax(), |
| 95 new BlockquoteSyntax(), | 95 new BlockquoteSyntax(), |
| 96 new HorizontalRuleSyntax(), | 96 new HorizontalRuleSyntax(), |
| 97 new UnorderedListSyntax(), | 97 new UnorderedListSyntax(), |
| 98 new OrderedListSyntax(), | 98 new OrderedListSyntax(), |
| 99 new ParagraphSyntax() | 99 new ParagraphSyntax() |
| 100 ]; | 100 ]; |
| 101 } | 101 } |
| 102 | 102 |
| 103 return _syntaxes; | 103 return _syntaxes; |
| 104 } | 104 } |
| 105 | 105 |
| 106 static List<BlockSyntax> _syntaxes; | 106 static List<BlockSyntax> _syntaxes; |
| 107 | 107 |
| 108 /// Gets the regex used to identify the beginning of this block, if any. | 108 /// Gets the regex used to identify the beginning of this block, if any. |
| 109 RegExp get pattern() => null; | 109 RegExp get pattern => null; |
| 110 | 110 |
| 111 bool get canEndBlock() => true; | 111 bool get canEndBlock => true; |
| 112 | 112 |
| 113 bool canParse(BlockParser parser) { | 113 bool canParse(BlockParser parser) { |
| 114 return pattern.firstMatch(parser.current) != null; | 114 return pattern.firstMatch(parser.current) != null; |
| 115 } | 115 } |
| 116 | 116 |
| 117 abstract Node parse(BlockParser parser); | 117 abstract Node parse(BlockParser parser); |
| 118 | 118 |
| 119 List<String> parseChildLines(BlockParser parser) { | 119 List<String> parseChildLines(BlockParser parser) { |
| 120 // Grab all of the lines that form the blockquote, stripping off the ">". | 120 // Grab all of the lines that form the blockquote, stripping off the ">". |
| 121 final childLines = <String>[]; | 121 final childLines = <String>[]; |
| 122 | 122 |
| 123 while (!parser.isDone) { | 123 while (!parser.isDone) { |
| 124 final match = pattern.firstMatch(parser.current); | 124 final match = pattern.firstMatch(parser.current); |
| 125 if (match == null) break; | 125 if (match == null) break; |
| 126 childLines.add(match[1]); | 126 childLines.add(match[1]); |
| 127 parser.advance(); | 127 parser.advance(); |
| 128 } | 128 } |
| 129 | 129 |
| 130 return childLines; | 130 return childLines; |
| 131 } | 131 } |
| 132 | 132 |
| 133 /// Gets whether or not [parser]'s current line should end the previous block. | 133 /// Gets whether or not [parser]'s current line should end the previous block. |
| 134 static bool isAtBlockEnd(BlockParser parser) { | 134 static bool isAtBlockEnd(BlockParser parser) { |
| 135 if (parser.isDone) return true; | 135 if (parser.isDone) return true; |
| 136 return syntaxes.some((s) => s.canParse(parser) && s.canEndBlock); | 136 return syntaxes.some((s) => s.canParse(parser) && s.canEndBlock); |
| 137 } | 137 } |
| 138 } | 138 } |
| 139 | 139 |
| 140 class EmptyBlockSyntax extends BlockSyntax { | 140 class EmptyBlockSyntax extends BlockSyntax { |
| 141 RegExp get pattern() => _RE_EMPTY; | 141 RegExp get pattern => _RE_EMPTY; |
| 142 | 142 |
| 143 Node parse(BlockParser parser) { | 143 Node parse(BlockParser parser) { |
| 144 parser.advance(); | 144 parser.advance(); |
| 145 | 145 |
| 146 // Don't actually emit anything. | 146 // Don't actually emit anything. |
| 147 return null; | 147 return null; |
| 148 } | 148 } |
| 149 } | 149 } |
| 150 | 150 |
| 151 /// Parses setext-style headers. | 151 /// Parses setext-style headers. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 163 final contents = parser.document.parseInline(parser.current); | 163 final contents = parser.document.parseInline(parser.current); |
| 164 parser.advance(); | 164 parser.advance(); |
| 165 parser.advance(); | 165 parser.advance(); |
| 166 | 166 |
| 167 return new Element(tag, contents); | 167 return new Element(tag, contents); |
| 168 } | 168 } |
| 169 } | 169 } |
| 170 | 170 |
| 171 /// Parses atx-style headers: `## Header ##`. | 171 /// Parses atx-style headers: `## Header ##`. |
| 172 class HeaderSyntax extends BlockSyntax { | 172 class HeaderSyntax extends BlockSyntax { |
| 173 RegExp get pattern() => _RE_HEADER; | 173 RegExp get pattern => _RE_HEADER; |
| 174 | 174 |
| 175 Node parse(BlockParser parser) { | 175 Node parse(BlockParser parser) { |
| 176 final match = pattern.firstMatch(parser.current); | 176 final match = pattern.firstMatch(parser.current); |
| 177 parser.advance(); | 177 parser.advance(); |
| 178 final level = match[1].length; | 178 final level = match[1].length; |
| 179 final contents = parser.document.parseInline(match[2].trim()); | 179 final contents = parser.document.parseInline(match[2].trim()); |
| 180 return new Element('h$level', contents); | 180 return new Element('h$level', contents); |
| 181 } | 181 } |
| 182 } | 182 } |
| 183 | 183 |
| 184 /// Parses email-style blockquotes: `> quote`. | 184 /// Parses email-style blockquotes: `> quote`. |
| 185 class BlockquoteSyntax extends BlockSyntax { | 185 class BlockquoteSyntax extends BlockSyntax { |
| 186 RegExp get pattern() => _RE_BLOCKQUOTE; | 186 RegExp get pattern => _RE_BLOCKQUOTE; |
| 187 | 187 |
| 188 Node parse(BlockParser parser) { | 188 Node parse(BlockParser parser) { |
| 189 final childLines = parseChildLines(parser); | 189 final childLines = parseChildLines(parser); |
| 190 | 190 |
| 191 // Recursively parse the contents of the blockquote. | 191 // Recursively parse the contents of the blockquote. |
| 192 final children = parser.document.parseLines(childLines); | 192 final children = parser.document.parseLines(childLines); |
| 193 | 193 |
| 194 return new Element('blockquote', children); | 194 return new Element('blockquote', children); |
| 195 } | 195 } |
| 196 } | 196 } |
| 197 | 197 |
| 198 /// Parses preformatted code blocks that are indented four spaces. | 198 /// Parses preformatted code blocks that are indented four spaces. |
| 199 class CodeBlockSyntax extends BlockSyntax { | 199 class CodeBlockSyntax extends BlockSyntax { |
| 200 RegExp get pattern() => _RE_INDENT; | 200 RegExp get pattern => _RE_INDENT; |
| 201 | 201 |
| 202 Node parse(BlockParser parser) { | 202 Node parse(BlockParser parser) { |
| 203 final childLines = parseChildLines(parser); | 203 final childLines = parseChildLines(parser); |
| 204 | 204 |
| 205 // The Markdown tests expect a trailing newline. | 205 // The Markdown tests expect a trailing newline. |
| 206 childLines.add(''); | 206 childLines.add(''); |
| 207 | 207 |
| 208 // Escape the code. | 208 // Escape the code. |
| 209 final escaped = escapeHtml(Strings.join(childLines, '\n')); | 209 final escaped = escapeHtml(Strings.join(childLines, '\n')); |
| 210 | 210 |
| 211 return new Element('pre', [new Element.text('code', escaped)]); | 211 return new Element('pre', [new Element.text('code', escaped)]); |
| 212 } | 212 } |
| 213 } | 213 } |
| 214 | 214 |
| 215 /// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. | 215 /// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. |
| 216 class HorizontalRuleSyntax extends BlockSyntax { | 216 class HorizontalRuleSyntax extends BlockSyntax { |
| 217 RegExp get pattern() => _RE_HR; | 217 RegExp get pattern => _RE_HR; |
| 218 | 218 |
| 219 Node parse(BlockParser parser) { | 219 Node parse(BlockParser parser) { |
| 220 final match = pattern.firstMatch(parser.current); | 220 final match = pattern.firstMatch(parser.current); |
| 221 parser.advance(); | 221 parser.advance(); |
| 222 return new Element.empty('hr'); | 222 return new Element.empty('hr'); |
| 223 } | 223 } |
| 224 } | 224 } |
| 225 | 225 |
| 226 /// Parses inline HTML at the block level. This differs from other markdown | 226 /// Parses inline HTML at the block level. This differs from other markdown |
| 227 /// implementations in several ways: | 227 /// implementations in several ways: |
| 228 /// | 228 /// |
| 229 /// 1. This one is way way WAY simpler. | 229 /// 1. This one is way way WAY simpler. |
| 230 /// 2. All HTML tags at the block level will be treated as blocks. If you | 230 /// 2. All HTML tags at the block level will be treated as blocks. If you |
| 231 /// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. | 231 /// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. |
| 232 /// As soon as it sees something like HTML, it stops mucking with it until | 232 /// As soon as it sees something like HTML, it stops mucking with it until |
| 233 /// it hits the next block. | 233 /// it hits the next block. |
| 234 /// 3. Absolutely no HTML parsing or validation is done. We're a markdown | 234 /// 3. Absolutely no HTML parsing or validation is done. We're a markdown |
| 235 /// parser not an HTML parser! | 235 /// parser not an HTML parser! |
| 236 class BlockHtmlSyntax extends BlockSyntax { | 236 class BlockHtmlSyntax extends BlockSyntax { |
| 237 RegExp get pattern() => _RE_HTML; | 237 RegExp get pattern => _RE_HTML; |
| 238 | 238 |
| 239 bool get canEndBlock() => false; | 239 bool get canEndBlock => false; |
| 240 | 240 |
| 241 Node parse(BlockParser parser) { | 241 Node parse(BlockParser parser) { |
| 242 final childLines = []; | 242 final childLines = []; |
| 243 | 243 |
| 244 // Eat until we hit a blank line. | 244 // Eat until we hit a blank line. |
| 245 while (!parser.isDone && !parser.matches(_RE_EMPTY)) { | 245 while (!parser.isDone && !parser.matches(_RE_EMPTY)) { |
| 246 childLines.add(parser.current); | 246 childLines.add(parser.current); |
| 247 parser.advance(); | 247 parser.advance(); |
| 248 } | 248 } |
| 249 | 249 |
| 250 return new Text(Strings.join(childLines, '\n')); | 250 return new Text(Strings.join(childLines, '\n')); |
| 251 } | 251 } |
| 252 } | 252 } |
| 253 | 253 |
| 254 class ListItem { | 254 class ListItem { |
| 255 bool forceBlock = false; | 255 bool forceBlock = false; |
| 256 final List<String> lines; | 256 final List<String> lines; |
| 257 | 257 |
| 258 ListItem(this.lines); | 258 ListItem(this.lines); |
| 259 } | 259 } |
| 260 | 260 |
| 261 /// Base class for both ordered and unordered lists. | 261 /// Base class for both ordered and unordered lists. |
| 262 class ListSyntax extends BlockSyntax { | 262 class ListSyntax extends BlockSyntax { |
| 263 bool get canEndBlock() => false; | 263 bool get canEndBlock => false; |
| 264 | 264 |
| 265 abstract String get listTag(); | 265 abstract String get listTag; |
| 266 | 266 |
| 267 Node parse(BlockParser parser) { | 267 Node parse(BlockParser parser) { |
| 268 final items = <ListItem>[]; | 268 final items = <ListItem>[]; |
| 269 var childLines = <String>[]; | 269 var childLines = <String>[]; |
| 270 | 270 |
| 271 endItem() { | 271 endItem() { |
| 272 if (childLines.length > 0) { | 272 if (childLines.length > 0) { |
| 273 items.add(new ListItem(childLines)); | 273 items.add(new ListItem(childLines)); |
| 274 childLines = <String>[]; | 274 childLines = <String>[]; |
| 275 } | 275 } |
| (...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 397 itemNodes.add(new Element('li', contents)); | 397 itemNodes.add(new Element('li', contents)); |
| 398 } | 398 } |
| 399 } | 399 } |
| 400 | 400 |
| 401 return new Element(listTag, itemNodes); | 401 return new Element(listTag, itemNodes); |
| 402 } | 402 } |
| 403 } | 403 } |
| 404 | 404 |
| 405 /// Parses unordered lists. | 405 /// Parses unordered lists. |
| 406 class UnorderedListSyntax extends ListSyntax { | 406 class UnorderedListSyntax extends ListSyntax { |
| 407 RegExp get pattern() => _RE_UL; | 407 RegExp get pattern => _RE_UL; |
| 408 String get listTag() => 'ul'; | 408 String get listTag => 'ul'; |
| 409 } | 409 } |
| 410 | 410 |
| 411 /// Parses ordered lists. | 411 /// Parses ordered lists. |
| 412 class OrderedListSyntax extends ListSyntax { | 412 class OrderedListSyntax extends ListSyntax { |
| 413 RegExp get pattern() => _RE_OL; | 413 RegExp get pattern => _RE_OL; |
| 414 String get listTag() => 'ol'; | 414 String get listTag => 'ol'; |
| 415 } | 415 } |
| 416 | 416 |
| 417 /// Parses paragraphs of regular text. | 417 /// Parses paragraphs of regular text. |
| 418 class ParagraphSyntax extends BlockSyntax { | 418 class ParagraphSyntax extends BlockSyntax { |
| 419 bool get canEndBlock() => false; | 419 bool get canEndBlock => false; |
| 420 | 420 |
| 421 bool canParse(BlockParser parser) => true; | 421 bool canParse(BlockParser parser) => true; |
| 422 | 422 |
| 423 Node parse(BlockParser parser) { | 423 Node parse(BlockParser parser) { |
| 424 final childLines = []; | 424 final childLines = []; |
| 425 | 425 |
| 426 // Eat until we hit something that ends a paragraph. | 426 // Eat until we hit something that ends a paragraph. |
| 427 while (!BlockSyntax.isAtBlockEnd(parser)) { | 427 while (!BlockSyntax.isAtBlockEnd(parser)) { |
| 428 childLines.add(parser.current); | 428 childLines.add(parser.current); |
| 429 parser.advance(); | 429 parser.advance(); |
| 430 } | 430 } |
| 431 | 431 |
| 432 final contents = parser.document.parseInline( | 432 final contents = parser.document.parseInline( |
| 433 Strings.join(childLines, '\n')); | 433 Strings.join(childLines, '\n')); |
| 434 return new Element('p', contents); | 434 return new Element('p', contents); |
| 435 } | 435 } |
| 436 } | 436 } |
| OLD | NEW |