📄 xpath.js
字号:
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 test
var 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 predicates
for (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();
} else {
s = new NodeSetValue([ ctx.node ]).stringValue();
}
return new NumberValue(s.length);
},
'normalize-space': function(ctx) {
var s;
if (this.args.length > 0) {
s = this.args[0].evaluate(ctx).stringValue();
} else {
s = new NodeSetValue([ ctx.node ]).stringValue();
}
s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');
return new StringValue(s);
},
'translate': function(ctx) {
assert(this.args.length == 3);
var s0 = this.args[0].evaluate(ctx).stringValue();
var s1 = this.args[1].evaluate(ctx).stringValue();
var s2 = this.args[2].evaluate(ctx).stringValue();
for (var i = 0; i < s1.length; ++i) {
s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));
}
return new StringValue(s0);
},
'boolean': function(ctx) {
assert(this.args.length == 1);
return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
},
'not': function(ctx) {
assert(this.args.length == 1);
var ret = !this.args[0].evaluate(ctx).booleanValue();
return new BooleanValue(ret);
},
'true': function(ctx) {
assert(this.args.length == 0);
return new BooleanValue(true);
},
'false': function(ctx) {
assert(this.args.length == 0);
return new BooleanValue(false);
},
'lang': function(ctx) {
assert(this.args.length == 1);
var lang = this.args[0].evaluate(ctx).stringValue();
var xmllang;
var n = ctx.node;
while (n && n != n.parentNode /* just in case ... */) {
xmllang = n.getAttribute('xml:lang');
if (xmllang) {
break;
}
n = n.parentNode;
}
if (!xmllang) {
return new BooleanValue(false);
} else {
var re = new RegExp('^' + lang + '$', 'i');
return new BooleanValue(xmllang.match(re) ||
xmllang.replace(/_.*$/,'').match(re));
}
},
'number': function(ctx) {
assert(this.args.length == 1 || this.args.length == 0);
if (this.args.length == 1) {
return new NumberValue(this.args[0].evaluate(ctx).numberValue());
} else {
return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());
}
},
'sum': function(ctx) {
assert(this.args.length == 1);
var n = this.args[0].evaluate(ctx).nodeSetValue();
var sum = 0;
for (var i = 0; i < n.length; ++i) {
sum += xmlValue(n[i]) - 0;
}
return new NumberValue(sum);
},
'floor': function(ctx) {
assert(this.args.length == 1);
var num = this.args[0].evaluate(ctx).numberValue();
return new NumberValue(Math.floor(num));
},
'ceiling': function(ctx) {
assert(this.args.length == 1);
var num = this.args[0].evaluate(ctx).numberValue();
return new NumberValue(Math.ceil(num));
},
'round': function(ctx) {
assert(this.args.length == 1);
var num = this.args[0].evaluate(ctx).numberValue();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -