Skip to content

Commit 88cbc82

Browse files
XPath current() function (#66)
* Implementing `current()` for XPath. * Unit tests.
1 parent 9cb8fe2 commit 88cbc82

17 files changed

+116
-63
lines changed

src/xpath/expressions/README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,5 @@ expression classes closely mirrors the set of non terminal symbols in the gramma
66
The common expression interface consists of the following methods:
77

88
- `evaluate(context)` - evaluates the expression, returns a value.
9-
109
- `toString(expr)` - returns the XPath text representation of the expression.
11-
12-
- `parseTree(expr, indent)` - returns a parse tree representation of the expression.
10+
- `parseTree(expr, indent)` - returns a parse tree representation of the expression.

src/xpath/expressions/binary-expr.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { xmlValue } from "../../dom";
2+
import { ExprContext } from "../expr-context";
23
import { BooleanValue } from "../values/boolean-value";
34
import { NumberValue } from "../values/number-value";
45
import { Expression } from "./expression";
@@ -80,7 +81,7 @@ export class BinaryExpr extends Expression {
8081
return ret;
8182
}
8283

83-
compare(ctx, cmp) {
84+
compare(ctx: ExprContext, cmp: any) {
8485
const v1 = this.expr1.evaluate(ctx);
8586
const v2 = this.expr2.evaluate(ctx);
8687

src/xpath/expressions/filter-expr.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,22 @@ export class FilterExpr extends Expression {
1212
this.predicate = predicate;
1313
}
1414

15-
evaluate(ctx: ExprContext) {
15+
evaluate(context: ExprContext) {
1616
// the filter expression should be evaluated in its entirety with no
1717
// optimization, as we can't backtrack to it after having moved on to
1818
// evaluating the relative location path. See the testReturnOnFirstMatch
1919
// unit test.
20-
const flag = ctx.returnOnFirstMatch;
21-
ctx.setReturnOnFirstMatch(false);
22-
let nodes = this.expr.evaluate(ctx).nodeSetValue();
23-
ctx.setReturnOnFirstMatch(flag);
20+
const flag = context.returnOnFirstMatch;
21+
context.setReturnOnFirstMatch(false);
22+
let nodes = this.expr.evaluate(context).nodeSetValue();
23+
context.setReturnOnFirstMatch(flag);
2424

2525
for (let i = 0; i < this.predicate.length; ++i) {
2626
const nodes0 = nodes;
2727
nodes = [];
2828
for (let j = 0; j < nodes0.length; ++j) {
2929
const n = nodes0[j];
30-
if (this.predicate[i].evaluate(ctx.clone(nodes0, undefined, j)).booleanValue()) {
30+
if (this.predicate[i].evaluate(context.clone(nodes0, undefined, j)).booleanValue()) {
3131
nodes.push(n);
3232
}
3333
}

src/xpath/expressions/function-call-expr.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import {
3030
sum,
3131
floor,
3232
ceiling,
33-
round
33+
round,
34+
current
3435
} from '../functions';
3536
import { extCardinal, extIf, extJoin } from '../functions/non-standard';
3637
import { BooleanValue } from '../values/boolean-value';
@@ -41,37 +42,38 @@ export class FunctionCallExpr extends Expression {
4142
args: any[];
4243

4344
xPathFunctions: { [key: string]: Function } = {
44-
last,
45-
position,
45+
boolean,
46+
ceiling,
47+
concat,
48+
contains,
4649
count,
50+
current,
51+
'ends-with': endsWith,
52+
false: _false,
53+
floor,
4754
'generate-id': generateId,
4855
id,
49-
'xml-to-json': xmlToJson,
56+
lang,
57+
last,
5058
'local-name': localName,
51-
'namespace-uri': namespaceUri,
59+
matches,
5260
name: _name,
53-
string: _string,
54-
concat,
61+
'namespace-uri': namespaceUri,
62+
'normalize-space': normalizeSpace,
63+
not,
64+
number,
65+
position,
66+
round,
5567
'starts-with': startsWith,
56-
'ends-with': endsWith,
57-
contains,
68+
string: _string,
69+
'xml-to-json': xmlToJson,
70+
substring,
5871
'substring-before': substringBefore,
5972
'substring-after': substringAfter,
60-
substring,
73+
sum,
6174
'string-length': stringLength,
62-
'normalize-space': normalizeSpace,
6375
translate,
64-
matches,
65-
boolean,
66-
not,
6776
true: _true,
68-
false: _false,
69-
lang,
70-
number,
71-
sum,
72-
floor,
73-
ceiling,
74-
round,
7577

7678
// TODO(mesch): The following functions are custom. There is a
7779
// standard that defines how to add functions, which should be

src/xpath/expressions/location-expr.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,11 @@ export class LocationExpr extends Expression {
6969
start = context.root;
7070
} else {
7171
start = context.nodeList[context.position];
72-
if (start.nodeName === '#document') {
72+
// TODO: `<xsl:template>` with relative path, starting on root node,
73+
// conflicts with `<xsl:template match="/">`, for some reason considered as relative.
74+
/* if (start.nodeName === '#document' && this.steps[0].axis === 'self-and-siblings') {
7375
start = start.childNodes[0];
74-
}
76+
} */
7577
}
7678

7779
const nodes = [];

src/xpath/expressions/predicate-expr.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@ import { BooleanValue } from "../values/boolean-value";
33
import { Expression } from "./expression";
44

55
export class PredicateExpr extends Expression {
6-
expr: any;
6+
expr: Expression;
77

8-
constructor(expr: any) {
8+
constructor(expr: Expression) {
99
super();
1010
this.expr = expr;
1111
}
1212

13-
evaluate(ctx: ExprContext) {
14-
const v = this.expr.evaluate(ctx);
13+
evaluate(context: ExprContext) {
14+
const v = this.expr.evaluate(context);
1515
if (v.type == 'number') {
1616
// NOTE(mesch): Internally, position is represented starting with
1717
// 0, however in XPath position starts with 1. See functions
1818
// position() and last().
19-
return new BooleanValue(ctx.position == v.numberValue() - 1);
20-
} else {
21-
return new BooleanValue(v.booleanValue());
19+
return new BooleanValue(context.position == v.numberValue() - 1);
2220
}
21+
22+
return new BooleanValue(v.booleanValue());
2323
}
2424
}

src/xpath/functions/standard.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { ExprContext } from "../expr-context";
33
import { BooleanValue, NodeSetValue, NumberValue, StringValue } from "../values";
44
import { assert, regExpEscape } from "./internal-functions";
55

6+
/* Support functions. They are not exported. */
7+
68
function cyrb53(str: string, seed = 0) {
79
let h1 = 0xdeadbeef ^ seed;
810
let h2 = 0x41c6ce57 ^ seed;
@@ -21,6 +23,7 @@ function cyrb53(str: string, seed = 0) {
2123
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
2224
}
2325

26+
// Exported functions.
2427
// In theory none of the `this.args` should work here,
2528
// but `this` is replaced on `FunctionCallExpr.evaluate()`
2629
// executes.
@@ -57,6 +60,11 @@ export function count(context: ExprContext) {
5760
return new NumberValue(v.nodeSetValue().length);
5861
}
5962

63+
export function current(context: ExprContext) {
64+
assert(this.args.length === 0);
65+
return new NodeSetValue([context.nodeList[context.position]]);
66+
}
67+
6068
export function endsWith(context: ExprContext) {
6169
assert(this.args.length == 2);
6270
const s0 = this.args[0].evaluate(context).stringValue();
@@ -114,21 +122,21 @@ export function id(context: ExprContext) {
114122
export function lang(context: ExprContext) {
115123
assert(this.args.length === 1);
116124
const lang = this.args[0].evaluate(context).stringValue();
117-
let xmllang;
125+
let xmlLang;
118126
let n = context.nodeList[context.position];
119127
while (n && n != n.parentNode /* just in case ... */) {
120-
xmllang = n.getAttributeValue('xml:lang');
121-
if (xmllang) {
128+
xmlLang = n.getAttributeValue('xml:lang');
129+
if (xmlLang) {
122130
break;
123131
}
124132
n = n.parentNode;
125133
}
126-
if (!xmllang) {
134+
if (!xmlLang) {
127135
return new BooleanValue(false);
128136
}
129137

130138
const re = new RegExp(`^${lang}$`, 'i');
131-
return new BooleanValue(xmllang.match(re) || xmllang.replace(/_.*$/, '').match(re));
139+
return new BooleanValue(xmlLang.match(re) || xmlLang.replace(/_.*$/, '').match(re));
132140
}
133141

134142
export function last(context: ExprContext) {
@@ -139,7 +147,7 @@ export function last(context: ExprContext) {
139147

140148
export function localName(context: ExprContext) {
141149
assert(this.args.length === 1 || this.args.length === 0);
142-
let n;
150+
let n: XNode[];
143151
if (this.args.length == 0) {
144152
n = [context.nodeList[context.position]];
145153
} else {

src/xpath/node-test-comment.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { DOM_COMMENT_NODE } from "../constants";
2+
import { ExprContext } from "./expr-context";
23
import { BooleanValue } from "./values/boolean-value";
34

45
export class NodeTestComment {
5-
evaluate(ctx) {
6-
return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);
6+
evaluate(ctx: ExprContext) {
7+
return new BooleanValue(ctx.nodeList[ctx.position].nodeType == DOM_COMMENT_NODE);
78
}
89
}

src/xpath/node-test-nc.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1+
import { ExprContext } from "./expr-context";
12
import { BooleanValue } from "./values/boolean-value";
23

34
export class NodeTestNC {
45
regex: RegExp;
56

67
nsprefix: any;
78

8-
constructor(nsprefix) {
9+
constructor(nsprefix: string) {
910
this.regex = new RegExp(`^${nsprefix}:`);
1011
this.nsprefix = nsprefix;
1112
}
1213

13-
evaluate(ctx) {
14-
const n = ctx.node;
14+
evaluate(ctx: ExprContext) {
15+
const n = ctx.nodeList[ctx.position];
1516
return new BooleanValue(n.nodeName.match(this.regex));
1617
}
1718
}

src/xpath/node-test-pi.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { DOM_PROCESSING_INSTRUCTION_NODE } from "../constants";
2+
import { ExprContext } from "./expr-context";
23
import { BooleanValue } from "./values/boolean-value";
34

45
export class NodeTestPI {
56
target: any;
67

7-
constructor(target) {
8+
constructor(target: any) {
89
this.target = target;
910
}
1011

11-
evaluate(ctx) {
12+
evaluate(ctx: ExprContext) {
13+
const node = ctx.nodeList[ctx.position];
1214
return new BooleanValue(
13-
ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE && (!this.target || ctx.node.nodeName == this.target)
15+
node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE && (!this.target || node.nodeName == this.target)
1416
);
1517
}
1618
}

0 commit comments

Comments
 (0)