OLD | NEW |
1 /** | 1 /// This library contains extra APIs that aren't in the DOM, but are useful |
2 * This library contains extra APIs that aren't in the DOM, but are useful | 2 /// when interacting with the parse tree. |
3 * when interacting with the parse tree. | |
4 */ | |
5 library dom_parsing; | 3 library dom_parsing; |
6 | 4 |
7 import 'dom.dart'; | 5 import 'dom.dart'; |
8 | 6 |
9 /** A simple tree visitor for the DOM nodes. */ | 7 /// A simple tree visitor for the DOM nodes. |
10 class TreeVisitor { | 8 class TreeVisitor { |
11 visit(Node node) { | 9 visit(Node node) { |
12 switch (node.nodeType) { | 10 switch (node.nodeType) { |
13 case Node.ELEMENT_NODE: return visitElement(node); | 11 case Node.ELEMENT_NODE: return visitElement(node); |
14 case Node.TEXT_NODE: return visitText(node); | 12 case Node.TEXT_NODE: return visitText(node); |
15 case Node.COMMENT_NODE: return visitComment(node); | 13 case Node.COMMENT_NODE: return visitComment(node); |
16 case Node.DOCUMENT_FRAGMENT_NODE: return visitDocumentFragment(node); | 14 case Node.DOCUMENT_FRAGMENT_NODE: return visitDocumentFragment(node); |
17 case Node.DOCUMENT_NODE: return visitDocument(node); | 15 case Node.DOCUMENT_NODE: return visitDocument(node); |
18 case Node.DOCUMENT_TYPE_NODE: return visitDocumentType(node); | 16 case Node.DOCUMENT_TYPE_NODE: return visitDocumentType(node); |
19 default: throw new UnsupportedError('DOM node type ${node.nodeType}'); | 17 default: throw new UnsupportedError('DOM node type ${node.nodeType}'); |
20 } | 18 } |
21 } | 19 } |
22 | 20 |
23 visitChildren(Node node) { | 21 visitChildren(Node node) { |
24 // Allow for mutations (remove works) while iterating. | 22 // Allow for mutations (remove works) while iterating. |
25 for (var child in node.nodes.toList()) visit(child); | 23 for (var child in node.nodes.toList()) visit(child); |
26 } | 24 } |
27 | 25 |
28 /** | 26 /// The fallback handler if the more specific visit method hasn't been |
29 * The fallback handler if the more specific visit method hasn't been | 27 /// overriden. Only use this from a subclass of [TreeVisitor], otherwise |
30 * overriden. Only use this from a subclass of [TreeVisitor], otherwise | 28 /// call [visit] instead. |
31 * call [visit] instead. | |
32 */ | |
33 visitNodeFallback(Node node) => visitChildren(node); | 29 visitNodeFallback(Node node) => visitChildren(node); |
34 | 30 |
35 visitDocument(Document node) => visitNodeFallback(node); | 31 visitDocument(Document node) => visitNodeFallback(node); |
36 | 32 |
37 visitDocumentType(DocumentType node) => visitNodeFallback(node); | 33 visitDocumentType(DocumentType node) => visitNodeFallback(node); |
38 | 34 |
39 visitText(Text node) => visitNodeFallback(node); | 35 visitText(Text node) => visitNodeFallback(node); |
40 | 36 |
41 // TODO(jmesserly): visit attributes. | 37 // TODO(jmesserly): visit attributes. |
42 visitElement(Element node) => visitNodeFallback(node); | 38 visitElement(Element node) => visitNodeFallback(node); |
43 | 39 |
44 visitComment(Comment node) => visitNodeFallback(node); | 40 visitComment(Comment node) => visitNodeFallback(node); |
45 | 41 |
46 // Note: visits document by default because DocumentFragment is a Document. | 42 // Note: visits document by default because DocumentFragment is a Document. |
47 visitDocumentFragment(DocumentFragment node) => visitDocument(node); | 43 visitDocumentFragment(DocumentFragment node) => visitDocument(node); |
48 } | 44 } |
49 | 45 |
50 /** | 46 /// Converts the DOM tree into an HTML string with code markup suitable for |
51 * Converts the DOM tree into an HTML string with code markup suitable for | 47 /// displaying the HTML's source code with CSS colors for different parts of the |
52 * displaying the HTML's source code with CSS colors for different parts of the | 48 /// markup. See also [CodeMarkupVisitor]. |
53 * markup. See also [CodeMarkupVisitor]. | |
54 */ | |
55 String htmlToCodeMarkup(Node node) { | 49 String htmlToCodeMarkup(Node node) { |
56 return (new CodeMarkupVisitor()..visit(node)).toString(); | 50 return (new CodeMarkupVisitor()..visit(node)).toString(); |
57 } | 51 } |
58 | 52 |
59 /** | 53 /// Converts the DOM tree into an HTML string with code markup suitable for |
60 * Converts the DOM tree into an HTML string with code markup suitable for | 54 /// displaying the HTML's source code with CSS colors for different parts of the |
61 * displaying the HTML's source code with CSS colors for different parts of the | 55 /// markup. See also [htmlToCodeMarkup]. |
62 * markup. See also [htmlToCodeMarkup]. | |
63 */ | |
64 class CodeMarkupVisitor extends TreeVisitor { | 56 class CodeMarkupVisitor extends TreeVisitor { |
65 final StringBuffer _str; | 57 final StringBuffer _str; |
66 | 58 |
67 CodeMarkupVisitor() : _str = new StringBuffer(); | 59 CodeMarkupVisitor() : _str = new StringBuffer(); |
68 | 60 |
69 String toString() => _str.toString(); | 61 String toString() => _str.toString(); |
70 | 62 |
71 visitDocument(Document node) { | 63 visitDocument(Document node) { |
72 _str.write("<pre>"); | 64 _str.write("<pre>"); |
73 visitChildren(node); | 65 visitChildren(node); |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
106 | 98 |
107 visitComment(Comment node) { | 99 visitComment(Comment node) { |
108 var data = htmlSerializeEscape(node.data); | 100 var data = htmlSerializeEscape(node.data); |
109 _str.write('<code class="markup comment"><!--${data}--></code>'); | 101 _str.write('<code class="markup comment"><!--${data}--></code>'); |
110 } | 102 } |
111 } | 103 } |
112 | 104 |
113 | 105 |
114 // TODO(jmesserly): reconcile this with dart:web htmlEscape. | 106 // TODO(jmesserly): reconcile this with dart:web htmlEscape. |
115 // This one might be more useful, as it is HTML5 spec compliant. | 107 // This one might be more useful, as it is HTML5 spec compliant. |
116 /** | 108 /// Escapes [text] for use in the |
117 * Escapes [text] for use in the | 109 /// [HTML fragment serialization algorithm][1]. In particular, as described |
118 * [HTML fragment serialization algorithm][1]. In particular, as described | 110 /// in the [specification][2]: |
119 * in the [specification][2]: | 111 /// |
120 * | 112 /// - Replace any occurrence of the `&` character by the string `&`. |
121 * - Replace any occurrence of the `&` character by the string `&`. | 113 /// - Replace any occurrences of the U+00A0 NO-BREAK SPACE character by the |
122 * - Replace any occurrences of the U+00A0 NO-BREAK SPACE character by the | 114 /// string ` `. |
123 * string ` `. | 115 /// - If the algorithm was invoked in [attributeMode], replace any occurrences |
124 * - If the algorithm was invoked in [attributeMode], replace any occurrences of | 116 /// of the `"` character by the string `"`. |
125 * the `"` character by the string `"`. | 117 /// - If the algorithm was not invoked in [attributeMode], replace any |
126 * - If the algorithm was not invoked in [attributeMode], replace any | 118 /// occurrences of the `<` character by the string `<`, and any occurrences |
127 * occurrences of the `<` character by the string `<`, and any occurrences | 119 /// of the `>` character by the string `>`. |
128 * of the `>` character by the string `>`. | 120 /// |
129 * | 121 /// [1]: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.htm
l#serializing-html-fragments |
130 * [1]: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html
#serializing-html-fragments | 122 /// [2]: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.htm
l#escapingString |
131 * [2]: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html
#escapingString | |
132 */ | |
133 String htmlSerializeEscape(String text, {bool attributeMode: false}) { | 123 String htmlSerializeEscape(String text, {bool attributeMode: false}) { |
134 // TODO(jmesserly): is it faster to build up a list of codepoints? | 124 // TODO(jmesserly): is it faster to build up a list of codepoints? |
135 // StringBuffer seems cleaner assuming Dart can unbox 1-char strings. | 125 // StringBuffer seems cleaner assuming Dart can unbox 1-char strings. |
136 StringBuffer result = null; | 126 StringBuffer result = null; |
137 for (int i = 0; i < text.length; i++) { | 127 for (int i = 0; i < text.length; i++) { |
138 var ch = text[i]; | 128 var ch = text[i]; |
139 String replace = null; | 129 String replace = null; |
140 switch (ch) { | 130 switch (ch) { |
141 case '&': replace = '&'; break; | 131 case '&': replace = '&'; break; |
142 case '\u00A0'/*NO-BREAK SPACE*/: replace = ' '; break; | 132 case '\u00A0'/*NO-BREAK SPACE*/: replace = ' '; break; |
143 case '"': if (attributeMode) replace = '"'; break; | 133 case '"': if (attributeMode) replace = '"'; break; |
144 case '<': if (!attributeMode) replace = '<'; break; | 134 case '<': if (!attributeMode) replace = '<'; break; |
145 case '>': if (!attributeMode) replace = '>'; break; | 135 case '>': if (!attributeMode) replace = '>'; break; |
146 } | 136 } |
147 if (replace != null) { | 137 if (replace != null) { |
148 if (result == null) result = new StringBuffer(text.substring(0, i)); | 138 if (result == null) result = new StringBuffer(text.substring(0, i)); |
149 result.write(replace); | 139 result.write(replace); |
150 } else if (result != null) { | 140 } else if (result != null) { |
151 result.write(ch); | 141 result.write(ch); |
152 } | 142 } |
153 } | 143 } |
154 | 144 |
155 return result != null ? result.toString() : text; | 145 return result != null ? result.toString() : text; |
156 } | 146 } |
157 | 147 |
158 | 148 |
159 /** | 149 /// Returns true if this tag name is a void element. |
160 * Returns true if this tag name is a void element. | 150 /// This method is useful to a pretty printer, because void elements must not |
161 * This method is useful to a pretty printer, because void elements must not | 151 /// have an end tag. |
162 * have an end tag. | 152 /// See also: <http://dev.w3.org/html5/markup/syntax.html#void-elements>. |
163 * See <http://dev.w3.org/html5/markup/syntax.html#void-elements> for more info. | |
164 */ | |
165 bool isVoidElement(String tagName) { | 153 bool isVoidElement(String tagName) { |
166 switch (tagName) { | 154 switch (tagName) { |
167 case "area": case "base": case "br": case "col": case "command": | 155 case "area": case "base": case "br": case "col": case "command": |
168 case "embed": case "hr": case "img": case "input": case "keygen": | 156 case "embed": case "hr": case "img": case "input": case "keygen": |
169 case "link": case "meta": case "param": case "source": case "track": | 157 case "link": case "meta": case "param": case "source": case "track": |
170 case "wbr": | 158 case "wbr": |
171 return true; | 159 return true; |
172 } | 160 } |
173 return false; | 161 return false; |
174 } | 162 } |
OLD | NEW |