分享

xpath.js

 pengyan 2006-12-06


Summary

No overview generated for ‘xpath.js‘


Class Summary
BinaryExpr 
BooleanValue 
ExprContext 
FilterExpr 
FunctionCallExpr 
LiteralExpr 
LocationExpr 
Log 
NodeSetValue 
NodeTestAny 
NodeTestComment 
NodeTestElement 
NodeTestName 
NodeTestNC 
NodeTestPI 
NodeTestText 
NumberExpr 
NumberValue 
PathExpr 
PredicateExpr 
StepExpr 
StringValue 
Timer 
TokenExpr 
UnaryMinusExpr 
UnionExpr 
VariableExpr 

Method Summary
static void assert(b)
          
static void copyArray(dst, src)
          
static Object el(i)
          
static Object makeAbbrevStep(abbrev)
          
static Object makeArgumentExpr(comma, expr)
          
static Object makeBinaryExpr(expr1, op, expr2)
          
static Object makeFilterExpr(expr, predicates)
          
static Object makeFunctionCallExpr1(name, pareno, parenc)
          
static Object makeFunctionCallExpr2(name, pareno, arg1, args, parenc)
          
static Object makeLiteralExpr(token)
          
static Object makeLocationExpr1(slash, rel)
          
static Object makeLocationExpr2(dslash, rel)
          
static Object makeLocationExpr3(slash)
          
static Object makeLocationExpr4(dslash)
          
static Object makeLocationExpr5(step)
          
static Object makeLocationExpr6(rel, slash, step)
          
static Object makeLocationExpr7(rel, dslash, step)
          
static Object makeNodeTestExpr1(asterisk)
          
static Object makeNodeTestExpr2(ncname, colon, asterisk)
          
static Object makeNodeTestExpr3(qname)
          
static Object makeNodeTestExpr4(typeo, parenc)
          
static Object makeNodeTestExpr5(typeo, target, parenc)
          
static Object makeNumberExpr(token)
          
static Object makePathExpr1(filter, slash, rel)
          
static Object makePathExpr2(filter, dslash, rel)
          
static Object makePredicateExpr(pareno, expr, parenc)
          
static Object makePrimaryExpr(pareno, expr, parenc)
          
static Object makeSimpleExpr(expr)
          
static Object makeSimpleExpr2(expr)
          
static Object makeStepExpr1(dot)
          
static Object makeStepExpr2(ddot)
          
static Object makeStepExpr3(axisname, axis, nodetest)
          
static Object makeStepExpr4(at, nodetest)
          
static Object makeStepExpr5(nodetest)
          
static Object makeStepExpr6(step, predicate)
          
static Object makeTokenExpr(m)
          
static Object makeUnaryMinusExpr(minus, expr)
          
static Object makeUnionExpr(expr1, pipe, expr2)
          
static Object makeVariableReference(dollar, name)
          
static void mapExec(array, func)
          
static Object mapExpr(array, func)
          
static Object passExpr(e)
          
static Object px(x)
          
static void reverseInplace(array)
          
static Object stackToString(stack)
          
static Object stringSplit(s, c)
          
static Object xmlEscapeAttr(s)
          
static Object xmlEscapeTags(s)
          
static Object xmlEscapeText(s)
          
static Object xmlText(node)
          
static Object xmlValue(node)
          
static Object xpathCacheLookup(expr)
          
static void xpathCollectDescendants(nodelist, node)
          
static void xpathCollectDescendantsReverse(nodelist, node)
          
static Object xpathDomEval(expr, node)
          
static Object xpathGrammarPrecedence(frame)
          
static Object xpathMatchStack(stack, pattern)
          
static Object xpathParse(expr)
          
static void xpathParseInit()
          
static Object xpathReduce(stack, ahead)
          
static void xpathSort(input, sort)
          
static Object xpathSortByKey(v1, v2)
          
static void xPathStep(nodes, steps, step, input, ctx)
          
static Object xpathTokenPrecedence(tag)
          
/* xpath.js - Revision: Spry Preview Release 1.3 */// Copyright (c) 2005, Google Inc.// All rights reserved.// // Redistribution and use in source and binary forms, with or without// modification, are permitted provided that the following conditions are// met://         //  * Redistributions of source code must retain the above copyright//    notice, this list of conditions and the following disclaimer.// //  * Redistributions in binary form must reproduce the above copyright//    notice, this list of conditions and the following disclaimer in the//    documentation and/or other materials provided with the//    distribution.// //  * Neither the name of Google Inc. nor the names of its contributors//    may be used to endorse or promote products derived from this//    software without specific prior written permission.// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.//// An XPath parser and evaluator written in JavaScript. The// implementation is complete except for functions handling// namespaces.//// Reference: [XPATH] XPath Specification// <http://www./TR/1999/REC-xpath-19991116>.////// The API of the parser has several parts://// 1. The parser function xpathParse() that takes a string and returns// an expession object.//// 2. The expression object that has an evaluate() method to evaluate the// XPath expression it represents. (It is actually a hierarchy of// objects that resembles the parse tree, but an application will call// evaluate() only on the top node of this hierarchy.)//// 3. The context object that is passed as an argument to the evaluate()// method, which represents the DOM context in which the expression is// evaluated.//// 4. The value object that is returned from evaluate() and represents// values of the different types that are defined by XPath (number,// string, boolean, and node-set), and allows to convert between them.//// These parts are near the top of the file, the functions and data// that are used internally follow after them.////// TODO(mesch): add jsdoc comments. Use more coherent naming.////// Author: Steffen Meschkat <mesch@google.com>// The entry point for the parser.//// @param expr a string that contains an XPath expression.// @return an expression object that can be evaluated with an// expression context.function xpathParse(expr) {if (xpathdebug) {
Log.write(‘XPath parse ‘ + expr);
}
xpathParseInit();
var cached = xpathCacheLookup(expr);if (cached) {if (xpathdebug) {
Log.write(‘ ... cached‘);
}return cached;
}// Optimize for a few common cases: simple attribute node tests// (@id), simple element node tests (page), variable references// ($address), numbers (4), multi-step path expressions where each// step is a plain element node test// (page/overlay/locations/location).if (expr.match(/^(\$|@)?\w+$/i)) {
var ret = makeSimpleExpr(expr);
xpathParseCache[expr] = ret;if (xpathdebug) {
Log.write(‘ ... simple‘);
}return ret;
}if (expr.match(/^\w+(\/\w+)*$/i)) {
var ret = makeSimpleExpr2(expr);
xpathParseCache[expr] = ret;if (xpathdebug) {
Log.write(‘ ... simple 2‘);
}return ret;
}
var cachekey = expr; // expr is modified during parseif (xpathdebug) {
Timer.start(‘XPath parse‘, cachekey);
}
var stack = [];
var ahead = null;
var previous = null;
var done = false;
var parse_count = 0;
var lexer_count = 0;
var reduce_count = 0;while (!done) {
parse_count++;
expr = expr.replace(/^\s*/, ‘‘);
previous = ahead;
ahead = null;
var rule = null;
var match = ‘‘;for (var i = 0; i < xpathTokenRules.length; ++i) {
var result = xpathTokenRules[i].re.exec(expr);
lexer_count++;if (result && result.length > 0 && result[0].length > match.length) {
rule = xpathTokenRules[i];
match = result[0];
break;
}
}// Special case: allow operator keywords to be element and// variable names.// NOTE(mesch): The parser resolves conflicts by looking ahead,// and this is the only case where we look back to// disambiguate. So this is indeed something different, and// looking back is usually done in the lexer (via states in the// general case, called "start conditions" in flex(1)). Also,the// conflict resolution in the parser is not as robust as it could// be, so I‘d like to keep as much off the parser as possible (all// these precedence values should be computed from the grammar// rules and possibly associativity declarations, as in bison(1),// and not explicitly set.if (rule &&
(rule == TOK_DIV ||
rule == TOK_MOD ||
rule == TOK_AND ||
rule == TOK_OR) &&
(!previous ||
previous.tag == TOK_AT ||
previous.tag == TOK_DSLASH ||
previous.tag == TOK_SLASH ||
previous.tag == TOK_AXIS ||
previous.tag == TOK_DOLLAR)) {
rule = TOK_QNAME;
}if (rule) {
expr = expr.substr(match.length);if (xpathdebug) {
Log.write(‘token: ‘ + match + ‘ -- ‘ + rule.label);
}
ahead = {
tag: rule,
match: match,
prec: rule.prec ?  rule.prec : 0, // || 0 is removed by the compilerexpr: makeTokenExpr(match)
};
} else {if (xpathdebug) {
Log.write(‘DONE‘);
}
done = true;
}while (xpathReduce(stack, ahead)) {
reduce_count++;if (xpathdebug) {
Log.write(‘stack: ‘ + stackToString(stack));
}
}
}if (xpathdebug) {
Log.write(stackToString(stack));
}if (stack.length != 1) {
throw ‘XPath parse error ‘ + cachekey + ‘:\n‘ + stackToString(stack);
}
var result = stack[0].expr;
xpathParseCache[cachekey] = result;if (xpathdebug) {
Timer.end(‘XPath parse‘, cachekey);
}if (xpathdebug) {
Log.write(‘XPath parse: ‘ + parse_count + ‘ / ‘ +
lexer_count + ‘ / ‘ + reduce_count);
}return result;
}
var xpathParseCache = {};function xpathCacheLookup(expr) {return xpathParseCache[expr];
}function xpathReduce(stack, ahead) {
var cand = null;if (stack.length > 0) {
var top = stack[stack.length-1];
var ruleset = xpathRules[top.tag.key];if (ruleset) {for (var i = 0; i < ruleset.length; ++i) {
var rule = ruleset[i];
var match = xpathMatchStack(stack, rule[1]);if (match.length) {
cand = {
tag: rule[0],
rule: rule,
match: match
};
cand.prec = xpathGrammarPrecedence(cand);
break;
}
}
}
}
var ret;if (cand && (!ahead || cand.prec > ahead.prec ||
(ahead.tag.left && cand.prec >= ahead.prec))) {for (var i = 0; i < cand.match.matchlength; ++i) {
stack.pop();
}if (xpathdebug) {
Log.write(‘reduce ‘ + cand.tag.label + ‘ ‘ + cand.prec +‘ ahead ‘ + (ahead ? ahead.tag.label + ‘ ‘ + ahead.prec +
(ahead.tag.left ? ‘ left‘ : ‘‘)
: ‘ none ‘));
}
var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
cand.expr = cand.rule[3].apply(null, matchexpr);
stack.push(cand);
ret = true;
} else {if (ahead) {if (xpathdebug) {
Log.write(‘shift ‘ + ahead.tag.label + ‘ ‘ + ahead.prec +
(ahead.tag.left ? ‘ left‘ : ‘‘) +‘ over ‘ + (cand ? cand.tag.label + ‘ ‘ +
cand.prec : ‘ none‘));
}
stack.push(ahead);
}
ret = false;
}return ret;
}function xpathMatchStack(stack, pattern) {// NOTE(mesch): The stack matches for variable cardinality are// greedy but don‘t do backtracking. This would be an issue only// with rules of the form A* A, i.e. with an element with variable// cardinality followed by the same element. Since that doesn‘t// occur in the grammar at hand, all matches on the stack are// unambiguous.var S = stack.length;
var P = pattern.length;
var p, s;
var match = [];
match.matchlength = 0;
var ds = 0;for (p = P - 1, s = S - 1; p >= 0 && s >= 0; --p, s -= ds) {
ds = 0;
var qmatch = [];if (pattern[p] == Q_MM) {
p -= 1;
match.push(qmatch);while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
qmatch.push(stack[s - ds]);
ds += 1;
match.matchlength += 1;
}
} else if (pattern[p] == Q_01) {
p -= 1;
match.push(qmatch);while (s - ds >= 0 && ds < 2 && stack[s - ds].tag == pattern[p]) {
qmatch.push(stack[s - ds]);
ds += 1;
match.matchlength += 1;
}
} else if (pattern[p] == Q_1M) {
p -= 1;
match.push(qmatch);if (stack[s].tag == pattern[p]) {while (s - ds >= 0 && stack[s - ds].tag == pattern[p]) {
qmatch.push(stack[s - ds]);
ds += 1;
match.matchlength += 1;
}
} else {return [];
}
} else if (stack[s].tag == pattern[p]) {
match.push(stack[s]);
ds += 1;
match.matchlength += 1;
} else {return [];
}
reverseInplace(qmatch);
qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });
}
reverseInplace(match);if (p == -1) {return match;
} else {return [];
}
}function xpathTokenPrecedence(tag) {return tag.prec || 2;
}function xpathGrammarPrecedence(frame) {
var ret = 0;if (frame.rule) { /* normal reduce */if (frame.rule.length >= 3 && frame.rule[2] >= 0) {
ret = frame.rule[2];
} else {for (var i = 0; i < frame.rule[1].length; ++i) {
var p = xpathTokenPrecedence(frame.rule[1][i]);
ret = Math.max(ret, p);
}
}
} else if (frame.tag) { /* TOKEN match */ret = xpathTokenPrecedence(frame.tag);
} else if (frame.length) { /* Q_ match */for (var j = 0; j < frame.length; ++j) {
var p = xpathGrammarPrecedence(frame[j]);
ret = Math.max(ret, p);
}
}return ret;
}function stackToString(stack) {
var ret = ‘‘;for (var i = 0; i < stack.length; ++i) {if (ret) {
ret += ‘\n‘;
}
ret += stack[i].tag.label;
}return ret;
}// XPath expression evaluation context. An XPath context consists of a// DOM node, a list of DOM nodes that contains this node, a number// that represents the position of the single node in the list, and a// current set of variable bindings. (See XPath spec.)//// The interface of the expression context:////   Constructor -- gets the node, its position, the node set it//   belongs to, and a parent context as arguments. The parent context//   is used to implement scoping rules for variables: if a variable//   is not found in the current context, it is looked for in the//   parent context, recursively. Except for node, all arguments have//   default values: default position is 0, default node set is the//   set that contains only the node, and the default parent is null.////     Notice that position starts at 0 at the outside interface;//     inside XPath expressions this shows up as position()=1.////   clone() -- creates a new context with the current context as//   parent. If passed as argument to clone(), the new context has a//   different node, position, or node set. What is not passed is//   inherited from the cloned context.////   setVariable(name, expr) -- binds given XPath expression to the//   name.////   getVariable(name) -- what the name says.////   setNode(node, position) -- sets the context to the new node and//   its corresponding position. Needed to implement scoping rules for//   variables in XPath. (A variable is visible to all subsequent//   siblings, not only to its children.)function ExprContext(node, position, nodelist, parent) {this.node = node;this.position = position || 0;this.nodelist = nodelist || [ node ];this.variables = {};this.parent = parent || null;this.root = parent ? parent.root : node.ownerDocument;
}
ExprContext.prototype.clone = function(node, position, nodelist) {return new
ExprContext(node || this.node,
typeof position != ‘undefined‘ ? position : this.position,
nodelist || this.nodelist, this);
};
ExprContext.prototype.setVariable = function(name, value) {this.variables[name] = value;
};
ExprContext.prototype.getVariable = function(name) {if (typeof this.variables[name] != ‘undefined‘) {return this.variables[name];
} else if (this.parent) {return this.parent.getVariable(name);
} else {return null;
}
}
ExprContext.prototype.setNode = function(node, position) {this.node = node;this.position = position;
}// XPath expression values. They are what XPath expressions evaluate// to. Strangely, the different value types are not specified in the// XPath syntax, but only in the semantics, so they don‘t show up as// nonterminals in the grammar. Yet, some expressions are required to// evaluate to particular types, and not every type can be coerced// into every other type. Although the types of XPath values are// similar to the types present in JavaScript, the type coercion rules// are a bit peculiar, so we explicitly model XPath types instead of// mapping them onto JavaScript types. (See XPath spec.)//// The four types are:////   StringValue////   NumberValue////   BooleanValue////   NodeSetValue//// The common interface of the value classes consists of methods that// implement the XPath type coercion rules:////   stringValue() -- returns the value as a JavaScript String,////   numberValue() -- returns the value as a JavaScript Number,////   booleanValue() -- returns the value as a JavaScript Boolean,////   nodeSetValue() -- returns the value as a JavaScript Array of DOM//   Node objects.//function StringValue(value) {this.value = value;this.type = ‘string‘;
}
StringValue.prototype.stringValue = function() {return this.value;
}
StringValue.prototype.booleanValue = function() {return this.value.length > 0;
}
StringValue.prototype.numberValue = function() {return this.value - 0;
}
StringValue.prototype.nodeSetValue = function() {
throw this + ‘ ‘ + Error().stack;
}function BooleanValue(value) {this.value = value;this.type = ‘boolean‘;
}
BooleanValue.prototype.stringValue = function() {return ‘‘ + this.value;
}
BooleanValue.prototype.booleanValue = function() {return this.value;
}
BooleanValue.prototype.numberValue = function() {return this.value ? 1 : 0;
}
BooleanValue.prototype.nodeSetValue = function() {
throw this + ‘ ‘ + Error().stack;
}function NumberValue(value) {this.value = value;this.type = ‘number‘;
}
NumberValue.prototype.stringValue = function() {return ‘‘ + this.value;
}
NumberValue.prototype.booleanValue = function() {return !!this.value;
}
NumberValue.prototype.numberValue = function() {return this.value - 0;
}
NumberValue.prototype.nodeSetValue = function() {
throw this + ‘ ‘ + Error().stack;
}function NodeSetValue(value) {this.value = value;this.type = ‘node-set‘;
}
NodeSetValue.prototype.stringValue = function() {if (this.value.length == 0) {return ‘‘;
} else {return xmlValue(this.value[0]);
}
}
NodeSetValue.prototype.booleanValue = function() {return this.value.length > 0;
}
NodeSetValue.prototype.numberValue = function() {return this.stringValue() - 0;
}
NodeSetValue.prototype.nodeSetValue = function() {return this.value;
};// XPath expressions. They are used as nodes in the parse tree and// possess an evaluate() method to compute an XPath value given an XPath// context. Expressions are returned from the parser. Teh set of// expression classes closely mirrors the set of non terminal symbols// in the grammar. Every non trivial nonterminal symbol has a// corresponding expression class.//// The common expression interface consists of the following methods://// evaluate(context) -- evaluates the expression, returns a value.//// toString() -- returns the XPath text representation of the// expression (defined in xsltdebug.js).//// parseTree(indent) -- returns a parse tree representation of the// expression (defined in xsltdebug.js).function TokenExpr(m) {this.value = m;
}
TokenExpr.prototype.evaluate = function() {return new StringValue(this.value);
};function LocationExpr() {this.absolute = false;this.steps = [];
}
LocationExpr.prototype.appendStep = function(s) {this.steps.push(s);
}
LocationExpr.prototype.prependStep = function(s) {
var steps0 = this.steps;this.steps = [ s ];for (var i = 0; i < steps0.length; ++i) {this.steps.push(steps0[i]);
}
};
LocationExpr.prototype.evaluate = function(ctx) {
var start;if (this.absolute) {
start = ctx.root;
} else {
start = ctx.node;
}
var nodes = [];
xPathStep(nodes, this.steps, 0, start, ctx);return new NodeSetValue(nodes);
};function xPathStep(nodes, steps, step, input, ctx) {
var s = steps[step];
var ctx2 = ctx.clone(input);
var nodelist = s.evaluate(ctx2).nodeSetValue();for (var i = 0; i < nodelist.length; ++i) {if (step == steps.length - 1) {
nodes.push(nodelist[i]);
} else {
xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
}
}
}function StepExpr(axis, nodetest, predicate) {this.axis = axis;this.nodetest = nodetest;this.predicate = predicate || [];
}
StepExpr.prototype.appendPredicate = function(p) {this.predicate.push(p);
}
StepExpr.prototype.evaluate = function(ctx) {
var input = ctx.node;
var nodelist = [];// NOTE(mesch): When this was a switch() statement, it didn‘t work// in Safari/2.0. Not sure why though; it resulted in the JavaScript// console output "undefined" (without any line number or so).if (this.axis ==  xpathAxis.ANCESTOR_OR_SELF) {
nodelist.push(input);for (var n = input.parentNode; n; n = input.parentNode) {
nodelist.push(n);
}
} else if (this.axis == xpathAxis.ANCESTOR) {for (var n = input.parentNode; n; n = input.parentNode) {
nodelist.push(n);
}
} else if (this.axis == xpathAxis.ATTRIBUTE) {
copyArray(nodelist, input.attributes);
} else if (this.axis == xpathAxis.CHILD) {
copyArray(nodelist, input.childNodes);
} else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
nodelist.push(input);
xpathCollectDescendants(nodelist, input);
} else if (this.axis == xpathAxis.DESCENDANT) {
xpathCollectDescendants(nodelist, input);
} else if (this.axis == xpathAxis.FOLLOWING) {for (var n = input.parentNode; n; n = n.parentNode) {for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
nodelist.push(nn);
xpathCollectDescendants(nodelist, nn);
}
}
} else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {for (var n = input.nextSibling; n; n = input.nextSibling) {
nodelist.push(n);
}
} else if (this.axis == xpathAxis.NAMESPACE) {
alert(‘not implemented: axis namespace‘);
} else if (this.axis == xpathAxis.PARENT) {if (input.parentNode) {
nodelist.push(input.parentNode);
}
} else if (this.axis == xpathAxis.PRECEDING) {for (var n = input.parentNode; n; n = n.parentNode) {for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
nodelist.push(nn);
xpathCollectDescendantsReverse(nodelist, nn);
}
}
} else if (this.axis == xpathAxis.PRECEDING_SIBLING) {for (var n = input.previousSibling; n; n = input.previousSibling) {
nodelist.push(n);
}
} else if (this.axis == xpathAxis.SELF) {
nodelist.push(input);
} else {
throw ‘ERROR -- NO SUCH AXIS: ‘ + this.axis;
}// process node testvar nodelist0 = nodelist;
nodelist = [];for (var i = 0; i < nodelist0.length; ++i) {
var n = nodelist0[i];if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {
nodelist.push(n);
}
}// process predicatesfor (var i = 0; i < this.predicate.length; ++i) {
var nodelist0 = nodelist;
nodelist = [];for (var ii = 0; ii < nodelist0.length; ++ii) {
var n = nodelist0[ii];if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {
nodelist.push(n);
}
}
}return new NodeSetValue(nodelist);
};function NodeTestAny() {this.value = new BooleanValue(true);
}
NodeTestAny.prototype.evaluate = function(ctx) {return this.value;
};function NodeTestElement() {}
NodeTestElement.prototype.evaluate = function(ctx) {return new BooleanValue(ctx.node.nodeType == DOM_ELEMENT_NODE);
}function NodeTestText() {}
NodeTestText.prototype.evaluate = function(ctx) {return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);
}function NodeTestComment() {}
NodeTestComment.prototype.evaluate = function(ctx) {return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);
}function NodeTestPI(target) {this.target = target;
}
NodeTestPI.prototype.evaluate = function(ctx) {return new
BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &&
(!this.target || ctx.node.nodeName == this.target));
}function NodeTestNC(nsprefix) {this.regex = new RegExp("^" + nsprefix + ":");this.nsprefix = nsprefix;
}
NodeTestNC.prototype.evaluate = function(ctx) {
var n = ctx.node;return new BooleanValue(this.regex.match(n.nodeName));
}function NodeTestName(name) {this.name = name;
}
NodeTestName.prototype.evaluate = function(ctx) {
var n = ctx.node;return new BooleanValue(n.nodeName == this.name);
}function PredicateExpr(expr) {this.expr = expr;
}
PredicateExpr.prototype.evaluate = function(ctx) {
var v = this.expr.evaluate(ctx);if (v.type == ‘number‘) {// NOTE(mesch): Internally, position is represented starting with// 0, however in XPath position starts with 1. See functions// position() and last().return new BooleanValue(ctx.position == v.numberValue() - 1);
} else {return new BooleanValue(v.booleanValue());
}
};function FunctionCallExpr(name) {this.name = name;this.args = [];
}
FunctionCallExpr.prototype.appendArg = function(arg) {this.args.push(arg);
};
FunctionCallExpr.prototype.evaluate = function(ctx) {
var fn = ‘‘ + this.name.value;
var f = this.xpathfunctions[fn];if (f) {return f.call(this, ctx);
} else {
Log.write(‘XPath NO SUCH FUNCTION ‘ + fn);return new BooleanValue(false);
}
};
FunctionCallExpr.prototype.xpathfunctions = {‘last‘: function(ctx) {
assert(this.args.length == 0);// NOTE(mesch): XPath position starts at 1.return new NumberValue(ctx.nodelist.length);
},‘position‘: function(ctx) {
assert(this.args.length == 0);// NOTE(mesch): XPath position starts at 1.return new NumberValue(ctx.position + 1);
},‘count‘: function(ctx) {
assert(this.args.length == 1);
var v = this.args[0].evaluate(ctx);return new NumberValue(v.nodeSetValue().length);
},‘id‘: function(ctx) {
assert(this.args.length == 1);
var e = this.args.evaluate(ctx);
var ret = [];
var ids;if (e.type == ‘node-set‘) {
ids = [];for (var i = 0; i < e.length; ++i) {
var v = xmlValue(e[i]).split(/\s+/);for (var ii = 0; ii < v.length; ++ii) {
ids.push(v[ii]);
}
}
} else {
ids = e.split(/\s+/);
}
var d = ctx.node.ownerDocument;for (var i = 0; i < ids.length; ++i) {
var n = d.getElementById(ids[i]);if (n) {
ret.push(n);
}
}return new NodeSetValue(ret);
},‘local-name‘: function(ctx) {
alert(‘not implmented yet: XPath function local-name()‘);
},‘namespace-uri‘: function(ctx) {
alert(‘not implmented yet: XPath function namespace-uri()‘);
},‘name‘: function(ctx) {
assert(this.args.length == 1 || this.args.length == 0);
var n;if (this.args.length == 0) {
n = [ ctx.node ];
} else {
n = this.args[0].evaluate(ctx).nodeSetValue();
}if (n.length == 0) {return new StringValue(‘‘);
} else {return new StringValue(n[0].nodeName);
}
},‘string‘:  function(ctx) {
assert(this.args.length == 1 || this.args.length == 0);if (this.args.length == 0) {return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());
} else {return new StringValue(this.args[0].evaluate(ctx).stringValue());
}
},‘concat‘: function(ctx) {
var ret = ‘‘;for (var i = 0; i < this.args.length; ++i) {
ret += this.args[i].evaluate(ctx).stringValue();
}return new StringValue(ret);
},‘starts-with‘: function(ctx) {
assert(this.args.length == 2);
var s0 = this.args[0].evaluate(ctx).stringValue();
var s1 = this.args[1].evaluate(ctx).stringValue();return new BooleanValue(s0.indexOf(s1) == 0);
},‘contains‘: function(ctx) {
assert(this.args.length == 2);
var s0 = this.args[0].evaluate(ctx).stringValue();
var s1 = this.args[1].evaluate(ctx).stringValue();return new BooleanValue(s0.indexOf(s1) != -1);
},‘substring-before‘: function(ctx) {
assert(this.args.length == 2);
var s0 = this.args[0].evaluate(ctx).stringValue();
var s1 = this.args[1].evaluate(ctx).stringValue();
var i = s0.indexOf(s1);
var ret;if (i == -1) {
ret = ‘‘;
} else {
ret = s0.substr(0,i);
}return new StringValue(ret);
},‘substring-after‘: function(ctx) {
assert(this.args.length == 2);
var s0 = this.args[0].evaluate(ctx).stringValue();
var s1 = this.args[1].evaluate(ctx).stringValue();
var i = s0.indexOf(s1);
var ret;if (i == -1) {
ret = ‘‘;
} else {
ret = s0.substr(i + s1.length);
}return new StringValue(ret);
},‘substring‘: function(ctx) {// NOTE: XPath defines the position of the first character in a// string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).assert(this.args.length == 2 || this.args.length == 3);
var s0 = this.args[0].evaluate(ctx).stringValue();
var s1 = this.args[1].evaluate(ctx).numberValue();
var ret;if (this.args.length == 2) {
var i1 = Math.max(0, Math.round(s1) - 1);
ret = s0.substr(i1);
} else {
var s2 = this.args[2].evaluate(ctx).numberValue();
var i0 = Math.round(s1) - 1;
var i1 = Math.max(0, i0);
var i2 = Math.round(s2) - Math.max(0, -i0);
ret = s0.substr(i1, i2);
}return new StringValue(ret);
},‘string-length‘: function(ctx) {
var s;if (this.args.length > 0) {
s = this.args[0].evaluate(ctx).stringValue();
} els#

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约