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