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

Side by Side Diff: treebuilders/simpletree.dart

Issue 10916294: switch html5lib to new pkg layout (Closed) Base URL: https://github.com/dart-lang/html5lib.git@master
Patch Set: Created 8 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
« no previous file with comments | « treebuilders/base.dart ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /**
2 * A simple tree API that results from parsing html. Intended to be compatible
3 * with dart:html, but right now it resembles the classic JS DOM.
4 */
5 #library('simpletree');
6
7 #import('../lib/constants.dart');
8 #import('../lib/utils.dart');
9 #import('base.dart');
10
11 class Span {
12 /** The line of this span. 1-based. */
13 final int line;
14
15 /** The column of this span. */
16 final int column;
17
18 Span(this.line, this.column);
19 }
20
21 // TODO(jmesserly): I added this class to replace the tuple usage in Python.
22 // How does this fit in to dart:html?
23 class AttributeName implements Hashable, Comparable {
24 /** The namespace prefix, e.g. `xlink`. */
25 final String prefix;
26
27 /** The attribute name, e.g. `title`. */
28 final String name;
29
30 /** The namespace url, e.g. `http://www.w3.org/1999/xlink` */
31 final String namespace;
32
33 const AttributeName(this.prefix, this.name, this.namespace);
34
35 int hashCode() {
36 int h = prefix.hashCode();
37 h = 37 * (h & 0x1FFFFF) + name.hashCode();
38 h = 37 * (h & 0x1FFFFF) + namespace.hashCode();
39 return h & 0x3FFFFFFF;
40 }
41
42 int compareTo(other) {
43 // Not sure about this sort order
44 if (other is! AttributeName) return 1;
45 int cmp = (prefix != null ? prefix : "").compareTo(
46 (other.prefix != null ? other.prefix : ""));
47 if (cmp != 0) return cmp;
48 cmp = name.compareTo(other.name);
49 if (cmp != 0) return cmp;
50 return namespace.compareTo(other.namespace);
51 }
52
53 bool operator ==(x) {
54 if (x is! AttributeName) return false;
55 return prefix == x.prefix && name == x.name && namespace == x.namespace;
56 }
57 }
58
59 // TODO(jmesserly): move code away from $dom methods
60 /** Really basic implementation of a DOM-core like Node. */
61 abstract class Node implements Hashable {
62 static const int ATTRIBUTE_NODE = 2;
63 static const int CDATA_SECTION_NODE = 4;
64 static const int COMMENT_NODE = 8;
65 static const int DOCUMENT_FRAGMENT_NODE = 11;
66 static const int DOCUMENT_NODE = 9;
67 static const int DOCUMENT_TYPE_NODE = 10;
68 static const int ELEMENT_NODE = 1;
69 static const int ENTITY_NODE = 6;
70 static const int ENTITY_REFERENCE_NODE = 5;
71 static const int NOTATION_NODE = 12;
72 static const int PROCESSING_INSTRUCTION_NODE = 7;
73 static const int TEXT_NODE = 3;
74
75 static int _lastHashCode = 0;
76 final int _hashCode;
77
78 // TODO(jmesserly): this should be on Element
79 /** The tag name associated with the node. */
80 final String tagName;
81
82 /** The parent of the current node (or null for the document node). */
83 Node parent;
84
85 /** A map holding name, value pairs for attributes of the node. */
86 Map attributes;
87
88 // TODO(jmesserly): this collection needs to handle addition and removal of
89 // items and automatically fix the parent pointer, like dart:html does.
90 /**
91 * A list of child nodes of the current node. This must
92 * include all elements but not necessarily other node types.
93 */
94 final List<Node> nodes;
95
96 Node(this.tagName)
97 : attributes = {},
98 nodes = <Node>[],
99 _hashCode = ++_lastHashCode;
100
101 /**
102 * Return a shallow copy of the current node i.e. a node with the same
103 * name and attributes but with no parent or child nodes.
104 */
105 abstract Node clone();
106
107 String get id {
108 var result = attributes['id'];
109 return result != null ? result : '';
110 }
111
112 String get namespace => null;
113
114 // TODO(jmesserly): do we need this here?
115 /** The value of the current node (applies to text nodes and comments). */
116 String get value => null;
117
118 // TODO(jmesserly): this is a workaround for http://dartbug.com/4754
119 int get $dom_nodeType => nodeType;
120
121 abstract int get nodeType;
122
123 String get outerHTML => _addOuterHtml(new StringBuffer()).toString();
124
125 String get innerHTML => _addInnerHtml(new StringBuffer()).toString();
126
127 abstract StringBuffer _addOuterHtml(StringBuffer str);
128
129 StringBuffer _addInnerHtml(StringBuffer str) {
130 for (Node child in nodes) child._addOuterHtml(str);
131 return str;
132 }
133
134 String toString() => tagName;
135
136 int hashCode() => _hashCode;
137
138 /**
139 * Insert [node] as a child of the current node
140 */
141 void $dom_appendChild(Node node) {
142 if (node is Text && nodes.length > 0 &&
143 nodes.last() is Text) {
144 Text last = nodes.last();
145 last.value = '${last.value}${node.value}';
146 } else {
147 nodes.add(node);
148 }
149 node.parent = this;
150 }
151
152 /**
153 * Insert [data] as text in the current node, positioned before the
154 * start of node [refNode] or to the end of the node's text.
155 */
156 void insertText(String data, [Node refNode]) {
157 if (refNode == null) {
158 $dom_appendChild(new Text(data));
159 } else {
160 insertBefore(new Text(data), refNode);
161 }
162 }
163
164 /**
165 * Insert [node] as a child of the current node, before [refNode] in the
166 * list of child nodes. Raises [UnsupportedOperationException] if [refNode]
167 * is not a child of the current node.
168 */
169 void insertBefore(Node node, Node refNode) {
170 int index = nodes.indexOf(refNode);
171 if (node is Text && index > 0 &&
172 nodes[index - 1] is Text) {
173 Text last = nodes[index - 1];
174 last.value = '${last.value}${node.value}';
175 } else {
176 nodes.insertRange(index, 1, node);
177 }
178 node.parent = this;
179 }
180
181 /**
182 * Remove [node] from the children of the current node
183 */
184 void $dom_removeChild(Node node) {
185 removeFromList(nodes, node);
186 node.parent = null;
187 }
188
189 // TODO(jmesserly): should this be a property?
190 /** Return true if the node has children or text. */
191 bool hasContent() => nodes.length > 0;
192
193 Pair<String, String> get nameTuple {
194 var ns = namespace != null ? namespace : Namespaces.html;
195 return new Pair(ns, tagName);
196 }
197
198 /**
199 * Move all the children of the current node to [newParent].
200 * This is needed so that trees that don't store text as nodes move the
201 * text in the correct way.
202 */
203 void reparentChildren(Node newParent) {
204 //XXX - should this method be made more general?
205 for (var child in nodes) {
206 newParent.$dom_appendChild(child);
207 }
208 nodes.clear();
209 }
210
211 /**
212 * Seaches for the first descendant node matching the given selectors, using a
213 * preorder traversal. NOTE: right now, this supports only a single type
214 * selectors, e.g. `node.query('div')`.
215 */
216 Element query(String selectors) => _queryType(_typeSelector(selectors));
217
218 /**
219 * Retursn all descendant nodes matching the given selectors, using a
220 * preorder traversal. NOTE: right now, this supports only a single type
221 * selectors, e.g. `node.queryAll('div')`.
222 */
223 List<Element> queryAll(String selectors) {
224 var results = new List<Element>();
225 _queryAllType(_typeSelector(selectors), results);
226 return results;
227 }
228
229 String _typeSelector(String selectors) {
230 selectors = selectors.trim();
231 if (!selectors.splitChars().every(isLetter)) {
232 throw new NotImplementedException('only type selectors are implemented');
233 }
234 return selectors;
235 }
236
237 Element _queryType(String tag) {
238 for (var node in nodes) {
239 if (node is! Element) continue;
240 if (node.tagName == tag) return node;
241 var result = node._queryType(tag);
242 if (result != null) return result;
243 }
244 return null;
245 }
246
247 void _queryAllType(String tag, List<Element> results) {
248 for (var node in nodes) {
249 if (node is! Element) continue;
250 if (node.tagName == tag) results.add(node);
251 node._queryAllType(tag, results);
252 }
253 }
254 }
255
256 class Document extends Node {
257 Document() : super(null);
258
259 int get nodeType => Node.DOCUMENT_NODE;
260
261 Element get body {
262 for (var node in nodes) {
263 if (node.tagName != 'html') continue;
264 for (var node2 in node.nodes) {
265 if (node2.tagName != 'body') continue;
266 return node2;
267 }
268 }
269 return null;
270 }
271
272 String toString() => "#document";
273
274 StringBuffer _addOuterHtml(StringBuffer str) => _addInnerHtml(str);
275
276 Document clone() => new Document();
277 }
278
279 class DocumentFragment extends Document {
280 int get nodeType => Node.DOCUMENT_FRAGMENT_NODE;
281
282 String toString() => "#document-fragment";
283
284 DocumentFragment clone() => new DocumentFragment();
285 }
286
287 class DocumentType extends Node {
288 final String publicId;
289 final String systemId;
290
291 DocumentType(String name, this.publicId, this.systemId) : super(name);
292
293 int get nodeType => Node.DOCUMENT_TYPE_NODE;
294
295 String toString() {
296 if (publicId != null || systemId != null) {
297 var pid = publicId != null ? publicId : '';
298 var sid = systemId != null ? systemId : '';
299 return '<!DOCTYPE $tagName "$pid" "$sid">';
300 } else {
301 return '<!DOCTYPE $tagName>';
302 }
303 }
304
305
306 StringBuffer _addOuterHtml(StringBuffer str) => str.add(toString());
307
308 DocumentType clone() => new DocumentType(tagName, publicId, systemId);
309 }
310
311 class Text extends Node {
312 String value;
313
314 Text(this.value) : super(null);
315
316 int get nodeType => Node.TEXT_NODE;
317
318 String toString() => '"$value"';
319
320 StringBuffer _addOuterHtml(StringBuffer str) =>
321 str.add(htmlEscapeMinimal(value));
322
323 Text clone() => new Text(value);
324 }
325
326 class Element extends Node {
327 final String namespace;
328
329 Element(String name, [this.namespace]) : super(name);
330
331 int get nodeType => Node.ELEMENT_NODE;
332
333 String toString() {
334 if (namespace == null) return "<$tagName>";
335 return "<${Namespaces.getPrefix(namespace)} $tagName>";
336 }
337
338 StringBuffer _addOuterHtml(StringBuffer str) {
339 str.add('<$tagName');
340 if (attributes.length > 0) {
341 attributes.forEach((key, v) {
342 v = htmlEscapeMinimal(v, {'"': "&quot;"});
343 str.add(' $key="$v"');
344 });
345 }
346 str.add('>');
347 if (nodes.length > 0) {
348 _addInnerHtml(str);
349 }
350 // void elements must not have an end tag
351 // http://dev.w3.org/html5/markup/syntax.html#void-elements
352 if (voidElements.indexOf(tagName) < 0) {
353 str.add('</$tagName>');
354 }
355 return str;
356 }
357
358 Element clone() =>
359 new Element(tagName, namespace)..attributes = new Map.from(attributes);
360 }
361
362 class Comment extends Node {
363 final String data;
364
365 Comment(this.data) : super(null);
366
367 int get nodeType => Node.COMMENT_NODE;
368
369 String toString() => "<!-- $data -->";
370
371 StringBuffer _addOuterHtml(StringBuffer str) => str.add("<!--$data-->");
372
373 Comment clone() => new Comment(data);
374 }
375
376 /** A simple tree visitor for the DOM nodes. */
377 class TreeVisitor {
378 visit(Node node) {
379 switch (node.nodeType) {
380 case Node.ELEMENT_NODE: return visitElement(node);
381 case Node.TEXT_NODE: return visitText(node);
382 case Node.COMMENT_NODE: return visitComment(node);
383 case Node.DOCUMENT_FRAGMENT_NODE: return visitDocumentFragment(node);
384 case Node.DOCUMENT_NODE: return visitDocument(node);
385 case Node.DOCUMENT_TYPE_NODE: return visitDocumentType(node);
386 default: throw new UnsupportedOperationException(
387 'DOM node type ${node.nodeType}');
388 }
389 }
390
391 visitChildren(Node node) {
392 for (var child in node.nodes) visit(child);
393 }
394
395 /**
396 * The fallback handler if the more specific visit method hasn't been
397 * overriden. Only use this from a subclass of [TreeVisitor], otherwise
398 * call [visit] instead.
399 */
400 visitNodeFallback(Node node) => visitChildren(node);
401
402 visitDocument(Document node) => visitNodeFallback(node);
403
404 visitDocumentType(DocumentType node) => visitNodeFallback(node);
405
406 visitText(Text node) => visitNodeFallback(node);
407
408 // TODO(jmesserly): visit attributes.
409 visitElement(Element node) => visitNodeFallback(node);
410
411 visitComment(Comment node) => visitNodeFallback(node);
412
413 // Note: visits document by default because DocumentFragment is a Document.
414 visitDocumentFragment(DocumentFragment node) => visitDocument(node);
415 }
416
417 /**
418 * Converts the DOM tree into an HTML string with code markup suitable for
419 * displaying the HTML's source code with CSS colors for different parts of the
420 * markup. See also [CodeMarkupVisitor].
421 */
422 String htmlToCodeMarkup(Node node) {
423 return (new CodeMarkupVisitor()..visit(node)).toString();
424 }
425
426 /**
427 * Converts the DOM tree into an HTML string with code markup suitable for
428 * displaying the HTML's source code with CSS colors for different parts of the
429 * markup. See also [htmlToCodeMarkup].
430 */
431 class CodeMarkupVisitor extends TreeVisitor {
432 final StringBuffer _str;
433
434 CodeMarkupVisitor() : _str = new StringBuffer();
435
436 String toString() => _str.toString();
437
438 visitDocument(Document node) {
439 _str.add("<pre>");
440 visitChildren(node);
441 _str.add("</pre>");
442 }
443
444 visitDocumentType(DocumentType node) {
445 _str.add('<code class="markup doctype">&lt;!DOCTYPE ${node.tagName}></code>' );
446 }
447
448 visitText(Text node) {
449 node._addOuterHtml(_str);
450 }
451
452 visitElement(Element node) {
453 _str.add('&lt;<code class="markup element-name">${node.tagName}</code>');
454 if (node.attributes.length > 0) {
455 node.attributes.forEach((key, v) {
456 v = htmlEscapeMinimal(v, {'"': "&quot;"});
457 _str.add(' <code class="markup attribute-name">$key</code>'
458 '=<code class="markup attribute-value">"$v"</code>');
459 });
460 }
461 if (node.nodes.length > 0) {
462 _str.add(">");
463 visitChildren(node);
464 } else if (voidElements.indexOf(node.tagName) >= 0) {
465 _str.add(">");
466 return;
467 }
468 _str.add('&lt;/<code class="markup element-name">${node.tagName}</code>>');
469 }
470
471 visitComment(Comment node) {
472 var data = htmlEscapeMinimal(node.data);
473 _str.add('<code class="markup comment">&lt;!--${data}--></code>');
474 }
475 }
OLDNEW
« no previous file with comments | « treebuilders/base.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698