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 /// 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 |