xpath-ts2
Version:
DOM 3 and 4 XPath 1.0 implementation for browser and Node.js environment with support for typescript 5.
632 lines • 17.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Operators = exports.XPathContext = exports.XNodeSet = exports.XString = exports.XNumber = exports.XBoolean = exports.Expression = void 0;
const avl_tree_1 = require("./avl-tree");
const types_1 = require("./utils/types");
// tslint:disable:prefer-for-of
// tslint:disable:member-ordering
class Expression {
toString() {
return '<Expression>';
}
evaluate(_c) {
throw new Error('Could not evaluate expression.');
}
get string() {
throw new Error('Could not evaluate expression.');
}
get number() {
throw new Error('Could not evaluate expression.');
}
get bool() {
throw new Error('Could not evaluate expression.');
}
get nodeset() {
throw new Error('Could not evaluate expression.');
}
get stringValue() {
throw new Error('Could not evaluate expression.');
}
get numberValue() {
throw new Error('Could not evaluate expression.');
}
get booleanValue() {
throw new Error('Could not evaluate expression.');
}
equals(_r) {
throw new Error('Could not evaluate expression.');
}
notequal(_r) {
throw new Error('Could not evaluate expression.');
}
lessthan(_r) {
throw new Error('Could not evaluate expression.');
}
greaterthan(_r) {
throw new Error('Could not evaluate expression.');
}
lessthanorequal(_r) {
throw new Error('Could not evaluate expression.');
}
greaterthanorequal(_r) {
throw new Error('Could not evaluate expression.');
}
}
exports.Expression = Expression;
class XBoolean extends Expression {
static TRUE = new XBoolean(true);
static FALSE = new XBoolean(false);
b;
constructor(b) {
super();
this.b = Boolean(b);
}
toString() {
return this.b.toString();
}
evaluate(_c) {
return this;
}
get string() {
return new XString(this.b);
}
get number() {
return new XNumber(this.b);
}
get bool() {
return this;
}
get nodeset() {
throw new Error('Cannot convert boolean to nodeset');
}
get stringValue() {
return this.string.stringValue;
}
get numberValue() {
return this.number.numberValue;
}
get booleanValue() {
return this.b;
}
not() {
return new XBoolean(!this.b);
}
equals(r) {
if (r instanceof XString || r instanceof XNumber) {
return this.equals(r.bool);
}
else if (r instanceof XNodeSet) {
return r.compareWithBoolean(this, Operators.equals);
}
else if (r instanceof XBoolean) {
return new XBoolean(this.b === r.b);
}
else {
throw new Error('Unsupported type');
}
}
notequal(r) {
if (r instanceof XString || r instanceof XNumber) {
return this.notequal(r.bool);
}
else if (r instanceof XNodeSet) {
return r.compareWithBoolean(this, Operators.notequal);
}
else if (r instanceof XBoolean) {
return new XBoolean(this.b !== r.b);
}
else {
throw new Error('Unsupported type');
}
}
lessthan(r) {
return this.number.lessthan(r);
}
greaterthan(r) {
return this.number.greaterthan(r);
}
lessthanorequal(r) {
return this.number.lessthanorequal(r);
}
greaterthanorequal(r) {
return this.number.greaterthanorequal(r);
}
}
exports.XBoolean = XBoolean;
class XNumber extends Expression {
num;
constructor(n) {
super();
this.num = typeof n === 'string' ? this.parse(n) : Number(n);
}
get numberFormat() {
return /^\s*-?[0-9]*\.?[0-9]+\s*$/;
}
parse(s) {
// XPath representation of numbers is more restrictive than what Number() or parseFloat() allow
return this.numberFormat.test(s) ? parseFloat(s) : Number.NaN;
}
toString() {
const strValue = this.num.toString();
if (strValue.indexOf('e-') !== -1) {
return padSmallNumber(strValue);
}
if (strValue.indexOf('e') !== -1) {
return padLargeNumber(strValue);
}
return strValue;
}
evaluate(_c) {
return this;
}
get string() {
return new XString(this.toString());
}
get number() {
return this;
}
get bool() {
return new XBoolean(this.num);
}
get nodeset() {
throw new Error('Cannot convert string to nodeset');
}
get stringValue() {
return this.string.stringValue;
}
get numberValue() {
return this.num;
}
get booleanValue() {
return this.bool.booleanValue;
}
negate() {
return new XNumber(-this.num);
}
equals(r) {
if (r instanceof XBoolean) {
return this.bool.equals(r);
}
else if (r instanceof XString) {
return this.string.equals(r);
}
else if (r instanceof XNodeSet) {
return r.compareWithNumber(this, Operators.equals);
}
else if (r instanceof XNumber) {
return new XBoolean(this.num === r.num);
}
else {
throw new Error('Unsupported type');
}
}
notequal(r) {
if (r instanceof XBoolean) {
return this.bool.notequal(r);
}
else if (r instanceof XString) {
return this.notequal(r.number);
}
else if (r instanceof XNodeSet) {
return r.compareWithNumber(this, Operators.notequal);
}
else if (r instanceof XNumber) {
return new XBoolean(this.num !== r.num);
}
else {
throw new Error('Unsupported type');
}
}
lessthan(r) {
if (r instanceof XNodeSet) {
return r.compareWithNumber(this, Operators.greaterthan);
}
else if (r instanceof XBoolean || r instanceof XString) {
return this.lessthan(r.number);
}
else if (r instanceof XNumber) {
return new XBoolean(this.num < r.num);
}
else {
throw new Error('Unsupported type');
}
}
greaterthan(r) {
if (r instanceof XNodeSet) {
return r.compareWithNumber(this, Operators.lessthan);
}
else if (r instanceof XBoolean || r instanceof XString) {
return this.greaterthan(r.number);
}
else if (r instanceof XNumber) {
return new XBoolean(this.num > r.num);
}
else {
throw new Error('Unsupported type');
}
}
lessthanorequal(r) {
if (r instanceof XNodeSet) {
return r.compareWithNumber(this, Operators.greaterthanorequal);
}
else if (r instanceof XBoolean || r instanceof XString) {
return this.lessthanorequal(r.number);
}
else if (r instanceof XNumber) {
return new XBoolean(this.num <= r.num);
}
else {
throw new Error('Unsupported type');
}
}
greaterthanorequal(r) {
if (r instanceof XNodeSet) {
return r.compareWithNumber(this, Operators.lessthanorequal);
}
else if (r instanceof XBoolean || r instanceof XString) {
return this.greaterthanorequal(r.number);
}
else if (r instanceof XNumber) {
return new XBoolean(this.num >= r.num);
}
else {
throw new Error('Unsupported type');
}
}
plus(r) {
return new XNumber(this.num + r.num);
}
minus(r) {
return new XNumber(this.num - r.num);
}
multiply(r) {
return new XNumber(this.num * r.num);
}
div(r) {
return new XNumber(this.num / r.num);
}
mod(r) {
return new XNumber(this.num % r.num);
}
}
exports.XNumber = XNumber;
function padSmallNumber(numberStr) {
const parts = numberStr.split('e-');
let base = parts[0].replace('.', '');
const exponent = Number(parts[1]);
for (let i = 0; i < exponent - 1; i += 1) {
base = '0' + base;
}
return '0.' + base;
}
function padLargeNumber(numberStr) {
const parts = numberStr.split('e');
let base = parts[0].replace('.', '');
const exponent = Number(parts[1]);
const zerosToAppend = exponent + 1 - base.length;
for (let i = 0; i < zerosToAppend; i += 1) {
base += '0';
}
return base;
}
class XString extends Expression {
str;
constructor(s) {
super();
this.str = String(s);
}
toString() {
return this.str;
}
evaluate(_c) {
return this;
}
get string() {
return this;
}
get number() {
return new XNumber(this.str);
}
get bool() {
return new XBoolean(this.str);
}
get nodeset() {
throw new Error('Cannot convert string to nodeset');
}
get stringValue() {
return this.str;
}
get numberValue() {
return this.number.numberValue;
}
get booleanValue() {
return this.bool.booleanValue;
}
equals(r) {
if (r instanceof XBoolean) {
return this.bool.equals(r);
}
else if (r instanceof XNumber) {
return this.number.equals(r);
}
else if (r instanceof XNodeSet) {
return r.compareWithString(this, Operators.equals);
}
else if (r instanceof XString) {
return new XBoolean(this.str === r.str);
}
else {
throw new Error('Unsupported type');
}
}
notequal(r) {
if (r instanceof XBoolean) {
return this.bool.notequal(r);
}
else if (r instanceof XNumber) {
return this.number.notequal(r);
}
else if (r instanceof XNodeSet) {
return r.compareWithString(this, Operators.notequal);
}
else if (r instanceof XString) {
return new XBoolean(this.str !== r.str);
}
else {
throw new Error('Unsupported type');
}
}
lessthan(r) {
return this.number.lessthan(r);
}
greaterthan(r) {
return this.number.greaterthan(r);
}
lessthanorequal(r) {
return this.number.lessthanorequal(r);
}
greaterthanorequal(r) {
return this.number.greaterthanorequal(r);
}
}
exports.XString = XString;
class XNodeSet extends Expression {
static compareWith(o) {
return function (r) {
if (r instanceof XString) {
return this.compareWithString(r, o);
}
else if (r instanceof XNumber) {
return this.compareWithNumber(r, o);
}
else if (r instanceof XBoolean) {
return this.compareWithBoolean(r, o);
}
else if (r instanceof XNodeSet) {
return this.compareWithNodeSet(r, o);
}
else {
throw new Error('Unsupported type');
}
};
}
tree;
nodes;
size;
constructor() {
super();
this.tree = null;
this.nodes = [];
this.size = 0;
}
toString() {
const p = this.first();
if (p == null) {
return '';
}
return this.stringForNode(p) || '';
}
evaluate(_c) {
return this;
}
get string() {
return new XString(this.toString());
}
get stringValue() {
return this.toString();
}
get number() {
return new XNumber(this.string);
}
get numberValue() {
return Number(this.string);
}
get bool() {
return new XBoolean(this.booleanValue);
}
get booleanValue() {
return !!this.size;
}
get nodeset() {
return this;
}
stringForNode(n) {
if ((0, types_1.isDocument)(n) || (0, types_1.isElement)(n) || (0, types_1.isFragment)(n)) {
return this.stringForContainerNode(n);
}
else if ((0, types_1.isAttribute)(n)) {
return n.value || n.nodeValue || '';
}
else if ((0, types_1.isNamespaceNode)(n)) {
return n.value;
}
return n.nodeValue || '';
}
stringForContainerNode(n) {
let s = '';
for (let n2 = n.firstChild; n2 != null; n2 = n2.nextSibling) {
if ((0, types_1.isElement)(n2) || (0, types_1.isText)(n2) || (0, types_1.isCData)(n2) || (0, types_1.isDocument)(n2) || (0, types_1.isFragment)(n2)) {
s += this.stringForNode(n2);
}
}
return s;
}
buildTree() {
if (!this.tree && this.nodes.length) {
this.tree = new avl_tree_1.AVLTree(this.nodes[0]);
for (let i = 1; i < this.nodes.length; i += 1) {
this.tree.add(this.nodes[i]);
}
}
return this.tree;
}
first() {
let p = this.buildTree();
if (p == null) {
return null;
}
while (p.left != null) {
p = p.left;
}
return p.node;
}
add(n) {
for (let i = 0; i < this.nodes.length; i += 1) {
if (n === this.nodes[i]) {
return;
}
}
this.tree = null;
this.nodes.push(n);
this.size += 1;
}
addArray(ns) {
const self = this;
ns.forEach((x) => self.add(x));
}
/**
* Returns an array of the node set's contents in document order
*/
toArray() {
const a = [];
this.toArrayRec(this.buildTree(), a);
return a;
}
toArrayRec(t, a) {
if (t != null) {
this.toArrayRec(t.left, a);
a.push(t.node);
this.toArrayRec(t.right, a);
}
}
/**
* Returns an array of the node set's contents in arbitrary order
*/
toUnsortedArray() {
return this.nodes.slice();
}
compareWithString(r, o) {
const a = this.toUnsortedArray();
for (let i = 0; i < a.length; i++) {
const n = a[i];
const l = new XString(this.stringForNode(n));
const res = o(l, r);
if (res.booleanValue) {
return res;
}
}
return new XBoolean(false);
}
compareWithNumber(r, o) {
const a = this.toUnsortedArray();
for (let i = 0; i < a.length; i++) {
const n = a[i];
const l = new XNumber(this.stringForNode(n));
const res = o(l, r);
if (res.booleanValue) {
return res;
}
}
return new XBoolean(false);
}
compareWithBoolean(r, o) {
return o(this.bool, r);
}
compareWithNodeSet(r, o) {
const arr = this.toUnsortedArray();
const oInvert = (lop, rop) => {
return o(rop, lop);
};
for (let i = 0; i < arr.length; i++) {
const l = new XString(this.stringForNode(arr[i]));
const res = r.compareWithString(l, oInvert);
if (res.booleanValue) {
return res;
}
}
return new XBoolean(false);
}
equals = XNodeSet.compareWith(Operators.equals);
notequal = XNodeSet.compareWith(Operators.notequal);
lessthan = XNodeSet.compareWith(Operators.lessthan);
greaterthan = XNodeSet.compareWith(Operators.greaterthan);
lessthanorequal = XNodeSet.compareWith(Operators.lessthanorequal);
greaterthanorequal = XNodeSet.compareWith(Operators.greaterthanorequal);
union(r) {
const ns = new XNodeSet();
ns.addArray(this.toUnsortedArray());
ns.addArray(r.toUnsortedArray());
return ns;
}
}
exports.XNodeSet = XNodeSet;
class XPathContext {
variableResolver;
namespaceResolver;
functionResolver;
contextNode = undefined;
virtualRoot;
expressionContextNode = undefined;
isHtml = undefined;
contextSize;
contextPosition;
allowAnyNamespaceForNoPrefix = undefined;
caseInsensitive = undefined;
constructor(vr, nr, fr) {
this.variableResolver = vr;
this.namespaceResolver = nr;
this.functionResolver = fr;
this.virtualRoot = null;
this.contextSize = 0;
this.contextPosition = 0;
}
clone() {
return Object.assign(new XPathContext(this.variableResolver, this.namespaceResolver, this.functionResolver), this);
}
extend(newProps) {
return Object.assign(new XPathContext(this.variableResolver, this.namespaceResolver, this.functionResolver), this, newProps);
}
}
exports.XPathContext = XPathContext;
class Operators {
static equals(l, r) {
return l.equals(r);
}
static notequal(l, r) {
return l.notequal(r);
}
static lessthan(l, r) {
return l.lessthan(r);
}
static greaterthan(l, r) {
return l.greaterthan(r);
}
static lessthanorequal(l, r) {
return l.lessthanorequal(r);
}
static greaterthanorequal(l, r) {
return l.greaterthanorequal(r);
}
}
exports.Operators = Operators;
//# sourceMappingURL=xpath-types.js.map