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

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

Issue 1325903002: Make markdown error-free in strong mode. (Closed) Base URL: https://github.com/dart-lang/markdown.git@master
Patch Set: Created 5 years, 3 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
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.src.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
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after
131 } 131 }
132 132
133 void writeTextRange(int start, int end) { 133 void writeTextRange(int start, int end) {
134 if (end <= start) return; 134 if (end <= start) return;
135 135
136 var text = source.substring(start, end); 136 var text = source.substring(start, end);
137 var nodes = _stack.last.children; 137 var nodes = _stack.last.children;
138 138
139 // If the previous node is text too, just append. 139 // If the previous node is text too, just append.
140 if (nodes.length > 0 && nodes.last is Text) { 140 if (nodes.length > 0 && nodes.last is Text) {
141 nodes[nodes.length - 1] = new Text('${nodes.last.text}$text'); 141 var textNode = nodes.last as Text;
142 nodes[nodes.length - 1] = new Text('${textNode.text}$text');
142 } else { 143 } else {
143 nodes.add(new Text(text)); 144 nodes.add(new Text(text));
144 } 145 }
145 } 146 }
146 147
147 void addNode(Node node) { 148 void addNode(Node node) {
148 _stack.last.children.add(node); 149 _stack.last.children.add(node);
149 } 150 }
150 151
151 // TODO(rnystrom): Only need this because RegExp doesn't let you start 152 // TODO(rnystrom): Only need this because RegExp doesn't let you start
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
185 } 186 }
186 187
187 bool onMatch(InlineParser parser, Match match); 188 bool onMatch(InlineParser parser, Match match);
188 } 189 }
189 190
190 /// Matches stuff that should just be passed through as straight text. 191 /// Matches stuff that should just be passed through as straight text.
191 class TextSyntax extends InlineSyntax { 192 class TextSyntax extends InlineSyntax {
192 final String substitute; 193 final String substitute;
193 194
194 TextSyntax(String pattern, {String sub}) 195 TextSyntax(String pattern, {String sub})
195 : super(pattern), 196 : substitute = sub,
196 substitute = sub; 197 super(pattern);
197 198
198 bool onMatch(InlineParser parser, Match match) { 199 bool onMatch(InlineParser parser, Match match) {
199 if (substitute == null) { 200 if (substitute == null) {
200 // Just use the original matched text. 201 // Just use the original matched text.
201 parser.advanceBy(match[0].length); 202 parser.advanceBy(match[0].length);
202 return false; 203 return false;
203 } 204 }
204 205
205 // Insert the substitution. 206 // Insert the substitution.
206 parser.addNode(new Text(substitute)); 207 parser.addNode(new Text(substitute));
(...skipping 16 matching lines...) Expand all
223 } 224 }
224 } 225 }
225 226
226 /// Matches syntax that has a pair of tags and becomes an element, like `*` for 227 /// Matches syntax that has a pair of tags and becomes an element, like `*` for
227 /// `<em>`. Allows nested tags. 228 /// `<em>`. Allows nested tags.
228 class TagSyntax extends InlineSyntax { 229 class TagSyntax extends InlineSyntax {
229 final RegExp endPattern; 230 final RegExp endPattern;
230 final String tag; 231 final String tag;
231 232
232 TagSyntax(String pattern, {this.tag, String end}) 233 TagSyntax(String pattern, {this.tag, String end})
233 : super(pattern), 234 : endPattern = new RegExp((end != null) ? end : pattern, multiLine: true),
234 endPattern = new RegExp((end != null) ? end : pattern, multiLine: true); 235 super(pattern);
235 236
236 bool onMatch(InlineParser parser, Match match) { 237 bool onMatch(InlineParser parser, Match match) {
237 parser._stack 238 parser._stack
238 .add(new TagState(parser.pos, parser.pos + match[0].length, this)); 239 .add(new TagState(parser.pos, parser.pos + match[0].length, this));
239 return true; 240 return true;
240 } 241 }
241 242
242 bool onMatchEnd(InlineParser parser, Match match, TagState state) { 243 bool onMatchEnd(InlineParser parser, Match match, TagState state) {
243 parser.addNode(new Element(tag, state.children)); 244 parser.addNode(new Element(tag, state.children));
244 return true; 245 return true;
245 } 246 }
246 } 247 }
247 248
248 /// Matches inline links like `[blah] [id]` and `[blah] (url)`. 249 /// Matches inline links like `[blah] [id]` and `[blah] (url)`.
249 class LinkSyntax extends TagSyntax { 250 class LinkSyntax extends TagSyntax {
250 final Resolver linkResolver; 251 final Resolver linkResolver;
251 252
252 /// Weather or not this link was resolved by a [Resolver]
253 bool resolved = false;
254
255 /// The regex for the end of a link needs to handle both reference style and 253 /// The regex for the end of a link needs to handle both reference style and
256 /// inline styles as well as optional titles for inline links. To make that 254 /// inline styles as well as optional titles for inline links. To make that
257 /// a bit more palatable, this breaks it into pieces. 255 /// a bit more palatable, this breaks it into pieces.
258 static get linkPattern { 256 static get linkPattern {
259 var refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id. 257 var refLink = r'\s?\[([^\]]*)\]'; // "[id]" reflink id.
260 var title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes. 258 var title = r'(?:[ ]*"([^"]+)"|)'; // Optional title in quotes.
261 var inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link. 259 var inlineLink = '\\s?\\(([^ )]+)$title\\)'; // "(url "title")" link.
262 return '\](?:($refLink|$inlineLink)|)'; 260 return '\](?:($refLink|$inlineLink)|)';
263 261
264 // The groups matched by this are: 262 // The groups matched by this are:
(...skipping 15 matching lines...) Expand all
280 // this. Otherwise, it's just treated as plain text. 278 // this. Otherwise, it's just treated as plain text.
281 if (match[1] == null) { 279 if (match[1] == null) {
282 if (linkResolver == null) return null; 280 if (linkResolver == null) return null;
283 281
284 // Treat the contents as unparsed text even if they happen to match. This 282 // Treat the contents as unparsed text even if they happen to match. This
285 // way, we can handle things like [LINK_WITH_UNDERSCORES] as a link and 283 // way, we can handle things like [LINK_WITH_UNDERSCORES] as a link and
286 // not get confused by the emphasis. 284 // not get confused by the emphasis.
287 var textToResolve = parser.source.substring(state.endPos, parser.pos); 285 var textToResolve = parser.source.substring(state.endPos, parser.pos);
288 286
289 // See if we have a resolver that will generate a link for us. 287 // See if we have a resolver that will generate a link for us.
290 resolved = true;
291 return linkResolver(textToResolve); 288 return linkResolver(textToResolve);
292 } else { 289 } else {
293 var link = getLink(parser, match, state); 290 return _createElement(parser, match, state);
294 if (link == null) return null;
295
296 var node = new Element('a', state.children);
297
298 node.attributes["href"] = escapeHtml(link.url);
299 if (link.title != null) node.attributes['title'] = escapeHtml(link.title);
300
301 return node;
302 } 291 }
303 } 292 }
304 293
294 /// Given that [match] has matched both a title and URL, creates an <a>
nweiz 2015/09/01 20:51:42 Nit: `<a>`
Bob Nystrom 2015/09/01 21:14:57 Done.
295 /// [Element] for it.
296 Element _createElement(InlineParser parser, Match match, TagState state) {
297 var link = getLink(parser, match, state);
298 if (link == null) return null;
299
300 var element = new Element('a', state.children);
301
302 element.attributes["href"] = escapeHtml(link.url);
303 if (link.title != null) {
304 element.attributes['title'] = escapeHtml(link.title);
305 }
306
307 return element;
308 }
309
305 Link getLink(InlineParser parser, Match match, TagState state) { 310 Link getLink(InlineParser parser, Match match, TagState state) {
306 if (match[3] != null && match[3] != '') { 311 if (match[3] != null && match[3] != '') {
307 // Inline link like [foo](url). 312 // Inline link like [foo](url).
308 var url = match[3]; 313 var url = match[3];
309 var title = match[4]; 314 var title = match[4];
310 315
311 // For whatever reason, markdown allows angle-bracketed URLs here. 316 // For whatever reason, markdown allows angle-bracketed URLs here.
312 if (url.startsWith('<') && url.endsWith('>')) { 317 if (url.startsWith('<') && url.endsWith('>')) {
313 url = url.substring(1, url.length - 1); 318 url = url.substring(1, url.length - 1);
314 } 319 }
(...skipping 20 matching lines...) Expand all
335 if (node == null) return false; 340 if (node == null) return false;
336 341
337 parser.addNode(node); 342 parser.addNode(node);
338 return true; 343 return true;
339 } 344 }
340 } 345 }
341 346
342 /// Matches images like `![alternate text](url "optional title")` and 347 /// Matches images like `![alternate text](url "optional title")` and
343 /// `![alternate text][url reference]`. 348 /// `![alternate text][url reference]`.
344 class ImageLinkSyntax extends LinkSyntax { 349 class ImageLinkSyntax extends LinkSyntax {
345 final Resolver linkResolver; 350 ImageLinkSyntax({Resolver linkResolver})
351 : super(linkResolver: linkResolver, pattern: r'!\[');
346 352
347 ImageLinkSyntax({this.linkResolver}) : super(pattern: r'!\['); 353 /// Creates an <a> element from the given complete [match].
354 Element _createElement(InlineParser parser, Match match, TagState state) {
355 var element = super._createElement(parser, match, state);
356 if (element == null) return null;
348 357
349 Node createNode(InlineParser parser, Match match, TagState state) { 358 var image = new Element.withTag("img");
350 var node = super.createNode(parser, match, state); 359 image.attributes["src"] = element.attributes["href"];
351 360
352 if (resolved) return node; 361 if (element.attributes.containsKey("title")) {
353 if (node == null) return null; 362 image.attributes["title"] = element.attributes["title"];
354
355 var imageElement = new Element.withTag("img");
356 imageElement.attributes["src"] = node.attributes["href"];
357
358 if (node.attributes.containsKey("title")) {
359 imageElement.attributes["title"] = node.attributes["title"];
360 } 363 }
361 364
362 var alt = node.children.map((e) => e is! Text ? '' : e.text).join(" "); 365 var alt = element.children.map((e) => e is! Text ? "" : e.text).join(" ");
363 if (alt != "") imageElement.attributes["alt"] = alt; 366 if (alt != "") image.attributes["alt"] = alt;
364 367
365 node.children 368 element.children
366 ..clear() 369 ..clear()
367 ..add(imageElement); 370 ..add(image);
368 371
369 return node; 372 return element;
370 } 373 }
371 } 374 }
372 375
373 /// Matches backtick-enclosed inline code blocks. 376 /// Matches backtick-enclosed inline code blocks.
374 class CodeSyntax extends InlineSyntax { 377 class CodeSyntax extends InlineSyntax {
375 CodeSyntax(String pattern) : super(pattern); 378 CodeSyntax(String pattern) : super(pattern);
376 379
377 bool onMatch(InlineParser parser, Match match) { 380 bool onMatch(InlineParser parser, Match match) {
378 parser.addNode(new Element.text('code', escapeHtml(match[1]))); 381 parser.addNode(new Element.text('code', escapeHtml(match[1])));
379 return true; 382 return true;
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
445 parser.consume(endMatch[0].length); 448 parser.consume(endMatch[0].length);
446 } else { 449 } else {
447 // Didn't close correctly so revert to text. 450 // Didn't close correctly so revert to text.
448 parser.start = startPos; 451 parser.start = startPos;
449 parser.advanceBy(endMatch[0].length); 452 parser.advanceBy(endMatch[0].length);
450 } 453 }
451 454
452 return null; 455 return null;
453 } 456 }
454 } 457 }
OLDNEW
« no previous file with comments | « CHANGELOG.md ('k') | pubspec.yaml » ('j') | test/markdown_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698