--- /dev/null
+// Copyright 2005 Google Inc.\r
+// All Rights Reserved\r
+//\r
+// An XPath parser and evaluator written in JavaScript. The\r
+// implementation is complete except for functions handling\r
+// namespaces.\r
+//\r
+// Reference: [XPATH] XPath Specification\r
+// <http://www.w3.org/TR/1999/REC-xpath-19991116>.\r
+//\r
+//\r
+// The API of the parser has several parts:\r
+//\r
+// 1. The parser function xpathParse() that takes a string and returns\r
+// an expession object.\r
+//\r
+// 2. The expression object that has an evaluate() method to evaluate the\r
+// XPath expression it represents. (It is actually a hierarchy of\r
+// objects that resembles the parse tree, but an application will call\r
+// evaluate() only on the top node of this hierarchy.)\r
+//\r
+// 3. The context object that is passed as an argument to the evaluate()\r
+// method, which represents the DOM context in which the expression is\r
+// evaluated.\r
+//\r
+// 4. The value object that is returned from evaluate() and represents\r
+// values of the different types that are defined by XPath (number,\r
+// string, boolean, and node-set), and allows to convert between them.\r
+//\r
+// These parts are near the top of the file, the functions and data\r
+// that are used internally follow after them.\r
+//\r
+//\r
+// TODO(mesch): add jsdoc comments. Use more coherent naming.\r
+//\r
+//\r
+// Author: Steffen Meschkat <mesch@google.com>\r
+\r
+\r
+// The entry point for the parser.\r
+//\r
+// @param expr a string that contains an XPath expression.\r
+// @return an expression object that can be evaluated with an\r
+// expression context.\r
+\r
+function xpathParse(expr) {\r
+ if (xpathdebug) {\r
+ Log.write('XPath parse ' + expr);\r
+ }\r
+ xpathParseInit();\r
+\r
+ var cached = xpathCacheLookup(expr);\r
+ if (cached) {\r
+ if (xpathdebug) {\r
+ Log.write(' ... cached');\r
+ }\r
+ return cached;\r
+ }\r
+\r
+ // Optimize for a few common cases: simple attribute node tests\r
+ // (@id), simple element node tests (page), variable references\r
+ // ($address), numbers (4), multi-step path expressions where each\r
+ // step is a plain element node test\r
+ // (page/overlay/locations/location).\r
+ \r
+ if (expr.match(/^(\$|@)?\w+$/i)) {\r
+ var ret = makeSimpleExpr(expr);\r
+ xpathParseCache[expr] = ret;\r
+ if (xpathdebug) {\r
+ Log.write(' ... simple');\r
+ }\r
+ return ret;\r
+ }\r
+\r
+ if (expr.match(/^\w+(\/\w+)*$/i)) {\r
+ var ret = makeSimpleExpr2(expr);\r
+ xpathParseCache[expr] = ret;\r
+ if (xpathdebug) {\r
+ Log.write(' ... simple 2');\r
+ }\r
+ return ret;\r
+ }\r
+\r
+ var cachekey = expr; // expr is modified during parse\r
+ if (xpathdebug) {\r
+ Timer.start('XPath parse', cachekey);\r
+ }\r
+\r
+ var stack = [];\r
+ var ahead = null;\r
+ var previous = null;\r
+ var done = false;\r
+\r
+ var parse_count = 0;\r
+ var lexer_count = 0;\r
+ var reduce_count = 0;\r
+ \r
+ while (!done) {\r
+ parse_count++;\r
+ expr = expr.replace(/^\s*/, '');\r
+ previous = ahead;\r
+ ahead = null;\r
+\r
+ var rule = null;\r
+ var match = '';\r
+ for (var i = 0; i < xpathTokenRules.length; ++i) {\r
+ var result = xpathTokenRules[i].re.exec(expr);\r
+ lexer_count++;\r
+ if (result && result.length > 0 && result[0].length > match.length) {\r
+ rule = xpathTokenRules[i];\r
+ match = result[0];\r
+ break;\r
+ }\r
+ }\r
+\r
+ // Special case: allow operator keywords to be element and\r
+ // variable names.\r
+\r
+ // NOTE(mesch): The parser resolves conflicts by looking ahead,\r
+ // and this is the only case where we look back to\r
+ // disambiguate. So this is indeed something different, and\r
+ // looking back is usually done in the lexer (via states in the\r
+ // general case, called "start conditions" in flex(1)). Also,the\r
+ // conflict resolution in the parser is not as robust as it could\r
+ // be, so I'd like to keep as much off the parser as possible (all\r
+ // these precedence values should be computed from the grammar\r
+ // rules and possibly associativity declarations, as in bison(1),\r
+ // and not explicitly set.\r
+\r
+ if (rule &&\r
+ (rule == TOK_DIV || \r
+ rule == TOK_MOD ||\r
+ rule == TOK_AND || \r
+ rule == TOK_OR) &&\r
+ (!previous || \r
+ previous.tag == TOK_AT || \r
+ previous.tag == TOK_DSLASH || \r
+ previous.tag == TOK_SLASH ||\r
+ previous.tag == TOK_AXIS || \r
+ previous.tag == TOK_DOLLAR)) {\r
+ rule = TOK_QNAME;\r
+ }\r
+\r
+ if (rule) {\r
+ expr = expr.substr(match.length);\r
+ if (xpathdebug) {\r
+ Log.write('token: ' + match + ' -- ' + rule.label);\r
+ }\r
+ ahead = {\r
+ tag: rule,\r
+ match: match,\r
+ prec: rule.prec ? rule.prec : 0, // || 0 is removed by the compiler\r
+ expr: makeTokenExpr(match)\r
+ };\r
+\r
+ } else {\r
+ if (xpathdebug) {\r
+ Log.write('DONE');\r
+ }\r
+ done = true;\r
+ }\r
+\r
+ while (xpathReduce(stack, ahead)) {\r
+ reduce_count++;\r
+ if (xpathdebug) {\r
+ Log.write('stack: ' + stackToString(stack));\r
+ }\r
+ }\r
+ }\r
+\r
+ if (xpathdebug) {\r
+ Log.write(stackToString(stack));\r
+ }\r
+\r
+ if (stack.length != 1) {\r
+ throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);\r
+ }\r
+\r
+ var result = stack[0].expr;\r
+ xpathParseCache[cachekey] = result;\r
+\r
+ if (xpathdebug) {\r
+ Timer.end('XPath parse', cachekey);\r
+ }\r
+\r
+ if (xpathdebug) {\r
+ Log.write('XPath parse: ' + parse_count + ' / ' + \r
+ lexer_count + ' / ' + reduce_count);\r
+ }\r
+\r
+ return result;\r
+}\r
+\r
+var xpathParseCache = {};\r
+\r
+function xpathCacheLookup(expr) {\r
+ return xpathParseCache[expr];\r
+}\r
+\r
+function xpathReduce(stack, ahead) {\r
+ var cand = null;\r
+\r
+ if (stack.length > 0) {\r
+ var top = stack[stack.length-1];\r
+ var ruleset = xpathRules[top.tag.key];\r
+\r
+ if (ruleset) {\r
+ for (var i = 0; i < ruleset.length; ++i) {\r
+ var rule = ruleset[i];\r
+ var match = xpathMatchStack(stack, rule[1]);\r
+ if (match.length) {\r
+ cand = {\r
+ tag: rule[0],\r
+ rule: rule,\r
+ match: match\r
+ };\r
+ cand.prec = xpathGrammarPrecedence(cand);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ var ret;\r
+ if (cand && (!ahead || cand.prec > ahead.prec || \r
+ (ahead.tag.left && cand.prec >= ahead.prec))) {\r
+ for (var i = 0; i < cand.match.matchlength; ++i) {\r
+ stack.pop();\r
+ }\r
+\r
+ if (xpathdebug) {\r
+ Log.write('reduce ' + cand.tag.label + ' ' + cand.prec +\r
+ ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec + \r
+ (ahead.tag.left ? ' left' : '')\r
+ : ' none '));\r
+ }\r
+\r
+ var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });\r
+ cand.expr = cand.rule[3].apply(null, matchexpr);\r
+\r
+ stack.push(cand);\r
+ ret = true;\r
+\r
+ } else {\r
+ if (ahead) {\r
+ if (xpathdebug) {\r
+ Log.write('shift ' + ahead.tag.label + ' ' + ahead.prec + \r
+ (ahead.tag.left ? ' left' : '') +\r
+ ' over ' + (cand ? cand.tag.label + ' ' + \r
+ cand.prec : ' none'));\r
+ }\r
+ stack.push(ahead);\r
+ }\r
+ ret = false;\r
+ }\r
+ return ret;\r
+}\r
+\r
+function xpathMatchStack(stack, pattern) {\r
+\r
+ // NOTE(mesch): The stack matches for variable cardinality are\r
+ // greedy but don't do backtracking. This would be an issue only\r
+ // with rules of the form A* A, i.e. with an element with variable\r
+ // cardinality followed by the same element. Since that doesn't\r
+ // occur in the grammar at hand, all matches on the stack are\r
+ // unambiguous.\r
+\r
+ var S = stack.length;\r
+ var P = pattern.length;\r
+ var p, s;\r
+ var match = [];\r
+ match.matchlength = 0;\r
+ var ds = 0;\r
+ for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {\r
+ ds = 0;\r
+ var qmatch = [];\r
+ if (pattern[p] == Q_MM) {\r
+ p -= 1;\r
+ match.push(qmatch);\r
+ while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {\r
+ qmatch.push(stack[s - ds]);\r
+ ds += 1;\r
+ match.matchlength += 1;\r
+ }\r
+\r
+ } else if (pattern[p] == Q_01) {\r
+ p -= 1;\r
+ match.push(qmatch);\r
+ while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {\r
+ qmatch.push(stack[s - ds]);\r
+ ds += 1;\r
+ match.matchlength += 1;\r
+ }\r
+\r
+ } else if (pattern[p] == Q_1M) {\r
+ p -= 1;\r
+ match.push(qmatch);\r
+ if (stack[s].tag == pattern[p]) {\r
+ while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {\r
+ qmatch.push(stack[s - ds]);\r
+ ds += 1;\r
+ match.matchlength += 1;\r
+ }\r
+ } else {\r
+ return [];\r
+ }\r
+\r
+ } else if (stack[s].tag == pattern[p]) {\r
+ match.push(stack[s]);\r
+ ds += 1;\r
+ match.matchlength += 1;\r
+\r
+ } else {\r
+ return [];\r
+ }\r
+\r
+ reverseInplace(qmatch);\r
+ qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });\r
+ }\r
+\r
+ reverseInplace(match);\r
+\r
+ if (p == -1) {\r
+ return match;\r
+\r
+ } else {\r
+ return [];\r
+ }\r
+}\r
+\r
+function xpathTokenPrecedence(tag) {\r
+ return tag.prec || 2;\r
+}\r
+\r
+function xpathGrammarPrecedence(frame) {\r
+ var ret = 0;\r
+\r
+ if (frame.rule) { /* normal reduce */\r
+ if (frame.rule.length >= 3 && frame.rule[2] >= 0) {\r
+ ret = frame.rule[2];\r
+\r
+ } else {\r
+ for (var i = 0; i < frame.rule[1].length; ++i) {\r
+ var p = xpathTokenPrecedence(frame.rule[1][i]);\r
+ ret = Math.max(ret, p);\r
+ }\r
+ }\r
+ } else if (frame.tag) { /* TOKEN match */\r
+ ret = xpathTokenPrecedence(frame.tag);\r
+\r
+ } else if (frame.length) { /* Q_ match */\r
+ for (var j = 0; j < frame.length; ++j) {\r
+ var p = xpathGrammarPrecedence(frame[j]);\r
+ ret = Math.max(ret, p);\r
+ }\r
+ }\r
+\r
+ return ret;\r
+}\r
+\r
+function stackToString(stack) {\r
+ var ret = '';\r
+ for (var i = 0; i < stack.length; ++i) {\r
+ if (ret) {\r
+ ret += '\n';\r
+ }\r
+ ret += stack[i].tag.label;\r
+ }\r
+ return ret;\r
+}\r
+\r
+\r
+// XPath expression evaluation context. An XPath context consists of a\r
+// DOM node, a list of DOM nodes that contains this node, a number\r
+// that represents the position of the single node in the list, and a\r
+// current set of variable bindings. (See XPath spec.)\r
+//\r
+// The interface of the expression context:\r
+//\r
+// Constructor -- gets the node, its position, the node set it\r
+// belongs to, and a parent context as arguments. The parent context\r
+// is used to implement scoping rules for variables: if a variable\r
+// is not found in the current context, it is looked for in the\r
+// parent context, recursively. Except for node, all arguments have\r
+// default values: default position is 0, default node set is the\r
+// set that contains only the node, and the default parent is null.\r
+//\r
+// Notice that position starts at 0 at the outside interface;\r
+// inside XPath expressions this shows up as position()=1.\r
+//\r
+// clone() -- creates a new context with the current context as\r
+// parent. If passed as argument to clone(), the new context has a\r
+// different node, position, or node set. What is not passed is\r
+// inherited from the cloned context.\r
+//\r
+// setVariable(name, expr) -- binds given XPath expression to the\r
+// name.\r
+//\r
+// getVariable(name) -- what the name says.\r
+//\r
+// setNode(node, position) -- sets the context to the new node and\r
+// its corresponding position. Needed to implement scoping rules for\r
+// variables in XPath. (A variable is visible to all subsequent\r
+// siblings, not only to its children.)\r
+\r
+function ExprContext(node, position, nodelist, parent) {\r
+ this.node = node;\r
+ this.position = position || 0;\r
+ this.nodelist = nodelist || [ node ];\r
+ this.variables = {};\r
+ this.parent = parent || null;\r
+ this.root = parent ? parent.root : node.ownerDocument;\r
+}\r
+\r
+ExprContext.prototype.clone = function(node, position, nodelist) {\r
+ return new\r
+ ExprContext(node || this.node,\r
+ typeof position != 'undefined' ? position : this.position,\r
+ nodelist || this.nodelist, this);\r
+};\r
+\r
+ExprContext.prototype.setVariable = function(name, value) {\r
+ this.variables[name] = value;\r
+};\r
+\r
+ExprContext.prototype.getVariable = function(name) {\r
+ if (typeof this.variables[name] != 'undefined') {\r
+ return this.variables[name];\r
+\r
+ } else if (this.parent) {\r
+ return this.parent.getVariable(name);\r
+\r
+ } else {\r
+ return null;\r
+ }\r
+}\r
+\r
+ExprContext.prototype.setNode = function(node, position) {\r
+ this.node = node;\r
+ this.position = position;\r
+}\r
+\r
+\r
+// XPath expression values. They are what XPath expressions evaluate\r
+// to. Strangely, the different value types are not specified in the\r
+// XPath syntax, but only in the semantics, so they don't show up as\r
+// nonterminals in the grammar. Yet, some expressions are required to\r
+// evaluate to particular types, and not every type can be coerced\r
+// into every other type. Although the types of XPath values are\r
+// similar to the types present in JavaScript, the type coercion rules\r
+// are a bit peculiar, so we explicitly model XPath types instead of\r
+// mapping them onto JavaScript types. (See XPath spec.)\r
+//\r
+// The four types are:\r
+//\r
+// StringValue\r
+//\r
+// NumberValue\r
+//\r
+// BooleanValue\r
+//\r
+// NodeSetValue\r
+//\r
+// The common interface of the value classes consists of methods that\r
+// implement the XPath type coercion rules:\r
+//\r
+// stringValue() -- returns the value as a JavaScript String,\r
+//\r
+// numberValue() -- returns the value as a JavaScript Number,\r
+//\r
+// booleanValue() -- returns the value as a JavaScript Boolean,\r
+//\r
+// nodeSetValue() -- returns the value as a JavaScript Array of DOM\r
+// Node objects.\r
+//\r
+\r
+function StringValue(value) {\r
+ this.value = value;\r
+ this.type = 'string';\r
+}\r
+\r
+StringValue.prototype.stringValue = function() {\r
+ return this.value;\r
+}\r
+\r
+StringValue.prototype.booleanValue = function() {\r
+ return this.value.length > 0;\r
+}\r
+\r
+StringValue.prototype.numberValue = function() {\r
+ return this.value - 0;\r
+}\r
+\r
+StringValue.prototype.nodeSetValue = function() {\r
+ throw this + ' ' + Error().stack;\r
+}\r
+\r
+function BooleanValue(value) {\r
+ this.value = value;\r
+ this.type = 'boolean';\r
+}\r
+\r
+BooleanValue.prototype.stringValue = function() {\r
+ return '' + this.value;\r
+}\r
+\r
+BooleanValue.prototype.booleanValue = function() {\r
+ return this.value;\r
+}\r
+\r
+BooleanValue.prototype.numberValue = function() {\r
+ return this.value ? 1 : 0;\r
+}\r
+\r
+BooleanValue.prototype.nodeSetValue = function() {\r
+ throw this + ' ' + Error().stack;\r
+}\r
+\r
+function NumberValue(value) {\r
+ this.value = value;\r
+ this.type = 'number';\r
+}\r
+\r
+NumberValue.prototype.stringValue = function() {\r
+ return '' + this.value;\r
+}\r
+\r
+NumberValue.prototype.booleanValue = function() {\r
+ return !!this.value;\r
+}\r
+\r
+NumberValue.prototype.numberValue = function() {\r
+ return this.value - 0;\r
+}\r
+\r
+NumberValue.prototype.nodeSetValue = function() {\r
+ throw this + ' ' + Error().stack;\r
+}\r
+\r
+function NodeSetValue(value) {\r
+ this.value = value;\r
+ this.type = 'node-set';\r
+}\r
+\r
+NodeSetValue.prototype.stringValue = function() {\r
+ if (this.value.length == 0) {\r
+ return '';\r
+ } else {\r
+ return xmlValue(this.value[0]);\r
+ }\r
+}\r
+\r
+NodeSetValue.prototype.booleanValue = function() {\r
+ return this.value.length > 0;\r
+}\r
+\r
+NodeSetValue.prototype.numberValue = function() {\r
+ return this.stringValue() - 0;\r
+}\r
+\r
+NodeSetValue.prototype.nodeSetValue = function() {\r
+ return this.value;\r
+};\r
+\r
+// XPath expressions. They are used as nodes in the parse tree and\r
+// possess an evaluate() method to compute an XPath value given an XPath\r
+// context. Expressions are returned from the parser. Teh set of\r
+// expression classes closely mirrors the set of non terminal symbols\r
+// in the grammar. Every non trivial nonterminal symbol has a\r
+// corresponding expression class.\r
+//\r
+// The common expression interface consists of the following methods:\r
+//\r
+// evaluate(context) -- evaluates the expression, returns a value.\r
+//\r
+// toString() -- returns the XPath text representation of the\r
+// expression (defined in xsltdebug.js).\r
+//\r
+// parseTree(indent) -- returns a parse tree representation of the\r
+// expression (defined in xsltdebug.js).\r
+\r
+function TokenExpr(m) {\r
+ this.value = m;\r
+}\r
+\r
+TokenExpr.prototype.evaluate = function() {\r
+ return new StringValue(this.value);\r
+};\r
+\r
+function LocationExpr() {\r
+ this.absolute = false;\r
+ this.steps = [];\r
+}\r
+\r
+LocationExpr.prototype.appendStep = function(s) {\r
+ this.steps.push(s);\r
+}\r
+\r
+LocationExpr.prototype.prependStep = function(s) {\r
+ var steps0 = this.steps;\r
+ this.steps = [ s ];\r
+ for (var i = 0; i < steps0.length; ++i) {\r
+ this.steps.push(steps0[i]);\r
+ }\r
+};\r
+\r
+LocationExpr.prototype.evaluate = function(ctx) {\r
+ var start;\r
+ if (this.absolute) {\r
+ start = ctx.root;\r
+\r
+ } else {\r
+ start = ctx.node;\r
+ }\r
+\r
+ var nodes = [];\r
+ xPathStep(nodes, this.steps, 0, start, ctx);\r
+ return new NodeSetValue(nodes);\r
+};\r
+\r
+function xPathStep(nodes, steps, step, input, ctx) {\r
+ var s = steps[step];\r
+ var ctx2 = ctx.clone(input);\r
+ var nodelist = s.evaluate(ctx2).nodeSetValue();\r
+\r
+ for (var i = 0; i < nodelist.length; ++i) {\r
+ if (step == steps.length - 1) {\r
+ nodes.push(nodelist[i]);\r
+ } else {\r
+ xPathStep(nodes, steps, step + 1, nodelist[i], ctx);\r
+ }\r
+ }\r
+}\r
+\r
+function StepExpr(axis, nodetest, predicate) {\r
+ this.axis = axis;\r
+ this.nodetest = nodetest;\r
+ this.predicate = predicate || [];\r
+}\r
+\r
+StepExpr.prototype.appendPredicate = function(p) {\r
+ this.predicate.push(p);\r
+}\r
+\r
+StepExpr.prototype.evaluate = function(ctx) {\r
+ var input = ctx.node;\r
+ var nodelist = [];\r
+\r
+ // NOTE(mesch): When this was a switch() statement, it didn't work\r
+ // in Safari/2.0. Not sure why though; it resulted in the JavaScript\r
+ // console output "undefined" (without any line number or so).\r
+\r
+ if (this.axis == xpathAxis.ANCESTOR_OR_SELF) {\r
+ nodelist.push(input);\r
+ for (var n = input.parentNode; n; n = input.parentNode) {\r
+ nodelist.push(n);\r
+ }\r
+\r
+ } else if (this.axis == xpathAxis.ANCESTOR) {\r
+ for (var n = input.parentNode; n; n = input.parentNode) {\r
+ nodelist.push(n);\r
+ }\r
+\r
+ } else if (this.axis == xpathAxis.ATTRIBUTE) {\r
+ copyArray(nodelist, input.attributes);\r
+\r
+ } else if (this.axis == xpathAxis.CHILD) {\r
+ copyArray(nodelist, input.childNodes);\r
+\r
+ } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {\r
+ nodelist.push(input);\r
+ xpathCollectDescendants(nodelist, input);\r
+\r
+ } else if (this.axis == xpathAxis.DESCENDANT) {\r
+ xpathCollectDescendants(nodelist, input);\r
+\r
+ } else if (this.axis == xpathAxis.FOLLOWING) {\r
+ for (var n = input.parentNode; n; n = n.parentNode) {\r
+ for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {\r
+ nodelist.push(nn);\r
+ xpathCollectDescendants(nodelist, nn);\r
+ }\r
+ }\r
+\r
+ } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {\r
+ for (var n = input.nextSibling; n; n = input.nextSibling) {\r
+ nodelist.push(n);\r
+ }\r
+\r
+ } else if (this.axis == xpathAxis.NAMESPACE) {\r
+ alert('not implemented: axis namespace');\r
+\r
+ } else if (this.axis == xpathAxis.PARENT) {\r
+ if (input.parentNode) {\r
+ nodelist.push(input.parentNode);\r
+ }\r
+\r
+ } else if (this.axis == xpathAxis.PRECEDING) {\r
+ for (var n = input.parentNode; n; n = n.parentNode) {\r
+ for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {\r
+ nodelist.push(nn);\r
+ xpathCollectDescendantsReverse(nodelist, nn);\r
+ }\r
+ }\r
+\r
+ } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {\r
+ for (var n = input.previousSibling; n; n = input.previousSibling) {\r
+ nodelist.push(n);\r
+ }\r
+\r
+ } else if (this.axis == xpathAxis.SELF) {\r
+ nodelist.push(input);\r
+\r
+ } else {\r
+ throw 'ERROR -- NO SUCH AXIS: ' + this.axis;\r
+ }\r
+\r
+ // process node test\r
+ var nodelist0 = nodelist;\r
+ nodelist = [];\r
+ for (var i = 0; i < nodelist0.length; ++i) {\r
+ var n = nodelist0[i];\r
+ if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {\r
+ nodelist.push(n);\r
+ }\r
+ }\r
+\r
+ // process predicates\r
+ for (var i = 0; i < this.predicate.length; ++i) {\r
+ var nodelist0 = nodelist;\r
+ nodelist = [];\r
+ for (var ii = 0; ii < nodelist0.length; ++ii) {\r
+ var n = nodelist0[ii];\r
+ if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {\r
+ nodelist.push(n);\r
+ }\r
+ }\r
+ }\r
+\r
+ return new NodeSetValue(nodelist);\r
+};\r
+\r
+function NodeTestAny() {\r
+ this.value = new BooleanValue(true);\r
+}\r
+\r
+NodeTestAny.prototype.evaluate = function(ctx) {\r
+ return this.value;\r
+};\r
+\r
+function NodeTestElement() {}\r
+\r
+NodeTestElement.prototype.evaluate = function(ctx) {\r
+ return new BooleanValue(ctx.node.nodeType == DOM_ELEMENT_NODE);\r
+}\r
+\r
+function NodeTestText() {}\r
+\r
+NodeTestText.prototype.evaluate = function(ctx) {\r
+ return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);\r
+}\r
+\r
+function NodeTestComment() {}\r
+\r
+NodeTestComment.prototype.evaluate = function(ctx) {\r
+ return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);\r
+}\r
+\r
+function NodeTestPI(target) {\r
+ this.target = target;\r
+}\r
+\r
+NodeTestPI.prototype.evaluate = function(ctx) {\r
+ return new\r
+ BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &&\r
+ (!this.target || ctx.node.nodeName == this.target));\r
+}\r
+\r
+function NodeTestNC(nsprefix) {\r
+ this.regex = new RegExp("^" + nsprefix + ":");\r
+ this.nsprefix = nsprefix;\r
+}\r
+\r
+NodeTestNC.prototype.evaluate = function(ctx) {\r
+ var n = ctx.node;\r
+ return new BooleanValue(this.regex.match(n.nodeName));\r
+}\r
+\r
+function NodeTestName(name) {\r
+ this.name = name;\r
+}\r
+\r
+NodeTestName.prototype.evaluate = function(ctx) {\r
+ var n = ctx.node;\r
+ // NOTE (Patrick Lightbody): this change allows node selection to be case-insensitive\r
+ return new BooleanValue(n.nodeName.toUpperCase() == this.name.toUpperCase());\r
+}\r
+\r
+function PredicateExpr(expr) {\r
+ this.expr = expr;\r
+}\r
+\r
+PredicateExpr.prototype.evaluate = function(ctx) {\r
+ var v = this.expr.evaluate(ctx);\r
+ if (v.type == 'number') {\r
+ // NOTE(mesch): Internally, position is represented starting with\r
+ // 0, however in XPath position starts with 1. See functions\r
+ // position() and last().\r
+ return new BooleanValue(ctx.position == v.numberValue() - 1);\r
+ } else {\r
+ return new BooleanValue(v.booleanValue());\r
+ }\r
+};\r
+\r
+function FunctionCallExpr(name) {\r
+ this.name = name;\r
+ this.args = [];\r
+}\r
+\r
+FunctionCallExpr.prototype.appendArg = function(arg) {\r
+ this.args.push(arg);\r
+};\r
+\r
+FunctionCallExpr.prototype.evaluate = function(ctx) {\r
+ var fn = '' + this.name.value;\r
+ var f = this.xpathfunctions[fn];\r
+ if (f) {\r
+ return f.call(this, ctx);\r
+ } else {\r
+ Log.write('XPath NO SUCH FUNCTION ' + fn);\r
+ return new BooleanValue(false);\r
+ }\r
+};\r
+\r
+FunctionCallExpr.prototype.xpathfunctions = {\r
+ 'last': function(ctx) {\r
+ assert(this.args.length == 0);\r
+ // NOTE(mesch): XPath position starts at 1.\r
+ return new NumberValue(ctx.nodelist.length);\r
+ },\r
+\r
+ 'position': function(ctx) {\r
+ assert(this.args.length == 0);\r
+ // NOTE(mesch): XPath position starts at 1.\r
+ return new NumberValue(ctx.position + 1);\r
+ },\r
+\r
+ 'count': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var v = this.args[0].evaluate(ctx);\r
+ return new NumberValue(v.nodeSetValue().length);\r
+ },\r
+\r
+ 'id': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var e = this.args.evaluate(ctx);\r
+ var ret = [];\r
+ var ids;\r
+ if (e.type == 'node-set') {\r
+ ids = [];\r
+ for (var i = 0; i < e.length; ++i) {\r
+ var v = xmlValue(e[i]).split(/\s+/);\r
+ for (var ii = 0; ii < v.length; ++ii) {\r
+ ids.push(v[ii]);\r
+ }\r
+ }\r
+ } else {\r
+ ids = e.split(/\s+/);\r
+ }\r
+ var d = ctx.node.ownerDocument;\r
+ for (var i = 0; i < ids.length; ++i) {\r
+ var n = d.getElementById(ids[i]);\r
+ if (n) {\r
+ ret.push(n);\r
+ }\r
+ }\r
+ return new NodeSetValue(ret);\r
+ },\r
+\r
+ 'local-name': function(ctx) {\r
+ alert('not implmented yet: XPath function local-name()');\r
+ },\r
+\r
+ 'namespace-uri': function(ctx) {\r
+ alert('not implmented yet: XPath function namespace-uri()');\r
+ },\r
+\r
+ 'name': function(ctx) {\r
+ assert(this.args.length == 1 || this.args.length == 0);\r
+ var n;\r
+ if (this.args.length == 0) {\r
+ n = [ ctx.node ];\r
+ } else {\r
+ n = this.args[0].evaluate(ctx).nodeSetValue();\r
+ }\r
+\r
+ if (n.length == 0) {\r
+ return new StringValue('');\r
+ } else {\r
+ return new StringValue(n[0].nodeName);\r
+ }\r
+ },\r
+\r
+ 'string': function(ctx) {\r
+ assert(this.args.length == 1 || this.args.length == 0);\r
+ if (this.args.length == 0) {\r
+ return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());\r
+ } else {\r
+ return new StringValue(this.args[0].evaluate(ctx).stringValue());\r
+ }\r
+ },\r
+\r
+ 'concat': function(ctx) {\r
+ var ret = '';\r
+ for (var i = 0; i < this.args.length; ++i) {\r
+ ret += this.args[i].evaluate(ctx).stringValue();\r
+ }\r
+ return new StringValue(ret);\r
+ },\r
+\r
+ 'starts-with': function(ctx) {\r
+ assert(this.args.length == 2);\r
+ var s0 = this.args[0].evaluate(ctx).stringValue();\r
+ var s1 = this.args[1].evaluate(ctx).stringValue();\r
+ return new BooleanValue(s0.indexOf(s1) == 0);\r
+ },\r
+\r
+ 'contains': function(ctx) {\r
+ assert(this.args.length == 2);\r
+ var s0 = this.args[0].evaluate(ctx).stringValue();\r
+ var s1 = this.args[1].evaluate(ctx).stringValue();\r
+ return new BooleanValue(s0.indexOf(s1) != -1);\r
+ },\r
+\r
+ 'substring-before': function(ctx) {\r
+ assert(this.args.length == 2);\r
+ var s0 = this.args[0].evaluate(ctx).stringValue();\r
+ var s1 = this.args[1].evaluate(ctx).stringValue();\r
+ var i = s0.indexOf(s1);\r
+ var ret;\r
+ if (i == -1) {\r
+ ret = '';\r
+ } else {\r
+ ret = s0.substr(0,i);\r
+ }\r
+ return new StringValue(ret);\r
+ },\r
+\r
+ 'substring-after': function(ctx) {\r
+ assert(this.args.length == 2);\r
+ var s0 = this.args[0].evaluate(ctx).stringValue();\r
+ var s1 = this.args[1].evaluate(ctx).stringValue();\r
+ var i = s0.indexOf(s1);\r
+ var ret;\r
+ if (i == -1) {\r
+ ret = '';\r
+ } else {\r
+ ret = s0.substr(i + s1.length);\r
+ }\r
+ return new StringValue(ret);\r
+ },\r
+\r
+ 'substring': function(ctx) {\r
+ // NOTE: XPath defines the position of the first character in a\r
+ // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).\r
+ assert(this.args.length == 2 || this.args.length == 3);\r
+ var s0 = this.args[0].evaluate(ctx).stringValue();\r
+ var s1 = this.args[1].evaluate(ctx).numberValue();\r
+ var ret;\r
+ if (this.args.length == 2) {\r
+ var i1 = Math.max(0, Math.round(s1) - 1);\r
+ ret = s0.substr(i1);\r
+\r
+ } else {\r
+ var s2 = this.args[2].evaluate(ctx).numberValue();\r
+ var i0 = Math.round(s1) - 1;\r
+ var i1 = Math.max(0, i0);\r
+ var i2 = Math.round(s2) - Math.max(0, -i0);\r
+ ret = s0.substr(i1, i2);\r
+ }\r
+ return new StringValue(ret);\r
+ },\r
+\r
+ 'string-length': function(ctx) {\r
+ var s;\r
+ if (this.args.length > 0) {\r
+ s = this.args[0].evaluate(ctx).stringValue();\r
+ } else {\r
+ s = new NodeSetValue([ ctx.node ]).stringValue();\r
+ }\r
+ return new NumberValue(s.length);\r
+ },\r
+\r
+ 'normalize-space': function(ctx) {\r
+ var s;\r
+ if (this.args.length > 0) {\r
+ s = this.args[0].evaluate(ctx).stringValue();\r
+ } else {\r
+ s = new NodeSetValue([ ctx.node ]).stringValue();\r
+ }\r
+ s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');\r
+ return new StringValue(s);\r
+ },\r
+\r
+ 'translate': function(ctx) {\r
+ assert(this.args.length == 3);\r
+ var s0 = this.args[0].evaluate(ctx).stringValue();\r
+ var s1 = this.args[1].evaluate(ctx).stringValue();\r
+ var s2 = this.args[2].evaluate(ctx).stringValue();\r
+\r
+ for (var i = 0; i < s1.length; ++i) {\r
+ s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));\r
+ }\r
+ return new StringValue(s0);\r
+ },\r
+\r
+ 'boolean': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());\r
+ },\r
+\r
+ 'not': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var ret = !this.args[0].evaluate(ctx).booleanValue();\r
+ return new BooleanValue(ret);\r
+ },\r
+\r
+ 'true': function(ctx) {\r
+ assert(this.args.length == 0);\r
+ return new BooleanValue(true);\r
+ },\r
+\r
+ 'false': function(ctx) {\r
+ assert(this.args.length == 0);\r
+ return new BooleanValue(false);\r
+ },\r
+\r
+ 'lang': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var lang = this.args[0].evaluate(ctx).stringValue();\r
+ var xmllang;\r
+ var n = ctx.node;\r
+ while (n && n != n.parentNode /* just in case ... */) {\r
+ xmllang = n.getAttribute('xml:lang');\r
+ if (xmllang) {\r
+ break;\r
+ }\r
+ n = n.parentNode;\r
+ }\r
+ if (!xmllang) {\r
+ return new BooleanValue(false);\r
+ } else {\r
+ var re = new RegExp('^' + lang + '$', 'i');\r
+ return new BooleanValue(xmllang.match(re) ||\r
+ xmllang.replace(/_.*$/,'').match(re));\r
+ }\r
+ },\r
+\r
+ 'number': function(ctx) {\r
+ assert(this.args.length == 1 || this.args.length == 0);\r
+\r
+ if (this.args.length == 1) {\r
+ return new NumberValue(this.args[0].evaluate(ctx).numberValue());\r
+ } else {\r
+ return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());\r
+ }\r
+ },\r
+\r
+ 'sum': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var n = this.args[0].evaluate(ctx).nodeSetValue();\r
+ var sum = 0;\r
+ for (var i = 0; i < n.length; ++i) {\r
+ sum += xmlValue(n[i]) - 0;\r
+ }\r
+ return new NumberValue(sum);\r
+ },\r
+\r
+ 'floor': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var num = this.args[0].evaluate(ctx).numberValue();\r
+ return new NumberValue(Math.floor(num));\r
+ },\r
+\r
+ 'ceiling': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var num = this.args[0].evaluate(ctx).numberValue();\r
+ return new NumberValue(Math.ceil(num));\r
+ },\r
+\r
+ 'round': function(ctx) {\r
+ assert(this.args.length == 1);\r
+ var num = this.args[0].evaluate(ctx).numberValue();\r
+ return new NumberValue(Math.round(num));\r
+ },\r
+\r
+ // TODO(mesch): The following functions are custom. There is a\r
+ // standard that defines how to add functions, which should be\r
+ // applied here.\r
+\r
+ 'ext-join': function(ctx) {\r
+ assert(this.args.length == 2);\r
+ var nodes = this.args[0].evaluate(ctx).nodeSetValue();\r
+ var delim = this.args[1].evaluate(ctx).stringValue();\r
+ var ret = '';\r
+ for (var i = 0; i < nodes.length; ++i) {\r
+ if (ret) {\r
+ ret += delim;\r
+ }\r
+ ret += xmlValue(nodes[i]);\r
+ }\r
+ return new StringValue(ret);\r
+ },\r
+\r
+ // ext-if() evaluates and returns its second argument, if the\r
+ // boolean value of its first argument is true, otherwise it\r
+ // evaluates and returns its third argument.\r
+\r
+ 'ext-if': function(ctx) {\r
+ assert(this.args.length == 3);\r
+ if (this.args[0].evaluate(ctx).booleanValue()) {\r
+ return this.args[1].evaluate(ctx);\r
+ } else {\r
+ return this.args[2].evaluate(ctx);\r
+ }\r
+ },\r
+\r
+ 'ext-sprintf': function(ctx) {\r
+ assert(this.args.length >= 1);\r
+ var args = [];\r
+ for (var i = 0; i < this.args.length; ++i) {\r
+ args.push(this.args[i].evaluate(ctx).stringValue());\r
+ }\r
+ return new StringValue(sprintf.apply(null, args));\r
+ },\r
+\r
+ // ext-cardinal() evaluates its single argument as a number, and\r
+ // returns the current node that many times. It can be used in the\r
+ // select attribute to iterate over an integer range.\r
+ \r
+ 'ext-cardinal': function(ctx) {\r
+ assert(this.args.length >= 1);\r
+ var c = this.args[0].evaluate(ctx).numberValue();\r
+ var ret = [];\r
+ for (var i = 0; i < c; ++i) {\r
+ ret.push(ctx.node);\r
+ }\r
+ return new NodeSetValue(ret);\r
+ }\r
+};\r
+\r
+function UnionExpr(expr1, expr2) {\r
+ this.expr1 = expr1;\r
+ this.expr2 = expr2;\r
+}\r
+\r
+UnionExpr.prototype.evaluate = function(ctx) {\r
+ var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();\r
+ var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();\r
+ var I1 = nodes1.length;\r
+ for (var i2 = 0; i2 < nodes2.length; ++i2) {\r
+ for (var i1 = 0; i1 < I1; ++i1) {\r
+ if (nodes1[i1] == nodes2[i2]) {\r
+ // break inner loop and continue outer loop, labels confuse\r
+ // the js compiler, so we don't use them here.\r
+ i1 = I1;\r
+ }\r
+ }\r
+ nodes1.push(nodes2[i2]);\r
+ }\r
+ return new NodeSetValue(nodes2);\r
+};\r
+\r
+function PathExpr(filter, rel) {\r
+ this.filter = filter;\r
+ this.rel = rel;\r
+}\r
+\r
+PathExpr.prototype.evaluate = function(ctx) {\r
+ var nodes = this.filter.evaluate(ctx).nodeSetValue();\r
+ var nodes1 = [];\r
+ for (var i = 0; i < nodes.length; ++i) {\r
+ var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();\r
+ for (var ii = 0; ii < nodes0.length; ++ii) {\r
+ nodes1.push(nodes0[ii]);\r
+ }\r
+ }\r
+ return new NodeSetValue(nodes1);\r
+};\r
+\r
+function FilterExpr(expr, predicate) {\r
+ this.expr = expr;\r
+ this.predicate = predicate;\r
+}\r
+\r
+FilterExpr.prototype.evaluate = function(ctx) {\r
+ var nodes = this.expr.evaluate(ctx).nodeSetValue();\r
+ for (var i = 0; i < this.predicate.length; ++i) {\r
+ var nodes0 = nodes;\r
+ nodes = [];\r
+ for (var j = 0; j < nodes0.length; ++j) {\r
+ var n = nodes0[j];\r
+ if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {\r
+ nodes.push(n);\r
+ }\r
+ }\r
+ }\r
+\r
+ return new NodeSetValue(nodes);\r
+}\r
+\r
+function UnaryMinusExpr(expr) {\r
+ this.expr = expr;\r
+}\r
+\r
+UnaryMinusExpr.prototype.evaluate = function(ctx) {\r
+ return new NumberValue(-this.expr.evaluate(ctx).numberValue());\r
+};\r
+\r
+function BinaryExpr(expr1, op, expr2) {\r
+ this.expr1 = expr1;\r
+ this.expr2 = expr2;\r
+ this.op = op;\r
+}\r
+\r
+BinaryExpr.prototype.evaluate = function(ctx) {\r
+ var ret;\r
+ switch (this.op.value) {\r
+ case 'or':\r
+ ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||\r
+ this.expr2.evaluate(ctx).booleanValue());\r
+ break;\r
+\r
+ case 'and':\r
+ ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &&\r
+ this.expr2.evaluate(ctx).booleanValue());\r
+ break;\r
+\r
+ case '+':\r
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +\r
+ this.expr2.evaluate(ctx).numberValue());\r
+ break;\r
+\r
+ case '-':\r
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -\r
+ this.expr2.evaluate(ctx).numberValue());\r
+ break;\r
+\r
+ case '*':\r
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *\r
+ this.expr2.evaluate(ctx).numberValue());\r
+ break;\r
+\r
+ case 'mod':\r
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %\r
+ this.expr2.evaluate(ctx).numberValue());\r
+ break;\r
+\r
+ case 'div':\r
+ ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /\r
+ this.expr2.evaluate(ctx).numberValue());\r
+ break;\r
+\r
+ case '=':\r
+ ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });\r
+ break;\r
+\r
+ case '!=':\r
+ ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });\r
+ break;\r
+\r
+ case '<':\r
+ ret = this.compare(ctx, function(x1, x2) { return x1 < x2; });\r
+ break;\r
+\r
+ case '<=':\r
+ ret = this.compare(ctx, function(x1, x2) { return x1 <= x2; });\r
+ break;\r
+\r
+ case '>':\r
+ ret = this.compare(ctx, function(x1, x2) { return x1 > x2; });\r
+ break;\r
+\r
+ case '>=':\r
+ ret = this.compare(ctx, function(x1, x2) { return x1 >= x2; });\r
+ break;\r
+\r
+ default:\r
+ alert('BinaryExpr.evaluate: ' + this.op.value);\r
+ }\r
+ return ret;\r
+};\r
+\r
+BinaryExpr.prototype.compare = function(ctx, cmp) {\r
+ var v1 = this.expr1.evaluate(ctx);\r
+ var v2 = this.expr2.evaluate(ctx);\r
+\r
+ var ret;\r
+ if (v1.type == 'node-set' && v2.type == 'node-set') {\r
+ var n1 = v1.nodeSetValue();\r
+ var n2 = v2.nodeSetValue();\r
+ ret = false;\r
+ for (var i1 = 0; i1 < n1.length; ++i1) {\r
+ for (var i2 = 0; i2 < n2.length; ++i2) {\r
+ if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {\r
+ ret = true;\r
+ // Break outer loop. Labels confuse the jscompiler and we\r
+ // don't use them.\r
+ i2 = n2.length;\r
+ i1 = n1.length;\r
+ }\r
+ }\r
+ }\r
+\r
+ } else if (v1.type == 'node-set' || v2.type == 'node-set') {\r
+\r
+ if (v1.type == 'number') {\r
+ var s = v1.numberValue();\r
+ var n = v2.nodeSetValue();\r
+\r
+ ret = false;\r
+ for (var i = 0; i < n.length; ++i) {\r
+ var nn = xmlValue(n[i]) - 0;\r
+ if (cmp(s, nn)) {\r
+ ret = true;\r
+ break;\r
+ }\r
+ }\r
+\r
+ } else if (v2.type == 'number') {\r
+ var n = v1.nodeSetValue();\r
+ var s = v2.numberValue();\r
+\r
+ ret = false;\r
+ for (var i = 0; i < n.length; ++i) {\r
+ var nn = xmlValue(n[i]) - 0;\r
+ if (cmp(nn, s)) {\r
+ ret = true;\r
+ break;\r
+ }\r
+ }\r
+\r
+ } else if (v1.type == 'string') {\r
+ var s = v1.stringValue();\r
+ var n = v2.nodeSetValue();\r
+\r
+ ret = false;\r
+ for (var i = 0; i < n.length; ++i) {\r
+ var nn = xmlValue(n[i]);\r
+ if (cmp(s, nn)) {\r
+ ret = true;\r
+ break;\r
+ }\r
+ }\r
+\r
+ } else if (v2.type == 'string') {\r
+ var n = v1.nodeSetValue();\r
+ var s = v2.stringValue();\r
+\r
+ ret = false;\r
+ for (var i = 0; i < n.length; ++i) {\r
+ var nn = xmlValue(n[i]);\r
+ if (cmp(nn, s)) {\r
+ ret = true;\r
+ break;\r
+ }\r
+ }\r
+\r
+ } else {\r
+ ret = cmp(v1.booleanValue(), v2.booleanValue());\r
+ }\r
+\r
+ } else if (v1.type == 'boolean' || v2.type == 'boolean') {\r
+ ret = cmp(v1.booleanValue(), v2.booleanValue());\r
+\r
+ } else if (v1.type == 'number' || v2.type == 'number') {\r
+ ret = cmp(v1.numberValue(), v2.numberValue());\r
+\r
+ } else {\r
+ ret = cmp(v1.stringValue(), v2.stringValue());\r
+ }\r
+\r
+ return new BooleanValue(ret);\r
+}\r
+\r
+function LiteralExpr(value) {\r
+ this.value = value;\r
+}\r
+\r
+LiteralExpr.prototype.evaluate = function(ctx) {\r
+ return new StringValue(this.value);\r
+};\r
+\r
+function NumberExpr(value) {\r
+ this.value = value;\r
+}\r
+\r
+NumberExpr.prototype.evaluate = function(ctx) {\r
+ return new NumberValue(this.value);\r
+};\r
+\r
+function VariableExpr(name) {\r
+ this.name = name;\r
+}\r
+\r
+VariableExpr.prototype.evaluate = function(ctx) {\r
+ return ctx.getVariable(this.name);\r
+}\r
+\r
+// Factory functions for semantic values (i.e. Expressions) of the\r
+// productions in the grammar. When a production is matched to reduce\r
+// the current parse state stack, the function is called with the\r
+// semantic values of the matched elements as arguments, and returns\r
+// another semantic value. The semantic value is a node of the parse\r
+// tree, an expression object with an evaluate() method that evaluates the\r
+// expression in an actual context. These factory functions are used\r
+// in the specification of the grammar rules, below.\r
+\r
+function makeTokenExpr(m) {\r
+ return new TokenExpr(m);\r
+}\r
+\r
+function passExpr(e) {\r
+ return e;\r
+}\r
+\r
+function makeLocationExpr1(slash, rel) {\r
+ rel.absolute = true;\r
+ return rel;\r
+}\r
+\r
+function makeLocationExpr2(dslash, rel) {\r
+ rel.absolute = true;\r
+ rel.prependStep(makeAbbrevStep(dslash.value));\r
+ return rel;\r
+}\r
+\r
+function makeLocationExpr3(slash) {\r
+ var ret = new LocationExpr();\r
+ ret.appendStep(makeAbbrevStep('.'));\r
+ ret.absolute = true;\r
+ return ret;\r
+}\r
+\r
+function makeLocationExpr4(dslash) {\r
+ var ret = new LocationExpr();\r
+ ret.absolute = true;\r
+ ret.appendStep(makeAbbrevStep(dslash.value));\r
+ return ret;\r
+}\r
+\r
+function makeLocationExpr5(step) {\r
+ var ret = new LocationExpr();\r
+ ret.appendStep(step);\r
+ return ret;\r
+}\r
+\r
+function makeLocationExpr6(rel, slash, step) {\r
+ rel.appendStep(step);\r
+ return rel;\r
+}\r
+\r
+function makeLocationExpr7(rel, dslash, step) {\r
+ rel.appendStep(makeAbbrevStep(dslash.value));\r
+ return rel;\r
+}\r
+\r
+function makeStepExpr1(dot) {\r
+ return makeAbbrevStep(dot.value);\r
+}\r
+\r
+function makeStepExpr2(ddot) {\r
+ return makeAbbrevStep(ddot.value);\r
+}\r
+\r
+function makeStepExpr3(axisname, axis, nodetest) {\r
+ return new StepExpr(axisname.value, nodetest);\r
+}\r
+\r
+function makeStepExpr4(at, nodetest) {\r
+ return new StepExpr('attribute', nodetest);\r
+}\r
+\r
+function makeStepExpr5(nodetest) {\r
+ return new StepExpr('child', nodetest);\r
+}\r
+\r
+function makeStepExpr6(step, predicate) {\r
+ step.appendPredicate(predicate);\r
+ return step;\r
+}\r
+\r
+function makeAbbrevStep(abbrev) {\r
+ switch (abbrev) {\r
+ case '//':\r
+ return new StepExpr('descendant-or-self', new NodeTestAny);\r
+\r
+ case '.':\r
+ return new StepExpr('self', new NodeTestAny);\r
+\r
+ case '..':\r
+ return new StepExpr('parent', new NodeTestAny);\r
+ }\r
+}\r
+\r
+function makeNodeTestExpr1(asterisk) {\r
+ return new NodeTestElement;\r
+}\r
+\r
+function makeNodeTestExpr2(ncname, colon, asterisk) {\r
+ return new NodeTestNC(ncname.value);\r
+}\r
+\r
+function makeNodeTestExpr3(qname) {\r
+ return new NodeTestName(qname.value);\r
+}\r
+\r
+function makeNodeTestExpr4(typeo, parenc) {\r
+ var type = typeo.value.replace(/\s*\($/, '');\r
+ switch(type) {\r
+ case 'node':\r
+ return new NodeTestAny;\r
+\r
+ case 'text':\r
+ return new NodeTestText;\r
+\r
+ case 'comment':\r
+ return new NodeTestComment;\r
+\r
+ case 'processing-instruction':\r
+ return new NodeTestPI;\r
+ }\r
+}\r
+\r
+function makeNodeTestExpr5(typeo, target, parenc) {\r
+ var type = typeo.replace(/\s*\($/, '');\r
+ if (type != 'processing-instruction') {\r
+ throw type + ' ' + Error().stack;\r
+ }\r
+ return new NodeTestPI(target.value);\r
+}\r
+\r
+function makePredicateExpr(pareno, expr, parenc) {\r
+ return new PredicateExpr(expr);\r
+}\r
+\r
+function makePrimaryExpr(pareno, expr, parenc) {\r
+ return expr;\r
+}\r
+\r
+function makeFunctionCallExpr1(name, pareno, parenc) {\r
+ return new FunctionCallExpr(name);\r
+}\r
+\r
+function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {\r
+ var ret = new FunctionCallExpr(name);\r
+ ret.appendArg(arg1);\r
+ for (var i = 0; i < args.length; ++i) {\r
+ ret.appendArg(args[i]);\r
+ }\r
+ return ret;\r
+}\r
+\r
+function makeArgumentExpr(comma, expr) {\r
+ return expr;\r
+}\r
+\r
+function makeUnionExpr(expr1, pipe, expr2) {\r
+ return new UnionExpr(expr1, expr2);\r
+}\r
+\r
+function makePathExpr1(filter, slash, rel) {\r
+ return new PathExpr(filter, rel);\r
+}\r
+\r
+function makePathExpr2(filter, dslash, rel) {\r
+ rel.prependStep(makeAbbrevStep(dslash.value));\r
+ return new PathExpr(filter, rel);\r
+}\r
+\r
+function makeFilterExpr(expr, predicates) {\r
+ if (predicates.length > 0) {\r
+ return new FilterExpr(expr, predicates);\r
+ } else {\r
+ return expr;\r
+ }\r
+}\r
+\r
+function makeUnaryMinusExpr(minus, expr) {\r
+ return new UnaryMinusExpr(expr);\r
+}\r
+\r
+function makeBinaryExpr(expr1, op, expr2) {\r
+ return new BinaryExpr(expr1, op, expr2);\r
+}\r
+\r
+function makeLiteralExpr(token) {\r
+ // remove quotes from the parsed value:\r
+ var value = token.value.substring(1, token.value.length - 1);\r
+ return new LiteralExpr(value);\r
+}\r
+\r
+function makeNumberExpr(token) {\r
+ return new NumberExpr(token.value);\r
+}\r
+\r
+function makeVariableReference(dollar, name) {\r
+ return new VariableExpr(name.value);\r
+}\r
+\r
+// Used before parsing for optimization of common simple cases. See\r
+// the begin of xpathParse() for which they are.\r
+function makeSimpleExpr(expr) {\r
+ if (expr.charAt(0) == '$') {\r
+ return new VariableExpr(expr.substr(1));\r
+ } else if (expr.charAt(0) == '@') {\r
+ var a = new NodeTestName(expr.substr(1));\r
+ var b = new StepExpr('attribute', a);\r
+ var c = new LocationExpr();\r
+ c.appendStep(b);\r
+ return c;\r
+ } else if (expr.match(/^[0-9]+$/)) {\r
+ return new NumberExpr(expr);\r
+ } else {\r
+ var a = new NodeTestName(expr);\r
+ var b = new StepExpr('child', a);\r
+ var c = new LocationExpr();\r
+ c.appendStep(b);\r
+ return c;\r
+ }\r
+}\r
+\r
+function makeSimpleExpr2(expr) {\r
+ var steps = expr.split('/');\r
+ var c = new LocationExpr();\r
+ for (var i in steps) {\r
+ var a = new NodeTestName(steps[i]);\r
+ var b = new StepExpr('child', a);\r
+ c.appendStep(b);\r
+ }\r
+ return c;\r
+}\r
+\r
+// The axes of XPath expressions.\r
+\r
+var xpathAxis = {\r
+ ANCESTOR_OR_SELF: 'ancestor-or-self',\r
+ ANCESTOR: 'ancestor',\r
+ ATTRIBUTE: 'attribute',\r
+ CHILD: 'child',\r
+ DESCENDANT_OR_SELF: 'descendant-or-self',\r
+ DESCENDANT: 'descendant',\r
+ FOLLOWING_SIBLING: 'following-sibling',\r
+ FOLLOWING: 'following',\r
+ NAMESPACE: 'namespace',\r
+ PARENT: 'parent',\r
+ PRECEDING_SIBLING: 'preceding-sibling',\r
+ PRECEDING: 'preceding',\r
+ SELF: 'self'\r
+};\r
+\r
+var xpathAxesRe = [\r
+ xpathAxis.ANCESTOR_OR_SELF,\r
+ xpathAxis.ANCESTOR,\r
+ xpathAxis.ATTRIBUTE,\r
+ xpathAxis.CHILD,\r
+ xpathAxis.DESCENDANT_OR_SELF,\r
+ xpathAxis.DESCENDANT,\r
+ xpathAxis.FOLLOWING_SIBLING,\r
+ xpathAxis.FOLLOWING,\r
+ xpathAxis.NAMESPACE,\r
+ xpathAxis.PARENT,\r
+ xpathAxis.PRECEDING_SIBLING,\r
+ xpathAxis.PRECEDING,\r
+ xpathAxis.SELF\r
+].join('|');\r
+\r
+\r
+// The tokens of the language. The label property is just used for\r
+// generating debug output. The prec property is the precedence used\r
+// for shift/reduce resolution. Default precedence is 0 as a lookahead\r
+// token and 2 on the stack. TODO(mesch): this is certainly not\r
+// necessary and too complicated. Simplify this!\r
+\r
+// NOTE: tabular formatting is the big exception, but here it should\r
+// be OK.\r
+\r
+var TOK_PIPE = { label: "|", prec: 17, re: new RegExp("^\\|") };\r
+var TOK_DSLASH = { label: "//", prec: 19, re: new RegExp("^//") };\r
+var TOK_SLASH = { label: "/", prec: 30, re: new RegExp("^/") };\r
+var TOK_AXIS = { label: "::", prec: 20, re: new RegExp("^::") };\r
+var TOK_COLON = { label: ":", prec: 1000, re: new RegExp("^:") };\r
+var TOK_AXISNAME = { label: "[axis]", re: new RegExp('^(' + xpathAxesRe + ')') };\r
+var TOK_PARENO = { label: "(", prec: 34, re: new RegExp("^\\(") };\r
+var TOK_PARENC = { label: ")", re: new RegExp("^\\)") };\r
+var TOK_DDOT = { label: "..", prec: 34, re: new RegExp("^\\.\\.") };\r
+var TOK_DOT = { label: ".", prec: 34, re: new RegExp("^\\.") };\r
+var TOK_AT = { label: "@", prec: 34, re: new RegExp("^@") };\r
+\r
+var TOK_COMMA = { label: ",", re: new RegExp("^,") };\r
+\r
+var TOK_OR = { label: "or", prec: 10, re: new RegExp("^or\\b") };\r
+var TOK_AND = { label: "and", prec: 11, re: new RegExp("^and\\b") };\r
+var TOK_EQ = { label: "=", prec: 12, re: new RegExp("^=") };\r
+var TOK_NEQ = { label: "!=", prec: 12, re: new RegExp("^!=") };\r
+var TOK_GE = { label: ">=", prec: 13, re: new RegExp("^>=") };\r
+var TOK_GT = { label: ">", prec: 13, re: new RegExp("^>") };\r
+var TOK_LE = { label: "<=", prec: 13, re: new RegExp("^<=") };\r
+var TOK_LT = { label: "<", prec: 13, re: new RegExp("^<") };\r
+var TOK_PLUS = { label: "+", prec: 14, re: new RegExp("^\\+"), left: true };\r
+var TOK_MINUS = { label: "-", prec: 14, re: new RegExp("^\\-"), left: true };\r
+var TOK_DIV = { label: "div", prec: 15, re: new RegExp("^div\\b"), left: true };\r
+var TOK_MOD = { label: "mod", prec: 15, re: new RegExp("^mod\\b"), left: true };\r
+\r
+var TOK_BRACKO = { label: "[", prec: 32, re: new RegExp("^\\[") };\r
+var TOK_BRACKC = { label: "]", re: new RegExp("^\\]") };\r
+var TOK_DOLLAR = { label: "$", re: new RegExp("^\\$") };\r
+\r
+var TOK_NCNAME = { label: "[ncname]", re: new RegExp('^[a-z][-\\w]*','i') };\r
+\r
+var TOK_ASTERISK = { label: "*", prec: 15, re: new RegExp("^\\*"), left: true };\r
+var TOK_LITERALQ = { label: "[litq]", prec: 20, re: new RegExp("^'[^\\']*'") };\r
+var TOK_LITERALQQ = {\r
+ label: "[litqq]",\r
+ prec: 20,\r
+ re: new RegExp('^"[^\\"]*"')\r
+};\r
+\r
+var TOK_NUMBER = {\r
+ label: "[number]",\r
+ prec: 35,\r
+ re: new RegExp('^\\d+(\\.\\d*)?') };\r
+\r
+var TOK_QNAME = {\r
+ label: "[qname]",\r
+ re: new RegExp('^([a-z][-\\w]*:)?[a-z][-\\w]*','i')\r
+};\r
+\r
+var TOK_NODEO = {\r
+ label: "[nodetest-start]",\r
+ re: new RegExp('^(processing-instruction|comment|text|node)\\(')\r
+};\r
+\r
+// The table of the tokens of our grammar, used by the lexer: first\r
+// column the tag, second column a regexp to recognize it in the\r
+// input, third column the precedence of the token, fourth column a\r
+// factory function for the semantic value of the token.\r
+//\r
+// NOTE: order of this list is important, because the first match\r
+// counts. Cf. DDOT and DOT, and AXIS and COLON.\r
+\r
+var xpathTokenRules = [\r
+ TOK_DSLASH,\r
+ TOK_SLASH,\r
+ TOK_DDOT,\r
+ TOK_DOT,\r
+ TOK_AXIS,\r
+ TOK_COLON,\r
+ TOK_AXISNAME,\r
+ TOK_NODEO,\r
+ TOK_PARENO,\r
+ TOK_PARENC,\r
+ TOK_BRACKO,\r
+ TOK_BRACKC,\r
+ TOK_AT,\r
+ TOK_COMMA,\r
+ TOK_OR,\r
+ TOK_AND,\r
+ TOK_NEQ,\r
+ TOK_EQ,\r
+ TOK_GE,\r
+ TOK_GT,\r
+ TOK_LE,\r
+ TOK_LT,\r
+ TOK_PLUS,\r
+ TOK_MINUS,\r
+ TOK_ASTERISK,\r
+ TOK_PIPE,\r
+ TOK_MOD,\r
+ TOK_DIV,\r
+ TOK_LITERALQ,\r
+ TOK_LITERALQQ,\r
+ TOK_NUMBER,\r
+ TOK_QNAME,\r
+ TOK_NCNAME,\r
+ TOK_DOLLAR\r
+];\r
+\r
+// All the nonterminals of the grammar. The nonterminal objects are\r
+// identified by object identity; the labels are used in the debug\r
+// output only.\r
+var XPathLocationPath = { label: "LocationPath" };\r
+var XPathRelativeLocationPath = { label: "RelativeLocationPath" };\r
+var XPathAbsoluteLocationPath = { label: "AbsoluteLocationPath" };\r
+var XPathStep = { label: "Step" };\r
+var XPathNodeTest = { label: "NodeTest" };\r
+var XPathPredicate = { label: "Predicate" };\r
+var XPathLiteral = { label: "Literal" };\r
+var XPathExpr = { label: "Expr" };\r
+var XPathPrimaryExpr = { label: "PrimaryExpr" };\r
+var XPathVariableReference = { label: "Variablereference" };\r
+var XPathNumber = { label: "Number" };\r
+var XPathFunctionCall = { label: "FunctionCall" };\r
+var XPathArgumentRemainder = { label: "ArgumentRemainder" };\r
+var XPathPathExpr = { label: "PathExpr" };\r
+var XPathUnionExpr = { label: "UnionExpr" };\r
+var XPathFilterExpr = { label: "FilterExpr" };\r
+var XPathDigits = { label: "Digits" };\r
+\r
+var xpathNonTerminals = [\r
+ XPathLocationPath,\r
+ XPathRelativeLocationPath,\r
+ XPathAbsoluteLocationPath,\r
+ XPathStep,\r
+ XPathNodeTest,\r
+ XPathPredicate,\r
+ XPathLiteral,\r
+ XPathExpr,\r
+ XPathPrimaryExpr,\r
+ XPathVariableReference,\r
+ XPathNumber,\r
+ XPathFunctionCall,\r
+ XPathArgumentRemainder,\r
+ XPathPathExpr,\r
+ XPathUnionExpr,\r
+ XPathFilterExpr,\r
+ XPathDigits\r
+];\r
+\r
+// Quantifiers that are used in the productions of the grammar.\r
+var Q_01 = { label: "?" };\r
+var Q_MM = { label: "*" };\r
+var Q_1M = { label: "+" };\r
+\r
+// Tag for left associativity (right assoc is implied by undefined).\r
+var ASSOC_LEFT = true;\r
+\r
+// The productions of the grammar. Columns of the table:\r
+//\r
+// - target nonterminal,\r
+// - pattern,\r
+// - precedence,\r
+// - semantic value factory\r
+//\r
+// The semantic value factory is a function that receives parse tree\r
+// nodes from the stack frames of the matched symbols as arguments and\r
+// returns an a node of the parse tree. The node is stored in the top\r
+// stack frame along with the target object of the rule. The node in\r
+// the parse tree is an expression object that has an evaluate() method\r
+// and thus evaluates XPath expressions.\r
+//\r
+// The precedence is used to decide between reducing and shifting by\r
+// comparing the precendence of the rule that is candidate for\r
+// reducing with the precedence of the look ahead token. Precedence of\r
+// -1 means that the precedence of the tokens in the pattern is used\r
+// instead. TODO: It shouldn't be necessary to explicitly assign\r
+// precedences to rules.\r
+\r
+var xpathGrammarRules =\r
+ [\r
+ [ XPathLocationPath, [ XPathRelativeLocationPath ], 18,\r
+ passExpr ],\r
+ [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18,\r
+ passExpr ],\r
+\r
+ [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18, \r
+ makeLocationExpr1 ],\r
+ [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18,\r
+ makeLocationExpr2 ],\r
+\r
+ [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0,\r
+ makeLocationExpr3 ],\r
+ [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0,\r
+ makeLocationExpr4 ],\r
+\r
+ [ XPathRelativeLocationPath, [ XPathStep ], 31,\r
+ makeLocationExpr5 ],\r
+ [ XPathRelativeLocationPath,\r
+ [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31,\r
+ makeLocationExpr6 ],\r
+ [ XPathRelativeLocationPath,\r
+ [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31,\r
+ makeLocationExpr7 ],\r
+\r
+ [ XPathStep, [ TOK_DOT ], 33,\r
+ makeStepExpr1 ],\r
+ [ XPathStep, [ TOK_DDOT ], 33,\r
+ makeStepExpr2 ],\r
+ [ XPathStep,\r
+ [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33,\r
+ makeStepExpr3 ],\r
+ [ XPathStep, [ TOK_AT, XPathNodeTest ], 33,\r
+ makeStepExpr4 ],\r
+ [ XPathStep, [ XPathNodeTest ], 33,\r
+ makeStepExpr5 ],\r
+ [ XPathStep, [ XPathStep, XPathPredicate ], 33,\r
+ makeStepExpr6 ],\r
+\r
+ [ XPathNodeTest, [ TOK_ASTERISK ], 33,\r
+ makeNodeTestExpr1 ],\r
+ [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33,\r
+ makeNodeTestExpr2 ],\r
+ [ XPathNodeTest, [ TOK_QNAME ], 33,\r
+ makeNodeTestExpr3 ],\r
+ [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33,\r
+ makeNodeTestExpr4 ],\r
+ [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33,\r
+ makeNodeTestExpr5 ],\r
+\r
+ [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33,\r
+ makePredicateExpr ],\r
+\r
+ [ XPathPrimaryExpr, [ XPathVariableReference ], 33,\r
+ passExpr ],\r
+ [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33,\r
+ makePrimaryExpr ],\r
+ [ XPathPrimaryExpr, [ XPathLiteral ], 30,\r
+ passExpr ],\r
+ [ XPathPrimaryExpr, [ XPathNumber ], 30,\r
+ passExpr ],\r
+ [ XPathPrimaryExpr, [ XPathFunctionCall ], 30,\r
+ passExpr ],\r
+\r
+ [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,\r
+ makeFunctionCallExpr1 ],\r
+ [ XPathFunctionCall,\r
+ [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM,\r
+ TOK_PARENC ], -1,\r
+ makeFunctionCallExpr2 ],\r
+ [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1,\r
+ makeArgumentExpr ],\r
+\r
+ [ XPathUnionExpr, [ XPathPathExpr ], 20,\r
+ passExpr ],\r
+ [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20,\r
+ makeUnionExpr ],\r
+\r
+ [ XPathPathExpr, [ XPathLocationPath ], 20, \r
+ passExpr ], \r
+ [ XPathPathExpr, [ XPathFilterExpr ], 19, \r
+ passExpr ], \r
+ [ XPathPathExpr, \r
+ [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 20,\r
+ makePathExpr1 ],\r
+ [ XPathPathExpr,\r
+ [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 20,\r
+ makePathExpr2 ],\r
+\r
+ [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 20,\r
+ makeFilterExpr ], \r
+\r
+ [ XPathExpr, [ XPathPrimaryExpr ], 16,\r
+ passExpr ],\r
+ [ XPathExpr, [ XPathUnionExpr ], 16,\r
+ passExpr ],\r
+\r
+ [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1,\r
+ makeUnaryMinusExpr ],\r
+\r
+ [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+ [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+\r
+ [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+ [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+\r
+ [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+ [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+ [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+ [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1,\r
+ makeBinaryExpr ],\r
+\r
+ [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1,\r
+ makeBinaryExpr, ASSOC_LEFT ],\r
+ [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1,\r
+ makeBinaryExpr, ASSOC_LEFT ],\r
+\r
+ [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1,\r
+ makeBinaryExpr, ASSOC_LEFT ],\r
+ [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1,\r
+ makeBinaryExpr, ASSOC_LEFT ],\r
+ [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1,\r
+ makeBinaryExpr, ASSOC_LEFT ],\r
+\r
+ [ XPathLiteral, [ TOK_LITERALQ ], -1,\r
+ makeLiteralExpr ],\r
+ [ XPathLiteral, [ TOK_LITERALQQ ], -1,\r
+ makeLiteralExpr ],\r
+\r
+ [ XPathNumber, [ TOK_NUMBER ], -1,\r
+ makeNumberExpr ],\r
+\r
+ [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200,\r
+ makeVariableReference ]\r
+ ];\r
+\r
+// That function computes some optimizations of the above data\r
+// structures and will be called right here. It merely takes the\r
+// counter variables out of the global scope.\r
+\r
+var xpathRules = [];\r
+\r
+function xpathParseInit() {\r
+ if (xpathRules.length) {\r
+ return;\r
+ }\r
+\r
+ // Some simple optimizations for the xpath expression parser: sort\r
+ // grammar rules descending by length, so that the longest match is\r
+ // first found.\r
+\r
+ xpathGrammarRules.sort(function(a,b) {\r
+ var la = a[1].length;\r
+ var lb = b[1].length;\r
+ if (la < lb) {\r
+ return 1;\r
+ } else if (la > lb) {\r
+ return -1;\r
+ } else {\r
+ return 0;\r
+ }\r
+ });\r
+\r
+ var k = 1;\r
+ for (var i = 0; i < xpathNonTerminals.length; ++i) {\r
+ xpathNonTerminals[i].key = k++;\r
+ }\r
+\r
+ for (i = 0; i < xpathTokenRules.length; ++i) {\r
+ xpathTokenRules[i].key = k++;\r
+ }\r
+\r
+ Log.write('XPath parse INIT: ' + k + ' rules');\r
+\r
+ // Another slight optimization: sort the rules into bins according\r
+ // to the last element (observing quantifiers), so we can restrict\r
+ // the match against the stack to the subest of rules that match the\r
+ // top of the stack.\r
+ //\r
+ // TODO(mesch): What we actually want is to compute states as in\r
+ // bison, so that we don't have to do any explicit and iterated\r
+ // match against the stack.\r
+\r
+ function push_(array, position, element) {\r
+ if (!array[position]) {\r
+ array[position] = [];\r
+ }\r
+ array[position].push(element);\r
+ }\r
+\r
+ for (i = 0; i < xpathGrammarRules.length; ++i) {\r
+ var rule = xpathGrammarRules[i];\r
+ var pattern = rule[1];\r
+\r
+ for (var j = pattern.length - 1; j >= 0; --j) {\r
+ if (pattern[j] == Q_1M) {\r
+ push_(xpathRules, pattern[j-1].key, rule);\r
+ break;\r
+ \r
+ } else if (pattern[j] == Q_MM || pattern[j] == Q_01) {\r
+ push_(xpathRules, pattern[j-1].key, rule);\r
+ --j;\r
+\r
+ } else {\r
+ push_(xpathRules, pattern[j].key, rule);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ Log.write('XPath parse INIT: ' + xpathRules.length + ' rule bins');\r
+ \r
+ var sum = 0;\r
+ mapExec(xpathRules, function(i) {\r
+ if (i) {\r
+ sum += i.length;\r
+ }\r
+ });\r
+ \r
+ Log.write('XPath parse INIT: ' + (sum / xpathRules.length) + ' average bin size');\r
+}\r
+\r
+// Local utility functions that are used by the lexer or parser.\r
+\r
+function xpathCollectDescendants(nodelist, node) {\r
+ for (var n = node.firstChild; n; n = n.nextSibling) {\r
+ nodelist.push(n);\r
+ arguments.callee(nodelist, n);\r
+ }\r
+}\r
+\r
+function xpathCollectDescendantsReverse(nodelist, node) {\r
+ for (var n = node.lastChild; n; n = n.previousSibling) {\r
+ nodelist.push(n);\r
+ arguments.callee(nodelist, n);\r
+ }\r
+}\r
+\r
+\r
+// The entry point for the library: match an expression against a DOM\r
+// node. Returns an XPath value.\r
+function xpathDomEval(expr, node) {\r
+ var expr1 = xpathParse(expr);\r
+ var ret = expr1.evaluate(new ExprContext(node));\r
+ return ret;\r
+}\r
+\r
+// Utility function to sort a list of nodes. Used by xsltSort() and\r
+// nxslSelect().\r
+function xpathSort(input, sort) {\r
+ if (sort.length == 0) {\r
+ return;\r
+ }\r
+\r
+ var sortlist = [];\r
+\r
+ for (var i = 0; i < input.nodelist.length; ++i) {\r
+ var node = input.nodelist[i];\r
+ var sortitem = { node: node, key: [] };\r
+ var context = input.clone(node, 0, [ node ]);\r
+ \r
+ for (var j = 0; j < sort.length; ++j) {\r
+ var s = sort[j];\r
+ var value = s.expr.evaluate(context);\r
+\r
+ var evalue;\r
+ if (s.type == 'text') {\r
+ evalue = value.stringValue();\r
+ } else if (s.type == 'number') {\r
+ evalue = value.numberValue();\r
+ }\r
+ sortitem.key.push({ value: evalue, order: s.order });\r
+ }\r
+\r
+ // Make the sort stable by adding a lowest priority sort by\r
+ // id. This is very convenient and furthermore required by the\r
+ // spec ([XSLT] - Section 10 Sorting).\r
+ sortitem.key.push({ value: i, order: 'ascending' });\r
+\r
+ sortlist.push(sortitem);\r
+ }\r
+\r
+ sortlist.sort(xpathSortByKey);\r
+\r
+ var nodes = [];\r
+ for (var i = 0; i < sortlist.length; ++i) {\r
+ nodes.push(sortlist[i].node);\r
+ }\r
+ input.nodelist = nodes;\r
+ input.setNode(nodes[0], 0);\r
+}\r
+\r
+\r
+// Sorts by all order criteria defined. According to the JavaScript\r
+// spec ([ECMA] Section 11.8.5), the compare operators compare strings\r
+// as strings and numbers as numbers.\r
+//\r
+// NOTE: In browsers which do not follow the spec, this breaks only in\r
+// the case that numbers should be sorted as strings, which is very\r
+// uncommon.\r
+\r
+function xpathSortByKey(v1, v2) {\r
+ // NOTE: Sort key vectors of different length never occur in\r
+ // xsltSort.\r
+\r
+ for (var i = 0; i < v1.key.length; ++i) {\r
+ var o = v1.key[i].order == 'descending' ? -1 : 1;\r
+ if (v1.key[i].value > v2.key[i].value) {\r
+ return +1 * o;\r
+ } else if (v1.key[i].value < v2.key[i].value) {\r
+ return -1 * o;\r
+ }\r
+ }\r
+\r
+ return 0;\r
+}\r