337 lines
8.7 KiB
JavaScript
Executable File
337 lines
8.7 KiB
JavaScript
Executable File
"use strict";
|
|
module.exports = TreeWalker;
|
|
|
|
var Node = require('./Node');
|
|
var NodeFilter = require('./NodeFilter');
|
|
var NodeTraversal = require('./NodeTraversal');
|
|
var utils = require('./utils');
|
|
|
|
var mapChild = {
|
|
first: 'firstChild',
|
|
last: 'lastChild',
|
|
next: 'firstChild',
|
|
previous: 'lastChild'
|
|
};
|
|
|
|
var mapSibling = {
|
|
first: 'nextSibling',
|
|
last: 'previousSibling',
|
|
next: 'nextSibling',
|
|
previous: 'previousSibling'
|
|
};
|
|
|
|
/* Private methods and helpers */
|
|
|
|
/**
|
|
* @spec https://dom.spec.whatwg.org/#concept-traverse-children
|
|
* @method
|
|
* @access private
|
|
* @param {TreeWalker} tw
|
|
* @param {string} type One of 'first' or 'last'.
|
|
* @return {Node|null}
|
|
*/
|
|
function traverseChildren(tw, type) {
|
|
var child, node, parent, result, sibling;
|
|
node = tw._currentNode[mapChild[type]];
|
|
while (node !== null) {
|
|
result = tw._internalFilter(node);
|
|
if (result === NodeFilter.FILTER_ACCEPT) {
|
|
tw._currentNode = node;
|
|
return node;
|
|
}
|
|
if (result === NodeFilter.FILTER_SKIP) {
|
|
child = node[mapChild[type]];
|
|
if (child !== null) {
|
|
node = child;
|
|
continue;
|
|
}
|
|
}
|
|
while (node !== null) {
|
|
sibling = node[mapSibling[type]];
|
|
if (sibling !== null) {
|
|
node = sibling;
|
|
break;
|
|
}
|
|
parent = node.parentNode;
|
|
if (parent === null || parent === tw.root || parent === tw._currentNode) {
|
|
return null;
|
|
} else {
|
|
node = parent;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @spec https://dom.spec.whatwg.org/#concept-traverse-siblings
|
|
* @method
|
|
* @access private
|
|
* @param {TreeWalker} tw
|
|
* @param {TreeWalker} type One of 'next' or 'previous'.
|
|
* @return {Node|nul}
|
|
*/
|
|
function traverseSiblings(tw, type) {
|
|
var node, result, sibling;
|
|
node = tw._currentNode;
|
|
if (node === tw.root) {
|
|
return null;
|
|
}
|
|
while (true) {
|
|
sibling = node[mapSibling[type]];
|
|
while (sibling !== null) {
|
|
node = sibling;
|
|
result = tw._internalFilter(node);
|
|
if (result === NodeFilter.FILTER_ACCEPT) {
|
|
tw._currentNode = node;
|
|
return node;
|
|
}
|
|
sibling = node[mapChild[type]];
|
|
if (result === NodeFilter.FILTER_REJECT || sibling === null) {
|
|
sibling = node[mapSibling[type]];
|
|
}
|
|
}
|
|
node = node.parentNode;
|
|
if (node === null || node === tw.root) {
|
|
return null;
|
|
}
|
|
if (tw._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Public API */
|
|
|
|
/**
|
|
* Latest version: https://dom.spec.whatwg.org/#treewalker
|
|
*
|
|
* @constructor
|
|
* @param {Node} root
|
|
* @param {number} whatToShow [optional]
|
|
* @param {Function|NodeFilter} filter [optional]
|
|
* @throws Error
|
|
*/
|
|
function TreeWalker(root, whatToShow, filter) {
|
|
if (!root || !root.nodeType) {
|
|
utils.NotSupportedError();
|
|
}
|
|
|
|
// Read-only properties
|
|
this._root = root;
|
|
this._whatToShow = Number(whatToShow) || 0;
|
|
this._filter = filter || null;
|
|
this._active = false;
|
|
// Read-write property
|
|
this._currentNode = root;
|
|
}
|
|
|
|
Object.defineProperties(TreeWalker.prototype, {
|
|
root: { get: function() { return this._root; } },
|
|
whatToShow: { get: function() { return this._whatToShow; } },
|
|
filter: { get: function() { return this._filter; } },
|
|
|
|
currentNode: {
|
|
get: function currentNode() {
|
|
return this._currentNode;
|
|
},
|
|
set: function setCurrentNode(v) {
|
|
if (!(v instanceof Node)) {
|
|
throw new TypeError("Not a Node"); // `null` is also not a node
|
|
}
|
|
this._currentNode = v;
|
|
},
|
|
},
|
|
|
|
/**
|
|
* @method
|
|
* @param {Node} node
|
|
* @return {Number} Constant NodeFilter.FILTER_ACCEPT,
|
|
* NodeFilter.FILTER_REJECT or NodeFilter.FILTER_SKIP.
|
|
*/
|
|
_internalFilter: { value: function _internalFilter(node) {
|
|
/* jshint bitwise: false */
|
|
var result, filter;
|
|
if (this._active) {
|
|
utils.InvalidStateError();
|
|
}
|
|
|
|
// Maps nodeType to whatToShow
|
|
if (!(((1 << (node.nodeType - 1)) & this._whatToShow))) {
|
|
return NodeFilter.FILTER_SKIP;
|
|
}
|
|
|
|
filter = this._filter;
|
|
if (filter === null) {
|
|
result = NodeFilter.FILTER_ACCEPT;
|
|
} else {
|
|
this._active = true;
|
|
try {
|
|
if (typeof filter === 'function') {
|
|
result = filter(node);
|
|
} else {
|
|
result = filter.acceptNode(node);
|
|
}
|
|
} finally {
|
|
this._active = false;
|
|
}
|
|
}
|
|
|
|
// Note that coercing to a number means that
|
|
// `true` becomes `1` (which is NodeFilter.FILTER_ACCEPT)
|
|
// `false` becomes `0` (neither accept, reject, or skip)
|
|
return (+result);
|
|
}},
|
|
|
|
/**
|
|
* @spec https://dom.spec.whatwg.org/#dom-treewalker-parentnode
|
|
* @based on WebKit's TreeWalker::parentNode
|
|
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/TreeWalker.cpp?rev=220453#L50
|
|
* @method
|
|
* @return {Node|null}
|
|
*/
|
|
parentNode: { value: function parentNode() {
|
|
var node = this._currentNode;
|
|
while (node !== this.root) {
|
|
node = node.parentNode;
|
|
if (node === null) {
|
|
return null;
|
|
}
|
|
if (this._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
|
|
this._currentNode = node;
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}},
|
|
|
|
/**
|
|
* @spec https://dom.spec.whatwg.org/#dom-treewalker-firstchild
|
|
* @method
|
|
* @return {Node|null}
|
|
*/
|
|
firstChild: { value: function firstChild() {
|
|
return traverseChildren(this, 'first');
|
|
}},
|
|
|
|
/**
|
|
* @spec https://dom.spec.whatwg.org/#dom-treewalker-lastchild
|
|
* @method
|
|
* @return {Node|null}
|
|
*/
|
|
lastChild: { value: function lastChild() {
|
|
return traverseChildren(this, 'last');
|
|
}},
|
|
|
|
/**
|
|
* @spec http://www.w3.org/TR/dom/#dom-treewalker-previoussibling
|
|
* @method
|
|
* @return {Node|null}
|
|
*/
|
|
previousSibling: { value: function previousSibling() {
|
|
return traverseSiblings(this, 'previous');
|
|
}},
|
|
|
|
/**
|
|
* @spec http://www.w3.org/TR/dom/#dom-treewalker-nextsibling
|
|
* @method
|
|
* @return {Node|null}
|
|
*/
|
|
nextSibling: { value: function nextSibling() {
|
|
return traverseSiblings(this, 'next');
|
|
}},
|
|
|
|
/**
|
|
* @spec https://dom.spec.whatwg.org/#dom-treewalker-previousnode
|
|
* @based on WebKit's TreeWalker::previousNode
|
|
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/TreeWalker.cpp?rev=220453#L181
|
|
* @method
|
|
* @return {Node|null}
|
|
*/
|
|
previousNode: { value: function previousNode() {
|
|
var node, result, previousSibling, lastChild;
|
|
node = this._currentNode;
|
|
while (node !== this._root) {
|
|
for (previousSibling = node.previousSibling;
|
|
previousSibling;
|
|
previousSibling = node.previousSibling) {
|
|
node = previousSibling;
|
|
result = this._internalFilter(node);
|
|
if (result === NodeFilter.FILTER_REJECT) {
|
|
continue;
|
|
}
|
|
for (lastChild = node.lastChild;
|
|
lastChild;
|
|
lastChild = node.lastChild) {
|
|
node = lastChild;
|
|
result = this._internalFilter(node);
|
|
if (result === NodeFilter.FILTER_REJECT) {
|
|
break;
|
|
}
|
|
}
|
|
if (result === NodeFilter.FILTER_ACCEPT) {
|
|
this._currentNode = node;
|
|
return node;
|
|
}
|
|
}
|
|
if (node === this.root || node.parentNode === null) {
|
|
return null;
|
|
}
|
|
node = node.parentNode;
|
|
if (this._internalFilter(node) === NodeFilter.FILTER_ACCEPT) {
|
|
this._currentNode = node;
|
|
return node;
|
|
}
|
|
}
|
|
return null;
|
|
}},
|
|
|
|
/**
|
|
* @spec https://dom.spec.whatwg.org/#dom-treewalker-nextnode
|
|
* @based on WebKit's TreeWalker::nextNode
|
|
* https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/TreeWalker.cpp?rev=220453#L228
|
|
* @method
|
|
* @return {Node|null}
|
|
*/
|
|
nextNode: { value: function nextNode() {
|
|
var node, result, firstChild, nextSibling;
|
|
node = this._currentNode;
|
|
result = NodeFilter.FILTER_ACCEPT;
|
|
|
|
CHILDREN:
|
|
while (true) {
|
|
for (firstChild = node.firstChild;
|
|
firstChild;
|
|
firstChild = node.firstChild) {
|
|
node = firstChild;
|
|
result = this._internalFilter(node);
|
|
if (result === NodeFilter.FILTER_ACCEPT) {
|
|
this._currentNode = node;
|
|
return node;
|
|
} else if (result === NodeFilter.FILTER_REJECT) {
|
|
break;
|
|
}
|
|
}
|
|
for (nextSibling = NodeTraversal.nextSkippingChildren(node, this.root);
|
|
nextSibling;
|
|
nextSibling = NodeTraversal.nextSkippingChildren(node, this.root)) {
|
|
node = nextSibling;
|
|
result = this._internalFilter(node);
|
|
if (result === NodeFilter.FILTER_ACCEPT) {
|
|
this._currentNode = node;
|
|
return node;
|
|
} else if (result === NodeFilter.FILTER_SKIP) {
|
|
continue CHILDREN;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}},
|
|
|
|
/** For compatibility with web-platform-tests. */
|
|
toString: { value: function toString() {
|
|
return "[object TreeWalker]";
|
|
}},
|
|
});
|