| 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 library markdown.block_parser; | 5 library markdown.block_parser; |
| 6 | 6 |
| 7 import 'ast.dart'; | 7 import 'ast.dart'; |
| 8 import 'document.dart'; | 8 import 'document.dart'; |
| 9 import 'util.dart'; | 9 import 'util.dart'; |
| 10 | 10 |
| 11 /// The line contains only whitespace or is empty. | 11 /// The line contains only whitespace or is empty. |
| 12 final _RE_EMPTY = new RegExp(r'^([ \t]*)$'); | 12 final _emptyPattern = new RegExp(r'^([ \t]*)$'); |
| 13 | 13 |
| 14 /// 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. |
| 15 final _RE_SETEXT = new RegExp(r'^((=+)|(-+))$'); | 15 final _setextPattern = new RegExp(r'^((=+)|(-+))$'); |
| 16 | 16 |
| 17 /// Leading (and trailing) `#` define atx-style headers. | 17 /// Leading (and trailing) `#` define atx-style headers. |
| 18 final _RE_HEADER = new RegExp(r'^(#{1,6})(.*?)#*$'); | 18 final _headerPattern = new RegExp(r'^(#{1,6})(.*?)#*$'); |
| 19 | 19 |
| 20 /// The line starts with `>` with one optional space after. | 20 /// The line starts with `>` with one optional space after. |
| 21 final _RE_BLOCKQUOTE = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); | 21 final _blockquotePattern = new RegExp(r'^[ ]{0,3}>[ ]?(.*)$'); |
| 22 | 22 |
| 23 /// A line indented four spaces. Used for code blocks and lists. | 23 /// A line indented four spaces. Used for code blocks and lists. |
| 24 final _RE_INDENT = new RegExp(r'^(?: |\t)(.*)$'); | 24 final _indentPattern = new RegExp(r'^(?: |\t)(.*)$'); |
| 25 | 25 |
| 26 /// Fenced code block. | 26 /// Fenced code block. |
| 27 final _RE_CODE = new RegExp(r'^(`{3,}|~{3,})(.*)$'); | 27 final _codePattern = new RegExp(r'^(`{3,}|~{3,})(.*)$'); |
| 28 | 28 |
| 29 /// Three or more hyphens, asterisks or underscores by themselves. Note that | 29 /// Three or more hyphens, asterisks or underscores by themselves. Note that |
| 30 /// 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, |
| 31 /// SETEXT should win. | 31 /// SETEXT should win. |
| 32 final _RE_HR = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' | 32 final _hrPattern = new RegExp(r'^[ ]{0,3}((-+[ ]{0,2}){3,}|' |
| 33 r'(_+[ ]{0,2}){3,}|' | 33 r'(_+[ ]{0,2}){3,}|' |
| 34 r'(\*+[ ]{0,2}){3,})$'); | 34 r'(\*+[ ]{0,2}){3,})$'); |
| 35 | 35 |
| 36 /// 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 |
| 37 /// "<somename". | 37 /// "<somename". |
| 38 final _RE_HTML = new RegExp(r'^<[ ]*\w+[ >]'); | 38 final _htmlPattern = new RegExp(r'^<[ ]*\w+[ >]'); |
| 39 | 39 |
| 40 /// A line starting with one of these markers: `-`, `*`, `+`. May have up to | 40 /// A line starting with one of these markers: `-`, `*`, `+`. May have up to |
| 41 /// three leading spaces before the marker and any number of spaces or tabs | 41 /// three leading spaces before the marker and any number of spaces or tabs |
| 42 /// after. | 42 /// after. |
| 43 final _RE_UL = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$'); | 43 final _ulPattern = new RegExp(r'^[ ]{0,3}[*+-][ \t]+(.*)$'); |
| 44 | 44 |
| 45 /// A line starting with a number like `123.`. May have up to three leading | 45 /// A line starting with a number like `123.`. May have up to three leading |
| 46 /// spaces before the marker and any number of spaces or tabs after. | 46 /// spaces before the marker and any number of spaces or tabs after. |
| 47 final _RE_OL = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$'); | 47 final _olPattern = new RegExp(r'^[ ]{0,3}\d+\.[ \t]+(.*)$'); |
| 48 | 48 |
| 49 /// 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 |
| 50 /// of markdown suitable for further inline parsing. | 50 /// of markdown suitable for further inline parsing. |
| 51 class BlockParser { | 51 class BlockParser { |
| 52 final List<String> lines; | 52 final List<String> lines; |
| 53 | 53 |
| 54 /// The markdown document this parser is parsing. | 54 /// The markdown document this parser is parsing. |
| 55 final Document document; | 55 final Document document; |
| 56 | 56 |
| 57 /// Index of the current line. | 57 /// Index of the current line. |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 113 bool get canEndBlock => true; | 113 bool get canEndBlock => true; |
| 114 | 114 |
| 115 bool canParse(BlockParser parser) { | 115 bool canParse(BlockParser parser) { |
| 116 return pattern.firstMatch(parser.current) != null; | 116 return pattern.firstMatch(parser.current) != null; |
| 117 } | 117 } |
| 118 | 118 |
| 119 Node parse(BlockParser parser); | 119 Node parse(BlockParser parser); |
| 120 | 120 |
| 121 List<String> parseChildLines(BlockParser parser) { | 121 List<String> parseChildLines(BlockParser parser) { |
| 122 // Grab all of the lines that form the blockquote, stripping off the ">". | 122 // Grab all of the lines that form the blockquote, stripping off the ">". |
| 123 final childLines = <String>[]; | 123 var childLines = <String>[]; |
| 124 | 124 |
| 125 while (!parser.isDone) { | 125 while (!parser.isDone) { |
| 126 final match = pattern.firstMatch(parser.current); | 126 var match = pattern.firstMatch(parser.current); |
| 127 if (match == null) break; | 127 if (match == null) break; |
| 128 childLines.add(match[1]); | 128 childLines.add(match[1]); |
| 129 parser.advance(); | 129 parser.advance(); |
| 130 } | 130 } |
| 131 | 131 |
| 132 return childLines; | 132 return childLines; |
| 133 } | 133 } |
| 134 | 134 |
| 135 /// Gets whether or not [parser]'s current line should end the previous block. | 135 /// Gets whether or not [parser]'s current line should end the previous block. |
| 136 static bool isAtBlockEnd(BlockParser parser) { | 136 static bool isAtBlockEnd(BlockParser parser) { |
| 137 if (parser.isDone) return true; | 137 if (parser.isDone) return true; |
| 138 return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock); | 138 return syntaxes.any((s) => s.canParse(parser) && s.canEndBlock); |
| 139 } | 139 } |
| 140 } | 140 } |
| 141 | 141 |
| 142 class EmptyBlockSyntax extends BlockSyntax { | 142 class EmptyBlockSyntax extends BlockSyntax { |
| 143 RegExp get pattern => _RE_EMPTY; | 143 RegExp get pattern => _emptyPattern; |
| 144 | 144 |
| 145 const EmptyBlockSyntax(); | 145 const EmptyBlockSyntax(); |
| 146 | 146 |
| 147 Node parse(BlockParser parser) { | 147 Node parse(BlockParser parser) { |
| 148 parser.advance(); | 148 parser.advance(); |
| 149 | 149 |
| 150 // Don't actually emit anything. | 150 // Don't actually emit anything. |
| 151 return null; | 151 return null; |
| 152 } | 152 } |
| 153 } | 153 } |
| 154 | 154 |
| 155 /// Parses setext-style headers. | 155 /// Parses setext-style headers. |
| 156 class SetextHeaderSyntax extends BlockSyntax { | 156 class SetextHeaderSyntax extends BlockSyntax { |
| 157 const SetextHeaderSyntax(); | 157 const SetextHeaderSyntax(); |
| 158 | 158 |
| 159 bool canParse(BlockParser parser) { | 159 bool canParse(BlockParser parser) { |
| 160 // Note: matches *next* line, not the current one. We're looking for the | 160 // Note: matches *next* line, not the current one. We're looking for the |
| 161 // underlining after this line. | 161 // underlining after this line. |
| 162 return parser.matchesNext(_RE_SETEXT); | 162 return parser.matchesNext(_setextPattern); |
| 163 } | 163 } |
| 164 | 164 |
| 165 Node parse(BlockParser parser) { | 165 Node parse(BlockParser parser) { |
| 166 final match = _RE_SETEXT.firstMatch(parser.next); | 166 var match = _setextPattern.firstMatch(parser.next); |
| 167 | 167 |
| 168 final tag = (match[1][0] == '=') ? 'h1' : 'h2'; | 168 var tag = (match[1][0] == '=') ? 'h1' : 'h2'; |
| 169 final contents = parser.document.parseInline(parser.current); | 169 var contents = parser.document.parseInline(parser.current); |
| 170 parser.advance(); | 170 parser.advance(); |
| 171 parser.advance(); | 171 parser.advance(); |
| 172 | 172 |
| 173 return new Element(tag, contents); | 173 return new Element(tag, contents); |
| 174 } | 174 } |
| 175 } | 175 } |
| 176 | 176 |
| 177 /// Parses atx-style headers: `## Header ##`. | 177 /// Parses atx-style headers: `## Header ##`. |
| 178 class HeaderSyntax extends BlockSyntax { | 178 class HeaderSyntax extends BlockSyntax { |
| 179 RegExp get pattern => _RE_HEADER; | 179 RegExp get pattern => _headerPattern; |
| 180 | 180 |
| 181 const HeaderSyntax(); | 181 const HeaderSyntax(); |
| 182 | 182 |
| 183 Node parse(BlockParser parser) { | 183 Node parse(BlockParser parser) { |
| 184 final match = pattern.firstMatch(parser.current); | 184 var match = pattern.firstMatch(parser.current); |
| 185 parser.advance(); | 185 parser.advance(); |
| 186 final level = match[1].length; | 186 var level = match[1].length; |
| 187 final contents = parser.document.parseInline(match[2].trim()); | 187 var contents = parser.document.parseInline(match[2].trim()); |
| 188 return new Element('h$level', contents); | 188 return new Element('h$level', contents); |
| 189 } | 189 } |
| 190 } | 190 } |
| 191 | 191 |
| 192 /// Parses email-style blockquotes: `> quote`. | 192 /// Parses email-style blockquotes: `> quote`. |
| 193 class BlockquoteSyntax extends BlockSyntax { | 193 class BlockquoteSyntax extends BlockSyntax { |
| 194 RegExp get pattern => _RE_BLOCKQUOTE; | 194 RegExp get pattern => _blockquotePattern; |
| 195 | 195 |
| 196 const BlockquoteSyntax(); | 196 const BlockquoteSyntax(); |
| 197 | 197 |
| 198 Node parse(BlockParser parser) { | 198 Node parse(BlockParser parser) { |
| 199 final childLines = parseChildLines(parser); | 199 var childLines = parseChildLines(parser); |
| 200 | 200 |
| 201 // Recursively parse the contents of the blockquote. | 201 // Recursively parse the contents of the blockquote. |
| 202 final children = parser.document.parseLines(childLines); | 202 var children = parser.document.parseLines(childLines); |
| 203 | 203 |
| 204 return new Element('blockquote', children); | 204 return new Element('blockquote', children); |
| 205 } | 205 } |
| 206 } | 206 } |
| 207 | 207 |
| 208 /// Parses preformatted code blocks that are indented four spaces. | 208 /// Parses preformatted code blocks that are indented four spaces. |
| 209 class CodeBlockSyntax extends BlockSyntax { | 209 class CodeBlockSyntax extends BlockSyntax { |
| 210 RegExp get pattern => _RE_INDENT; | 210 RegExp get pattern => _indentPattern; |
| 211 | 211 |
| 212 const CodeBlockSyntax(); | 212 const CodeBlockSyntax(); |
| 213 | 213 |
| 214 List<String> parseChildLines(BlockParser parser) { | 214 List<String> parseChildLines(BlockParser parser) { |
| 215 final childLines = <String>[]; | 215 var childLines = <String>[]; |
| 216 | 216 |
| 217 while (!parser.isDone) { | 217 while (!parser.isDone) { |
| 218 var match = pattern.firstMatch(parser.current); | 218 var match = pattern.firstMatch(parser.current); |
| 219 if (match != null) { | 219 if (match != null) { |
| 220 childLines.add(match[1]); | 220 childLines.add(match[1]); |
| 221 parser.advance(); | 221 parser.advance(); |
| 222 } else { | 222 } else { |
| 223 // If there's a codeblock, then a newline, then a codeblock, keep the | 223 // If there's a codeblock, then a newline, then a codeblock, keep the |
| 224 // code blocks together. | 224 // code blocks together. |
| 225 var nextMatch = | 225 var nextMatch = |
| 226 parser.next != null ? pattern.firstMatch(parser.next) : null; | 226 parser.next != null ? pattern.firstMatch(parser.next) : null; |
| 227 if (parser.current.trim() == '' && nextMatch != null) { | 227 if (parser.current.trim() == '' && nextMatch != null) { |
| 228 childLines.add(''); | 228 childLines.add(''); |
| 229 childLines.add(nextMatch[1]); | 229 childLines.add(nextMatch[1]); |
| 230 parser.advance(); | 230 parser.advance(); |
| 231 parser.advance(); | 231 parser.advance(); |
| 232 } else { | 232 } else { |
| 233 break; | 233 break; |
| 234 } | 234 } |
| 235 } | 235 } |
| 236 } | 236 } |
| 237 return childLines; | 237 return childLines; |
| 238 } | 238 } |
| 239 | 239 |
| 240 Node parse(BlockParser parser) { | 240 Node parse(BlockParser parser) { |
| 241 final childLines = parseChildLines(parser); | 241 var childLines = parseChildLines(parser); |
| 242 | 242 |
| 243 // The Markdown tests expect a trailing newline. | 243 // The Markdown tests expect a trailing newline. |
| 244 childLines.add(''); | 244 childLines.add(''); |
| 245 | 245 |
| 246 // Escape the code. | 246 // Escape the code. |
| 247 final escaped = escapeHtml(childLines.join('\n')); | 247 var escaped = escapeHtml(childLines.join('\n')); |
| 248 | 248 |
| 249 return new Element('pre', [new Element.text('code', escaped)]); | 249 return new Element('pre', [new Element.text('code', escaped)]); |
| 250 } | 250 } |
| 251 } | 251 } |
| 252 | 252 |
| 253 /// Parses preformatted code blocks between two ~~~ or ``` sequences. | 253 /// Parses preformatted code blocks between two ~~~ or ``` sequences. |
| 254 /// [Pandoc's markdown documentation](http://johnmacfarlane.net/pandoc/demo/exam
ple9/pandocs-markdown.html). | 254 /// |
| 255 /// See [Pandoc's documentation](http://johnmacfarlane.net/pandoc/demo/example9/
pandocs-markdown.html). |
| 255 class FencedCodeBlockSyntax extends BlockSyntax { | 256 class FencedCodeBlockSyntax extends BlockSyntax { |
| 256 RegExp get pattern => _RE_CODE; | 257 RegExp get pattern => _codePattern; |
| 257 | 258 |
| 258 const FencedCodeBlockSyntax(); | 259 const FencedCodeBlockSyntax(); |
| 259 | 260 |
| 260 List<String> parseChildLines(BlockParser parser, [String endBlock]) { | 261 List<String> parseChildLines(BlockParser parser, [String endBlock]) { |
| 261 if (endBlock == null) endBlock = ''; | 262 if (endBlock == null) endBlock = ''; |
| 262 | 263 |
| 263 final childLines = <String>[]; | 264 var childLines = <String>[]; |
| 264 parser.advance(); | 265 parser.advance(); |
| 266 |
| 265 while (!parser.isDone) { | 267 while (!parser.isDone) { |
| 266 var match = pattern.firstMatch(parser.current); | 268 var match = pattern.firstMatch(parser.current); |
| 267 if (match == null || !match[1].startsWith(endBlock)) { | 269 if (match == null || !match[1].startsWith(endBlock)) { |
| 268 childLines.add(parser.current); | 270 childLines.add(parser.current); |
| 269 parser.advance(); | 271 parser.advance(); |
| 270 } else { | 272 } else { |
| 271 parser.advance(); | 273 parser.advance(); |
| 272 break; | 274 break; |
| 273 } | 275 } |
| 274 } | 276 } |
| 277 |
| 275 return childLines; | 278 return childLines; |
| 276 } | 279 } |
| 277 | 280 |
| 278 Node parse(BlockParser parser) { | 281 Node parse(BlockParser parser) { |
| 279 // Get the syntax identifier, if there is one. | 282 // Get the syntax identifier, if there is one. |
| 280 var match = pattern.firstMatch(parser.current); | 283 var match = pattern.firstMatch(parser.current); |
| 281 var endBlock = match.group(1); | 284 var endBlock = match.group(1); |
| 282 var syntax = match.group(2); | 285 var syntax = match.group(2); |
| 283 | 286 |
| 284 final childLines = parseChildLines(parser, endBlock); | 287 var childLines = parseChildLines(parser, endBlock); |
| 285 | 288 |
| 286 // The Markdown tests expect a trailing newline. | 289 // The Markdown tests expect a trailing newline. |
| 287 childLines.add(''); | 290 childLines.add(''); |
| 288 | 291 |
| 289 // Escape the code. | 292 // Escape the code. |
| 290 final escaped = escapeHtml(childLines.join('\n')); | 293 var escaped = escapeHtml(childLines.join('\n')); |
| 291 | 294 |
| 292 var element = new Element('pre', [new Element.text('code', escaped)]); | 295 var element = new Element('pre', [new Element.text('code', escaped)]); |
| 293 if (syntax != '') { | 296 if (syntax != '') element.attributes['class'] = syntax; |
| 294 element.attributes['class'] = syntax; | 297 |
| 295 } | |
| 296 return element; | 298 return element; |
| 297 } | 299 } |
| 298 } | 300 } |
| 299 | 301 |
| 300 /// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. | 302 /// Parses horizontal rules like `---`, `_ _ _`, `* * *`, etc. |
| 301 class HorizontalRuleSyntax extends BlockSyntax { | 303 class HorizontalRuleSyntax extends BlockSyntax { |
| 302 RegExp get pattern => _RE_HR; | 304 RegExp get pattern => _hrPattern; |
| 303 | 305 |
| 304 const HorizontalRuleSyntax(); | 306 const HorizontalRuleSyntax(); |
| 305 | 307 |
| 306 Node parse(BlockParser parser) { | 308 Node parse(BlockParser parser) { |
| 307 parser.advance(); | 309 parser.advance(); |
| 308 return new Element.empty('hr'); | 310 return new Element.empty('hr'); |
| 309 } | 311 } |
| 310 } | 312 } |
| 311 | 313 |
| 312 /// Parses inline HTML at the block level. This differs from other markdown | 314 /// Parses inline HTML at the block level. This differs from other markdown |
| 313 /// implementations in several ways: | 315 /// implementations in several ways: |
| 314 /// | 316 /// |
| 315 /// 1. This one is way way WAY simpler. | 317 /// 1. This one is way way WAY simpler. |
| 316 /// 2. All HTML tags at the block level will be treated as blocks. If you | 318 /// 2. All HTML tags at the block level will be treated as blocks. If you |
| 317 /// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. | 319 /// start a paragraph with `<em>`, it will not wrap it in a `<p>` for you. |
| 318 /// As soon as it sees something like HTML, it stops mucking with it until | 320 /// As soon as it sees something like HTML, it stops mucking with it until |
| 319 /// it hits the next block. | 321 /// it hits the next block. |
| 320 /// 3. Absolutely no HTML parsing or validation is done. We're a markdown | 322 /// 3. Absolutely no HTML parsing or validation is done. We're a markdown |
| 321 /// parser not an HTML parser! | 323 /// parser not an HTML parser! |
| 322 class BlockHtmlSyntax extends BlockSyntax { | 324 class BlockHtmlSyntax extends BlockSyntax { |
| 323 RegExp get pattern => _RE_HTML; | 325 RegExp get pattern => _htmlPattern; |
| 324 | 326 |
| 325 bool get canEndBlock => false; | 327 bool get canEndBlock => false; |
| 326 | 328 |
| 327 const BlockHtmlSyntax(); | 329 const BlockHtmlSyntax(); |
| 328 | 330 |
| 329 Node parse(BlockParser parser) { | 331 Node parse(BlockParser parser) { |
| 330 final childLines = []; | 332 var childLines = <String>[]; |
| 331 | 333 |
| 332 // Eat until we hit a blank line. | 334 // Eat until we hit a blank line. |
| 333 while (!parser.isDone && !parser.matches(_RE_EMPTY)) { | 335 while (!parser.isDone && !parser.matches(_emptyPattern)) { |
| 334 childLines.add(parser.current); | 336 childLines.add(parser.current); |
| 335 parser.advance(); | 337 parser.advance(); |
| 336 } | 338 } |
| 337 | 339 |
| 338 return new Text(childLines.join('\n')); | 340 return new Text(childLines.join('\n')); |
| 339 } | 341 } |
| 340 } | 342 } |
| 341 | 343 |
| 342 class ListItem { | 344 class ListItem { |
| 343 bool forceBlock = false; | 345 bool forceBlock = false; |
| 344 final List<String> lines; | 346 final List<String> lines; |
| 345 | 347 |
| 346 ListItem(this.lines); | 348 ListItem(this.lines); |
| 347 } | 349 } |
| 348 | 350 |
| 349 /// Base class for both ordered and unordered lists. | 351 /// Base class for both ordered and unordered lists. |
| 350 abstract class ListSyntax extends BlockSyntax { | 352 abstract class ListSyntax extends BlockSyntax { |
| 351 bool get canEndBlock => false; | 353 bool get canEndBlock => false; |
| 352 | 354 |
| 353 String get listTag; | 355 String get listTag; |
| 354 | 356 |
| 355 const ListSyntax(); | 357 const ListSyntax(); |
| 356 | 358 |
| 357 Node parse(BlockParser parser) { | 359 Node parse(BlockParser parser) { |
| 358 final items = <ListItem>[]; | 360 var items = <ListItem>[]; |
| 359 var childLines = <String>[]; | 361 var childLines = <String>[]; |
| 360 | 362 |
| 361 endItem() { | 363 endItem() { |
| 362 if (childLines.length > 0) { | 364 if (childLines.length > 0) { |
| 363 items.add(new ListItem(childLines)); | 365 items.add(new ListItem(childLines)); |
| 364 childLines = <String>[]; | 366 childLines = <String>[]; |
| 365 } | 367 } |
| 366 } | 368 } |
| 367 | 369 |
| 368 var match; | 370 var match; |
| 369 tryMatch(RegExp pattern) { | 371 tryMatch(RegExp pattern) { |
| 370 match = pattern.firstMatch(parser.current); | 372 match = pattern.firstMatch(parser.current); |
| 371 return match != null; | 373 return match != null; |
| 372 } | 374 } |
| 373 | 375 |
| 374 while (!parser.isDone) { | 376 while (!parser.isDone) { |
| 375 if (tryMatch(_RE_EMPTY)) { | 377 if (tryMatch(_emptyPattern)) { |
| 376 // Add a blank line to the current list item. | 378 // Add a blank line to the current list item. |
| 377 childLines.add(''); | 379 childLines.add(''); |
| 378 } else if (tryMatch(_RE_UL) || tryMatch(_RE_OL)) { | 380 } else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) { |
| 379 // End the current list item and start a new one. | 381 // End the current list item and start a new one. |
| 380 endItem(); | 382 endItem(); |
| 381 childLines.add(match[1]); | 383 childLines.add(match[1]); |
| 382 } else if (tryMatch(_RE_INDENT)) { | 384 } else if (tryMatch(_indentPattern)) { |
| 383 // Strip off indent and add to current item. | 385 // Strip off indent and add to current item. |
| 384 childLines.add(match[1]); | 386 childLines.add(match[1]); |
| 385 } else if (BlockSyntax.isAtBlockEnd(parser)) { | 387 } else if (BlockSyntax.isAtBlockEnd(parser)) { |
| 386 // Done with the list. | 388 // Done with the list. |
| 387 break; | 389 break; |
| 388 } else { | 390 } else { |
| 389 // Anything else is paragraph text or other stuff that can be in a list | 391 // Anything else is paragraph text or other stuff that can be in a list |
| 390 // item. However, if the previous item is a blank line, this means we're | 392 // item. However, if the previous item is a blank line, this means we're |
| 391 // done with the list and are starting a new top-level paragraph. | 393 // done with the list and are starting a new top-level paragraph. |
| 392 if ((childLines.length > 0) && (childLines.last == '')) break; | 394 if ((childLines.length > 0) && (childLines.last == '')) break; |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 429 // UL, OL) it's a block. (This is for cases like "* > quote".) | 431 // UL, OL) it's a block. (This is for cases like "* > quote".) |
| 430 // - If there was a blank line between this item and the previous one, it's | 432 // - If there was a blank line between this item and the previous one, it's |
| 431 // a block. | 433 // a block. |
| 432 // - If there was a blank line between this item and the next one, it's a | 434 // - If there was a blank line between this item and the next one, it's a |
| 433 // block. | 435 // block. |
| 434 // - Otherwise, parse it as an inline. | 436 // - Otherwise, parse it as an inline. |
| 435 | 437 |
| 436 // Remove any trailing empty lines and note which items are separated by | 438 // Remove any trailing empty lines and note which items are separated by |
| 437 // empty lines. Do this before seeing which items are single-line so that | 439 // empty lines. Do this before seeing which items are single-line so that |
| 438 // trailing empty lines on the last item don't force it into being a block. | 440 // trailing empty lines on the last item don't force it into being a block. |
| 439 for (int i = 0; i < items.length; i++) { | 441 for (var i = 0; i < items.length; i++) { |
| 440 for (int j = items[i].lines.length - 1; j > 0; j--) { | 442 for (var j = items[i].lines.length - 1; j > 0; j--) { |
| 441 if (_RE_EMPTY.firstMatch(items[i].lines[j]) != null) { | 443 if (_emptyPattern.firstMatch(items[i].lines[j]) != null) { |
| 442 // Found an empty line. Item and one after it are blocks. | 444 // Found an empty line. Item and one after it are blocks. |
| 443 if (i < items.length - 1) { | 445 if (i < items.length - 1) { |
| 444 items[i].forceBlock = true; | 446 items[i].forceBlock = true; |
| 445 items[i + 1].forceBlock = true; | 447 items[i + 1].forceBlock = true; |
| 446 } | 448 } |
| 447 items[i].lines.removeLast(); | 449 items[i].lines.removeLast(); |
| 448 } else { | 450 } else { |
| 449 break; | 451 break; |
| 450 } | 452 } |
| 451 } | 453 } |
| 452 } | 454 } |
| 453 | 455 |
| 454 // Convert the list items to Nodes. | 456 // Convert the list items to Nodes. |
| 455 final itemNodes = <Node>[]; | 457 var itemNodes = <Node>[]; |
| 456 for (final item in items) { | 458 for (var item in items) { |
| 457 bool blockItem = item.forceBlock || (item.lines.length > 1); | 459 var blockItem = item.forceBlock || (item.lines.length > 1); |
| 458 | 460 |
| 459 // See if it matches some block parser. | 461 // See if it matches some block parser. |
| 460 final blocksInList = [ | 462 var blocksInList = [ |
| 461 _RE_BLOCKQUOTE, | 463 _blockquotePattern, |
| 462 _RE_HEADER, | 464 _headerPattern, |
| 463 _RE_HR, | 465 _hrPattern, |
| 464 _RE_INDENT, | 466 _indentPattern, |
| 465 _RE_UL, | 467 _ulPattern, |
| 466 _RE_OL | 468 _olPattern |
| 467 ]; | 469 ]; |
| 468 | 470 |
| 469 if (!blockItem) { | 471 if (!blockItem) { |
| 470 for (final pattern in blocksInList) { | 472 for (var pattern in blocksInList) { |
| 471 if (pattern.firstMatch(item.lines[0]) != null) { | 473 if (pattern.firstMatch(item.lines[0]) != null) { |
| 472 blockItem = true; | 474 blockItem = true; |
| 473 break; | 475 break; |
| 474 } | 476 } |
| 475 } | 477 } |
| 476 } | 478 } |
| 477 | 479 |
| 478 // Parse the item as a block or inline. | 480 // Parse the item as a block or inline. |
| 479 if (blockItem) { | 481 if (blockItem) { |
| 480 // Block list item. | 482 // Block list item. |
| 481 final children = parser.document.parseLines(item.lines); | 483 var children = parser.document.parseLines(item.lines); |
| 482 itemNodes.add(new Element('li', children)); | 484 itemNodes.add(new Element('li', children)); |
| 483 } else { | 485 } else { |
| 484 // Raw list item. | 486 // Raw list item. |
| 485 final contents = parser.document.parseInline(item.lines[0]); | 487 var contents = parser.document.parseInline(item.lines[0]); |
| 486 itemNodes.add(new Element('li', contents)); | 488 itemNodes.add(new Element('li', contents)); |
| 487 } | 489 } |
| 488 } | 490 } |
| 489 | 491 |
| 490 return new Element(listTag, itemNodes); | 492 return new Element(listTag, itemNodes); |
| 491 } | 493 } |
| 492 } | 494 } |
| 493 | 495 |
| 494 /// Parses unordered lists. | 496 /// Parses unordered lists. |
| 495 class UnorderedListSyntax extends ListSyntax { | 497 class UnorderedListSyntax extends ListSyntax { |
| 496 RegExp get pattern => _RE_UL; | 498 RegExp get pattern => _ulPattern; |
| 497 String get listTag => 'ul'; | 499 String get listTag => 'ul'; |
| 498 | 500 |
| 499 const UnorderedListSyntax(); | 501 const UnorderedListSyntax(); |
| 500 } | 502 } |
| 501 | 503 |
| 502 /// Parses ordered lists. | 504 /// Parses ordered lists. |
| 503 class OrderedListSyntax extends ListSyntax { | 505 class OrderedListSyntax extends ListSyntax { |
| 504 RegExp get pattern => _RE_OL; | 506 RegExp get pattern => _olPattern; |
| 505 String get listTag => 'ol'; | 507 String get listTag => 'ol'; |
| 506 | 508 |
| 507 const OrderedListSyntax(); | 509 const OrderedListSyntax(); |
| 508 } | 510 } |
| 509 | 511 |
| 510 /// Parses paragraphs of regular text. | 512 /// Parses paragraphs of regular text. |
| 511 class ParagraphSyntax extends BlockSyntax { | 513 class ParagraphSyntax extends BlockSyntax { |
| 512 bool get canEndBlock => false; | 514 bool get canEndBlock => false; |
| 513 | 515 |
| 514 const ParagraphSyntax(); | 516 const ParagraphSyntax(); |
| 515 | 517 |
| 516 bool canParse(BlockParser parser) => true; | 518 bool canParse(BlockParser parser) => true; |
| 517 | 519 |
| 518 Node parse(BlockParser parser) { | 520 Node parse(BlockParser parser) { |
| 519 final childLines = []; | 521 var childLines = <String>[]; |
| 520 | 522 |
| 521 // Eat until we hit something that ends a paragraph. | 523 // Eat until we hit something that ends a paragraph. |
| 522 while (!BlockSyntax.isAtBlockEnd(parser)) { | 524 while (!BlockSyntax.isAtBlockEnd(parser)) { |
| 523 childLines.add(parser.current); | 525 childLines.add(parser.current); |
| 524 parser.advance(); | 526 parser.advance(); |
| 525 } | 527 } |
| 526 | 528 |
| 527 final contents = parser.document.parseInline(childLines.join('\n')); | 529 var contents = parser.document.parseInline(childLines.join('\n')); |
| 528 return new Element('p', contents); | 530 return new Element('p', contents); |
| 529 } | 531 } |
| 530 } | 532 } |
| OLD | NEW |