Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(15)

Side by Side Diff: third_party/pkg/markdown/lib/src/block_parser.dart

Issue 189333002: pkg/docgen: update markdown to 0.5.1 (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: updates Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « third_party/pkg/markdown/lib/src/ast.dart ('k') | third_party/pkg/markdown/lib/src/document.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698