| 1 | // xpath.js - version 0.4 - Revision: Spry Pre-Release 1.5 |
|---|
| 2 | // |
|---|
| 3 | // Copyright (c) 2005, Google Inc. |
|---|
| 4 | // All rights reserved. |
|---|
| 5 | // |
|---|
| 6 | // Redistribution and use in source and binary forms, with or without |
|---|
| 7 | // modification, are permitted provided that the following conditions are |
|---|
| 8 | // met: |
|---|
| 9 | // |
|---|
| 10 | // * Redistributions of source code must retain the above copyright |
|---|
| 11 | // notice, this list of conditions and the following disclaimer. |
|---|
| 12 | // |
|---|
| 13 | // * Redistributions in binary form must reproduce the above copyright |
|---|
| 14 | // notice, this list of conditions and the following disclaimer in the |
|---|
| 15 | // documentation and/or other materials provided with the |
|---|
| 16 | // distribution. |
|---|
| 17 | // |
|---|
| 18 | // * Neither the name of Google Inc. nor the names of its contributors |
|---|
| 19 | // may be used to endorse or promote products derived from this |
|---|
| 20 | // software without specific prior written permission. |
|---|
| 21 | // |
|---|
| 22 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|---|
| 23 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|---|
| 24 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|---|
| 25 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|---|
| 26 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|---|
| 27 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|---|
| 28 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|---|
| 29 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|---|
| 30 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|---|
| 31 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|---|
| 32 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 33 | // |
|---|
| 34 | // An XPath parser and evaluator written in JavaScript. The |
|---|
| 35 | // implementation is complete except for functions handling |
|---|
| 36 | // namespaces. |
|---|
| 37 | // |
|---|
| 38 | // Reference: [XPATH] XPath Specification |
|---|
| 39 | // <http://www.w3.org/TR/1999/REC-xpath-19991116>. |
|---|
| 40 | // |
|---|
| 41 | // |
|---|
| 42 | // The API of the parser has several parts: |
|---|
| 43 | // |
|---|
| 44 | // 1. The parser function xpathParse() that takes a string and returns |
|---|
| 45 | // an expession object. |
|---|
| 46 | // |
|---|
| 47 | // 2. The expression object that has an evaluate() method to evaluate the |
|---|
| 48 | // XPath expression it represents. (It is actually a hierarchy of |
|---|
| 49 | // objects that resembles the parse tree, but an application will call |
|---|
| 50 | // evaluate() only on the top node of this hierarchy.) |
|---|
| 51 | // |
|---|
| 52 | // 3. The context object that is passed as an argument to the evaluate() |
|---|
| 53 | // method, which represents the DOM context in which the expression is |
|---|
| 54 | // evaluated. |
|---|
| 55 | // |
|---|
| 56 | // 4. The value object that is returned from evaluate() and represents |
|---|
| 57 | // values of the different types that are defined by XPath (number, |
|---|
| 58 | // string, boolean, and node-set), and allows to convert between them. |
|---|
| 59 | // |
|---|
| 60 | // These parts are near the top of the file, the functions and data |
|---|
| 61 | // that are used internally follow after them. |
|---|
| 62 | // |
|---|
| 63 | // |
|---|
| 64 | // TODO(mesch): add jsdoc comments. Use more coherent naming. |
|---|
| 65 | // |
|---|
| 66 | // |
|---|
| 67 | // Author: Steffen Meschkat <mesch@google.com> |
|---|
| 68 | |
|---|
| 69 | |
|---|
| 70 | // The entry point for the parser. |
|---|
| 71 | // |
|---|
| 72 | // @param expr a string that contains an XPath expression. |
|---|
| 73 | // @return an expression object that can be evaluated with an |
|---|
| 74 | // expression context. |
|---|
| 75 | |
|---|
| 76 | function xpathParse(expr) { |
|---|
| 77 | if (xpathdebug) { |
|---|
| 78 | Log.write('XPath parse ' + expr); |
|---|
| 79 | } |
|---|
| 80 | xpathParseInit(); |
|---|
| 81 | |
|---|
| 82 | var cached = xpathCacheLookup(expr); |
|---|
| 83 | if (cached) { |
|---|
| 84 | if (xpathdebug) { |
|---|
| 85 | Log.write(' ... cached'); |
|---|
| 86 | } |
|---|
| 87 | return cached; |
|---|
| 88 | } |
|---|
| 89 | |
|---|
| 90 | // Optimize for a few common cases: simple attribute node tests |
|---|
| 91 | // (@id), simple element node tests (page), variable references |
|---|
| 92 | // ($address), numbers (4), multi-step path expressions where each |
|---|
| 93 | // step is a plain element node test |
|---|
| 94 | // (page/overlay/locations/location). |
|---|
| 95 | |
|---|
| 96 | if (expr.match(/^(\$|@)?\w+$/i)) { |
|---|
| 97 | var ret = makeSimpleExpr(expr); |
|---|
| 98 | xpathParseCache[expr] = ret; |
|---|
| 99 | if (xpathdebug) { |
|---|
| 100 | Log.write(' ... simple'); |
|---|
| 101 | } |
|---|
| 102 | return ret; |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | if (expr.match(/^\w+(\/\w+)*$/i)) { |
|---|
| 106 | var ret = makeSimpleExpr2(expr); |
|---|
| 107 | xpathParseCache[expr] = ret; |
|---|
| 108 | if (xpathdebug) { |
|---|
| 109 | Log.write(' ... simple 2'); |
|---|
| 110 | } |
|---|
| 111 | return ret; |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | var cachekey = expr; // expr is modified during parse |
|---|
| 115 | if (xpathdebug) { |
|---|
| 116 | Timer.start('XPath parse', cachekey); |
|---|
| 117 | } |
|---|
| 118 | |
|---|
| 119 | var stack = []; |
|---|
| 120 | var ahead = null; |
|---|
| 121 | var previous = null; |
|---|
| 122 | var done = false; |
|---|
| 123 | |
|---|
| 124 | var parse_count = 0; |
|---|
| 125 | var lexer_count = 0; |
|---|
| 126 | var reduce_count = 0; |
|---|
| 127 | |
|---|
| 128 | while (!done) { |
|---|
| 129 | parse_count++; |
|---|
| 130 | expr = expr.replace(/^\s*/, ''); |
|---|
| 131 | previous = ahead; |
|---|
| 132 | ahead = null; |
|---|
| 133 | |
|---|
| 134 | var rule = null; |
|---|
| 135 | var match = ''; |
|---|
| 136 | for (var i = 0; i < xpathTokenRules.length; ++i) { |
|---|
| 137 | var result = xpathTokenRules[i].re.exec(expr); |
|---|
| 138 | lexer_count++; |
|---|
| 139 | if (result && result.length > 0 && result[0].length > match.length) { |
|---|
| 140 | rule = xpathTokenRules[i]; |
|---|
| 141 | match = result[0]; |
|---|
| 142 | break; |
|---|
| 143 | } |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | // Special case: allow operator keywords to be element and |
|---|
| 147 | // variable names. |
|---|
| 148 | |
|---|
| 149 | // NOTE(mesch): The parser resolves conflicts by looking ahead, |
|---|
| 150 | // and this is the only case where we look back to |
|---|
| 151 | // disambiguate. So this is indeed something different, and |
|---|
| 152 | // looking back is usually done in the lexer (via states in the |
|---|
| 153 | // general case, called "start conditions" in flex(1)). Also,the |
|---|
| 154 | // conflict resolution in the parser is not as robust as it could |
|---|
| 155 | // be, so I'd like to keep as much off the parser as possible (all |
|---|
| 156 | // these precedence values should be computed from the grammar |
|---|
| 157 | // rules and possibly associativity declarations, as in bison(1), |
|---|
| 158 | // and not explicitly set. |
|---|
| 159 | |
|---|
| 160 | if (rule && |
|---|
| 161 | (rule == TOK_DIV || |
|---|
| 162 | rule == TOK_MOD || |
|---|
| 163 | rule == TOK_AND || |
|---|
| 164 | rule == TOK_OR) && |
|---|
| 165 | (!previous || |
|---|
| 166 | previous.tag == TOK_AT || |
|---|
| 167 | previous.tag == TOK_DSLASH || |
|---|
| 168 | previous.tag == TOK_SLASH || |
|---|
| 169 | previous.tag == TOK_AXIS || |
|---|
| 170 | previous.tag == TOK_DOLLAR)) { |
|---|
| 171 | rule = TOK_QNAME; |
|---|
| 172 | } |
|---|
| 173 | |
|---|
| 174 | if (rule) { |
|---|
| 175 | expr = expr.substr(match.length); |
|---|
| 176 | if (xpathdebug) { |
|---|
| 177 | Log.write('token: ' + match + ' -- ' + rule.label); |
|---|
| 178 | } |
|---|
| 179 | ahead = { |
|---|
| 180 | tag: rule, |
|---|
| 181 | match: match, |
|---|
| 182 | prec: rule.prec ? rule.prec : 0, // || 0 is removed by the compiler |
|---|
| 183 | expr: makeTokenExpr(match) |
|---|
| 184 | }; |
|---|
| 185 | |
|---|
| 186 | } else { |
|---|
| 187 | if (xpathdebug) { |
|---|
| 188 | Log.write('DONE'); |
|---|
| 189 | } |
|---|
| 190 | done = true; |
|---|
| 191 | } |
|---|
| 192 | |
|---|
| 193 | while (xpathReduce(stack, ahead)) { |
|---|
| 194 | reduce_count++; |
|---|
| 195 | if (xpathdebug) { |
|---|
| 196 | Log.write('stack: ' + stackToString(stack)); |
|---|
| 197 | } |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | if (xpathdebug) { |
|---|
| 202 | Log.write(stackToString(stack)); |
|---|
| 203 | } |
|---|
| 204 | |
|---|
| 205 | if (stack.length != 1) { |
|---|
| 206 | throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack); |
|---|
| 207 | } |
|---|
| 208 | |
|---|
| 209 | var result = stack[0].expr; |
|---|
| 210 | xpathParseCache[cachekey] = result; |
|---|
| 211 | |
|---|
| 212 | if (xpathdebug) { |
|---|
| 213 | Timer.end('XPath parse', cachekey); |
|---|
| 214 | } |
|---|
| 215 | |
|---|
| 216 | if (xpathdebug) { |
|---|
| 217 | Log.write('XPath parse: ' + parse_count + ' / ' + |
|---|
| 218 | lexer_count + ' / ' + reduce_count); |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | return result; |
|---|
| 222 | } |
|---|
| 223 | |
|---|
| 224 | var xpathParseCache = {}; |
|---|
| 225 | |
|---|
| 226 | function xpathCacheLookup(expr) { |
|---|
| 227 | return xpathParseCache[expr]; |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | function xpathReduce(stack, ahead) { |
|---|
| 231 | var cand = null; |
|---|
| 232 | |
|---|
| 233 | if (stack.length > 0) { |
|---|
| 234 | var top = stack[stack.length-1]; |
|---|
| 235 | var ruleset = xpathRules[top.tag.key]; |
|---|
| 236 | |
|---|
| 237 | if (ruleset) { |
|---|
| 238 | for (var i = 0; i < ruleset.length; ++i) { |
|---|
| 239 | var rule = ruleset[i]; |
|---|
| 240 | var match = xpathMatchStack(stack, rule[1]); |
|---|
| 241 | if (match.length) { |
|---|
| 242 | cand = { |
|---|
| 243 | tag: rule[0], |
|---|
| 244 | rule: rule, |
|---|
| 245 | match: match |
|---|
| 246 | }; |
|---|
| 247 | cand.prec = xpathGrammarPrecedence(cand); |
|---|
| 248 | break; |
|---|
| 249 | } |
|---|
| 250 | } |
|---|
| 251 | } |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | var ret; |
|---|
| 255 | if (cand && (!ahead || cand.prec > ahead.prec || |
|---|
| 256 | (ahead.tag.left && cand.prec >= ahead.prec))) { |
|---|
| 257 | for (var i = 0; i < cand.match.matchlength; ++i) { |
|---|
| 258 | stack.pop(); |
|---|
| 259 | } |
|---|
| 260 | |
|---|
| 261 | if (xpathdebug) { |
|---|
| 262 | Log.write('reduce ' + cand.tag.label + ' ' + cand.prec + |
|---|
| 263 | ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec + |
|---|
| 264 | (ahead.tag.left ? ' left' : '') |
|---|
| 265 | : ' none ')); |
|---|
| 266 | } |
|---|
| 267 | |
|---|
| 268 | var matchexpr = mapExpr(cand.match, function(m) { return m.expr; }); |
|---|
| 269 | cand.expr = cand.rule[3].apply(null, matchexpr); |
|---|
| 270 | |
|---|
| 271 | stack.push(cand); |
|---|
| 272 | ret = true; |
|---|
| 273 | |
|---|
| 274 | } else { |
|---|
| 275 | if (ahead) { |
|---|
| 276 | if (xpathdebug) { |
|---|
| 277 | Log.write('shift ' + ahead.tag.label + ' ' + ahead.prec + |
|---|
| 278 | (ahead.tag.left ? ' left' : '') + |
|---|
| 279 | ' over ' + (cand ? cand.tag.label + ' ' + |
|---|
| 280 | cand.prec : ' none')); |
|---|
| 281 | } |
|---|
| 282 | stack.push(ahead); |
|---|
| 283 | } |
|---|
| 284 | ret = false; |
|---|
| 285 | } |
|---|
| 286 | return ret; |
|---|
| 287 | } |
|---|
| 288 | |
|---|
| 289 | function xpathMatchStack(stack, pattern) { |
|---|
| 290 | |
|---|
| 291 | // NOTE(mesch): The stack matches for variable cardinality are |
|---|
| 292 | // greedy but don't do backtracking. This would be an issue only |
|---|
| 293 | // with rules of the form A* A, i.e. with an element with variable |
|---|
| 294 | // cardinality followed by the same element. Since that doesn't |
|---|
| 295 | // occur in the grammar at hand, all matches on the stack are |
|---|
| 296 | // unambiguous. |
|---|
| 297 | |
|---|
| 298 | var S = stack.length; |
|---|
| 299 | var P = pattern.length; |
|---|
| 300 | var p, s; |
|---|
| 301 | var match = []; |
|---|
| 302 | match.matchlength = 0; |
|---|
| 303 | var ds = 0; |
|---|
| 304 | for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) { |
|---|
| 305 | ds = 0; |
|---|
| 306 | var qmatch = []; |
|---|
| 307 | if (pattern[p] == Q_MM) { |
|---|
| 308 | p -= 1; |
|---|
| 309 | match.push(qmatch); |
|---|
| 310 | while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) { |
|---|
| 311 | qmatch.push(stack[s - ds]); |
|---|
| 312 | ds += 1; |
|---|
| 313 | match.matchlength += 1; |
|---|
| 314 | } |
|---|
| 315 | |
|---|
| 316 | } else if (pattern[p] == Q_01) { |
|---|
| 317 | p -= 1; |
|---|
| 318 | match.push(qmatch); |
|---|
| 319 | while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) { |
|---|
| 320 | qmatch.push(stack[s - ds]); |
|---|
| 321 | ds += 1; |
|---|
| 322 | match.matchlength += 1; |
|---|
| 323 | } |
|---|
| 324 | |
|---|
| 325 | } else if (pattern[p] == Q_1M) { |
|---|
| 326 | p -= 1; |
|---|
| 327 | match.push(qmatch); |
|---|
| 328 | if (stack[s].tag == pattern[p]) { |
|---|
| 329 | while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) { |
|---|
| 330 | qmatch.push(stack[s - ds]); |
|---|
| 331 | ds += 1; |
|---|
| 332 | match.matchlength += 1; |
|---|
| 333 | } |
|---|
| 334 | } else { |
|---|
| 335 | return []; |
|---|
| 336 | } |
|---|
| 337 | |
|---|
| 338 | } else if (stack[s].tag == pattern[p]) { |
|---|
| 339 | match.push(stack[s]); |
|---|
| 340 | ds += 1; |
|---|
| 341 | match.matchlength += 1; |
|---|
| 342 | |
|---|
| 343 | } else { |
|---|
| 344 | return []; |
|---|
| 345 | } |
|---|
| 346 | |
|---|
| 347 | reverseInplace(qmatch); |
|---|
| 348 | qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; }); |
|---|
| 349 | } |
|---|
| 350 | |
|---|
| 351 | reverseInplace(match); |
|---|
| 352 | |
|---|
| 353 | if (p == -1) { |
|---|
| 354 | return match; |
|---|
| 355 | |
|---|
| 356 | } else { |
|---|
| 357 | return []; |
|---|
| 358 | } |
|---|
| 359 | } |
|---|
| 360 | |
|---|
| 361 | function xpathTokenPrecedence(tag) { |
|---|
| 362 | return tag.prec || 2; |
|---|
| 363 | } |
|---|
| 364 | |
|---|
| 365 | function xpathGrammarPrecedence(frame) { |
|---|
| 366 | var ret = 0; |
|---|
| 367 | |
|---|
| 368 | if (frame.rule) { /* normal reduce */ |
|---|
| 369 | if (frame.rule.length >= 3 && frame.rule[2] >= 0) { |
|---|
| 370 | ret = frame.rule[2]; |
|---|
| 371 | |
|---|
| 372 | } else { |
|---|
| 373 | for (var i = 0; i < frame.rule[1].length; ++i) { |
|---|
| 374 | var p = xpathTokenPrecedence(frame.rule[1][i]); |
|---|
| 375 | ret = Math.max(ret, p); |
|---|
| 376 | } |
|---|
| 377 | } |
|---|
| 378 | } else if (frame.tag) { /* TOKEN match */ |
|---|
| 379 | ret = xpathTokenPrecedence(frame.tag); |
|---|
| 380 | |
|---|
| 381 | } else if (frame.length) { /* Q_ match */ |
|---|
| 382 | for (var j = 0; j < frame.length; ++j) { |
|---|
| 383 | var p = xpathGrammarPrecedence(frame[j]); |
|---|
| 384 | ret = Math.max(ret, p); |
|---|
| 385 | } |
|---|
| 386 | } |
|---|
| 387 | |
|---|
| 388 | return ret; |
|---|
| 389 | } |
|---|
| 390 | |
|---|
| 391 | function stackToString(stack) { |
|---|
| 392 | var ret = ''; |
|---|
| 393 | for (var i = 0; i < stack.length; ++i) { |
|---|
| 394 | if (ret) { |
|---|
| 395 | ret += '\n'; |
|---|
| 396 | } |
|---|
| 397 | ret += stack[i].tag.label; |
|---|
| 398 | } |
|---|
| 399 | return ret; |
|---|
| 400 | } |
|---|
| 401 | |
|---|
| 402 | |
|---|
| 403 | // XPath expression evaluation context. An XPath context consists of a |
|---|
| 404 | // DOM node, a list of DOM nodes that contains this node, a number |
|---|
| 405 | // that represents the position of the single node in the list, and a |
|---|
| 406 | // current set of variable bindings. (See XPath spec.) |
|---|
| 407 | // |
|---|
| 408 | // The interface of the expression context: |
|---|
| 409 | // |
|---|
| 410 | // Constructor -- gets the node, its position, the node set it |
|---|
| 411 | // belongs to, and a parent context as arguments. The parent context |
|---|
| 412 | // is used to implement scoping rules for variables: if a variable |
|---|
| 413 | // is not found in the current context, it is looked for in the |
|---|
| 414 | // parent context, recursively. Except for node, all arguments have |
|---|
| 415 | // default values: default position is 0, default node set is the |
|---|
| 416 | // set that contains only the node, and the default parent is null. |
|---|
| 417 | // |
|---|
| 418 | // Notice that position starts at 0 at the outside interface; |
|---|
| 419 | // inside XPath expressions this shows up as position()=1. |
|---|
| 420 | // |
|---|
| 421 | // clone() -- creates a new context with the current context as |
|---|
| 422 | // parent. If passed as argument to clone(), the new context has a |
|---|
| 423 | // different node, position, or node set. What is not passed is |
|---|
| 424 | // inherited from the cloned context. |
|---|
| 425 | // |
|---|
| 426 | // setVariable(name, expr) -- binds given XPath expression to the |
|---|
| 427 | // name. |
|---|
| 428 | // |
|---|
| 429 | // getVariable(name) -- what the name says. |
|---|
| 430 | // |
|---|
| 431 | // setNode(node, position) -- sets the context to the new node and |
|---|
| 432 | // its corresponding position. Needed to implement scoping rules for |
|---|
| 433 | // variables in XPath. (A variable is visible to all subsequent |
|---|
| 434 | // siblings, not only to its children.) |
|---|
| 435 | |
|---|
| 436 | function ExprContext(node, position, nodelist, parent) { |
|---|
| 437 | this.node = node; |
|---|
| 438 | this.position = position || 0; |
|---|
| 439 | this.nodelist = nodelist || [ node ]; |
|---|
| 440 | this.variables = {}; |
|---|
| 441 | this.parent = parent || null; |
|---|
| 442 | this.root = parent ? parent.root : node.ownerDocument; |
|---|
| 443 | } |
|---|
| 444 | |
|---|
| 445 | ExprContext.prototype.clone = function(node, position, nodelist) { |
|---|
| 446 | return new |
|---|
| 447 | ExprContext(node || this.node, |
|---|
| 448 | typeof position != 'undefined' ? position : this.position, |
|---|
| 449 | nodelist || this.nodelist, this); |
|---|
| 450 | }; |
|---|
| 451 | |
|---|
| 452 | ExprContext.prototype.setVariable = function(name, value) { |
|---|
| 453 | this.variables[name] = value; |
|---|
| 454 | }; |
|---|
| 455 | |
|---|
| 456 | ExprContext.prototype.getVariable = function(name) { |
|---|
| 457 | if (typeof this.variables[name] != 'undefined') { |
|---|
| 458 | return this.variables[name]; |
|---|
| 459 | |
|---|
| 460 | } else if (this.parent) { |
|---|
| 461 | return this.parent.getVariable(name); |
|---|
| 462 | |
|---|
| 463 | } else { |
|---|
| 464 | return null; |
|---|
| 465 | } |
|---|
| 466 | } |
|---|
| 467 | |
|---|
| 468 | ExprContext.prototype.setNode = function(node, position) { |
|---|
| 469 | this.node = node; |
|---|
| 470 | this.position = position; |
|---|
| 471 | } |
|---|
| 472 | |
|---|
| 473 | |
|---|
| 474 | // XPath expression values. They are what XPath expressions evaluate |
|---|
| 475 | // to. Strangely, the different value types are not specified in the |
|---|
| 476 | // XPath syntax, but only in the semantics, so they don't show up as |
|---|
| 477 | // nonterminals in the grammar. Yet, some expressions are required to |
|---|
| 478 | // evaluate to particular types, and not every type can be coerced |
|---|
| 479 | // into every other type. Although the types of XPath values are |
|---|
| 480 | // similar to the types present in JavaScript, the type coercion rules |
|---|
| 481 | // are a bit peculiar, so we explicitly model XPath types instead of |
|---|
| 482 | // mapping them onto JavaScript types. (See XPath spec.) |
|---|
| 483 | // |
|---|
| 484 | // The four types are: |
|---|
| 485 | // |
|---|
| 486 | // StringValue |
|---|
| 487 | // |
|---|
| 488 | // NumberValue |
|---|
| 489 | // |
|---|
| 490 | // BooleanValue |
|---|
| 491 | // |
|---|
| 492 | // NodeSetValue |
|---|
| 493 | // |
|---|
| 494 | // The common interface of the value classes consists of methods that |
|---|
| 495 | // implement the XPath type coercion rules: |
|---|
| 496 | // |
|---|
| 497 | // stringValue() -- returns the value as a JavaScript String, |
|---|
| 498 | // |
|---|
| 499 | // numberValue() -- returns the value as a JavaScript Number, |
|---|
| 500 | // |
|---|
| 501 | // booleanValue() -- returns the value as a JavaScript Boolean, |
|---|
| 502 | // |
|---|
| 503 | // nodeSetValue() -- returns the value as a JavaScript Array of DOM |
|---|
| 504 | // Node objects. |
|---|
| 505 | // |
|---|
| 506 | |
|---|
| 507 | function StringValue(value) { |
|---|
| 508 | this.value = value; |
|---|
| 509 | this.type = 'string'; |
|---|
| 510 | } |
|---|
| 511 | |
|---|
| 512 | StringValue.prototype.stringValue = function() { |
|---|
| 513 | return this.value; |
|---|
| 514 | } |
|---|
| 515 | |
|---|
| 516 | StringValue.prototype.booleanValue = function() { |
|---|
| 517 | return this.value.length > 0; |
|---|
| 518 | } |
|---|
| 519 | |
|---|
| 520 | StringValue.prototype.numberValue = function() { |
|---|
| 521 | return this.value - 0; |
|---|
| 522 | } |
|---|
| 523 | |
|---|
| 524 | StringValue.prototype.nodeSetValue = function() { |
|---|
| 525 | throw this + ' ' + Error().stack; |
|---|
| 526 | } |
|---|
| 527 | |
|---|
| 528 | function BooleanValue(value) { |
|---|
| 529 | this.value = value; |
|---|
| 530 | this.type = 'boolean'; |
|---|
| 531 | } |
|---|
| 532 | |
|---|
| 533 | BooleanValue.prototype.stringValue = function() { |
|---|
| 534 | return '' + this.value; |
|---|
| 535 | } |
|---|
| 536 | |
|---|
| 537 | BooleanValue.prototype.booleanValue = function() { |
|---|
| 538 | return this.value; |
|---|
| 539 | } |
|---|
| 540 | |
|---|
| 541 | BooleanValue.prototype.numberValue = function() { |
|---|
| 542 | return this.value ? 1 : 0; |
|---|
| 543 | } |
|---|
| 544 | |
|---|
| 545 | BooleanValue.prototype.nodeSetValue = function() { |
|---|
| 546 | throw this + ' ' + Error().stack; |
|---|
| 547 | } |
|---|
| 548 | |
|---|
| 549 | function NumberValue(value) { |
|---|
| 550 | this.value = value; |
|---|
| 551 | this.type = 'number'; |
|---|
| 552 | } |
|---|
| 553 | |
|---|
| 554 | NumberValue.prototype.stringValue = function() { |
|---|
| 555 | return '' + this.value; |
|---|
| 556 | } |
|---|
| 557 | |
|---|
| 558 | NumberValue.prototype.booleanValue = function() { |
|---|
| 559 | return !!this.value; |
|---|
| 560 | } |
|---|
| 561 | |
|---|
| 562 | NumberValue.prototype.numberValue = function() { |
|---|
| 563 | return this.value - 0; |
|---|
| 564 | } |
|---|
| 565 | |
|---|
| 566 | NumberValue.prototype.nodeSetValue = function() { |
|---|
| 567 | throw this + ' ' + Error().stack; |
|---|
| 568 | } |
|---|
| 569 | |
|---|
| 570 | function NodeSetValue(value) { |
|---|
| 571 | this.value = value; |
|---|
| 572 | this.type = 'node-set'; |
|---|
| 573 | } |
|---|
| 574 | |
|---|
| 575 | NodeSetValue.prototype.stringValue = function() { |
|---|
| 576 | if (this.value.length == 0) { |
|---|
| 577 | return ''; |
|---|
| 578 | } else { |
|---|
| 579 | return xmlValue(this.value[0]); |
|---|
| 580 | } |
|---|
| 581 | } |
|---|
| 582 | |
|---|
| 583 | NodeSetValue.prototype.booleanValue = function() { |
|---|
| 584 | return this.value.length > 0; |
|---|
| 585 | } |
|---|
| 586 | |
|---|
| 587 | NodeSetValue.prototype.numberValue = function() { |
|---|
| 588 | return this.stringValue() - 0; |
|---|
| 589 | } |
|---|
| 590 | |
|---|
| 591 | NodeSetValue.prototype.nodeSetValue = function() { |
|---|
| 592 | return this.value; |
|---|
| 593 | }; |
|---|
| 594 | |
|---|
| 595 | // XPath expressions. They are used as nodes in the parse tree and |
|---|
| 596 | // possess an evaluate() method to compute an XPath value given an XPath |
|---|
| 597 | // context. Expressions are returned from the parser. Teh set of |
|---|
| 598 | // expression classes closely mirrors the set of non terminal symbols |
|---|
| 599 | // in the grammar. Every non trivial nonterminal symbol has a |
|---|
| 600 | // corresponding expression class. |
|---|
| 601 | // |
|---|
| 602 | // The common expression interface consists of the following methods: |
|---|
| 603 | // |
|---|
| 604 | // evaluate(context) -- evaluates the expression, returns a value. |
|---|
| 605 | // |
|---|
| 606 | // toString() -- returns the XPath text representation of the |
|---|
| 607 | // expression (defined in xsltdebug.js). |
|---|
| 608 | // |
|---|
| 609 | // parseTree(indent) -- returns a parse tree representation of the |
|---|
| 610 | // expression (defined in xsltdebug.js). |
|---|
| 611 | |
|---|
| 612 | function TokenExpr(m) { |
|---|
| 613 | this.value = m; |
|---|
| 614 | } |
|---|
| 615 | |
|---|
| 616 | TokenExpr.prototype.evaluate = function() { |
|---|
| 617 | return new StringValue(this.value); |
|---|
| 618 | }; |
|---|
| 619 | |
|---|
| 620 | function LocationExpr() { |
|---|
| 621 | this.absolute = false; |
|---|
| 622 | this.steps = []; |
|---|
| 623 | } |
|---|
| 624 | |
|---|
| 625 | LocationExpr.prototype.appendStep = function(s) { |
|---|
| 626 | this.steps.push(s); |
|---|
| 627 | } |
|---|
| 628 | |
|---|
| 629 | LocationExpr.prototype.prependStep = function(s) { |
|---|
| 630 | var steps0 = this.steps; |
|---|
| 631 | this.steps = [ s ]; |
|---|
| 632 | for (var i = 0; i < steps0.length; ++i) { |
|---|
| 633 | this.steps.push(steps0[i]); |
|---|
| 634 | } |
|---|
| 635 | }; |
|---|
| 636 | |
|---|
| 637 | LocationExpr.prototype.evaluate = function(ctx) { |
|---|
| 638 | var start; |
|---|
| 639 | if (this.absolute) { |
|---|
| 640 | start = ctx.root; |
|---|
| 641 | |
|---|
| 642 | } else { |
|---|
| 643 | start = ctx.node; |
|---|
| 644 | } |
|---|
| 645 | |
|---|
| 646 | var nodes = []; |
|---|
| 647 | xPathStep(nodes, this.steps, 0, start, ctx); |
|---|
| 648 | return new NodeSetValue(nodes); |
|---|
| 649 | }; |
|---|
| 650 | |
|---|
| 651 | function xPathStep(nodes, steps, step, input, ctx) { |
|---|
| 652 | var s = steps[step]; |
|---|
| 653 | var ctx2 = ctx.clone(input); |
|---|
| 654 | var nodelist = s.evaluate(ctx2).nodeSetValue(); |
|---|
| 655 | |
|---|
| 656 | for (var i = 0; i < nodelist.length; ++i) { |
|---|
| 657 | if (step == steps.length - 1) { |
|---|
| 658 | nodes.push(nodelist[i]); |
|---|
| 659 | } else { |
|---|
| 660 | xPathStep(nodes, steps, step + 1, nodelist[i], ctx); |
|---|
| 661 | } |
|---|
| 662 | } |
|---|
| 663 | } |
|---|
| 664 | |
|---|
| 665 | function StepExpr(axis, nodetest, predicate) { |
|---|
| 666 | this.axis = axis; |
|---|
| 667 | this.nodetest = nodetest; |
|---|
| 668 | this.predicate = predicate || []; |
|---|
| 669 | } |
|---|
| 670 | |
|---|
| 671 | StepExpr.prototype.appendPredicate = function(p) { |
|---|
| 672 | this.predicate.push(p); |
|---|
| 673 | } |
|---|
| 674 | |
|---|
| 675 | StepExpr.prototype.evaluate = function(ctx) { |
|---|
| 676 | var input = ctx.node; |
|---|
| 677 | var nodelist = []; |
|---|
| 678 | |
|---|
| 679 | // NOTE(mesch): When this was a switch() statement, it didn't work |
|---|
| 680 | // in Safari/2.0. Not sure why though; it resulted in the JavaScript |
|---|
| 681 | // console output "undefined" (without any line number or so). |
|---|
| 682 | |
|---|
| 683 | if (this.axis == xpathAxis.ANCESTOR_OR_SELF) { |
|---|
| 684 | nodelist.push(input); |
|---|
| 685 | for (var n = input.parentNode; n; n = input.parentNode) { |
|---|
| 686 | nodelist.push(n); |
|---|
| 687 | } |
|---|
| 688 | |
|---|
| 689 | } else if (this.axis == xpathAxis.ANCESTOR) { |
|---|
| 690 | for (var n = input.parentNode; n; n = input.parentNode) { |
|---|
| 691 | nodelist.push(n); |
|---|
| 692 | } |
|---|
| 693 | |
|---|
| 694 | } else if (this.axis == xpathAxis.ATTRIBUTE) { |
|---|
| 695 | copyArray(nodelist, input.attributes); |
|---|
| 696 | |
|---|
| 697 | } else if (this.axis == xpathAxis.CHILD) { |
|---|
| 698 | copyArray(nodelist, input.childNodes); |
|---|
| 699 | |
|---|
| 700 | } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) { |
|---|
| 701 | nodelist.push(input); |
|---|
| 702 | xpathCollectDescendants(nodelist, input); |
|---|
| 703 | |
|---|
| 704 | } else if (this.axis == xpathAxis.DESCENDANT) { |
|---|
| 705 | xpathCollectDescendants(nodelist, input); |
|---|
| 706 | |
|---|
| 707 | } else if (this.axis == xpathAxis.FOLLOWING) { |
|---|
| 708 | for (var n = input.parentNode; n; n = n.parentNode) { |
|---|
| 709 | for (var nn = n.nextSibling; nn; nn = nn.nextSibling) { |
|---|
| 710 | nodelist.push(nn); |
|---|
| 711 | xpathCollectDescendants(nodelist, nn); |
|---|
| 712 | } |
|---|
| 713 | } |
|---|
| 714 | |
|---|
| 715 | } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) { |
|---|
| 716 | for (var n = input.nextSibling; n; n = input.nextSibling) { |
|---|
| 717 | nodelist.push(n); |
|---|
| 718 | } |
|---|
| 719 | |
|---|
| 720 | } else if (this.axis == xpathAxis.NAMESPACE) { |
|---|
| 721 | alert('not implemented: axis namespace'); |
|---|
| 722 | |
|---|
| 723 | } else if (this.axis == xpathAxis.PARENT) { |
|---|
| 724 | if (input.parentNode) { |
|---|
| 725 | nodelist.push(input.parentNode); |
|---|
| 726 | } |
|---|
| 727 | |
|---|
| 728 | } else if (this.axis == xpathAxis.PRECEDING) { |
|---|
| 729 | for (var n = input.parentNode; n; n = n.parentNode) { |
|---|
| 730 | for (var nn = n.previousSibling; nn; nn = nn.previousSibling) { |
|---|
| 731 | nodelist.push(nn); |
|---|
| 732 | xpathCollectDescendantsReverse(nodelist, nn); |
|---|
| 733 | } |
|---|
| 734 | } |
|---|
| 735 | |
|---|
| 736 | } else if (this.axis == xpathAxis.PRECEDING_SIBLING) { |
|---|
| 737 | for (var n = input.previousSibling; n; n = input.previousSibling) { |
|---|
| 738 | nodelist.push(n); |
|---|
| 739 | } |
|---|
| 740 | |
|---|
| 741 | } else if (this.axis == xpathAxis.SELF) { |
|---|
| 742 | nodelist.push(input); |
|---|
| 743 | |
|---|
| 744 | } else { |
|---|
| 745 | throw 'ERROR -- NO SUCH AXIS: ' + this.axis; |
|---|
| 746 | } |
|---|
| 747 | |
|---|
| 748 | // process node test |
|---|
| 749 | var nodelist0 = nodelist; |
|---|
| 750 | nodelist = []; |
|---|
| 751 | for (var i = 0; i < nodelist0.length; ++i) { |
|---|
| 752 | var n = nodelist0[i]; |
|---|
| 753 | if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) { |
|---|
| 754 | nodelist.push(n); |
|---|
| 755 | } |
|---|
| 756 | } |
|---|
| 757 | |
|---|
| 758 | // process predicates |
|---|
| 759 | for (var i = 0; i < this.predicate.length; ++i) { |
|---|
| 760 | var nodelist0 = nodelist; |
|---|
| 761 | nodelist = []; |
|---|
| 762 | for (var ii = 0; ii < nodelist0.length; ++ii) { |
|---|
| 763 | var n = nodelist0[ii]; |
|---|
| 764 | if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) { |
|---|
| 765 | nodelist.push(n); |
|---|
| 766 | } |
|---|
| 767 | } |
|---|
| 768 | } |
|---|
| 769 | |
|---|
| 770 | return new NodeSetValue(nodelist); |
|---|
| 771 | }; |
|---|
| 772 | |
|---|
| 773 | function NodeTestAny() { |
|---|
| 774 | this.value = new BooleanValue(true); |
|---|
| 775 | } |
|---|
| 776 | |
|---|
| 777 | NodeTestAny.prototype.evaluate = function(ctx) { |
|---|
| 778 | return this.value; |
|---|
| 779 | }; |
|---|
| 780 | |
|---|
| 781 | function NodeTestElement() {} |
|---|
| 782 | |
|---|
| 783 | NodeTestElement.prototype.evaluate = function(ctx) { |
|---|
| 784 | return new BooleanValue(ctx.node.nodeType == DOM_ELEMENT_NODE); |
|---|
| 785 | } |
|---|
| 786 | |
|---|
| 787 | function NodeTestText() {} |
|---|
| 788 | |
|---|
| 789 | NodeTestText.prototype.evaluate = function(ctx) { |
|---|
| 790 | return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE); |
|---|
| 791 | } |
|---|
| 792 | |
|---|
| 793 | function NodeTestComment() {} |
|---|
| 794 | |
|---|
| 795 | NodeTestComment.prototype.evaluate = function(ctx) { |
|---|
| 796 | return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE); |
|---|
| 797 | } |
|---|
| 798 | |
|---|
| 799 | function NodeTestPI(target) { |
|---|
| 800 | this.target = target; |
|---|
| 801 | } |
|---|
| 802 | |
|---|
| 803 | NodeTestPI.prototype.evaluate = function(ctx) { |
|---|
| 804 | return new |
|---|
| 805 | BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE && |
|---|
| 806 | (!this.target || ctx.node.nodeName == this.target)); |
|---|
| 807 | } |
|---|
| 808 | |
|---|
| 809 | function NodeTestNC(nsprefix) { |
|---|
| 810 | this.regex = new RegExp("^" + nsprefix + ":"); |
|---|
| 811 | this.nsprefix = nsprefix; |
|---|
| 812 | } |
|---|
| 813 | |
|---|
| 814 | NodeTestNC.prototype.evaluate = function(ctx) { |
|---|
| 815 | var n = ctx.node; |
|---|
| 816 | return new BooleanValue(this.regex.match(n.nodeName)); |
|---|
| 817 | } |
|---|
| 818 | |
|---|
| 819 | function NodeTestName(name) { |
|---|
| 820 | this.name = name; |
|---|
| 821 | } |
|---|
| 822 | |
|---|
| 823 | NodeTestName.prototype.evaluate = function(ctx) { |
|---|
| 824 | var n = ctx.node; |
|---|
| 825 | return new BooleanValue(n.nodeName == this.name); |
|---|
| 826 | } |
|---|
| 827 | |
|---|
| 828 | function PredicateExpr(expr) { |
|---|
| 829 | this.expr = expr; |
|---|
| 830 | } |
|---|
| 831 | |
|---|
| 832 | PredicateExpr.prototype.evaluate = function(ctx) { |
|---|
| 833 | var v = this.expr.evaluate(ctx); |
|---|
| 834 | if (v.type == 'number') { |
|---|
| 835 | // NOTE(mesch): Internally, position is represented starting with |
|---|
| 836 | // 0, however in XPath position starts with 1. See functions |
|---|
| 837 | // position() and last(). |
|---|
| 838 | return new BooleanValue(ctx.position == v.numberValue() - 1); |
|---|
| 839 | } else { |
|---|
| 840 | return new BooleanValue(v.booleanValue()); |
|---|
| 841 | } |
|---|
| 842 | }; |
|---|
| 843 | |
|---|
| 844 | function FunctionCallExpr(name) { |
|---|
| 845 | this.name = name; |
|---|
| 846 | this.args = []; |
|---|
| 847 | } |
|---|
| 848 | |
|---|
| 849 | FunctionCallExpr.prototype.appendArg = function(arg) { |
|---|
| 850 | this.args.push(arg); |
|---|
| 851 | }; |
|---|
| 852 | |
|---|
| 853 | FunctionCallExpr.prototype.evaluate = function(ctx) { |
|---|
| 854 | var fn = '' + this.name.value; |
|---|
| 855 | var f = this.xpathfunctions[fn]; |
|---|
| 856 | if (f) { |
|---|
| 857 | return f.call(this, ctx); |
|---|
| 858 | } else { |
|---|
| 859 | Log.write('XPath NO SUCH FUNCTION ' + fn); |
|---|
| 860 | return new BooleanValue(false); |
|---|
| 861 | } |
|---|
| 862 | }; |
|---|
| 863 | |
|---|
| 864 | FunctionCallExpr.prototype.xpathfunctions = { |
|---|
| 865 | 'last': function(ctx) { |
|---|
| 866 | assert(this.args.length == 0); |
|---|
| 867 | // NOTE(mesch): XPath position starts at 1. |
|---|
| 868 | return new NumberValue(ctx.nodelist.length); |
|---|
| 869 | }, |
|---|
| 870 | |
|---|
| 871 | 'position': function(ctx) { |
|---|
| 872 | assert(this.args.length == 0); |
|---|
| 873 | // NOTE(mesch): XPath position starts at 1. |
|---|
| 874 | return new NumberValue(ctx.position + 1); |
|---|
| 875 | }, |
|---|
| 876 | |
|---|
| 877 | 'count': function(ctx) { |
|---|
| 878 | assert(this.args.length == 1); |
|---|
| 879 | var v = this.args[0].evaluate(ctx); |
|---|
| 880 | return new NumberValue(v.nodeSetValue().length); |
|---|
| 881 | }, |
|---|
| 882 | |
|---|
| 883 | 'id': function(ctx) { |
|---|
| 884 | assert(this.args.length == 1); |
|---|
| 885 | var e = this.args.evaluate(ctx); |
|---|
| 886 | var ret = []; |
|---|
| 887 | var ids; |
|---|
| 888 | if (e.type == 'node-set') { |
|---|
| 889 | ids = []; |
|---|
| 890 | for (var i = 0; i < e.length; ++i) { |
|---|
| 891 | var v = xmlValue(e[i]).split(/\s+/); |
|---|
| 892 | for (var ii = 0; ii < v.length; ++ii) { |
|---|
| 893 | ids.push(v[ii]); |
|---|
| 894 | } |
|---|
| 895 | } |
|---|
| 896 | } else { |
|---|
| 897 | ids = e.split(/\s+/); |
|---|
| 898 | } |
|---|
| 899 | var d = ctx.node.ownerDocument; |
|---|
| 900 | for (var i = 0; i < ids.length; ++i) { |
|---|
| 901 | var n = d.getElementById(ids[i]); |
|---|
| 902 | if (n) { |
|---|
| 903 | ret.push(n); |
|---|
| 904 | } |
|---|
| 905 | } |
|---|
| 906 | return new NodeSetValue(ret); |
|---|
| 907 | }, |
|---|
| 908 | |
|---|
| 909 | 'local-name': function(ctx) { |
|---|
| 910 | alert('not implmented yet: XPath function local-name()'); |
|---|
| 911 | }, |
|---|
| 912 | |
|---|
| 913 | 'namespace-uri': function(ctx) { |
|---|
| 914 | alert('not implmented yet: XPath function namespace-uri()'); |
|---|
| 915 | }, |
|---|
| 916 | |
|---|
| 917 | 'name': function(ctx) { |
|---|
| 918 | assert(this.args.length == 1 || this.args.length == 0); |
|---|
| 919 | var n; |
|---|
| 920 | if (this.args.length == 0) { |
|---|
| 921 | n = [ ctx.node ]; |
|---|
| 922 | } else { |
|---|
| 923 | n = this.args[0].evaluate(ctx).nodeSetValue(); |
|---|
| 924 | } |
|---|
| 925 | |
|---|
| 926 | if (n.length == 0) { |
|---|
| 927 | return new StringValue(''); |
|---|
| 928 | } else { |
|---|
| 929 | return new StringValue(n[0].nodeName); |
|---|
| 930 | } |
|---|
| 931 | }, |
|---|
| 932 | |
|---|
| 933 | 'string': function(ctx) { |
|---|
| 934 | assert(this.args.length == 1 || this.args.length == 0); |
|---|
| 935 | if (this.args.length == 0) { |
|---|
| 936 | return new StringValue(new NodeSetValue([ ctx.node ]).stringValue()); |
|---|
| 937 | } else { |
|---|
| 938 | return new StringValue(this.args[0].evaluate(ctx).stringValue()); |
|---|
| 939 | } |
|---|
| 940 | }, |
|---|
| 941 | |
|---|
| 942 | 'concat': function(ctx) { |
|---|
| 943 | var ret = ''; |
|---|
| 944 | for (var i = 0; i < this.args.length; ++i) { |
|---|
| 945 | ret += this.args[i].evaluate(ctx).stringValue(); |
|---|
| 946 | } |
|---|
| 947 | return new StringValue(ret); |
|---|
| 948 | }, |
|---|
| 949 | |
|---|
| 950 | 'starts-with': function(ctx) { |
|---|
| 951 | assert(this.args.length == 2); |
|---|
| 952 | var s0 = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 953 | var s1 = this.args[1].evaluate(ctx).stringValue(); |
|---|
| 954 | return new BooleanValue(s0.indexOf(s1) == 0); |
|---|
| 955 | }, |
|---|
| 956 | |
|---|
| 957 | 'contains': function(ctx) { |
|---|
| 958 | assert(this.args.length == 2); |
|---|
| 959 | var s0 = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 960 | var s1 = this.args[1].evaluate(ctx).stringValue(); |
|---|
| 961 | return new BooleanValue(s0.indexOf(s1) != -1); |
|---|
| 962 | }, |
|---|
| 963 | |
|---|
| 964 | 'substring-before': function(ctx) { |
|---|
| 965 | assert(this.args.length == 2); |
|---|
| 966 | var s0 = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 967 | var s1 = this.args[1].evaluate(ctx).stringValue(); |
|---|
| 968 | var i = s0.indexOf(s1); |
|---|
| 969 | var ret; |
|---|
| 970 | if (i == -1) { |
|---|
| 971 | ret = ''; |
|---|
| 972 | } else { |
|---|
| 973 | ret = s0.substr(0,i); |
|---|
| 974 | } |
|---|
| 975 | return new StringValue(ret); |
|---|
| 976 | }, |
|---|
| 977 | |
|---|
| 978 | 'substring-after': function(ctx) { |
|---|
| 979 | assert(this.args.length == 2); |
|---|
| 980 | var s0 = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 981 | var s1 = this.args[1].evaluate(ctx).stringValue(); |
|---|
| 982 | var i = s0.indexOf(s1); |
|---|
| 983 | var ret; |
|---|
| 984 | if (i == -1) { |
|---|
| 985 | ret = ''; |
|---|
| 986 | } else { |
|---|
| 987 | ret = s0.substr(i + s1.length); |
|---|
| 988 | } |
|---|
| 989 | return new StringValue(ret); |
|---|
| 990 | }, |
|---|
| 991 | |
|---|
| 992 | 'substring': function(ctx) { |
|---|
| 993 | // NOTE: XPath defines the position of the first character in a |
|---|
| 994 | // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2). |
|---|
| 995 | assert(this.args.length == 2 || this.args.length == 3); |
|---|
| 996 | var s0 = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 997 | var s1 = this.args[1].evaluate(ctx).numberValue(); |
|---|
| 998 | var ret; |
|---|
| 999 | if (this.args.length == 2) { |
|---|
| 1000 | var i1 = Math.max(0, Math.round(s1) - 1); |
|---|
| 1001 | ret = s0.substr(i1); |
|---|
| 1002 | |
|---|
| 1003 | } else { |
|---|
| 1004 | var s2 = this.args[2].evaluate(ctx).numberValue(); |
|---|
| 1005 | var i0 = Math.round(s1) - 1; |
|---|
| 1006 | var i1 = Math.max(0, i0); |
|---|
| 1007 | var i2 = Math.round(s2) - Math.max(0, -i0); |
|---|
| 1008 | ret = s0.substr(i1, i2); |
|---|
| 1009 | } |
|---|
| 1010 | return new StringValue(ret); |
|---|
| 1011 | }, |
|---|
| 1012 | |
|---|
| 1013 | 'string-length': function(ctx) { |
|---|
| 1014 | var s; |
|---|
| 1015 | if (this.args.length > 0) { |
|---|
| 1016 | s = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 1017 | } else { |
|---|
| 1018 | s = new NodeSetValue([ ctx.node ]).stringValue(); |
|---|
| 1019 | } |
|---|
| 1020 | return new NumberValue(s.length); |
|---|
| 1021 | }, |
|---|
| 1022 | |
|---|
| 1023 | 'normalize-space': function(ctx) { |
|---|
| 1024 | var s; |
|---|
| 1025 | if (this.args.length > 0) { |
|---|
| 1026 | s = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 1027 | } else { |
|---|
| 1028 | s = new NodeSetValue([ ctx.node ]).stringValue(); |
|---|
| 1029 | } |
|---|
| 1030 | s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' '); |
|---|
| 1031 | return new StringValue(s); |
|---|
| 1032 | }, |
|---|
| 1033 | |
|---|
| 1034 | 'translate': function(ctx) { |
|---|
| 1035 | assert(this.args.length == 3); |
|---|
| 1036 | var s0 = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 1037 | var s1 = this.args[1].evaluate(ctx).stringValue(); |
|---|
| 1038 | var s2 = this.args[2].evaluate(ctx).stringValue(); |
|---|
| 1039 | |
|---|
| 1040 | for (var i = 0; i < s1.length; ++i) { |
|---|
| 1041 | s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i)); |
|---|
| 1042 | } |
|---|
| 1043 | return new StringValue(s0); |
|---|
| 1044 | }, |
|---|
| 1045 | |
|---|
| 1046 | 'boolean': function(ctx) { |
|---|
| 1047 | assert(this.args.length == 1); |
|---|
| 1048 | return new BooleanValue(this.args[0].evaluate(ctx).booleanValue()); |
|---|
| 1049 | }, |
|---|
| 1050 | |
|---|
| 1051 | 'not': function(ctx) { |
|---|
| 1052 | assert(this.args.length == 1); |
|---|
| 1053 | var ret = !this.args[0].evaluate(ctx).booleanValue(); |
|---|
| 1054 | return new BooleanValue(ret); |
|---|
| 1055 | }, |
|---|
| 1056 | |
|---|
| 1057 | 'true': function(ctx) { |
|---|
| 1058 | assert(this.args.length == 0); |
|---|
| 1059 | return new BooleanValue(true); |
|---|
| 1060 | }, |
|---|
| 1061 | |
|---|
| 1062 | 'false': function(ctx) { |
|---|
| 1063 | assert(this.args.length == 0); |
|---|
| 1064 | return new BooleanValue(false); |
|---|
| 1065 | }, |
|---|
| 1066 | |
|---|
| 1067 | 'lang': function(ctx) { |
|---|
| 1068 | assert(this.args.length == 1); |
|---|
| 1069 | var lang = this.args[0].evaluate(ctx).stringValue(); |
|---|
| 1070 | var xmllang; |
|---|
| 1071 | var n = ctx.node; |
|---|
| 1072 | while (n && n != n.parentNode /* just in case ... */) { |
|---|
| 1073 | xmllang = n.getAttribute('xml:lang'); |
|---|
| 1074 | if (xmllang) { |
|---|
| 1075 | break; |
|---|
| 1076 | } |
|---|
| 1077 | n = n.parentNode; |
|---|
| 1078 | } |
|---|
| 1079 | if (!xmllang) { |
|---|
| 1080 | return new BooleanValue(false); |
|---|
| 1081 | } else { |
|---|
| 1082 | var re = new RegExp('^' + lang + '$', 'i'); |
|---|
| 1083 | return new BooleanValue(xmllang.match(re) || |
|---|
| 1084 | xmllang.replace(/_.*$/,'').match(re)); |
|---|
| 1085 | } |
|---|
| 1086 | }, |
|---|
| 1087 | |
|---|
| 1088 | 'number': function(ctx) { |
|---|
| 1089 | assert(this.args.length == 1 || this.args.length == 0); |
|---|
| 1090 | |
|---|
| 1091 | if (this.args.length == 1) { |
|---|
| 1092 | return new NumberValue(this.args[0].evaluate(ctx).numberValue()); |
|---|
| 1093 | } else { |
|---|
| 1094 | return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue()); |
|---|
| 1095 | } |
|---|
| 1096 | }, |
|---|
| 1097 | |
|---|
| 1098 | 'sum': function(ctx) { |
|---|
| 1099 | assert(this.args.length == 1); |
|---|
| 1100 | var n = this.args[0].evaluate(ctx).nodeSetValue(); |
|---|
| 1101 | var sum = 0; |
|---|
| 1102 | for (var i = 0; i < n.length; ++i) { |
|---|
| 1103 | sum += xmlValue(n[i]) - 0; |
|---|
| 1104 | } |
|---|
| 1105 | return new NumberValue(sum); |
|---|
| 1106 | }, |
|---|
| 1107 | |
|---|
| 1108 | 'floor': function(ctx) { |
|---|
| 1109 | assert(this.args.length == 1); |
|---|
| 1110 | var num = this.args[0].evaluate(ctx).numberValue(); |
|---|
| 1111 | return new NumberValue(Math.floor(num)); |
|---|
| 1112 | }, |
|---|
| 1113 | |
|---|
| 1114 | 'ceiling': function(ctx) { |
|---|
| 1115 | assert(this.args.length == 1); |
|---|
| 1116 | var num = this.args[0].evaluate(ctx).numberValue(); |
|---|
| 1117 | return new NumberValue(Math.ceil(num)); |
|---|
| 1118 | }, |
|---|
| 1119 | |
|---|
| 1120 | 'round': function(ctx) { |
|---|
| 1121 | assert(this.args.length == 1); |
|---|
| 1122 | var num = this.args[0].evaluate(ctx).numberValue(); |
|---|
| 1123 | return new NumberValue(Math.round(num)); |
|---|
| 1124 | }, |
|---|
| 1125 | |
|---|
| 1126 | // TODO(mesch): The following functions are custom. There is a |
|---|
| 1127 | // standard that defines how to add functions, which should be |
|---|
| 1128 | // applied here. |
|---|
| 1129 | |
|---|
| 1130 | 'ext-join': function(ctx) { |
|---|
| 1131 | assert(this.args.length == 2); |
|---|
| 1132 | var nodes = this.args[0].evaluate(ctx).nodeSetValue(); |
|---|
| 1133 | var delim = this.args[1].evaluate(ctx).stringValue(); |
|---|
| 1134 | var ret = ''; |
|---|
| 1135 | for (var i = 0; i < nodes.length; ++i) { |
|---|
| 1136 | if (ret) { |
|---|
| 1137 | ret += delim; |
|---|
| 1138 | } |
|---|
| 1139 | ret += xmlValue(nodes[i]); |
|---|
| 1140 | } |
|---|
| 1141 | return new StringValue(ret); |
|---|
| 1142 | }, |
|---|
| 1143 | |
|---|
| 1144 | // ext-if() evaluates and returns its second argument, if the |
|---|
| 1145 | // boolean value of its first argument is true, otherwise it |
|---|
| 1146 | // evaluates and returns its third argument. |
|---|
| 1147 | |
|---|
| 1148 | 'ext-if': function(ctx) { |
|---|
| 1149 | assert(this.args.length == 3); |
|---|
| 1150 | if (this.args[0].evaluate(ctx).booleanValue()) { |
|---|
| 1151 | return this.args[1].evaluate(ctx); |
|---|
| 1152 | } else { |
|---|
| 1153 | return this.args[2].evaluate(ctx); |
|---|
| 1154 | } |
|---|
| 1155 | }, |
|---|
| 1156 | |
|---|
| 1157 | 'ext-sprintf': function(ctx) { |
|---|
| 1158 | assert(this.args.length >= 1); |
|---|
| 1159 | var args = []; |
|---|
| 1160 | for (var i = 0; i < this.args.length; ++i) { |
|---|
| 1161 | args.push(this.args[i].evaluate(ctx).stringValue()); |
|---|
| 1162 | } |
|---|
| 1163 | return new StringValue(sprintf.apply(null, args)); |
|---|
| 1164 | }, |
|---|
| 1165 | |
|---|
| 1166 | // ext-cardinal() evaluates its single argument as a number, and |
|---|
| 1167 | // returns the current node that many times. It can be used in the |
|---|
| 1168 | // select attribute to iterate over an integer range. |
|---|
| 1169 | |
|---|
| 1170 | 'ext-cardinal': function(ctx) { |
|---|
| 1171 | assert(this.args.length >= 1); |
|---|
| 1172 | var c = this.args[0].evaluate(ctx).numberValue(); |
|---|
| 1173 | var ret = []; |
|---|
| 1174 | for (var i = 0; i < c; ++i) { |
|---|
| 1175 | ret.push(ctx.node); |
|---|
| 1176 | } |
|---|
| 1177 | return new NodeSetValue(ret); |
|---|
| 1178 | } |
|---|
| 1179 | }; |
|---|
| 1180 | |
|---|
| 1181 | function UnionExpr(expr1, expr2) { |
|---|
| 1182 | this.expr1 = expr1; |
|---|
| 1183 | this.expr2 = expr2; |
|---|
| 1184 | } |
|---|
| 1185 | |
|---|
| 1186 | UnionExpr.prototype.evaluate = function(ctx) { |
|---|
| 1187 | var nodes1 = this.expr1.evaluate(ctx).nodeSetValue(); |
|---|
| 1188 | var nodes2 = this.expr2.evaluate(ctx).nodeSetValue(); |
|---|
| 1189 | var I1 = nodes1.length; |
|---|
| 1190 | for (var i2 = 0; i2 < nodes2.length; ++i2) { |
|---|
| 1191 | for (var i1 = 0; i1 < I1; ++i1) { |
|---|
| 1192 | if (nodes1[i1] == nodes2[i2]) { |
|---|
| 1193 | // break inner loop and continue outer loop, labels confuse |
|---|
| 1194 | // the js compiler, so we don't use them here. |
|---|
| 1195 | i1 = I1; |
|---|
| 1196 | } |
|---|
| 1197 | } |
|---|
| 1198 | nodes1.push(nodes2[i2]); |
|---|
| 1199 | } |
|---|
| 1200 | return new NodeSetValue(nodes2); |
|---|
| 1201 | }; |
|---|
| 1202 | |
|---|
| 1203 | function PathExpr(filter, rel) { |
|---|
| 1204 | this.filter = filter; |
|---|
| 1205 | this.rel = rel; |
|---|
| 1206 | } |
|---|
| 1207 | |
|---|
| 1208 | PathExpr.prototype.evaluate = function(ctx) { |
|---|
| 1209 | var nodes = this.filter.evaluate(ctx).nodeSetValue(); |
|---|
| 1210 | var nodes1 = []; |
|---|
| 1211 | for (var i = 0; i < nodes.length; ++i) { |
|---|
| 1212 | var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue(); |
|---|
| 1213 | for (var ii = 0; ii < nodes0.length; ++ii) { |
|---|
| 1214 | nodes1.push(nodes0[ii]); |
|---|
| 1215 | } |
|---|
| 1216 | } |
|---|
| 1217 | return new NodeSetValue(nodes1); |
|---|
| 1218 | }; |
|---|
| 1219 | |
|---|
| 1220 | function FilterExpr(expr, predicate) { |
|---|
| 1221 | this.expr = expr; |
|---|
| 1222 | this.predicate = predicate; |
|---|
| 1223 | } |
|---|
| 1224 | |
|---|
| 1225 | FilterExpr.prototype.evaluate = function(ctx) { |
|---|
| 1226 | var nodes = this.expr.evaluate(ctx).nodeSetValue(); |
|---|
| 1227 | for (var i = 0; i < this.predicate.length; ++i) { |
|---|
| 1228 | var nodes0 = nodes; |
|---|
| 1229 | nodes = []; |
|---|
| 1230 | for (var j = 0; j < nodes0.length; ++j) { |
|---|
| 1231 | var n = nodes0[j]; |
|---|
| 1232 | if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) { |
|---|
| 1233 | nodes.push(n); |
|---|
| 1234 | } |
|---|
| 1235 | } |
|---|
| 1236 | } |
|---|
| 1237 | |
|---|
| 1238 | return new NodeSetValue(nodes); |
|---|
| 1239 | } |
|---|
| 1240 | |
|---|
| 1241 | function UnaryMinusExpr(expr) { |
|---|
| 1242 | this.expr = expr; |
|---|
| 1243 | } |
|---|
| 1244 | |
|---|
| 1245 | UnaryMinusExpr.prototype.evaluate = function(ctx) { |
|---|
| 1246 | return new NumberValue(-this.expr.evaluate(ctx).numberValue()); |
|---|
| 1247 | }; |
|---|
| 1248 | |
|---|
| 1249 | function BinaryExpr(expr1, op, expr2) { |
|---|
| 1250 | this.expr1 = expr1; |
|---|
| 1251 | this.expr2 = expr2; |
|---|
| 1252 | this.op = op; |
|---|
| 1253 | } |
|---|
| 1254 | |
|---|
| 1255 | BinaryExpr.prototype.evaluate = function(ctx) { |
|---|
| 1256 | var ret; |
|---|
| 1257 | switch (this.op.value) { |
|---|
| 1258 | case 'or': |
|---|
| 1259 | ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() || |
|---|
| 1260 | this.expr2.evaluate(ctx).booleanValue()); |
|---|
| 1261 | break; |
|---|
| 1262 | |
|---|
| 1263 | case 'and': |
|---|
| 1264 | ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() && |
|---|
| 1265 | this.expr2.evaluate(ctx).booleanValue()); |
|---|
| 1266 | break; |
|---|
| 1267 | |
|---|
| 1268 | case '+': |
|---|
| 1269 | ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() + |
|---|
| 1270 | this.expr2.evaluate(ctx).numberValue()); |
|---|
| 1271 | break; |
|---|
| 1272 | |
|---|
| 1273 | case '-': |
|---|
| 1274 | ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() - |
|---|
| 1275 | this.expr2.evaluate(ctx).numberValue()); |
|---|
| 1276 | break; |
|---|
| 1277 | |
|---|
| 1278 | case '*': |
|---|
| 1279 | ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() * |
|---|
| 1280 | this.expr2.evaluate(ctx).numberValue()); |
|---|
| 1281 | break; |
|---|
| 1282 | |
|---|
| 1283 | case 'mod': |
|---|
| 1284 | ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() % |
|---|
| 1285 | this.expr2.evaluate(ctx).numberValue()); |
|---|
| 1286 | break; |
|---|
| 1287 | |
|---|
| 1288 | case 'div': |
|---|
| 1289 | ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() / |
|---|
| 1290 | this.expr2.evaluate(ctx).numberValue()); |
|---|
| 1291 | break; |
|---|
| 1292 | |
|---|
| 1293 | case '=': |
|---|
| 1294 | ret = this.compare(ctx, function(x1, x2) { return x1 == x2; }); |
|---|
| 1295 | break; |
|---|
| 1296 | |
|---|
| 1297 | case '!=': |
|---|
| 1298 | ret = this.compare(ctx, function(x1, x2) { return x1 != x2; }); |
|---|
| 1299 | break; |
|---|
| 1300 | |
|---|
| 1301 | case '<': |
|---|
| 1302 | ret = this.compare(ctx, function(x1, x2) { return x1 < x2; }); |
|---|
| 1303 | break; |
|---|
| 1304 | |
|---|
| 1305 | case '<=': |
|---|
| 1306 | ret = this.compare(ctx, function(x1, x2) { return x1 <= x2; }); |
|---|
| 1307 | break; |
|---|
| 1308 | |
|---|
| 1309 | case '>': |
|---|
| 1310 | ret = this.compare(ctx, function(x1, x2) { return x1 > x2; }); |
|---|
| 1311 | break; |
|---|
| 1312 | |
|---|
| 1313 | case '>=': |
|---|
| 1314 | ret = this.compare(ctx, function(x1, x2) { return x1 >= x2; }); |
|---|
| 1315 | break; |
|---|
| 1316 | |
|---|
| 1317 | default: |
|---|
| 1318 | alert('BinaryExpr.evaluate: ' + this.op.value); |
|---|
| 1319 | } |
|---|
| 1320 | return ret; |
|---|
| 1321 | }; |
|---|
| 1322 | |
|---|
| 1323 | BinaryExpr.prototype.compare = function(ctx, cmp) { |
|---|
| 1324 | var v1 = this.expr1.evaluate(ctx); |
|---|
| 1325 | var v2 = this.expr2.evaluate(ctx); |
|---|
| 1326 | |
|---|
| 1327 | var ret; |
|---|
| 1328 | if (v1.type == 'node-set' && v2.type == 'node-set') { |
|---|
| 1329 | var n1 = v1.nodeSetValue(); |
|---|
| 1330 | var n2 = v2.nodeSetValue(); |
|---|
| 1331 | ret = false; |
|---|
| 1332 | for (var i1 = 0; i1 < n1.length; ++i1) { |
|---|
| 1333 | for (var i2 = 0; i2 < n2.length; ++i2) { |
|---|
| 1334 | if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) { |
|---|
| 1335 | ret = true; |
|---|
| 1336 | // Break outer loop. Labels confuse the jscompiler and we |
|---|
| 1337 | // don't use them. |
|---|
| 1338 | i2 = n2.length; |
|---|
| 1339 | i1 = n1.length; |
|---|
| 1340 | } |
|---|
| 1341 | } |
|---|
| 1342 | } |
|---|
| 1343 | |
|---|
| 1344 | } else if (v1.type == 'node-set' || v2.type == 'node-set') { |
|---|
| 1345 | |
|---|
| 1346 | if (v1.type == 'number') { |
|---|
| 1347 | var s = v1.numberValue(); |
|---|
| 1348 | var n = v2.nodeSetValue(); |
|---|
| 1349 | |
|---|
| 1350 | ret = false; |
|---|
| 1351 | for (var i = 0; i < n.length; ++i) { |
|---|
| 1352 | var nn = xmlValue(n[i]) - 0; |
|---|
| 1353 | if (cmp(s, nn)) { |
|---|
| 1354 | ret = true; |
|---|
| 1355 | break; |
|---|
| 1356 | } |
|---|
| 1357 | } |
|---|
| 1358 | |
|---|
| 1359 | } else if (v2.type == 'number') { |
|---|
| 1360 | var n = v1.nodeSetValue(); |
|---|
| 1361 | var s = v2.numberValue(); |
|---|
| 1362 | |
|---|
| 1363 | ret = false; |
|---|
| 1364 | for (var i = 0; i < n.length; ++i) { |
|---|
| 1365 | var nn = xmlValue(n[i]) - 0; |
|---|
| 1366 | if (cmp(nn, s)) { |
|---|
| 1367 | ret = true; |
|---|
| 1368 | break; |
|---|
| 1369 | } |
|---|
| 1370 | } |
|---|
| 1371 | |
|---|
| 1372 | } else if (v1.type == 'string') { |
|---|
| 1373 | var s = v1.stringValue(); |
|---|
| 1374 | var n = v2.nodeSetValue(); |
|---|
| 1375 | |
|---|
| 1376 | ret = false; |
|---|
| 1377 | for (var i = 0; i < n.length; ++i) { |
|---|
| 1378 | var nn = xmlValue(n[i]); |
|---|
| 1379 | if (cmp(s, nn)) { |
|---|
| 1380 | ret = true; |
|---|
| 1381 | break; |
|---|
| 1382 | } |
|---|
| 1383 | } |
|---|
| 1384 | |
|---|
| 1385 | } else if (v2.type == 'string') { |
|---|
| 1386 | var n = v1.nodeSetValue(); |
|---|
| 1387 | var s = v2.stringValue(); |
|---|
| 1388 | |
|---|
| 1389 | ret = false; |
|---|
| 1390 | for (var i = 0; i < n.length; ++i) { |
|---|
| 1391 | var nn = xmlValue(n[i]); |
|---|
| 1392 | if (cmp(nn, s)) { |
|---|
| 1393 | ret = true; |
|---|
| 1394 | break; |
|---|
| 1395 | } |
|---|
| 1396 | } |
|---|
| 1397 | |
|---|
| 1398 | } else { |
|---|
| 1399 | ret = cmp(v1.booleanValue(), v2.booleanValue()); |
|---|
| 1400 | } |
|---|
| 1401 | |
|---|
| 1402 | } else if (v1.type == 'boolean' || v2.type == 'boolean') { |
|---|
| 1403 | ret = cmp(v1.booleanValue(), v2.booleanValue()); |
|---|
| 1404 | |
|---|
| 1405 | } else if (v1.type == 'number' || v2.type == 'number') { |
|---|
| 1406 | ret = cmp(v1.numberValue(), v2.numberValue()); |
|---|
| 1407 | |
|---|
| 1408 | } else { |
|---|
| 1409 | ret = cmp(v1.stringValue(), v2.stringValue()); |
|---|
| 1410 | } |
|---|
| 1411 | |
|---|
| 1412 | return new BooleanValue(ret); |
|---|
| 1413 | } |
|---|
| 1414 | |
|---|
| 1415 | function LiteralExpr(value) { |
|---|
| 1416 | this.value = value; |
|---|
| 1417 | } |
|---|
| 1418 | |
|---|
| 1419 | LiteralExpr.prototype.evaluate = function(ctx) { |
|---|
| 1420 | return new StringValue(this.value); |
|---|
| 1421 | }; |
|---|
| 1422 | |
|---|
| 1423 | function NumberExpr(value) { |
|---|
| 1424 | this.value = value; |
|---|
| 1425 | } |
|---|
| 1426 | |
|---|
| 1427 | NumberExpr.prototype.evaluate = function(ctx) { |
|---|
| 1428 | return new NumberValue(this.value); |
|---|
| 1429 | }; |
|---|
| 1430 | |
|---|
| 1431 | function VariableExpr(name) { |
|---|
| 1432 | this.name = name; |
|---|
| 1433 | } |
|---|
| 1434 | |
|---|
| 1435 | VariableExpr.prototype.evaluate = function(ctx) { |
|---|
| 1436 | return ctx.getVariable(this.name); |
|---|
| 1437 | } |
|---|
| 1438 | |
|---|
| 1439 | // Factory functions for semantic values (i.e. Expressions) of the |
|---|
| 1440 | // productions in the grammar. When a production is matched to reduce |
|---|
| 1441 | // the current parse state stack, the function is called with the |
|---|
| 1442 | // semantic values of the matched elements as arguments, and returns |
|---|
| 1443 | // another semantic value. The semantic value is a node of the parse |
|---|
| 1444 | // tree, an expression object with an evaluate() method that evaluates the |
|---|
| 1445 | // expression in an actual context. These factory functions are used |
|---|
| 1446 | // in the specification of the grammar rules, below. |
|---|
| 1447 | |
|---|
| 1448 | function makeTokenExpr(m) { |
|---|
| 1449 | return new TokenExpr(m); |
|---|
| 1450 | } |
|---|
| 1451 | |
|---|
| 1452 | function passExpr(e) { |
|---|
| 1453 | return e; |
|---|
| 1454 | } |
|---|
| 1455 | |
|---|
| 1456 | function makeLocationExpr1(slash, rel) { |
|---|
| 1457 | rel.absolute = true; |
|---|
| 1458 | return rel; |
|---|
| 1459 | } |
|---|
| 1460 | |
|---|
| 1461 | function makeLocationExpr2(dslash, rel) { |
|---|
| 1462 | rel.absolute = true; |
|---|
| 1463 | rel.prependStep(makeAbbrevStep(dslash.value)); |
|---|
| 1464 | return rel; |
|---|
| 1465 | } |
|---|
| 1466 | |
|---|
| 1467 | function makeLocationExpr3(slash) { |
|---|
| 1468 | var ret = new LocationExpr(); |
|---|
| 1469 | ret.appendStep(makeAbbrevStep('.')); |
|---|
| 1470 | ret.absolute = true; |
|---|
| 1471 | return ret; |
|---|
| 1472 | } |
|---|
| 1473 | |
|---|
| 1474 | function makeLocationExpr4(dslash) { |
|---|
| 1475 | var ret = new LocationExpr(); |
|---|
| 1476 | ret.absolute = true; |
|---|
| 1477 | ret.appendStep(makeAbbrevStep(dslash.value)); |
|---|
| 1478 | return ret; |
|---|
| 1479 | } |
|---|
| 1480 | |
|---|
| 1481 | function makeLocationExpr5(step) { |
|---|
| 1482 | var ret = new LocationExpr(); |
|---|
| 1483 | ret.appendStep(step); |
|---|
| 1484 | return ret; |
|---|
| 1485 | } |
|---|
| 1486 | |
|---|
| 1487 | function makeLocationExpr6(rel, slash, step) { |
|---|
| 1488 | rel.appendStep(step); |
|---|
| 1489 | return rel; |
|---|
| 1490 | } |
|---|
| 1491 | |
|---|
| 1492 | function makeLocationExpr7(rel, dslash, step) { |
|---|
| 1493 | rel.appendStep(makeAbbrevStep(dslash.value)); |
|---|
| 1494 | return rel; |
|---|
| 1495 | } |
|---|
| 1496 | |
|---|
| 1497 | function makeStepExpr1(dot) { |
|---|
| 1498 | return makeAbbrevStep(dot.value); |
|---|
| 1499 | } |
|---|
| 1500 | |
|---|
| 1501 | function makeStepExpr2(ddot) { |
|---|
| 1502 | return makeAbbrevStep(ddot.value); |
|---|
| 1503 | } |
|---|
| 1504 | |
|---|
| 1505 | function makeStepExpr3(axisname, axis, nodetest) { |
|---|
| 1506 | return new StepExpr(axisname.value, nodetest); |
|---|
| 1507 | } |
|---|
| 1508 | |
|---|
| 1509 | function makeStepExpr4(at, nodetest) { |
|---|
| 1510 | return new StepExpr('attribute', nodetest); |
|---|
| 1511 | } |
|---|
| 1512 | |
|---|
| 1513 | function makeStepExpr5(nodetest) { |
|---|
| 1514 | return new StepExpr('child', nodetest); |
|---|
| 1515 | } |
|---|
| 1516 | |
|---|
| 1517 | function makeStepExpr6(step, predicate) { |
|---|
| 1518 | step.appendPredicate(predicate); |
|---|
| 1519 | return step; |
|---|
| 1520 | } |
|---|
| 1521 | |
|---|
| 1522 | function makeAbbrevStep(abbrev) { |
|---|
| 1523 | switch (abbrev) { |
|---|
| 1524 | case '//': |
|---|
| 1525 | return new StepExpr('descendant-or-self', new NodeTestAny); |
|---|
| 1526 | |
|---|
| 1527 | case '.': |
|---|
| 1528 | return new StepExpr('self', new NodeTestAny); |
|---|
| 1529 | |
|---|
| 1530 | case '..': |
|---|
| 1531 | return new StepExpr('parent', new NodeTestAny); |
|---|
| 1532 | } |
|---|
| 1533 | } |
|---|
| 1534 | |
|---|
| 1535 | function makeNodeTestExpr1(asterisk) { |
|---|
| 1536 | return new NodeTestElement; |
|---|
| 1537 | } |
|---|
| 1538 | |
|---|
| 1539 | function makeNodeTestExpr2(ncname, colon, asterisk) { |
|---|
| 1540 | return new NodeTestNC(ncname.value); |
|---|
| 1541 | } |
|---|
| 1542 | |
|---|
| 1543 | function makeNodeTestExpr3(qname) { |
|---|
| 1544 | return new NodeTestName(qname.value); |
|---|
| 1545 | } |
|---|
| 1546 | |
|---|
| 1547 | function makeNodeTestExpr4(typeo, parenc) { |
|---|
| 1548 | var type = typeo.value.replace(/\s*\($/, ''); |
|---|
| 1549 | switch(type) { |
|---|
| 1550 | case 'node': |
|---|
| 1551 | return new NodeTestAny; |
|---|
| 1552 | |
|---|
| 1553 | case 'text': |
|---|
| 1554 | return new NodeTestText; |
|---|
| 1555 | |
|---|
| 1556 | case 'comment': |
|---|
| 1557 | return new NodeTestComment; |
|---|
| 1558 | |
|---|
| 1559 | case 'processing-instruction': |
|---|
| 1560 | return new NodeTestPI; |
|---|
| 1561 | } |
|---|
| 1562 | } |
|---|
| 1563 | |
|---|
| 1564 | function makeNodeTestExpr5(typeo, target, parenc) { |
|---|
| 1565 | var type = typeo.replace(/\s*\($/, ''); |
|---|
| 1566 | if (type != 'processing-instruction') { |
|---|
| 1567 | throw type + ' ' + Error().stack; |
|---|
| 1568 | } |
|---|
| 1569 | return new NodeTestPI(target.value); |
|---|
| 1570 | } |
|---|
| 1571 | |
|---|
| 1572 | function makePredicateExpr(pareno, expr, parenc) { |
|---|
| 1573 | return new PredicateExpr(expr); |
|---|
| 1574 | } |
|---|
| 1575 | |
|---|
| 1576 | function makePrimaryExpr(pareno, expr, parenc) { |
|---|
| 1577 | return expr; |
|---|
| 1578 | } |
|---|
| 1579 | |
|---|
| 1580 | function makeFunctionCallExpr1(name, pareno, parenc) { |
|---|
| 1581 | return new FunctionCallExpr(name); |
|---|
| 1582 | } |
|---|
| 1583 | |
|---|
| 1584 | function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) { |
|---|
| 1585 | var ret = new FunctionCallExpr(name); |
|---|
| 1586 | ret.appendArg(arg1); |
|---|
| 1587 | for (var i = 0; i < args.length; ++i) { |
|---|
| 1588 | ret.appendArg(args[i]); |
|---|
| 1589 | } |
|---|
| 1590 | return ret; |
|---|
| 1591 | } |
|---|
| 1592 | |
|---|
| 1593 | function makeArgumentExpr(comma, expr) { |
|---|
| 1594 | return expr; |
|---|
| 1595 | } |
|---|
| 1596 | |
|---|
| 1597 | function makeUnionExpr(expr1, pipe, expr2) { |
|---|
| 1598 | return new UnionExpr(expr1, expr2); |
|---|
| 1599 | } |
|---|
| 1600 | |
|---|
| 1601 | function makePathExpr1(filter, slash, rel) { |
|---|
| 1602 | return new PathExpr(filter, rel); |
|---|
| 1603 | } |
|---|
| 1604 | |
|---|
| 1605 | function makePathExpr2(filter, dslash, rel) { |
|---|
| 1606 | rel.prependStep(makeAbbrevStep(dslash.value)); |
|---|
| 1607 | return new PathExpr(filter, rel); |
|---|
| 1608 | } |
|---|
| 1609 | |
|---|
| 1610 | function makeFilterExpr(expr, predicates) { |
|---|
| 1611 | if (predicates.length > 0) { |
|---|
| 1612 | return new FilterExpr(expr, predicates); |
|---|
| 1613 | } else { |
|---|
| 1614 | return expr; |
|---|
| 1615 | } |
|---|
| 1616 | } |
|---|
| 1617 | |
|---|
| 1618 | function makeUnaryMinusExpr(minus, expr) { |
|---|
| 1619 | return new UnaryMinusExpr(expr); |
|---|
| 1620 | } |
|---|
| 1621 | |
|---|
| 1622 | function makeBinaryExpr(expr1, op, expr2) { |
|---|
| 1623 | return new BinaryExpr(expr1, op, expr2); |
|---|
| 1624 | } |
|---|
| 1625 | |
|---|
| 1626 | function makeLiteralExpr(token) { |
|---|
| 1627 | // remove quotes from the parsed value: |
|---|
| 1628 | var value = token.value.substring(1, token.value.length - 1); |
|---|
| 1629 | return new LiteralExpr(value); |
|---|
| 1630 | } |
|---|
| 1631 | |
|---|
| 1632 | function makeNumberExpr(token) { |
|---|
| 1633 | return new NumberExpr(token.value); |
|---|
| 1634 | } |
|---|
| 1635 | |
|---|
| 1636 | function makeVariableReference(dollar, name) { |
|---|
| 1637 | return new VariableExpr(name.value); |
|---|
| 1638 | } |
|---|
| 1639 | |
|---|
| 1640 | // Used before parsing for optimization of common simple cases. See |
|---|
| 1641 | // the begin of xpathParse() for which they are. |
|---|
| 1642 | function makeSimpleExpr(expr) { |
|---|
| 1643 | if (expr.charAt(0) == '$') { |
|---|
| 1644 | return new VariableExpr(expr.substr(1)); |
|---|
| 1645 | } else if (expr.charAt(0) == '@') { |
|---|
| 1646 | var a = new NodeTestName(expr.substr(1)); |
|---|
| 1647 | var b = new StepExpr('attribute', a); |
|---|
| 1648 | var c = new LocationExpr(); |
|---|
| 1649 | c.appendStep(b); |
|---|
| 1650 | return c; |
|---|
| 1651 | } else if (expr.match(/^[0-9]+$/)) { |
|---|
| 1652 | return new NumberExpr(expr); |
|---|
| 1653 | } else { |
|---|
| 1654 | var a = new NodeTestName(expr); |
|---|
| 1655 | var b = new StepExpr('child', a); |
|---|
| 1656 | var c = new LocationExpr(); |
|---|
| 1657 | c.appendStep(b); |
|---|
| 1658 | return c; |
|---|
| 1659 | } |
|---|
| 1660 | } |
|---|
| 1661 | |
|---|
| 1662 | function makeSimpleExpr2(expr) { |
|---|
| 1663 | var steps = expr.split('/'); |
|---|
| 1664 | var c = new LocationExpr(); |
|---|
| 1665 | for (var i = 0; i < steps.length; i++) { |
|---|
| 1666 | var a = new NodeTestName(steps[i]); |
|---|
| 1667 | var b = new StepExpr('child', a); |
|---|
| 1668 | c.appendStep(b); |
|---|
| 1669 | } |
|---|
| 1670 | return c; |
|---|
| 1671 | } |
|---|
| 1672 | |
|---|
| 1673 | // The axes of XPath expressions. |
|---|
| 1674 | |
|---|
| 1675 | var xpathAxis = { |
|---|
| 1676 | ANCESTOR_OR_SELF: 'ancestor-or-self', |
|---|
| 1677 | ANCESTOR: 'ancestor', |
|---|
| 1678 | ATTRIBUTE: 'attribute', |
|---|
| 1679 | CHILD: 'child', |
|---|
| 1680 | DESCENDANT_OR_SELF: 'descendant-or-self', |
|---|
| 1681 | DESCENDANT: 'descendant', |
|---|
| 1682 | FOLLOWING_SIBLING: 'following-sibling', |
|---|
| 1683 | FOLLOWING: 'following', |
|---|
| 1684 | NAMESPACE: 'namespace', |
|---|
| 1685 | PARENT: 'parent', |
|---|
| 1686 | PRECEDING_SIBLING: 'preceding-sibling', |
|---|
| 1687 | PRECEDING: 'preceding', |
|---|
| 1688 | SELF: 'self' |
|---|
| 1689 | }; |
|---|
| 1690 | |
|---|
| 1691 | var xpathAxesRe = [ |
|---|
| 1692 | xpathAxis.ANCESTOR_OR_SELF, |
|---|
| 1693 | xpathAxis.ANCESTOR, |
|---|
| 1694 | xpathAxis.ATTRIBUTE, |
|---|
| 1695 | xpathAxis.CHILD, |
|---|
| 1696 | xpathAxis.DESCENDANT_OR_SELF, |
|---|
| 1697 | xpathAxis.DESCENDANT, |
|---|
| 1698 | xpathAxis.FOLLOWING_SIBLING, |
|---|
| 1699 | xpathAxis.FOLLOWING, |
|---|
| 1700 | xpathAxis.NAMESPACE, |
|---|
| 1701 | xpathAxis.PARENT, |
|---|
| 1702 | xpathAxis.PRECEDING_SIBLING, |
|---|
| 1703 | xpathAxis.PRECEDING, |
|---|
| 1704 | xpathAxis.SELF |
|---|
| 1705 | ].join('|'); |
|---|
| 1706 | |
|---|
| 1707 | |
|---|
| 1708 | // The tokens of the language. The label property is just used for |
|---|
| 1709 | // generating debug output. The prec property is the precedence used |
|---|
| 1710 | // for shift/reduce resolution. Default precedence is 0 as a lookahead |
|---|
| 1711 | // token and 2 on the stack. TODO(mesch): this is certainly not |
|---|
| 1712 | // necessary and too complicated. Simplify this! |
|---|
| 1713 | |
|---|
| 1714 | // NOTE: tabular formatting is the big exception, but here it should |
|---|
| 1715 | // be OK. |
|---|
| 1716 | |
|---|
| 1717 | var TOK_PIPE = { label: "|", prec: 17, re: new RegExp("^\\|") }; |
|---|
| 1718 | var TOK_DSLASH = { label: "//", prec: 19, re: new RegExp("^//") }; |
|---|
| 1719 | var TOK_SLASH = { label: "/", prec: 30, re: new RegExp("^/") }; |
|---|
| 1720 | var TOK_AXIS = { label: "::", prec: 20, re: new RegExp("^::") }; |
|---|
| 1721 | var TOK_COLON = { label: ":", prec: 1000, re: new RegExp("^:") }; |
|---|
| 1722 | var TOK_AXISNAME = { label: "[axis]", re: new RegExp('^(' + xpathAxesRe + ')') }; |
|---|
| 1723 | var TOK_PARENO = { label: "(", prec: 34, re: new RegExp("^\\(") }; |
|---|
| 1724 | var TOK_PARENC = { label: ")", re: new RegExp("^\\)") }; |
|---|
| 1725 | var TOK_DDOT = { label: "..", prec: 34, re: new RegExp("^\\.\\.") }; |
|---|
| 1726 | var TOK_DOT = { label: ".", prec: 34, re: new RegExp("^\\.") }; |
|---|
| 1727 | var TOK_AT = { label: "@", prec: 34, re: new RegExp("^@") }; |
|---|
| 1728 | |
|---|
| 1729 | var TOK_COMMA = { label: ",", re: new RegExp("^,") }; |
|---|
| 1730 | |
|---|
| 1731 | var TOK_OR = { label: "or", prec: 10, re: new RegExp("^or\\b") }; |
|---|
| 1732 | var TOK_AND = { label: "and", prec: 11, re: new RegExp("^and\\b") }; |
|---|
| 1733 | var TOK_EQ = { label: "=", prec: 12, re: new RegExp("^=") }; |
|---|
| 1734 | var TOK_NEQ = { label: "!=", prec: 12, re: new RegExp("^!=") }; |
|---|
| 1735 | var TOK_GE = { label: ">=", prec: 13, re: new RegExp("^>=") }; |
|---|
| 1736 | var TOK_GT = { label: ">", prec: 13, re: new RegExp("^>") }; |
|---|
| 1737 | var TOK_LE = { label: "<=", prec: 13, re: new RegExp("^<=") }; |
|---|
| 1738 | var TOK_LT = { label: "<", prec: 13, re: new RegExp("^<") }; |
|---|
| 1739 | var TOK_PLUS = { label: "+", prec: 14, re: new RegExp("^\\+"), left: true }; |
|---|
| 1740 | var TOK_MINUS = { label: "-", prec: 14, re: new RegExp("^\\-"), left: true }; |
|---|
| 1741 | var TOK_DIV = { label: "div", prec: 15, re: new RegExp("^div\\b"), left: true }; |
|---|
| 1742 | var TOK_MOD = { label: "mod", prec: 15, re: new RegExp("^mod\\b"), left: true }; |
|---|
| 1743 | |
|---|
| 1744 | var TOK_BRACKO = { label: "[", prec: 32, re: new RegExp("^\\[") }; |
|---|
| 1745 | var TOK_BRACKC = { label: "]", re: new RegExp("^\\]") }; |
|---|
| 1746 | var TOK_DOLLAR = { label: "$", re: new RegExp("^\\$") }; |
|---|
| 1747 | |
|---|
| 1748 | var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^[a-z][-\\w]*','i') }; |
|---|
| 1749 | |
|---|
| 1750 | var TOK_ASTERISK = { label: "*", prec: 15, re: new RegExp("^\\*"), left: true }; |
|---|
| 1751 | var TOK_LITERALQ = { label: "[litq]", prec: 20, re: new RegExp("^'[^\\']*'") }; |
|---|
| 1752 | var TOK_LITERALQQ = { |
|---|
| 1753 | label: "[litqq]", |
|---|
| 1754 | prec: 20, |
|---|
| 1755 | re: new RegExp('^"[^\\"]*"') |
|---|
| 1756 | }; |
|---|
| 1757 | |
|---|
| 1758 | var TOK_NUMBER = { |
|---|
| 1759 | label: "[number]", |
|---|
| 1760 | prec: 35, |
|---|
| 1761 | re: new RegExp('^\\d+(\\.\\d*)?') }; |
|---|
| 1762 | |
|---|
| 1763 | var TOK_QNAME = { |
|---|
| 1764 | label: "[qname]", |
|---|
| 1765 | re: new RegExp('^([a-z][-\\w]*:)?[a-z][-\\w]*','i') |
|---|
| 1766 | }; |
|---|
| 1767 | |
|---|
| 1768 | var TOK_NODEO = { |
|---|
| 1769 | label: "[nodetest-start]", |
|---|
| 1770 | re: new RegExp('^(processing-instruction|comment|text|node)\\(') |
|---|
| 1771 | }; |
|---|
| 1772 | |
|---|
| 1773 | // The table of the tokens of our grammar, used by the lexer: first |
|---|
| 1774 | // column the tag, second column a regexp to recognize it in the |
|---|
| 1775 | // input, third column the precedence of the token, fourth column a |
|---|
| 1776 | // factory function for the semantic value of the token. |
|---|
| 1777 | // |
|---|
| 1778 | // NOTE: order of this list is important, because the first match |
|---|
| 1779 | // counts. Cf. DDOT and DOT, and AXIS and COLON. |
|---|
| 1780 | |
|---|
| 1781 | var xpathTokenRules = [ |
|---|
| 1782 | TOK_DSLASH, |
|---|
| 1783 | TOK_SLASH, |
|---|
| 1784 | TOK_DDOT, |
|---|
| 1785 | TOK_DOT, |
|---|
| 1786 | TOK_AXIS, |
|---|
| 1787 | TOK_COLON, |
|---|
| 1788 | TOK_AXISNAME, |
|---|
| 1789 | TOK_NODEO, |
|---|
| 1790 | TOK_PARENO, |
|---|
| 1791 | TOK_PARENC, |
|---|
| 1792 | TOK_BRACKO, |
|---|
| 1793 | TOK_BRACKC, |
|---|
| 1794 | TOK_AT, |
|---|
| 1795 | TOK_COMMA, |
|---|
| 1796 | TOK_OR, |
|---|
| 1797 | TOK_AND, |
|---|
| 1798 | TOK_NEQ, |
|---|
| 1799 | TOK_EQ, |
|---|
| 1800 | TOK_GE, |
|---|
| 1801 | TOK_GT, |
|---|
| 1802 | TOK_LE, |
|---|
| 1803 | TOK_LT, |
|---|
| 1804 | TOK_PLUS, |
|---|
| 1805 | TOK_MINUS, |
|---|
| 1806 | TOK_ASTERISK, |
|---|
| 1807 | TOK_PIPE, |
|---|
| 1808 | TOK_MOD, |
|---|
| 1809 | TOK_DIV, |
|---|
| 1810 | TOK_LITERALQ, |
|---|
| 1811 | TOK_LITERALQQ, |
|---|
| 1812 | TOK_NUMBER, |
|---|
| 1813 | TOK_QNAME, |
|---|
| 1814 | TOK_NCNAME, |
|---|
| 1815 | TOK_DOLLAR |
|---|
| 1816 | ]; |
|---|
| 1817 | |
|---|
| 1818 | // All the nonterminals of the grammar. The nonterminal objects are |
|---|
| 1819 | // identified by object identity; the labels are used in the debug |
|---|
| 1820 | // output only. |
|---|
| 1821 | var XPathLocationPath = { label: "LocationPath" }; |
|---|
| 1822 | var XPathRelativeLocationPath = { label: "RelativeLocationPath" }; |
|---|
| 1823 | var XPathAbsoluteLocationPath = { label: "AbsoluteLocationPath" }; |
|---|
| 1824 | var XPathStep = { label: "Step" }; |
|---|
| 1825 | var XPathNodeTest = { label: "NodeTest" }; |
|---|
| 1826 | var XPathPredicate = { label: "Predicate" }; |
|---|
| 1827 | var XPathLiteral = { label: "Literal" }; |
|---|
| 1828 | var XPathExpr = { label: "Expr" }; |
|---|
| 1829 | var XPathPrimaryExpr = { label: "PrimaryExpr" }; |
|---|
| 1830 | var XPathVariableReference = { label: "Variablereference" }; |
|---|
| 1831 | var XPathNumber = { label: "Number" }; |
|---|
| 1832 | var XPathFunctionCall = { label: "FunctionCall" }; |
|---|
| 1833 | var XPathArgumentRemainder = { label: "ArgumentRemainder" }; |
|---|
| 1834 | var XPathPathExpr = { label: "PathExpr" }; |
|---|
| 1835 | var XPathUnionExpr = { label: "UnionExpr" }; |
|---|
| 1836 | var XPathFilterExpr = { label: "FilterExpr" }; |
|---|
| 1837 | var XPathDigits = { label: "Digits" }; |
|---|
| 1838 | |
|---|
| 1839 | var xpathNonTerminals = [ |
|---|
| 1840 | XPathLocationPath, |
|---|
| 1841 | XPathRelativeLocationPath, |
|---|
| 1842 | XPathAbsoluteLocationPath, |
|---|
| 1843 | XPathStep, |
|---|
| 1844 | XPathNodeTest, |
|---|
| 1845 | XPathPredicate, |
|---|
| 1846 | XPathLiteral, |
|---|
| 1847 | XPathExpr, |
|---|
| 1848 | XPathPrimaryExpr, |
|---|
| 1849 | XPathVariableReference, |
|---|
| 1850 | XPathNumber, |
|---|
| 1851 | XPathFunctionCall, |
|---|
| 1852 | XPathArgumentRemainder, |
|---|
| 1853 | XPathPathExpr, |
|---|
| 1854 | XPathUnionExpr, |
|---|
| 1855 | XPathFilterExpr, |
|---|
| 1856 | XPathDigits |
|---|
| 1857 | ]; |
|---|
| 1858 | |
|---|
| 1859 | // Quantifiers that are used in the productions of the grammar. |
|---|
| 1860 | var Q_01 = { label: "?" }; |
|---|
| 1861 | var Q_MM = { label: "*" }; |
|---|
| 1862 | var Q_1M = { label: "+" }; |
|---|
| 1863 | |
|---|
| 1864 | // Tag for left associativity (right assoc is implied by undefined). |
|---|
| 1865 | var ASSOC_LEFT = true; |
|---|
| 1866 | |
|---|
| 1867 | // The productions of the grammar. Columns of the table: |
|---|
| 1868 | // |
|---|
| 1869 | // - target nonterminal, |
|---|
| 1870 | // - pattern, |
|---|
| 1871 | // - precedence, |
|---|
| 1872 | // - semantic value factory |
|---|
| 1873 | // |
|---|
| 1874 | // The semantic value factory is a function that receives parse tree |
|---|
| 1875 | // nodes from the stack frames of the matched symbols as arguments and |
|---|
| 1876 | // returns an a node of the parse tree. The node is stored in the top |
|---|
| 1877 | // stack frame along with the target object of the rule. The node in |
|---|
| 1878 | // the parse tree is an expression object that has an evaluate() method |
|---|
| 1879 | // and thus evaluates XPath expressions. |
|---|
| 1880 | // |
|---|
| 1881 | // The precedence is used to decide between reducing and shifting by |
|---|
| 1882 | // comparing the precendence of the rule that is candidate for |
|---|
| 1883 | // reducing with the precedence of the look ahead token. Precedence of |
|---|
| 1884 | // -1 means that the precedence of the tokens in the pattern is used |
|---|
| 1885 | // instead. TODO: It shouldn't be necessary to explicitly assign |
|---|
| 1886 | // precedences to rules. |
|---|
| 1887 | |
|---|
| 1888 | var xpathGrammarRules = |
|---|
| 1889 | [ |
|---|
| 1890 | [ XPathLocationPath, [ XPathRelativeLocationPath ], 18, |
|---|
| 1891 | passExpr ], |
|---|
| 1892 | [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18, |
|---|
| 1893 | passExpr ], |
|---|
| 1894 | |
|---|
| 1895 | [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18, |
|---|
| 1896 | makeLocationExpr1 ], |
|---|
| 1897 | [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18, |
|---|
| 1898 | makeLocationExpr2 ], |
|---|
| 1899 | |
|---|
| 1900 | [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0, |
|---|
| 1901 | makeLocationExpr3 ], |
|---|
| 1902 | [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0, |
|---|
| 1903 | makeLocationExpr4 ], |
|---|
| 1904 | |
|---|
| 1905 | [ XPathRelativeLocationPath, [ XPathStep ], 31, |
|---|
| 1906 | makeLocationExpr5 ], |
|---|
| 1907 | [ XPathRelativeLocationPath, |
|---|
| 1908 | [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31, |
|---|
| 1909 | makeLocationExpr6 ], |
|---|
| 1910 | [ XPathRelativeLocationPath, |
|---|
| 1911 | [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31, |
|---|
| 1912 | makeLocationExpr7 ], |
|---|
| 1913 | |
|---|
| 1914 | [ XPathStep, [ TOK_DOT ], 33, |
|---|
| 1915 | makeStepExpr1 ], |
|---|
| 1916 | [ XPathStep, [ TOK_DDOT ], 33, |
|---|
| 1917 | makeStepExpr2 ], |
|---|
| 1918 | [ XPathStep, |
|---|
| 1919 | [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33, |
|---|
| 1920 | makeStepExpr3 ], |
|---|
| 1921 | [ XPathStep, [ TOK_AT, XPathNodeTest ], 33, |
|---|
| 1922 | makeStepExpr4 ], |
|---|
| 1923 | [ XPathStep, [ XPathNodeTest ], 33, |
|---|
| 1924 | makeStepExpr5 ], |
|---|
| 1925 | [ XPathStep, [ XPathStep, XPathPredicate ], 33, |
|---|
| 1926 | makeStepExpr6 ], |
|---|
| 1927 | |
|---|
| 1928 | [ XPathNodeTest, [ TOK_ASTERISK ], 33, |
|---|
| 1929 | makeNodeTestExpr1 ], |
|---|
| 1930 | [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33, |
|---|
| 1931 | makeNodeTestExpr2 ], |
|---|
| 1932 | [ XPathNodeTest, [ TOK_QNAME ], 33, |
|---|
| 1933 | makeNodeTestExpr3 ], |
|---|
| 1934 | [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33, |
|---|
| 1935 | makeNodeTestExpr4 ], |
|---|
| 1936 | [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33, |
|---|
| 1937 | makeNodeTestExpr5 ], |
|---|
| 1938 | |
|---|
| 1939 | [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33, |
|---|
| 1940 | makePredicateExpr ], |
|---|
| 1941 | |
|---|
| 1942 | [ XPathPrimaryExpr, [ XPathVariableReference ], 33, |
|---|
| 1943 | passExpr ], |
|---|
| 1944 | [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33, |
|---|
| 1945 | makePrimaryExpr ], |
|---|
| 1946 | [ XPathPrimaryExpr, [ XPathLiteral ], 30, |
|---|
| 1947 | passExpr ], |
|---|
| 1948 | [ XPathPrimaryExpr, [ XPathNumber ], 30, |
|---|
| 1949 | passExpr ], |
|---|
| 1950 | [ XPathPrimaryExpr, [ XPathFunctionCall ], 30, |
|---|
| 1951 | passExpr ], |
|---|
| 1952 | |
|---|
| 1953 | [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1, |
|---|
| 1954 | makeFunctionCallExpr1 ], |
|---|
| 1955 | [ XPathFunctionCall, |
|---|
| 1956 | [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM, |
|---|
| 1957 | TOK_PARENC ], -1, |
|---|
| 1958 | makeFunctionCallExpr2 ], |
|---|
| 1959 | [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1, |
|---|
| 1960 | makeArgumentExpr ], |
|---|
| 1961 | |
|---|
| 1962 | [ XPathUnionExpr, [ XPathPathExpr ], 20, |
|---|
| 1963 | passExpr ], |
|---|
| 1964 | [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20, |
|---|
| 1965 | makeUnionExpr ], |
|---|
| 1966 | |
|---|
| 1967 | [ XPathPathExpr, [ XPathLocationPath ], 20, |
|---|
| 1968 | passExpr ], |
|---|
| 1969 | [ XPathPathExpr, [ XPathFilterExpr ], 19, |
|---|
| 1970 | passExpr ], |
|---|
| 1971 | [ XPathPathExpr, |
|---|
| 1972 | [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 20, |
|---|
| 1973 | makePathExpr1 ], |
|---|
| 1974 | [ XPathPathExpr, |
|---|
| 1975 | [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 20, |
|---|
| 1976 | makePathExpr2 ], |
|---|
| 1977 | |
|---|
| 1978 | [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 20, |
|---|
| 1979 | makeFilterExpr ], |
|---|
| 1980 | |
|---|
| 1981 | [ XPathExpr, [ XPathPrimaryExpr ], 16, |
|---|
| 1982 | passExpr ], |
|---|
| 1983 | [ XPathExpr, [ XPathUnionExpr ], 16, |
|---|
| 1984 | passExpr ], |
|---|
| 1985 | |
|---|
| 1986 | [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1, |
|---|
| 1987 | makeUnaryMinusExpr ], |
|---|
| 1988 | |
|---|
| 1989 | [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1, |
|---|
| 1990 | makeBinaryExpr ], |
|---|
| 1991 | [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1, |
|---|
| 1992 | makeBinaryExpr ], |
|---|
| 1993 | |
|---|
| 1994 | [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1, |
|---|
| 1995 | makeBinaryExpr ], |
|---|
| 1996 | [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1, |
|---|
| 1997 | makeBinaryExpr ], |
|---|
| 1998 | |
|---|
| 1999 | [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1, |
|---|
| 2000 | makeBinaryExpr ], |
|---|
| 2001 | [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1, |
|---|
| 2002 | makeBinaryExpr ], |
|---|
| 2003 | [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1, |
|---|
| 2004 | makeBinaryExpr ], |
|---|
| 2005 | [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1, |
|---|
| 2006 | makeBinaryExpr ], |
|---|
| 2007 | |
|---|
| 2008 | [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1, |
|---|
| 2009 | makeBinaryExpr, ASSOC_LEFT ], |
|---|
| 2010 | [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1, |
|---|
| 2011 | makeBinaryExpr, ASSOC_LEFT ], |
|---|
| 2012 | |
|---|
| 2013 | [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1, |
|---|
| 2014 | makeBinaryExpr, ASSOC_LEFT ], |
|---|
| 2015 | [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1, |
|---|
| 2016 | makeBinaryExpr, ASSOC_LEFT ], |
|---|
| 2017 | [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1, |
|---|
| 2018 | makeBinaryExpr, ASSOC_LEFT ], |
|---|
| 2019 | |
|---|
| 2020 | [ XPathLiteral, [ TOK_LITERALQ ], -1, |
|---|
| 2021 | makeLiteralExpr ], |
|---|
| 2022 | [ XPathLiteral, [ TOK_LITERALQQ ], -1, |
|---|
| 2023 | makeLiteralExpr ], |
|---|
| 2024 | |
|---|
| 2025 | [ XPathNumber, [ TOK_NUMBER ], -1, |
|---|
| 2026 | makeNumberExpr ], |
|---|
| 2027 | |
|---|
| 2028 | [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200, |
|---|
| 2029 | makeVariableReference ] |
|---|
| 2030 | ]; |
|---|
| 2031 | |
|---|
| 2032 | // That function computes some optimizations of the above data |
|---|
| 2033 | // structures and will be called right here. It merely takes the |
|---|
| 2034 | // counter variables out of the global scope. |
|---|
| 2035 | |
|---|
| 2036 | var xpathRules = []; |
|---|
| 2037 | |
|---|
| 2038 | function xpathParseInit() { |
|---|
| 2039 | if (xpathRules.length) { |
|---|
| 2040 | return; |
|---|
| 2041 | } |
|---|
| 2042 | |
|---|
| 2043 | // Some simple optimizations for the xpath expression parser: sort |
|---|
| 2044 | // grammar rules descending by length, so that the longest match is |
|---|
| 2045 | // first found. |
|---|
| 2046 | |
|---|
| 2047 | xpathGrammarRules.sort(function(a,b) { |
|---|
| 2048 | var la = a[1].length; |
|---|
| 2049 | var lb = b[1].length; |
|---|
| 2050 | if (la < lb) { |
|---|
| 2051 | return 1; |
|---|
| 2052 | } else if (la > lb) { |
|---|
| 2053 | return -1; |
|---|
| 2054 | } else { |
|---|
| 2055 | return 0; |
|---|
| 2056 | } |
|---|
| 2057 | }); |
|---|
| 2058 | |
|---|
| 2059 | var k = 1; |
|---|
| 2060 | for (var i = 0; i < xpathNonTerminals.length; ++i) { |
|---|
| 2061 | xpathNonTerminals[i].key = k++; |
|---|
| 2062 | } |
|---|
| 2063 | |
|---|
| 2064 | for (i = 0; i < xpathTokenRules.length; ++i) { |
|---|
| 2065 | xpathTokenRules[i].key = k++; |
|---|
| 2066 | } |
|---|
| 2067 | |
|---|
| 2068 | if (xpathdebug) |
|---|
| 2069 | Log.write('XPath parse INIT: ' + k + ' rules'); |
|---|
| 2070 | |
|---|
| 2071 | // Another slight optimization: sort the rules into bins according |
|---|
| 2072 | // to the last element (observing quantifiers), so we can restrict |
|---|
| 2073 | // the match against the stack to the subest of rules that match the |
|---|
| 2074 | // top of the stack. |
|---|
| 2075 | // |
|---|
| 2076 | // TODO(mesch): What we actually want is to compute states as in |
|---|
| 2077 | // bison, so that we don't have to do any explicit and iterated |
|---|
| 2078 | // match against the stack. |
|---|
| 2079 | |
|---|
| 2080 | function push_(array, position, element) { |
|---|
| 2081 | if (!array[position]) { |
|---|
| 2082 | array[position] = []; |
|---|
| 2083 | } |
|---|
| 2084 | array[position].push(element); |
|---|
| 2085 | } |
|---|
| 2086 | |
|---|
| 2087 | for (i = 0; i < xpathGrammarRules.length; ++i) { |
|---|
| 2088 | var rule = xpathGrammarRules[i]; |
|---|
| 2089 | var pattern = rule[1]; |
|---|
| 2090 | |
|---|
| 2091 | for (var j = pattern.length - 1; j >= 0; --j) { |
|---|
| 2092 | if (pattern[j] == Q_1M) { |
|---|
| 2093 | push_(xpathRules, pattern[j-1].key, rule); |
|---|
| 2094 | break; |
|---|
| 2095 | |
|---|
| 2096 | } else if (pattern[j] == Q_MM || pattern[j] == Q_01) { |
|---|
| 2097 | push_(xpathRules, pattern[j-1].key, rule); |
|---|
| 2098 | --j; |
|---|
| 2099 | |
|---|
| 2100 | } else { |
|---|
| 2101 | push_(xpathRules, pattern[j].key, rule); |
|---|
| 2102 | break; |
|---|
| 2103 | } |
|---|
| 2104 | } |
|---|
| 2105 | } |
|---|
| 2106 | |
|---|
| 2107 | if (xpathdebug) |
|---|
| 2108 | Log.write('XPath parse INIT: ' + xpathRules.length + ' rule bins'); |
|---|
| 2109 | |
|---|
| 2110 | var sum = 0; |
|---|
| 2111 | mapExec(xpathRules, function(i) { |
|---|
| 2112 | if (i) { |
|---|
| 2113 | sum += i.length; |
|---|
| 2114 | } |
|---|
| 2115 | }); |
|---|
| 2116 | |
|---|
| 2117 | if (xpathdebug) |
|---|
| 2118 | Log.write('XPath parse INIT: ' + (sum / xpathRules.length) + ' average bin size'); |
|---|
| 2119 | } |
|---|
| 2120 | |
|---|
| 2121 | // Local utility functions that are used by the lexer or parser. |
|---|
| 2122 | |
|---|
| 2123 | function xpathCollectDescendants(nodelist, node) { |
|---|
| 2124 | for (var n = node.firstChild; n; n = n.nextSibling) { |
|---|
| 2125 | nodelist.push(n); |
|---|
| 2126 | arguments.callee(nodelist, n); |
|---|
| 2127 | } |
|---|
| 2128 | } |
|---|
| 2129 | |
|---|
| 2130 | function xpathCollectDescendantsReverse(nodelist, node) { |
|---|
| 2131 | for (var n = node.lastChild; n; n = n.previousSibling) { |
|---|
| 2132 | nodelist.push(n); |
|---|
| 2133 | arguments.callee(nodelist, n); |
|---|
| 2134 | } |
|---|
| 2135 | } |
|---|
| 2136 | |
|---|
| 2137 | |
|---|
| 2138 | // The entry point for the library: match an expression against a DOM |
|---|
| 2139 | // node. Returns an XPath value. |
|---|
| 2140 | function xpathDomEval(expr, node) { |
|---|
| 2141 | var expr1 = xpathParse(expr); |
|---|
| 2142 | var ret = expr1.evaluate(new ExprContext(node)); |
|---|
| 2143 | return ret; |
|---|
| 2144 | } |
|---|
| 2145 | |
|---|
| 2146 | // Utility function to sort a list of nodes. Used by xsltSort() and |
|---|
| 2147 | // nxslSelect(). |
|---|
| 2148 | function xpathSort(input, sort) { |
|---|
| 2149 | if (sort.length == 0) { |
|---|
| 2150 | return; |
|---|
| 2151 | } |
|---|
| 2152 | |
|---|
| 2153 | var sortlist = []; |
|---|
| 2154 | |
|---|
| 2155 | for (var i = 0; i < input.nodelist.length; ++i) { |
|---|
| 2156 | var node = input.nodelist[i]; |
|---|
| 2157 | var sortitem = { node: node, key: [] }; |
|---|
| 2158 | var context = input.clone(node, 0, [ node ]); |
|---|
| 2159 | |
|---|
| 2160 | for (var j = 0; j < sort.length; ++j) { |
|---|
| 2161 | var s = sort[j]; |
|---|
| 2162 | var value = s.expr.evaluate(context); |
|---|
| 2163 | |
|---|
| 2164 | var evalue; |
|---|
| 2165 | if (s.type == 'text') { |
|---|
| 2166 | evalue = value.stringValue(); |
|---|
| 2167 | } else if (s.type == 'number') { |
|---|
| 2168 | evalue = value.numberValue(); |
|---|
| 2169 | } |
|---|
| 2170 | sortitem.key.push({ value: evalue, order: s.order }); |
|---|
| 2171 | } |
|---|
| 2172 | |
|---|
| 2173 | // Make the sort stable by adding a lowest priority sort by |
|---|
| 2174 | // id. This is very convenient and furthermore required by the |
|---|
| 2175 | // spec ([XSLT] - Section 10 Sorting). |
|---|
| 2176 | sortitem.key.push({ value: i, order: 'ascending' }); |
|---|
| 2177 | |
|---|
| 2178 | sortlist.push(sortitem); |
|---|
| 2179 | } |
|---|
| 2180 | |
|---|
| 2181 | sortlist.sort(xpathSortByKey); |
|---|
| 2182 | |
|---|
| 2183 | var nodes = []; |
|---|
| 2184 | for (var i = 0; i < sortlist.length; ++i) { |
|---|
| 2185 | nodes.push(sortlist[i].node); |
|---|
| 2186 | } |
|---|
| 2187 | input.nodelist = nodes; |
|---|
| 2188 | input.setNode(nodes[0], 0); |
|---|
| 2189 | } |
|---|
| 2190 | |
|---|
| 2191 | |
|---|
| 2192 | // Sorts by all order criteria defined. According to the JavaScript |
|---|
| 2193 | // spec ([ECMA] Section 11.8.5), the compare operators compare strings |
|---|
| 2194 | // as strings and numbers as numbers. |
|---|
| 2195 | // |
|---|
| 2196 | // NOTE: In browsers which do not follow the spec, this breaks only in |
|---|
| 2197 | // the case that numbers should be sorted as strings, which is very |
|---|
| 2198 | // uncommon. |
|---|
| 2199 | |
|---|
| 2200 | function xpathSortByKey(v1, v2) { |
|---|
| 2201 | // NOTE: Sort key vectors of different length never occur in |
|---|
| 2202 | // xsltSort. |
|---|
| 2203 | |
|---|
| 2204 | for (var i = 0; i < v1.key.length; ++i) { |
|---|
| 2205 | var o = v1.key[i].order == 'descending' ? -1 : 1; |
|---|
| 2206 | if (v1.key[i].value > v2.key[i].value) { |
|---|
| 2207 | return +1 * o; |
|---|
| 2208 | } else if (v1.key[i].value < v2.key[i].value) { |
|---|
| 2209 | return -1 * o; |
|---|
| 2210 | } |
|---|
| 2211 | } |
|---|
| 2212 | |
|---|
| 2213 | return 0; |
|---|
| 2214 | } |
|---|
| 2215 | |
|---|
| 2216 | |
|---|
| 2217 | // Copyright (c) 2005, Google Inc. |
|---|
| 2218 | // All rights reserved. |
|---|
| 2219 | // |
|---|
| 2220 | // Redistribution and use in source and binary forms, with or without |
|---|
| 2221 | // modification, are permitted provided that the following conditions are |
|---|
| 2222 | // met: |
|---|
| 2223 | // |
|---|
| 2224 | // * Redistributions of source code must retain the above copyright |
|---|
| 2225 | // notice, this list of conditions and the following disclaimer. |
|---|
| 2226 | // |
|---|
| 2227 | // * Redistributions in binary form must reproduce the above copyright |
|---|
| 2228 | // notice, this list of conditions and the following disclaimer in the |
|---|
| 2229 | // documentation and/or other materials provided with the |
|---|
| 2230 | // distribution. |
|---|
| 2231 | // |
|---|
| 2232 | // * Neither the name of Google Inc. nor the names of its contributors |
|---|
| 2233 | // may be used to endorse or promote products derived from this |
|---|
| 2234 | // software without specific prior written permission. |
|---|
| 2235 | // |
|---|
| 2236 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|---|
| 2237 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|---|
| 2238 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|---|
| 2239 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|---|
| 2240 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|---|
| 2241 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|---|
| 2242 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|---|
| 2243 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|---|
| 2244 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|---|
| 2245 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|---|
| 2246 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 2247 | // |
|---|
| 2248 | // Miscellania that support the ajaxslt implementation. |
|---|
| 2249 | // |
|---|
| 2250 | // Author: Steffen Meschkat <mesch@google.com> |
|---|
| 2251 | // |
|---|
| 2252 | |
|---|
| 2253 | function el(i) { |
|---|
| 2254 | return document.getElementById(i); |
|---|
| 2255 | } |
|---|
| 2256 | |
|---|
| 2257 | function px(x) { |
|---|
| 2258 | return x + 'px'; |
|---|
| 2259 | } |
|---|
| 2260 | |
|---|
| 2261 | // Split a string s at all occurrences of character c. This is like |
|---|
| 2262 | // the split() method of the string object, but IE omits empty |
|---|
| 2263 | // strings, which violates the invariant (s.split(x).join(x) == s). |
|---|
| 2264 | function stringSplit(s, c) { |
|---|
| 2265 | var a = s.indexOf(c); |
|---|
| 2266 | if (a == -1) { |
|---|
| 2267 | return [ s ]; |
|---|
| 2268 | } |
|---|
| 2269 | |
|---|
| 2270 | var parts = []; |
|---|
| 2271 | parts.push(s.substr(0,a)); |
|---|
| 2272 | while (a != -1) { |
|---|
| 2273 | var a1 = s.indexOf(c, a + 1); |
|---|
| 2274 | if (a1 != -1) { |
|---|
| 2275 | parts.push(s.substr(a + 1, a1 - a - 1)); |
|---|
| 2276 | } else { |
|---|
| 2277 | parts.push(s.substr(a + 1)); |
|---|
| 2278 | } |
|---|
| 2279 | a = a1; |
|---|
| 2280 | } |
|---|
| 2281 | |
|---|
| 2282 | return parts; |
|---|
| 2283 | } |
|---|
| 2284 | |
|---|
| 2285 | // Returns the text value if a node; for nodes without children this |
|---|
| 2286 | // is the nodeValue, for nodes with children this is the concatenation |
|---|
| 2287 | // of the value of all children. |
|---|
| 2288 | function xmlValue(node) { |
|---|
| 2289 | if (!node) { |
|---|
| 2290 | return ''; |
|---|
| 2291 | } |
|---|
| 2292 | |
|---|
| 2293 | var ret = ''; |
|---|
| 2294 | if (node.nodeType == DOM_TEXT_NODE || |
|---|
| 2295 | node.nodeType == DOM_CDATA_SECTION_NODE || |
|---|
| 2296 | node.nodeType == DOM_ATTRIBUTE_NODE) { |
|---|
| 2297 | ret += node.nodeValue; |
|---|
| 2298 | |
|---|
| 2299 | } else if (node.nodeType == DOM_ELEMENT_NODE || |
|---|
| 2300 | node.nodeType == DOM_DOCUMENT_NODE || |
|---|
| 2301 | node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { |
|---|
| 2302 | for (var i = 0; i < node.childNodes.length; ++i) { |
|---|
| 2303 | ret += arguments.callee(node.childNodes[i]); |
|---|
| 2304 | } |
|---|
| 2305 | } |
|---|
| 2306 | return ret; |
|---|
| 2307 | } |
|---|
| 2308 | |
|---|
| 2309 | // Returns the representation of a node as XML text. |
|---|
| 2310 | function xmlText(node) { |
|---|
| 2311 | var ret = ''; |
|---|
| 2312 | if (node.nodeType == DOM_TEXT_NODE) { |
|---|
| 2313 | ret += xmlEscapeText(node.nodeValue); |
|---|
| 2314 | |
|---|
| 2315 | } else if (node.nodeType == DOM_ELEMENT_NODE) { |
|---|
| 2316 | ret += '<' + node.nodeName; |
|---|
| 2317 | for (var i = 0; i < node.attributes.length; ++i) { |
|---|
| 2318 | var a = node.attributes[i]; |
|---|
| 2319 | if (a && a.nodeName && a.nodeValue) { |
|---|
| 2320 | ret += ' ' + a.nodeName; |
|---|
| 2321 | ret += '="' + xmlEscapeAttr(a.nodeValue) + '"'; |
|---|
| 2322 | } |
|---|
| 2323 | } |
|---|
| 2324 | |
|---|
| 2325 | if (node.childNodes.length == 0) { |
|---|
| 2326 | ret += '/>'; |
|---|
| 2327 | |
|---|
| 2328 | } else { |
|---|
| 2329 | ret += '>'; |
|---|
| 2330 | for (var i = 0; i < node.childNodes.length; ++i) { |
|---|
| 2331 | ret += arguments.callee(node.childNodes[i]); |
|---|
| 2332 | } |
|---|
| 2333 | ret += '</' + node.nodeName + '>'; |
|---|
| 2334 | } |
|---|
| 2335 | |
|---|
| 2336 | } else if (node.nodeType == DOM_DOCUMENT_NODE || |
|---|
| 2337 | node.nodeType == DOM_DOCUMENT_FRAGMENT_NODE) { |
|---|
| 2338 | for (var i = 0; i < node.childNodes.length; ++i) { |
|---|
| 2339 | ret += arguments.callee(node.childNodes[i]); |
|---|
| 2340 | } |
|---|
| 2341 | } |
|---|
| 2342 | |
|---|
| 2343 | return ret; |
|---|
| 2344 | } |
|---|
| 2345 | |
|---|
| 2346 | // Applies the given function to each element of the array. |
|---|
| 2347 | function mapExec(array, func) { |
|---|
| 2348 | for (var i = 0; i < array.length; ++i) { |
|---|
| 2349 | func(array[i]); |
|---|
| 2350 | } |
|---|
| 2351 | } |
|---|
| 2352 | |
|---|
| 2353 | // Returns an array that contains the return value of the given |
|---|
| 2354 | // function applied to every element of the input array. |
|---|
| 2355 | function mapExpr(array, func) { |
|---|
| 2356 | var ret = []; |
|---|
| 2357 | for (var i = 0; i < array.length; ++i) { |
|---|
| 2358 | ret.push(func(array[i])); |
|---|
| 2359 | } |
|---|
| 2360 | return ret; |
|---|
| 2361 | }; |
|---|
| 2362 | |
|---|
| 2363 | // Reverses the given array in place. |
|---|
| 2364 | function reverseInplace(array) { |
|---|
| 2365 | for (var i = 0; i < array.length / 2; ++i) { |
|---|
| 2366 | var h = array[i]; |
|---|
| 2367 | var ii = array.length - i - 1; |
|---|
| 2368 | array[i] = array[ii]; |
|---|
| 2369 | array[ii] = h; |
|---|
| 2370 | } |
|---|
| 2371 | } |
|---|
| 2372 | |
|---|
| 2373 | // Shallow-copies an array. |
|---|
| 2374 | function copyArray(dst, src) { |
|---|
| 2375 | for (var i = 0; i < src.length; ++i) { |
|---|
| 2376 | dst.push(src[i]); |
|---|
| 2377 | } |
|---|
| 2378 | } |
|---|
| 2379 | |
|---|
| 2380 | function assert(b) { |
|---|
| 2381 | if (!b) { |
|---|
| 2382 | throw 'assertion failed'; |
|---|
| 2383 | } |
|---|
| 2384 | } |
|---|
| 2385 | |
|---|
| 2386 | // Based on |
|---|
| 2387 | // <http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247> |
|---|
| 2388 | var DOM_ELEMENT_NODE = 1; |
|---|
| 2389 | var DOM_ATTRIBUTE_NODE = 2; |
|---|
| 2390 | var DOM_TEXT_NODE = 3; |
|---|
| 2391 | var DOM_CDATA_SECTION_NODE = 4; |
|---|
| 2392 | var DOM_ENTITY_REFERENCE_NODE = 5; |
|---|
| 2393 | var DOM_ENTITY_NODE = 6; |
|---|
| 2394 | var DOM_PROCESSING_INSTRUCTION_NODE = 7; |
|---|
| 2395 | var DOM_COMMENT_NODE = 8; |
|---|
| 2396 | var DOM_DOCUMENT_NODE = 9; |
|---|
| 2397 | var DOM_DOCUMENT_TYPE_NODE = 10; |
|---|
| 2398 | var DOM_DOCUMENT_FRAGMENT_NODE = 11; |
|---|
| 2399 | var DOM_NOTATION_NODE = 12; |
|---|
| 2400 | |
|---|
| 2401 | |
|---|
| 2402 | var xpathdebug = false; // trace xpath parsing |
|---|
| 2403 | var xsltdebug = false; // trace xslt processing |
|---|
| 2404 | |
|---|
| 2405 | |
|---|
| 2406 | // Escape XML special markup chracters: tag delimiter < > and entity |
|---|
| 2407 | // reference start delimiter &. The escaped string can be used in XML |
|---|
| 2408 | // text portions (i.e. between tags). |
|---|
| 2409 | function xmlEscapeText(s) { |
|---|
| 2410 | return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); |
|---|
| 2411 | } |
|---|
| 2412 | |
|---|
| 2413 | // Escape XML special markup characters: tag delimiter < > entity |
|---|
| 2414 | // reference start delimiter & and quotes ". The escaped string can be |
|---|
| 2415 | // used in double quoted XML attribute value portions (i.e. in |
|---|
| 2416 | // attributes within start tags). |
|---|
| 2417 | function xmlEscapeAttr(s) { |
|---|
| 2418 | return xmlEscapeText(s).replace(/\"/g, '"'); |
|---|
| 2419 | } |
|---|
| 2420 | |
|---|
| 2421 | // Escape markup in XML text, but don't touch entity references. The |
|---|
| 2422 | // escaped string can be used as XML text (i.e. between tags). |
|---|
| 2423 | function xmlEscapeTags(s) { |
|---|
| 2424 | return s.replace(/</g, '<').replace(/>/g, '>'); |
|---|
| 2425 | } |
|---|
| 2426 | |
|---|
| 2427 | // An implementation of the debug log. |
|---|
| 2428 | |
|---|
| 2429 | var logging__ = true; |
|---|
| 2430 | |
|---|
| 2431 | function Log() {}; |
|---|
| 2432 | |
|---|
| 2433 | Log.lines = []; |
|---|
| 2434 | |
|---|
| 2435 | Log.write = function(s) { |
|---|
| 2436 | if (logging__) { |
|---|
| 2437 | this.lines.push(xmlEscapeText(s)); |
|---|
| 2438 | this.show(); |
|---|
| 2439 | } |
|---|
| 2440 | }; |
|---|
| 2441 | |
|---|
| 2442 | // Writes the given XML with every tag on a new line. |
|---|
| 2443 | Log.writeXML = function(xml) { |
|---|
| 2444 | if (logging__) { |
|---|
| 2445 | var s0 = xml.replace(/</g, '\n<'); |
|---|
| 2446 | var s1 = xmlEscapeText(s0); |
|---|
| 2447 | var s2 = s1.replace(/\s*\n(\s|\n)*/g, '<br/>'); |
|---|
| 2448 | this.lines.push(s2); |
|---|
| 2449 | this.show(); |
|---|
| 2450 | } |
|---|
| 2451 | } |
|---|
| 2452 | |
|---|
| 2453 | // Writes without any escaping |
|---|
| 2454 | Log.writeRaw = function(s) { |
|---|
| 2455 | if (logging__) { |
|---|
| 2456 | this.lines.push(s); |
|---|
| 2457 | this.show(); |
|---|
| 2458 | } |
|---|
| 2459 | } |
|---|
| 2460 | |
|---|
| 2461 | Log.clear = function() { |
|---|
| 2462 | if (logging__) { |
|---|
| 2463 | var l = this.div(); |
|---|
| 2464 | l.innerHTML = ''; |
|---|
| 2465 | this.lines = []; |
|---|
| 2466 | } |
|---|
| 2467 | } |
|---|
| 2468 | |
|---|
| 2469 | Log.show = function() { |
|---|
| 2470 | var l = this.div(); |
|---|
| 2471 | l.innerHTML += this.lines.join('<br/>') + '<br/>'; |
|---|
| 2472 | this.lines = []; |
|---|
| 2473 | l.scrollTop = l.scrollHeight; |
|---|
| 2474 | } |
|---|
| 2475 | |
|---|
| 2476 | Log.div = function() { |
|---|
| 2477 | var l = document.getElementById('log'); |
|---|
| 2478 | if (!l) { |
|---|
| 2479 | l = document.createElement('div'); |
|---|
| 2480 | l.id = 'log'; |
|---|
| 2481 | l.style.position = 'absolute'; |
|---|
| 2482 | l.style.right = '5px'; |
|---|
| 2483 | l.style.top = '5px'; |
|---|
| 2484 | l.style.width = '250px'; |
|---|
| 2485 | l.style.height = '150px'; |
|---|
| 2486 | l.style.overflow = 'auto'; |
|---|
| 2487 | l.style.backgroundColor = '#f0f0f0'; |
|---|
| 2488 | l.style.border = '1px solid gray'; |
|---|
| 2489 | l.style.fontSize = '10px'; |
|---|
| 2490 | l.style.padding = '5px'; |
|---|
| 2491 | document.body.appendChild(l); |
|---|
| 2492 | } |
|---|
| 2493 | return l; |
|---|
| 2494 | } |
|---|
| 2495 | |
|---|
| 2496 | |
|---|
| 2497 | function Timer() {} |
|---|
| 2498 | Timer.start = function() {} |
|---|
| 2499 | Timer.end = function() {} |
|---|