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

Side by Side Diff: lib/src/inline_parser.dart

Issue 1274763005: Clean up: (Closed) Base URL: https://github.com/dart-lang/markdown.git@master
Patch Set: Bump. Created 5 years, 4 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
« no previous file with comments | « lib/src/html_renderer.dart ('k') | lib/src/util.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 library markdown.inline_parser; 5 library markdown.src.inline_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 /// Maintains the internal state needed to parse inline span elements in 11 /// Maintains the internal state needed to parse inline span elements in
12 /// markdown. 12 /// markdown.
13 class InlineParser { 13 class InlineParser {
14 static final List<InlineSyntax> _defaultSyntaxes = <InlineSyntax>[ 14 static final List<InlineSyntax> _defaultSyntaxes = <InlineSyntax>[
15 // This first regexp matches plain text to accelerate parsing. It must 15 // This first regexp matches plain text to accelerate parsing. It must
16 // be written so that it does not match any prefix of any following 16 // be written so that it does not match any prefix of any following
17 // syntax. Most markdown is plain text, so it is faster to match one 17 // syntax. Most markdown is plain text, so it is faster to match one
18 // regexp per 'word' rather than fail to match all the following regexps 18 // regexp per 'word' rather than fail to match all the following regexps
19 // at each non-syntax character position. It is much more important 19 // at each non-syntax character position. It is much more important
20 // that the regexp is fast than complete (for example, adding grouping 20 // that the regexp is fast than complete (for example, adding grouping
21 // is likely to slow the regexp down enough to negate its benefit). 21 // is likely to slow the regexp down enough to negate its benefit).
22 // Since it is purely for optimization, it can be removed for debugging. 22 // Since it is purely for optimization, it can be removed for debugging.
23 23
24 // TODO(amouravski): this regex will glom up any custom syntaxes unless 24 // TODO(amouravski): this regex will glom up any custom syntaxes unless
25 // they're at the beginning. 25 // they're at the beginning.
26 new TextSyntax(r'\s*[A-Za-z0-9]+'), 26 new TextSyntax(r'\s*[A-Za-z0-9]+'),
27 27
28 // The real syntaxes. 28 // The real syntaxes.
29
30 new AutolinkSyntax(), 29 new AutolinkSyntax(),
31 new LinkSyntax(), 30 new LinkSyntax(),
32 new ImageLinkSyntax(), 31 new ImageLinkSyntax(),
33 // "*" surrounded by spaces is left alone. 32 // "*" surrounded by spaces is left alone.
34 new TextSyntax(r' \* '), 33 new TextSyntax(r' \* '),
35 // "_" surrounded by spaces is left alone. 34 // "_" surrounded by spaces is left alone.
36 new TextSyntax(r' _ '), 35 new TextSyntax(r' _ '),
37 // Leave already-encoded HTML entities alone. Ensures we don't turn 36 // Leave already-encoded HTML entities alone. Ensures we don't turn
38 // "&amp;" into "&amp;amp;" 37 // "&amp;" into "&amp;amp;"
39 new TextSyntax(r'&[#a-zA-Z0-9]*;'), 38 new TextSyntax(r'&[#a-zA-Z0-9]*;'),
(...skipping 28 matching lines...) Expand all
68 67
69 /// The current read position. 68 /// The current read position.
70 int pos = 0; 69 int pos = 0;
71 70
72 /// Starting position of the last unconsumed text. 71 /// Starting position of the last unconsumed text.
73 int start = 0; 72 int start = 0;
74 73
75 final List<TagState> _stack; 74 final List<TagState> _stack;
76 75
77 InlineParser(this.source, this.document) : _stack = <TagState>[] { 76 InlineParser(this.source, this.document) : _stack = <TagState>[] {
78 /// User specified syntaxes will be the first syntaxes to be evaluated. 77 // User specified syntaxes are the first syntaxes to be evaluated.
79 if (document.inlineSyntaxes != null) { 78 if (document.inlineSyntaxes != null) {
80 syntaxes.addAll(document.inlineSyntaxes); 79 syntaxes.addAll(document.inlineSyntaxes);
81 } 80 }
81
82 syntaxes.addAll(_defaultSyntaxes); 82 syntaxes.addAll(_defaultSyntaxes);
83
83 // Custom link resolvers goes after the generic text syntax. 84 // Custom link resolvers goes after the generic text syntax.
84 syntaxes.insertAll(1, <InlineSyntax>[ 85 syntaxes.insertAll(1, [
85 new LinkSyntax(linkResolver: document.linkResolver), 86 new LinkSyntax(linkResolver: document.linkResolver),
86 new ImageLinkSyntax(linkResolver: document.imageLinkResolver) 87 new ImageLinkSyntax(linkResolver: document.imageLinkResolver)
87 ]); 88 ]);
88 } 89 }
89 90
90 List<Node> parse() { 91 List<Node> parse() {
91 // Make a fake top tag to hold the results. 92 // Make a fake top tag to hold the results.
92 _stack.add(new TagState(0, 0, null)); 93 _stack.add(new TagState(0, 0, null));
93 94
94 while (!isDone) { 95 while (!isDone) {
95 bool matched = false; 96 var matched = false;
96 97
97 // See if any of the current tags on the stack match. We don't allow tags 98 // See if any of the current tags on the stack match. We don't allow tags
98 // of the same kind to nest, so this takes priority over other possible // matches. 99 // of the same kind to nest, so this takes priority over other possible
99 for (int i = _stack.length - 1; i > 0; i--) { 100 // matches.
101 for (var i = _stack.length - 1; i > 0; i--) {
100 if (_stack[i].tryMatch(this)) { 102 if (_stack[i].tryMatch(this)) {
101 matched = true; 103 matched = true;
102 break; 104 break;
103 } 105 }
104 } 106 }
107
105 if (matched) continue; 108 if (matched) continue;
106 109
107 // See if the current text matches any defined markdown syntax. 110 // See if the current text matches any defined markdown syntax.
108 for (final syntax in syntaxes) { 111 for (var syntax in syntaxes) {
109 if (syntax.tryMatch(this)) { 112 if (syntax.tryMatch(this)) {
110 matched = true; 113 matched = true;
111 break; 114 break;
112 } 115 }
113 } 116 }
117
114 if (matched) continue; 118 if (matched) continue;
115 119
116 // If we got here, it's just text. 120 // If we got here, it's just text.
117 advanceBy(1); 121 advanceBy(1);
118 } 122 }
119 123
120 // Unwind any unmatched tags and get the results. 124 // Unwind any unmatched tags and get the results.
121 return _stack[0].close(this, null); 125 return _stack[0].close(this, null);
122 } 126 }
123 127
124 void writeText() { 128 void writeText() {
125 writeTextRange(start, pos); 129 writeTextRange(start, pos);
126 start = pos; 130 start = pos;
127 } 131 }
128 132
129 void writeTextRange(int start, int end) { 133 void writeTextRange(int start, int end) {
130 if (end > start) { 134 if (end <= start) return;
131 final text = source.substring(start, end);
132 final nodes = _stack.last.children;
133 135
134 // If the previous node is text too, just append. 136 var text = source.substring(start, end);
135 if ((nodes.length > 0) && (nodes.last is Text)) { 137 var nodes = _stack.last.children;
136 final newNode = new Text('${nodes.last.text}$text'); 138
137 nodes[nodes.length - 1] = newNode; 139 // If the previous node is text too, just append.
138 } else { 140 if (nodes.length > 0 && nodes.last is Text) {
139 nodes.add(new Text(text)); 141 nodes[nodes.length - 1] = new Text('${nodes.last.text}$text');
140 } 142 } else {
143 nodes.add(new Text(text));
141 } 144 }
142 } 145 }
143 146
144 void addNode(Node node) { 147 void addNode(Node node) {
145 _stack.last.children.add(node); 148 _stack.last.children.add(node);
146 } 149 }
147 150
148 // TODO(rnystrom): Only need this because RegExp doesn't let you start 151 // TODO(rnystrom): Only need this because RegExp doesn't let you start
149 // searching from a given offset. 152 // searching from a given offset.
153 @deprecated
150 String get currentSource => source.substring(pos, source.length); 154 String get currentSource => source.substring(pos, source.length);
151 155
152 bool get isDone => pos == source.length; 156 bool get isDone => pos == source.length;
153 157
154 void advanceBy(int length) { 158 void advanceBy(int length) {
155 pos += length; 159 pos += length;
156 } 160 }
157 161
158 void consume(int length) { 162 void consume(int length) {
159 pos += length; 163 pos += length;
160 start = pos; 164 start = pos;
161 } 165 }
162 } 166 }
163 167
164 /// Represents one kind of markdown tag that can be parsed. 168 /// Represents one kind of markdown tag that can be parsed.
165 abstract class InlineSyntax { 169 abstract class InlineSyntax {
166 final RegExp pattern; 170 final RegExp pattern;
167 171
168 InlineSyntax(String pattern) : pattern = new RegExp(pattern, multiLine: true); 172 InlineSyntax(String pattern) : pattern = new RegExp(pattern, multiLine: true);
169 173
170 bool tryMatch(InlineParser parser) { 174 bool tryMatch(InlineParser parser) {
171 final startMatch = pattern.firstMatch(parser.currentSource); 175 var startMatch = pattern.matchAsPrefix(parser.source, parser.pos);
172 if ((startMatch != null) && (startMatch.start == 0)) { 176 if (startMatch != null) {
173 // Write any existing plain text up to this point. 177 // Write any existing plain text up to this point.
174 parser.writeText(); 178 parser.writeText();
175 179
176 if (onMatch(parser, startMatch)) { 180 if (onMatch(parser, startMatch)) parser.consume(startMatch[0].length);
177 parser.consume(startMatch[0].length);
178 }
179 return true; 181 return true;
180 } 182 }
183
181 return false; 184 return false;
182 } 185 }
183 186
184 bool onMatch(InlineParser parser, Match match); 187 bool onMatch(InlineParser parser, Match match);
185 } 188 }
186 189
187 /// Matches stuff that should just be passed through as straight text. 190 /// Matches stuff that should just be passed through as straight text.
188 class TextSyntax extends InlineSyntax { 191 class TextSyntax extends InlineSyntax {
189 final String substitute; 192 final String substitute;
193
190 TextSyntax(String pattern, {String sub}) 194 TextSyntax(String pattern, {String sub})
191 : super(pattern), 195 : super(pattern),
192 substitute = sub; 196 substitute = sub;
193 197
194 bool onMatch(InlineParser parser, Match match) { 198 bool onMatch(InlineParser parser, Match match) {
195 if (substitute == null) { 199 if (substitute == null) {
196 // Just use the original matched text. 200 // Just use the original matched text.
197 parser.advanceBy(match[0].length); 201 parser.advanceBy(match[0].length);
198 return false; 202 return false;
199 } 203 }
200 204
201 // Insert the substitution. 205 // Insert the substitution.
202 parser.addNode(new Text(substitute)); 206 parser.addNode(new Text(substitute));
203 return true; 207 return true;
204 } 208 }
205 } 209 }
206 210
207 /// Matches autolinks like `<http://foo.com>`. 211 /// Matches autolinks like `<http://foo.com>`.
208 class AutolinkSyntax extends InlineSyntax { 212 class AutolinkSyntax extends InlineSyntax {
209 AutolinkSyntax() : super(r'<((http|https|ftp)://[^>]*)>'); 213 AutolinkSyntax() : super(r'<((http|https|ftp)://[^>]*)>');
210 // TODO(rnystrom): Make case insensitive. 214 // TODO(rnystrom): Make case insensitive.
211 215
212 bool onMatch(InlineParser parser, Match match) { 216 bool onMatch(InlineParser parser, Match match) {
213 final url = match[1]; 217 var url = match[1];
214 218 var anchor = new Element.text('a', escapeHtml(url));
215 final anchor = new Element.text('a', escapeHtml(url)) 219 anchor.attributes['href'] = url;
216 ..attributes['href'] = url;
217 parser.addNode(anchor); 220 parser.addNode(anchor);
218 221
219 return true; 222 return true;
220 } 223 }
221 } 224 }
222 225
223 /// Matches syntax that has a pair of tags and becomes an element, like `*` for 226 /// Matches syntax that has a pair of tags and becomes an element, like `*` for
224 /// `<em>`. Allows nested tags. 227 /// `<em>`. Allows nested tags.
225 class TagSyntax extends InlineSyntax { 228 class TagSyntax extends InlineSyntax {
226 final RegExp endPattern; 229 final RegExp endPattern;
227 final String tag; 230 final String tag;
228 231
229 TagSyntax(String pattern, {String tag, String end}) 232 TagSyntax(String pattern, {this.tag, String end})
230 : super(pattern), 233 : super(pattern),
231 endPattern = new RegExp((end != null) ? end : pattern, multiLine: true), 234 endPattern = new RegExp((end != null) ? end : pattern, multiLine: true);
232 tag = tag;
233 // TODO(rnystrom): Doing this.field doesn't seem to work with named args.
234 235
235 bool onMatch(InlineParser parser, Match match) { 236 bool onMatch(InlineParser parser, Match match) {
236 parser._stack.add( 237 parser._stack
237 new TagState(parser.pos, parser.pos + match[0].length, this)); 238 .add(new TagState(parser.pos, parser.pos + match[0].length, this));
238 return true; 239 return true;
239 } 240 }
240 241
241 bool onMatchEnd(InlineParser parser, Match match, TagState state) { 242 bool onMatchEnd(InlineParser parser, Match match, TagState state) {
242 parser.addNode(new Element(tag, state.children)); 243 parser.addNode(new Element(tag, state.children));
243 return true; 244 return true;
244 } 245 }
245 } 246 }
246 247
247 /// Matches inline links like `[blah] [id]` and `[blah] (url)`. 248 /// Matches inline links like `[blah] [id]` and `[blah] (url)`.
248 class LinkSyntax extends TagSyntax { 249 class LinkSyntax extends TagSyntax {
249 final Resolver linkResolver; 250 final Resolver linkResolver;
250 251
251 /// Weather or not this link was resolved by a [Resolver] 252 /// Weather or not this link was resolved by a [Resolver]
252 bool resolved = false; 253 bool resolved = false;
253 254
254 /// The regex for the end of a link needs to handle both reference style and 255 /// The regex for the end of a link needs to handle both reference style and
255 /// inline styles as well as optional titles for inline links. To make that 256 /// inline styles as well as optional titles for inline links. To make that
256 /// a bit more palatable, this breaks it into pieces. 257 /// a bit more palatable, this breaks it into pieces.
257 static get linkPattern { 258 static get linkPattern {
258 final refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id. 259 var refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id.
259 final title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes. 260 var title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes.
260 final inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link. 261 var inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
261 return '\](?:($refLink|$inlineLink)|)'; 262 return '\](?:($refLink|$inlineLink)|)';
262 263
263 // The groups matched by this are: 264 // The groups matched by this are:
264 // 1: Will be non-empty if it's either a ref or inline link. Will be empty 265 // 1: Will be non-empty if it's either a ref or inline link. Will be empty
265 // if it's just a bare pair of square brackets with nothing after them. 266 // if it's just a bare pair of square brackets with nothing after them.
266 // 2: Contains the id inside [] for a reference-style link. 267 // 2: Contains the id inside [] for a reference-style link.
267 // 3: Contains the URL for an inline link. 268 // 3: Contains the URL for an inline link.
268 // 4: Contains the title, if present, for an inline link. 269 // 4: Contains the title, if present, for an inline link.
269 } 270 }
270 271
271 LinkSyntax({this.linkResolver, String pattern: r'\['}) 272 LinkSyntax({this.linkResolver, String pattern: r'\['})
272 : super(pattern, end: linkPattern); 273 : super(pattern, end: linkPattern);
273 274
274 Node createNode(InlineParser parser, Match match, TagState state) { 275 Node createNode(InlineParser parser, Match match, TagState state) {
275 // If we didn't match refLink or inlineLink, then it means there was 276 // If we didn't match refLink or inlineLink, then it means there was
276 // nothing after the first square bracket, so it isn't a normal markdown 277 // nothing after the first square bracket, so it isn't a normal markdown
277 // link at all. Instead, we allow users of the library to specify a special 278 // link at all. Instead, we allow users of the library to specify a special
278 // resolver function ([linkResolver]) that may choose to handle 279 // resolver function ([linkResolver]) that may choose to handle
279 // this. Otherwise, it's just treated as plain text. 280 // this. Otherwise, it's just treated as plain text.
280 if (isNullOrEmpty(match[1])) { 281 if (match[1] == null) {
281 if (linkResolver == null) return null; 282 if (linkResolver == null) return null;
282 283
283 // Treat the contents as unparsed text even if they happen to match. This 284 // Treat the contents as unparsed text even if they happen to match. This
284 // way, we can handle things like [LINK_WITH_UNDERSCORES] as a link and 285 // way, we can handle things like [LINK_WITH_UNDERSCORES] as a link and
285 // not get confused by the emphasis. 286 // not get confused by the emphasis.
286 var textToResolve = parser.source.substring(state.endPos, parser.pos); 287 var textToResolve = parser.source.substring(state.endPos, parser.pos);
287 288
288 // See if we have a resolver that will generate a link for us. 289 // See if we have a resolver that will generate a link for us.
289 resolved = true; 290 resolved = true;
290 return linkResolver(textToResolve); 291 return linkResolver(textToResolve);
291 } else { 292 } else {
292 Link link = getLink(parser, match, state); 293 var link = getLink(parser, match, state);
293 if (link == null) return null; 294 if (link == null) return null;
294 295
295 final Element node = new Element('a', state.children) 296 var node = new Element('a', state.children);
296 ..attributes["href"] = escapeHtml(link.url)
297 ..attributes['title'] = escapeHtml(link.title);
298 297
299 cleanMap(node.attributes); 298 node.attributes["href"] = escapeHtml(link.url);
299 if (link.title != null) node.attributes['title'] = escapeHtml(link.title);
300
300 return node; 301 return node;
301 } 302 }
302 } 303 }
303 304
304 Link getLink(InlineParser parser, Match match, TagState state) { 305 Link getLink(InlineParser parser, Match match, TagState state) {
305 if ((match[3] != null) && (match[3] != '')) { 306 if (match[3] != null && match[3] != '') {
306 // Inline link like [foo](url). 307 // Inline link like [foo](url).
307 var url = match[3]; 308 var url = match[3];
308 var title = match[4]; 309 var title = match[4];
309 310
310 // For whatever reason, markdown allows angle-bracketed URLs here. 311 // For whatever reason, markdown allows angle-bracketed URLs here.
311 if (url.startsWith('<') && url.endsWith('>')) { 312 if (url.startsWith('<') && url.endsWith('>')) {
312 url = url.substring(1, url.length - 1); 313 url = url.substring(1, url.length - 1);
313 } 314 }
314 315
315 return new Link(null, url, title); 316 return new Link(null, url, title);
316 } else { 317 } else {
317 var id; 318 var id;
318 // Reference link like [foo] [bar]. 319 // Reference link like [foo] [bar].
319 if (match[2] == '') 320 if (match[2] == '') {
320 // The id is empty ("[]") so infer it from the contents. 321 // The id is empty ("[]") so infer it from the contents.
321 id = parser.source.substring(state.startPos + 1, parser.pos); 322 id = parser.source.substring(state.startPos + 1, parser.pos);
322 else id = match[2]; 323 } else {
324 id = match[2];
325 }
323 326
324 // References are case-insensitive. 327 // References are case-insensitive.
325 id = id.toLowerCase(); 328 id = id.toLowerCase();
326 return parser.document.refLinks[id]; 329 return parser.document.refLinks[id];
327 } 330 }
328 } 331 }
329 332
330 bool onMatchEnd(InlineParser parser, Match match, TagState state) { 333 bool onMatchEnd(InlineParser parser, Match match, TagState state) {
331 Node node = createNode(parser, match, state); 334 var node = createNode(parser, match, state);
332 if (node == null) return false; 335 if (node == null) return false;
336
333 parser.addNode(node); 337 parser.addNode(node);
334 return true; 338 return true;
335 } 339 }
336 } 340 }
337 341
338 /// Matches images like `![alternate text](url "optional title")` and 342 /// Matches images like `![alternate text](url "optional title")` and
339 /// `![alternate text][url reference]`. 343 /// `![alternate text][url reference]`.
340 class ImageLinkSyntax extends LinkSyntax { 344 class ImageLinkSyntax extends LinkSyntax {
341 final Resolver linkResolver; 345 final Resolver linkResolver;
346
342 ImageLinkSyntax({this.linkResolver}) : super(pattern: r'!\['); 347 ImageLinkSyntax({this.linkResolver}) : super(pattern: r'!\[');
343 348
344 Node createNode(InlineParser parser, Match match, TagState state) { 349 Node createNode(InlineParser parser, Match match, TagState state) {
345 var node = super.createNode(parser, match, state); 350 var node = super.createNode(parser, match, state);
351
346 if (resolved) return node; 352 if (resolved) return node;
347 if (node == null) return null; 353 if (node == null) return null;
348 354
349 final Element imageElement = new Element.withTag("img") 355 var imageElement = new Element.withTag("img");
350 ..attributes["src"] = node.attributes["href"] 356 imageElement.attributes["src"] = node.attributes["href"];
351 ..attributes["title"] = node.attributes["title"]
352 ..attributes["alt"] = node.children
353 .map((e) => isNullOrEmpty(e) || e is! Text ? '' : e.text)
354 .join(' ');
355 357
356 cleanMap(imageElement.attributes); 358 if (node.attributes.containsKey("title")) {
359 imageElement.attributes["title"] = node.attributes["title"];
360 }
361
362 var alt = node.children.map((e) => e is! Text ? '' : e.text).join(" ");
363 if (alt != "") imageElement.attributes["alt"] = alt;
357 364
358 node.children 365 node.children
359 ..clear() 366 ..clear()
360 ..add(imageElement); 367 ..add(imageElement);
361 368
362 return node; 369 return node;
363 } 370 }
364 } 371 }
365 372
366 /// Matches backtick-enclosed inline code blocks. 373 /// Matches backtick-enclosed inline code blocks.
(...skipping 19 matching lines...) Expand all
386 final TagSyntax syntax; 393 final TagSyntax syntax;
387 394
388 /// The children of this node. Will be `null` for text nodes. 395 /// The children of this node. Will be `null` for text nodes.
389 final List<Node> children; 396 final List<Node> children;
390 397
391 TagState(this.startPos, this.endPos, this.syntax) : children = <Node>[]; 398 TagState(this.startPos, this.endPos, this.syntax) : children = <Node>[];
392 399
393 /// Attempts to close this tag by matching the current text against its end 400 /// Attempts to close this tag by matching the current text against its end
394 /// pattern. 401 /// pattern.
395 bool tryMatch(InlineParser parser) { 402 bool tryMatch(InlineParser parser) {
396 Match endMatch = syntax.endPattern.firstMatch(parser.currentSource); 403 var endMatch = syntax.endPattern.matchAsPrefix(parser.source, parser.pos);
397 if ((endMatch != null) && (endMatch.start == 0)) { 404 if (endMatch != null) {
398 // Close the tag. 405 // Close the tag.
399 close(parser, endMatch); 406 close(parser, endMatch);
400 return true; 407 return true;
401 } 408 }
402 409
403 return false; 410 return false;
404 } 411 }
405 412
406 /// Pops this tag off the stack, completes it, and adds it to the output. 413 /// Pops this tag off the stack, completes it, and adds it to the output.
407 /// Will discard any unmatched tags that happen to be above it on the stack. 414 /// Will discard any unmatched tags that happen to be above it on the stack.
408 /// If this is the last node in the stack, returns its children. 415 /// If this is the last node in the stack, returns its children.
409 List<Node> close(InlineParser parser, Match endMatch) { 416 List<Node> close(InlineParser parser, Match endMatch) {
410 // If there are unclosed tags on top of this one when it's closed, that 417 // If there are unclosed tags on top of this one when it's closed, that
411 // means they are mismatched. Mismatched tags are treated as plain text in 418 // means they are mismatched. Mismatched tags are treated as plain text in
412 // markdown. So for each tag above this one, we write its start tag as text 419 // markdown. So for each tag above this one, we write its start tag as text
413 // and then adds its children to this one's children. 420 // and then adds its children to this one's children.
414 int index = parser._stack.indexOf(this); 421 var index = parser._stack.indexOf(this);
415 422
416 // Remove the unmatched children. 423 // Remove the unmatched children.
417 final unmatchedTags = parser._stack.sublist(index + 1); 424 var unmatchedTags = parser._stack.sublist(index + 1);
418 parser._stack.removeRange(index + 1, parser._stack.length); 425 parser._stack.removeRange(index + 1, parser._stack.length);
419 426
420 // Flatten them out onto this tag. 427 // Flatten them out onto this tag.
421 for (final unmatched in unmatchedTags) { 428 for (var unmatched in unmatchedTags) {
422 // Write the start tag as text. 429 // Write the start tag as text.
423 parser.writeTextRange(unmatched.startPos, unmatched.endPos); 430 parser.writeTextRange(unmatched.startPos, unmatched.endPos);
424 431
425 // Bequeath its children unto this tag. 432 // Bequeath its children unto this tag.
426 children.addAll(unmatched.children); 433 children.addAll(unmatched.children);
427 } 434 }
428 435
429 // Pop this off the stack. 436 // Pop this off the stack.
430 parser.writeText(); 437 parser.writeText();
431 parser._stack.removeLast(); 438 parser._stack.removeLast();
432 439
433 // If the stack is empty now, this is the special "results" node. 440 // If the stack is empty now, this is the special "results" node.
434 if (parser._stack.length == 0) return children; 441 if (parser._stack.length == 0) return children;
435 442
436 // We are still parsing, so add this to its parent's children. 443 // We are still parsing, so add this to its parent's children.
437 if (syntax.onMatchEnd(parser, endMatch, this)) { 444 if (syntax.onMatchEnd(parser, endMatch, this)) {
438 parser.consume(endMatch[0].length); 445 parser.consume(endMatch[0].length);
439 } else { 446 } else {
440 // Didn't close correctly so revert to text. 447 // Didn't close correctly so revert to text.
441 parser.start = startPos; 448 parser.start = startPos;
442 parser.advanceBy(endMatch[0].length); 449 parser.advanceBy(endMatch[0].length);
443 } 450 }
444 451
445 return null; 452 return null;
446 } 453 }
447 } 454 }
OLDNEW
« no previous file with comments | « lib/src/html_renderer.dart ('k') | lib/src/util.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698