Passed
Pull Request — develop (#92)
by Felipe
06:21
created

.tests/selenium/selenium-lib/core/xpath/xpath.js   F

Complexity

Total Complexity 441
Complexity/F 2.51

Size

Lines of Code 2439
Function Count 176

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
nc 12582912
dl 0
loc 2439
rs 2.4
c 0
b 0
f 0
wmc 441
mnd 13
bc 415
fnc 176
bpm 2.3579
cpm 2.5056
noi 63

164 Functions

Rating   Name   Duplication   Size   Complexity  
A xpath.js ➔ makePathExpr1 0 3 1
A FunctionCallExpr.xpathfunctions.number 0 9 2
A PredicateExpr.evaluate 0 11 2
B xpath.js ➔ makeNodeTestExpr4 0 16 5
A FunctionCallExpr.xpathfunctions.translate 0 11 2
A xpath.js ➔ makeNumberExpr 0 3 1
A BooleanValue.booleanValue 0 3 1
A xpath.js ➔ LocationExpr 0 4 1
A FunctionCallExpr.xpathfunctions.ext-cardinal 0 9 2
A xpath.js ➔ xpathEval 0 5 1
A NumberValue.booleanValue 0 3 1
A NumberExpr.evaluate 0 3 1
A LocationExpr.evaluate 0 13 2
A FunctionCallExpr.xpathfunctions.substring-after 0 13 2
A xpath.js ➔ StringValue 0 4 1
A ExprContext.isIgnoreAttributesWithoutValue 0 3 1
A NodeTestAny.evaluate 0 3 1
A xpath.js ➔ makeAbbrevStep 0 12 4
A xpath.js ➔ makeLocationExpr4 0 6 1
C xpath.js ➔ xpathSort 0 42 7
A FunctionCallExpr.xpathfunctions.starts-with 0 6 1
A xpath.js ➔ NodeTestNC 0 4 1
A StepExpr.appendPredicate 0 6 2
A StringValue.nodeSetValue 0 3 1
A BooleanValue.nodeSetValue 0 3 1
A xpath.js ➔ FilterExpr 0 4 1
A xpath.js ➔ makeLocationExpr2 0 5 1
C xpath.js ➔ xpathMatchStack 0 71 16
A ExprContext.setCaseInsensitive 0 3 1
A ExprContext.getVariable 0 11 3
A xpath.js ➔ makeSimpleExpr2 0 10 2
A ExprContext.isCaseInsensitive 0 3 1
A xpath.js ➔ NodeSetValue 0 4 1
A xpath.js ➔ makeLiteralExpr 0 5 1
A FunctionCallExpr.xpathfunctions.local-name 0 3 1
D BinaryExpr.compare 0 91 24
A FunctionCallExpr.xpathfunctions.not 0 5 1
A NodeSetValue.booleanValue 0 3 1
A FunctionCallExpr.xpathfunctions.name 0 15 3
A NodeTestName.evaluate 0 9 3
A ExprContext.isReturnOnFirstMatch 0 3 1
A NumberValue.numberValue 0 3 1
A ExprContext.contextSize 0 3 1
A FunctionCallExpr.xpathfunctions.substring 0 20 2
A xpath.js ➔ makeNodeTestExpr2 0 3 1
A FunctionCallExpr.xpathfunctions.concat 0 7 2
A FunctionCallExpr.xpathfunctions.last 0 5 1
A xpath.js ➔ makeStepExpr5 0 3 1
A xpath.js ➔ makeArgumentExpr 0 3 1
B xpath.js ➔ xpathSortByKey 0 15 5
C xpath.js ➔ xpathGrammarPrecedence 0 25 8
A BooleanValue.stringValue 0 3 1
A ExprContext.isIgnoreNonElementNodesForNTA 0 3 1
A xpath.js ➔ PathExpr 0 4 1
A xpath.js ➔ NodeTestPI 0 3 1
A ExprContext.setIgnoreAttributesWithoutValue 0 3 1
D StepExpr.evaluate 0 147 41
A xpath.js ➔ xpathCacheLookup 0 3 1
A FunctionCallExpr.xpathfunctions.ext-if 0 8 2
A xpath.js ➔ makeStepExpr1 0 3 1
A xpath.js ➔ makeLocationExpr5 0 5 1
A xpath.js ➔ stackToString 0 10 3
B xpath.js ➔ ExprContext 0 25 3
A xpath.js ➔ UnaryMinusExpr 0 3 1
A FunctionCallExpr.xpathfunctions.false 0 4 1
A xpath.js ➔ makeNodeTestExpr5 0 7 2
C LocationExpr._combineSteps 0 24 10
A FunctionCallExpr.xpathfunctions.string 0 8 2
A NodeTestComment.evaluate 0 3 1
A xpath.js ➔ xpathCollectDescendantsReverse 0 6 2
A ExprContext.setReturnOnFirstMatch 0 3 1
A xpath.js ➔ makeStepExpr3 0 3 1
A FunctionCallExpr.xpathfunctions.matches 0 19 4
A BooleanValue.numberValue 0 3 2
A FunctionCallExpr.xpathfunctions.sum 0 9 2
A FunctionCallExpr.xpathfunctions.round 0 5 1
A xpath.js ➔ BinaryExpr 0 5 1
A xpath.js ➔ NodeTestText 0 1 1
A FunctionCallExpr.xpathfunctions.count 0 5 1
A StringValue.stringValue 0 3 1
A NodeTestElementOrAttribute.evaluate 0 5 1
A NodeTestText.evaluate 0 3 1
A FunctionCallExpr.xpathfunctions.boolean 0 4 1
A xpath.js ➔ VariableExpr 0 3 1
A FunctionCallExpr.xpathfunctions.position 0 5 1
B ExprContext.setVariable 0 17 8
A LiteralExpr.evaluate 0 3 1
A FunctionCallExpr.xpathfunctions.ends-with 0 7 1
A UnaryMinusExpr.evaluate 0 3 1
B PathExpr.evaluate 0 29 6
A xpath.js ➔ makeSimpleExpr 0 19 4
A xpath.js ➔ makePathExpr2 0 4 1
A FunctionCallExpr.xpathfunctions.string-length 0 9 2
A xpath.js ➔ makeVariableReference 0 3 1
A FunctionCallExpr.xpathfunctions.ceiling 0 5 1
A xpath.js ➔ NumberValue 0 4 1
A xpath.js ➔ passExpr 0 3 1
A ExprContext.clone 0 8 2
A NodeSetValue.numberValue 0 3 1
A ExprContext.setIgnoreNonElementNodesForNTA 0 3 1
A xpath.js ➔ NumberExpr 0 3 1
A NumberValue.stringValue 0 3 1
A xpath.js ➔ makePrimaryExpr 0 3 1
A TokenExpr.evaluate 0 3 1
A StringValue.booleanValue 0 3 1
A FilterExpr.evaluate 0 19 4
A VariableExpr.evaluate 0 3 1
A xpath.js ➔ makeFilterExpr 0 7 2
A xpath.js ➔ makeLocationExpr7 0 5 1
A xpath.js ➔ PredicateExpr 0 3 1
A FunctionCallExpr.xpathfunctions.substring-before 0 13 2
A xpath.js ➔ makeStepExpr2 0 3 1
C xpath.js ➔ xpathParseInit 0 80 9
A xpath.js ➔ xpathTokenPrecedence 0 3 1
A FunctionCallExpr.xpathfunctions.ext-join 0 13 3
A xpath.js ➔ makeNodeTestExpr1 0 3 1
A xpath.js ➔ xpathCollectDescendants 0 10 4
A xpath.js ➔ makeLocationExpr3 0 6 1
C xpath.js ➔ xPathStep 0 50 10
A xpath.js ➔ NodeTestAny 0 3 1
A NodeTestNC.evaluate 0 4 1
A FunctionCallExpr.evaluate 0 10 2
A xpath.js ➔ makeFunctionCallExpr1 0 3 1
A xpath.js ➔ UnionExpr 0 4 1
A xpath.js ➔ makeBinaryExpr 0 3 1
A FunctionCallExpr.xpathfunctions.floor 0 5 1
A xpath.js ➔ makeUnaryMinusExpr 0 3 1
B UnionExpr.evaluate 0 19 5
C BinaryExpr.evaluate 0 67 15
A xpath.js ➔ xpathDomEval 0 5 1
A xpath.js ➔ makeUnionExpr 0 3 1
A xpath.js ➔ NodeTestElementOrAttribute 0 1 1
A NodeSetValue.stringValue 0 7 2
B FunctionCallExpr.xpathfunctions.id 0 26 6
A xpath.js ➔ LiteralExpr 0 3 1
A FunctionCallExpr.xpathfunctions.namespace-uri 0 3 1
A xpath.js ➔ NodeTestComment 0 1 1
A xpath.js ➔ makeTokenExpr 0 3 1
A LocationExpr.prependStep 0 8 2
A xpath.js ➔ makeFunctionCallExpr2 0 8 2
A xpath.js ➔ makePredicateExpr 0 3 1
A xpath.js ➔ makeLocationExpr6 0 4 1
A FunctionCallExpr.appendArg 0 3 1
A NumberValue.nodeSetValue 0 3 1
A FunctionCallExpr.xpathfunctions.contains 0 6 1
B xpath.js ➔ xpathExtractTagNameFromNodeTest 0 9 5
A xpath.js ➔ makeLocationExpr1 0 4 1
A LocationExpr.appendStep 0 8 2
A xpath.js ➔ TokenExpr 0 3 1
A xpath.js ➔ makeStepExpr4 0 3 1
A StringValue.numberValue 0 3 1
A xpath.js ➔ FunctionCallExpr 0 4 1
A FunctionCallExpr.xpathfunctions.true 0 4 1
C xpath.js ➔ xpathReduce 0 55 16
A ExprContext.setNode 0 4 1
A xpath.js ➔ makeStepExpr6 0 4 1
B FunctionCallExpr.xpathfunctions.lang 0 20 5
A FunctionCallExpr.xpathfunctions.normalize-space 0 10 2
A xpath.js ➔ makeNodeTestExpr3 0 3 1
A NodeSetValue.nodeSetValue 0 3 1
A xpath.js ➔ BooleanValue 0 4 1
A xpath.js ➔ StepExpr 0 12 3
A NodeTestPI.evaluate 0 5 1
A xpath.js ➔ NodeTestName 0 4 1

How to fix   Complexity   

Complexity

Complex classes like .tests/selenium/selenium-lib/core/xpath/xpath.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// Copyright 2005 Google Inc.
2
// All Rights Reserved
3
//
4
// An XPath parser and evaluator written in JavaScript. The
5
// implementation is complete except for functions handling
6
// namespaces.
7
//
8
// Reference: [XPATH] XPath Specification
9
// <http://www.w3.org/TR/1999/REC-xpath-19991116>.
10
//
11
//
12
// The API of the parser has several parts:
13
//
14
// 1. The parser function xpathParse() that takes a string and returns
15
// an expession object.
16
//
17
// 2. The expression object that has an evaluate() method to evaluate the
18
// XPath expression it represents. (It is actually a hierarchy of
19
// objects that resembles the parse tree, but an application will call
20
// evaluate() only on the top node of this hierarchy.)
21
//
22
// 3. The context object that is passed as an argument to the evaluate()
23
// method, which represents the DOM context in which the expression is
24
// evaluated.
25
//
26
// 4. The value object that is returned from evaluate() and represents
27
// values of the different types that are defined by XPath (number,
28
// string, boolean, and node-set), and allows to convert between them.
29
//
30
// These parts are near the top of the file, the functions and data
31
// that are used internally follow after them.
32
//
33
//
34
// Author: Steffen Meschkat <[email protected]>
35
36
37
// The entry point for the parser.
38
//
39
// @param expr a string that contains an XPath expression.
40
// @return an expression object that can be evaluated with an
41
// expression context.
42
43
function xpathParse(expr) {
44
  xpathLog('parse ' + expr);
45
  xpathParseInit();
46
47
  var cached = xpathCacheLookup(expr);
48
  if (cached) {
49
    xpathLog(' ... cached');
50
    return cached;
51
  }
52
53
  // Optimize for a few common cases: simple attribute node tests
54
  // (@id), simple element node tests (page), variable references
55
  // ($address), numbers (4), multi-step path expressions where each
56
  // step is a plain element node test
57
  // (page/overlay/locations/location).
58
59
  if (expr.match(/^(\$|@)?\w+$/i)) {
60
    var ret = makeSimpleExpr(expr);
61
    xpathParseCache[expr] = ret;
62
    xpathLog(' ... simple');
63
    return ret;
64
  }
65
66
  if (expr.match(/^\w+(\/\w+)*$/i)) {
67
    var ret = makeSimpleExpr2(expr);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable ret already seems to be declared on line 60. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
68
    xpathParseCache[expr] = ret;
69
    xpathLog(' ... simple 2');
70
    return ret;
71
  }
72
73
  var cachekey = expr; // expr is modified during parse
74
75
  var stack = [];
76
  var ahead = null;
77
  var previous = null;
0 ignored issues
show
Unused Code introduced by
The assignment to previous seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
78
  var done = false;
79
80
  var parse_count = 0;
81
  var lexer_count = 0;
82
  var reduce_count = 0;
83
84
  while (!done) {
85
    parse_count++;
86
    expr = expr.replace(/^\s*/, '');
87
    previous = ahead;
88
    ahead = null;
89
90
    var rule = null;
91
    var match = '';
92
    for (var i = 0; i < xpathTokenRules.length; ++i) {
93
      var result = xpathTokenRules[i].re.exec(expr);
94
      lexer_count++;
95
      if (result && result.length > 0 && result[0].length > match.length) {
96
        rule = xpathTokenRules[i];
97
        match = result[0];
98
        break;
99
      }
100
    }
101
102
    // Special case: allow operator keywords to be element and
103
    // variable names.
104
105
    // NOTE(mesch): The parser resolves conflicts by looking ahead,
106
    // and this is the only case where we look back to
107
    // disambiguate. So this is indeed something different, and
108
    // looking back is usually done in the lexer (via states in the
109
    // general case, called "start conditions" in flex(1)). Also,the
110
    // conflict resolution in the parser is not as robust as it could
111
    // be, so I'd like to keep as much off the parser as possible (all
112
    // these precedence values should be computed from the grammar
113
    // rules and possibly associativity declarations, as in bison(1),
114
    // and not explicitly set.
115
116
    if (rule &&
117
        (rule == TOK_DIV ||
118
         rule == TOK_MOD ||
119
         rule == TOK_AND ||
120
         rule == TOK_OR) &&
121
        (!previous ||
122
         previous.tag == TOK_AT ||
123
         previous.tag == TOK_DSLASH ||
124
         previous.tag == TOK_SLASH ||
125
         previous.tag == TOK_AXIS ||
126
         previous.tag == TOK_DOLLAR)) {
127
      rule = TOK_QNAME;
128
    }
129
130
    if (rule) {
131
      expr = expr.substr(match.length);
132
      xpathLog('token: ' + match + ' -- ' + rule.label);
133
      ahead = {
134
        tag: rule,
135
        match: match,
136
        prec: rule.prec ?  rule.prec : 0, // || 0 is removed by the compiler
137
        expr: makeTokenExpr(match)
138
      };
139
140
    } else {
141
      xpathLog('DONE');
142
      done = true;
143
    }
144
145
    while (xpathReduce(stack, ahead)) {
146
      reduce_count++;
147
      xpathLog('stack: ' + stackToString(stack));
148
    }
149
  }
150
151
  xpathLog('stack: ' + stackToString(stack));
152
153
  // DGF any valid XPath should "reduce" to a single Expr token
154
  if (stack.length != 1) {
155
    throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);
156
  }
157
158
  var result = stack[0].expr;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable result already seems to be declared on line 93. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
159
  xpathParseCache[cachekey] = result;
160
161
  xpathLog('XPath parse: ' + parse_count + ' / ' +
162
           lexer_count + ' / ' + reduce_count);
163
164
  return result;
165
}
166
167
var xpathParseCache = {};
168
169
function xpathCacheLookup(expr) {
170
  return xpathParseCache[expr];
171
}
172
173
/*DGF xpathReduce is where the magic happens in this parser.
174
Skim down to the bottom of this file to find the table of 
175
grammatical rules and precedence numbers, "The productions of the grammar".
176
177
The idea here
178
is that we want to take a stack of tokens and apply
179
grammatical rules to them, "reducing" them to higher-level
180
tokens.  Ultimately, any valid XPath should reduce to exactly one
181
"Expr" token.
182
183
Reduce too early or too late and you'll have two tokens that can't reduce
184
to single Expr.  For example, you may hastily reduce a qname that
185
should name a function, incorrectly treating it as a tag name.
186
Or you may reduce too late, accidentally reducing the last part of the
187
XPath into a top-level "Expr" that won't reduce with earlier parts of
188
the XPath.
189
190
A "cand" is a grammatical rule candidate, with a given precedence
191
number.  "ahead" is the upcoming token, which also has a precedence
192
number.  If the token has a higher precedence number than
193
the rule candidate, we'll "shift" the token onto the token stack,
194
instead of immediately applying the rule candidate.
195
196
Some tokens have left associativity, in which case we shift when they
197
have LOWER precedence than the candidate.
198
*/
199
function xpathReduce(stack, ahead) {
200
  var cand = null;
201
202
  if (stack.length > 0) {
203
    var top = stack[stack.length-1];
204
    var ruleset = xpathRules[top.tag.key];
205
206
    if (ruleset) {
207
      for (var i = 0; i < ruleset.length; ++i) {
208
        var rule = ruleset[i];
209
        var match = xpathMatchStack(stack, rule[1]);
210
        if (match.length) {
211
          cand = {
212
            tag: rule[0],
213
            rule: rule,
214
            match: match
215
          };
216
          cand.prec = xpathGrammarPrecedence(cand);
217
          break;
218
        }
219
      }
220
    }
221
  }
222
223
  var ret;
224
  if (cand && (!ahead || cand.prec > ahead.prec ||
225
               (ahead.tag.left && cand.prec >= ahead.prec))) {
226
    for (var i = 0; i < cand.match.matchlength; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 207. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
227
      stack.pop();
228
    }
229
230
    xpathLog('reduce ' + cand.tag.label + ' ' + cand.prec +
231
             ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec +
232
                          (ahead.tag.left ? ' left' : '')
233
                          : ' none '));
234
235
    var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
236
    xpathLog('going to apply ' + cand.rule[3].toString());
237
    cand.expr = cand.rule[3].apply(null, matchexpr);
238
239
    stack.push(cand);
240
    ret = true;
241
242
  } else {
243
    if (ahead) {
244
      xpathLog('shift ' + ahead.tag.label + ' ' + ahead.prec +
245
               (ahead.tag.left ? ' left' : '') +
246
               ' over ' + (cand ? cand.tag.label + ' ' +
247
                           cand.prec : ' none'));
248
      stack.push(ahead);
249
    }
250
    ret = false;
251
  }
252
  return ret;
253
}
254
255
function xpathMatchStack(stack, pattern) {
256
257
  // NOTE(mesch): The stack matches for variable cardinality are
258
  // greedy but don't do backtracking. This would be an issue only
259
  // with rules of the form A* A, i.e. with an element with variable
260
  // cardinality followed by the same element. Since that doesn't
261
  // occur in the grammar at hand, all matches on the stack are
262
  // unambiguous.
263
264
  var S = stack.length;
265
  var P = pattern.length;
266
  var p, s;
267
  var match = [];
268
  match.matchlength = 0;
269
  var ds = 0;
270
  for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {
271
    ds = 0;
272
    var qmatch = [];
273
    if (pattern[p] == Q_MM) {
274
      p -= 1;
275
      match.push(qmatch);
276
      while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
277
        qmatch.push(stack[s - ds]);
278
        ds += 1;
279
        match.matchlength += 1;
280
      }
281
282
    } else if (pattern[p] == Q_01) {
283
      p -= 1;
284
      match.push(qmatch);
285
      while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {
286
        qmatch.push(stack[s - ds]);
287
        ds += 1;
288
        match.matchlength += 1;
289
      }
290
291
    } else if (pattern[p] == Q_1M) {
292
      p -= 1;
293
      match.push(qmatch);
294
      if (stack[s].tag == pattern[p]) {
295
        while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
296
          qmatch.push(stack[s - ds]);
297
          ds += 1;
298
          match.matchlength += 1;
299
        }
300
      } else {
301
        return [];
302
      }
303
304
    } else if (stack[s].tag == pattern[p]) {
305
      match.push(stack[s]);
306
      ds += 1;
307
      match.matchlength += 1;
308
309
    } else {
310
      return [];
311
    }
312
313
    reverseInplace(qmatch);
314
    qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });
315
  }
316
317
  reverseInplace(match);
318
319
  if (p == -1) {
320
    return match;
321
322
  } else {
323
    return [];
324
  }
325
}
326
327
function xpathTokenPrecedence(tag) {
328
  return tag.prec || 2;
329
}
330
331
function xpathGrammarPrecedence(frame) {
332
  var ret = 0;
333
334
  if (frame.rule) { /* normal reduce */
335
    if (frame.rule.length >= 3 && frame.rule[2] >= 0) {
336
      ret = frame.rule[2];
337
338
    } else {
339
      for (var i = 0; i < frame.rule[1].length; ++i) {
340
        var p = xpathTokenPrecedence(frame.rule[1][i]);
341
        ret = Math.max(ret, p);
342
      }
343
    }
344
  } else if (frame.tag) { /* TOKEN match */
345
    ret = xpathTokenPrecedence(frame.tag);
346
347
  } else if (frame.length) { /* Q_ match */
348
    for (var j = 0; j < frame.length; ++j) {
349
      var p = xpathGrammarPrecedence(frame[j]);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable p already seems to be declared on line 340. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
350
      ret = Math.max(ret, p);
351
    }
352
  }
353
354
  return ret;
355
}
356
357
function stackToString(stack) {
358
  var ret = '';
359
  for (var i = 0; i < stack.length; ++i) {
360
    if (ret) {
361
      ret += '\n';
362
    }
363
    ret += stack[i].tag.label;
364
  }
365
  return ret;
366
}
367
368
369
// XPath expression evaluation context. An XPath context consists of a
370
// DOM node, a list of DOM nodes that contains this node, a number
371
// that represents the position of the single node in the list, and a
372
// current set of variable bindings. (See XPath spec.)
373
//
374
// The interface of the expression context:
375
//
376
//   Constructor -- gets the node, its position, the node set it
377
//   belongs to, and a parent context as arguments. The parent context
378
//   is used to implement scoping rules for variables: if a variable
379
//   is not found in the current context, it is looked for in the
380
//   parent context, recursively. Except for node, all arguments have
381
//   default values: default position is 0, default node set is the
382
//   set that contains only the node, and the default parent is null.
383
//
384
//     Notice that position starts at 0 at the outside interface;
385
//     inside XPath expressions this shows up as position()=1.
386
//
387
//   clone() -- creates a new context with the current context as
388
//   parent. If passed as argument to clone(), the new context has a
389
//   different node, position, or node set. What is not passed is
390
//   inherited from the cloned context.
391
//
392
//   setVariable(name, expr) -- binds given XPath expression to the
393
//   name.
394
//
395
//   getVariable(name) -- what the name says.
396
//
397
//   setNode(position) -- sets the context to the node at the given
398
//   position. Needed to implement scoping rules for variables in
399
//   XPath. (A variable is visible to all subsequent siblings, not
400
//   only to its children.)
401
//
402
//   set/isCaseInsensitive -- specifies whether node name tests should
403
//   be case sensitive.  If you're executing xpaths against a regular
404
//   HTML DOM, you probably don't want case-sensitivity, because
405
//   browsers tend to disagree about whether elements & attributes
406
//   should be upper/lower case.  If you're running xpaths in an
407
//   XSLT instance, you probably DO want case sensitivity, as per the
408
//   XSL spec.
409
//
410
//   set/isReturnOnFirstMatch -- whether XPath evaluation should quit as soon
411
//   as a result is found. This is an optimization that might make sense if you
412
//   only care about the first result.
413
//
414
//   set/isIgnoreNonElementNodesForNTA -- whether to ignore non-element nodes
415
//   when evaluating the "node()" any node test. While technically this is
416
//   contrary to the XPath spec, practically it can enhance performance
417
//   significantly, and makes sense if you a) use "node()" when you mean "*",
418
//   and b) use "//" when you mean "/descendant::*/".
419
420
function ExprContext(node, opt_position, opt_nodelist, opt_parent,
421
  opt_caseInsensitive, opt_ignoreAttributesWithoutValue,
422
  opt_returnOnFirstMatch, opt_ignoreNonElementNodesForNTA)
423
{
424
  this.node = node;
425
  this.position = opt_position || 0;
426
  this.nodelist = opt_nodelist || [ node ];
427
  this.variables = {};
428
  this.parent = opt_parent || null;
429
  this.caseInsensitive = opt_caseInsensitive || false;
430
  this.ignoreAttributesWithoutValue = opt_ignoreAttributesWithoutValue || false;
431
  this.returnOnFirstMatch = opt_returnOnFirstMatch || false;
432
  this.ignoreNonElementNodesForNTA = opt_ignoreNonElementNodesForNTA || false;
433
  if (opt_parent) {
434
    this.root = opt_parent.root;
435
  } else if (this.node.nodeType == DOM_DOCUMENT_NODE) {
436
    // NOTE(mesch): DOM Spec stipulates that the ownerDocument of a
437
    // document is null. Our root, however is the document that we are
438
    // processing, so the initial context is created from its document
439
    // node, which case we must handle here explcitly.
440
    this.root = node;
441
  } else {
442
    this.root = node.ownerDocument;
443
  }
444
}
445
446
ExprContext.prototype.clone = function(opt_node, opt_position, opt_nodelist) {
447
  return new ExprContext(
448
      opt_node || this.node,
449
      typeof opt_position != 'undefined' ? opt_position : this.position,
450
      opt_nodelist || this.nodelist, this, this.caseInsensitive,
451
      this.ignoreAttributesWithoutValue, this.returnOnFirstMatch,
452
      this.ignoreNonElementNodesForNTA);
453
};
454
455
ExprContext.prototype.setVariable = function(name, value) {
456
  if (value instanceof StringValue || value instanceof BooleanValue || 
457
    value instanceof NumberValue || value instanceof NodeSetValue) {
458
    this.variables[name] = value;
459
    return;
460
  }
461
  if ('true' === value) {
462
    this.variables[name] = new BooleanValue(true);
463
  } else if ('false' === value) {
464
    this.variables[name] = new BooleanValue(false);
465
  } else if (TOK_NUMBER.re.test(value)) {
466
    this.variables[name] = new NumberValue(value);
467
  } else {
468
    // DGF What if it's null?
469
    this.variables[name] = new StringValue(value);
470
  }
471
};
472
473
ExprContext.prototype.getVariable = function(name) {
474
  if (typeof this.variables[name] != 'undefined') {
475
    return this.variables[name];
476
477
  } else if (this.parent) {
478
    return this.parent.getVariable(name);
479
480
  } else {
481
    return null;
482
  }
483
};
484
485
ExprContext.prototype.setNode = function(position) {
486
  this.node = this.nodelist[position];
487
  this.position = position;
488
};
489
490
ExprContext.prototype.contextSize = function() {
491
  return this.nodelist.length;
492
};
493
494
ExprContext.prototype.isCaseInsensitive = function() {
495
  return this.caseInsensitive;
496
};
497
498
ExprContext.prototype.setCaseInsensitive = function(caseInsensitive) {
499
  return this.caseInsensitive = caseInsensitive;
500
};
501
502
ExprContext.prototype.isIgnoreAttributesWithoutValue = function() {
503
  return this.ignoreAttributesWithoutValue;
504
};
505
506
ExprContext.prototype.setIgnoreAttributesWithoutValue = function(ignore) {
507
  return this.ignoreAttributesWithoutValue = ignore;
508
};
509
510
ExprContext.prototype.isReturnOnFirstMatch = function() {
511
  return this.returnOnFirstMatch;
512
};
513
514
ExprContext.prototype.setReturnOnFirstMatch = function(returnOnFirstMatch) {
515
  return this.returnOnFirstMatch = returnOnFirstMatch;
516
};
517
518
ExprContext.prototype.isIgnoreNonElementNodesForNTA = function() {
519
  return this.ignoreNonElementNodesForNTA;
520
};
521
522
ExprContext.prototype.setIgnoreNonElementNodesForNTA = function(ignoreNonElementNodesForNTA) {
523
  return this.ignoreNonElementNodesForNTA = ignoreNonElementNodesForNTA;
524
};
525
526
// XPath expression values. They are what XPath expressions evaluate
527
// to. Strangely, the different value types are not specified in the
528
// XPath syntax, but only in the semantics, so they don't show up as
529
// nonterminals in the grammar. Yet, some expressions are required to
530
// evaluate to particular types, and not every type can be coerced
531
// into every other type. Although the types of XPath values are
532
// similar to the types present in JavaScript, the type coercion rules
533
// are a bit peculiar, so we explicitly model XPath types instead of
534
// mapping them onto JavaScript types. (See XPath spec.)
535
//
536
// The four types are:
537
//
538
//   StringValue
539
//
540
//   NumberValue
541
//
542
//   BooleanValue
543
//
544
//   NodeSetValue
545
//
546
// The common interface of the value classes consists of methods that
547
// implement the XPath type coercion rules:
548
//
549
//   stringValue() -- returns the value as a JavaScript String,
550
//
551
//   numberValue() -- returns the value as a JavaScript Number,
552
//
553
//   booleanValue() -- returns the value as a JavaScript Boolean,
554
//
555
//   nodeSetValue() -- returns the value as a JavaScript Array of DOM
556
//   Node objects.
557
//
558
559
function StringValue(value) {
560
  this.value = value;
561
  this.type = 'string';
562
}
563
564
StringValue.prototype.stringValue = function() {
565
  return this.value;
566
}
567
568
StringValue.prototype.booleanValue = function() {
569
  return this.value.length > 0;
570
}
571
572
StringValue.prototype.numberValue = function() {
573
  return this.value - 0;
574
}
575
576
StringValue.prototype.nodeSetValue = function() {
577
  throw this;
578
}
579
580
function BooleanValue(value) {
581
  this.value = value;
582
  this.type = 'boolean';
583
}
584
585
BooleanValue.prototype.stringValue = function() {
586
  return '' + this.value;
587
}
588
589
BooleanValue.prototype.booleanValue = function() {
590
  return this.value;
591
}
592
593
BooleanValue.prototype.numberValue = function() {
594
  return this.value ? 1 : 0;
595
}
596
597
BooleanValue.prototype.nodeSetValue = function() {
598
  throw this;
599
}
600
601
function NumberValue(value) {
602
  this.value = value;
603
  this.type = 'number';
604
}
605
606
NumberValue.prototype.stringValue = function() {
607
  return '' + this.value;
608
}
609
610
NumberValue.prototype.booleanValue = function() {
611
  return !!this.value;
612
}
613
614
NumberValue.prototype.numberValue = function() {
615
  return this.value - 0;
616
}
617
618
NumberValue.prototype.nodeSetValue = function() {
619
  throw this;
620
}
621
622
function NodeSetValue(value) {
623
  this.value = value;
624
  this.type = 'node-set';
625
}
626
627
NodeSetValue.prototype.stringValue = function() {
628
  if (this.value.length == 0) {
629
    return '';
630
  } else {
631
    return xmlValue(this.value[0]);
632
  }
633
}
634
635
NodeSetValue.prototype.booleanValue = function() {
636
  return this.value.length > 0;
637
}
638
639
NodeSetValue.prototype.numberValue = function() {
640
  return this.stringValue() - 0;
641
}
642
643
NodeSetValue.prototype.nodeSetValue = function() {
644
  return this.value;
645
};
646
647
// XPath expressions. They are used as nodes in the parse tree and
648
// possess an evaluate() method to compute an XPath value given an XPath
649
// context. Expressions are returned from the parser. Teh set of
650
// expression classes closely mirrors the set of non terminal symbols
651
// in the grammar. Every non trivial nonterminal symbol has a
652
// corresponding expression class.
653
//
654
// The common expression interface consists of the following methods:
655
//
656
// evaluate(context) -- evaluates the expression, returns a value.
657
//
658
// toString() -- returns the XPath text representation of the
659
// expression (defined in xsltdebug.js).
660
//
661
// parseTree(indent) -- returns a parse tree representation of the
662
// expression (defined in xsltdebug.js).
663
664
function TokenExpr(m) {
665
  this.value = m;
666
}
667
668
TokenExpr.prototype.evaluate = function() {
669
  return new StringValue(this.value);
670
};
671
672
function LocationExpr() {
673
  this.absolute = false;
674
  this.steps = [];
675
}
676
677
LocationExpr.prototype.appendStep = function(s) {
678
  var combinedStep = this._combineSteps(this.steps[this.steps.length-1], s);
679
  if (combinedStep) {
680
    this.steps[this.steps.length-1] = combinedStep;
681
  } else {
682
    this.steps.push(s);
683
  }
684
}
685
686
LocationExpr.prototype.prependStep = function(s) {
687
  var combinedStep = this._combineSteps(s, this.steps[0]);
688
  if (combinedStep) {
689
    this.steps[0] = combinedStep;
690
  } else {
691
    this.steps.unshift(s);
692
  }
693
};
694
695
// DGF try to combine two steps into one step (perf enhancement)
696
LocationExpr.prototype._combineSteps = function(prevStep, nextStep) {
697
  if (!prevStep) return null;
698
  if (!nextStep) return null;
699
  var hasPredicates = (prevStep.predicates && prevStep.predicates.length > 0);
700
  if (prevStep.nodetest instanceof NodeTestAny && !hasPredicates) {
701
    // maybe suitable to be combined
702
    if (prevStep.axis == xpathAxis.DESCENDANT_OR_SELF) {
703
      if (nextStep.axis == xpathAxis.CHILD) {
704
        // HBC - commenting out, because this is not a valid reduction
705
        //nextStep.axis = xpathAxis.DESCENDANT;
706
        //return nextStep;
707
      } else if (nextStep.axis == xpathAxis.SELF) {
708
        nextStep.axis = xpathAxis.DESCENDANT_OR_SELF;
709
        return nextStep;
710
      }
711
    } else if (prevStep.axis == xpathAxis.DESCENDANT) {
712
      if (nextStep.axis == xpathAxis.SELF) {
713
        nextStep.axis = xpathAxis.DESCENDANT;
714
        return nextStep;
715
      }
716
    }
717
  }
718
  return null;
719
}
720
721
LocationExpr.prototype.evaluate = function(ctx) {
722
  var start;
723
  if (this.absolute) {
724
    start = ctx.root;
725
726
  } else {
727
    start = ctx.node;
728
  }
729
730
  var nodes = [];
731
  xPathStep(nodes, this.steps, 0, start, ctx);
732
  return new NodeSetValue(nodes);
733
};
734
735
function xPathStep(nodes, steps, step, input, ctx) {
736
  var s = steps[step];
737
  var ctx2 = ctx.clone(input);
738
  
739
  if (ctx.returnOnFirstMatch && !s.hasPositionalPredicate) {
740
    var nodelist = s.evaluate(ctx2).nodeSetValue();
741
    // the predicates were not processed in the last evaluate(), so that we can
742
    // process them here with the returnOnFirstMatch optimization. We do a
743
    // depth-first grab at any nodes that pass the predicate tests. There is no
744
    // way to optimize when predicates contain positional selectors, including
745
    // indexes or uses of the last() or position() functions, because they
746
    // typically require the entire nodelist for context. Process without
747
    // optimization if we encounter such selectors.
748
    var nLength = nodelist.length;
749
    var pLength = s.predicate.length;
750
    nodelistLoop:
751
    for (var i = 0; i < nLength; ++i) {
752
      var n = nodelist[i];
753
      for (var j = 0; j < pLength; ++j) {
754
        if (!s.predicate[j].evaluate(ctx.clone(n, i, nodelist)).booleanValue()) {
755
          continue nodelistLoop;
756
        }
757
      }
758
      // n survived the predicate tests!
759
      if (step == steps.length - 1) {
760
        nodes.push(n);
761
      }
762
      else {
763
        xPathStep(nodes, steps, step + 1, n, ctx);
764
      }
765
      if (nodes.length > 0) {
766
        break;
767
      }
768
    }
769
  }
770
  else {
771
    // set returnOnFirstMatch to false for the cloned ExprContext, because
772
    // behavior in StepExpr.prototype.evaluate is driven off its value. Note
773
    // that the original context may still have true for this value.
774
    ctx2.returnOnFirstMatch = false;
775
    var nodelist = s.evaluate(ctx2).nodeSetValue();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable nodelist already seems to be declared on line 740. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
776
    for (var i = 0; i < nodelist.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 751. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
777
      if (step == steps.length - 1) {
778
        nodes.push(nodelist[i]);
779
      } else {
780
        xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
781
      }
782
    }
783
  }
784
}
785
786
function StepExpr(axis, nodetest, opt_predicate) {
787
  this.axis = axis;
788
  this.nodetest = nodetest;
789
  this.predicate = opt_predicate || [];
790
  this.hasPositionalPredicate = false;
791
  for (var i = 0; i < this.predicate.length; ++i) {
792
    if (predicateExprHasPositionalSelector(this.predicate[i].expr)) {
793
      this.hasPositionalPredicate = true;
794
      break;
795
    }
796
  }
797
}
798
799
StepExpr.prototype.appendPredicate = function(p) {
800
  this.predicate.push(p);
801
  if (!this.hasPositionalPredicate) {
802
    this.hasPositionalPredicate = predicateExprHasPositionalSelector(p.expr);
803
  }
804
}
805
806
StepExpr.prototype.evaluate = function(ctx) {
807
  var input = ctx.node;
808
  var nodelist = [];
809
  var skipNodeTest = false;
810
  
811
  if (this.nodetest instanceof NodeTestAny) {
812
    skipNodeTest = true;
813
  }
814
815
  // NOTE(mesch): When this was a switch() statement, it didn't work
816
  // in Safari/2.0. Not sure why though; it resulted in the JavaScript
817
  // console output "undefined" (without any line number or so).
818
819
  if (this.axis ==  xpathAxis.ANCESTOR_OR_SELF) {
820
    nodelist.push(input);
821
    for (var n = input.parentNode; n; n = n.parentNode) {
822
      nodelist.push(n);
823
    }
824
825
  } else if (this.axis == xpathAxis.ANCESTOR) {
826
    for (var n = input.parentNode; n; n = n.parentNode) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 821. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
827
      nodelist.push(n);
828
    }
829
830
  } else if (this.axis == xpathAxis.ATTRIBUTE) {
831
    if (this.nodetest.name != undefined) {
832
      // single-attribute step
833
      if (input.attributes) {
834
        if (input.attributes instanceof Array) {
835
          // probably evaluating on document created by xmlParse()
836
          copyArray(nodelist, input.attributes);
837
        }
838
        else {
839
          if (this.nodetest.name == 'style') {
840
            var value = input.getAttribute('style');
841
            if (value && typeof(value) != 'string') {
842
              // this is the case where indexing into the attributes array
843
              // doesn't give us the attribute node in IE - we create our own
844
              // node instead
845
              nodelist.push(XNode.create(DOM_ATTRIBUTE_NODE, 'style',
846
                value.cssText, document));
847
            }
848
            else {
849
              nodelist.push(input.attributes[this.nodetest.name]);
850
            }
851
          }
852
          else {
853
            nodelist.push(input.attributes[this.nodetest.name]);
854
          }
855
        }
856
      }
857
    }
858
    else {
859
      // all-attributes step
860
      if (ctx.ignoreAttributesWithoutValue) {
861
        copyArrayIgnoringAttributesWithoutValue(nodelist, input.attributes);
862
      }
863
      else {
864
        copyArray(nodelist, input.attributes);
865
      }
866
    }
867
    
868
  } else if (this.axis == xpathAxis.CHILD) {
869
    copyArray(nodelist, input.childNodes);
870
871
  } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
872
    if (this.nodetest.evaluate(ctx).booleanValue()) {
873
      nodelist.push(input);
874
    }
875
    var tagName = xpathExtractTagNameFromNodeTest(this.nodetest, ctx.ignoreNonElementNodesForNTA);
876
    xpathCollectDescendants(nodelist, input, tagName);
877
    if (tagName) skipNodeTest = true;
878
879
  } else if (this.axis == xpathAxis.DESCENDANT) {
880
    var tagName = xpathExtractTagNameFromNodeTest(this.nodetest, ctx.ignoreNonElementNodesForNTA);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable tagName already seems to be declared on line 875. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
881
    xpathCollectDescendants(nodelist, input, tagName);
882
    if (tagName) skipNodeTest = true;
883
884
  } else if (this.axis == xpathAxis.FOLLOWING) {
885
    for (var n = input; n; n = n.parentNode) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 821. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
886
      for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
887
        nodelist.push(nn);
888
        xpathCollectDescendants(nodelist, nn);
889
      }
890
    }
891
892
  } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {
893
    for (var n = input.nextSibling; n; n = n.nextSibling) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 821. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
894
      nodelist.push(n);
895
    }
896
897
  } else if (this.axis == xpathAxis.NAMESPACE) {
898
    alert('not implemented: axis namespace');
899
900
  } else if (this.axis == xpathAxis.PARENT) {
901
    if (input.parentNode) {
902
      nodelist.push(input.parentNode);
903
    }
904
905
  } else if (this.axis == xpathAxis.PRECEDING) {
906
    for (var n = input; n; n = n.parentNode) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 821. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
907
      for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable nn already seems to be declared on line 886. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
908
        nodelist.push(nn);
909
        xpathCollectDescendantsReverse(nodelist, nn);
910
      }
911
    }
912
913
  } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {
914
    for (var n = input.previousSibling; n; n = n.previousSibling) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 821. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
915
      nodelist.push(n);
916
    }
917
918
  } else if (this.axis == xpathAxis.SELF) {
919
    nodelist.push(input);
920
921
  } else {
922
    throw 'ERROR -- NO SUCH AXIS: ' + this.axis;
923
  }
924
925
  if (!skipNodeTest) {
926
    // process node test
927
    var nodelist0 = nodelist;
928
    nodelist = [];
929
    for (var i = 0; i < nodelist0.length; ++i) {
930
      var n = nodelist0[i];
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 821. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
931
      if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {
932
        nodelist.push(n);
933
      }
934
    }
935
  }
936
937
  // process predicates
938
  if (!ctx.returnOnFirstMatch) {
939
    for (var i = 0; i < this.predicate.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 929. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
940
      var nodelist0 = nodelist;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable nodelist0 already seems to be declared on line 927. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
941
      nodelist = [];
942
      for (var ii = 0; ii < nodelist0.length; ++ii) {
943
        var n = nodelist0[ii];
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 821. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
944
        if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {
945
          nodelist.push(n);
946
        }
947
      }
948
    }
949
  }
950
951
  return new NodeSetValue(nodelist);
952
};
953
954
function NodeTestAny() {
955
  this.value = new BooleanValue(true);
956
}
957
958
NodeTestAny.prototype.evaluate = function(ctx) {
0 ignored issues
show
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
959
  return this.value;
960
};
961
962
function NodeTestElementOrAttribute() {}
963
964
NodeTestElementOrAttribute.prototype.evaluate = function(ctx) {
965
  return new BooleanValue(
966
      ctx.node.nodeType == DOM_ELEMENT_NODE ||
967
      ctx.node.nodeType == DOM_ATTRIBUTE_NODE);
968
}
969
970
function NodeTestText() {}
971
972
NodeTestText.prototype.evaluate = function(ctx) {
973
  return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);
974
}
975
976
function NodeTestComment() {}
977
978
NodeTestComment.prototype.evaluate = function(ctx) {
979
  return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);
980
}
981
982
function NodeTestPI(target) {
983
  this.target = target;
984
}
985
986
NodeTestPI.prototype.evaluate = function(ctx) {
987
  return new
988
  BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &&
989
               (!this.target || ctx.node.nodeName == this.target));
990
}
991
992
function NodeTestNC(nsprefix) {
993
  this.regex = new RegExp("^" + nsprefix + ":");
994
  this.nsprefix = nsprefix;
995
}
996
997
NodeTestNC.prototype.evaluate = function(ctx) {
998
  var n = ctx.node;
999
  return new BooleanValue(this.regex.match(n.nodeName));
1000
}
1001
1002
function NodeTestName(name) {
1003
  this.name = name;
1004
  this.re = new RegExp('^' + name + '$', "i");
1005
}
1006
1007
NodeTestName.prototype.evaluate = function(ctx) {
1008
  var n = ctx.node;
1009
  if (ctx.caseInsensitive) {
1010
    if (n.nodeName.length != this.name.length) return new BooleanValue(false);
1011
    return new BooleanValue(this.re.test(n.nodeName));
1012
  } else {
1013
    return new BooleanValue(n.nodeName == this.name);
1014
  }
1015
}
1016
1017
function PredicateExpr(expr) {
1018
  this.expr = expr;
1019
}
1020
1021
PredicateExpr.prototype.evaluate = function(ctx) {
1022
  var v = this.expr.evaluate(ctx);
1023
  if (v.type == 'number') {
1024
    // NOTE(mesch): Internally, position is represented starting with
1025
    // 0, however in XPath position starts with 1. See functions
1026
    // position() and last().
1027
    return new BooleanValue(ctx.position == v.numberValue() - 1);
1028
  } else {
1029
    return new BooleanValue(v.booleanValue());
1030
  }
1031
};
1032
1033
function FunctionCallExpr(name) {
1034
  this.name = name;
1035
  this.args = [];
1036
}
1037
1038
FunctionCallExpr.prototype.appendArg = function(arg) {
1039
  this.args.push(arg);
1040
};
1041
1042
FunctionCallExpr.prototype.evaluate = function(ctx) {
1043
  var fn = '' + this.name.value;
1044
  var f = this.xpathfunctions[fn];
1045
  if (f) {
1046
    return f.call(this, ctx);
1047
  } else {
1048
    xpathLog('XPath NO SUCH FUNCTION ' + fn);
1049
    return new BooleanValue(false);
1050
  }
1051
};
1052
1053
FunctionCallExpr.prototype.xpathfunctions = {
1054
  'last': function(ctx) {
1055
    assert(this.args.length == 0);
1056
    // NOTE(mesch): XPath position starts at 1.
1057
    return new NumberValue(ctx.contextSize());
1058
  },
1059
1060
  'position': function(ctx) {
1061
    assert(this.args.length == 0);
1062
    // NOTE(mesch): XPath position starts at 1.
1063
    return new NumberValue(ctx.position + 1);
1064
  },
1065
1066
  'count': function(ctx) {
1067
    assert(this.args.length == 1);
1068
    var v = this.args[0].evaluate(ctx);
1069
    return new NumberValue(v.nodeSetValue().length);
1070
  },
1071
1072
  'id': function(ctx) {
1073
    assert(this.args.length == 1);
1074
    var e = this.args[0].evaluate(ctx);
1075
    var ret = [];
1076
    var ids;
1077
    if (e.type == 'node-set') {
1078
      ids = [];
1079
      var en = e.nodeSetValue();
1080
      for (var i = 0; i < en.length; ++i) {
1081
        var v = xmlValue(en[i]).split(/\s+/);
1082
        for (var ii = 0; ii < v.length; ++ii) {
1083
          ids.push(v[ii]);
1084
        }
1085
      }
1086
    } else {
1087
      ids = e.stringValue().split(/\s+/);
1088
    }
1089
    var d = ctx.root;
1090
    for (var i = 0; i < ids.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 1080. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1091
      var n = d.getElementById(ids[i]);
1092
      if (n) {
1093
        ret.push(n);
1094
      }
1095
    }
1096
    return new NodeSetValue(ret);
1097
  },
1098
1099
  'local-name': function(ctx) {
0 ignored issues
show
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1100
    alert('not implmented yet: XPath function local-name()');
1101
  },
1102
1103
  'namespace-uri': function(ctx) {
0 ignored issues
show
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1104
    alert('not implmented yet: XPath function namespace-uri()');
1105
  },
1106
1107
  'name': function(ctx) {
1108
    assert(this.args.length == 1 || this.args.length == 0);
1109
    var n;
1110
    if (this.args.length == 0) {
1111
      n = [ ctx.node ];
1112
    } else {
1113
      n = this.args[0].evaluate(ctx).nodeSetValue();
1114
    }
1115
1116
    if (n.length == 0) {
1117
      return new StringValue('');
1118
    } else {
1119
      return new StringValue(n[0].nodeName);
1120
    }
1121
  },
1122
1123
  'string':  function(ctx) {
1124
    assert(this.args.length == 1 || this.args.length == 0);
1125
    if (this.args.length == 0) {
1126
      return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());
1127
    } else {
1128
      return new StringValue(this.args[0].evaluate(ctx).stringValue());
1129
    }
1130
  },
1131
1132
  'concat': function(ctx) {
1133
    var ret = '';
1134
    for (var i = 0; i < this.args.length; ++i) {
1135
      ret += this.args[i].evaluate(ctx).stringValue();
1136
    }
1137
    return new StringValue(ret);
1138
  },
1139
1140
  'starts-with': function(ctx) {
1141
    assert(this.args.length == 2);
1142
    var s0 = this.args[0].evaluate(ctx).stringValue();
1143
    var s1 = this.args[1].evaluate(ctx).stringValue();
1144
    return new BooleanValue(s0.indexOf(s1) == 0);
1145
  },
1146
  
1147
  'ends-with': function(ctx) {
1148
    assert(this.args.length == 2);
1149
    var s0 = this.args[0].evaluate(ctx).stringValue();
1150
    var s1 = this.args[1].evaluate(ctx).stringValue();
1151
    var re = new RegExp(RegExp.escape(s1) + '$');
1152
    return new BooleanValue(re.test(s0));
1153
  },
1154
1155
  'contains': function(ctx) {
1156
    assert(this.args.length == 2);
1157
    var s0 = this.args[0].evaluate(ctx).stringValue();
1158
    var s1 = this.args[1].evaluate(ctx).stringValue();
1159
    return new BooleanValue(s0.indexOf(s1) != -1);
1160
  },
1161
1162
  'substring-before': function(ctx) {
1163
    assert(this.args.length == 2);
1164
    var s0 = this.args[0].evaluate(ctx).stringValue();
1165
    var s1 = this.args[1].evaluate(ctx).stringValue();
1166
    var i = s0.indexOf(s1);
1167
    var ret;
1168
    if (i == -1) {
1169
      ret = '';
1170
    } else {
1171
      ret = s0.substr(0,i);
1172
    }
1173
    return new StringValue(ret);
1174
  },
1175
1176
  'substring-after': function(ctx) {
1177
    assert(this.args.length == 2);
1178
    var s0 = this.args[0].evaluate(ctx).stringValue();
1179
    var s1 = this.args[1].evaluate(ctx).stringValue();
1180
    var i = s0.indexOf(s1);
1181
    var ret;
1182
    if (i == -1) {
1183
      ret = '';
1184
    } else {
1185
      ret = s0.substr(i + s1.length);
1186
    }
1187
    return new StringValue(ret);
1188
  },
1189
1190
  'substring': function(ctx) {
1191
    // NOTE: XPath defines the position of the first character in a
1192
    // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).
1193
    assert(this.args.length == 2 || this.args.length == 3);
1194
    var s0 = this.args[0].evaluate(ctx).stringValue();
1195
    var s1 = this.args[1].evaluate(ctx).numberValue();
1196
    var ret;
1197
    if (this.args.length == 2) {
1198
      var i1 = Math.max(0, Math.round(s1) - 1);
1199
      ret = s0.substr(i1);
1200
1201
    } else {
1202
      var s2 = this.args[2].evaluate(ctx).numberValue();
1203
      var i0 = Math.round(s1) - 1;
1204
      var i1 = Math.max(0, i0);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i1 already seems to be declared on line 1198. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1205
      var i2 = Math.round(s2) - Math.max(0, -i0);
1206
      ret = s0.substr(i1, i2);
1207
    }
1208
    return new StringValue(ret);
1209
  },
1210
1211
  'string-length': function(ctx) {
1212
    var s;
1213
    if (this.args.length > 0) {
1214
      s = this.args[0].evaluate(ctx).stringValue();
1215
    } else {
1216
      s = new NodeSetValue([ ctx.node ]).stringValue();
1217
    }
1218
    return new NumberValue(s.length);
1219
  },
1220
1221
  'normalize-space': function(ctx) {
1222
    var s;
1223
    if (this.args.length > 0) {
1224
      s = this.args[0].evaluate(ctx).stringValue();
1225
    } else {
1226
      s = new NodeSetValue([ ctx.node ]).stringValue();
1227
    }
1228
    s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');
1229
    return new StringValue(s);
1230
  },
1231
1232
  'translate': function(ctx) {
1233
    assert(this.args.length == 3);
1234
    var s0 = this.args[0].evaluate(ctx).stringValue();
1235
    var s1 = this.args[1].evaluate(ctx).stringValue();
1236
    var s2 = this.args[2].evaluate(ctx).stringValue();
1237
1238
    for (var i = 0; i < s1.length; ++i) {
1239
      s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));
1240
    }
1241
    return new StringValue(s0);
1242
  },
1243
  
1244
  'matches': function(ctx) {
1245
    assert(this.args.length >= 2);
1246
    var s0 = this.args[0].evaluate(ctx).stringValue();
1247
    var s1 = this.args[1].evaluate(ctx).stringValue();
1248
    if (this.args.length > 2) {
1249
      var s2 = this.args[2].evaluate(ctx).stringValue();
1250
      if (/[^mi]/.test(s2)) {
1251
        throw 'Invalid regular expression syntax: ' + s2;
1252
      }
1253
    }
1254
    
1255
    try {
1256
      var re = new RegExp(s1, s2);
0 ignored issues
show
Bug introduced by
The variable s2 does not seem to be initialized in case this.args.length > 2 on line 1248 is false. Are you sure this can never be the case?
Loading history...
1257
    }
1258
    catch (e) {
1259
      throw 'Invalid matches argument: ' + s1;
1260
    }
1261
    return new BooleanValue(re.test(s0));
1262
  },
1263
1264
  'boolean': function(ctx) {
1265
    assert(this.args.length == 1);
1266
    return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
1267
  },
1268
1269
  'not': function(ctx) {
1270
    assert(this.args.length == 1);
1271
    var ret = !this.args[0].evaluate(ctx).booleanValue();
1272
    return new BooleanValue(ret);
1273
  },
1274
1275
  'true': function(ctx) {
0 ignored issues
show
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1276
    assert(this.args.length == 0);
1277
    return new BooleanValue(true);
1278
  },
1279
1280
  'false': function(ctx) {
0 ignored issues
show
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1281
    assert(this.args.length == 0);
1282
    return new BooleanValue(false);
1283
  },
1284
1285
  'lang': function(ctx) {
1286
    assert(this.args.length == 1);
1287
    var lang = this.args[0].evaluate(ctx).stringValue();
1288
    var xmllang;
1289
    var n = ctx.node;
1290
    while (n && n != n.parentNode /* just in case ... */) {
1291
      xmllang = n.getAttribute('xml:lang');
1292
      if (xmllang) {
1293
        break;
1294
      }
1295
      n = n.parentNode;
1296
    }
1297
    if (!xmllang) {
1298
      return new BooleanValue(false);
1299
    } else {
1300
      var re = new RegExp('^' + lang + '$', 'i');
1301
      return new BooleanValue(xmllang.match(re) ||
1302
                              xmllang.replace(/_.*$/,'').match(re));
1303
    }
1304
  },
1305
1306
  'number': function(ctx) {
1307
    assert(this.args.length == 1 || this.args.length == 0);
1308
1309
    if (this.args.length == 1) {
1310
      return new NumberValue(this.args[0].evaluate(ctx).numberValue());
1311
    } else {
1312
      return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());
1313
    }
1314
  },
1315
1316
  'sum': function(ctx) {
1317
    assert(this.args.length == 1);
1318
    var n = this.args[0].evaluate(ctx).nodeSetValue();
1319
    var sum = 0;
1320
    for (var i = 0; i < n.length; ++i) {
1321
      sum += xmlValue(n[i]) - 0;
1322
    }
1323
    return new NumberValue(sum);
1324
  },
1325
1326
  'floor': function(ctx) {
1327
    assert(this.args.length == 1);
1328
    var num = this.args[0].evaluate(ctx).numberValue();
1329
    return new NumberValue(Math.floor(num));
1330
  },
1331
1332
  'ceiling': function(ctx) {
1333
    assert(this.args.length == 1);
1334
    var num = this.args[0].evaluate(ctx).numberValue();
1335
    return new NumberValue(Math.ceil(num));
1336
  },
1337
1338
  'round': function(ctx) {
1339
    assert(this.args.length == 1);
1340
    var num = this.args[0].evaluate(ctx).numberValue();
1341
    return new NumberValue(Math.round(num));
1342
  },
1343
1344
  // TODO(mesch): The following functions are custom. There is a
1345
  // standard that defines how to add functions, which should be
1346
  // applied here.
1347
1348
  'ext-join': function(ctx) {
1349
    assert(this.args.length == 2);
1350
    var nodes = this.args[0].evaluate(ctx).nodeSetValue();
1351
    var delim = this.args[1].evaluate(ctx).stringValue();
1352
    var ret = '';
1353
    for (var i = 0; i < nodes.length; ++i) {
1354
      if (ret) {
1355
        ret += delim;
1356
      }
1357
      ret += xmlValue(nodes[i]);
1358
    }
1359
    return new StringValue(ret);
1360
  },
1361
1362
  // ext-if() evaluates and returns its second argument, if the
1363
  // boolean value of its first argument is true, otherwise it
1364
  // evaluates and returns its third argument.
1365
1366
  'ext-if': function(ctx) {
1367
    assert(this.args.length == 3);
1368
    if (this.args[0].evaluate(ctx).booleanValue()) {
1369
      return this.args[1].evaluate(ctx);
1370
    } else {
1371
      return this.args[2].evaluate(ctx);
1372
    }
1373
  },
1374
1375
  // ext-cardinal() evaluates its single argument as a number, and
1376
  // returns the current node that many times. It can be used in the
1377
  // select attribute to iterate over an integer range.
1378
1379
  'ext-cardinal': function(ctx) {
1380
    assert(this.args.length >= 1);
1381
    var c = this.args[0].evaluate(ctx).numberValue();
1382
    var ret = [];
1383
    for (var i = 0; i < c; ++i) {
1384
      ret.push(ctx.node);
1385
    }
1386
    return new NodeSetValue(ret);
1387
  }
1388
};
1389
1390
function UnionExpr(expr1, expr2) {
1391
  this.expr1 = expr1;
1392
  this.expr2 = expr2;
1393
}
1394
1395
UnionExpr.prototype.evaluate = function(ctx) {
1396
  var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();
1397
  var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();
1398
  var I1 = nodes1.length;
1399
  for (var i2 = 0; i2 < nodes2.length; ++i2) {
1400
    var n = nodes2[i2];
1401
    var inBoth = false;
1402
    for (var i1 = 0; i1 < I1; ++i1) {
1403
      if (nodes1[i1] == n) {
1404
        inBoth = true;
1405
        i1 = I1; // break inner loop
1406
      }
1407
    }
1408
    if (!inBoth) {
1409
      nodes1.push(n);
1410
    }
1411
  }
1412
  return new NodeSetValue(nodes1);
1413
};
1414
1415
function PathExpr(filter, rel) {
1416
  this.filter = filter;
1417
  this.rel = rel;
1418
}
1419
1420
PathExpr.prototype.evaluate = function(ctx) {
1421
  // the filter expression should be evaluated in its entirety with no
1422
  // optimization, as we can't backtrack to it after having moved on to
1423
  // evaluating the relative location path
1424
  var flag = ctx.returnOnFirstMatch;
1425
  ctx.setReturnOnFirstMatch(false);
1426
  var nodes = this.filter.evaluate(ctx).nodeSetValue();
1427
  ctx.setReturnOnFirstMatch(flag);
1428
  
1429
  var nodes1 = [];
1430
  if (ctx.returnOnFirstMatch) {
1431
    for (var i = 0; i < nodes.length; ++i) {
1432
      nodes1 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
1433
      if (nodes1.length > 0) {
1434
        break;
1435
      }
1436
    }
1437
    return new NodeSetValue(nodes1);
1438
  }
1439
  else {
1440
    for (var i = 0; i < nodes.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 1431. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1441
      var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
1442
      for (var ii = 0; ii < nodes0.length; ++ii) {
1443
        nodes1.push(nodes0[ii]);
1444
      }
1445
    }
1446
    return new NodeSetValue(nodes1);
1447
  }
1448
};
1449
1450
function FilterExpr(expr, predicate) {
1451
  this.expr = expr;
1452
  this.predicate = predicate;
1453
}
1454
1455
FilterExpr.prototype.evaluate = function(ctx) {
1456
  var flag = ctx.returnOnFirstMatch;
1457
  ctx.setReturnOnFirstMatch(false);
1458
  var nodes = this.expr.evaluate(ctx).nodeSetValue();
1459
  ctx.setReturnOnFirstMatch(flag);
1460
  
1461
  for (var i = 0; i < this.predicate.length; ++i) {
1462
    var nodes0 = nodes;
1463
    nodes = [];
1464
    for (var j = 0; j < nodes0.length; ++j) {
1465
      var n = nodes0[j];
1466
      if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {
1467
        nodes.push(n);
1468
      }
1469
    }
1470
  }
1471
1472
  return new NodeSetValue(nodes);
1473
}
1474
1475
function UnaryMinusExpr(expr) {
1476
  this.expr = expr;
1477
}
1478
1479
UnaryMinusExpr.prototype.evaluate = function(ctx) {
1480
  return new NumberValue(-this.expr.evaluate(ctx).numberValue());
1481
};
1482
1483
function BinaryExpr(expr1, op, expr2) {
1484
  this.expr1 = expr1;
1485
  this.expr2 = expr2;
1486
  this.op = op;
1487
}
1488
1489
BinaryExpr.prototype.evaluate = function(ctx) {
1490
  var ret;
1491
  switch (this.op.value) {
1492
    case 'or':
1493
      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||
1494
                             this.expr2.evaluate(ctx).booleanValue());
1495
      break;
1496
1497
    case 'and':
1498
      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &&
1499
                             this.expr2.evaluate(ctx).booleanValue());
1500
      break;
1501
1502
    case '+':
1503
      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +
1504
                            this.expr2.evaluate(ctx).numberValue());
1505
      break;
1506
1507
    case '-':
1508
      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -
1509
                            this.expr2.evaluate(ctx).numberValue());
1510
      break;
1511
1512
    case '*':
1513
      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *
1514
                            this.expr2.evaluate(ctx).numberValue());
1515
      break;
1516
1517
    case 'mod':
1518
      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %
1519
                            this.expr2.evaluate(ctx).numberValue());
1520
      break;
1521
1522
    case 'div':
1523
      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /
1524
                            this.expr2.evaluate(ctx).numberValue());
1525
      break;
1526
1527
    case '=':
1528
      ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });
1529
      break;
1530
1531
    case '!=':
1532
      ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });
1533
      break;
1534
1535
    case '<':
1536
      ret = this.compare(ctx, function(x1, x2) { return x1 < x2; });
1537
      break;
1538
1539
    case '<=':
1540
      ret = this.compare(ctx, function(x1, x2) { return x1 <= x2; });
1541
      break;
1542
1543
    case '>':
1544
      ret = this.compare(ctx, function(x1, x2) { return x1 > x2; });
1545
      break;
1546
1547
    case '>=':
1548
      ret = this.compare(ctx, function(x1, x2) { return x1 >= x2; });
1549
      break;
1550
1551
    default:
1552
      alert('BinaryExpr.evaluate: ' + this.op.value);
1553
  }
1554
  return ret;
0 ignored issues
show
Bug introduced by
The variable ret seems to not be initialized for all possible execution paths.
Loading history...
1555
};
1556
1557
BinaryExpr.prototype.compare = function(ctx, cmp) {
1558
  var v1 = this.expr1.evaluate(ctx);
1559
  var v2 = this.expr2.evaluate(ctx);
1560
1561
  var ret;
1562
  if (v1.type == 'node-set' && v2.type == 'node-set') {
1563
    var n1 = v1.nodeSetValue();
1564
    var n2 = v2.nodeSetValue();
1565
    ret = false;
1566
    for (var i1 = 0; i1 < n1.length; ++i1) {
1567
      for (var i2 = 0; i2 < n2.length; ++i2) {
1568
        if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {
1569
          ret = true;
1570
          // Break outer loop. Labels confuse the jscompiler and we
1571
          // don't use them.
1572
          i2 = n2.length;
1573
          i1 = n1.length;
1574
        }
1575
      }
1576
    }
1577
1578
  } else if (v1.type == 'node-set' || v2.type == 'node-set') {
1579
1580
    if (v1.type == 'number') {
1581
      var s = v1.numberValue();
1582
      var n = v2.nodeSetValue();
1583
1584
      ret = false;
1585
      for (var i = 0;  i < n.length; ++i) {
1586
        var nn = xmlValue(n[i]) - 0;
1587
        if (cmp(s, nn)) {
1588
          ret = true;
1589
          break;
1590
        }
1591
      }
1592
1593
    } else if (v2.type == 'number') {
1594
      var n = v1.nodeSetValue();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 1582. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1595
      var s = v2.numberValue();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable s already seems to be declared on line 1581. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1596
1597
      ret = false;
1598
      for (var i = 0;  i < n.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 1585. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1599
        var nn = xmlValue(n[i]) - 0;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable nn already seems to be declared on line 1586. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1600
        if (cmp(nn, s)) {
1601
          ret = true;
1602
          break;
1603
        }
1604
      }
1605
1606
    } else if (v1.type == 'string') {
1607
      var s = v1.stringValue();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable s already seems to be declared on line 1581. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1608
      var n = v2.nodeSetValue();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 1582. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1609
1610
      ret = false;
1611
      for (var i = 0;  i < n.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 1585. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1612
        var nn = xmlValue(n[i]);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable nn already seems to be declared on line 1586. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1613
        if (cmp(s, nn)) {
1614
          ret = true;
1615
          break;
1616
        }
1617
      }
1618
1619
    } else if (v2.type == 'string') {
1620
      var n = v1.nodeSetValue();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable n already seems to be declared on line 1582. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1621
      var s = v2.stringValue();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable s already seems to be declared on line 1581. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1622
1623
      ret = false;
1624
      for (var i = 0;  i < n.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 1585. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1625
        var nn = xmlValue(n[i]);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable nn already seems to be declared on line 1586. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1626
        if (cmp(nn, s)) {
1627
          ret = true;
1628
          break;
1629
        }
1630
      }
1631
1632
    } else {
1633
      ret = cmp(v1.booleanValue(), v2.booleanValue());
1634
    }
1635
1636
  } else if (v1.type == 'boolean' || v2.type == 'boolean') {
1637
    ret = cmp(v1.booleanValue(), v2.booleanValue());
1638
1639
  } else if (v1.type == 'number' || v2.type == 'number') {
1640
    ret = cmp(v1.numberValue(), v2.numberValue());
1641
1642
  } else {
1643
    ret = cmp(v1.stringValue(), v2.stringValue());
1644
  }
1645
1646
  return new BooleanValue(ret);
1647
}
1648
1649
function LiteralExpr(value) {
1650
  this.value = value;
1651
}
1652
1653
LiteralExpr.prototype.evaluate = function(ctx) {
0 ignored issues
show
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1654
  return new StringValue(this.value);
1655
};
1656
1657
function NumberExpr(value) {
1658
  this.value = value;
1659
}
1660
1661
NumberExpr.prototype.evaluate = function(ctx) {
0 ignored issues
show
Unused Code introduced by
The parameter ctx is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1662
  return new NumberValue(this.value);
1663
};
1664
1665
function VariableExpr(name) {
1666
  this.name = name;
1667
}
1668
1669
VariableExpr.prototype.evaluate = function(ctx) {
1670
  return ctx.getVariable(this.name);
1671
}
1672
1673
// Factory functions for semantic values (i.e. Expressions) of the
1674
// productions in the grammar. When a production is matched to reduce
1675
// the current parse state stack, the function is called with the
1676
// semantic values of the matched elements as arguments, and returns
1677
// another semantic value. The semantic value is a node of the parse
1678
// tree, an expression object with an evaluate() method that evaluates the
1679
// expression in an actual context. These factory functions are used
1680
// in the specification of the grammar rules, below.
1681
1682
function makeTokenExpr(m) {
1683
  return new TokenExpr(m);
1684
}
1685
1686
function passExpr(e) {
1687
  return e;
1688
}
1689
1690
function makeLocationExpr1(slash, rel) {
1691
  rel.absolute = true;
1692
  return rel;
1693
}
1694
1695
function makeLocationExpr2(dslash, rel) {
1696
  rel.absolute = true;
1697
  rel.prependStep(makeAbbrevStep(dslash.value));
1698
  return rel;
1699
}
1700
1701
function makeLocationExpr3(slash) {
0 ignored issues
show
Unused Code introduced by
The parameter slash is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1702
  var ret = new LocationExpr();
1703
  ret.appendStep(makeAbbrevStep('.'));
1704
  ret.absolute = true;
1705
  return ret;
1706
}
1707
1708
function makeLocationExpr4(dslash) {
1709
  var ret = new LocationExpr();
1710
  ret.absolute = true;
1711
  ret.appendStep(makeAbbrevStep(dslash.value));
1712
  return ret;
1713
}
1714
1715
function makeLocationExpr5(step) {
1716
  var ret = new LocationExpr();
1717
  ret.appendStep(step);
1718
  return ret;
1719
}
1720
1721
function makeLocationExpr6(rel, slash, step) {
1722
  rel.appendStep(step);
1723
  return rel;
1724
}
1725
1726
function makeLocationExpr7(rel, dslash, step) {
1727
  rel.appendStep(makeAbbrevStep(dslash.value));
1728
  rel.appendStep(step);
1729
  return rel;
1730
}
1731
1732
function makeStepExpr1(dot) {
1733
  return makeAbbrevStep(dot.value);
1734
}
1735
1736
function makeStepExpr2(ddot) {
1737
  return makeAbbrevStep(ddot.value);
1738
}
1739
1740
function makeStepExpr3(axisname, axis, nodetest) {
1741
  return new StepExpr(axisname.value, nodetest);
1742
}
1743
1744
function makeStepExpr4(at, nodetest) {
1745
  return new StepExpr('attribute', nodetest);
1746
}
1747
1748
function makeStepExpr5(nodetest) {
1749
  return new StepExpr('child', nodetest);
1750
}
1751
1752
function makeStepExpr6(step, predicate) {
1753
  step.appendPredicate(predicate);
1754
  return step;
1755
}
1756
1757
function makeAbbrevStep(abbrev) {
1758
  switch (abbrev) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1759
  case '//':
1760
    return new StepExpr('descendant-or-self', new NodeTestAny);
1761
1762
  case '.':
1763
    return new StepExpr('self', new NodeTestAny);
1764
1765
  case '..':
1766
    return new StepExpr('parent', new NodeTestAny);
1767
  }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
1768
}
1769
1770
function makeNodeTestExpr1(asterisk) {
0 ignored issues
show
Unused Code introduced by
The parameter asterisk is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1771
  return new NodeTestElementOrAttribute;
1772
}
1773
1774
function makeNodeTestExpr2(ncname, colon, asterisk) {
0 ignored issues
show
Unused Code introduced by
The parameter asterisk is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter colon is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1775
  return new NodeTestNC(ncname.value);
1776
}
1777
1778
function makeNodeTestExpr3(qname) {
1779
  return new NodeTestName(qname.value);
1780
}
1781
1782
function makeNodeTestExpr4(typeo, parenc) {
0 ignored issues
show
Unused Code introduced by
The parameter parenc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1783
  var type = typeo.value.replace(/\s*\($/, '');
1784
  switch(type) {
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1785
  case 'node':
1786
    return new NodeTestAny;
1787
1788
  case 'text':
1789
    return new NodeTestText;
1790
1791
  case 'comment':
1792
    return new NodeTestComment;
1793
1794
  case 'processing-instruction':
1795
    return new NodeTestPI('');
1796
  }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
1797
}
1798
1799
function makeNodeTestExpr5(typeo, target, parenc) {
0 ignored issues
show
Unused Code introduced by
The parameter parenc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1800
  var type = typeo.replace(/\s*\($/, '');
1801
  if (type != 'processing-instruction') {
1802
    throw type;
1803
  }
1804
  return new NodeTestPI(target.value);
1805
}
1806
1807
function makePredicateExpr(pareno, expr, parenc) {
0 ignored issues
show
Unused Code introduced by
The parameter parenc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1808
  return new PredicateExpr(expr);
1809
}
1810
1811
function makePrimaryExpr(pareno, expr, parenc) {
0 ignored issues
show
Unused Code introduced by
The parameter parenc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1812
  return expr;
1813
}
1814
1815
function makeFunctionCallExpr1(name, pareno, parenc) {
0 ignored issues
show
Unused Code introduced by
The parameter pareno is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter parenc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1816
  return new FunctionCallExpr(name);
1817
}
1818
1819
function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {
0 ignored issues
show
Unused Code introduced by
The parameter parenc is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
1820
  var ret = new FunctionCallExpr(name);
1821
  ret.appendArg(arg1);
1822
  for (var i = 0; i < args.length; ++i) {
1823
    ret.appendArg(args[i]);
1824
  }
1825
  return ret;
1826
}
1827
1828
function makeArgumentExpr(comma, expr) {
1829
  return expr;
1830
}
1831
1832
function makeUnionExpr(expr1, pipe, expr2) {
1833
  return new UnionExpr(expr1, expr2);
1834
}
1835
1836
function makePathExpr1(filter, slash, rel) {
1837
  return new PathExpr(filter, rel);
1838
}
1839
1840
function makePathExpr2(filter, dslash, rel) {
1841
  rel.prependStep(makeAbbrevStep(dslash.value));
1842
  return new PathExpr(filter, rel);
1843
}
1844
1845
function makeFilterExpr(expr, predicates) {
1846
  if (predicates.length > 0) {
1847
    return new FilterExpr(expr, predicates);
1848
  } else {
1849
    return expr;
1850
  }
1851
}
1852
1853
function makeUnaryMinusExpr(minus, expr) {
1854
  return new UnaryMinusExpr(expr);
1855
}
1856
1857
function makeBinaryExpr(expr1, op, expr2) {
1858
  return new BinaryExpr(expr1, op, expr2);
1859
}
1860
1861
function makeLiteralExpr(token) {
1862
  // remove quotes from the parsed value:
1863
  var value = token.value.substring(1, token.value.length - 1);
1864
  return new LiteralExpr(value);
1865
}
1866
1867
function makeNumberExpr(token) {
1868
  return new NumberExpr(token.value);
1869
}
1870
1871
function makeVariableReference(dollar, name) {
1872
  return new VariableExpr(name.value);
1873
}
1874
1875
// Used before parsing for optimization of common simple cases. See
1876
// the begin of xpathParse() for which they are.
1877
function makeSimpleExpr(expr) {
1878
  if (expr.charAt(0) == '$') {
1879
    return new VariableExpr(expr.substr(1));
1880
  } else if (expr.charAt(0) == '@') {
1881
    var a = new NodeTestName(expr.substr(1));
1882
    var b = new StepExpr('attribute', a);
1883
    var c = new LocationExpr();
1884
    c.appendStep(b);
1885
    return c;
1886
  } else if (expr.match(/^[0-9]+$/)) {
1887
    return new NumberExpr(expr);
1888
  } else {
1889
    var a = new NodeTestName(expr);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable a already seems to be declared on line 1881. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1890
    var b = new StepExpr('child', a);
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable b already seems to be declared on line 1882. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1891
    var c = new LocationExpr();
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable c already seems to be declared on line 1883. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
1892
    c.appendStep(b);
1893
    return c;
1894
  }
1895
}
1896
1897
function makeSimpleExpr2(expr) {
1898
  var steps = stringSplit(expr, '/');
1899
  var c = new LocationExpr();
1900
  for (var i = 0; i < steps.length; ++i) {
1901
    var a = new NodeTestName(steps[i]);
1902
    var b = new StepExpr('child', a);
1903
    c.appendStep(b);
1904
  }
1905
  return c;
1906
}
1907
1908
// The axes of XPath expressions.
1909
1910
var xpathAxis = {
1911
  ANCESTOR_OR_SELF: 'ancestor-or-self',
1912
  ANCESTOR: 'ancestor',
1913
  ATTRIBUTE: 'attribute',
1914
  CHILD: 'child',
1915
  DESCENDANT_OR_SELF: 'descendant-or-self',
1916
  DESCENDANT: 'descendant',
1917
  FOLLOWING_SIBLING: 'following-sibling',
1918
  FOLLOWING: 'following',
1919
  NAMESPACE: 'namespace',
1920
  PARENT: 'parent',
1921
  PRECEDING_SIBLING: 'preceding-sibling',
1922
  PRECEDING: 'preceding',
1923
  SELF: 'self'
1924
};
1925
1926
var xpathAxesRe = [
1927
    xpathAxis.ANCESTOR_OR_SELF,
1928
    xpathAxis.ANCESTOR,
1929
    xpathAxis.ATTRIBUTE,
1930
    xpathAxis.CHILD,
1931
    xpathAxis.DESCENDANT_OR_SELF,
1932
    xpathAxis.DESCENDANT,
1933
    xpathAxis.FOLLOWING_SIBLING,
1934
    xpathAxis.FOLLOWING,
1935
    xpathAxis.NAMESPACE,
1936
    xpathAxis.PARENT,
1937
    xpathAxis.PRECEDING_SIBLING,
1938
    xpathAxis.PRECEDING,
1939
    xpathAxis.SELF
1940
].join('|');
1941
1942
1943
// The tokens of the language. The label property is just used for
1944
// generating debug output. The prec property is the precedence used
1945
// for shift/reduce resolution. Default precedence is 0 as a lookahead
1946
// token and 2 on the stack. TODO(mesch): this is certainly not
1947
// necessary and too complicated. Simplify this!
1948
1949
// NOTE: tabular formatting is the big exception, but here it should
1950
// be OK.
1951
1952
var TOK_PIPE =   { label: "|",   prec:   17, re: new RegExp("^\\|") };
1953
var TOK_DSLASH = { label: "//",  prec:   19, re: new RegExp("^//")  };
1954
var TOK_SLASH =  { label: "/",   prec:   30, re: new RegExp("^/")   };
1955
var TOK_AXIS =   { label: "::",  prec:   20, re: new RegExp("^::")  };
1956
var TOK_COLON =  { label: ":",   prec: 1000, re: new RegExp("^:")  };
1957
var TOK_AXISNAME = { label: "[axis]", re: new RegExp('^(' + xpathAxesRe + ')') };
1958
var TOK_PARENO = { label: "(",   prec:   34, re: new RegExp("^\\(") };
1959
var TOK_PARENC = { label: ")",               re: new RegExp("^\\)") };
1960
var TOK_DDOT =   { label: "..",  prec:   34, re: new RegExp("^\\.\\.") };
1961
var TOK_DOT =    { label: ".",   prec:   34, re: new RegExp("^\\.") };
1962
var TOK_AT =     { label: "@",   prec:   34, re: new RegExp("^@")   };
1963
1964
var TOK_COMMA =  { label: ",",               re: new RegExp("^,") };
1965
1966
var TOK_OR =     { label: "or",  prec:   10, re: new RegExp("^or\\b") };
1967
var TOK_AND =    { label: "and", prec:   11, re: new RegExp("^and\\b") };
1968
var TOK_EQ =     { label: "=",   prec:   12, re: new RegExp("^=")   };
1969
var TOK_NEQ =    { label: "!=",  prec:   12, re: new RegExp("^!=")  };
1970
var TOK_GE =     { label: ">=",  prec:   13, re: new RegExp("^>=")  };
1971
var TOK_GT =     { label: ">",   prec:   13, re: new RegExp("^>")   };
1972
var TOK_LE =     { label: "<=",  prec:   13, re: new RegExp("^<=")  };
1973
var TOK_LT =     { label: "<",   prec:   13, re: new RegExp("^<")   };
1974
var TOK_PLUS =   { label: "+",   prec:   14, re: new RegExp("^\\+"), left: true };
1975
var TOK_MINUS =  { label: "-",   prec:   14, re: new RegExp("^\\-"), left: true };
1976
var TOK_DIV =    { label: "div", prec:   15, re: new RegExp("^div\\b"), left: true };
1977
var TOK_MOD =    { label: "mod", prec:   15, re: new RegExp("^mod\\b"), left: true };
1978
1979
var TOK_BRACKO = { label: "[",   prec:   32, re: new RegExp("^\\[") };
1980
var TOK_BRACKC = { label: "]",               re: new RegExp("^\\]") };
1981
var TOK_DOLLAR = { label: "$",               re: new RegExp("^\\$") };
1982
1983
var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^' + XML_NC_NAME) };
1984
1985
var TOK_ASTERISK = { label: "*", prec: 15, re: new RegExp("^\\*"), left: true };
1986
var TOK_LITERALQ = { label: "[litq]", prec: 20, re: new RegExp("^'[^\\']*'") };
1987
var TOK_LITERALQQ = {
1988
  label: "[litqq]",
1989
  prec: 20,
1990
  re: new RegExp('^"[^\\"]*"')
1991
};
1992
1993
var TOK_NUMBER  = {
1994
  label: "[number]",
1995
  prec: 35,
1996
  re: new RegExp('^\\d+(\\.\\d*)?') };
1997
1998
var TOK_QNAME = {
1999
  label: "[qname]",
2000
  re: new RegExp('^(' + XML_NC_NAME + ':)?' + XML_NC_NAME)
2001
};
2002
2003
var TOK_NODEO = {
2004
  label: "[nodetest-start]",
2005
  re: new RegExp('^(processing-instruction|comment|text|node)\\(')
2006
};
2007
2008
// The table of the tokens of our grammar, used by the lexer: first
2009
// column the tag, second column a regexp to recognize it in the
2010
// input, third column the precedence of the token, fourth column a
2011
// factory function for the semantic value of the token.
2012
//
2013
// NOTE: order of this list is important, because the first match
2014
// counts. Cf. DDOT and DOT, and AXIS and COLON.
2015
2016
var xpathTokenRules = [
2017
    TOK_DSLASH,
2018
    TOK_SLASH,
2019
    TOK_DDOT,
2020
    TOK_DOT,
2021
    TOK_AXIS,
2022
    TOK_COLON,
2023
    TOK_AXISNAME,
2024
    TOK_NODEO,
2025
    TOK_PARENO,
2026
    TOK_PARENC,
2027
    TOK_BRACKO,
2028
    TOK_BRACKC,
2029
    TOK_AT,
2030
    TOK_COMMA,
2031
    TOK_OR,
2032
    TOK_AND,
2033
    TOK_NEQ,
2034
    TOK_EQ,
2035
    TOK_GE,
2036
    TOK_GT,
2037
    TOK_LE,
2038
    TOK_LT,
2039
    TOK_PLUS,
2040
    TOK_MINUS,
2041
    TOK_ASTERISK,
2042
    TOK_PIPE,
2043
    TOK_MOD,
2044
    TOK_DIV,
2045
    TOK_LITERALQ,
2046
    TOK_LITERALQQ,
2047
    TOK_NUMBER,
2048
    TOK_QNAME,
2049
    TOK_NCNAME,
2050
    TOK_DOLLAR
2051
];
2052
2053
// All the nonterminals of the grammar. The nonterminal objects are
2054
// identified by object identity; the labels are used in the debug
2055
// output only.
2056
var XPathLocationPath = { label: "LocationPath" };
2057
var XPathRelativeLocationPath = { label: "RelativeLocationPath" };
2058
var XPathAbsoluteLocationPath = { label: "AbsoluteLocationPath" };
2059
var XPathStep = { label: "Step" };
2060
var XPathNodeTest = { label: "NodeTest" };
2061
var XPathPredicate = { label: "Predicate" };
2062
var XPathLiteral = { label: "Literal" };
2063
var XPathExpr = { label: "Expr" };
2064
var XPathPrimaryExpr = { label: "PrimaryExpr" };
2065
var XPathVariableReference = { label: "Variablereference" };
2066
var XPathNumber = { label: "Number" };
2067
var XPathFunctionCall = { label: "FunctionCall" };
2068
var XPathArgumentRemainder = { label: "ArgumentRemainder" };
2069
var XPathPathExpr = { label: "PathExpr" };
2070
var XPathUnionExpr = { label: "UnionExpr" };
2071
var XPathFilterExpr = { label: "FilterExpr" };
2072
var XPathDigits = { label: "Digits" };
2073
2074
var xpathNonTerminals = [
2075
    XPathLocationPath,
2076
    XPathRelativeLocationPath,
2077
    XPathAbsoluteLocationPath,
2078
    XPathStep,
2079
    XPathNodeTest,
2080
    XPathPredicate,
2081
    XPathLiteral,
2082
    XPathExpr,
2083
    XPathPrimaryExpr,
2084
    XPathVariableReference,
2085
    XPathNumber,
2086
    XPathFunctionCall,
2087
    XPathArgumentRemainder,
2088
    XPathPathExpr,
2089
    XPathUnionExpr,
2090
    XPathFilterExpr,
2091
    XPathDigits
2092
];
2093
2094
// Quantifiers that are used in the productions of the grammar.
2095
var Q_01 = { label: "?" };
2096
var Q_MM = { label: "*" };
2097
var Q_1M = { label: "+" };
2098
2099
// Tag for left associativity (right assoc is implied by undefined).
2100
var ASSOC_LEFT = true;
2101
2102
// The productions of the grammar. Columns of the table:
2103
//
2104
// - target nonterminal,
2105
// - pattern,
2106
// - precedence,
2107
// - semantic value factory
2108
//
2109
// The semantic value factory is a function that receives parse tree
2110
// nodes from the stack frames of the matched symbols as arguments and
2111
// returns an a node of the parse tree. The node is stored in the top
2112
// stack frame along with the target object of the rule. The node in
2113
// the parse tree is an expression object that has an evaluate() method
2114
// and thus evaluates XPath expressions.
2115
//
2116
// The precedence is used to decide between reducing and shifting by
2117
// comparing the precendence of the rule that is candidate for
2118
// reducing with the precedence of the look ahead token. Precedence of
2119
// -1 means that the precedence of the tokens in the pattern is used
2120
// instead. TODO: It shouldn't be necessary to explicitly assign
2121
// precedences to rules.
2122
2123
// DGF As it stands, these precedences are purely empirical; we're
2124
// not sure they can be made to be consistent at all.
2125
2126
var xpathGrammarRules =
2127
  [
2128
   [ XPathLocationPath, [ XPathRelativeLocationPath ], 18,
2129
     passExpr ],
2130
   [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18,
2131
     passExpr ],
2132
2133
   [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18,
2134
     makeLocationExpr1 ],
2135
   [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18,
2136
     makeLocationExpr2 ],
2137
2138
   [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0,
2139
     makeLocationExpr3 ],
2140
   [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0,
2141
     makeLocationExpr4 ],
2142
2143
   [ XPathRelativeLocationPath, [ XPathStep ], 31,
2144
     makeLocationExpr5 ],
2145
   [ XPathRelativeLocationPath,
2146
     [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31,
2147
     makeLocationExpr6 ],
2148
   [ XPathRelativeLocationPath,
2149
     [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31,
2150
     makeLocationExpr7 ],
2151
2152
   [ XPathStep, [ TOK_DOT ], 33,
2153
     makeStepExpr1 ],
2154
   [ XPathStep, [ TOK_DDOT ], 33,
2155
     makeStepExpr2 ],
2156
   [ XPathStep,
2157
     [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33,
2158
     makeStepExpr3 ],
2159
   [ XPathStep, [ TOK_AT, XPathNodeTest ], 33,
2160
     makeStepExpr4 ],
2161
   [ XPathStep, [ XPathNodeTest ], 33,
2162
     makeStepExpr5 ],
2163
   [ XPathStep, [ XPathStep, XPathPredicate ], 33,
2164
     makeStepExpr6 ],
2165
2166
   [ XPathNodeTest, [ TOK_ASTERISK ], 33,
2167
     makeNodeTestExpr1 ],
2168
   [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33,
2169
     makeNodeTestExpr2 ],
2170
   [ XPathNodeTest, [ TOK_QNAME ], 33,
2171
     makeNodeTestExpr3 ],
2172
   [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33,
2173
     makeNodeTestExpr4 ],
2174
   [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33,
2175
     makeNodeTestExpr5 ],
2176
2177
   [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33,
2178
     makePredicateExpr ],
2179
2180
   [ XPathPrimaryExpr, [ XPathVariableReference ], 33,
2181
     passExpr ],
2182
   [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33,
2183
     makePrimaryExpr ],
2184
   [ XPathPrimaryExpr, [ XPathLiteral ], 30,
2185
     passExpr ],
2186
   [ XPathPrimaryExpr, [ XPathNumber ], 30,
2187
     passExpr ],
2188
   [ XPathPrimaryExpr, [ XPathFunctionCall ], 31,
2189
     passExpr ],
2190
2191
   [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,
2192
     makeFunctionCallExpr1 ],
2193
   [ XPathFunctionCall,
2194
     [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM,
2195
       TOK_PARENC ], -1,
2196
     makeFunctionCallExpr2 ],
2197
   [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1,
2198
     makeArgumentExpr ],
2199
2200
   [ XPathUnionExpr, [ XPathPathExpr ], 20,
2201
     passExpr ],
2202
   [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20,
2203
     makeUnionExpr ],
2204
2205
   [ XPathPathExpr, [ XPathLocationPath ], 20,
2206
     passExpr ],
2207
   [ XPathPathExpr, [ XPathFilterExpr ], 19,
2208
     passExpr ],
2209
   [ XPathPathExpr,
2210
     [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 19,
2211
     makePathExpr1 ],
2212
   [ XPathPathExpr,
2213
     [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 19,
2214
     makePathExpr2 ],
2215
2216
   [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 31,
2217
     makeFilterExpr ],
2218
2219
   [ XPathExpr, [ XPathPrimaryExpr ], 16,
2220
     passExpr ],
2221
   [ XPathExpr, [ XPathUnionExpr ], 16,
2222
     passExpr ],
2223
2224
   [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1,
2225
     makeUnaryMinusExpr ],
2226
2227
   [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1,
2228
     makeBinaryExpr ],
2229
   [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1,
2230
     makeBinaryExpr ],
2231
2232
   [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1,
2233
     makeBinaryExpr ],
2234
   [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1,
2235
     makeBinaryExpr ],
2236
2237
   [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1,
2238
     makeBinaryExpr ],
2239
   [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1,
2240
     makeBinaryExpr ],
2241
   [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1,
2242
     makeBinaryExpr ],
2243
   [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1,
2244
     makeBinaryExpr ],
2245
2246
   [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1,
2247
     makeBinaryExpr, ASSOC_LEFT ],
2248
   [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1,
2249
     makeBinaryExpr, ASSOC_LEFT ],
2250
2251
   [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1,
2252
     makeBinaryExpr, ASSOC_LEFT ],
2253
   [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1,
2254
     makeBinaryExpr, ASSOC_LEFT ],
2255
   [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1,
2256
     makeBinaryExpr, ASSOC_LEFT ],
2257
2258
   [ XPathLiteral, [ TOK_LITERALQ ], -1,
2259
     makeLiteralExpr ],
2260
   [ XPathLiteral, [ TOK_LITERALQQ ], -1,
2261
     makeLiteralExpr ],
2262
2263
   [ XPathNumber, [ TOK_NUMBER ], -1,
2264
     makeNumberExpr ],
2265
2266
   [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200,
2267
     makeVariableReference ]
2268
   ];
2269
2270
// That function computes some optimizations of the above data
2271
// structures and will be called right here. It merely takes the
2272
// counter variables out of the global scope.
2273
2274
var xpathRules = [];
2275
2276
function xpathParseInit() {
2277
  if (xpathRules.length) {
2278
    return;
2279
  }
2280
2281
  // Some simple optimizations for the xpath expression parser: sort
2282
  // grammar rules descending by length, so that the longest match is
2283
  // first found.
2284
2285
  xpathGrammarRules.sort(function(a,b) {
2286
    var la = a[1].length;
2287
    var lb = b[1].length;
2288
    if (la < lb) {
2289
      return 1;
2290
    } else if (la > lb) {
2291
      return -1;
2292
    } else {
2293
      return 0;
2294
    }
2295
  });
2296
2297
  var k = 1;
2298
  for (var i = 0; i < xpathNonTerminals.length; ++i) {
2299
    xpathNonTerminals[i].key = k++;
2300
  }
2301
2302
  for (i = 0; i < xpathTokenRules.length; ++i) {
2303
    xpathTokenRules[i].key = k++;
2304
  }
2305
2306
  xpathLog('XPath parse INIT: ' + k + ' rules');
2307
2308
  // Another slight optimization: sort the rules into bins according
2309
  // to the last element (observing quantifiers), so we can restrict
2310
  // the match against the stack to the subest of rules that match the
2311
  // top of the stack.
2312
  //
2313
  // TODO(mesch): What we actually want is to compute states as in
2314
  // bison, so that we don't have to do any explicit and iterated
2315
  // match against the stack.
2316
2317
  function push_(array, position, element) {
2318
    if (!array[position]) {
2319
      array[position] = [];
2320
    }
2321
    array[position].push(element);
2322
  }
2323
2324
  for (i = 0; i < xpathGrammarRules.length; ++i) {
2325
    var rule = xpathGrammarRules[i];
2326
    var pattern = rule[1];
2327
2328
    for (var j = pattern.length - 1; j >= 0; --j) {
2329
      if (pattern[j] == Q_1M) {
2330
        push_(xpathRules, pattern[j-1].key, rule);
2331
        break;
2332
2333
      } else if (pattern[j] == Q_MM || pattern[j] == Q_01) {
2334
        push_(xpathRules, pattern[j-1].key, rule);
2335
        --j;
2336
2337
      } else {
2338
        push_(xpathRules, pattern[j].key, rule);
2339
        break;
2340
      }
2341
    }
2342
  }
2343
2344
  xpathLog('XPath parse INIT: ' + xpathRules.length + ' rule bins');
2345
2346
  var sum = 0;
2347
  mapExec(xpathRules, function(i) {
2348
    if (i) {
2349
      sum += i.length;
2350
    }
2351
  });
2352
2353
  xpathLog('XPath parse INIT: ' + (sum / xpathRules.length) +
2354
           ' average bin size');
2355
}
2356
2357
// Local utility functions that are used by the lexer or parser.
2358
2359
function xpathCollectDescendants(nodelist, node, opt_tagName) {
2360
  if (opt_tagName && node.getElementsByTagName) {
2361
    copyArray(nodelist, node.getElementsByTagName(opt_tagName));
2362
    return;
2363
  }
2364
  for (var n = node.firstChild; n; n = n.nextSibling) {
2365
    nodelist.push(n);
2366
    xpathCollectDescendants(nodelist, n);
2367
  }
2368
}
2369
2370
/**
2371
 * DGF - extract a tag name suitable for getElementsByTagName
2372
 *
2373
 * @param nodetest                     the node test
2374
 * @param ignoreNonElementNodesForNTA  if true, the node list returned when
2375
 *                                     evaluating "node()" will not contain
2376
 *                                     non-element nodes. This can boost
2377
 *                                     performance. This is false by default.
2378
 */
2379
function xpathExtractTagNameFromNodeTest(nodetest, ignoreNonElementNodesForNTA) {
2380
  if (nodetest instanceof NodeTestName) {
2381
    return nodetest.name;
2382
  }
2383
  if ((ignoreNonElementNodesForNTA && nodetest instanceof NodeTestAny) ||
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if ignoreNonElementNodesFor...eTestElementOrAttribute is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
2384
    nodetest instanceof NodeTestElementOrAttribute) {
2385
    return "*";
2386
  }
2387
}
2388
2389
function xpathCollectDescendantsReverse(nodelist, node) {
2390
  for (var n = node.lastChild; n; n = n.previousSibling) {
2391
    nodelist.push(n);
2392
    xpathCollectDescendantsReverse(nodelist, n);
2393
  }
2394
}
2395
2396
2397
// The entry point for the library: match an expression against a DOM
2398
// node. Returns an XPath value.
2399
function xpathDomEval(expr, node) {
2400
  var expr1 = xpathParse(expr);
2401
  var ret = expr1.evaluate(new ExprContext(node));
2402
  return ret;
2403
}
2404
2405
// Utility function to sort a list of nodes. Used by xsltSort() and
2406
// nxslSelect().
2407
function xpathSort(input, sort) {
2408
  if (sort.length == 0) {
2409
    return;
2410
  }
2411
2412
  var sortlist = [];
2413
2414
  for (var i = 0; i < input.contextSize(); ++i) {
2415
    var node = input.nodelist[i];
2416
    var sortitem = { node: node, key: [] };
2417
    var context = input.clone(node, 0, [ node ]);
2418
2419
    for (var j = 0; j < sort.length; ++j) {
2420
      var s = sort[j];
2421
      var value = s.expr.evaluate(context);
2422
2423
      var evalue;
2424
      if (s.type == 'text') {
2425
        evalue = value.stringValue();
2426
      } else if (s.type == 'number') {
2427
        evalue = value.numberValue();
2428
      }
2429
      sortitem.key.push({ value: evalue, order: s.order });
0 ignored issues
show
Bug introduced by
The variable evalue seems to not be initialized for all possible execution paths.
Loading history...
2430
    }
2431
2432
    // Make the sort stable by adding a lowest priority sort by
2433
    // id. This is very convenient and furthermore required by the
2434
    // spec ([XSLT] - Section 10 Sorting).
2435
    sortitem.key.push({ value: i, order: 'ascending' });
2436
2437
    sortlist.push(sortitem);
2438
  }
2439
2440
  sortlist.sort(xpathSortByKey);
2441
2442
  var nodes = [];
2443
  for (var i = 0; i < sortlist.length; ++i) {
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable i already seems to be declared on line 2414. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
2444
    nodes.push(sortlist[i].node);
2445
  }
2446
  input.nodelist = nodes;
2447
  input.setNode(0);
2448
}
2449
2450
2451
// Sorts by all order criteria defined. According to the JavaScript
2452
// spec ([ECMA] Section 11.8.5), the compare operators compare strings
2453
// as strings and numbers as numbers.
2454
//
2455
// NOTE: In browsers which do not follow the spec, this breaks only in
2456
// the case that numbers should be sorted as strings, which is very
2457
// uncommon.
2458
function xpathSortByKey(v1, v2) {
2459
  // NOTE: Sort key vectors of different length never occur in
2460
  // xsltSort.
2461
2462
  for (var i = 0; i < v1.key.length; ++i) {
2463
    var o = v1.key[i].order == 'descending' ? -1 : 1;
2464
    if (v1.key[i].value > v2.key[i].value) {
2465
      return +1 * o;
2466
    } else if (v1.key[i].value < v2.key[i].value) {
2467
      return -1 * o;
2468
    }
2469
  }
2470
2471
  return 0;
2472
}
2473
2474
2475
// Parses and then evaluates the given XPath expression in the given
2476
// input context. Notice that parsed xpath expressions are cached.
2477
function xpathEval(select, context) {
2478
  var expr = xpathParse(select);
2479
  var ret = expr.evaluate(context);
2480
  return ret;
2481
}
2482