How can I rewrite this function with tail recursion - javascript

I wrote the following function to linearize the text content of an arbitrary string of HTML:
html2text(html) {
function _html2text(element, accum) {
return Array.prototype.slice.call(element.childNodes).reduce((accum, node) => {
return (node.nodeType === 3)
? `${accum} ${node.textContent}`
: _html2text(node, accum);
}, accum);
}
const div = document.createElement('div');
div.innerHTML = html;
return _html2text(div, '');
}
But now I am not able to transform it into a trail-recusive style so that it can be optimized.
My problem is that the recursion recurs within the reduction.
I will write this out as loops, but it's been killing me that I've not been able tail recurse this.
Here is my loop version:
function html2text(html) {
var div = document.createElement('div');
div.innerHTML = html;
var accum = '';
var stack = [];
stack.push([div, 0]);
while (stack.length !== 0) {
var frame = stack.pop();
var el = frame[0];
var i = frame[1];
for (; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
stack.push([el, i+1]);
stack.push([node, 0]);
break;
} else if (node.nodeType === Node.TEXT_NODE) {
accum += ' ' + node.textContent;
}
}
}
return accum;
}
Here's the function above written with a recursive tail call, passing the stack:
function html2text(html) {
function recurse(stack, accum) {
if (!stack.length) {
return accum;
}
var frame = stack.pop();
var el = frame[0];
var i = frame[1];
for (; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
stack.push([el, i+1]);
stack.push([node, 0]);
break;
} else if (node.nodeType === Node.TEXT_NODE) {
accum += ' ' + node.textContent;
}
}
return recurse(stack, accum)
}
var div = document.createElement('div');
div.innerHTML = html;
var stack = [];
stack.push([div, 0]);
return recurse(stack, '');
}
The inner loop can be converted to another recursion, but without using immutable datastructures none of this really makes too much sense for me.

function html2text(current, text = "") {
"use strict";
if (current === null) return text;
var nextNode = current.nextSibling || current.parentNode.nextSibling,
more = current.nodeType === 3 ? current.textContent : "";
return html2text(nextNode, text + more);
}
You were right, you can do the concatenation before the tail call. I think this fits all the criteria, but ES6 is new, so please do double check.

Related

Get XPATHs of all elements of an HTML/DOM

Preferably in Javascript.
[Maybe] iterate through all the nodes and console.log() its XPath.
I searched enough and could not find an answer.
Why I need it?
I want to see the coordinates(getBoundingClientRect()) of all leaf nodes of a DOM (means, elements of an HTML).
A combination of How to loop through ALL DOM elements on a page and how do I get the XPath of an element in an X/HTML file
should do:
function getXPath(node) {
var comp, comps = [];
var parent = null;
var xpath = '';
var getPos = function(node) {
var position = 1, curNode;
if (node.nodeType == Node.ATTRIBUTE_NODE) {
return null;
}
for (curNode = node.previousSibling; curNode; curNode = curNode.previousSibling) {
if (curNode.nodeName == node.nodeName) {
++position;
}
}
return position;
}
if (node instanceof Document) {
return '/';
}
for (; node && !(node instanceof Document); node = node.nodeType == Node.ATTRIBUTE_NODE ? node.ownerElement : node.parentNode) {
comp = comps[comps.length] = {};
switch (node.nodeType) {
case Node.TEXT_NODE:
comp.name = 'text()';
break;
case Node.ATTRIBUTE_NODE:
comp.name = '#' + node.nodeName;
break;
case Node.PROCESSING_INSTRUCTION_NODE:
comp.name = 'processing-instruction()';
break;
case Node.COMMENT_NODE:
comp.name = 'comment()';
break;
case Node.ELEMENT_NODE:
comp.name = node.nodeName;
break;
}
comp.position = getPos(node);
}
for (var i = comps.length - 1; i >= 0; i--) {
comp = comps[i];
xpath += '/' + comp.name;
if (comp.position != null) {
xpath += '[' + comp.position + ']';
}
}
return xpath;
}
var all = document.getElementsByTagName("*");
for (var i=0, max=all.length; i < max; i++) {
console.log(getXPath(all[i]));
}
The updated version works much better than the first installment.

How to implement lambda /anonymous functions in JavaScript

So I am trying to implement a subset of LISP using JavaScript. I am stuck on two things related to lambdas.
How to implement the ability to create a lambda and at the same time feed it the arguments and have it immediately evaluated? For example:
((lambda(x)(* x 2)) 3)
For now I hard-coded this functionality in my eval-loop like this:
else if (isArray(expr)){
if (expr[0][0] === 'lambda' || expr[0][0] === 'string') {
console.log("This is a special lambda");
var lambdaFunc = evaluate(expr[0], env)
var lambdaArgs = [];
for(var i = 1; i < expr.length; i++){
lambdaArgs.push(expr[i]);
}
return lambdaFunc.apply(this, lambdaArgs);
}
Now this works, and if I write the above lambda with the parameter it will evaluate to 6, however, I am wondering if there is any smarter way to implement this?
If a lambda is instead bound to a symbol, for example:
(define fib (lambda(n)
(if (< n 2) 1
(+ (fib (- n 1))(fib (- n 2)))
)))
In this case, the (define fib) part will be evaluated by the eval-loop first, just as if fib was simply being assigned a number:
else if (expr[0] === 'define') { // (define var value)
console.log(expr + " is a define statement");
var newVar = expr[1];
var newVal = evaluate(expr[2], env);
env.add(newVar, newVal);
return env;
}
And the lambda-function is being created like this:
else if (expr[0] === 'lambda') { // (lambda args body)
console.log(expr + " is a lambda statement");
var args = expr[1];
var body = expr[2];
return createLambda(args, body, env);
}
Separate function to create the lambda:
function createLambda(args, body, env){ // lambda args body
function runLambda(){
var lambdaEnvironment = new environment(env, "lambda environment");
for (var i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], evaluate(arguments[i], env));
}
return evaluate(body, lambdaEnvironment);
}
return runLambda;
}
This works fine for lambdas such as:
(define range (lambda (a b)
(if (= a b) (quote ())
(cons a (range (+ a 1) b)))))
(define fact (lambda (n)
(if (<= n 1) 1
(* n (fact (- n 1))))))
For example, (range 0 10) returns a list from 0 to 10.
But if I try a lambda within a lambda, it does not work. For example:
(define twice (lambda (x) (* 2 x)))
(define repeat (lambda (f) (lambda (x) (f (f x)))))
I would expect the following to return 40:
((repeat twice) 10)
But instead, it returns a list looking like this:
function runLambda(){ var lambdaEnvironment = new environment(env, "lambda
environment"); for (var i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], evaluate(arguments[i], env)); } return
evaluate(body, lambdaEnvironment); },10
Any ideas what might be missing here?
Full JavaScript;
//functions for parsing invoice String
function parse(exp) {
return readFromTokes(tokenize(exp));//code
}
function isNumeric(arg){
return !isNaN(arg);
}
function isArray(obj){
return !!obj && obj.constructor === Array;
}
function readFromTokes(exp){
//Create abstract syntax tree
if (exp.length == 0) {
}
var token = exp.shift();
if (token == '('){
var L = [];
while (exp[0] != ')') {
L.push(readFromTokes(exp));
}
exp.shift(); //remove end paranthesis
return L;
} else {
if (token == ')') {
console.log("Unexpected )");
} else {
return atom(token);
}
}
}
function tokenize(exp){
//Convert a program in form of a string into an array (list)
var re = /\(/g;
var re2 = /\)/g;
exp = exp.replace(re, " ( ");
exp = exp.replace(re2, " ) ");
exp = exp.replace(/\s+/g, ' ');
exp = exp.trim().split(" ");
return exp;
}
function atom(exp){
if (isNumeric(exp)) {
return parseInt(exp); //A number is a number
} else {
return exp; //Everything else is a symbol
}
}
function environment(parentEnvironment, name){
var bindings = [];
var parent = parentEnvironment;
var name = name;
function add(variable, value){
console.log("variable: " + variable + " value: " + value);
bindings.push([variable, value]);
}
function printName(){
console.log(name);
}
function print() {
console.log("printing environment: ")
for (var i = 0; i < bindings.length; i++){
console.log(bindings[i][0] + " " + bindings[i][1]);
}
}
function get(variable){
for (var i = 0; i < bindings.length; i++){
if (variable == bindings[i][0]){
return bindings[i][1];
}
}
if (parent != null){
return parent.get(variable);
} else {
console.log("No such variable");
return false;
}
}
function getParent(){
return parent;
}
this.add = add;
this.get = get;
this.getParent = getParent;
this.print = print;
this.printName = printName;
return this;
}
function addPrimitives(env){
env.add("+", function() {var s = 0; for (var i = 0; i<arguments.length;i++){ s += arguments[i];} return s});
env.add("-", function() {var s = arguments[0]; for (var i = 1; i<arguments.length;i++){ s -= arguments[i];} return s});
env.add("*", function() {var s = 1; for (var i = 0; i<arguments.length;i++){ s *= arguments[i];} return s});
env.add("/", function(x, y) { return x / y });
env.add("false", false);
env.add("true", true);
env.add(">", function(x, y){ return (x > y) });
env.add("<", function(x, y){ return (x < y) });
env.add("=", function(x, y){ return (x === y)});
env.add(">=", function(x, y){ if (x >= y){return true;} else {return false;}});
env.add("<=", function(x, y){ if (x <= y){return true;} else {return false;}});
env.add("eq?", function() {var s = arguments[0]; var t = true; for(var i = 1; i<arguments.length; i++){ if (arguments[i] != s) {t = false }} return t;});
env.add("cons", function(x, y) { var temp = [x]; return temp.concat(y); });
env.add("car", function(x) { return x[0]; });
env.add("cdr", function(x) { return x.slice(1); });
env.add("list", function () { return Array.prototype.slice.call(arguments); });
env.add("list?", function(x) {return isArray(x); });
env.add("null", null);
env.add("null?", function (x) { return (!x || x.length === 0); });
}
function createLambda(args, body, env){ // lambda args body
function runLambda(){
var lambdaEnvironment = new environment(env, "lambda environment");
for (var i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], evaluate(arguments[i], env));
}
return evaluate(body, lambdaEnvironment);
}
return runLambda;
}
function evaluate(expr, env) {
console.log(expr + " has entered evaluate loop");
if (typeof expr === 'string') {
console.log(expr + " is a symbol");
return env.get(expr);
} else if (typeof expr === 'number') {
console.log(expr + " is a number");
return expr;
} else if (expr[0] === 'define') { // (define var value)
console.log(expr + " is a define statement");
var newVar = expr[1];
var newVal = evaluate(expr[2], env);
env.add(newVar, newVal);
return env;
} else if (expr[0] === 'lambda') { // (lambda args body)
console.log(expr + " is a lambda statement");
var args = expr[1];
var body = expr[2];
return createLambda(args, body, env);
} else if (expr[0] === 'quote') {
return expr[1];
} else if (expr[0] === 'cond'){
console.log(expr + " is a conditional");
for (var i = 1; i < expr.length; i++){
var temp = expr[i];
if (evaluate(temp[0], env)) {
console.log(temp[0] + " is evaluated as true");
return evaluate(temp[1], env);
}
}
console.log("no case was evaluated as true");
return;
} else if (expr[0] === 'if') {
console.log(expr + "is an if case");
return function(test, caseyes, caseno, env){
if (test) {
return evaluate(caseyes, env);
} else {
return evaluate(caseno, env);
}
}(evaluate(expr[1], env), expr[2], expr[3], env);
} else if (typeof expr[0] === 'string'){
console.log(expr + " is a function call");
var lispFunc = env.get(expr[0]);
var lispFuncArgs = [];
for(var i = 1; i < expr.length; i++){
lispFuncArgs.push(evaluate(expr[i], env));
}
return lispFunc.apply(this, lispFuncArgs);
} else if (isArray(expr)){
if (expr[0][0] === 'lambda' || expr[0][0] === 'string') {
console.log("This is a special lambda");
var lambdaFunc = evaluate(expr[0], env)
var lambdaArgs= [];
for(var i = 1; i < expr.length; i++){
lambdaArgs.push(expr[i]);
}
return lambdaFunc.apply(this, lambdaArgs);
} else {
console.log(expr + " is a list");
var evaluatedList = [];
for(var i = 0; i < expr.length; i++){
evaluatedList.push(evaluate(expr[i], env));
}
return evaluatedList;
}
} else {
console.log(expr + " cannot be interpreted");
}
}
var globalEnvironment = new environment(null, "Global");
addPrimitives(globalEnvironment);
function start(string) {
return evaluate(parse(string), globalEnvironment);
}
var output = function (string) {
try {
document.getElementById('debugdiv').innerHTML = start(string);
} catch (e) {
document.getElementById('debugdiv').innerHTML = e.name + ': ' + e.message;
}
};
Full HTML;
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Script-Type" content="text/javascript">
<title>LISP in JavaScript</title>
<script type="text/javascript" src="lisp.js"></script>
</head>
<body>
<form id="repl" name="repl" action="parse(prompt.value)">
lisp==>
<input id="prompt" size="200" value="" name="prompt" maxlength="512">
<br>
<input type=button style="width:60px;height:30px" name="btnEval" value="eval" onclick="output(prompt.value)">
<br>
</form>
<div id="debugdiv" style="background-color:orange;width=100px;height=20px">
</div>
</body>
</html>
Further thoughts, suggestions and comments are of course also welcome.
Thank you!
You don't inspect the structure of the operand to see if it's a lambda you eval the operand. The standard way of eval is to check if it's primitive type, then check for special forms and macros, then eval the operator and operands before applying.
Just remove the part where expr[0][0] === 'lambda' || expr[0][0] === 'string' is true and instead of just returning the evaluation of the form you need to apply the operand:
else if (isArray(expr)){
const fn = evaluate(expr[0], env);
const evaluatedList = [];
for(var i = 1; i < expr.length; i++){
evaluatedList.push(evaluate(expr[i], env));
}
return fn(...evaluatedList);
}
The createLambda is wrong since you are evaluating the arguments in the wrong environment. This would be correct since the arguments are already evaluated:
function createLambda(args, body, env){ // lambda args body
function runLambda(){
const lambdaEnvironment = new environment(env, "lambda environment");
for (let i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], arguments[i]);
}
return evaluate(body, lambdaEnvironment);
}
return runLambda;
}

parsing xml to get the equation to be returned to python

Here is a javascript file which turns xml file to text.This text is often equation.I want this equation to be in such a way that the result of xml passed to python produce required result.any help is appreciable.
function getDOM(xmlstring) {
parser=new DOMParser();
return parser.parseFromString(xmlstring, "text/xml");
}
function remove_tags(node) {
var result = "";
var nodes = node.childNodes;
var tagName = node.tagName;
if (!nodes.length) {
if (node.nodeValue == "π") result = "pi";
else if (node.nodeValue == " ") result = "";
else result = node.nodeValue;
} else if (tagName == "mfrac") {
result = "("+remove_tags(nodes[0])+")/("+remove_tags(nodes[1])+")";
} else if (tagName == "msup") {
result = "Math.pow(("+remove_tags(nodes[0])+"),("+remove_tags(nodes[1])+"))";
} else for (var i = 0; i < nodes.length; ++i) {
result += remove_tags(nodes[i]);
}
if (tagName == "mfenced") result = "("+result+")";
if (tagName == "msqrt") result = "Math.sqrt("+result+")";
return result;
}
function stringifyMathML(mml) {
xmlDoc = getDOM(mml);
return remove_tags(xmlDoc.documentElement);
}
Example of xml file is
s = stringifyMathML(" <math><mi>sin</mi><mfenced><mi>x</mi></mfenced></math>");
alert(s);
alert(eval(s));
I am expecting output to be
math.sin(x)
Adding the specific .math part will solve the issue:
The "math." part must be added only when special keyword are present.
So first, build the potential operation you will need to cover in a list (mList)
Then, if you meet this operation, prepend it with ".math"
var mList = ['pow', 'sin', 'cos', 'pow', 'sqrt', 'π'];
function getDOM(xmlstring) {
parser=new DOMParser();
return parser.parseFromString(xmlstring, "text/xml");
}
function remove_tags(node) {
var result = "";
var nodes = node.childNodes;
var tagName = node.tagName;
if (!nodes.length) {
if(mList.indexOf(node.nodeValue) != -1 ) {
result += 'math.'
}
if (node.nodeValue == "π") result += "pi";
else if (node.nodeValue == " ") result += "";
else result += node.nodeValue;
} else if (tagName == "mfrac") {
result += "("+remove_tags(nodes[0])+")/("+remove_tags(nodes[1])+")";
} else if (tagName == "msup") {
result += "pow(("+remove_tags(nodes[0])+"),("+remove_tags(nodes[1])+"))";
} else for (var i = 0; i < nodes.length; ++i) {
result += remove_tags(nodes[i]);
}
if (tagName == "mfenced") result = "("+result+")";
if (tagName == "msqrt") result = "sqrt("+result+")";
console.log('returning', result)
return result;
}
function stringifyMathML(mml) {
xmlDoc = getDOM(mml);
return remove_tags(xmlDoc.documentElement);
}
a = stringifyMathML("<math><mi>x</mi></math>");
b = stringifyMathML("<math><mi>x</mi><mo>+</mo><mn>5</mn></math> ");
c = stringifyMathML("<math><mi>sin</mi><mfenced><mi>x</mi></mfenced></math> ");
console.log(a, 'vs x');
console.log(b, 'vs x+5');
console.log(c, 'vs math.sin(x)');
Output
x vs x
x+5 vs x+5
math.sin(x) vs math.sin(x)

Getting Error: Could not convert JavaScript argument arg 0 [nsIDOMWindow.getComputedStyle]

I have a bit of javascript code to find and replace text into an image. I then gather the font size of the original text and use that to set the size of the new image.
Problem is, I keep getting the error: Could not convert JavaScript argument arg 0 [nsIDOMWindow.getComputedStyle]
Code:
function findAndReplace(searchText, replacement, searchNode) {
if (!searchText || typeof replacement === 'undefined') {
// Throw error here if you want...
return;
}
var regex = typeof searchText === 'string' ?
new RegExp(searchText, 'g') : searchText,
childNodes = (searchNode || $("body").get(0)).childNodes,
excludes = 'html,head,style,title,link,meta,script,object,iframe';
var cnLength = childNodes.length;
while (cnLength--) {
var currentNode = childNodes[cnLength];
if (currentNode.nodeType === 1 &&
(excludes + ',').indexOf(currentNode.nodeName.toLowerCase() + ',') === -1) {
arguments.callee(searchText, replacement, currentNode);
}
if (currentNode.nodeType !== 3 || !regex.test(currentNode.data) ) {
continue;
}
var parent = currentNode.parentNode;
var frag = (function(){
var html = currentNode.data.replace(regex, replacement);
var wrap = document.createElement('div');
var frag = document.createDocumentFragment();
wrap.innerHTML = html;
while (wrap.firstChild) {
frag.appendChild(wrap.firstChild);
}
console.log(currentNode);
var jQNode = $(currentNode);
console.log("yay");
// var fontSize = jQNode.css('font-size');
if (!currentNode || currentNode == document) currentNode = document.body
var fontSize = getStyle(currentNode, 'font-size');
console.log("tast");
var heightPixels = fontSizeToPixels(fontSize);
$(".InLogo",frag).each(function(){
$(this).css("height", heightPixels+"px");
});
return frag;
})();
parent.insertBefore(frag, currentNode);
parent.removeChild(currentNode);
}
}
function getStyle(el,styleProp) {
var camelize = function (str) {
return str.replace(/\-(\w)/g, function(str, letter){
return letter.toUpperCase();
});
};
if (el.currentStyle) {
return el.currentStyle[camelize(styleProp)];
} else if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(el,null)
.getPropertyValue(styleProp);
} else {
return el.style[camelize(styleProp)];
}
}
The error occurs at this line return document.defaultView.getComputedStyle(el,null).getPropertyValue(styleProp); of getStyle()
something.childNodes includes textNodes as well as Elements, and that's a problem for the getStyle() function.
Nodes don't have a style (Elements do), so who knows what will happen when you feed getStyle something that has .data; a plain Node.
Check for the existence of style to avoid the run-time error:
FIX:
var fontSize = currentNode.style ? getStyle(currentNode, 'font-size') : 0;

cross browser compare document position

DOM4 compareDocumentPosition
I want to implement compareDocumentPosition. Resig has made a great start at doing just this. I've taken his code and neatened it up
function compareDocumentPosition(other) {
var ret = 0;
if (this.contains) {
if (this !== other && this.contains(other)) {
ret += 16;
}
if (this !== other && other.contains(this)) {
ret += 8;
}
if (this.sourceIndex >= 0 && other.sourceIndex >= 0) {
if (this.sourceIndex < other.sourceIndex) {
ret += 4;
}
if (this.sourceIndex > other.sourceIndex) {
ret += 2;
}
} else {
ret += 1;
}
}
return ret;
}
This works for Element but does not for Text or DocumentFragment. This is because IE8 does not give .sourceIndex on those nodes. (It doesn't give .contains either but I've fixed that problem already)
How do I efficiently write the +=4 and +=2 bit which correspond to DOCUMENT_POSITION_FOLLOWING and DOCUMENT_POSITION_PRECEDING.
For extra reference those two are defined by tree-order which DOM4 defines as
An object A is preceding an object B if A and B are in the same tree and A comes before B in tree order.
An object A is following an object B if A and B are in the same tree and A comes after B in tree order.
The tree order is preorder, depth-first traversal.
Most modern browsers implement this (including IE9). So you only need something that works in IE8 (I don't care about IE6/7, but if it works awesome!)
function recursivelyWalk(nodes, cb) {
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var ret = cb(node);
if (ret) {
return ret;
}
if (node.childNodes && node.childNodes.length) {
var ret = recursivelyWalk(node.childNodes, cb);
if (ret) {
return ret;
}
}
}
}
function testNodeForComparePosition(node, other) {
if (node === other) {
return true;
}
}
function compareDocumentPosition(other) {
function identifyWhichIsFirst(node) {
if (node === other) {
return "other";
} else if (node === reference) {
return "reference";
}
}
var reference = this,
referenceTop = this,
otherTop = other;
if (this === other) {
return 0;
}
while (referenceTop.parentNode) {
referenceTop = referenceTop.parentNode;
}
while (otherTop.parentNode) {
otherTop = otherTop.parentNode;
}
if (referenceTop !== otherTop) {
return Node.DOCUMENT_POSITION_DISCONNECTED;
}
var children = reference.childNodes;
var ret = recursivelyWalk(
children,
testNodeForComparePosition.bind(null, other)
);
if (ret) {
return Node.DOCUMENT_POSITION_CONTAINED_BY +
Node.DOCUMENT_POSITION_FOLLOWING;
}
var children = other.childNodes;
var ret = recursivelyWalk(
children,
testNodeForComparePosition.bind(null, reference)
);
if (ret) {
return Node.DOCUMENT_POSITION_CONTAINS +
Node.DOCUMENT_POSITION_PRECEDING;
}
var ret = recursivelyWalk(
[referenceTop],
identifyWhichIsFirst
);
if (ret === "other") {
return Node.DOCUMENT_POSITION_PRECEDING;
} else {
return Node.DOCUMENT_POSITION_FOLLOWING;
}
}
I wrote it myself. I thought this implementation was bugged but it was a bug in some other code of mine. Seems pretty solid.
The answer from Raynos is a top start, but is not runnable out of the box. Node.* cannot be found and .bind is not available in IE8.
Here is the code ready for use in Internet Explorer 8:
function recursivelyWalk(nodes, cb) {
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var ret = cb(node);
if (ret) {
return ret;
}
if (node.childNodes && node.childNodes.length) {
var ret = recursivelyWalk(node.childNodes, cb);
if (ret) {
return ret;
}
}
}
}
function testNodeForComparePosition(node, other) {
if (node === other) {
return true;
}
}
var DOCUMENT_POSITION_DISCONNECTED = 1;
var DOCUMENT_POSITION_PRECEDING = 2;
var DOCUMENT_POSITION_FOLLOWING = 4;
var DOCUMENT_POSITION_CONTAINS = 8;
var DOCUMENT_POSITION_CONTAINED_BY = 16;
function compareDocumentPosition(thisNode, other) {
function identifyWhichIsFirst(node) {
if (node === other) {
return "other";
} else if (node === reference) {
return "reference";
}
}
var reference = thisNode,
referenceTop = thisNode,
otherTop = other;
if (this === other) {
return 0;
}
while (referenceTop.parentNode) {
referenceTop = referenceTop.parentNode;
}
while (otherTop.parentNode) {
otherTop = otherTop.parentNode;
}
if (referenceTop !== otherTop) {
return DOCUMENT_POSITION_DISCONNECTED;
}
var children = reference.childNodes;
var ret = recursivelyWalk(
children,
function(p) {
(function() {
var localOther = other;
return testNodeForComparePosition(localOther, p);
})();
}
);
if (ret) {
return DOCUMENT_POSITION_CONTAINED_BY +
DOCUMENT_POSITION_FOLLOWING;
}
var children = other.childNodes;
var ret = recursivelyWalk(
children,
function(p) {
(function() {
var localOther = reference;
return testNodeForComparePosition(localOther, p);
})();
}
);
if (ret) {
return DOCUMENT_POSITION_CONTAINS +
DOCUMENT_POSITION_PRECEDING;
}
var ret = recursivelyWalk(
[referenceTop],
identifyWhichIsFirst
);
if (ret === "other") {
return DOCUMENT_POSITION_PRECEDING;
} else {
return DOCUMENT_POSITION_FOLLOWING;
}
}
You call it like this:
compareDocumentPosition(sourceElement, elementToTest)
(It's like calling sourceElement.compareDocumentPosition(elementToTest))

Categories