312 lines
9.8 KiB
JavaScript
312 lines
9.8 KiB
JavaScript
/*jslint vars: true, white: true, plusplus: true, maxerr: 50, indent: 4 */
|
|
|
|
|
|
|
|
// Offers functional views, traversals of the DOM and other tree-like structures.
|
|
// See Functional Pearl: The Zipper, by G\'erard Huet
|
|
// J. Functional Programming 7 (5): 549--554 Sepember 1997
|
|
|
|
|
|
var TreeCursor = (function() {
|
|
"use strict";
|
|
|
|
var TreeCursor = function(parent, node, prevs, nexts, openF, closeF, atomicF) {
|
|
this.parent = parent; // Parent can be the top (undefined), or a TreeCursor
|
|
this.node = node;
|
|
this.prevs = prevs;
|
|
this.nexts = nexts;
|
|
|
|
// openF: node -> (arrayof node)
|
|
this.openF = openF;
|
|
|
|
// closeF: node (arrayof node) -> node
|
|
// Given a node and its array of children, return a new node.
|
|
this.closeF = closeF;
|
|
|
|
// atomicF: node -> boolean
|
|
// Produces true if the node should be treated atomically.
|
|
this.atomicF = atomicF;
|
|
};
|
|
|
|
TreeCursor.prototype.isOnAtomicElement = function() {
|
|
return this.atomicF(this.node);
|
|
};
|
|
|
|
TreeCursor.prototype.canDown = function() {
|
|
return (!(this.atomicF(this.node)) &&
|
|
this.openF(this.node).length !== 0);
|
|
};
|
|
|
|
TreeCursor.prototype.down = function() {
|
|
if (this.atomicF(this.node)) {
|
|
throw new Error("down of atomic element");
|
|
}
|
|
var opened = this.openF(this.node);
|
|
if (opened.length === 0) {
|
|
throw new Error("down of empty");
|
|
}
|
|
return new TreeCursor(this,
|
|
opened[0],
|
|
[],
|
|
opened.slice(1),
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
|
|
TreeCursor.prototype.canUp = function() {
|
|
return this.parent !== undefined;
|
|
};
|
|
|
|
TreeCursor.prototype.up = function() {
|
|
var parent = this.parent;
|
|
return new TreeCursor(parent.parent,
|
|
this.closeF(parent.node,
|
|
this.prevs.concat([this.node]).concat(this.nexts)),
|
|
parent.prevs,
|
|
parent.nexts,
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
TreeCursor.prototype.canLeft = function() { return this.prevs.length !== 0; };
|
|
|
|
TreeCursor.prototype.left = function() {
|
|
if (this.prevs.length === 0) { throw new Error("left of first"); }
|
|
return new TreeCursor(this.parent,
|
|
this.prevs[this.prevs.length - 1],
|
|
this.prevs.slice(0, this.prevs.length - 1),
|
|
[this.node].concat(this.nexts),
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
TreeCursor.prototype.canRight = function() { return this.nexts.length !== 0; };
|
|
|
|
TreeCursor.prototype.right = function() {
|
|
if (this.nexts.length === 0) { throw new Error("right of last"); }
|
|
return new TreeCursor(this.parent,
|
|
this.nexts[0],
|
|
this.prevs.concat([this.node]),
|
|
this.nexts.slice(1),
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
TreeCursor.prototype.succ = function() {
|
|
var n;
|
|
if (this.canDown()) {
|
|
return this.down();
|
|
} else if (this.canRight()) {
|
|
return this.right();
|
|
} else {
|
|
n = this;
|
|
while (true) {
|
|
n = n.up();
|
|
if (n.canRight()) {
|
|
return n.right();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
TreeCursor.prototype.pred = function() {
|
|
var n;
|
|
if (this.canLeft()) {
|
|
n = this.left();
|
|
while (n.canDown()) {
|
|
n = n.down();
|
|
while (n.canRight()) {
|
|
n = n.right();
|
|
}
|
|
}
|
|
return n;
|
|
} else {
|
|
return this.up();
|
|
}
|
|
};
|
|
|
|
TreeCursor.prototype.canPred = function() {
|
|
return this.canLeft() || this.canUp();
|
|
};
|
|
|
|
TreeCursor.prototype.canSucc = function() {
|
|
var n;
|
|
if (this.canDown()) {
|
|
return true;
|
|
} else if (this.canRight()) {
|
|
return true;
|
|
} else {
|
|
n = this;
|
|
while (true) {
|
|
if (! n.canUp()) { return false; }
|
|
n = n.up();
|
|
if (n.canRight()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
TreeCursor.prototype.top = function() {
|
|
var n = this;
|
|
while (n.canUp()) { n = n.up(); }
|
|
return n;
|
|
};
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
TreeCursor.prototype.replaceNode = function(n) {
|
|
return new TreeCursor(this.parent,
|
|
n,
|
|
this.prevs,
|
|
this.nexts,
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
TreeCursor.prototype.insertRight = function(n) {
|
|
return new TreeCursor(this.parent,
|
|
n,
|
|
this.prevs.concat([this.node]),
|
|
this.nexts,
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
TreeCursor.prototype.insertLeft = function(n) {
|
|
return new TreeCursor(this.parent,
|
|
n,
|
|
this.prevs,
|
|
[this.node].concat(this.nexts),
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
TreeCursor.prototype.insertDown = function(n) {
|
|
if (this.atomicF(this.node)) {
|
|
throw new Error("down of atomic element");
|
|
}
|
|
return new TreeCursor(this,
|
|
n,
|
|
[],
|
|
this.openF(this.node),
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
};
|
|
|
|
TreeCursor.prototype.deleteNode = function() {
|
|
var parent;
|
|
if (this.nexts.length !== 0) {
|
|
return new TreeCursor(this.parent,
|
|
this.nexts[0],
|
|
this.prevs,
|
|
this.nexts.slice(1),
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
} else if (this.prevs.length !== 0) {
|
|
return new TreeCursor(this.parent,
|
|
this.prevs[this.prevs.length - 1],
|
|
this.prevs.slice(0, this.prevs.length - 1),
|
|
this.nexts,
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
} else {
|
|
parent = this.parent;
|
|
return new TreeCursor(parent.parent,
|
|
this.closeF(parent.node, []),
|
|
parent.prevs,
|
|
parent.nexts,
|
|
this.openF,
|
|
this.closeF,
|
|
this.atomicF);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
TreeCursor.adaptTreeCursor = function(node, openF, closeF, atomicF) {
|
|
return new TreeCursor(undefined,
|
|
node,
|
|
[],
|
|
[],
|
|
openF,
|
|
closeF,
|
|
atomicF);
|
|
};
|
|
|
|
|
|
TreeCursor.arrayToCursor = function(anArray) {
|
|
var arrayOpenF = function(n) {
|
|
if (n instanceof Array) {
|
|
return n;
|
|
} else {
|
|
return [];
|
|
}
|
|
};
|
|
var arrayCloseF = function(n, children) {
|
|
if (n instanceof Array) {
|
|
return children;
|
|
} else {
|
|
return n;
|
|
}
|
|
};
|
|
|
|
var arrayAtomicF = function(n) {
|
|
return !(n instanceof Array);
|
|
};
|
|
return TreeCursor.adaptTreeCursor(anArray,
|
|
arrayOpenF,
|
|
arrayCloseF,
|
|
arrayAtomicF);
|
|
};
|
|
|
|
|
|
TreeCursor.domToCursor = function(dom) {
|
|
var domOpenF =
|
|
// To go down, just take the children.
|
|
function(n) {
|
|
return [].slice.call(n.childNodes, 0);
|
|
};
|
|
var domCloseF =
|
|
// To go back up, take the node, do a shallow cloning, and replace the children.
|
|
function(node, children) {
|
|
var i;
|
|
var newNode = node.cloneNode(false);
|
|
for (i = 0; i < children.length; i++) {
|
|
newNode.appendChild(children[i].cloneNode(true));
|
|
}
|
|
return newNode;
|
|
};
|
|
var domAtomicF =
|
|
function(node) {
|
|
return node.nodeType !== 1;
|
|
};
|
|
return TreeCursor.adaptTreeCursor(dom.cloneNode(true),
|
|
domOpenF,
|
|
domCloseF,
|
|
domAtomicF);
|
|
};
|
|
|
|
|
|
return TreeCursor;
|
|
}()); |