| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 part of markdown; | 5 library markdown.block_parser; |
| 6 |
| 7 import 'ast.dart'; |
| 8 import 'document.dart'; |
| 9 import 'util.dart'; |
| 6 | 10 |
| 7 /// The line contains only whitespace or is empty. | 11 /// The line contains only whitespace or is empty. |
| 8 final _RE_EMPTY = new RegExp(r'^([ \t]*)$'); | 12 final _RE_EMPTY = new RegExp(r'^([ \t]*)$'); |
| 9 | 13 |
| 10 /// A series of `=` or `-` (on the next line) define setext-style headers. | 14 /// A series of `=` or `-` (on the next line) define setext-style headers. |
| 11 final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$'); | 15 final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$'); |
| 12 | 16 |
| 13 /// Leading (and trailing) `#` define atx-style headers. | 17 /// Leading (and trailing) `#` define atx-style headers. |
| 14 final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$'); | 18 final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$'); |
| 15 | 19 |
| 16 /// The line starts with `>` with one optional space after. | 20 /// The line starts with `>` with one optional space after. |
| 17 final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); | 21 final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); |
| 18 | 22 |
| 19 /// A line indented four spaces. Used for code blocks and lists. | 23 /// A line indented four spaces. Used for code blocks and lists. |
| 20 final _RE_INDENT = new RegExp(r'^(?: |\t)(.*)$'); | 24 final _RE_INDENT = new RegExp(r'^(?: |\t)(.*)$'); |
| 21 | 25 |
| 22 /// GitHub style triple quoted code block. | 26 /// Fenced code block. |
| 23 final _RE_CODE = new RegExp(r'^```(.*)$'); | 27 final _RE_CODE = new RegExp(r'^(`{3,}|~{3,})(.*)$'); |
| 24 | 28 |
| 25 /// Three or more hyphens, asterisks or underscores by themselves. Note that | 29 /// Three or more hyphens, asterisks or underscores by themselves. Note that |
| 26 /// a line like `----` is valid as both HR and SETEXT. In case of a tie, | 30 /// a line like `----` is valid as both HR and SETEXT. In case of a tie, |
| 27 /// SETEXT should win. | 31 /// SETEXT should win. |
| 28 final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' | 32 final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' |
| 29 r'(_+[ ]{0,2}){3,}|' | 33 r'(_+[ ]{0,2}){3,}|' |
| 30 r'(\*+[ ]{0,2}){3,})$'); | 34 r'(\*+[ ]{0,2}){3,})$'); |
| 31 | 35 |
| 32 /// Really hacky way to detect block-level embedded HTML. Just looks for | 36 /// Really hacky way to detect block-level embedded HTML. Just looks for |
| 33 /// "<somename". | 37 /// "<somename". |
| (...skipping 10 matching lines...) Expand all Loading... |
| 44 | 48 |
| 45 /// Maintains the internal state needed to parse a series of lines into blocks | 49 /// Maintains the internal state needed to parse a series of lines into blocks |
| 46 /// of markdown suitable for further inline parsing. | 50 /// of markdown suitable for further inline parsing. |
| 47 class BlockParser { | 51 class BlockParser { |
| 48 final List<String> lines; | 52 final List<String> lines; |
| 49 | 53 |
| 50 /// The markdown document this parser is parsing. | 54 /// The markdown document this parser is parsing. |
| 51 final Document document; | 55 final Document document; |
| 52 | 56 |
| 53 /// Index of the current line. | 57 /// Index of the current line. |
| 54 int pos; | 58 int _pos; |
| 55 | 59 |
| 56 BlockParser(this.lines, this.document) | 60 BlockParser(this.lines, this.document) |
| 57 : pos = 0; | 61 : _pos = 0; |
| 58 | 62 |
| 59 /// Gets the current line. | 63 /// Gets the current line. |
| 60 String get current => lines[pos]; | 64 String get current => lines[_pos]; |
| 61 | 65 |
| 62 /// Gets the line after the current one or `null` if there is none. | 66 /// Gets the line after the current one or `null` if there is none. |
| 63 String get next { | 67 String get next { |
| 64 // Don't read past the end. | 68 // Don't read past the end. |
| 65 if (pos >= lines.length - 1) return null; | 69 if (_pos >= lines.length - 1) return null; |
| 66 return lines[pos + 1]; | 70 return lines[_pos + 1]; |
| 67 } | 71 } |
| 68 | 72 |
| 69 void advance() { | 73 void advance() { |
| 70 pos++; | 74 _pos++; |
| 71 } | 75 } |
| 72 | 76 |
| 73 bool get isDone => pos >= lines.length; | 77 bool get isDone => _pos >= lines.length; |
| 74 | 78 |
| 75 /// Gets whether or not the current line matches the given pattern. | 79 /// Gets whether or not the current line matches the given pattern. |
| 76 bool matches(RegExp regex) { | 80 bool matches(RegExp regex) { |
| 77 if (isDone) return false; | 81 if (isDone) return false; |
| 78 return regex.firstMatch(current) != null; | 82 return regex.firstMatch(current) != null; |
| 79 } | 83 } |
| 80 | 84 |
| 81 /// Gets whether or not the current line matches the given pattern. | 85 /// Gets whether or not the current line matches the given pattern. |
| 82 bool matchesNext(RegExp regex) { | 86 bool matchesNext(RegExp regex) { |
| 83 if (next == null) return false; | 87 if (next == null) return false; |
| 84 return regex.firstMatch(next) != null; | 88 return regex.firstMatch(next) != null; |
| 85 } | 89 } |
| 86 } | 90 } |
| 87 | 91 |
| 88 abstract class BlockSyntax { | 92 abstract class BlockSyntax { |
| 89 /// Gets the collection of built-in block parsers. To turn a series of lines | 93 /// Gets the collection of built-in block parsers. To turn a series of lines |
| 90 /// into blocks, each of these will be tried in turn. Order matters here. | 94 /// into blocks, each of these will be tried in turn. Order matters here. |
| 91 static List<BlockSyntax> get syntaxes { | 95 static const List<BlockSyntax> syntaxes = const[ |
| 92 // Lazy initialize. | 96 const EmptyBlockSyntax(), |
| 93 if (_syntaxes == null) { | 97 const BlockHtmlSyntax(), |
| 94 _syntaxes = [ | 98 const SetextHeaderSyntax(), |
| 95 new EmptyBlockSyntax(), | 99 const HeaderSyntax(), |
| 96 new BlockHtmlSyntax(), | 100 const CodeBlockSyntax(), |
| 97 new SetextHeaderSyntax(), | 101 const FencedCodeBlockSyntax(), |
| 98 new HeaderSyntax(), | 102 const BlockquoteSyntax(), |
| 99 new CodeBlockSyntax(), | 103 const HorizontalRuleSyntax(), |
| 100 new GitHubCodeBlockSyntax(), | 104 const UnorderedListSyntax(), |
| 101 new BlockquoteSyntax(), | 105 const OrderedListSyntax(), |
| 102 new HorizontalRuleSyntax(), | 106 const ParagraphSyntax() |
| 103 new UnorderedListSyntax(), | 107 ]; |
| 104 new OrderedListSyntax(), | |
| 105 new ParagraphSyntax() | |
| 106 ]; | |
| 107 } | |
| 108 | 108 |
| 109 return _syntaxes; | 109 const BlockSyntax(); |
| 110 } | |
| 111 | |
| 112 static List<BlockSyntax> _syntaxes; | |
| 113 | 110 |
| 114 /// Gets the regex used to identify the beginning of this block, if any. | 111 /// Gets the regex used to identify the beginning of this block, if any. |
| 115 RegExp get pattern => null; | 112 RegExp get pattern => null; |
| 116 | 113 |
| 117 bool get canEndBlock => true; | 114 bool get canEndBlock => true; |
| 118 | 115 |
| 119 bool canParse(BlockParser parser) { | 116 bool canParse(BlockParser parser) { |
| 120 return pattern.firstMatch(parser.current) != null; | 117 return pattern.firstMatch(parser.current) != null; |
| 121 } | 118 } |
| 122 | 119 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 139 /// Gets whether or not [parser]'s current line should end the previous block. | 136 /// Gets whether or not [parser]'s current line should end the previous block. |
| 140 static bool isAtBlockEnd(BlockParser parser) { | 137 static bool isAtBlockEnd(BlockParser parser) { |
| 141 if (parser.isDone) return true; | 138 if (parser.isDone) return true; |
| 142 return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock); | 139 return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock); |
| 143 } | 140 } |
| 144 } | 141 } |
| 145 | 142 |
| 146 class EmptyBlockSyntax extends BlockSyntax { | 143 class EmptyBlockSyntax extends BlockSyntax { |
| 147 RegExp get pattern => _RE_EMPTY; | 144 RegExp get pattern => _RE_EMPTY; |
| 148 | 145 |
| 146 const EmptyBlockSyntax(); |
| 147 |
| 149 Node parse(BlockParser parser) { | 148 Node parse(BlockParser parser) { |
| 150 parser.advance(); | 149 parser.advance(); |
| 151 | 150 |
| 152 // Don't actually emit anything. | 151 // Don't actually emit anything. |
| 153 return null; | 152 return null; |
| 154 } | 153 } |
| 155 } | 154 } |
| 156 | 155 |
| 157 /// Parses setext-style headers. | 156 /// Parses setext-style headers. |
| 158 class SetextHeaderSyntax extends BlockSyntax { | 157 class SetextHeaderSyntax extends BlockSyntax { |
| 158 |
| 159 const SetextHeaderSyntax(); |
| 160 |
| 159 bool canParse(BlockParser parser) { | 161 bool canParse(BlockParser parser) { |
| 160 // Note: matches *next* line, not the current one. We're looking for the | 162 // Note: matches *next* line, not the current one. We're looking for the |
| 161 // underlining after this line. | 163 // underlining after this line. |
| 162 return parser.matchesNext(_RE_SETEXT); | 164 return parser.matchesNext(_RE_SETEXT); |
| 163 } | 165 } |
| 164 | 166 |
| 165 Node parse(BlockParser parser) { | 167 Node parse(BlockParser parser) { |
| 166 final match = _RE_SETEXT.firstMatch(parser.next); | 168 final match = _RE_SETEXT.firstMatch(parser.next); |
| 167 | 169 |
| 168 final tag = (match[1][0] == '=') ? 'h1' : 'h2'; | 170 final tag = (match[1][0] == '=') ? 'h1' : 'h2'; |
| 169 final contents = parser.document.parseInline(parser.current); | 171 final contents = parser.document.parseInline(parser.current); |
| 170 parser.advance(); | 172 parser.advance(); |
| 171 parser.advance(); | 173 parser.advance(); |
| 172 | 174 |
| 173 return new Element(tag, contents); | 175 return new Element(tag, contents); |
| 174 } | 176 } |
| 175 } | 177 } |
| 176 | 178 |
| 177 /// Parses atx-style headers: `## Header ##`. | 179 /// Parses atx-style headers: `## Header ##`. |
| 178 class HeaderSyntax extends BlockSyntax { | 180 class HeaderSyntax extends BlockSyntax { |
| 179 RegExp get pattern => _RE_HEADER; | 181 RegExp get pattern => _RE_HEADER; |
| 180 | 182 |
| 183 const HeaderSyntax(); |
| 184 |
| 181 Node parse(BlockParser parser) { | 185 Node parse(BlockParser parser) { |
| 182 final match = pattern.firstMatch(parser.current); | 186 final match = pattern.firstMatch(parser.current); |
| 183 parser.advance(); | 187 parser.advance(); |
| 184 final level = match[1].length; | 188 final level = match[1].length; |
| 185 final contents = parser.document.parseInline(match[2].trim()); | 189 final contents = parser.document.parseInline(match[2].trim()); |
| 186 return new Element('h$level', contents); | 190 return new Element('h$level', contents); |
| 187 } | 191 } |
| 188 } | 192 } |
| 189 | 193 |
| 190 /// Parses email-style blockquotes: `> quote`. | 194 /// Parses email-style blockquotes: `> quote`. |
| 191 class BlockquoteSyntax extends BlockSyntax { | 195 class BlockquoteSyntax extends BlockSyntax { |
| 192 RegExp get pattern => _RE_BLOCKQUOTE; | 196 RegExp get pattern => _RE_BLOCKQUOTE; |
| 193 | 197 |
| 198 const BlockquoteSyntax(); |
| 199 |
| 194 Node parse(BlockParser parser) { | 200 Node parse(BlockParser parser) { |
| 195 final childLines = parseChildLines(parser); | 201 final childLines = parseChildLines(parser); |
| 196 | 202 |
| 197 // Recursively parse the contents of the blockquote. | 203 // Recursively parse the contents of the blockquote. |
| 198 final children = parser.document.parseLines(childLines); | 204 final children = parser.document.parseLines(childLines); |
| 199 | 205 |
| 200 return new Element('blockquote', children); | 206 return new Element('blockquote', children); |
| 201 } | 207 } |
| 202 } | 208 } |
| 203 | 209 |
| 204 /// Parses preformatted code blocks that are indented four spaces. | 210 /// Parses preformatted code blocks that are indented four spaces. |
| 205 class CodeBlockSyntax extends BlockSyntax { | 211 class CodeBlockSyntax extends BlockSyntax { |
| 206 RegExp get pattern => _RE_INDENT; | 212 RegExp get pattern => _RE_INDENT; |
| 207 | 213 |
| 214 const CodeBlockSyntax(); |
| 215 |
| 208 List<String> parseChildLines(BlockParser parser) { | 216 List<String> parseChildLines(BlockParser parser) { |
| 209 final childLines = <String>[]; | 217 final childLines = <String>[]; |
| 210 | 218 |
| 211 while (!parser.isDone) { | 219 while (!parser.isDone) { |
| 212 var match = pattern.firstMatch(parser.current); | 220 var match = pattern.firstMatch(parser.current); |
| 213 if (match != null) { | 221 if (match != null) { |
| 214 childLines.add(match[1]); | 222 childLines.add(match[1]); |
| 215 parser.advance(); | 223 parser.advance(); |
| 216 } else { | 224 } else { |
| 217 // If there's a codeblock, then a newline, then a codeblock, keep the | 225 // If there's a codeblock, then a newline, then a codeblock, keep the |
| (...skipping 19 matching lines...) Expand all Loading... |
| 237 // The Markdown tests expect a trailing newline. | 245 // The Markdown tests expect a trailing newline. |
| 238 childLines.add(''); | 246 childLines.add(''); |
| 239 | 247 |
| 240 // Escape the code. | 248 // Escape the code. |
| 241 final escaped = escapeHtml(childLines.join('\n')); | 249 final escaped = escapeHtml(childLines.join('\n')); |
| 242 | 250 |
| 243 return new Element('pre', [new Element.text('code', escaped)]); | 251 return new Element('pre', [new Element.text('code', escaped)]); |
| 244 } | 252 } |
| 245 } | 253 } |
| 246 | 254 |
| 247 /// Parses preformatted code blocks between two ``` sequences. | 255 /// Parses preformatted code blocks between two ~~~ or ``` sequences. |
| 248 class GitHubCodeBlockSyntax extends BlockSyntax { | 256 /// [Pandoc's markdown documentation](http://johnmacfarlane.net/pandoc/demo/exam
ple9/pandocs-markdown.html). |
| 257 class FencedCodeBlockSyntax extends BlockSyntax { |
| 249 RegExp get pattern => _RE_CODE; | 258 RegExp get pattern => _RE_CODE; |
| 250 | 259 |
| 251 List<String> parseChildLines(BlockParser parser) { | 260 const FencedCodeBlockSyntax(); |
| 261 |
| 262 List<String> parseChildLines(BlockParser parser, [String endBlock]) { |
| 263 if(endBlock == null) endBlock = ''; |
| 264 |
| 252 final childLines = <String>[]; | 265 final childLines = <String>[]; |
| 253 parser.advance(); | 266 parser.advance(); |
| 254 while (!parser.isDone) { | 267 while (!parser.isDone) { |
| 255 var match = pattern.firstMatch(parser.current); | 268 var match = pattern.firstMatch(parser.current); |
| 256 if (match == null) { | 269 if (match == null || !match[1].startsWith(endBlock)) { |
| 257 childLines.add(parser.current); | 270 childLines.add(parser.current); |
| 258 parser.advance(); | 271 parser.advance(); |
| 259 } else { | 272 } else { |
| 260 parser.advance(); | 273 parser.advance(); |
| 261 break; | 274 break; |
| 262 } | 275 } |
| 263 } | 276 } |
| 264 return childLines; | 277 return childLines; |
| 265 } | 278 } |
| 266 | 279 |
| 267 Node parse(BlockParser parser) { | 280 Node parse(BlockParser parser) { |
| 268 // Get the syntax identifier, if there is one. | 281 // Get the syntax identifier, if there is one. |
| 269 var syntax = pattern.firstMatch(parser.current).group(1); | 282 var match = pattern.firstMatch(parser.current); |
| 270 | 283 var endBlock = match.group(1); |
| 271 final childLines = parseChildLines(parser); | 284 var syntax = match.group(2); |
| 285 |
| 286 final childLines = parseChildLines(parser, endBlock); |
| 272 | 287 |
| 273 // The Markdown tests expect a trailing newline. | 288 // The Markdown tests expect a trailing newline. |
| 274 childLines.add(''); | 289 childLines.add(''); |
| 275 | 290 |
| 276 // Escape the code. | 291 // Escape the code. |
| 277 final escaped = escapeHtml(childLines.join('\n')); | 292 final escaped = escapeHtml(childLines.join('\n')); |
| 278 | 293 |
| 279 return new Element('pre', [new Element.text('code', escaped)]); | 294 var element = new Element('pre', [new Element.text('code', escaped)]); |
| 295 if (syntax != '') { |
| 296 element.attributes['class'] = syntax; |
| 297 } |
| 298 return element; |
| 280 } | 299 } |
| 281 } | 300 } |
| 282 | 301 |
| 283 /// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. | 302 /// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. |
| 284 class HorizontalRuleSyntax extends BlockSyntax { | 303 class HorizontalRuleSyntax extends BlockSyntax { |
| 285 RegExp get pattern => _RE_HR; | 304 RegExp get pattern => _RE_HR; |
| 286 | 305 |
| 306 const HorizontalRuleSyntax(); |
| 307 |
| 287 Node parse(BlockParser parser) { | 308 Node parse(BlockParser parser) { |
| 288 final match = pattern.firstMatch(parser.current); | 309 final match = pattern.firstMatch(parser.current); |
| 289 parser.advance(); | 310 parser.advance(); |
| 290 return new Element.empty('hr'); | 311 return new Element.empty('hr'); |
| 291 } | 312 } |
| 292 } | 313 } |
| 293 | 314 |
| 294 /// Parses inline HTML at the block level. This differs from other markdown | 315 /// Parses inline HTML at the block level. This differs from other markdown |
| 295 /// implementations in several ways: | 316 /// implementations in several ways: |
| 296 /// | 317 /// |
| 297 /// 1. This one is way way WAY simpler. | 318 /// 1. This one is way way WAY simpler. |
| 298 /// 2. All HTML tags at the block level will be treated as blocks. If you | 319 /// 2. All HTML tags at the block level will be treated as blocks. If you |
| 299 /// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. | 320 /// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. |
| 300 /// As soon as it sees something like HTML, it stops mucking with it until | 321 /// As soon as it sees something like HTML, it stops mucking with it until |
| 301 /// it hits the next block. | 322 /// it hits the next block. |
| 302 /// 3. Absolutely no HTML parsing or validation is done. We're a markdown | 323 /// 3. Absolutely no HTML parsing or validation is done. We're a markdown |
| 303 /// parser not an HTML parser! | 324 /// parser not an HTML parser! |
| 304 class BlockHtmlSyntax extends BlockSyntax { | 325 class BlockHtmlSyntax extends BlockSyntax { |
| 305 RegExp get pattern => _RE_HTML; | 326 RegExp get pattern => _RE_HTML; |
| 306 | 327 |
| 307 bool get canEndBlock => false; | 328 bool get canEndBlock => false; |
| 308 | 329 |
| 330 const BlockHtmlSyntax(); |
| 331 |
| 309 Node parse(BlockParser parser) { | 332 Node parse(BlockParser parser) { |
| 310 final childLines = []; | 333 final childLines = []; |
| 311 | 334 |
| 312 // Eat until we hit a blank line. | 335 // Eat until we hit a blank line. |
| 313 while (!parser.isDone && !parser.matches(_RE_EMPTY)) { | 336 while (!parser.isDone && !parser.matches(_RE_EMPTY)) { |
| 314 childLines.add(parser.current); | 337 childLines.add(parser.current); |
| 315 parser.advance(); | 338 parser.advance(); |
| 316 } | 339 } |
| 317 | 340 |
| 318 return new Text(childLines.join('\n')); | 341 return new Text(childLines.join('\n')); |
| 319 } | 342 } |
| 320 } | 343 } |
| 321 | 344 |
| 322 class ListItem { | 345 class ListItem { |
| 323 bool forceBlock = false; | 346 bool forceBlock = false; |
| 324 final List<String> lines; | 347 final List<String> lines; |
| 325 | 348 |
| 326 ListItem(this.lines); | 349 ListItem(this.lines); |
| 327 } | 350 } |
| 328 | 351 |
| 329 /// Base class for both ordered and unordered lists. | 352 /// Base class for both ordered and unordered lists. |
| 330 abstract class ListSyntax extends BlockSyntax { | 353 abstract class ListSyntax extends BlockSyntax { |
| 331 bool get canEndBlock => false; | 354 bool get canEndBlock => false; |
| 332 | 355 |
| 333 String get listTag; | 356 String get listTag; |
| 334 | 357 |
| 358 const ListSyntax(); |
| 359 |
| 335 Node parse(BlockParser parser) { | 360 Node parse(BlockParser parser) { |
| 336 final items = <ListItem>[]; | 361 final items = <ListItem>[]; |
| 337 var childLines = <String>[]; | 362 var childLines = <String>[]; |
| 338 | 363 |
| 339 endItem() { | 364 endItem() { |
| 340 if (childLines.length > 0) { | 365 if (childLines.length > 0) { |
| 341 items.add(new ListItem(childLines)); | 366 items.add(new ListItem(childLines)); |
| 342 childLines = <String>[]; | 367 childLines = <String>[]; |
| 343 } | 368 } |
| 344 } | 369 } |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 467 } | 492 } |
| 468 | 493 |
| 469 return new Element(listTag, itemNodes); | 494 return new Element(listTag, itemNodes); |
| 470 } | 495 } |
| 471 } | 496 } |
| 472 | 497 |
| 473 /// Parses unordered lists. | 498 /// Parses unordered lists. |
| 474 class UnorderedListSyntax extends ListSyntax { | 499 class UnorderedListSyntax extends ListSyntax { |
| 475 RegExp get pattern => _RE_UL; | 500 RegExp get pattern => _RE_UL; |
| 476 String get listTag => 'ul'; | 501 String get listTag => 'ul'; |
| 502 |
| 503 const UnorderedListSyntax(); |
| 477 } | 504 } |
| 478 | 505 |
| 479 /// Parses ordered lists. | 506 /// Parses ordered lists. |
| 480 class OrderedListSyntax extends ListSyntax { | 507 class OrderedListSyntax extends ListSyntax { |
| 481 RegExp get pattern => _RE_OL; | 508 RegExp get pattern => _RE_OL; |
| 482 String get listTag => 'ol'; | 509 String get listTag => 'ol'; |
| 510 |
| 511 const OrderedListSyntax(); |
| 483 } | 512 } |
| 484 | 513 |
| 485 /// Parses paragraphs of regular text. | 514 /// Parses paragraphs of regular text. |
| 486 class ParagraphSyntax extends BlockSyntax { | 515 class ParagraphSyntax extends BlockSyntax { |
| 487 bool get canEndBlock => false; | 516 bool get canEndBlock => false; |
| 488 | 517 |
| 518 const ParagraphSyntax(); |
| 519 |
| 489 bool canParse(BlockParser parser) => true; | 520 bool canParse(BlockParser parser) => true; |
| 490 | 521 |
| 491 Node parse(BlockParser parser) { | 522 Node parse(BlockParser parser) { |
| 492 final childLines = []; | 523 final childLines = []; |
| 493 | 524 |
| 494 // Eat until we hit something that ends a paragraph. | 525 // Eat until we hit something that ends a paragraph. |
| 495 while (!BlockSyntax.isAtBlockEnd(parser)) { | 526 while (!BlockSyntax.isAtBlockEnd(parser)) { |
| 496 childLines.add(parser.current); | 527 childLines.add(parser.current); |
| 497 parser.advance(); | 528 parser.advance(); |
| 498 } | 529 } |
| 499 | 530 |
| 500 final contents = parser.document.parseInline(childLines.join('\n')); | 531 final contents = parser.document.parseInline(childLines.join('\n')); |
| 501 return new Element('p', contents); | 532 return new Element('p', contents); |
| 502 } | 533 } |
| 503 } | 534 } |
| OLD | NEW |