openrosa-xpath-evaluator
Version:
Wrapper for browsers' XPath evaluator with added support for OpenRosa extensions.
281 lines (251 loc) • 7.86 kB
JavaScript
const { assert } = require('chai');
const OpenRosaXpath = require('../../src/openrosa-xpath');
const { toDbgString } = require('../dbg');
let xEval;
const nsResolver = {
lookupNamespaceURI: (prefix) => {
const ns = {
xhtml: 'http://www.w3.org/1999/xhtml',
mathml: 'http://www.w3.org/1998/Math/MathML',
jr: 'http://openrosa.org/javarosa',
};
return ns[prefix];
},
};
const initDoc = (xml, nsr) => {
const doc = new DOMParser().parseFromString(xml, 'application/xml');
const evaluator = OpenRosaXpath();
xEval = function (e, xnode, xrt) {
const node = xnode || doc;
const rt = xrt || XPathResult.ANY_TYPE;
return evaluator.evaluate(e, node, nsr, rt);
};
doc.evaluate = evaluator.evaluate;
doc.xEval = xEval;
return doc;
};
const simpleValueIs = (textValue) =>
initDoc(`<simple><xpath><to>
<node>${textValue}</node>
</to></xpath><empty/></simple>`);
const initBasicXmlDoc = () => simpleValueIs('');
const assertTrue = (...args) => {
const regex = args[args.length - 1];
if (args.length > 1 && args[args.length - 2]) {
simpleValueIs(args[args.length - 2]);
}
const node = args.length > 2 ? args[args.length - 3] : null;
assert.isTrue(xEval(regex, node, XPathResult.BOOLEAN_TYPE).booleanValue);
};
const assertFalse = (...args) => {
const regex = args[args.length - 1];
if (args.length > 1 && args[args.length - 2]) {
simpleValueIs(args[args.length - 2]);
}
const node = args.length > 2 ? args[args.length - 3] : null;
assert.isFalse(xEval(regex, node, XPathResult.BOOLEAN_TYPE).booleanValue);
};
const assertBoolean = (...args) => {
const value = args.pop();
if (value) {
assertTrue(...args);
} else {
assertFalse(...args);
}
};
const assertString = (...args) => {
const expected = args[args.length - 1];
const regex = args[args.length - 2];
if (args.length > 2 && args[args.length - 3]) {
simpleValueIs(args[args.length - 3]);
}
const node = args.length > 3 ? args[args.length - 4] : null;
assert.equal(
xEval(regex, node, XPathResult.STRING_TYPE).stringValue,
expected
);
};
const assertStringValue = (...args) => {
const expected = args[args.length - 1];
const regex = args[args.length - 2];
if (args.length > 2 && args[args.length - 3]) {
simpleValueIs(args[args.length - 3]);
}
const node = args.length > 3 ? args[args.length - 4] : null;
assert.equal(
xEval(regex, node, XPathResult.STRING_TYPE).stringValue,
expected
);
};
const assertStringLength = (...args) => {
const expected = args[args.length - 1];
const regex = args[args.length - 2];
if (args.length > 2 && args[args.length - 3]) {
simpleValueIs(args[args.length - 3]);
}
const node = args.length > 3 ? args[args.length - 4] : null;
assert.equal(
xEval(regex, node, XPathResult.STRING_TYPE).stringValue.length,
expected
);
};
const assertMatch = (...args) => {
const expected = args[args.length - 1];
const regex = args[args.length - 2];
if (args.length > 2 && args[args.length - 3]) {
simpleValueIs(args[args.length - 3]);
}
const node = args.length > 3 ? args[args.length - 4] : null;
assert.match(
xEval(regex, node, XPathResult.STRING_TYPE).stringValue,
expected
);
};
const assertNumber = (...args) => {
const expected = args[args.length - 1];
const regex = args[args.length - 2];
if (args.length > 2 && args[args.length - 3]) {
simpleValueIs(args[args.length - 3]);
}
const node = args.length > 3 ? args[args.length - 4] : null;
const actual = xEval(regex, node).numberValue;
if (Number.isNaN(Number(expected))) {
assert.isNaN(actual);
} else {
assert.equal(actual, expected);
}
};
const assertNumberValue = (...args) => {
const expected = args[args.length - 1];
const regex = args[args.length - 2];
if (args.length > 2 && args[args.length - 3]) {
simpleValueIs(args[args.length - 3]);
}
const node = args.length > 3 ? args[args.length - 4] : null;
const actual = xEval(regex, node, XPathResult.NUMBER_TYPE).numberValue;
if (Number.isNaN(Number(expected))) {
assert.isNaN(actual);
} else {
assert.equal(actual, expected);
}
};
beforeEach(() => {
initBasicXmlDoc();
});
const getNextChildElementNode = (parentNode) => {
let childNode = parentNode.firstChild;
while (childNode.nodeName === '#text') {
childNode = childNode.nextSibling;
}
return childNode;
};
const setAttribute = (node, namespace, name, value) => {
if (node.setAttributeNS) {
// for XML documents
node.setAttributeNS(namespace, name, value);
} else {
// normal HTML documents
node.setAttribute(name, value);
}
};
const getAllNodes = (node) => {
const nodes = [node];
let i;
for (i = 0; i < node.childNodes.length; i++) {
nodes.push(...getAllNodes(node.childNodes.item(i)));
}
return nodes;
};
const filterAttributes = (attributes) => {
let i;
let name;
const specifiedAttributes = [];
for (i = 0; i < attributes.length; i++) {
name = attributes[i].nodeName.split(':');
if (name[0] === 'xmlns') {
// ignore namespaces
continue;
}
specifiedAttributes.push(attributes[i]);
}
return specifiedAttributes;
};
const assertNodes = (expr, node, expected, nsr) => {
const result = xEval(
expr,
node,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
nsr
);
assert.equal(result.snapshotLength, expected.length);
for (let j = 0; j < result.snapshotLength; j++) {
const item = result.snapshotItem(j);
assert.equal(
item,
expected[j],
`expected: ${toDbgString(expected[j])}, got: ${toDbgString(item)}`
);
}
};
const sorted = (nodes) =>
nodes.sort((a, b) => {
if (a.nodeName > b.nodeName) return 1;
if (a.nodeName < b.nodeName) return -1;
return 0;
});
const snapshotItems = (result) => {
const all = [];
for (let j = 0; j < result.snapshotLength; j++) {
all.push(result.snapshotItem(j));
}
return all;
};
// Compares nodes and ignores node and attribute order
const assertUnorderedNodes = (expr, node, expected) => {
const result = xEval(expr, node, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
assert.equal(result.snapshotLength, expected.length);
const resultNodes = sorted(snapshotItems(result));
const expectedNodes = sorted(expected);
for (let j = 0; j < resultNodes.length; j++) {
assert.equal(resultNodes[j].nodeName, expectedNodes[j].nodeName);
}
};
const snapshotToArray = (result) => {
let i;
const nodes = [];
for (i = 0; i < result.snapshotLength; i++) {
nodes.push(result.snapshotItem(i));
}
return nodes;
};
const assertThrow = (expr) => {
assert.throw(() => xEval(expr), Error);
};
const assertNumberRounded = (expr, expected, factor, node) => {
const val = xEval(expr, node, XPathResult.NUMBER_TYPE).numberValue;
assert.equal(Math.round(val * factor) / factor, expected);
};
module.exports = {
initDoc,
nsResolver,
simpleValueIs,
filterAttributes,
getNextChildElementNode,
getAllNodes,
snapshotToArray,
setAttribute,
assert,
assertThrow,
assertNumberValue,
assertNumberRounded,
assertNumber,
assertString,
assertStringValue,
assertStringLength,
assertMatch,
assertBoolean,
assertFalse,
assertTrue,
assertNodes,
assertUnorderedNodes,
};