TEST
Simple Implementation of DOM Traversal in JavaScript
最終更新:
eriax
-
view
制限
- なし。
仕様上の注意
- TreeWalker と違い、NodeIterator は参照ノードが除去されても文書木に残り、参照ノードを調節する。
- NodeIterator は NodeFilter.FILTER_REJECT と FILTER_SKIP を同一に扱う。
ソースコード
function DOMObject(arg) { if (arguments.length > 0) { var name; for (name in arg) { if (arg.hasOwnProperty(name)) { this[name] = arg[name]; } } } } (function () { this.constructor = DOMObject; }).call(DOMObject.prototype); //////////////////////////////////////////////////////////////////////// function DOMNodeFilter(arg) { if (arguments.length > 0) { if (arg) { DOMObject.apply(this, arguments); } } }; DOMNodeFilter.prototype = new DOMObject; (function () { this.constructor = DOMNodeFilter; this.acceptNode = null; }).call(DOMNodeFilter.prototype); (function () { // Constants returned by acceptNode this.FILTER_ACCEPT = 1; this.FILTER_REJECT = 2; this.FILTER_SKIP = 3; // Constants for whatToShow this.SHOW_ALL = 0xFFFFFFFF; this.SHOW_ELEMENT = 0x00000001; this.SHOW_ATTRIBUTE = 0x00000002; this.SHOW_TEXT = 0x00000004; this.SHOW_CDATA_SECTION = 0x00000008; this.SHOW_ENTITY_REFERENCE = 0x00000010; this.SHOW_ENTITY = 0x00000020; this.SHOW_PROCESSING_INSTRUCTION = 0x00000040; this.SHOW_COMMENT = 0x00000080; this.SHOW_DOCUMENT = 0x00000100; this.SHOW_DOCUMENT_TYPE = 0x00000200; this.SHOW_DOCUMENT_FRAGMENT = 0x00000400; this.SHOW_NOTATION = 0x00000800; }).call(DOMNodeFilter); //////////////////////////////////////////////////////////////////////// function DOMNodeIterator(arg) { if (arguments.length > 0) { if (arg) { DOMObject.apply(this, arguments); } } } DOMNodeIterator.prototype = new DOMObject; (function () { this.constructor = DOMNodeIterator; this.root = null; this.whatToShow = null; this.filter = null; this.expandEntityReference = null; this['[CurrentRoute]'] = null; }).call(DOMNodeIterator.prototype); (function () { this['[GetChildIndex]'] = function (node) { var i = 0; var n = node; while ((n = n.previousSibling)) { i += 1; } return i; }; this['[TraceRoute]'] = function (node) { var result = []; var root = this.root; while (node) { if (root === node) { result[result.length] = [node, 0]; return result.reverse(); } else { result[result.length] = [node, this['[GetChildIndex]'](node)]; node = node.parentNode; } } return []; }; this['[HasRoot]'] = function (node) { var root = this.root; var n = node; for (; n; n = n.parentNode) { if (n === root) { break; } } return Boolean(n); }; this.nextNode = function () { if (!this.root) { throw new Error('already detached.'); // DOMException.INVALID_STATE_ERR } var root = this.root; var node = root; var route = this['[CurrentRoute]']; var stepCount = route.length; var n; switch (stepCount) { case 0: return null; case 1: var step0 = route[0]; if (step0[0] === root && step0[1] === 1) { return null; } break; default: var step; var i; for (i = 1; i < stepCount; i++) { step = route[i]; var cache = step[0]; if (this['[HasRoot]'](cache)) { // cache still exists under the root node = cache; continue; } var cndex = step[1]; var childNodes = node.childNodes; if (cndex < childNodes.length) { node = childNodes[cndex]; break; } this['[CurrentRoute]'] = [ [root, 1] ]; return null; } break; } var accepted = null; var dir; while (true) { switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: accepted = node; // NodeIterators treat this as a synonym for FILTER_SKIP case DOMNodeFilter.FILTER_REJECT: case DOMNodeFilter.FILTER_SKIP: dir = 'firstChild'; break; default: throw new Error; } if ((n = node[dir])) { node = n; } else { do { if (node === root) { node = null; break; } if ((n = node.nextSibling)) { node = n; break; } } while ((node = node.parentNode)); } if (accepted) { if (node == null) { // out of visible list this['[CurrentRoute]'] = [ [root, 1] ]; } else { this['[CurrentRoute]'] = this['[TraceRoute]'](node); // A . [N] } return accepted; } if (node == null) { // out of visible list this['[CurrentRoute]'] = [ [root, 1] ]; return null; } } }; this.previousNode = function () { if (!this.root) { throw new Error('already detached.'); // DOMException.INVALID_STATE_ERR } var root = this.root; var node = root; var route = this['[CurrentRoute]']; var stepCount = route.length; var n; switch (stepCount) { case 0: return null; case 1: var step0 = route[0]; if (step0[0] === root && step0[1] === 0) { return null; } while ((n = node.lastChild)) { node = n; } break; default: var step; var i; for (i = 1; i < stepCount; i++) { step = route[i]; var cache = step[0]; if (this['[HasRoot]'](cache)) { // cache still exists under the root node = cache; continue; } var cndex = step[1]; var childNodes = node.childNodes; if (cndex < childNodes.length) { node = childNodes[cndex]; break; } this['[CurrentRoute]'] = [ [root, 0] ]; return null; } break; } while (true) { if ((n = node.previousSibling)) { for (node = n; true;) { if ((n = node.lastChild)) { node = n; continue; } break; } } else { node = node.parentNode; if (node == null) { // out of visible list this['[CurrentRoute]'] = [ [root, 0] ]; return null; } } switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: if (node === root) { this['[CurrentRoute]'] = [ [root, 0] ]; } else { this['[CurrentRoute]'] = this['[TraceRoute]'](node); // [P] . A } return node; case DOMNodeFilter.FILTER_REJECT: case DOMNodeFilter.FILTER_SKIP: break; default: throw new Error; } } }; this.detach = function () { if (!this.root) { throw new Error('already detached.'); // DOMException.INVALID_STATE_ERR } this.root = null; this.whatToShow = null; this.filter = null; this.expandEntityReference = null; }; this['[CallFilter]'] = function (node) { if (0 !== (this.whatToShow & (1 << node.nodeType - 1))) { var filter = this.filter; if (filter) { if ('function' === typeof filter.acceptNode) { return filter.acceptNode(node); } return filter.call(filter, node); } return DOMNodeFilter.FILTER_ACCEPT; } return DOMNodeFilter.FILTER_SKIP; }; }).call(DOMNodeIterator.prototype); var as_DOM_DocumentTraversal_createNodeIterator = function (doc, root, whatToShow, filter, entityReferenceExpansion) { if (root == null) { // or undefined throw new Error; // DOMException.NOT_SUPPORTED_ERR(9) } var nt = new DOMNodeIterator({ root: root, whatToShow: whatToShow, filter: filter, expandEntityReference: entityReferenceExpansion }); nt['[CurrentRoute]'] = nt['[TraceRoute]'](root); return nt; }; //////////////////////////////////////////////////////////////////////// function DOMTreeWalker(arg) { if (arguments.length > 0) { if (arg) { DOMObject.apply(this, arguments); } } } DOMTreeWalker.prototype = new DOMObject; (function () { this.constructor = DOMTreeWalker; this.root = null; this.whatToShow = null; this.filter = null; this.expandEntityReference = null; this.currentNode = null; }).call(DOMTreeWalker.prototype); (function () { this['[HasRoot]'] = function (node) { var root = this.root; var n = node; for (; n; n = n.parentNode) { if (n === root) { break; } } return Boolean(n); }; this['[CallFilter]'] = function (node) { if (0 !== (this.whatToShow & (1 << node.nodeType - 1))) { var filter = this.filter; if (filter) { if ('function' === typeof filter.acceptNode) { return filter.acceptNode(node); } return filter.call(filter, node); } return DOMNodeFilter.FILTER_ACCEPT; } return DOMNodeFilter.FILTER_SKIP; }; this.nextNode = function () { var node = this.currentNode; if (!this['[HasRoot]'](node)) { return null; } var root = this.root; var dir = 'firstChild'; var n; while (true) { if ((n = node[dir])) { node = n; } else { do { if (node === root) { node = null; break; } if ((n = node.nextSibling)) { node = n; break; } } while ((node = node.parentNode)); } if (node == null) { return null; } switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: this.currentNode = node; return node; case DOMNodeFilter.FILTER_SKIP: dir = 'firstChild'; break; case DOMNodeFilter.FILTER_REJECT: dir = 'nextSibling'; break; default: throw new Error; } } }; this.previousNode = function () { var node = this.currentNode; if (!this['[HasRoot]'](node)) { return null; } var root = this.root; var n; while (true) { if (node === root) { return null; } if ((n = node.previousSibling)) { for (node = n; true;) { if (this['[CallFilter]'](node) !== DOMNodeFilter.FILTER_REJECT && ((n = node.lastChild))) { node = n; continue; } break; } } else { node = node.parentNode; if (node == null) { // out of visible list return null; } } switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: this.currentNode = node; return node; case DOMNodeFilter.FILTER_SKIP: case DOMNodeFilter.FILTER_REJECT: break; default: throw new Error; } } }; this.parentNode = function () { var node = this.currentNode; if (!this['[HasRoot]'](node)) { return null; } var root = this.root; var n; while (true) { if (node === root) { return null; } node = node.parentNode; if (node == null) { // out of visible list return null; } switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: this.currentNode = node; return node; case DOMNodeFilter.FILTER_SKIP: case DOMNodeFilter.FILTER_REJECT: break; default: throw new Error; } } }; this.firstChild = function () { var node = this.currentNode; if (!this['[HasRoot]'](node)) { return null; } for (node = node.firstChild; node; node = node.nextSibling) { switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: this.currentNode = node; return node; case DOMNodeFilter.FILTER_SKIP: case DOMNodeFilter.FILTER_REJECT: break; default: throw new Error; } } return null; }; this.lastChild = function () { var node = this.currentNode; if (!this['[HasRoot]'](node)) { return null; } for (node = node.lastChild; node; node = node.previousSibling) { switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: this.currentNode = node; return node; case DOMNodeFilter.FILTER_SKIP: case DOMNodeFilter.FILTER_REJECT: break; default: throw new Error; } } return null; }; this.previousSibling = function () { var node = this.currentNode; if (!this['[HasRoot]'](node)) { return null; } while ((node = node.previousSibling)) { switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: this.currentNode = node; return node; case DOMNodeFilter.FILTER_SKIP: case DOMNodeFilter.FILTER_REJECT: break; default: throw new Error; } } return null; }; this.nextSibling = function () { var node = this.currentNode; if (!this['[HasRoot]'](node)) { return null; } while ((node = node.nextSibling)) { switch (this['[CallFilter]'](node)) { case DOMNodeFilter.FILTER_ACCEPT: this.currentNode = node; return node; case DOMNodeFilter.FILTER_SKIP: case DOMNodeFilter.FILTER_REJECT: break; default: throw new Error; } } return null; }; }).call(DOMTreeWalker.prototype); var as_DOM_DocumentTraversal_createTreeWalker = function (doc, root, whatToShow, filter, entityReferenceExpansion) { if (root == null) { // or undefined throw new Error; // DOMException.NOT_SUPPORTED_ERR(9) } var tw = new DOMTreeWalker({ root: root, whatToShow: whatToShow, filter: filter, expandEntityReference: entityReferenceExpansion, currentNode: root }); return tw; };
- 初出 2011-08-24