Chromium Code Reviews| 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.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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 `` and | 347 /// Matches images like `` 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 Loading... | |
| 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 } |
| OLD | NEW |