| 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 |