~dongpo-deng/sahana-eden/test

« back to all changes in this revision

Viewing changes to static/selenium/core/xpath/xpath.js

  • Committer: Deng Dongpo
  • Date: 2010-08-01 09:29:44 UTC
  • Revision ID: dongpo@dhcp-21193.iis.sinica.edu.tw-20100801092944-8t9obt4xtl7otesb
initial

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 <mesch@google.com>
 
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);
 
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;
 
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;
 
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) {
 
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]);
 
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();
 
776
    for (var i = 0; i < nodelist.length; ++i) {
 
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) {
 
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);
 
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) {
 
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) {
 
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) {
 
907
      for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
 
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) {
 
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];
 
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) {
 
940
      var nodelist0 = nodelist;
 
941
      nodelist = [];
 
942
      for (var ii = 0; ii < nodelist0.length; ++ii) {
 
943
        var n = nodelist0[ii];
 
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) {
 
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) {
 
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) {
 
1100
    alert('not implmented yet: XPath function local-name()');
 
1101
  },
 
1102
 
 
1103
  'namespace-uri': function(ctx) {
 
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);
 
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);
 
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) {
 
1276
    assert(this.args.length == 0);
 
1277
    return new BooleanValue(true);
 
1278
  },
 
1279
 
 
1280
  'false': function(ctx) {
 
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) {
 
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;
 
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();
 
1595
      var s = v2.numberValue();
 
1596
 
 
1597
      ret = false;
 
1598
      for (var i = 0;  i < n.length; ++i) {
 
1599
        var nn = xmlValue(n[i]) - 0;
 
1600
        if (cmp(nn, s)) {
 
1601
          ret = true;
 
1602
          break;
 
1603
        }
 
1604
      }
 
1605
 
 
1606
    } else if (v1.type == 'string') {
 
1607
      var s = v1.stringValue();
 
1608
      var n = v2.nodeSetValue();
 
1609
 
 
1610
      ret = false;
 
1611
      for (var i = 0;  i < n.length; ++i) {
 
1612
        var nn = xmlValue(n[i]);
 
1613
        if (cmp(s, nn)) {
 
1614
          ret = true;
 
1615
          break;
 
1616
        }
 
1617
      }
 
1618
 
 
1619
    } else if (v2.type == 'string') {
 
1620
      var n = v1.nodeSetValue();
 
1621
      var s = v2.stringValue();
 
1622
 
 
1623
      ret = false;
 
1624
      for (var i = 0;  i < n.length; ++i) {
 
1625
        var nn = xmlValue(n[i]);
 
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) {
 
1654
  return new StringValue(this.value);
 
1655
};
 
1656
 
 
1657
function NumberExpr(value) {
 
1658
  this.value = value;
 
1659
}
 
1660
 
 
1661
NumberExpr.prototype.evaluate = function(ctx) {
 
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) {
 
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) {
 
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
  }
 
1768
}
 
1769
 
 
1770
function makeNodeTestExpr1(asterisk) {
 
1771
  return new NodeTestElementOrAttribute;
 
1772
}
 
1773
 
 
1774
function makeNodeTestExpr2(ncname, colon, asterisk) {
 
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) {
 
1783
  var type = typeo.value.replace(/\s*\($/, '');
 
1784
  switch(type) {
 
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
  }
 
1797
}
 
1798
 
 
1799
function makeNodeTestExpr5(typeo, target, parenc) {
 
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) {
 
1808
  return new PredicateExpr(expr);
 
1809
}
 
1810
 
 
1811
function makePrimaryExpr(pareno, expr, parenc) {
 
1812
  return expr;
 
1813
}
 
1814
 
 
1815
function makeFunctionCallExpr1(name, pareno, parenc) {
 
1816
  return new FunctionCallExpr(name);
 
1817
}
 
1818
 
 
1819
function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {
 
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);
 
1890
    var b = new StepExpr('child', a);
 
1891
    var c = new LocationExpr();
 
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) ||
 
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 });
 
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) {
 
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
}