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

Unified Diff: pkg/third_party/html5lib/lib/dom.dart

Issue 268623002: [html5lib] implement querySelector/querySelectorAll (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 7 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/pkg.status ('k') | pkg/third_party/html5lib/lib/dom_parsing.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/third_party/html5lib/lib/dom.dart
diff --git a/pkg/third_party/html5lib/lib/dom.dart b/pkg/third_party/html5lib/lib/dom.dart
index 973e5ae1bc89c003e39e087275280c39fd0e72f7..a87bca96e9eda2f85465dffb42a220c4a9f18e2e 100644
--- a/pkg/third_party/html5lib/lib/dom.dart
+++ b/pkg/third_party/html5lib/lib/dom.dart
@@ -1,18 +1,26 @@
/// A simple tree API that results from parsing html. Intended to be compatible
-/// with dart:html, but right now it resembles the classic JS DOM.
+/// with dart:html, but it is missing many types and APIs.
library dom;
+// TODO(jmesserly): lots to do here. Originally I wanted to generate this using
+// our Blink IDL generator, but another idea is to directly use the excellent
+// http://dom.spec.whatwg.org/ and http://html.spec.whatwg.org/ and just
+// implement that.
+
import 'dart:collection';
import 'package:source_maps/span.dart' show FileSpan;
import 'src/constants.dart';
+import 'src/css_class_set.dart';
import 'src/list_proxy.dart';
+import 'src/query_selector.dart' as query;
import 'src/token.dart';
import 'src/tokenizer.dart';
-import 'src/utils.dart';
import 'dom_parsing.dart';
import 'parser.dart';
+export 'src/css_class_set.dart' show CssClassSet;
+
// TODO(jmesserly): this needs to be replaced by an AttributeMap for attributes
// that exposes namespace info.
class AttributeName implements Comparable {
@@ -60,6 +68,55 @@ class AttributeName implements Comparable {
}
}
+// http://dom.spec.whatwg.org/#parentnode
+abstract class _ParentNode implements Node {
+ // TODO(jmesserly): this is only a partial implementation
+
+ /// Seaches for the first descendant node matching the given selectors, using
+ /// a preorder traversal.
+ ///
+ /// NOTE: Not all selectors from
+ /// [selectors level 4](http://dev.w3.org/csswg/selectors-4/)
+ /// are implemented. For example, nth-child does not implement An+B syntax
+ /// and *-of-type is not implemented. If a selector is not implemented this
+ /// method will throw [UniplmentedError].
+ Element querySelector(String selector) =>
+ query.querySelector(this, selector);
+
+ /// Returns all descendant nodes matching the given selectors, using a
+ /// preorder traversal.
+ ///
+ /// NOTE: Not all selectors from
+ /// [selectors level 4](http://dev.w3.org/csswg/selectors-4/)
+ /// are implemented. For example, nth-child does not implement An+B syntax
+ /// and *-of-type is not implemented. If a selector is not implemented this
+ /// method will throw [UniplmentedError].
+ List<Element> querySelectorAll(String selector) =>
+ query.querySelectorAll(this, selector);
+}
+
+// http://dom.spec.whatwg.org/#interface-nonelementparentnode
+abstract class _NonElementParentNode implements _ParentNode {
+ // TODO(jmesserly): could be faster, should throw on invalid id.
+ Element getElementById(String id) => querySelector('#$id');
+}
+
+// This doesn't exist as an interface in the spec, but it's useful to merge
+// common methods from these:
+// http://dom.spec.whatwg.org/#interface-document
+// http://dom.spec.whatwg.org/#element
+abstract class _ElementAndDocument implements _ParentNode {
+ // TODO(jmesserly): could be faster, should throw on invalid tag/class names.
+
+ List<Element> getElementsByTagName(String localName) =>
+ querySelectorAll(localName);
+
+ List<Element> getElementsByClassName(String classNames) =>
+ querySelectorAll(classNames.splitMapJoin(' ',
+ onNonMatch: (m) => m.isNotEmpty ? '.$m' : m,
+ onMatch: (m) => ''));
+}
+
/// Really basic implementation of a DOM-core like Node.
abstract class Node {
static const int ATTRIBUTE_NODE = 2;
@@ -75,16 +132,14 @@ abstract class Node {
static const int PROCESSING_INSTRUCTION_NODE = 7;
static const int TEXT_NODE = 3;
- /// Note: For now we use it to implement the deprecated tagName property.
- final String _tagName;
-
- /// *Deprecated* use [Element.localName] instead.
- /// Note: after removal, this will be replaced by a correct version that
- /// returns uppercase [as specified](http://dom.spec.whatwg.org/#dom-element-tagname).
- @deprecated String get tagName => _tagName;
-
/// The parent of the current node (or null for the document node).
- Node parent;
+ Node parentNode;
+
+ /// The parent element of this node.
+ ///
+ /// Returns null if this node either does not have a parent or its parent is
+ /// not an element.
+ Element get parent => parentNode is Element ? parentNode : null;
// TODO(jmesserly): should move to Element.
/// A map holding name, value pairs for attributes of the node.
@@ -108,10 +163,7 @@ abstract class Node {
LinkedHashMap<dynamic, FileSpan> _attributeSpans;
LinkedHashMap<dynamic, FileSpan> _attributeValueSpans;
- /// *Deprecated* use [new Element.tag] instead.
- @deprecated Node(String tagName) : this._(tagName);
-
- Node._([this._tagName]) {
+ Node._() {
nodes._parent = this;
}
@@ -140,29 +192,14 @@ abstract class Node {
return _elements;
}
- // TODO(jmesserly): needs to support deep clone.
- /// Return a shallow copy of the current node i.e. a node with the same
- /// name and attributes but with no parent or child nodes.
- Node clone();
-
- /// *Deprecated* use [Element.namespaceUri] instead.
- @deprecated String get namespace => null;
+ /// Returns a copy of this node.
+ ///
+ /// If [deep] is `true`, then all of this node's children and decendents are
+ /// copied as well. If [deep] is `false`, then only this node is copied.
+ Node clone(bool deep);
int get nodeType;
- /// *Deprecated* use [text], [Text.data] or [Comment.data].
- @deprecated String get value => null;
-
- /// *Deprecated* use [nodeType].
- @deprecated int get $dom_nodeType => nodeType;
-
- /// *Deprecated* use [Element.outerHtml]
- @deprecated String get outerHtml => _outerHtml;
-
- /// *Deprecated* use [Element.innerHtml]
- @deprecated String get innerHtml => _innerHtml;
- @deprecated set innerHtml(String value) { _innerHtml = value; }
-
// http://domparsing.spec.whatwg.org/#extensions-to-the-element-interface
String get _outerHtml {
var str = new StringBuffer();
@@ -176,13 +213,6 @@ abstract class Node {
return str.toString();
}
- set _innerHtml(String value) {
- nodes.clear();
- // TODO(jmesserly): should be able to get the same effect by adding the
- // fragment directly.
- nodes.addAll(parseFragment(value, container: _tagName).nodes);
- }
-
// Implemented per: http://dom.spec.whatwg.org/#dom-node-textcontent
String get text => null;
set text(String value) {}
@@ -199,8 +229,8 @@ abstract class Node {
Node remove() {
// TODO(jmesserly): is parent == null an error?
- if (parent != null) {
- parent.nodes.remove(this);
+ if (parentNode != null) {
+ parentNode.nodes.remove(this);
}
return this;
}
@@ -219,10 +249,10 @@ abstract class Node {
/// Replaces this node with another node.
Node replaceWith(Node otherNode) {
- if (parent == null) {
+ if (parentNode == null) {
throw new UnsupportedError('Node must have a parent to replace it.');
}
- parent.nodes[parent.nodes.indexOf(this)] = otherNode;
+ parentNode.nodes[parentNode.nodes.indexOf(this)] = otherNode;
return this;
}
@@ -230,10 +260,6 @@ abstract class Node {
/// Return true if the node has children or text.
bool hasContent() => nodes.length > 0;
- /// *Deprecated* construct a pair using the namespaceUri and the name.
- @deprecated Pair<String, String> get nameTuple =>
- this is Element ? getElementNameTuple(this) : null;
-
/// Move all the children of the current node to [newParent].
/// This is needed so that trees that don't store text as nodes move the
/// text in the correct way.
@@ -242,42 +268,10 @@ abstract class Node {
nodes.clear();
}
- /// *Deprecated* use [querySelector] instead.
- @deprecated
- Element query(String selectors) => querySelector(selectors);
-
- /// *Deprecated* use [querySelectorAll] instead.
- @deprecated
- List<Element> queryAll(String selectors) => querySelectorAll(selectors);
-
- /// Seaches for the first descendant node matching the given selectors, using a
- /// preorder traversal. NOTE: right now, this supports only a single type
- /// selectors, e.g. `node.query('div')`.
-
- Element querySelector(String selectors) =>
- _queryType(_typeSelector(selectors));
-
- /// Returns all descendant nodes matching the given selectors, using a
- /// preorder traversal. NOTE: right now, this supports only a single type
- /// selectors, e.g. `node.queryAll('div')`.
- List<Element> querySelectorAll(String selectors) {
- var results = new List<Element>();
- _queryAllType(_typeSelector(selectors), results);
- return results;
- }
-
bool hasChildNodes() => !nodes.isEmpty;
bool contains(Node node) => nodes.contains(node);
- String _typeSelector(String selectors) {
- selectors = selectors.trim();
- if (!_isTypeSelector(selectors)) {
- throw new UnimplementedError('only type selectors are implemented');
- }
- return selectors;
- }
-
/// Checks if this is a type selector.
/// See <http://www.w3.org/TR/CSS2/grammar.html>.
/// Note: this doesn't support '*', the universal selector, non-ascii chars or
@@ -318,24 +312,6 @@ abstract class Node {
return true;
}
- Element _queryType(String tag) {
- for (var node in nodes) {
- if (node is! Element) continue;
- if (node.localName == tag) return node;
- var result = node._queryType(tag);
- if (result != null) return result;
- }
- return null;
- }
-
- void _queryAllType(String tag, List<Element> results) {
- for (var node in nodes) {
- if (node is! Element) continue;
- if (node.localName == tag) results.add(node);
- node._queryAllType(tag, results);
- }
- }
-
/// Initialize [attributeSpans] using [sourceSpan].
void _ensureAttributeSpans() {
if (_attributeSpans != null) return;
@@ -363,9 +339,20 @@ abstract class Node {
}
}
}
+
+ _clone(Node shallowClone, bool deep) {
+ if (deep) {
+ for (var child in nodes) {
+ shallowClone.append(child.clone(true));
+ }
+ }
+ return shallowClone;
+ }
}
-class Document extends Node {
+class Document extends Node
+ with _ParentNode, _NonElementParentNode, _ElementAndDocument {
+
Document() : super._();
factory Document.html(String html) => parse(html);
@@ -388,18 +375,41 @@ class Document extends Node {
void _addOuterHtml(StringBuffer str) => _addInnerHtml(str);
- Document clone() => new Document();
+ Document clone(bool deep) => _clone(new Document(), deep);
+
+ Element createElement(String tag) => new Element.tag(tag);
+
+ // TODO(jmesserly): this is only a partial implementation of:
+ // http://dom.spec.whatwg.org/#dom-document-createelementns
+ Element createElementNS(String namespaceUri, String tag) {
+ if (namespaceUri == '') namespaceUri = null;
+ return new Element._(tag, namespaceUri);
+ }
+
+ DocumentFragment createDocumentFragment() => new DocumentFragment();
}
-class DocumentFragment extends Document {
- DocumentFragment();
+class DocumentFragment extends Node
+ with _ParentNode, _NonElementParentNode {
+
+ DocumentFragment() : super._();
factory DocumentFragment.html(String html) => parseFragment(html);
int get nodeType => Node.DOCUMENT_FRAGMENT_NODE;
+ /// Returns a fragment of HTML or XML that represents the element and its
+ /// contents.
+ // TODO(jmesserly): this API is not specified in:
+ // <http://domparsing.spec.whatwg.org/> nor is it in dart:html, instead
+ // only Element has outerHtml. However it is quite useful. Should we move it
+ // to dom_parsing, where we keep other custom APIs?
+ String get outerHtml => _outerHtml;
+
String toString() => "#document-fragment";
- DocumentFragment clone() => new DocumentFragment();
+ DocumentFragment clone(bool deep) => _clone(new DocumentFragment(), deep);
+
+ void _addOuterHtml(StringBuffer str) => _addInnerHtml(str);
String get text => _getText(this);
set text(String value) => _setText(this, value);
@@ -412,7 +422,7 @@ class DocumentType extends Node {
DocumentType(String name, this.publicId, this.systemId)
// Note: once Node.tagName is removed, don't pass "name" to super
- : name = name, super._(name);
+ : name = name, super._();
int get nodeType => Node.DOCUMENT_TYPE_NODE;
@@ -433,7 +443,7 @@ class DocumentType extends Node {
str.write(toString());
}
- DocumentType clone() => new DocumentType(name, publicId, systemId);
+ DocumentType clone(bool deep) => new DocumentType(name, publicId, systemId);
}
class Text extends Node {
@@ -441,36 +451,29 @@ class Text extends Node {
Text(this.data) : super._();
- /// *Deprecated* use [data].
- @deprecated String get value => data;
- @deprecated set value(String x) { data = x; }
-
int get nodeType => Node.TEXT_NODE;
String toString() => '"$data"';
void _addOuterHtml(StringBuffer str) => writeTextNodeAsHtml(str, this);
- Text clone() => new Text(data);
+ Text clone(bool deep) => new Text(data);
String get text => data;
set text(String value) { data = value; }
}
-class Element extends Node {
+// TODO(jmesserly): Elements should have a pointer back to their document
+class Element extends Node with _ParentNode, _ElementAndDocument {
final String namespaceUri;
- @deprecated String get namespace => namespaceUri;
-
/// The [local name](http://dom.spec.whatwg.org/#concept-element-local-name)
/// of this element.
- String get localName => _tagName;
+ final String localName;
- // TODO(jmesserly): deprecate in favor of [Document.createElementNS].
- // However we need every element to have a Document before this can work.
- Element(String name, [this.namespaceUri]) : super._(name);
+ Element._(this.localName, [this.namespaceUri]) : super._();
- Element.tag(String name) : namespaceUri = null, super._(name);
+ Element.tag(this.localName) : namespaceUri = Namespaces.html, super._();
static final _START_TAG_REGEXP = new RegExp('<(\\w+)');
@@ -528,9 +531,30 @@ class Element extends Node {
int get nodeType => Node.ELEMENT_NODE;
+ // TODO(jmesserly): we can make this faster
+ Element get previousElementSibling {
+ if (parentNode == null) return null;
+ var siblings = parentNode.nodes;
+ for (int i = siblings.indexOf(this) - 1; i >= 0; i--) {
+ var s = siblings[i];
+ if (s is Element) return s;
+ }
+ return null;
+ }
+
+ Element get nextElementSibling {
+ if (parentNode == null) return null;
+ var siblings = parentNode.nodes;
+ for (int i = siblings.indexOf(this) + 1; i < siblings.length; i++) {
+ var s = siblings[i];
+ if (s is Element) return s;
+ }
+ return null;
+ }
+
String toString() {
- if (namespaceUri == null) return "<$localName>";
- return "<${Namespaces.getPrefix(namespaceUri)} $localName>";
+ var prefix = Namespaces.getPrefix(namespaceUri);
+ return "<${prefix == null ? '' : '$prefix '}$localName>";
}
String get text => _getText(this);
@@ -546,21 +570,17 @@ class Element extends Node {
String get innerHtml => _innerHtml;
// TODO(jmesserly): deprecate in favor of:
// <https://api.dartlang.org/apidocs/channels/stable/#dart-dom-html.Element@id_setInnerHtml>
- set innerHtml(String value) { _innerHtml = value; }
+ set innerHtml(String value) {
+ nodes.clear();
+ // TODO(jmesserly): should be able to get the same effect by adding the
+ // fragment directly.
+ nodes.addAll(parseFragment(value, container: localName).nodes);
+ }
void _addOuterHtml(StringBuffer str) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments
// Element is the most complicated one.
- if (namespaceUri == null ||
- namespaceUri == Namespaces.html ||
- namespaceUri == Namespaces.mathml ||
- namespaceUri == Namespaces.svg) {
- str.write('<$localName');
- } else {
- // TODO(jmesserly): the spec doesn't define "qualified name".
- // I'm not sure if this is correct, but it should parse reasonably.
- str.write('<${Namespaces.getPrefix(namespaceUri)}:$localName');
- }
+ str.write('<${_getSerializationPrefix(namespaceUri)}$localName');
if (attributes.length > 0) {
attributes.forEach((key, v) {
@@ -591,21 +611,56 @@ class Element extends Node {
if (!isVoidElement(localName)) str.write('</$localName>');
}
- Element clone() => new Element(localName, namespaceUri)
- ..attributes = new LinkedHashMap.from(attributes);
+ static String _getSerializationPrefix(String uri) {
+ if (uri == null ||
+ uri == Namespaces.html ||
+ uri == Namespaces.mathml ||
+ uri == Namespaces.svg) {
+ return '';
+ }
+ var prefix = Namespaces.getPrefix(uri);
+ // TODO(jmesserly): the spec doesn't define "qualified name".
+ // I'm not sure if this is correct, but it should parse reasonably.
+ return prefix == null ? '' : '$prefix:';
+ }
+
+ Element clone(bool deep) {
+ var result = new Element._(localName, namespaceUri)
+ ..attributes = new LinkedHashMap.from(attributes);
+ return _clone(result, deep);
+ }
+ // http://dom.spec.whatwg.org/#dom-element-id
String get id {
var result = attributes['id'];
return result != null ? result : '';
}
set id(String value) {
- if (value == null) {
- attributes.remove('id');
- } else {
- attributes['id'] = value;
- }
+ attributes['id'] = '$value';
}
+
+ // http://dom.spec.whatwg.org/#dom-element-classname
+ String get className {
+ var result = attributes['class'];
+ return result != null ? result : '';
+ }
+
+ set className(String value) {
+ attributes['class'] = '$value';
+ }
+
+ /**
+ * The set of CSS classes applied to this element.
+ *
+ * This set makes it easy to add, remove or toggle the classes applied to
+ * this element.
+ *
+ * element.classes.add('selected');
+ * element.classes.toggle('isOnline');
+ * element.classes.remove('selected');
+ */
+ CssClassSet get classes => new ElementCssClassSet(this);
}
class Comment extends Node {
@@ -621,7 +676,7 @@ class Comment extends Node {
str.write("<!--$data-->");
}
- Comment clone() => new Comment(data);
+ Comment clone(bool deep) => new Comment(data);
String get text => data;
set text(String value) {
@@ -646,7 +701,7 @@ class NodeList extends ListProxy<Node> {
// Note: we need to remove the node from its previous parent node, if any,
// before updating its parent pointer to point at our parent.
node.remove();
- node.parent = _parent;
+ node.parentNode = _parent;
return node;
}
@@ -681,12 +736,12 @@ class NodeList extends ListProxy<Node> {
}
}
- Node removeLast() => super.removeLast()..parent = null;
+ Node removeLast() => super.removeLast()..parentNode = null;
- Node removeAt(int i) => super.removeAt(i)..parent = null;
+ Node removeAt(int i) => super.removeAt(i)..parentNode = null;
void clear() {
- for (var node in this) node.parent = null;
+ for (var node in this) node.parentNode = null;
super.clear();
}
@@ -695,7 +750,7 @@ class NodeList extends ListProxy<Node> {
removeAt(index);
insertAll(index, value.nodes);
} else {
- this[index].parent = null;
+ this[index].parentNode = null;
super[index] = _setParent(value);
}
}
@@ -721,20 +776,20 @@ class NodeList extends ListProxy<Node> {
}
void removeRange(int start, int rangeLength) {
- for (int i = start; i < rangeLength; i++) this[i].parent = null;
+ for (int i = start; i < rangeLength; i++) this[i].parentNode = null;
super.removeRange(start, rangeLength);
}
void removeWhere(bool test(Element e)) {
for (var node in where(test)) {
- node.parent = null;
+ node.parentNode = null;
}
super.removeWhere(test);
}
void retainWhere(bool test(Element e)) {
for (var node in where((n) => !test(n))) {
- node.parent = null;
+ node.parentNode = null;
}
super.retainWhere(test);
}
« no previous file with comments | « pkg/pkg.status ('k') | pkg/third_party/html5lib/lib/dom_parsing.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698