Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /** | 1 /// A simple tree API that results from parsing html. Intended to be compatible |
| 2 * A simple tree API that results from parsing html. Intended to be compatible | 2 /// with dart:html, but right now it resembles the classic JS DOM. |
| 3 * with dart:html, but right now it resembles the classic JS DOM. | |
| 4 */ | |
| 5 library dom; | 3 library dom; |
| 6 | 4 |
| 7 import 'dart:collection'; | 5 import 'dart:collection'; |
| 8 import 'package:source_maps/span.dart' show FileSpan; | 6 import 'package:source_maps/span.dart' show FileSpan; |
| 9 | 7 |
| 10 import 'src/constants.dart'; | 8 import 'src/constants.dart'; |
| 11 import 'src/list_proxy.dart'; | 9 import 'src/list_proxy.dart'; |
| 12 import 'src/token.dart'; | 10 import 'src/token.dart'; |
| 13 import 'src/tokenizer.dart'; | 11 import 'src/tokenizer.dart'; |
| 14 import 'src/utils.dart'; | 12 import 'src/utils.dart'; |
| 15 import 'dom_parsing.dart'; | 13 import 'dom_parsing.dart'; |
| 16 import 'parser.dart'; | 14 import 'parser.dart'; |
| 17 | 15 |
| 18 // TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes | 16 // TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes |
| 19 // that exposes namespace info. | 17 // that exposes namespace info. |
| 20 class AttributeName implements Comparable { | 18 class AttributeName implements Comparable { |
| 21 /** The namespace prefix, e.g. `xlink`. */ | 19 /// The namespace prefix, e.g. `xlink`. |
| 22 final String prefix; | 20 final String prefix; |
| 23 | 21 |
| 24 /** The attribute name, e.g. `title`. */ | 22 /// The attribute name, e.g. `title`. |
| 25 final String name; | 23 final String name; |
| 26 | 24 |
| 27 /** The namespace url, e.g. `http://www.w3.org/1999/xlink` */ | 25 /// The namespace url, e.g. `http://www.w3.org/1999/xlink` |
| 28 final String namespace; | 26 final String namespace; |
| 29 | 27 |
| 30 const AttributeName(this.prefix, this.name, this.namespace); | 28 const AttributeName(this.prefix, this.name, this.namespace); |
| 31 | 29 |
| 32 String toString() { | 30 String toString() { |
| 33 // Implement: | 31 // Implement: |
| 34 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html# serializing-html-fragments | 32 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html# serializing-html-fragments |
| 35 // If we get here we know we are xml, xmlns, or xlink, because of | 33 // If we get here we know we are xml, xmlns, or xlink, because of |
| 36 // [HtmlParser.adjustForeignAttriubtes] is the only place we create | 34 // [HtmlParser.adjustForeignAttriubtes] is the only place we create |
| 37 // an AttributeName. | 35 // an AttributeName. |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 55 if (cmp != 0) return cmp; | 53 if (cmp != 0) return cmp; |
| 56 return namespace.compareTo(other.namespace); | 54 return namespace.compareTo(other.namespace); |
| 57 } | 55 } |
| 58 | 56 |
| 59 bool operator ==(x) { | 57 bool operator ==(x) { |
| 60 if (x is! AttributeName) return false; | 58 if (x is! AttributeName) return false; |
| 61 return prefix == x.prefix && name == x.name && namespace == x.namespace; | 59 return prefix == x.prefix && name == x.name && namespace == x.namespace; |
| 62 } | 60 } |
| 63 } | 61 } |
| 64 | 62 |
| 65 /** Really basic implementation of a DOM-core like Node. */ | 63 /// Really basic implementation of a DOM-core like Node. |
| 66 abstract class Node { | 64 abstract class Node { |
| 67 static const int ATTRIBUTE_NODE = 2; | 65 static const int ATTRIBUTE_NODE = 2; |
| 68 static const int CDATA_SECTION_NODE = 4; | 66 static const int CDATA_SECTION_NODE = 4; |
| 69 static const int COMMENT_NODE = 8; | 67 static const int COMMENT_NODE = 8; |
| 70 static const int DOCUMENT_FRAGMENT_NODE = 11; | 68 static const int DOCUMENT_FRAGMENT_NODE = 11; |
| 71 static const int DOCUMENT_NODE = 9; | 69 static const int DOCUMENT_NODE = 9; |
| 72 static const int DOCUMENT_TYPE_NODE = 10; | 70 static const int DOCUMENT_TYPE_NODE = 10; |
| 73 static const int ELEMENT_NODE = 1; | 71 static const int ELEMENT_NODE = 1; |
| 74 static const int ENTITY_NODE = 6; | 72 static const int ENTITY_NODE = 6; |
| 75 static const int ENTITY_REFERENCE_NODE = 5; | 73 static const int ENTITY_REFERENCE_NODE = 5; |
| 76 static const int NOTATION_NODE = 12; | 74 static const int NOTATION_NODE = 12; |
| 77 static const int PROCESSING_INSTRUCTION_NODE = 7; | 75 static const int PROCESSING_INSTRUCTION_NODE = 7; |
| 78 static const int TEXT_NODE = 3; | 76 static const int TEXT_NODE = 3; |
| 79 | 77 |
| 80 // TODO(jmesserly): this should be on Element | 78 // TODO(jmesserly): this should be on Element |
| 81 /** The tag name associated with the node. */ | 79 /// The tag name associated with the node. |
| 82 final String tagName; | 80 final String tagName; |
| 83 | 81 |
| 84 /** The parent of the current node (or null for the document node). */ | 82 /// The parent of the current node (or null for the document node). |
| 85 Node parent; | 83 Node parent; |
| 86 | 84 |
| 87 // TODO(jmesserly): should move to Element. | 85 // TODO(jmesserly): should move to Element. |
| 88 /** | 86 /// A map holding name, value pairs for attributes of the node. |
| 89 * A map holding name, value pairs for attributes of the node. | 87 /// |
| 90 * | 88 /// Note that attribute order needs to be stable for serialization, so we use a |
|
Siggi Cherem (dart-lang)
2014/02/24 23:03:29
80
Jennifer Messerly
2014/02/25 00:32:08
funny. this was the line I was iterating on when I
| |
| 91 * Note that attribute order needs to be stable for serialization, so we use a | 89 /// LinkedHashMap. Each key is a [String] or [AttributeName]. |
| 92 * LinkedHashMap. Each key is a [String] or [AttributeName]. | |
| 93 */ | |
| 94 LinkedHashMap<dynamic, String> attributes = new LinkedHashMap(); | 90 LinkedHashMap<dynamic, String> attributes = new LinkedHashMap(); |
| 95 | 91 |
| 96 /** | 92 /// A list of child nodes of the current node. This must |
| 97 * A list of child nodes of the current node. This must | 93 /// include all elements but not necessarily other node types. |
| 98 * include all elements but not necessarily other node types. | |
| 99 */ | |
| 100 final NodeList nodes = new NodeList._(); | 94 final NodeList nodes = new NodeList._(); |
| 101 | 95 |
| 102 List<Element> _elements; | 96 List<Element> _elements; |
| 103 | 97 |
| 104 // TODO(jmesserly): consider using an Expando for this, and put it in | 98 // TODO(jmesserly): consider using an Expando for this, and put it in |
| 105 // dom_parsing. Need to check the performance affect. | 99 // dom_parsing. Need to check the performance affect. |
| 106 /** The source span of this node, if it was created by the [HtmlParser]. */ | 100 /// The source span of this node, if it was created by the [HtmlParser]. |
| 107 FileSpan sourceSpan; | 101 FileSpan sourceSpan; |
| 108 | 102 |
| 109 /** The attribute spans if requested. Otherwise null. */ | 103 /// The attribute spans if requested. Otherwise null. |
| 110 LinkedHashMap<dynamic, FileSpan> _attributeSpans; | 104 LinkedHashMap<dynamic, FileSpan> _attributeSpans; |
| 111 LinkedHashMap<dynamic, FileSpan> _attributeValueSpans; | 105 LinkedHashMap<dynamic, FileSpan> _attributeValueSpans; |
| 112 | 106 |
| 113 Node(this.tagName) { | 107 Node(this.tagName) { |
| 114 nodes._parent = this; | 108 nodes._parent = this; |
| 115 } | 109 } |
| 116 | 110 |
| 117 /** | 111 /// If [sourceSpan] is available, this contains the spans of each attribute. |
| 118 * If [sourceSpan] is available, this contains the spans of each attribute. | 112 /// The span of an attribute is the entire attribute, including the name and |
| 119 * The span of an attribute is the entire attribute, including the name and | 113 /// quotes (if any). For example, the span of "attr" in `<a attr="value">` |
| 120 * quotes (if any). For example, the span of "attr" in `<a attr="value">` | 114 /// would be the text `attr="value"`. |
| 121 * would be the text `attr="value"`. | |
| 122 */ | |
| 123 LinkedHashMap<dynamic, FileSpan> get attributeSpans { | 115 LinkedHashMap<dynamic, FileSpan> get attributeSpans { |
| 124 _ensureAttributeSpans(); | 116 _ensureAttributeSpans(); |
| 125 return _attributeSpans; | 117 return _attributeSpans; |
| 126 } | 118 } |
| 127 | 119 |
| 128 /** | 120 /// If [sourceSpan] is available, this contains the spans of each attribute's |
| 129 * If [sourceSpan] is available, this contains the spans of each attribute's | 121 /// value. Unlike [attributeSpans], this span will inlcude only the value. |
| 130 * value. Unlike [attributeSpans], this span will inlcude only the value. | 122 /// For example, the value span of "attr" in `<a attr="value">` would be the |
| 131 * For example, the value span of "attr" in `<a attr="value">` would be the | 123 /// text `value`. |
| 132 * text `value`. | |
| 133 */ | |
| 134 LinkedHashMap<dynamic, FileSpan> get attributeValueSpans { | 124 LinkedHashMap<dynamic, FileSpan> get attributeValueSpans { |
| 135 _ensureAttributeSpans(); | 125 _ensureAttributeSpans(); |
| 136 return _attributeValueSpans; | 126 return _attributeValueSpans; |
| 137 } | 127 } |
| 138 | 128 |
| 139 List<Element> get children { | 129 List<Element> get children { |
| 140 if (_elements == null) { | 130 if (_elements == null) { |
| 141 _elements = new FilteredElementList(this); | 131 _elements = new FilteredElementList(this); |
| 142 } | 132 } |
| 143 return _elements; | 133 return _elements; |
| 144 } | 134 } |
| 145 | 135 |
| 146 // TODO(jmesserly): needs to support deep clone. | 136 // TODO(jmesserly): needs to support deep clone. |
| 147 /** | 137 /// Return a shallow copy of the current node i.e. a node with the same |
| 148 * Return a shallow copy of the current node i.e. a node with the same | 138 /// name and attributes but with no parent or child nodes. |
| 149 * name and attributes but with no parent or child nodes. | |
| 150 */ | |
| 151 Node clone(); | 139 Node clone(); |
| 152 | 140 |
| 153 String get namespace => null; | 141 String get namespace => null; |
| 154 | 142 |
| 155 int get nodeType; | 143 int get nodeType; |
| 156 | 144 |
| 157 /** *Deprecated* use [text], [Text.data] or [Comment.data]. */ | 145 /// *Deprecated* use [text], [Text.data] or [Comment.data]. |
| 158 @deprecated String get value => null; | 146 @deprecated String get value => null; |
| 159 | 147 |
| 160 /** *Deprecated* use [nodeType]. */ | 148 /// *Deprecated* use [nodeType]. |
| 161 @deprecated int get $dom_nodeType => nodeType; | 149 @deprecated int get $dom_nodeType => nodeType; |
| 162 | 150 |
| 163 String get outerHtml { | 151 String get outerHtml { |
| 164 var str = new StringBuffer(); | 152 var str = new StringBuffer(); |
| 165 _addOuterHtml(str); | 153 _addOuterHtml(str); |
| 166 return str.toString(); | 154 return str.toString(); |
| 167 } | 155 } |
| 168 | 156 |
| 169 String get innerHtml { | 157 String get innerHtml { |
| 170 var str = new StringBuffer(); | 158 var str = new StringBuffer(); |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 196 String toString() => tagName; | 184 String toString() => tagName; |
| 197 | 185 |
| 198 Node remove() { | 186 Node remove() { |
| 199 // TODO(jmesserly): is parent == null an error? | 187 // TODO(jmesserly): is parent == null an error? |
| 200 if (parent != null) { | 188 if (parent != null) { |
| 201 parent.nodes.remove(this); | 189 parent.nodes.remove(this); |
| 202 } | 190 } |
| 203 return this; | 191 return this; |
| 204 } | 192 } |
| 205 | 193 |
| 206 /** | 194 /// Insert [node] as a child of the current node, before [refNode] in the |
| 207 * Insert [node] as a child of the current node, before [refNode] in the | 195 /// list of child nodes. Raises [UnsupportedOperationException] if [refNode] |
| 208 * list of child nodes. Raises [UnsupportedOperationException] if [refNode] | 196 /// is not a child of the current node. If refNode is null, this adds to the |
| 209 * is not a child of the current node. If refNode is null, this adds to the | 197 /// end of the list. |
| 210 * end of the list. | |
| 211 */ | |
| 212 void insertBefore(Node node, Node refNode) { | 198 void insertBefore(Node node, Node refNode) { |
| 213 if (refNode == null) { | 199 if (refNode == null) { |
| 214 nodes.add(node); | 200 nodes.add(node); |
| 215 } else { | 201 } else { |
| 216 nodes.insert(nodes.indexOf(refNode), node); | 202 nodes.insert(nodes.indexOf(refNode), node); |
| 217 } | 203 } |
| 218 } | 204 } |
| 219 | 205 |
| 220 /** Replaces this node with another node. */ | 206 /// Replaces this node with another node. |
| 221 Node replaceWith(Node otherNode) { | 207 Node replaceWith(Node otherNode) { |
| 222 if (parent == null) { | 208 if (parent == null) { |
| 223 throw new UnsupportedError('Node must have a parent to replace it.'); | 209 throw new UnsupportedError('Node must have a parent to replace it.'); |
| 224 } | 210 } |
| 225 parent.nodes[parent.nodes.indexOf(this)] = otherNode; | 211 parent.nodes[parent.nodes.indexOf(this)] = otherNode; |
| 226 return this; | 212 return this; |
| 227 } | 213 } |
| 228 | 214 |
| 229 // TODO(jmesserly): should this be a property or remove? | 215 // TODO(jmesserly): should this be a property or remove? |
| 230 /** Return true if the node has children or text. */ | 216 /// Return true if the node has children or text. |
| 231 bool hasContent() => nodes.length > 0; | 217 bool hasContent() => nodes.length > 0; |
| 232 | 218 |
| 233 Pair<String, String> get nameTuple { | 219 Pair<String, String> get nameTuple { |
| 234 var ns = namespace != null ? namespace : Namespaces.html; | 220 var ns = namespace != null ? namespace : Namespaces.html; |
| 235 return new Pair(ns, tagName); | 221 return new Pair(ns, tagName); |
| 236 } | 222 } |
| 237 | 223 |
| 238 /** | 224 /// Move all the children of the current node to [newParent]. |
| 239 * Move all the children of the current node to [newParent]. | 225 /// This is needed so that trees that don't store text as nodes move the |
| 240 * This is needed so that trees that don't store text as nodes move the | 226 /// text in the correct way. |
| 241 * text in the correct way. | |
| 242 */ | |
| 243 void reparentChildren(Node newParent) { | 227 void reparentChildren(Node newParent) { |
| 244 newParent.nodes.addAll(nodes); | 228 newParent.nodes.addAll(nodes); |
| 245 nodes.clear(); | 229 nodes.clear(); |
| 246 } | 230 } |
| 247 | 231 |
| 248 /** *Deprecated* use [querySelector] instead. */ | 232 /// *Deprecated* use [querySelector] instead. |
| 249 @deprecated | 233 @deprecated |
| 250 Element query(String selectors) => querySelector(selectors); | 234 Element query(String selectors) => querySelector(selectors); |
| 251 | 235 |
| 252 /** *Deprecated* use [querySelectorAll] instead. */ | 236 /// *Deprecated* use [querySelectorAll] instead. |
| 253 @deprecated | 237 @deprecated |
| 254 List<Element> queryAll(String selectors) => querySelectorAll(selectors); | 238 List<Element> queryAll(String selectors) => querySelectorAll(selectors); |
| 255 | 239 |
| 256 /** | 240 /// Seaches for the first descendant node matching the given selectors, using a |
| 257 * Seaches for the first descendant node matching the given selectors, using a | 241 /// preorder traversal. NOTE: right now, this supports only a single type |
| 258 * preorder traversal. NOTE: right now, this supports only a single type | 242 /// selectors, e.g. `node.query('div')`. |
| 259 * selectors, e.g. `node.query('div')`. | |
| 260 */ | |
| 261 | 243 |
| 262 Element querySelector(String selectors) => | 244 Element querySelector(String selectors) => |
| 263 _queryType(_typeSelector(selectors)); | 245 _queryType(_typeSelector(selectors)); |
| 264 | 246 |
| 265 /** | 247 /// Returns all descendant nodes matching the given selectors, using a |
| 266 * Returns all descendant nodes matching the given selectors, using a | 248 /// preorder traversal. NOTE: right now, this supports only a single type |
| 267 * preorder traversal. NOTE: right now, this supports only a single type | 249 /// selectors, e.g. `node.queryAll('div')`. |
| 268 * selectors, e.g. `node.queryAll('div')`. | |
| 269 */ | |
| 270 List<Element> querySelectorAll(String selectors) { | 250 List<Element> querySelectorAll(String selectors) { |
| 271 var results = new List<Element>(); | 251 var results = new List<Element>(); |
| 272 _queryAllType(_typeSelector(selectors), results); | 252 _queryAllType(_typeSelector(selectors), results); |
| 273 return results; | 253 return results; |
| 274 } | 254 } |
| 275 | 255 |
| 276 bool hasChildNodes() => !nodes.isEmpty; | 256 bool hasChildNodes() => !nodes.isEmpty; |
| 277 | 257 |
| 278 bool contains(Node node) => nodes.contains(node); | 258 bool contains(Node node) => nodes.contains(node); |
| 279 | 259 |
| 280 String _typeSelector(String selectors) { | 260 String _typeSelector(String selectors) { |
| 281 selectors = selectors.trim(); | 261 selectors = selectors.trim(); |
| 282 if (!_isTypeSelector(selectors)) { | 262 if (!_isTypeSelector(selectors)) { |
| 283 throw new UnimplementedError('only type selectors are implemented'); | 263 throw new UnimplementedError('only type selectors are implemented'); |
| 284 } | 264 } |
| 285 return selectors; | 265 return selectors; |
| 286 } | 266 } |
| 287 | 267 |
| 288 /** | 268 /// Checks if this is a type selector. |
| 289 * Checks if this is a type selector. | 269 /// See <http://www.w3.org/TR/CSS2/grammar.html>. |
| 290 * See <http://www.w3.org/TR/CSS2/grammar.html>. | 270 /// Note: this doesn't support '*', the universal selector, non-ascii chars or |
| 291 * Note: this doesn't support '*', the universal selector, non-ascii chars or | 271 /// escape chars. |
| 292 * escape chars. | |
| 293 */ | |
| 294 bool _isTypeSelector(String selector) { | 272 bool _isTypeSelector(String selector) { |
| 295 // Parser: | 273 // Parser: |
| 296 | 274 |
| 297 // element_name | 275 // element_name |
| 298 // : IDENT | '*' | 276 // : IDENT | '*' |
| 299 // ; | 277 // ; |
| 300 | 278 |
| 301 // Lexer: | 279 // Lexer: |
| 302 | 280 |
| 303 // nmstart [_a-z]|{nonascii}|{escape} | 281 // nmstart [_a-z]|{nonascii}|{escape} |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 338 } | 316 } |
| 339 | 317 |
| 340 void _queryAllType(String tag, List<Element> results) { | 318 void _queryAllType(String tag, List<Element> results) { |
| 341 for (var node in nodes) { | 319 for (var node in nodes) { |
| 342 if (node is! Element) continue; | 320 if (node is! Element) continue; |
| 343 if (node.tagName == tag) results.add(node); | 321 if (node.tagName == tag) results.add(node); |
| 344 node._queryAllType(tag, results); | 322 node._queryAllType(tag, results); |
| 345 } | 323 } |
| 346 } | 324 } |
| 347 | 325 |
| 348 /** Initialize [attributeSpans] using [sourceSpan]. */ | 326 /// Initialize [attributeSpans] using [sourceSpan]. |
| 349 void _ensureAttributeSpans() { | 327 void _ensureAttributeSpans() { |
| 350 if (_attributeSpans != null) return; | 328 if (_attributeSpans != null) return; |
| 351 | 329 |
| 352 _attributeSpans = new LinkedHashMap<dynamic, FileSpan>(); | 330 _attributeSpans = new LinkedHashMap<dynamic, FileSpan>(); |
| 353 _attributeValueSpans = new LinkedHashMap<dynamic, FileSpan>(); | 331 _attributeValueSpans = new LinkedHashMap<dynamic, FileSpan>(); |
| 354 | 332 |
| 355 if (sourceSpan == null) return; | 333 if (sourceSpan == null) return; |
| 356 | 334 |
| 357 var tokenizer = new HtmlTokenizer(sourceSpan.text, generateSpans: true, | 335 var tokenizer = new HtmlTokenizer(sourceSpan.text, generateSpans: true, |
| 358 attributeSpans: true); | 336 attributeSpans: true); |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 432 } | 410 } |
| 433 | 411 |
| 434 DocumentType clone() => new DocumentType(tagName, publicId, systemId); | 412 DocumentType clone() => new DocumentType(tagName, publicId, systemId); |
| 435 } | 413 } |
| 436 | 414 |
| 437 class Text extends Node { | 415 class Text extends Node { |
| 438 String data; | 416 String data; |
| 439 | 417 |
| 440 Text(this.data) : super(null); | 418 Text(this.data) : super(null); |
| 441 | 419 |
| 442 /** *Deprecated* use [data]. */ | 420 /// *Deprecated* use [data]. |
| 443 @deprecated String get value => data; | 421 @deprecated String get value => data; |
| 444 @deprecated set value(String x) { data = x; } | 422 @deprecated set value(String x) { data = x; } |
| 445 | 423 |
| 446 int get nodeType => Node.TEXT_NODE; | 424 int get nodeType => Node.TEXT_NODE; |
| 447 | 425 |
| 448 String toString() => '"$data"'; | 426 String toString() => '"$data"'; |
| 449 | 427 |
| 450 void _addOuterHtml(StringBuffer str) { | 428 void _addOuterHtml(StringBuffer str) { |
| 451 // Don't escape text for certain elements, notably <script>. | 429 // Don't escape text for certain elements, notably <script>. |
| 452 if (rcdataElements.contains(parent.tagName) || | 430 if (rcdataElements.contains(parent.tagName) || |
| (...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 713 super.retainWhere(test); | 691 super.retainWhere(test); |
| 714 } | 692 } |
| 715 | 693 |
| 716 void insertAll(int index, List<Node> nodes) { | 694 void insertAll(int index, List<Node> nodes) { |
| 717 for (var node in nodes) _setParent(node); | 695 for (var node in nodes) _setParent(node); |
| 718 super.insertAll(index, nodes); | 696 super.insertAll(index, nodes); |
| 719 } | 697 } |
| 720 } | 698 } |
| 721 | 699 |
| 722 | 700 |
| 723 /** | 701 /// An indexable collection of a node's descendants in the document tree, |
| 724 * An indexable collection of a node's descendants in the document tree, | 702 /// filtered so that only elements are in the collection. |
| 725 * filtered so that only elements are in the collection. | |
| 726 */ | |
| 727 // TODO(jmesserly): this was copied from dart:html | 703 // TODO(jmesserly): this was copied from dart:html |
| 728 // TODO(jmesserly): "implements List<Element>" is a workaround for analyzer bug. | 704 // TODO(jmesserly): "implements List<Element>" is a workaround for analyzer bug. |
| 729 class FilteredElementList extends IterableBase<Element> with ListMixin<Element> | 705 class FilteredElementList extends IterableBase<Element> with ListMixin<Element> |
| 730 implements List<Element> { | 706 implements List<Element> { |
| 731 | 707 |
| 732 final Node _node; | 708 final Node _node; |
| 733 final List<Node> _childNodes; | 709 final List<Node> _childNodes; |
| 734 | 710 |
| 735 /** | 711 /// Creates a collection of the elements that descend from a node. |
| 736 * Creates a collection of the elements that descend from a node. | 712 /// |
| 737 * | 713 /// Example usage: |
| 738 * Example usage: | 714 /// |
| 739 * | 715 /// var filteredElements = new FilteredElementList(query("#container")); |
| 740 * var filteredElements = new FilteredElementList(query("#container")); | 716 /// // filteredElements is [a, b, c]. |
| 741 * // filteredElements is [a, b, c]. | |
| 742 */ | |
| 743 FilteredElementList(Node node): _childNodes = node.nodes, _node = node; | 717 FilteredElementList(Node node): _childNodes = node.nodes, _node = node; |
| 744 | 718 |
| 745 // We can't memoize this, since it's possible that children will be messed | 719 // We can't memoize this, since it's possible that children will be messed |
| 746 // with externally to this class. | 720 // with externally to this class. |
| 747 // | 721 // |
| 748 // TODO(nweiz): we don't always need to create a new list. For example | 722 // TODO(nweiz): we don't always need to create a new list. For example |
| 749 // forEach, every, any, ... could directly work on the _childNodes. | 723 // forEach, every, any, ... could directly work on the _childNodes. |
| 750 List<Element> get _filtered => | 724 List<Element> get _filtered => |
| 751 new List<Element>.from(_childNodes.where((n) => n is Element)); | 725 new List<Element>.from(_childNodes.where((n) => n is Element)); |
| 752 | 726 |
| (...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 917 | 891 |
| 918 class _ConcatTextVisitor extends TreeVisitor { | 892 class _ConcatTextVisitor extends TreeVisitor { |
| 919 final _str = new StringBuffer(); | 893 final _str = new StringBuffer(); |
| 920 | 894 |
| 921 String toString() => _str.toString(); | 895 String toString() => _str.toString(); |
| 922 | 896 |
| 923 visitText(Text node) { | 897 visitText(Text node) { |
| 924 _str.write(node.data); | 898 _str.write(node.data); |
| 925 } | 899 } |
| 926 } | 900 } |
| OLD | NEW |