| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859 |
- /** Here is yet another implementation of XPath 1.0 in Javascript.
- *
- * My goal was to make it relatively compact, but as I fixed all the axis bugs
- * the axes became more and more complicated. :-(.
- *
- * I have not implemented namespaces or case-sensitive axes for XML yet.
- *
- * How to test it in Chrome: You can make a Chrome extension that replaces
- * the WebKit XPath parser with this one. But it takes a bit of effort to
- * get around isolated world and same-origin restrictions:
- * manifest.json:
- {
- "name": "XPathTest",
- "version": "0.1",
- "content_scripts": [{
- "matches": ["http://localhost/*"], // or wildcard host
- "js": ["xpath.js", "injection.js"],
- "all_frames": true, "run_at": "document_start"
- }]
- }
- * injection.js:
- // goal: give my xpath object to the website's JS context.
- var script = document.createElement('script');
- script.textContent =
- "document.addEventListener('xpathextend', function(e) {\n" +
- " console.log('extending document with xpath...');\n" +
- " e.detail(window);" +
- "});";
- document.documentElement.appendChild(script);
- document.documentElement.removeChild(script);
- var evt = document.createEvent('CustomEvent');
- evt.initCustomEvent('xpathextend', true, true, this.xpath.extend);
- document.dispatchEvent(evt);
- */
- (function() {
- var core;
- var xpath;
- if ('function' === typeof require) {
- core = require("../level3/core").dom.level3.core;
- xpath = exports;
- } else {
- core = this;
- xpath = {};
- }
- /***************************************************************************
- * Tokenization *
- ***************************************************************************/
- /**
- * The XPath lexer is basically a single regular expression, along with
- * some helper functions to pop different types.
- */
- var Stream = xpath.Stream = function Stream(str) {
- this.original = this.str = str;
- this.peeked = null;
- // TODO: not really needed, but supposedly tokenizer also disambiguates
- // a * b vs. node test *
- this.prev = null; // for debugging
- this.prevprev = null;
- }
- Stream.prototype = {
- peek: function() {
- if (this.peeked) return this.peeked;
- var m = this.re.exec(this.str);
- if (!m) return null;
- this.str = this.str.substr(m[0].length);
- return this.peeked = m[1];
- },
- /** Peek 2 tokens ahead. */
- peek2: function() {
- this.peek(); // make sure this.peeked is set
- var m = this.re.exec(this.str);
- if (!m) return null;
- return m[1];
- },
- pop: function() {
- var r = this.peek();
- this.peeked = null;
- this.prevprev = this.prev;
- this.prev = r;
- return r;
- },
- trypop: function(tokens) {
- var tok = this.peek();
- if (tok === tokens) return this.pop();
- if (Array.isArray(tokens)) {
- for (var i = 0; i < tokens.length; ++i) {
- var t = tokens[i];
- if (t == tok) return this.pop();;
- }
- }
- },
- trypopfuncname: function() {
- var tok = this.peek();
- if (!this.isQnameRe.test(tok))
- return null;
- switch (tok) {
- case 'comment': case 'text': case 'processing-instruction': case 'node':
- return null;
- }
- if ('(' != this.peek2()) return null;
- return this.pop();
- },
- trypopaxisname: function() {
- var tok = this.peek();
- switch (tok) {
- case 'ancestor': case 'ancestor-or-self': case 'attribute':
- case 'child': case 'descendant': case 'descendant-or-self':
- case 'following': case 'following-sibling': case 'namespace':
- case 'parent': case 'preceding': case 'preceding-sibling': case 'self':
- if ('::' == this.peek2()) return this.pop();
- }
- return null;
- },
- trypopnametest: function() {
- var tok = this.peek();
- if ('*' === tok || this.startsWithNcNameRe.test(tok)) return this.pop();
- return null;
- },
- trypopliteral: function() {
- var tok = this.peek();
- if (null == tok) return null;
- var first = tok.charAt(0);
- var last = tok.charAt(tok.length - 1);
- if ('"' === first && '"' === last ||
- "'" === first && "'" === last) {
- this.pop();
- return tok.substr(1, tok.length - 2);
- }
- },
- trypopnumber: function() {
- var tok = this.peek();
- if (this.isNumberRe.test(tok)) return parseFloat(this.pop());
- else return null;
- },
- trypopvarref: function() {
- var tok = this.peek();
- if (null == tok) return null;
- if ('$' === tok.charAt(0)) return this.pop().substr(1);
- else return null;
- },
- position: function() {
- return this.original.length - this.str.length;
- }
- };
- (function() {
- // http://www.w3.org/TR/REC-xml-names/#NT-NCName
- var nameStartCharsExceptColon =
- 'A-Z_a-z\xc0-\xd6\xd8-\xf6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF' +
- '\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF' +
- '\uFDF0-\uFFFD'; // JS doesn't support [#x10000-#xEFFFF]
- var nameCharExceptColon = nameStartCharsExceptColon +
- '\\-\\.0-9\xb7\u0300-\u036F\u203F-\u2040';
- var ncNameChars = '[' + nameStartCharsExceptColon +
- '][' + nameCharExceptColon + ']*'
- // http://www.w3.org/TR/REC-xml-names/#NT-QName
- var qNameChars = ncNameChars + '(?::' + ncNameChars + ')?';
- var otherChars = '\\.\\.|[\\(\\)\\[\\].@,]|::'; // .. must come before [.]
- var operatorChars =
- 'and|or|mod|div|' +
- '//|!=|<=|>=|[*/|+\\-=<>]'; // //, !=, <=, >= before individual ones.
- var literal = '"[^"]*"|' + "'[^']*'";
- var numberChars = '[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+';
- var variableReference = '\\$' + qNameChars;
- var nameTestChars = '\\*|' + ncNameChars + ':\\*|' + qNameChars;
- var optionalSpace = '[ \t\r\n]*'; // stricter than regexp \s.
- var nodeType = 'comment|text|processing-instruction|node';
- var re = new RegExp(
- // numberChars before otherChars so that leading-decimal doesn't become .
- '^' + optionalSpace + '(' + numberChars + '|' + otherChars + '|' +
- nameTestChars + '|' + operatorChars + '|' + literal + '|' +
- variableReference + ')'
- // operatorName | nodeType | functionName | axisName are lumped into
- // qName for now; we'll check them on pop.
- );
- Stream.prototype.re = re;
- Stream.prototype.startsWithNcNameRe = new RegExp('^' + ncNameChars);
- Stream.prototype.isQnameRe = new RegExp('^' + qNameChars + '$');
- Stream.prototype.isNumberRe = new RegExp('^' + numberChars + '$');
- })();
- /***************************************************************************
- * Parsing *
- ***************************************************************************/
- var parse = xpath.parse = function parse(stream, a) {
- var r = orExpr(stream,a);
- var x, unparsed = [];
- while (x = stream.pop()) {
- unparsed.push(x);
- }
- if (unparsed.length)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Unparsed tokens: ' + unparsed.join(' '));
- return r;
- }
- /**
- * binaryL ::= subExpr
- * | binaryL op subExpr
- * so a op b op c becomes ((a op b) op c)
- */
- function binaryL(subExpr, stream, a, ops) {
- var lhs = subExpr(stream, a);
- if (lhs == null) return null;
- var op;
- while (op = stream.trypop(ops)) {
- var rhs = subExpr(stream, a);
- if (rhs == null)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected something after ' + op);
- lhs = a.node(op, lhs, rhs);
- }
- return lhs;
- }
- /**
- * Too bad this is never used. If they made a ** operator (raise to power),
- ( we would use it.
- * binaryR ::= subExpr
- * | subExpr op binaryR
- * so a op b op c becomes (a op (b op c))
- */
- function binaryR(subExpr, stream, a, ops) {
- var lhs = subExpr(stream, a);
- if (lhs == null) return null;
- var op = stream.trypop(ops);
- if (op) {
- var rhs = binaryR(stream, a);
- if (rhs == null)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected something after ' + op);
- return a.node(op, lhs, rhs);
- } else {
- return lhs;// TODO
- }
- }
- /** [1] LocationPath::= RelativeLocationPath | AbsoluteLocationPath
- * e.g. a, a/b, //a/b
- */
- function locationPath(stream, a) {
- return absoluteLocationPath(stream, a) ||
- relativeLocationPath(null, stream, a);
- }
- /** [2] AbsoluteLocationPath::= '/' RelativeLocationPath? | AbbreviatedAbsoluteLocationPath
- * [10] AbbreviatedAbsoluteLocationPath::= '//' RelativeLocationPath
- */
- function absoluteLocationPath(stream, a) {
- var op = stream.peek();
- if ('/' === op || '//' === op) {
- var lhs = a.node('Root');
- return relativeLocationPath(lhs, stream, a, true);
- } else {
- return null;
- }
- }
- /** [3] RelativeLocationPath::= Step | RelativeLocationPath '/' Step |
- * | AbbreviatedRelativeLocationPath
- * [11] AbbreviatedRelativeLocationPath::= RelativeLocationPath '//' Step
- * e.g. p/a, etc.
- */
- function relativeLocationPath(lhs, stream, a, isOnlyRootOk) {
- if (null == lhs) {
- lhs = step(stream, a);
- if (null == lhs) return lhs;
- }
- var op;
- while (op = stream.trypop(['/', '//'])) {
- if ('//' === op) {
- lhs = a.node('/', lhs,
- a.node('Axis', 'descendant-or-self', 'node', undefined));
- }
- var rhs = step(stream, a);
- if (null == rhs && '/' === op && isOnlyRootOk) return lhs;
- else isOnlyRootOk = false;
- if (null == rhs)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected step after ' + op);
- lhs = a.node('/', lhs, rhs);
- }
- return lhs;
- }
- /** [4] Step::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
- * [12] AbbreviatedStep::= '.' | '..'
- * e.g. @href, self::p, p, a[@href], ., ..
- */
- function step(stream, a) {
- var abbrStep = stream.trypop(['.', '..']);
- if ('.' === abbrStep) // A location step of . is short for self::node().
- return a.node('Axis', 'self', 'node');
- if ('..' === abbrStep) // A location step of .. is short for parent::node()
- return a.node('Axis', 'parent', 'node');
- var axis = axisSpecifier(stream, a);
- var nodeType = nodeTypeTest(stream, a);
- var nodeName;
- if (null == nodeType) nodeName = nodeNameTest(stream, a);
- if (null == axis && null == nodeType && null == nodeName) return null;
- if (null == nodeType && null == nodeName)
- throw new XPathException(
- XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected nodeTest after axisSpecifier ' + axis);
- if (null == axis) axis = 'child';
- if (null == nodeType) {
- // When there's only a node name, then the node type is forced to be the
- // principal node type of the axis.
- // see http://www.w3.org/TR/xpath/#dt-principal-node-type
- if ('attribute' === axis) nodeType = 'attribute';
- else if ('namespace' === axis) nodeType = 'namespace';
- else nodeType = 'element';
- }
- var lhs = a.node('Axis', axis, nodeType, nodeName);
- var pred;
- while (null != (pred = predicate(lhs, stream, a))) {
- lhs = pred;
- }
- return lhs;
- }
- /** [5] AxisSpecifier::= AxisName '::' | AbbreviatedAxisSpecifier
- * [6] AxisName::= 'ancestor' | 'ancestor-or-self' | 'attribute' | 'child'
- * | 'descendant' | 'descendant-or-self' | 'following'
- * | 'following-sibling' | 'namespace' | 'parent' |
- * | 'preceding' | 'preceding-sibling' | 'self'
- * [13] AbbreviatedAxisSpecifier::= '@'?
- */
- function axisSpecifier(stream, a) {
- var attr = stream.trypop('@');
- if (null != attr) return 'attribute';
- var axisName = stream.trypopaxisname();
- if (null != axisName) {
- var coloncolon = stream.trypop('::');
- if (null == coloncolon)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Should not happen. Should be ::.');
- return axisName;
- }
- }
- /** [7] NodeTest::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')'
- * [38] NodeType::= 'comment' | 'text' | 'processing-instruction' | 'node'
- * I've split nodeTypeTest from nodeNameTest for convenience.
- */
- function nodeTypeTest(stream, a) {
- if ('(' !== stream.peek2()) {
- return null;
- }
- var type = stream.trypop(['comment', 'text', 'processing-instruction', 'node']);
- if (null != type) {
- if (null == stream.trypop('('))
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Should not happen.');
- var param = undefined;
- if (type == 'processing-instruction') {
- param = stream.trypopliteral();
- }
- if (null == stream.trypop(')'))
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected close parens.');
- return type
- }
- }
- function nodeNameTest(stream, a) {
- var name = stream.trypopnametest();
- if (name != null) return name;
- else return null;
- }
- /** [8] Predicate::= '[' PredicateExpr ']'
- * [9] PredicateExpr::= Expr
- */
- function predicate(lhs, stream, a) {
- if (null == stream.trypop('[')) return null;
- var expr = orExpr(stream, a);
- if (null == expr)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected expression after [');
- if (null == stream.trypop(']'))
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected ] after expression.');
- return a.node('Predicate', lhs, expr);
- }
- /** [14] Expr::= OrExpr
- */
- /** [15] PrimaryExpr::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
- * e.g. $x, (3+4), "hi", 32, f(x)
- */
- function primaryExpr(stream, a) {
- var x = stream.trypopliteral();
- if (null == x)
- x = stream.trypopnumber();
- if (null != x) {
- return x;
- }
- var varRef = stream.trypopvarref();
- if (null != varRef) return a.node('VariableReference', varRef);
- var funCall = functionCall(stream, a);
- if (null != funCall) {
- return funCall;
- }
- if (stream.trypop('(')) {
- var e = orExpr(stream, a);
- if (null == e)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected expression after (.');
- if (null == stream.trypop(')'))
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected ) after expression.');
- return e;
- }
- return null;
- }
- /** [16] FunctionCall::= FunctionName '(' ( Argument ( ',' Argument )* )? ')'
- * [17] Argument::= Expr
- */
- function functionCall(stream, a) {
- var name = stream.trypopfuncname(stream, a);
- if (null == name) return null;
- if (null == stream.trypop('('))
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected ( ) after function name.');
- var params = [];
- var first = true;
- while (null == stream.trypop(')')) {
- if (!first && null == stream.trypop(','))
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected , between arguments of the function.');
- first = false;
- var param = orExpr(stream, a);
- if (param == null)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected expression as argument of function.');
- params.push(param);
- }
- return a.node('FunctionCall', name, params);
- }
- /** [18] UnionExpr::= PathExpr | UnionExpr '|' PathExpr
- */
- function unionExpr(stream, a) { return binaryL(pathExpr, stream, a, '|'); }
- /** [19] PathExpr ::= LocationPath
- * | FilterExpr
- * | FilterExpr '/' RelativeLocationPath
- * | FilterExpr '//' RelativeLocationPath
- * Unlike most other nodes, this one always generates a node because
- * at this point all reverse nodesets must turn into a forward nodeset
- */
- function pathExpr(stream, a) {
- // We have to do FilterExpr before LocationPath because otherwise
- // LocationPath will eat up the name from a function call.
- var filter = filterExpr(stream, a);
- if (null == filter) {
- var loc = locationPath(stream, a);
- if (null == loc) {
- throw new Error
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': The expression shouldn\'t be empty...');
- }
- return a.node('PathExpr', loc);
- }
- var rel = relativeLocationPath(filter, stream, a, false);
- if (filter === rel) return rel;
- else return a.node('PathExpr', rel);
- }
- /** [20] FilterExpr::= PrimaryExpr | FilterExpr Predicate
- * aka. FilterExpr ::= PrimaryExpr Predicate*
- */
- function filterExpr(stream, a) {
- var primary = primaryExpr(stream, a);
- if (primary == null) return null;
- var pred, lhs = primary;
- while (null != (pred = predicate(lhs, stream, a))) {
- lhs = pred;
- }
- return lhs;
- }
- /** [21] OrExpr::= AndExpr | OrExpr 'or' AndExpr
- */
- function orExpr(stream, a) {
- var orig = (stream.peeked || '') + stream.str
- var r = binaryL(andExpr, stream, a, 'or');
- var now = (stream.peeked || '') + stream.str;
- return r;
- }
- /** [22] AndExpr::= EqualityExpr | AndExpr 'and' EqualityExpr
- */
- function andExpr(stream, a) { return binaryL(equalityExpr, stream, a, 'and'); }
- /** [23] EqualityExpr::= RelationalExpr | EqualityExpr '=' RelationalExpr
- * | EqualityExpr '!=' RelationalExpr
- */
- function equalityExpr(stream, a) { return binaryL(relationalExpr, stream, a, ['=','!=']); }
- /** [24] RelationalExpr::= AdditiveExpr | RelationalExpr '<' AdditiveExpr
- * | RelationalExpr '>' AdditiveExpr
- * | RelationalExpr '<=' AdditiveExpr
- * | RelationalExpr '>=' AdditiveExpr
- */
- function relationalExpr(stream, a) { return binaryL(additiveExpr, stream, a, ['<','>','<=','>=']); }
- /** [25] AdditiveExpr::= MultiplicativeExpr
- * | AdditiveExpr '+' MultiplicativeExpr
- * | AdditiveExpr '-' MultiplicativeExpr
- */
- function additiveExpr(stream, a) { return binaryL(multiplicativeExpr, stream, a, ['+','-']); }
- /** [26] MultiplicativeExpr::= UnaryExpr
- * | MultiplicativeExpr MultiplyOperator UnaryExpr
- * | MultiplicativeExpr 'div' UnaryExpr
- * | MultiplicativeExpr 'mod' UnaryExpr
- */
- function multiplicativeExpr(stream, a) { return binaryL(unaryExpr, stream, a, ['*','div','mod']); }
- /** [27] UnaryExpr::= UnionExpr | '-' UnaryExpr
- */
- function unaryExpr(stream, a) {
- if (stream.trypop('-')) {
- var e = unaryExpr(stream, a);
- if (null == e)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Expected unary expression after -');
- return a.node('UnaryMinus', e);
- }
- else return unionExpr(stream, a);
- }
- var astFactory = {
- node: function() {return Array.prototype.slice.call(arguments);}
- };
- /***************************************************************************
- * Optimizations (TODO) *
- ***************************************************************************/
- /**
- * Some things I've been considering:
- * 1) a//b becomes a/descendant::b if there's no predicate that uses
- * position() or last()
- * 2) axis[pred]: when pred doesn't use position, evaluate it just once per
- * node in the node-set rather than once per (node, position, last).
- * For more optimizations, look up Gecko's optimizer:
- * http://mxr.mozilla.org/mozilla-central/source/content/xslt/src/xpath/txXPathOptimizer.cpp
- */
- // TODO
- function optimize(ast) {
- }
- /***************************************************************************
- * Evaluation: axes *
- ***************************************************************************/
- /**
- * Data types: For string, number, boolean, we just use Javascript types.
- * Node-sets have the form
- * {nodes: [node, ...]}
- * or {nodes: [node, ...], pos: [[1], [2], ...], lasts: [[1], [2], ...]}
- *
- * Most of the time, only the node is used and the position information is
- * discarded. But if you use a predicate, we need to try every value of
- * position and last in case the predicate calls position() or last().
- */
- /**
- * The NodeMultiSet is a helper class to help generate
- * {nodes:[], pos:[], lasts:[]} structures. It is useful for the
- * descendant, descendant-or-self, following-sibling, and
- * preceding-sibling axes for which we can use a stack to organize things.
- */
- function NodeMultiSet(isReverseAxis) {
- this.nodes = [];
- this.pos = [];
- this.lasts = [];
- this.nextPos = [];
- this.seriesIndexes = []; // index within nodes that each series begins.
- this.isReverseAxis = isReverseAxis;
- this._pushToNodes = isReverseAxis ? Array.prototype.unshift : Array.prototype.push;
- }
- NodeMultiSet.prototype = {
- pushSeries: function pushSeries() {
- this.nextPos.push(1);
- this.seriesIndexes.push(this.nodes.length);
- },
- popSeries: function popSeries() {
- console.assert(0 < this.nextPos.length, this.nextPos);
- var last = this.nextPos.pop() - 1,
- indexInPos = this.nextPos.length,
- seriesBeginIndex = this.seriesIndexes.pop(),
- seriesEndIndex = this.nodes.length;
- for (var i = seriesBeginIndex; i < seriesEndIndex; ++i) {
- console.assert(indexInPos < this.lasts[i].length);
- console.assert(undefined === this.lasts[i][indexInPos]);
- this.lasts[i][indexInPos] = last;
- }
- },
- finalize: function() {
- if (null == this.nextPos) return this;
- console.assert(0 === this.nextPos.length);
- for (var i = 0; i < this.lasts.length; ++i) {
- for (var j = 0; j < this.lasts[i].length; ++j) {
- console.assert(null != this.lasts[i][j], i + ',' + j + ':' + JSON.stringify(this.lasts));
- }
- }
- this.pushSeries = this.popSeries = this.addNode = function() {
- throw new Error('Already finalized.');
- };
- return this;
- },
- addNode: function addNode(node) {
- console.assert(node);
- this._pushToNodes.call(this.nodes, node)
- this._pushToNodes.call(this.pos, this.nextPos.slice());
- this._pushToNodes.call(this.lasts, new Array(this.nextPos.length));
- for (var i = 0; i < this.nextPos.length; ++i) this.nextPos[i]++;
- },
- simplify: function() {
- this.finalize();
- return {nodes:this.nodes, pos:this.pos, lasts:this.lasts};
- }
- };
- function eachContext(nodeMultiSet) {
- var r = [];
- for (var i = 0; i < nodeMultiSet.nodes.length; i++) {
- var node = nodeMultiSet.nodes[i];
- if (!nodeMultiSet.pos) {
- r.push({nodes:[node], pos: [[i + 1]], lasts: [[nodeMultiSet.nodes.length]]});
- } else {
- for (var j = 0; j < nodeMultiSet.pos[i].length; ++j) {
- r.push({nodes:[node], pos: [[nodeMultiSet.pos[i][j]]], lasts: [[nodeMultiSet.lasts[i][j]]]});
- }
- }
- }
- return r;
- }
- /** Matcher used in the axes.
- */
- function NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase) {
- this.nodeTypeNum = nodeTypeNum;
- this.nodeName = nodeName;
- this.shouldLowerCase = shouldLowerCase;
- this.nodeNameTest =
- null == nodeName ? this._alwaysTrue :
- shouldLowerCase ? this._nodeNameLowerCaseEquals :
- this._nodeNameEquals;
- }
- NodeMatcher.prototype = {
- matches: function matches(node) {
- return (0 === this.nodeTypeNum || node.nodeType === this.nodeTypeNum) &&
- this.nodeNameTest(node.nodeName);
- },
- _alwaysTrue: function(name) {return true;},
- _nodeNameEquals: function _nodeNameEquals(name) {
- return this.nodeName === name;
- },
- _nodeNameLowerCaseEquals: function _nodeNameLowerCaseEquals(name) {
- return this.nodeName === name.toLowerCase();
- }
- };
- function followingSiblingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, shift, peek, followingNode, andSelf, isReverseAxis) {
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var nodeMultiSet = new NodeMultiSet(isReverseAxis);
- while (0 < nodeList.length) { // can be if for following, preceding
- var node = shift.call(nodeList);
- console.assert(node != null);
- node = followingNode(node);
- nodeMultiSet.pushSeries();
- var numPushed = 1;
- while (null != node) {
- if (! andSelf && matcher.matches(node))
- nodeMultiSet.addNode(node);
- if (node === peek.call(nodeList)) {
- shift.call(nodeList);
- nodeMultiSet.pushSeries();
- numPushed++;
- }
- if (andSelf && matcher.matches(node))
- nodeMultiSet.addNode(node);
- node = followingNode(node);
- }
- while (0 < numPushed--)
- nodeMultiSet.popSeries();
- }
- return nodeMultiSet;
- }
- /** Returns the next non-descendant node in document order.
- * This is the first node in following::node(), if node is the context.
- */
- function followingNonDescendantNode(node) {
- if (node.ownerElement) {
- if (node.ownerElement.firstChild)
- return node.ownerElement.firstChild;
- node = node.ownerElement;
- }
- do {
- if (node.nextSibling) return node.nextSibling;
- } while (node = node.parentNode);
- return null;
- }
- /** Returns the next node in a document-order depth-first search.
- * See the definition of document order[1]:
- * 1) element
- * 2) namespace nodes
- * 3) attributes
- * 4) children
- * [1]: http://www.w3.org/TR/xpath/#dt-document-order
- */
- function followingNode(node) {
- if (node.ownerElement) // attributes: following node of element.
- node = node.ownerElement;
- if (null != node.firstChild)
- return node.firstChild;
- do {
- if (null != node.nextSibling) {
- return node.nextSibling;
- }
- node = node.parentNode;
- } while (node);
- return null;
- }
- /** Returns the previous node in document order (excluding attributes
- * and namespace nodes).
- */
- function precedingNode(node) {
- if (node.ownerElement)
- return node.ownerElement;
- if (null != node.previousSibling) {
- node = node.previousSibling;
- while (null != node.lastChild) {
- node = node.lastChild;
- }
- return node;
- }
- if (null != node.parentNode) {
- return node.parentNode;
- }
- return null;
- }
- /** This axis is inefficient if there are many nodes in the nodeList.
- * But I think it's a pretty useless axis so it's ok. */
- function followingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var nodeMultiSet = new NodeMultiSet(false);
- var cursor = nodeList[0];
- var unorderedFollowingStarts = [];
- for (var i = 0; i < nodeList.length; i++) {
- var node = nodeList[i];
- var start = followingNonDescendantNode(node);
- if (start)
- unorderedFollowingStarts.push(start);
- }
- if (0 === unorderedFollowingStarts.length)
- return {nodes:[]};
- var pos = [], nextPos = [];
- var started = 0;
- while (cursor = followingNode(cursor)) {
- for (var i = unorderedFollowingStarts.length - 1; i >= 0; i--){
- if (cursor === unorderedFollowingStarts[i]) {
- nodeMultiSet.pushSeries();
- unorderedFollowingStarts.splice(i,i+1);
- started++;
- }
- }
- if (started && matcher.matches(cursor)) {
- nodeMultiSet.addNode(cursor);
- }
- }
- console.assert(0 === unorderedFollowingStarts.length);
- for (var i = 0; i < started; i++)
- nodeMultiSet.popSeries();
- return nodeMultiSet.finalize();
- }
- function precedingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var cursor = nodeList.pop();
- if (null == cursor) return {nodes:{}};
- var r = {nodes:[], pos:[], lasts:[]};
- var nextParents = [cursor.parentNode || cursor.ownerElement], nextPos = [1];
- while (cursor = precedingNode(cursor)) {
- if (cursor === nodeList[nodeList.length - 1]) {
- nextParents.push(nodeList.pop());
- nextPos.push(1);
- }
- var matches = matcher.matches(cursor);
- var pos, someoneUsed = false;
- if (matches)
- pos = nextPos.slice();
- for (var i = 0; i < nextParents.length; ++i) {
- if (cursor === nextParents[i]) {
- nextParents[i] = cursor.parentNode || cursor.ownerElement;
- if (matches) {
- pos[i] = null;
- }
- } else {
- if (matches) {
- pos[i] = nextPos[i]++;
- someoneUsed = true;
- }
- }
- }
- if (someoneUsed) {
- r.nodes.unshift(cursor);
- r.pos.unshift(pos);
- }
- }
- for (var i = 0; i < r.pos.length; ++i) {
- var lasts = [];
- r.lasts.push(lasts);
- for (var j = r.pos[i].length - 1; j >= 0; j--) {
- if (null == r.pos[i][j]) {
- r.pos[i].splice(j, j+1);
- } else {
- lasts.unshift(nextPos[j] - 1);
- }
- }
- }
- return r;
- }
- /** node-set, axis -> node-set */
- function descendantDfs(nodeMultiSet, node, remaining, matcher, andSelf, attrIndices, attrNodes) {
- while (0 < remaining.length && null != remaining[0].ownerElement) {
- var attr = remaining.shift();
- if (andSelf && matcher.matches(attr)) {
- attrNodes.push(attr);
- attrIndices.push(nodeMultiSet.nodes.length);
- }
- }
- if (null != node && !andSelf) {
- if (matcher.matches(node))
- nodeMultiSet.addNode(node);
- }
- var pushed = false;
- if (null == node) {
- if (0 === remaining.length) return;
- node = remaining.shift();
- nodeMultiSet.pushSeries();
- pushed = true;
- } else if (0 < remaining.length && node === remaining[0]) {
- nodeMultiSet.pushSeries();
- pushed = true;
- remaining.shift();
- }
- if (andSelf) {
- if (matcher.matches(node))
- nodeMultiSet.addNode(node);
- }
- // TODO: use optimization. Also try element.getElementsByTagName
- // var nodeList = 1 === nodeTypeNum && null != node.children ? node.children : node.childNodes;
- var nodeList = node.childNodes;
- for (var j = 0; j < nodeList.length; ++j) {
- var child = nodeList[j];
- descendantDfs(nodeMultiSet, child, remaining, matcher, andSelf, attrIndices, attrNodes);
- }
- if (pushed) {
- nodeMultiSet.popSeries();
- }
- }
- function descenantHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, andSelf) {
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var nodeMultiSet = new NodeMultiSet(false);
- var attrIndices = [], attrNodes = [];
- while (0 < nodeList.length) {
- // var node = nodeList.shift();
- descendantDfs(nodeMultiSet, null, nodeList, matcher, andSelf, attrIndices, attrNodes);
- }
- nodeMultiSet.finalize();
- for (var i = attrNodes.length-1; i >= 0; --i) {
- nodeMultiSet.nodes.splice(attrIndices[i], attrIndices[i], attrNodes[i]);
- nodeMultiSet.pos.splice(attrIndices[i], attrIndices[i], [1]);
- nodeMultiSet.lasts.splice(attrIndices[i], attrIndices[i], [1]);
- }
- return nodeMultiSet;
- }
- /**
- */
- function ancestorHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, andSelf) {
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var ancestors = []; // array of non-empty arrays of matching ancestors
- for (var i = 0; i < nodeList.length; ++i) {
- var node = nodeList[i];
- var isFirst = true;
- var a = [];
- while (null != node) {
- if (!isFirst || andSelf) {
- if (matcher.matches(node))
- a.push(node);
- }
- isFirst = false;
- node = node.parentNode || node.ownerElement;
- }
- if (0 < a.length)
- ancestors.push(a);
- }
- var lasts = [];
- for (var i = 0; i < ancestors.length; ++i) lasts.push(ancestors[i].length);
- var nodeMultiSet = new NodeMultiSet(true);
- var newCtx = {nodes:[], pos:[], lasts:[]};
- while (0 < ancestors.length) {
- var pos = [ancestors[0].length];
- var last = [lasts[0]];
- var node = ancestors[0].pop();
- for (var i = ancestors.length - 1; i > 0; --i) {
- if (node === ancestors[i][ancestors[i].length - 1]) {
- pos.push(ancestors[i].length);
- last.push(lasts[i]);
- ancestors[i].pop();
- if (0 === ancestors[i].length) {
- ancestors.splice(i, i+1);
- lasts.splice(i, i+1);
- }
- }
- }
- if (0 === ancestors[0].length) {
- ancestors.shift();
- lasts.shift();
- }
- newCtx.nodes.push(node);
- newCtx.pos.push(pos);
- newCtx.lasts.push(last);
- }
- return newCtx;
- }
- /** Helper function for sortDocumentOrder. Returns a list of indices, from the
- * node to the root, of positions within parent.
- * For convenience, the node is the first element of the array.
- */
- function addressVector(node) {
- var r = [node];
- if (null != node.ownerElement) {
- node = node.ownerElement;
- r.push(-1);
- }
- while (null != node) {
- var i = 0;
- while (null != node.previousSibling) {
- node = node.previousSibling;
- i++;
- }
- r.push(i);
- node = node.parentNode
- }
- return r;
- }
- function addressComparator(a, b) {
- var minlen = Math.min(a.length - 1, b.length - 1), // not including [0]=node
- alen = a.length,
- blen = b.length;
- if (a[0] === b[0]) return 0;
- var c;
- for (var i = 0; i < minlen; ++i) {
- c = a[alen - i - 1] - b[blen - i - 1];
- if (0 !== c)
- break;
- }
- if (null == c || 0 === c) {
- // All equal until one of the nodes. The longer one is the descendant.
- c = alen - blen;
- }
- if (0 === c)
- c = a.nodeName - b.nodeName;
- if (0 === c)
- c = 1;
- return c;
- }
- var sortUniqDocumentOrder = xpath.sortUniqDocumentOrder = function(nodes) {
- var a = [];
- for (var i = 0; i < nodes.length; i++) {
- var node = nodes[i];
- var v = addressVector(node);
- a.push(v);
- }
- a.sort(addressComparator);
- var b = [];
- for (var i = 0; i < a.length; i++) {
- if (0 < i && a[i][0] === a[i - 1][0])
- continue;
- b.push(a[i][0]);
- }
- return b;
- }
- /** Sort node multiset. Does not do any de-duping. */
- function sortNodeMultiSet(nodeMultiSet) {
- var a = [];
- for (var i = 0; i < nodeMultiSet.nodes.length; i++) {
- var v = addressVector(nodeMultiSet.nodes[i]);
- a.push({v:v, n:nodeMultiSet.nodes[i],
- p:nodeMultiSet.pos[i], l:nodeMultiSet.lasts[i]});
- }
- a.sort(compare);
- var r = {nodes:[], pos:[], lasts:[]};
- for (var i = 0; i < a.length; ++i) {
- r.nodes.push(a[i].n);
- r.pos.push(a[i].p);
- r.lasts.push(a[i].l);
- }
- function compare(x, y) {
- return addressComparator(x.v, y.v);
- }
- return r;
- }
- /** Returns an array containing all the ancestors down to a node.
- * The array starts with document.
- */
- function nodeAndAncestors(node) {
- var ancestors = [node];
- var p = node;
- while (p = p.parentNode || p.ownerElement) {
- ancestors.unshift(p);
- }
- return ancestors;
- }
- function compareSiblings(a, b) {
- if (a === b) return 0;
- var c = a;
- while (c = c.previousSibling) {
- if (c === b)
- return 1; // b < a
- }
- c = b;
- while (c = c.previousSibling) {
- if (c === a)
- return -1; // a < b
- }
- throw new Error('a and b are not siblings: ' + xpath.stringifyObject(a) + ' vs ' + xpath.stringifyObject(b));
- }
- /** The merge in merge-sort.*/
- function mergeNodeLists(x, y) {
- var a, b, aanc, banc, r = [];
- if ('object' !== typeof x)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Invalid LHS for | operator ' +
- '(expected node-set): ' + x);
- if ('object' !== typeof y)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Invalid LHS for | operator ' +
- '(expected node-set): ' + y);
- while (true) {
- if (null == a) {
- a = x.shift();
- if (null != a)
- aanc = addressVector(a);
- }
- if (null == b) {
- b = y.shift();
- if (null != b)
- banc = addressVector(b);
- }
- if (null == a || null == b) break;
- var c = addressComparator(aanc, banc);
- if (c < 0) {
- r.push(a);
- a = null;
- aanc = null;
- } else if (c > 0) {
- r.push(b);
- b = null;
- banc = null;
- } else if (a.nodeName < b.nodeName) { // attributes
- r.push(a);
- a = null;
- aanc = null;
- } else if (a.nodeName > b.nodeName) { // attributes
- r.push(b);
- b = null;
- banc = null;
- } else if (a !== b) {
- // choose b arbitrarily
- r.push(b);
- b = null;
- banc = null;
- } else {
- console.assert(a === b, c);
- // just skip b without pushing it.
- b = null;
- banc = null;
- }
- }
- while (a) {
- r.push(a);
- a = x.shift();
- }
- while (b) {
- r.push(b);
- b = y.shift();
- }
- return r;
- }
- function comparisonHelper(test, x, y, isNumericComparison) {
- var coersion;
- if (isNumericComparison)
- coersion = fn.number;
- else coersion =
- 'boolean' === typeof x || 'boolean' === typeof y ? fn['boolean'] :
- 'number' === typeof x || 'number' === typeof y ? fn.number :
- fn.string;
- if ('object' === typeof x && 'object' === typeof y) {
- var aMap = {};
- for (var i = 0; i < x.nodes.length; ++i) {
- var xi = coersion({nodes:[x.nodes[i]]});
- for (var j = 0; j < y.nodes.length; ++j) {
- var yj = coersion({nodes:[y.nodes[j]]});
- if (test(xi, yj)) return true;
- }
- }
- return false;
- } else if ('object' === typeof x && x.nodes && x.nodes.length) {
- for (var i = 0; i < x.nodes.length; ++i) {
- var xi = coersion({nodes:[x.nodes[i]]}), yc = coersion(y);
- if (test(xi, yc))
- return true;
- }
- return false;
- } else if ('object' === typeof y && x.nodes && x.nodes.length) {
- for (var i = 0; i < x.nodes.length; ++i) {
- var yi = coersion({nodes:[y.nodes[i]]}), xc = coersion(x);
- if (test(xc, yi))
- return true;
- }
- return false;
- } else {
- var xc = coersion(x), yc = coersion(y);
- return test(xc, yc);
- }
- }
- var axes = xpath.axes = {
- 'ancestor':
- function ancestor(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return ancestorHelper(
- nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, false);
- },
- 'ancestor-or-self':
- function ancestorOrSelf(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return ancestorHelper(
- nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, true);
- },
- 'attribute':
- function attribute(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- // TODO: figure out whether positions should be undefined here.
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var nodeMultiSet = new NodeMultiSet(false);
- if (null != nodeName) {
- // TODO: with namespace
- for (var i = 0; i < nodeList.length; ++i) {
- var node = nodeList[i];
- if (null == node.getAttributeNode)
- continue; // only Element has .getAttributeNode
- var attr = node.getAttributeNode(nodeName);
- if (null != attr && matcher.matches(attr)) {
- nodeMultiSet.pushSeries();
- nodeMultiSet.addNode(attr);
- nodeMultiSet.popSeries();
- }
- }
- } else {
- for (var i = 0; i < nodeList.length; ++i) {
- var node = nodeList[i];
- if (null != node.attributes) {
- nodeMultiSet.pushSeries();
- for (var j = 0; j < node.attributes.length; j++) { // all nodes have .attributes
- var attr = node.attributes[j];
- if (matcher.matches(attr)) // TODO: I think this check is unnecessary
- nodeMultiSet.addNode(attr);
- }
- nodeMultiSet.popSeries();
- }
- }
- }
- return nodeMultiSet.finalize();
- },
- 'child':
- function child(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var nodeMultiSet = new NodeMultiSet(false);
- for (var i = 0; i < nodeList.length; ++i) {
- var n = nodeList[i];
- if (n.ownerElement) // skip attribute nodes' text child.
- continue;
- if (n.childNodes) {
- nodeMultiSet.pushSeries();
- var childList = 1 === nodeTypeNum && null != n.children ?
- n.children : n.childNodes;
- for (var j = 0; j < childList.length; ++j) {
- var child = childList[j];
- if (matcher.matches(child)) {
- nodeMultiSet.addNode(child);
- }
- // don't have to do de-duping because children have parent,
- // which are current context.
- }
- nodeMultiSet.popSeries();
- }
- }
- nodeMultiSet.finalize();
- r = sortNodeMultiSet(nodeMultiSet);
- return r;
- },
- 'descendant':
- function descenant(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return descenantHelper(
- nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, false);
- },
- 'descendant-or-self':
- function descenantOrSelf(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return descenantHelper(
- nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, true);
- },
- 'following':
- function following(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return followingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
- },
- 'following-sibling':
- function followingSibling(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return followingSiblingHelper(
- nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase,
- Array.prototype.shift, function() {return this[0];},
- function(node) {return node.nextSibling;});
- },
- 'namespace':
- function namespace(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- // TODO
- },
- 'parent':
- function parent(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- var nodes = [], pos = [];
- for (var i = 0; i < nodeList.length; ++i) {
- var parent = nodeList[i].parentNode || nodeList[i].ownerElement;
- if (null == parent)
- continue;
- if (!matcher.matches(parent))
- continue;
- if (nodes.length > 0 && parent === nodes[nodes.length-1])
- continue;
- nodes.push(parent);
- pos.push([1]);
- }
- return {nodes:nodes, pos:pos, lasts:pos};
- },
- 'preceding':
- function preceding(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return precedingHelper(
- nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
- },
- 'preceding-sibling':
- function precedingSibling(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- return followingSiblingHelper(
- nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase,
- Array.prototype.pop, function() {return this[this.length-1];},
- function(node) {return node.previousSibling},
- false, true);
- },
- 'self':
- function self(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
- var nodes = [], pos = [];
- var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
- for (var i = 0; i < nodeList.length; ++i) {
- if (matcher.matches(nodeList[i])) {
- nodes.push(nodeList[i]);
- pos.push([1]);
- }
- }
- return {nodes: nodes, pos: pos, lasts: pos}
- }
- };
- /***************************************************************************
- * Evaluation: functions *
- ***************************************************************************/
- var fn = {
- 'number': function number(optObject) {
- if ('number' === typeof optObject)
- return optObject;
- if ('string' === typeof optObject)
- return parseFloat(optObject); // note: parseFloat(' ') -> NaN, unlike +' ' -> 0.
- if ('boolean' === typeof optObject)
- return +optObject;
- return fn.number(fn.string.call(this, optObject)); // for node-sets
- },
- 'string': function string(optObject) {
- if (null == optObject)
- return fn.string(this);
- if ('string' === typeof optObject || 'boolean' === typeof optObject ||
- 'number' === typeof optObject)
- return '' + optObject;
- if (0 == optObject.nodes.length) return '';
- if (null != optObject.nodes[0].textContent)
- return optObject.nodes[0].textContent;
- return optObject.nodes[0].nodeValue;
- },
- 'boolean': function booleanVal(x) {
- return 'object' === typeof x ? x.nodes.length > 0 : !!x;
- },
- 'last': function last() {
- console.assert(Array.isArray(this.pos));
- console.assert(Array.isArray(this.lasts));
- console.assert(1 === this.pos.length);
- console.assert(1 === this.lasts.length);
- console.assert(1 === this.lasts[0].length);
- return this.lasts[0][0];
- },
- 'position': function position() {
- console.assert(Array.isArray(this.pos));
- console.assert(Array.isArray(this.lasts));
- console.assert(1 === this.pos.length);
- console.assert(1 === this.lasts.length);
- console.assert(1 === this.pos[0].length);
- return this.pos[0][0];
- },
- 'count': function count(nodeSet) {
- if ('object' !== typeof nodeSet)
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Position ' + stream.position() +
- ': Function count(node-set) ' +
- 'got wrong argument type: ' + nodeSet);
- return nodeSet.nodes.length;
- },
- 'id': function id(object) {
- var r = {nodes: []};
- var doc = this.nodes[0].ownerDocument || this.nodes[0];
- console.assert(doc);
- var ids;
- if ('object' === typeof object) {
- // for node-sets, map id over each node value.
- ids = [];
- for (var i = 0; i < object.nodes.length; ++i) {
- var idNode = object.nodes[i];
- var idsString = fn.string({nodes:[idNode]});
- var a = idsString.split(/[ \t\r\n]+/g);
- Array.prototype.push.apply(ids, a);
- }
- } else {
- var idsString = fn.string(object);
- var a = idsString.split(/[ \t\r\n]+/g);
- ids = a;
- }
- for (var i = 0; i < ids.length; ++i) {
- var id = ids[i];
- if (0 === id.length)
- continue;
- var node = doc.getElementById(id);
- if (null != node)
- r.nodes.push(node);
- }
- r.nodes = sortUniqDocumentOrder(r.nodes);
- return r;
- },
- 'local-name': function(nodeSet) {
- if (null == nodeSet)
- return fn.name(this);
- if (null == nodeSet.nodes) {
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'argument to name() must be a node-set. got ' + nodeSet);
- }
- // TODO: namespaced version
- return nodeSet.nodes[0].nodeName.toLowerCase(); // TODO: no toLowerCase for xml
- },
- 'namespace-uri': function(nodeSet) {
- // TODO
- throw new Error('not implemented yet');
- },
- 'name': function(nodeSet) {
- if (null == nodeSet)
- return fn.name(this);
- if (null == nodeSet.nodes) {
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'argument to name() must be a node-set. got ' + nodeSet);
- }
- return nodeSet.nodes[0].nodeName.toLowerCase(); // TODO: no toLowerCase for xml
- },
- 'concat': function concat(x) {
- var l = [];
- for (var i = 0; i < arguments.length; ++i) {
- l.push(fn.string(arguments[i]));
- }
- return l.join('');
- },
- 'starts-with': function startsWith(a, b) {
- var as = fn.string(a), bs = fn.string(b);
- return as.substr(0, bs.length) === bs;
- },
- 'contains': function contains(a, b) {
- var as = fn.string(a), bs = fn.string(b);
- var i = as.indexOf(bs);
- if (-1 === i) return false;
- return true;
- },
- 'substring-before': function substringBefore(a, b) {
- var as = fn.string(a), bs = fn.string(b);
- var i = as.indexOf(bs);
- if (-1 === i) return '';
- return as.substr(0, i);
- },
- 'substring-after': function substringBefore(a, b) {
- var as = fn.string(a), bs = fn.string(b);
- var i = as.indexOf(bs);
- if (-1 === i) return '';
- return as.substr(i + bs.length);
- },
- 'substring': function substring(string, start, optEnd) {
- if (null == string || null == start) {
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Must be at least 2 arguments to string()');
- }
- var sString = fn.string(string),
- iStart = fn.round(start),
- iEnd = optEnd == null ? null : fn.round(optEnd);
- // Note that xpath string positions user 1-based index
- if (iEnd == null)
- return sString.substr(iStart - 1);
- else
- return sString.substr(iStart - 1, iEnd);
- },
- 'string-length': function stringLength(optString) {
- return fn.string.call(this, optString).length;
- },
- 'normalize-space': function normalizeSpace(optString) {
- var s = fn.string.call(this, optString);
- return s.replace(/[ \t\r\n]+/g, ' ').replace(/^ | $/g, '');
- },
- 'translate': function translate(string, from, to) {
- var sString = fn.string.call(this, string),
- sFrom = fn.string(from),
- sTo = fn.string(to);
- var eachCharRe = [];
- var map = {};
- for (var i = 0; i < sFrom.length; ++i) {
- var c = sFrom.charAt(i);
- map[c] = sTo.charAt(i); // returns '' if beyond length of sTo.
- // copied from goog.string.regExpEscape in the Closure library.
- eachCharRe.push(
- c.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
- replace(/\x08/g, '\\x08'));
- }
- var re = new RegExp(eachCharRe.join('|'), 'g');
- return sString.replace(re, function(c) {return map[c];});
- },
- /// Boolean functions
- 'not': function not(x) {
- var bx = fn['boolean'](x);
- return !bx;
- },
- 'true': function trueVal() { return true; },
- 'false': function falseVal() { return false; },
- // TODO
- 'lang': function lang(string) { throw new Error('Not implemented');},
- 'sum': function sum(optNodeSet) {
- if (null == optNodeSet) return fn.sum(this);
- // for node-sets, map id over each node value.
- var sum = 0;
- for (var i = 0; i < optNodeSet.nodes.length; ++i) {
- var node = optNodeSet.nodes[i];
- var x = fn.number({nodes:[node]});
- sum += x;
- }
- return sum;
- },
- 'floor': function floor(number) {
- return Math.floor(fn.number(number));
- },
- 'ceiling': function ceiling(number) {
- return Math.ceil(fn.number(number));
- },
- 'round': function round(number) {
- return Math.round(fn.number(number));
- }
- };
- /***************************************************************************
- * Evaluation: operators *
- ***************************************************************************/
- var more = {
- UnaryMinus: function(x) { return -fn.number(x); },
- '+': function(x, y) { return fn.number(x) + fn.number(y); },
- '-': function(x, y) { return fn.number(x) - fn.number(y); },
- '*': function(x, y) { return fn.number(x) * fn.number(y); },
- 'div': function(x, y) { return fn.number(x) / fn.number(y); },
- 'mod': function(x, y) { return fn.number(x) % fn.number(y); },
- '<': function(x, y) {
- return comparisonHelper(function(x, y) { return fn.number(x) < fn.number(y);}, x, y, true);
- },
- '<=': function(x, y) {
- return comparisonHelper(function(x, y) { return fn.number(x) <= fn.number(y);}, x, y, true);
- },
- '>': function(x, y) {
- return comparisonHelper(function(x, y) { return fn.number(x) > fn.number(y);}, x, y, true);
- },
- '>=': function(x, y) {
- return comparisonHelper(function(x, y) { return fn.number(x) >= fn.number(y);}, x, y, true);
- },
- 'and': function(x, y) { return fn['boolean'](x) && fn['boolean'](y); },
- 'or': function(x, y) { return fn['boolean'](x) || fn['boolean'](y); },
- '|': function(x, y) { return {nodes: mergeNodeLists(x.nodes, y.nodes)}; },
- '=': function(x, y) {
- // optimization for two node-sets case: avoid n^2 comparisons.
- if ('object' === typeof x && 'object' === typeof y) {
- var aMap = {};
- for (var i = 0; i < x.nodes.length; ++i) {
- var s = fn.string({nodes:[x.nodes[i]]});
- aMap[s] = true;
- }
- for (var i = 0; i < y.nodes.length; ++i) {
- var s = fn.string({nodes:[y.nodes[i]]});
- if (aMap[s]) return true;
- }
- return false;
- } else {
- return comparisonHelper(function(x, y) {return x === y;}, x, y);
- }
- },
- '!=': function(x, y) {
- // optimization for two node-sets case: avoid n^2 comparisons.
- if ('object' === typeof x && 'object' === typeof y) {
- if (0 === x.nodes.length || 0 === y.nodes.length) return false;
- var aMap = {};
- for (var i = 0; i < x.nodes.length; ++i) {
- var s = fn.string({nodes:[x.nodes[i]]});
- aMap[s] = true;
- }
- for (var i = 0; i < y.nodes.length; ++i) {
- var s = fn.string({nodes:[y.nodes[i]]});
- if (!aMap[s]) return true;
- }
- return false;
- } else {
- return comparisonHelper(function(x, y) {return x !== y;}, x, y);
- }
- }
- };
- var nodeTypes = xpath.nodeTypes = {
- 'node': 0,
- 'attribute': 2,
- 'comment': 8, // this.doc.COMMENT_NODE,
- 'text': 3, // this.doc.TEXT_NODE,
- 'processing-instruction': 7, // this.doc.PROCESSING_INSTRUCTION_NODE,
- 'element': 1 //this.doc.ELEMENT_NODE
- };
- /** For debugging and unit tests: returnjs a stringified version of the
- * argument. */
- var stringifyObject = xpath.stringifyObject = function stringifyObject(ctx) {
- var seenKey = 'seen' + Math.floor(Math.random()*1000000000);
- return JSON.stringify(helper(ctx));
- function helper(ctx) {
- if (Array.isArray(ctx)) {
- return ctx.map(function(x) {return helper(x);});
- }
- if ('object' !== typeof ctx) return ctx;
- if (null == ctx) return ctx;
- // if (ctx.toString) return ctx.toString();
- if (null != ctx.outerHTML) return ctx.outerHTML;
- if (null != ctx.nodeValue) return ctx.nodeName + '=' + ctx.nodeValue;
- if (ctx[seenKey]) return '[circular]';
- ctx[seenKey] = true;
- var nicer = {};
- for (var key in ctx) {
- if (seenKey === key)
- continue;
- try {
- nicer[key] = helper(ctx[key]);
- } catch (e) {
- nicer[key] = '[exception: ' + e.message + ']';
- }
- }
- delete ctx[seenKey];
- return nicer;
- }
- }
- var Evaluator = xpath.Evaluator = function Evaluator(doc) {
- this.doc = doc;
- }
- Evaluator.prototype = {
- val: function val(ast, ctx) {
- console.assert(ctx.nodes);
- if ('number' === typeof ast || 'string' === typeof ast) return ast;
- if (more[ast[0]]) {
- var evaluatedParams = [];
- for (var i = 1; i < ast.length; ++i) {
- evaluatedParams.push(this.val(ast[i], ctx));
- }
- var r = more[ast[0]].apply(ctx, evaluatedParams);
- return r;
- }
- switch (ast[0]) {
- case 'Root': return {nodes: [this.doc]};
- case 'FunctionCall':
- var functionName = ast[1], functionParams = ast[2];
- if (null == fn[functionName])
- throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
- 'Unknown function: ' + functionName);
- var evaluatedParams = [];
- for (var i = 0; i < functionParams.length; ++i) {
- evaluatedParams.push(this.val(functionParams[i], ctx));
- }
- var r = fn[functionName].apply(ctx, evaluatedParams);
- return r;
- case 'Predicate':
- var lhs = this.val(ast[1], ctx);
- var ret = {nodes: []};
- var contexts = eachContext(lhs);
- for (var i = 0; i < contexts.length; ++i) {
- var singleNodeSet = contexts[i];
- var rhs = this.val(ast[2], singleNodeSet);
- var success;
- if ('number' === typeof rhs) {
- success = rhs === singleNodeSet.pos[0][0];
- } else {
- success = fn['boolean'](rhs);
- }
- if (success) {
- var node = singleNodeSet.nodes[0];
- ret.nodes.push(node);
- // skip over all the rest of the same node.
- while (i+1 < contexts.length && node === contexts[i+1].nodes[0]) {
- i++;
- }
- }
- }
- return ret;
- case 'PathExpr':
- // turn the path into an expressoin; i.e., remove the position
- // information of the last axis.
- var x = this.val(ast[1], ctx);
- // Make the nodeset a forward-direction-only one.
- if (x.finalize) { // it is a NodeMultiSet
- for (var i = 0; i < x.nodes.length; ++i) {
- console.assert(null != x.nodes[i].nodeType);
- }
- return {nodes: x.nodes};
- } else {
- return x;
- }
- case '/':
- // TODO: don't generate '/' nodes, just Axis nodes.
- var lhs = this.val(ast[1], ctx);
- console.assert(null != lhs);
- var r = this.val(ast[2], lhs);
- console.assert(null != r);
- return r;
- case 'Axis':
- // All the axis tests from Step. We only get AxisSpecifier NodeTest,
- // not the predicate (which is applied later)
- var axis = ast[1],
- nodeType = ast[2],
- nodeTypeNum = nodeTypes[nodeType],
- shouldLowerCase = true, // TODO: give option
- nodeName = ast[3] && shouldLowerCase ? ast[3].toLowerCase() : ast[3];
- nodeName = nodeName === '*' ? null : nodeName;
- if ('object' !== typeof ctx) return {nodes:[], pos:[]};
- var nodeList = ctx.nodes.slice(); // TODO: is copy needed?
- var r = axes[axis](nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
- return r;
- }
- }
- };
- var evaluate = xpath.evaluate = function evaluate(expr, doc, context) {
- //var astFactory = new AstEvaluatorFactory(doc, context);
- var stream = new Stream(expr);
- var ast = parse(stream, astFactory);
- var val = new Evaluator(doc).val(ast, {nodes: [context]});
- return val;
- }
- /***************************************************************************
- * DOM interface *
- ***************************************************************************/
- var XPathException = xpath.XPathException = function XPathException(code, message) {
- var e = new Error(message);
- this.__proto__ = e;
- this.name = 'XPathException';
- this.code = code;
- }
- XPathException.prototype = Error.prototype;
- XPathException.prototype.__proto__ = XPathException;
- XPathException.INVALID_EXPRESSION_ERR = 51;
- XPathException.TYPE_ERR = 52;
- var XPathEvaluator = xpath.XPathEvaluator = function XPathEvaluator() {}
- XPathEvaluator.prototype = {
- createExpression: function(expression, resolver) {
- return new XPathExpression(expression, resolver);
- },
- createNSResolver: function(nodeResolver) {
- // TODO
- },
- evaluate: function evaluate(expression, contextNode, resolver, type, result) {
- var expr = new XPathExpression(expression, resolver);
- return expr.evaluate(contextNode, type, result);
- }
- };
- var XPathExpression = xpath.XPathExpression = function XPathExpression(expression, resolver, optDoc) {
- var stream = new Stream(expression);
- this._ast = parse(stream, astFactory);
- this._doc = optDoc;
- }
- XPathExpression.prototype = {
- evaluate: function evaluate(contextNode, type, result) {
- if (null == contextNode.nodeType)
- throw new Error('bad argument (expected context node): ' + contextNode);
- var doc = contextNode.ownerDocument || contextNode;
- if (null != this._doc && this._doc !== doc) {
- throw new core.DOMException(
- core.WRONG_DOCUMENT_ERR,
- 'The document must be the same as the context node\'s document.');
- }
- var evaluator = new Evaluator(doc);
- var value = evaluator.val(this._ast, {nodes: [contextNode]});
- if (XPathResult.NUMBER_TYPE === type)
- value = fn.number(value);
- else if (XPathResult.STRING_TYPE === type)
- value = fn.string(value);
- else if (XPathResult.BOOLEAN_TYPE === type)
- value = fn['boolean'](value);
- else if (XPathResult.ANY_TYPE !== type &&
- XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== type &&
- XPathResult.ORDERED_NODE_ITERATOR_TYPE !== type &&
- XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== type &&
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== type &&
- XPathResult.ANY_UNORDERED_NODE_TYPE !== type &&
- XPathResult.FIRST_ORDERED_NODE_TYPE !== type)
- throw new core.DOMException(
- core.NOT_SUPPORTED_ERR,
- 'You must provide an XPath result type (0=any).');
- else if (XPathResult.ANY_TYPE !== type &&
- 'object' !== typeof value)
- throw new XPathException(
- XPathException.TYPE_ERR,
- 'Value should be a node-set: ' + value);
- return new XPathResult(doc, value, type);
- }
- }
- var XPathResult = xpath.XPathResult = function XPathResult(doc, value, resultType) {
- this._value = value;
- this._resultType = resultType;
- this._i = 0;
- this._invalidated = false;
- if (this.resultType === XPathResult.UNORDERED_NODE_ITERATOR_TYPE ||
- this.resultType === XPathResult.ORDERED_NODE_ITERATOR_TYPE) {
- doc.addEventListener('DOMSubtreeModified', invalidate, true);
- var self = this;
- function invalidate() {
- self._invalidated = true;
- doc.removeEventListener('DOMSubtreeModified', invalidate, true);
- }
- }
- }
- XPathResult.ANY_TYPE = 0;
- XPathResult.NUMBER_TYPE = 1;
- XPathResult.STRING_TYPE = 2;
- XPathResult.BOOLEAN_TYPE = 3;
- XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4;
- XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5;
- XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE = 6;
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7;
- XPathResult.ANY_UNORDERED_NODE_TYPE = 8;
- XPathResult.FIRST_ORDERED_NODE_TYPE = 9;
- XPathResult.prototype = {
- // XPathResultType
- get resultType() {
- if (this._resultType) return this._resultType;
- switch (typeof this._value) {
- case 'number': return XPathResult.NUMBER_TYPE;
- case 'string': return XPathResult.STRING_TYPE;
- case 'boolean': return XPathResult.BOOLEAN_TYPE;
- default: return XPathResult.UNORDERED_NODE_ITERATOR_TYPE;
- }
- },
- get numberValue() {
- if (XPathResult.NUMBER_TYPE !== this.resultType)
- throw new XPathException(XPathException.TYPE_ERR,
- 'You should have asked for a NUMBER_TYPE.');
- return this._value;
- },
- get stringValue() {
- if (XPathResult.STRING_TYPE !== this.resultType)
- throw new XPathException(XPathException.TYPE_ERR,
- 'You should have asked for a STRING_TYPE.');
- return this._value;
- },
- get booleanValue() {
- if (XPathResult.BOOLEAN_TYPE !== this.resultType)
- throw new XPathException(XPathException.TYPE_ERR,
- 'You should have asked for a BOOLEAN_TYPE.');
- return this._value;
- },
- get singleNodeValue() {
- if (XPathResult.ANY_UNORDERED_NODE_TYPE !== this.resultType &&
- XPathResult.FIRST_ORDERED_NODE_TYPE !== this.resultType)
- throw new XPathException(
- XPathException.TYPE_ERR,
- 'You should have asked for a FIRST_ORDERED_NODE_TYPE.');
- return this._value.nodes[0] || null;
- },
- get invalidIteratorState() {
- if (XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== this.resultType &&
- XPathResult.ORDERED_NODE_ITERATOR_TYPE !== this.resultType)
- return false;
- return !!this._invalidated;
- },
- get snapshotLength() {
- if (XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== this.resultType &&
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== this.resultType)
- throw new XPathException(
- XPathException.TYPE_ERR,
- 'You should have asked for a ORDERED_NODE_SNAPSHOT_TYPE.');
- return this._value.nodes.length;
- },
- iterateNext: function iterateNext() {
- if (XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== this.resultType &&
- XPathResult.ORDERED_NODE_ITERATOR_TYPE !== this.resultType)
- throw new XPathException(
- XPathException.TYPE_ERR,
- 'You should have asked for a ORDERED_NODE_ITERATOR_TYPE.');
- if (this.invalidIteratorState)
- throw new core.DOMException(
- core.INVALID_STATE_ERR,
- 'The document has been mutated since the result was returned');
- return this._value.nodes[this._i++] || null;
- },
- snapshotItem: function snapshotItem(index) {
- if (XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== this.resultType &&
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== this.resultType)
- throw new XPathException(
- XPathException.TYPE_ERR,
- 'You should have asked for a ORDERED_NODE_SNAPSHOT_TYPE.');
- return this._value.nodes[index] || null;
- }
- };
- // so you can access ANY_TYPE etc. from the instances:
- XPathResult.prototype.__proto__ = XPathResult;
- core.XPathException = XPathException;
- core.XPathExpression = XPathExpression;
- core.XPathResult = XPathResult;
- core.XPathEvaluator = XPathEvaluator;
- core.Document.prototype.createExpression =
- XPathEvaluator.prototype.createExpression;
- core.Document.prototype.createNSResolver =
- XPathEvaluator.prototype.createNSResolver;
- core.Document.prototype.evaluate = XPathEvaluator.prototype.evaluate;
- })();
|