ref: 52ebaa6c60bf90f3b7b326b94723c1999ea99655
dir: /domino-lib/TreeWalker.js/
"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]"; }}, });