OLD | NEW |
1 /// A simple tree API that results from parsing html. Intended to be compatible | 1 /// A simple tree API that results from parsing html. Intended to be compatible |
2 /// with dart:html, but it is missing many types and APIs. | 2 /// with dart:html, but it is missing many types and APIs. |
3 library dom; | 3 library dom; |
4 | 4 |
5 // TODO(jmesserly): lots to do here. Originally I wanted to generate this using | 5 // TODO(jmesserly): lots to do here. Originally I wanted to generate this using |
6 // our Blink IDL generator, but another idea is to directly use the excellent | 6 // our Blink IDL generator, but another idea is to directly use the excellent |
7 // http://dom.spec.whatwg.org/ and http://html.spec.whatwg.org/ and just | 7 // http://dom.spec.whatwg.org/ and http://html.spec.whatwg.org/ and just |
8 // implement that. | 8 // implement that. |
9 | 9 |
10 import 'dart:collection'; | 10 import 'dart:collection'; |
| 11 |
11 import 'package:source_span/source_span.dart'; | 12 import 'package:source_span/source_span.dart'; |
12 | 13 |
| 14 import 'dom_parsing.dart'; |
| 15 import 'parser.dart'; |
13 import 'src/constants.dart'; | 16 import 'src/constants.dart'; |
14 import 'src/css_class_set.dart'; | 17 import 'src/css_class_set.dart'; |
15 import 'src/list_proxy.dart'; | 18 import 'src/list_proxy.dart'; |
16 import 'src/query_selector.dart' as query; | 19 import 'src/query_selector.dart' as query; |
17 import 'src/token.dart'; | 20 import 'src/token.dart'; |
18 import 'src/tokenizer.dart'; | 21 import 'src/tokenizer.dart'; |
19 import 'dom_parsing.dart'; | |
20 import 'parser.dart'; | |
21 | 22 |
22 export 'src/css_class_set.dart' show CssClassSet; | 23 export 'src/css_class_set.dart' show CssClassSet; |
23 | 24 |
24 // TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes | 25 // TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes |
25 // that exposes namespace info. | 26 // that exposes namespace info. |
26 class AttributeName implements Comparable { | 27 class AttributeName implements Comparable { |
27 /// The namespace prefix, e.g. `xlink`. | 28 /// The namespace prefix, e.g. `xlink`. |
28 final String prefix; | 29 final String prefix; |
29 | 30 |
30 /// The attribute name, e.g. `title`. | 31 /// The attribute name, e.g. `title`. |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
103 // This doesn't exist as an interface in the spec, but it's useful to merge | 104 // This doesn't exist as an interface in the spec, but it's useful to merge |
104 // common methods from these: | 105 // common methods from these: |
105 // http://dom.spec.whatwg.org/#interface-document | 106 // http://dom.spec.whatwg.org/#interface-document |
106 // http://dom.spec.whatwg.org/#element | 107 // http://dom.spec.whatwg.org/#element |
107 abstract class _ElementAndDocument implements _ParentNode { | 108 abstract class _ElementAndDocument implements _ParentNode { |
108 // TODO(jmesserly): could be faster, should throw on invalid tag/class names. | 109 // TODO(jmesserly): could be faster, should throw on invalid tag/class names. |
109 | 110 |
110 List<Element> getElementsByTagName(String localName) => | 111 List<Element> getElementsByTagName(String localName) => |
111 querySelectorAll(localName); | 112 querySelectorAll(localName); |
112 | 113 |
113 List<Element> getElementsByClassName(String classNames) => querySelectorAll( | 114 List<Element> getElementsByClassName(String classNames) => |
114 classNames.splitMapJoin(' ', | 115 querySelectorAll(classNames.splitMapJoin(' ', |
115 onNonMatch: (m) => m.isNotEmpty ? '.$m' : m, onMatch: (m) => '')); | 116 onNonMatch: (m) => m.isNotEmpty ? '.$m' : m, onMatch: (m) => '')); |
116 } | 117 } |
117 | 118 |
118 /// Really basic implementation of a DOM-core like Node. | 119 /// Really basic implementation of a DOM-core like Node. |
119 abstract class Node { | 120 abstract class Node { |
120 static const int ATTRIBUTE_NODE = 2; | 121 static const int ATTRIBUTE_NODE = 2; |
121 static const int CDATA_SECTION_NODE = 4; | 122 static const int CDATA_SECTION_NODE = 4; |
122 static const int COMMENT_NODE = 8; | 123 static const int COMMENT_NODE = 8; |
123 static const int DOCUMENT_FRAGMENT_NODE = 11; | 124 static const int DOCUMENT_FRAGMENT_NODE = 11; |
124 static const int DOCUMENT_NODE = 9; | 125 static const int DOCUMENT_NODE = 9; |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
285 tokenizer.moveNext(); | 286 tokenizer.moveNext(); |
286 var token = tokenizer.current as StartTagToken; | 287 var token = tokenizer.current as StartTagToken; |
287 | 288 |
288 if (token.attributeSpans == null) return; // no attributes | 289 if (token.attributeSpans == null) return; // no attributes |
289 | 290 |
290 for (var attr in token.attributeSpans) { | 291 for (var attr in token.attributeSpans) { |
291 var offset = sourceSpan.start.offset; | 292 var offset = sourceSpan.start.offset; |
292 _attributeSpans[attr.name] = | 293 _attributeSpans[attr.name] = |
293 sourceSpan.file.span(offset + attr.start, offset + attr.end); | 294 sourceSpan.file.span(offset + attr.start, offset + attr.end); |
294 if (attr.startValue != null) { | 295 if (attr.startValue != null) { |
295 _attributeValueSpans[attr.name] = sourceSpan.file.span( | 296 _attributeValueSpans[attr.name] = sourceSpan.file |
296 offset + attr.startValue, offset + attr.endValue); | 297 .span(offset + attr.startValue, offset + attr.endValue); |
297 } | 298 } |
298 } | 299 } |
299 } | 300 } |
300 | 301 |
301 _clone(Node shallowClone, bool deep) { | 302 _clone(Node shallowClone, bool deep) { |
302 if (deep) { | 303 if (deep) { |
303 for (var child in nodes) { | 304 for (var child in nodes) { |
304 shallowClone.append(child.clone(true)); | 305 shallowClone.append(child.clone(true)); |
305 } | 306 } |
306 } | 307 } |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
400 | 401 |
401 DocumentType clone(bool deep) => new DocumentType(name, publicId, systemId); | 402 DocumentType clone(bool deep) => new DocumentType(name, publicId, systemId); |
402 } | 403 } |
403 | 404 |
404 class Text extends Node { | 405 class Text extends Node { |
405 /// The text node's data, stored as either a String or StringBuffer. | 406 /// The text node's data, stored as either a String or StringBuffer. |
406 /// We support storing a StringBuffer here to support fast [appendData]. | 407 /// We support storing a StringBuffer here to support fast [appendData]. |
407 /// It will flatten back to a String on read. | 408 /// It will flatten back to a String on read. |
408 var _data; | 409 var _data; |
409 | 410 |
410 Text(String data) : _data = data != null ? data : '', super._(); | 411 Text(String data) |
| 412 : _data = data != null ? data : '', |
| 413 super._(); |
411 | 414 |
412 int get nodeType => Node.TEXT_NODE; | 415 int get nodeType => Node.TEXT_NODE; |
413 | 416 |
414 String get data => _data = _data.toString(); | 417 String get data => _data = _data.toString(); |
415 set data(String value) { | 418 set data(String value) { |
416 _data = value != null ? value : ''; | 419 _data = value != null ? value : ''; |
417 } | 420 } |
418 | 421 |
419 String toString() => '"$data"'; | 422 String toString() => '"$data"'; |
420 | 423 |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
469 'thead': 'table', | 472 'thead': 'table', |
470 'track': 'audio', | 473 'track': 'audio', |
471 }; | 474 }; |
472 | 475 |
473 // TODO(jmesserly): this is from dart:html _ElementFactoryProvider... | 476 // TODO(jmesserly): this is from dart:html _ElementFactoryProvider... |
474 // TODO(jmesserly): have a look at fixing some things in dart:html, in | 477 // TODO(jmesserly): have a look at fixing some things in dart:html, in |
475 // particular: is the parent tag map complete? Is it faster without regexp? | 478 // particular: is the parent tag map complete? Is it faster without regexp? |
476 // TODO(jmesserly): for our version we can do something smarter in the parser. | 479 // TODO(jmesserly): for our version we can do something smarter in the parser. |
477 // All we really need is to set the correct parse state. | 480 // All we really need is to set the correct parse state. |
478 factory Element.html(String html) { | 481 factory Element.html(String html) { |
479 | |
480 // TODO(jacobr): this method can be made more robust and performant. | 482 // TODO(jacobr): this method can be made more robust and performant. |
481 // 1) Cache the dummy parent elements required to use innerHTML rather than | 483 // 1) Cache the dummy parent elements required to use innerHTML rather than |
482 // creating them every call. | 484 // creating them every call. |
483 // 2) Verify that the html does not contain leading or trailing text nodes. | 485 // 2) Verify that the html does not contain leading or trailing text nodes. |
484 // 3) Verify that the html does not contain both <head> and <body> tags. | 486 // 3) Verify that the html does not contain both <head> and <body> tags. |
485 // 4) Detatch the created element from its dummy parent. | 487 // 4) Detatch the created element from its dummy parent. |
486 String parentTag = 'div'; | 488 String parentTag = 'div'; |
487 String tag; | 489 String tag; |
488 final match = _START_TAG_REGEXP.firstMatch(html); | 490 final match = _START_TAG_REGEXP.firstMatch(html); |
489 if (match != null) { | 491 if (match != null) { |
(...skipping 310 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
800 } | 802 } |
801 } | 803 } |
802 return result; | 804 return result; |
803 } | 805 } |
804 } | 806 } |
805 | 807 |
806 /// An indexable collection of a node's descendants in the document tree, | 808 /// An indexable collection of a node's descendants in the document tree, |
807 /// filtered so that only elements are in the collection. | 809 /// filtered so that only elements are in the collection. |
808 // TODO(jmesserly): this was copied from dart:html | 810 // TODO(jmesserly): this was copied from dart:html |
809 // TODO(jmesserly): "implements List<Element>" is a workaround for analyzer bug. | 811 // TODO(jmesserly): "implements List<Element>" is a workaround for analyzer bug. |
810 class FilteredElementList extends IterableBase<Element> with ListMixin<Element> | 812 class FilteredElementList extends IterableBase<Element> |
| 813 with ListMixin<Element> |
811 implements List<Element> { | 814 implements List<Element> { |
812 final List<Node> _childNodes; | 815 final List<Node> _childNodes; |
813 | 816 |
814 /// Creates a collection of the elements that descend from a node. | 817 /// Creates a collection of the elements that descend from a node. |
815 /// | 818 /// |
816 /// Example usage: | 819 /// Example usage: |
817 /// | 820 /// |
818 /// var filteredElements = new FilteredElementList(query("#container")); | 821 /// var filteredElements = new FilteredElementList(query("#container")); |
819 /// // filteredElements is [a, b, c]. | 822 /// // filteredElements is [a, b, c]. |
820 FilteredElementList(Node node) | 823 FilteredElementList(Node node) : _childNodes = node.nodes; |
821 : _childNodes = node.nodes; | |
822 | |
823 | 824 |
824 // We can't memoize this, since it's possible that children will be messed | 825 // We can't memoize this, since it's possible that children will be messed |
825 // with externally to this class. | 826 // with externally to this class. |
826 // | 827 // |
827 // TODO(nweiz): we don't always need to create a new list. For example | 828 // TODO(nweiz): we don't always need to create a new list. For example |
828 // forEach, every, any, ... could directly work on the _childNodes. | 829 // forEach, every, any, ... could directly work on the _childNodes. |
829 List<Element> get _filtered => | 830 List<Element> get _filtered => |
830 new List<Element>.from(_childNodes.where((n) => n is Element)); | 831 new List<Element>.from(_childNodes.where((n) => n is Element)); |
831 | 832 |
832 void forEach(void f(Element element)) { | 833 void forEach(void f(Element element)) { |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
894 } | 895 } |
895 | 896 |
896 Element removeLast() { | 897 Element removeLast() { |
897 final result = this.last; | 898 final result = this.last; |
898 if (result != null) { | 899 if (result != null) { |
899 result.remove(); | 900 result.remove(); |
900 } | 901 } |
901 return result; | 902 return result; |
902 } | 903 } |
903 | 904 |
904 Iterable/*<T>*/ map/*<T>*/(/*=T*/ f(Element element)) => _filtered.map(f); | 905 Iterable<T> map<T>(T f(Element element)) => _filtered.map(f); |
905 Iterable<Element> where(bool f(Element element)) => _filtered.where(f); | 906 Iterable<Element> where(bool f(Element element)) => _filtered.where(f); |
906 Iterable/*<T>*/ expand/*<T>*/(Iterable/*<T>*/ f(Element element)) => | 907 Iterable<T> expand<T>(Iterable<T> f(Element element)) => _filtered.expand(f); |
907 _filtered.expand(f); | |
908 | 908 |
909 void insert(int index, Element value) { | 909 void insert(int index, Element value) { |
910 _childNodes.insert(index, value); | 910 _childNodes.insert(index, value); |
911 } | 911 } |
912 | 912 |
913 void insertAll(int index, Iterable<Element> iterable) { | 913 void insertAll(int index, Iterable<Element> iterable) { |
914 _childNodes.insertAll(index, iterable); | 914 _childNodes.insertAll(index, iterable); |
915 } | 915 } |
916 | 916 |
917 Element removeAt(int index) { | 917 Element removeAt(int index) { |
(...skipping 11 matching lines...) Expand all Loading... |
929 return true; | 929 return true; |
930 } | 930 } |
931 } | 931 } |
932 return false; | 932 return false; |
933 } | 933 } |
934 | 934 |
935 Element reduce(Element combine(Element value, Element element)) { | 935 Element reduce(Element combine(Element value, Element element)) { |
936 return _filtered.reduce(combine); | 936 return _filtered.reduce(combine); |
937 } | 937 } |
938 | 938 |
939 dynamic/*=T*/ fold/*<T>*/(var/*=T*/ initialValue, | 939 T fold<T>(T initialValue, T combine(T previousValue, Element element)) { |
940 dynamic/*=T*/ combine(var/*=T*/ previousValue, Element element)) { | |
941 return _filtered.fold(initialValue, combine); | 940 return _filtered.fold(initialValue, combine); |
942 } | 941 } |
943 | 942 |
944 bool every(bool f(Element element)) => _filtered.every(f); | 943 bool every(bool f(Element element)) => _filtered.every(f); |
945 bool any(bool f(Element element)) => _filtered.any(f); | 944 bool any(bool f(Element element)) => _filtered.any(f); |
946 List<Element> toList({bool growable: true}) => | 945 List<Element> toList({bool growable: true}) => |
947 new List<Element>.from(this, growable: growable); | 946 new List<Element>.from(this, growable: growable); |
948 Set<Element> toSet() => new Set<Element>.from(this); | 947 Set<Element> toSet() => new Set<Element>.from(this); |
949 Element firstWhere(bool test(Element value), {Element orElse()}) { | 948 Element firstWhere(bool test(Element value), {Element orElse()}) { |
950 return _filtered.firstWhere(test, orElse: orElse); | 949 return _filtered.firstWhere(test, orElse: orElse); |
(...skipping 13 matching lines...) Expand all Loading... |
964 | 963 |
965 bool get isEmpty => _filtered.isEmpty; | 964 bool get isEmpty => _filtered.isEmpty; |
966 int get length => _filtered.length; | 965 int get length => _filtered.length; |
967 Element operator [](int index) => _filtered[index]; | 966 Element operator [](int index) => _filtered[index]; |
968 Iterator<Element> get iterator => _filtered.iterator; | 967 Iterator<Element> get iterator => _filtered.iterator; |
969 List<Element> sublist(int start, [int end]) => _filtered.sublist(start, end); | 968 List<Element> sublist(int start, [int end]) => _filtered.sublist(start, end); |
970 Iterable<Element> getRange(int start, int end) => | 969 Iterable<Element> getRange(int start, int end) => |
971 _filtered.getRange(start, end); | 970 _filtered.getRange(start, end); |
972 // TODO(sigmund): this should be typed Element, but we currently run into a | 971 // TODO(sigmund): this should be typed Element, but we currently run into a |
973 // bug where ListMixin<E>.indexOf() expects Object as the argument. | 972 // bug where ListMixin<E>.indexOf() expects Object as the argument. |
974 int indexOf(element, [int start = 0]) => | 973 int indexOf(element, [int start = 0]) => _filtered.indexOf(element, start); |
975 _filtered.indexOf(element, start); | |
976 | 974 |
977 // TODO(sigmund): this should be typed Element, but we currently run into a | 975 // TODO(sigmund): this should be typed Element, but we currently run into a |
978 // bug where ListMixin<E>.lastIndexOf() expects Object as the argument. | 976 // bug where ListMixin<E>.lastIndexOf() expects Object as the argument. |
979 int lastIndexOf(element, [int start = null]) { | 977 int lastIndexOf(element, [int start = null]) { |
980 if (start == null) start = length - 1; | 978 if (start == null) start = length - 1; |
981 return _filtered.lastIndexOf(element, start); | 979 return _filtered.lastIndexOf(element, start); |
982 } | 980 } |
983 | 981 |
984 Element get first => _filtered.first; | 982 Element get first => _filtered.first; |
985 | 983 |
(...skipping 14 matching lines...) Expand all Loading... |
1000 | 998 |
1001 class _ConcatTextVisitor extends TreeVisitor { | 999 class _ConcatTextVisitor extends TreeVisitor { |
1002 final _str = new StringBuffer(); | 1000 final _str = new StringBuffer(); |
1003 | 1001 |
1004 String toString() => _str.toString(); | 1002 String toString() => _str.toString(); |
1005 | 1003 |
1006 visitText(Text node) { | 1004 visitText(Text node) { |
1007 _str.write(node.data); | 1005 _str.write(node.data); |
1008 } | 1006 } |
1009 } | 1007 } |
OLD | NEW |