blob: ef111fbe9645ba013f86546524c75b47c401ee8f [file] [log] [blame]
David Peekf5d84732013-02-20 09:14:56 +11001// 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
3// BSD-style license that can be found in the LICENSE file.
4
5/// Parses text in a markdown-like format and renders to HTML.
6library markdown;
7
8import 'classify.dart';
9
10// TODO(rnystrom): Use "package:" URL (#4968).
11part 'src/markdown/ast.dart';
12part 'src/markdown/block_parser.dart';
13part 'src/markdown/html_renderer.dart';
14part 'src/markdown/inline_parser.dart';
15
16/// Converts the given string of markdown to HTML.
17String markdownToHtml(String markdown) {
18 final document = new Document();
19
20 // Replace windows line endings with unix line endings, and split.
21 final lines = markdown.replaceAll('\r\n','\n').split('\n');
22 document.parseRefLinks(lines);
23 final blocks = document.parseLines(lines);
24 return renderToHtml(blocks);
25}
26
27/// Replaces `<`, `&`, and `>`, with their HTML entity equivalents.
28String escapeHtml(String html) {
29 return html.replaceAll('&', '&amp;')
30 .replaceAll('<', '&lt;')
31 .replaceAll('>', '&gt;');
32}
33
34var _implicitLinkResolver;
35
36Node setImplicitLinkResolver(Node resolver(String text)) {
37 _implicitLinkResolver = resolver;
38}
39
40/// Maintains the context needed to parse a markdown document.
41class Document {
42 final Map<String, Link> refLinks;
43
44 Document()
45 : refLinks = <String, Link>{};
46
47 parseRefLinks(List<String> lines) {
48 // This is a hideous regex. It matches:
49 // [id]: http:foo.com "some title"
50 // Where there may whitespace in there, and where the title may be in
51 // single quotes, double quotes, or parentheses.
52 final indent = r'^[ ]{0,3}'; // Leading indentation.
53 final id = r'\[([^\]]+)\]'; // Reference id in [brackets].
54 final quote = r'"[^"]+"'; // Title in "double quotes".
55 final apos = r"'[^']+'"; // Title in 'single quotes'.
56 final paren = r"\([^)]+\)"; // Title in (parentheses).
57 final pattern = new RegExp(
58 '$indent$id:\\s+(\\S+)\\s*($quote|$apos|$paren|)\\s*\$');
59
60 for (int i = 0; i < lines.length; i++) {
61 final match = pattern.firstMatch(lines[i]);
62 if (match != null) {
63 // Parse the link.
64 var id = match[1];
65 var url = match[2];
66 var title = match[3];
67
68 if (title == '') {
69 // No title.
70 title = null;
71 } else {
72 // Remove "", '', or ().
73 title = title.substring(1, title.length - 1);
74 }
75
76 // References are case-insensitive.
77 id = id.toLowerCase();
78
79 refLinks[id] = new Link(id, url, title);
80
81 // Remove it from the output. We replace it with a blank line which will
82 // get consumed by later processing.
83 lines[i] = '';
84 }
85 }
86 }
87
88 /// Parse the given [lines] of markdown to a series of AST nodes.
89 List<Node> parseLines(List<String> lines) {
90 final parser = new BlockParser(lines, this);
91
92 final blocks = [];
93 while (!parser.isDone) {
94 for (final syntax in BlockSyntax.syntaxes) {
95 if (syntax.canParse(parser)) {
96 final block = syntax.parse(parser);
97 if (block != null) blocks.add(block);
98 break;
99 }
100 }
101 }
102
103 return blocks;
104 }
105
106 /// Takes a string of raw text and processes all inline markdown tags,
107 /// returning a list of AST nodes. For example, given ``"*this **is** a*
108 /// `markdown`"``, returns:
109 /// `<em>this <strong>is</strong> a</em> <code>markdown</code>`.
110 List<Node> parseInline(String text) => new InlineParser(text, this).parse();
111}
112
113class Link {
114 final String id;
115 final String url;
116 final String title;
117 Link(this.id, this.url, this.title);
118}