xpath.js 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859
  1. /** Here is yet another implementation of XPath 1.0 in Javascript.
  2. *
  3. * My goal was to make it relatively compact, but as I fixed all the axis bugs
  4. * the axes became more and more complicated. :-(.
  5. *
  6. * I have not implemented namespaces or case-sensitive axes for XML yet.
  7. *
  8. * How to test it in Chrome: You can make a Chrome extension that replaces
  9. * the WebKit XPath parser with this one. But it takes a bit of effort to
  10. * get around isolated world and same-origin restrictions:
  11. * manifest.json:
  12. {
  13. "name": "XPathTest",
  14. "version": "0.1",
  15. "content_scripts": [{
  16. "matches": ["http://localhost/*"], // or wildcard host
  17. "js": ["xpath.js", "injection.js"],
  18. "all_frames": true, "run_at": "document_start"
  19. }]
  20. }
  21. * injection.js:
  22. // goal: give my xpath object to the website's JS context.
  23. var script = document.createElement('script');
  24. script.textContent =
  25. "document.addEventListener('xpathextend', function(e) {\n" +
  26. " console.log('extending document with xpath...');\n" +
  27. " e.detail(window);" +
  28. "});";
  29. document.documentElement.appendChild(script);
  30. document.documentElement.removeChild(script);
  31. var evt = document.createEvent('CustomEvent');
  32. evt.initCustomEvent('xpathextend', true, true, this.xpath.extend);
  33. document.dispatchEvent(evt);
  34. */
  35. (function() {
  36. var core;
  37. var xpath;
  38. if ('function' === typeof require) {
  39. core = require("../level3/core").dom.level3.core;
  40. xpath = exports;
  41. } else {
  42. core = this;
  43. xpath = {};
  44. }
  45. /***************************************************************************
  46. * Tokenization *
  47. ***************************************************************************/
  48. /**
  49. * The XPath lexer is basically a single regular expression, along with
  50. * some helper functions to pop different types.
  51. */
  52. var Stream = xpath.Stream = function Stream(str) {
  53. this.original = this.str = str;
  54. this.peeked = null;
  55. // TODO: not really needed, but supposedly tokenizer also disambiguates
  56. // a * b vs. node test *
  57. this.prev = null; // for debugging
  58. this.prevprev = null;
  59. }
  60. Stream.prototype = {
  61. peek: function() {
  62. if (this.peeked) return this.peeked;
  63. var m = this.re.exec(this.str);
  64. if (!m) return null;
  65. this.str = this.str.substr(m[0].length);
  66. return this.peeked = m[1];
  67. },
  68. /** Peek 2 tokens ahead. */
  69. peek2: function() {
  70. this.peek(); // make sure this.peeked is set
  71. var m = this.re.exec(this.str);
  72. if (!m) return null;
  73. return m[1];
  74. },
  75. pop: function() {
  76. var r = this.peek();
  77. this.peeked = null;
  78. this.prevprev = this.prev;
  79. this.prev = r;
  80. return r;
  81. },
  82. trypop: function(tokens) {
  83. var tok = this.peek();
  84. if (tok === tokens) return this.pop();
  85. if (Array.isArray(tokens)) {
  86. for (var i = 0; i < tokens.length; ++i) {
  87. var t = tokens[i];
  88. if (t == tok) return this.pop();;
  89. }
  90. }
  91. },
  92. trypopfuncname: function() {
  93. var tok = this.peek();
  94. if (!this.isQnameRe.test(tok))
  95. return null;
  96. switch (tok) {
  97. case 'comment': case 'text': case 'processing-instruction': case 'node':
  98. return null;
  99. }
  100. if ('(' != this.peek2()) return null;
  101. return this.pop();
  102. },
  103. trypopaxisname: function() {
  104. var tok = this.peek();
  105. switch (tok) {
  106. case 'ancestor': case 'ancestor-or-self': case 'attribute':
  107. case 'child': case 'descendant': case 'descendant-or-self':
  108. case 'following': case 'following-sibling': case 'namespace':
  109. case 'parent': case 'preceding': case 'preceding-sibling': case 'self':
  110. if ('::' == this.peek2()) return this.pop();
  111. }
  112. return null;
  113. },
  114. trypopnametest: function() {
  115. var tok = this.peek();
  116. if ('*' === tok || this.startsWithNcNameRe.test(tok)) return this.pop();
  117. return null;
  118. },
  119. trypopliteral: function() {
  120. var tok = this.peek();
  121. if (null == tok) return null;
  122. var first = tok.charAt(0);
  123. var last = tok.charAt(tok.length - 1);
  124. if ('"' === first && '"' === last ||
  125. "'" === first && "'" === last) {
  126. this.pop();
  127. return tok.substr(1, tok.length - 2);
  128. }
  129. },
  130. trypopnumber: function() {
  131. var tok = this.peek();
  132. if (this.isNumberRe.test(tok)) return parseFloat(this.pop());
  133. else return null;
  134. },
  135. trypopvarref: function() {
  136. var tok = this.peek();
  137. if (null == tok) return null;
  138. if ('$' === tok.charAt(0)) return this.pop().substr(1);
  139. else return null;
  140. },
  141. position: function() {
  142. return this.original.length - this.str.length;
  143. }
  144. };
  145. (function() {
  146. // http://www.w3.org/TR/REC-xml-names/#NT-NCName
  147. var nameStartCharsExceptColon =
  148. 'A-Z_a-z\xc0-\xd6\xd8-\xf6\xF8-\u02FF\u0370-\u037D\u037F-\u1FFF' +
  149. '\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF' +
  150. '\uFDF0-\uFFFD'; // JS doesn't support [#x10000-#xEFFFF]
  151. var nameCharExceptColon = nameStartCharsExceptColon +
  152. '\\-\\.0-9\xb7\u0300-\u036F\u203F-\u2040';
  153. var ncNameChars = '[' + nameStartCharsExceptColon +
  154. '][' + nameCharExceptColon + ']*'
  155. // http://www.w3.org/TR/REC-xml-names/#NT-QName
  156. var qNameChars = ncNameChars + '(?::' + ncNameChars + ')?';
  157. var otherChars = '\\.\\.|[\\(\\)\\[\\].@,]|::'; // .. must come before [.]
  158. var operatorChars =
  159. 'and|or|mod|div|' +
  160. '//|!=|<=|>=|[*/|+\\-=<>]'; // //, !=, <=, >= before individual ones.
  161. var literal = '"[^"]*"|' + "'[^']*'";
  162. var numberChars = '[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+';
  163. var variableReference = '\\$' + qNameChars;
  164. var nameTestChars = '\\*|' + ncNameChars + ':\\*|' + qNameChars;
  165. var optionalSpace = '[ \t\r\n]*'; // stricter than regexp \s.
  166. var nodeType = 'comment|text|processing-instruction|node';
  167. var re = new RegExp(
  168. // numberChars before otherChars so that leading-decimal doesn't become .
  169. '^' + optionalSpace + '(' + numberChars + '|' + otherChars + '|' +
  170. nameTestChars + '|' + operatorChars + '|' + literal + '|' +
  171. variableReference + ')'
  172. // operatorName | nodeType | functionName | axisName are lumped into
  173. // qName for now; we'll check them on pop.
  174. );
  175. Stream.prototype.re = re;
  176. Stream.prototype.startsWithNcNameRe = new RegExp('^' + ncNameChars);
  177. Stream.prototype.isQnameRe = new RegExp('^' + qNameChars + '$');
  178. Stream.prototype.isNumberRe = new RegExp('^' + numberChars + '$');
  179. })();
  180. /***************************************************************************
  181. * Parsing *
  182. ***************************************************************************/
  183. var parse = xpath.parse = function parse(stream, a) {
  184. var r = orExpr(stream,a);
  185. var x, unparsed = [];
  186. while (x = stream.pop()) {
  187. unparsed.push(x);
  188. }
  189. if (unparsed.length)
  190. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  191. 'Position ' + stream.position() +
  192. ': Unparsed tokens: ' + unparsed.join(' '));
  193. return r;
  194. }
  195. /**
  196. * binaryL ::= subExpr
  197. * | binaryL op subExpr
  198. * so a op b op c becomes ((a op b) op c)
  199. */
  200. function binaryL(subExpr, stream, a, ops) {
  201. var lhs = subExpr(stream, a);
  202. if (lhs == null) return null;
  203. var op;
  204. while (op = stream.trypop(ops)) {
  205. var rhs = subExpr(stream, a);
  206. if (rhs == null)
  207. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  208. 'Position ' + stream.position() +
  209. ': Expected something after ' + op);
  210. lhs = a.node(op, lhs, rhs);
  211. }
  212. return lhs;
  213. }
  214. /**
  215. * Too bad this is never used. If they made a ** operator (raise to power),
  216. ( we would use it.
  217. * binaryR ::= subExpr
  218. * | subExpr op binaryR
  219. * so a op b op c becomes (a op (b op c))
  220. */
  221. function binaryR(subExpr, stream, a, ops) {
  222. var lhs = subExpr(stream, a);
  223. if (lhs == null) return null;
  224. var op = stream.trypop(ops);
  225. if (op) {
  226. var rhs = binaryR(stream, a);
  227. if (rhs == null)
  228. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  229. 'Position ' + stream.position() +
  230. ': Expected something after ' + op);
  231. return a.node(op, lhs, rhs);
  232. } else {
  233. return lhs;// TODO
  234. }
  235. }
  236. /** [1] LocationPath::= RelativeLocationPath | AbsoluteLocationPath
  237. * e.g. a, a/b, //a/b
  238. */
  239. function locationPath(stream, a) {
  240. return absoluteLocationPath(stream, a) ||
  241. relativeLocationPath(null, stream, a);
  242. }
  243. /** [2] AbsoluteLocationPath::= '/' RelativeLocationPath? | AbbreviatedAbsoluteLocationPath
  244. * [10] AbbreviatedAbsoluteLocationPath::= '//' RelativeLocationPath
  245. */
  246. function absoluteLocationPath(stream, a) {
  247. var op = stream.peek();
  248. if ('/' === op || '//' === op) {
  249. var lhs = a.node('Root');
  250. return relativeLocationPath(lhs, stream, a, true);
  251. } else {
  252. return null;
  253. }
  254. }
  255. /** [3] RelativeLocationPath::= Step | RelativeLocationPath '/' Step |
  256. * | AbbreviatedRelativeLocationPath
  257. * [11] AbbreviatedRelativeLocationPath::= RelativeLocationPath '//' Step
  258. * e.g. p/a, etc.
  259. */
  260. function relativeLocationPath(lhs, stream, a, isOnlyRootOk) {
  261. if (null == lhs) {
  262. lhs = step(stream, a);
  263. if (null == lhs) return lhs;
  264. }
  265. var op;
  266. while (op = stream.trypop(['/', '//'])) {
  267. if ('//' === op) {
  268. lhs = a.node('/', lhs,
  269. a.node('Axis', 'descendant-or-self', 'node', undefined));
  270. }
  271. var rhs = step(stream, a);
  272. if (null == rhs && '/' === op && isOnlyRootOk) return lhs;
  273. else isOnlyRootOk = false;
  274. if (null == rhs)
  275. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  276. 'Position ' + stream.position() +
  277. ': Expected step after ' + op);
  278. lhs = a.node('/', lhs, rhs);
  279. }
  280. return lhs;
  281. }
  282. /** [4] Step::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
  283. * [12] AbbreviatedStep::= '.' | '..'
  284. * e.g. @href, self::p, p, a[@href], ., ..
  285. */
  286. function step(stream, a) {
  287. var abbrStep = stream.trypop(['.', '..']);
  288. if ('.' === abbrStep) // A location step of . is short for self::node().
  289. return a.node('Axis', 'self', 'node');
  290. if ('..' === abbrStep) // A location step of .. is short for parent::node()
  291. return a.node('Axis', 'parent', 'node');
  292. var axis = axisSpecifier(stream, a);
  293. var nodeType = nodeTypeTest(stream, a);
  294. var nodeName;
  295. if (null == nodeType) nodeName = nodeNameTest(stream, a);
  296. if (null == axis && null == nodeType && null == nodeName) return null;
  297. if (null == nodeType && null == nodeName)
  298. throw new XPathException(
  299. XPathException.INVALID_EXPRESSION_ERR,
  300. 'Position ' + stream.position() +
  301. ': Expected nodeTest after axisSpecifier ' + axis);
  302. if (null == axis) axis = 'child';
  303. if (null == nodeType) {
  304. // When there's only a node name, then the node type is forced to be the
  305. // principal node type of the axis.
  306. // see http://www.w3.org/TR/xpath/#dt-principal-node-type
  307. if ('attribute' === axis) nodeType = 'attribute';
  308. else if ('namespace' === axis) nodeType = 'namespace';
  309. else nodeType = 'element';
  310. }
  311. var lhs = a.node('Axis', axis, nodeType, nodeName);
  312. var pred;
  313. while (null != (pred = predicate(lhs, stream, a))) {
  314. lhs = pred;
  315. }
  316. return lhs;
  317. }
  318. /** [5] AxisSpecifier::= AxisName '::' | AbbreviatedAxisSpecifier
  319. * [6] AxisName::= 'ancestor' | 'ancestor-or-self' | 'attribute' | 'child'
  320. * | 'descendant' | 'descendant-or-self' | 'following'
  321. * | 'following-sibling' | 'namespace' | 'parent' |
  322. * | 'preceding' | 'preceding-sibling' | 'self'
  323. * [13] AbbreviatedAxisSpecifier::= '@'?
  324. */
  325. function axisSpecifier(stream, a) {
  326. var attr = stream.trypop('@');
  327. if (null != attr) return 'attribute';
  328. var axisName = stream.trypopaxisname();
  329. if (null != axisName) {
  330. var coloncolon = stream.trypop('::');
  331. if (null == coloncolon)
  332. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  333. 'Position ' + stream.position() +
  334. ': Should not happen. Should be ::.');
  335. return axisName;
  336. }
  337. }
  338. /** [7] NodeTest::= NameTest | NodeType '(' ')' | 'processing-instruction' '(' Literal ')'
  339. * [38] NodeType::= 'comment' | 'text' | 'processing-instruction' | 'node'
  340. * I've split nodeTypeTest from nodeNameTest for convenience.
  341. */
  342. function nodeTypeTest(stream, a) {
  343. if ('(' !== stream.peek2()) {
  344. return null;
  345. }
  346. var type = stream.trypop(['comment', 'text', 'processing-instruction', 'node']);
  347. if (null != type) {
  348. if (null == stream.trypop('('))
  349. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  350. 'Position ' + stream.position() +
  351. ': Should not happen.');
  352. var param = undefined;
  353. if (type == 'processing-instruction') {
  354. param = stream.trypopliteral();
  355. }
  356. if (null == stream.trypop(')'))
  357. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  358. 'Position ' + stream.position() +
  359. ': Expected close parens.');
  360. return type
  361. }
  362. }
  363. function nodeNameTest(stream, a) {
  364. var name = stream.trypopnametest();
  365. if (name != null) return name;
  366. else return null;
  367. }
  368. /** [8] Predicate::= '[' PredicateExpr ']'
  369. * [9] PredicateExpr::= Expr
  370. */
  371. function predicate(lhs, stream, a) {
  372. if (null == stream.trypop('[')) return null;
  373. var expr = orExpr(stream, a);
  374. if (null == expr)
  375. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  376. 'Position ' + stream.position() +
  377. ': Expected expression after [');
  378. if (null == stream.trypop(']'))
  379. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  380. 'Position ' + stream.position() +
  381. ': Expected ] after expression.');
  382. return a.node('Predicate', lhs, expr);
  383. }
  384. /** [14] Expr::= OrExpr
  385. */
  386. /** [15] PrimaryExpr::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
  387. * e.g. $x, (3+4), "hi", 32, f(x)
  388. */
  389. function primaryExpr(stream, a) {
  390. var x = stream.trypopliteral();
  391. if (null == x)
  392. x = stream.trypopnumber();
  393. if (null != x) {
  394. return x;
  395. }
  396. var varRef = stream.trypopvarref();
  397. if (null != varRef) return a.node('VariableReference', varRef);
  398. var funCall = functionCall(stream, a);
  399. if (null != funCall) {
  400. return funCall;
  401. }
  402. if (stream.trypop('(')) {
  403. var e = orExpr(stream, a);
  404. if (null == e)
  405. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  406. 'Position ' + stream.position() +
  407. ': Expected expression after (.');
  408. if (null == stream.trypop(')'))
  409. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  410. 'Position ' + stream.position() +
  411. ': Expected ) after expression.');
  412. return e;
  413. }
  414. return null;
  415. }
  416. /** [16] FunctionCall::= FunctionName '(' ( Argument ( ',' Argument )* )? ')'
  417. * [17] Argument::= Expr
  418. */
  419. function functionCall(stream, a) {
  420. var name = stream.trypopfuncname(stream, a);
  421. if (null == name) return null;
  422. if (null == stream.trypop('('))
  423. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  424. 'Position ' + stream.position() +
  425. ': Expected ( ) after function name.');
  426. var params = [];
  427. var first = true;
  428. while (null == stream.trypop(')')) {
  429. if (!first && null == stream.trypop(','))
  430. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  431. 'Position ' + stream.position() +
  432. ': Expected , between arguments of the function.');
  433. first = false;
  434. var param = orExpr(stream, a);
  435. if (param == null)
  436. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  437. 'Position ' + stream.position() +
  438. ': Expected expression as argument of function.');
  439. params.push(param);
  440. }
  441. return a.node('FunctionCall', name, params);
  442. }
  443. /** [18] UnionExpr::= PathExpr | UnionExpr '|' PathExpr
  444. */
  445. function unionExpr(stream, a) { return binaryL(pathExpr, stream, a, '|'); }
  446. /** [19] PathExpr ::= LocationPath
  447. * | FilterExpr
  448. * | FilterExpr '/' RelativeLocationPath
  449. * | FilterExpr '//' RelativeLocationPath
  450. * Unlike most other nodes, this one always generates a node because
  451. * at this point all reverse nodesets must turn into a forward nodeset
  452. */
  453. function pathExpr(stream, a) {
  454. // We have to do FilterExpr before LocationPath because otherwise
  455. // LocationPath will eat up the name from a function call.
  456. var filter = filterExpr(stream, a);
  457. if (null == filter) {
  458. var loc = locationPath(stream, a);
  459. if (null == loc) {
  460. throw new Error
  461. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  462. 'Position ' + stream.position() +
  463. ': The expression shouldn\'t be empty...');
  464. }
  465. return a.node('PathExpr', loc);
  466. }
  467. var rel = relativeLocationPath(filter, stream, a, false);
  468. if (filter === rel) return rel;
  469. else return a.node('PathExpr', rel);
  470. }
  471. /** [20] FilterExpr::= PrimaryExpr | FilterExpr Predicate
  472. * aka. FilterExpr ::= PrimaryExpr Predicate*
  473. */
  474. function filterExpr(stream, a) {
  475. var primary = primaryExpr(stream, a);
  476. if (primary == null) return null;
  477. var pred, lhs = primary;
  478. while (null != (pred = predicate(lhs, stream, a))) {
  479. lhs = pred;
  480. }
  481. return lhs;
  482. }
  483. /** [21] OrExpr::= AndExpr | OrExpr 'or' AndExpr
  484. */
  485. function orExpr(stream, a) {
  486. var orig = (stream.peeked || '') + stream.str
  487. var r = binaryL(andExpr, stream, a, 'or');
  488. var now = (stream.peeked || '') + stream.str;
  489. return r;
  490. }
  491. /** [22] AndExpr::= EqualityExpr | AndExpr 'and' EqualityExpr
  492. */
  493. function andExpr(stream, a) { return binaryL(equalityExpr, stream, a, 'and'); }
  494. /** [23] EqualityExpr::= RelationalExpr | EqualityExpr '=' RelationalExpr
  495. * | EqualityExpr '!=' RelationalExpr
  496. */
  497. function equalityExpr(stream, a) { return binaryL(relationalExpr, stream, a, ['=','!=']); }
  498. /** [24] RelationalExpr::= AdditiveExpr | RelationalExpr '<' AdditiveExpr
  499. * | RelationalExpr '>' AdditiveExpr
  500. * | RelationalExpr '<=' AdditiveExpr
  501. * | RelationalExpr '>=' AdditiveExpr
  502. */
  503. function relationalExpr(stream, a) { return binaryL(additiveExpr, stream, a, ['<','>','<=','>=']); }
  504. /** [25] AdditiveExpr::= MultiplicativeExpr
  505. * | AdditiveExpr '+' MultiplicativeExpr
  506. * | AdditiveExpr '-' MultiplicativeExpr
  507. */
  508. function additiveExpr(stream, a) { return binaryL(multiplicativeExpr, stream, a, ['+','-']); }
  509. /** [26] MultiplicativeExpr::= UnaryExpr
  510. * | MultiplicativeExpr MultiplyOperator UnaryExpr
  511. * | MultiplicativeExpr 'div' UnaryExpr
  512. * | MultiplicativeExpr 'mod' UnaryExpr
  513. */
  514. function multiplicativeExpr(stream, a) { return binaryL(unaryExpr, stream, a, ['*','div','mod']); }
  515. /** [27] UnaryExpr::= UnionExpr | '-' UnaryExpr
  516. */
  517. function unaryExpr(stream, a) {
  518. if (stream.trypop('-')) {
  519. var e = unaryExpr(stream, a);
  520. if (null == e)
  521. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  522. 'Position ' + stream.position() +
  523. ': Expected unary expression after -');
  524. return a.node('UnaryMinus', e);
  525. }
  526. else return unionExpr(stream, a);
  527. }
  528. var astFactory = {
  529. node: function() {return Array.prototype.slice.call(arguments);}
  530. };
  531. /***************************************************************************
  532. * Optimizations (TODO) *
  533. ***************************************************************************/
  534. /**
  535. * Some things I've been considering:
  536. * 1) a//b becomes a/descendant::b if there's no predicate that uses
  537. * position() or last()
  538. * 2) axis[pred]: when pred doesn't use position, evaluate it just once per
  539. * node in the node-set rather than once per (node, position, last).
  540. * For more optimizations, look up Gecko's optimizer:
  541. * http://mxr.mozilla.org/mozilla-central/source/content/xslt/src/xpath/txXPathOptimizer.cpp
  542. */
  543. // TODO
  544. function optimize(ast) {
  545. }
  546. /***************************************************************************
  547. * Evaluation: axes *
  548. ***************************************************************************/
  549. /**
  550. * Data types: For string, number, boolean, we just use Javascript types.
  551. * Node-sets have the form
  552. * {nodes: [node, ...]}
  553. * or {nodes: [node, ...], pos: [[1], [2], ...], lasts: [[1], [2], ...]}
  554. *
  555. * Most of the time, only the node is used and the position information is
  556. * discarded. But if you use a predicate, we need to try every value of
  557. * position and last in case the predicate calls position() or last().
  558. */
  559. /**
  560. * The NodeMultiSet is a helper class to help generate
  561. * {nodes:[], pos:[], lasts:[]} structures. It is useful for the
  562. * descendant, descendant-or-self, following-sibling, and
  563. * preceding-sibling axes for which we can use a stack to organize things.
  564. */
  565. function NodeMultiSet(isReverseAxis) {
  566. this.nodes = [];
  567. this.pos = [];
  568. this.lasts = [];
  569. this.nextPos = [];
  570. this.seriesIndexes = []; // index within nodes that each series begins.
  571. this.isReverseAxis = isReverseAxis;
  572. this._pushToNodes = isReverseAxis ? Array.prototype.unshift : Array.prototype.push;
  573. }
  574. NodeMultiSet.prototype = {
  575. pushSeries: function pushSeries() {
  576. this.nextPos.push(1);
  577. this.seriesIndexes.push(this.nodes.length);
  578. },
  579. popSeries: function popSeries() {
  580. console.assert(0 < this.nextPos.length, this.nextPos);
  581. var last = this.nextPos.pop() - 1,
  582. indexInPos = this.nextPos.length,
  583. seriesBeginIndex = this.seriesIndexes.pop(),
  584. seriesEndIndex = this.nodes.length;
  585. for (var i = seriesBeginIndex; i < seriesEndIndex; ++i) {
  586. console.assert(indexInPos < this.lasts[i].length);
  587. console.assert(undefined === this.lasts[i][indexInPos]);
  588. this.lasts[i][indexInPos] = last;
  589. }
  590. },
  591. finalize: function() {
  592. if (null == this.nextPos) return this;
  593. console.assert(0 === this.nextPos.length);
  594. for (var i = 0; i < this.lasts.length; ++i) {
  595. for (var j = 0; j < this.lasts[i].length; ++j) {
  596. console.assert(null != this.lasts[i][j], i + ',' + j + ':' + JSON.stringify(this.lasts));
  597. }
  598. }
  599. this.pushSeries = this.popSeries = this.addNode = function() {
  600. throw new Error('Already finalized.');
  601. };
  602. return this;
  603. },
  604. addNode: function addNode(node) {
  605. console.assert(node);
  606. this._pushToNodes.call(this.nodes, node)
  607. this._pushToNodes.call(this.pos, this.nextPos.slice());
  608. this._pushToNodes.call(this.lasts, new Array(this.nextPos.length));
  609. for (var i = 0; i < this.nextPos.length; ++i) this.nextPos[i]++;
  610. },
  611. simplify: function() {
  612. this.finalize();
  613. return {nodes:this.nodes, pos:this.pos, lasts:this.lasts};
  614. }
  615. };
  616. function eachContext(nodeMultiSet) {
  617. var r = [];
  618. for (var i = 0; i < nodeMultiSet.nodes.length; i++) {
  619. var node = nodeMultiSet.nodes[i];
  620. if (!nodeMultiSet.pos) {
  621. r.push({nodes:[node], pos: [[i + 1]], lasts: [[nodeMultiSet.nodes.length]]});
  622. } else {
  623. for (var j = 0; j < nodeMultiSet.pos[i].length; ++j) {
  624. r.push({nodes:[node], pos: [[nodeMultiSet.pos[i][j]]], lasts: [[nodeMultiSet.lasts[i][j]]]});
  625. }
  626. }
  627. }
  628. return r;
  629. }
  630. /** Matcher used in the axes.
  631. */
  632. function NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase) {
  633. this.nodeTypeNum = nodeTypeNum;
  634. this.nodeName = nodeName;
  635. this.shouldLowerCase = shouldLowerCase;
  636. this.nodeNameTest =
  637. null == nodeName ? this._alwaysTrue :
  638. shouldLowerCase ? this._nodeNameLowerCaseEquals :
  639. this._nodeNameEquals;
  640. }
  641. NodeMatcher.prototype = {
  642. matches: function matches(node) {
  643. return (0 === this.nodeTypeNum || node.nodeType === this.nodeTypeNum) &&
  644. this.nodeNameTest(node.nodeName);
  645. },
  646. _alwaysTrue: function(name) {return true;},
  647. _nodeNameEquals: function _nodeNameEquals(name) {
  648. return this.nodeName === name;
  649. },
  650. _nodeNameLowerCaseEquals: function _nodeNameLowerCaseEquals(name) {
  651. return this.nodeName === name.toLowerCase();
  652. }
  653. };
  654. function followingSiblingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, shift, peek, followingNode, andSelf, isReverseAxis) {
  655. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  656. var nodeMultiSet = new NodeMultiSet(isReverseAxis);
  657. while (0 < nodeList.length) { // can be if for following, preceding
  658. var node = shift.call(nodeList);
  659. console.assert(node != null);
  660. node = followingNode(node);
  661. nodeMultiSet.pushSeries();
  662. var numPushed = 1;
  663. while (null != node) {
  664. if (! andSelf && matcher.matches(node))
  665. nodeMultiSet.addNode(node);
  666. if (node === peek.call(nodeList)) {
  667. shift.call(nodeList);
  668. nodeMultiSet.pushSeries();
  669. numPushed++;
  670. }
  671. if (andSelf && matcher.matches(node))
  672. nodeMultiSet.addNode(node);
  673. node = followingNode(node);
  674. }
  675. while (0 < numPushed--)
  676. nodeMultiSet.popSeries();
  677. }
  678. return nodeMultiSet;
  679. }
  680. /** Returns the next non-descendant node in document order.
  681. * This is the first node in following::node(), if node is the context.
  682. */
  683. function followingNonDescendantNode(node) {
  684. if (node.ownerElement) {
  685. if (node.ownerElement.firstChild)
  686. return node.ownerElement.firstChild;
  687. node = node.ownerElement;
  688. }
  689. do {
  690. if (node.nextSibling) return node.nextSibling;
  691. } while (node = node.parentNode);
  692. return null;
  693. }
  694. /** Returns the next node in a document-order depth-first search.
  695. * See the definition of document order[1]:
  696. * 1) element
  697. * 2) namespace nodes
  698. * 3) attributes
  699. * 4) children
  700. * [1]: http://www.w3.org/TR/xpath/#dt-document-order
  701. */
  702. function followingNode(node) {
  703. if (node.ownerElement) // attributes: following node of element.
  704. node = node.ownerElement;
  705. if (null != node.firstChild)
  706. return node.firstChild;
  707. do {
  708. if (null != node.nextSibling) {
  709. return node.nextSibling;
  710. }
  711. node = node.parentNode;
  712. } while (node);
  713. return null;
  714. }
  715. /** Returns the previous node in document order (excluding attributes
  716. * and namespace nodes).
  717. */
  718. function precedingNode(node) {
  719. if (node.ownerElement)
  720. return node.ownerElement;
  721. if (null != node.previousSibling) {
  722. node = node.previousSibling;
  723. while (null != node.lastChild) {
  724. node = node.lastChild;
  725. }
  726. return node;
  727. }
  728. if (null != node.parentNode) {
  729. return node.parentNode;
  730. }
  731. return null;
  732. }
  733. /** This axis is inefficient if there are many nodes in the nodeList.
  734. * But I think it's a pretty useless axis so it's ok. */
  735. function followingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  736. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  737. var nodeMultiSet = new NodeMultiSet(false);
  738. var cursor = nodeList[0];
  739. var unorderedFollowingStarts = [];
  740. for (var i = 0; i < nodeList.length; i++) {
  741. var node = nodeList[i];
  742. var start = followingNonDescendantNode(node);
  743. if (start)
  744. unorderedFollowingStarts.push(start);
  745. }
  746. if (0 === unorderedFollowingStarts.length)
  747. return {nodes:[]};
  748. var pos = [], nextPos = [];
  749. var started = 0;
  750. while (cursor = followingNode(cursor)) {
  751. for (var i = unorderedFollowingStarts.length - 1; i >= 0; i--){
  752. if (cursor === unorderedFollowingStarts[i]) {
  753. nodeMultiSet.pushSeries();
  754. unorderedFollowingStarts.splice(i,i+1);
  755. started++;
  756. }
  757. }
  758. if (started && matcher.matches(cursor)) {
  759. nodeMultiSet.addNode(cursor);
  760. }
  761. }
  762. console.assert(0 === unorderedFollowingStarts.length);
  763. for (var i = 0; i < started; i++)
  764. nodeMultiSet.popSeries();
  765. return nodeMultiSet.finalize();
  766. }
  767. function precedingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  768. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  769. var cursor = nodeList.pop();
  770. if (null == cursor) return {nodes:{}};
  771. var r = {nodes:[], pos:[], lasts:[]};
  772. var nextParents = [cursor.parentNode || cursor.ownerElement], nextPos = [1];
  773. while (cursor = precedingNode(cursor)) {
  774. if (cursor === nodeList[nodeList.length - 1]) {
  775. nextParents.push(nodeList.pop());
  776. nextPos.push(1);
  777. }
  778. var matches = matcher.matches(cursor);
  779. var pos, someoneUsed = false;
  780. if (matches)
  781. pos = nextPos.slice();
  782. for (var i = 0; i < nextParents.length; ++i) {
  783. if (cursor === nextParents[i]) {
  784. nextParents[i] = cursor.parentNode || cursor.ownerElement;
  785. if (matches) {
  786. pos[i] = null;
  787. }
  788. } else {
  789. if (matches) {
  790. pos[i] = nextPos[i]++;
  791. someoneUsed = true;
  792. }
  793. }
  794. }
  795. if (someoneUsed) {
  796. r.nodes.unshift(cursor);
  797. r.pos.unshift(pos);
  798. }
  799. }
  800. for (var i = 0; i < r.pos.length; ++i) {
  801. var lasts = [];
  802. r.lasts.push(lasts);
  803. for (var j = r.pos[i].length - 1; j >= 0; j--) {
  804. if (null == r.pos[i][j]) {
  805. r.pos[i].splice(j, j+1);
  806. } else {
  807. lasts.unshift(nextPos[j] - 1);
  808. }
  809. }
  810. }
  811. return r;
  812. }
  813. /** node-set, axis -> node-set */
  814. function descendantDfs(nodeMultiSet, node, remaining, matcher, andSelf, attrIndices, attrNodes) {
  815. while (0 < remaining.length && null != remaining[0].ownerElement) {
  816. var attr = remaining.shift();
  817. if (andSelf && matcher.matches(attr)) {
  818. attrNodes.push(attr);
  819. attrIndices.push(nodeMultiSet.nodes.length);
  820. }
  821. }
  822. if (null != node && !andSelf) {
  823. if (matcher.matches(node))
  824. nodeMultiSet.addNode(node);
  825. }
  826. var pushed = false;
  827. if (null == node) {
  828. if (0 === remaining.length) return;
  829. node = remaining.shift();
  830. nodeMultiSet.pushSeries();
  831. pushed = true;
  832. } else if (0 < remaining.length && node === remaining[0]) {
  833. nodeMultiSet.pushSeries();
  834. pushed = true;
  835. remaining.shift();
  836. }
  837. if (andSelf) {
  838. if (matcher.matches(node))
  839. nodeMultiSet.addNode(node);
  840. }
  841. // TODO: use optimization. Also try element.getElementsByTagName
  842. // var nodeList = 1 === nodeTypeNum && null != node.children ? node.children : node.childNodes;
  843. var nodeList = node.childNodes;
  844. for (var j = 0; j < nodeList.length; ++j) {
  845. var child = nodeList[j];
  846. descendantDfs(nodeMultiSet, child, remaining, matcher, andSelf, attrIndices, attrNodes);
  847. }
  848. if (pushed) {
  849. nodeMultiSet.popSeries();
  850. }
  851. }
  852. function descenantHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, andSelf) {
  853. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  854. var nodeMultiSet = new NodeMultiSet(false);
  855. var attrIndices = [], attrNodes = [];
  856. while (0 < nodeList.length) {
  857. // var node = nodeList.shift();
  858. descendantDfs(nodeMultiSet, null, nodeList, matcher, andSelf, attrIndices, attrNodes);
  859. }
  860. nodeMultiSet.finalize();
  861. for (var i = attrNodes.length-1; i >= 0; --i) {
  862. nodeMultiSet.nodes.splice(attrIndices[i], attrIndices[i], attrNodes[i]);
  863. nodeMultiSet.pos.splice(attrIndices[i], attrIndices[i], [1]);
  864. nodeMultiSet.lasts.splice(attrIndices[i], attrIndices[i], [1]);
  865. }
  866. return nodeMultiSet;
  867. }
  868. /**
  869. */
  870. function ancestorHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, andSelf) {
  871. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  872. var ancestors = []; // array of non-empty arrays of matching ancestors
  873. for (var i = 0; i < nodeList.length; ++i) {
  874. var node = nodeList[i];
  875. var isFirst = true;
  876. var a = [];
  877. while (null != node) {
  878. if (!isFirst || andSelf) {
  879. if (matcher.matches(node))
  880. a.push(node);
  881. }
  882. isFirst = false;
  883. node = node.parentNode || node.ownerElement;
  884. }
  885. if (0 < a.length)
  886. ancestors.push(a);
  887. }
  888. var lasts = [];
  889. for (var i = 0; i < ancestors.length; ++i) lasts.push(ancestors[i].length);
  890. var nodeMultiSet = new NodeMultiSet(true);
  891. var newCtx = {nodes:[], pos:[], lasts:[]};
  892. while (0 < ancestors.length) {
  893. var pos = [ancestors[0].length];
  894. var last = [lasts[0]];
  895. var node = ancestors[0].pop();
  896. for (var i = ancestors.length - 1; i > 0; --i) {
  897. if (node === ancestors[i][ancestors[i].length - 1]) {
  898. pos.push(ancestors[i].length);
  899. last.push(lasts[i]);
  900. ancestors[i].pop();
  901. if (0 === ancestors[i].length) {
  902. ancestors.splice(i, i+1);
  903. lasts.splice(i, i+1);
  904. }
  905. }
  906. }
  907. if (0 === ancestors[0].length) {
  908. ancestors.shift();
  909. lasts.shift();
  910. }
  911. newCtx.nodes.push(node);
  912. newCtx.pos.push(pos);
  913. newCtx.lasts.push(last);
  914. }
  915. return newCtx;
  916. }
  917. /** Helper function for sortDocumentOrder. Returns a list of indices, from the
  918. * node to the root, of positions within parent.
  919. * For convenience, the node is the first element of the array.
  920. */
  921. function addressVector(node) {
  922. var r = [node];
  923. if (null != node.ownerElement) {
  924. node = node.ownerElement;
  925. r.push(-1);
  926. }
  927. while (null != node) {
  928. var i = 0;
  929. while (null != node.previousSibling) {
  930. node = node.previousSibling;
  931. i++;
  932. }
  933. r.push(i);
  934. node = node.parentNode
  935. }
  936. return r;
  937. }
  938. function addressComparator(a, b) {
  939. var minlen = Math.min(a.length - 1, b.length - 1), // not including [0]=node
  940. alen = a.length,
  941. blen = b.length;
  942. if (a[0] === b[0]) return 0;
  943. var c;
  944. for (var i = 0; i < minlen; ++i) {
  945. c = a[alen - i - 1] - b[blen - i - 1];
  946. if (0 !== c)
  947. break;
  948. }
  949. if (null == c || 0 === c) {
  950. // All equal until one of the nodes. The longer one is the descendant.
  951. c = alen - blen;
  952. }
  953. if (0 === c)
  954. c = a.nodeName - b.nodeName;
  955. if (0 === c)
  956. c = 1;
  957. return c;
  958. }
  959. var sortUniqDocumentOrder = xpath.sortUniqDocumentOrder = function(nodes) {
  960. var a = [];
  961. for (var i = 0; i < nodes.length; i++) {
  962. var node = nodes[i];
  963. var v = addressVector(node);
  964. a.push(v);
  965. }
  966. a.sort(addressComparator);
  967. var b = [];
  968. for (var i = 0; i < a.length; i++) {
  969. if (0 < i && a[i][0] === a[i - 1][0])
  970. continue;
  971. b.push(a[i][0]);
  972. }
  973. return b;
  974. }
  975. /** Sort node multiset. Does not do any de-duping. */
  976. function sortNodeMultiSet(nodeMultiSet) {
  977. var a = [];
  978. for (var i = 0; i < nodeMultiSet.nodes.length; i++) {
  979. var v = addressVector(nodeMultiSet.nodes[i]);
  980. a.push({v:v, n:nodeMultiSet.nodes[i],
  981. p:nodeMultiSet.pos[i], l:nodeMultiSet.lasts[i]});
  982. }
  983. a.sort(compare);
  984. var r = {nodes:[], pos:[], lasts:[]};
  985. for (var i = 0; i < a.length; ++i) {
  986. r.nodes.push(a[i].n);
  987. r.pos.push(a[i].p);
  988. r.lasts.push(a[i].l);
  989. }
  990. function compare(x, y) {
  991. return addressComparator(x.v, y.v);
  992. }
  993. return r;
  994. }
  995. /** Returns an array containing all the ancestors down to a node.
  996. * The array starts with document.
  997. */
  998. function nodeAndAncestors(node) {
  999. var ancestors = [node];
  1000. var p = node;
  1001. while (p = p.parentNode || p.ownerElement) {
  1002. ancestors.unshift(p);
  1003. }
  1004. return ancestors;
  1005. }
  1006. function compareSiblings(a, b) {
  1007. if (a === b) return 0;
  1008. var c = a;
  1009. while (c = c.previousSibling) {
  1010. if (c === b)
  1011. return 1; // b < a
  1012. }
  1013. c = b;
  1014. while (c = c.previousSibling) {
  1015. if (c === a)
  1016. return -1; // a < b
  1017. }
  1018. throw new Error('a and b are not siblings: ' + xpath.stringifyObject(a) + ' vs ' + xpath.stringifyObject(b));
  1019. }
  1020. /** The merge in merge-sort.*/
  1021. function mergeNodeLists(x, y) {
  1022. var a, b, aanc, banc, r = [];
  1023. if ('object' !== typeof x)
  1024. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  1025. 'Invalid LHS for | operator ' +
  1026. '(expected node-set): ' + x);
  1027. if ('object' !== typeof y)
  1028. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  1029. 'Invalid LHS for | operator ' +
  1030. '(expected node-set): ' + y);
  1031. while (true) {
  1032. if (null == a) {
  1033. a = x.shift();
  1034. if (null != a)
  1035. aanc = addressVector(a);
  1036. }
  1037. if (null == b) {
  1038. b = y.shift();
  1039. if (null != b)
  1040. banc = addressVector(b);
  1041. }
  1042. if (null == a || null == b) break;
  1043. var c = addressComparator(aanc, banc);
  1044. if (c < 0) {
  1045. r.push(a);
  1046. a = null;
  1047. aanc = null;
  1048. } else if (c > 0) {
  1049. r.push(b);
  1050. b = null;
  1051. banc = null;
  1052. } else if (a.nodeName < b.nodeName) { // attributes
  1053. r.push(a);
  1054. a = null;
  1055. aanc = null;
  1056. } else if (a.nodeName > b.nodeName) { // attributes
  1057. r.push(b);
  1058. b = null;
  1059. banc = null;
  1060. } else if (a !== b) {
  1061. // choose b arbitrarily
  1062. r.push(b);
  1063. b = null;
  1064. banc = null;
  1065. } else {
  1066. console.assert(a === b, c);
  1067. // just skip b without pushing it.
  1068. b = null;
  1069. banc = null;
  1070. }
  1071. }
  1072. while (a) {
  1073. r.push(a);
  1074. a = x.shift();
  1075. }
  1076. while (b) {
  1077. r.push(b);
  1078. b = y.shift();
  1079. }
  1080. return r;
  1081. }
  1082. function comparisonHelper(test, x, y, isNumericComparison) {
  1083. var coersion;
  1084. if (isNumericComparison)
  1085. coersion = fn.number;
  1086. else coersion =
  1087. 'boolean' === typeof x || 'boolean' === typeof y ? fn['boolean'] :
  1088. 'number' === typeof x || 'number' === typeof y ? fn.number :
  1089. fn.string;
  1090. if ('object' === typeof x && 'object' === typeof y) {
  1091. var aMap = {};
  1092. for (var i = 0; i < x.nodes.length; ++i) {
  1093. var xi = coersion({nodes:[x.nodes[i]]});
  1094. for (var j = 0; j < y.nodes.length; ++j) {
  1095. var yj = coersion({nodes:[y.nodes[j]]});
  1096. if (test(xi, yj)) return true;
  1097. }
  1098. }
  1099. return false;
  1100. } else if ('object' === typeof x && x.nodes && x.nodes.length) {
  1101. for (var i = 0; i < x.nodes.length; ++i) {
  1102. var xi = coersion({nodes:[x.nodes[i]]}), yc = coersion(y);
  1103. if (test(xi, yc))
  1104. return true;
  1105. }
  1106. return false;
  1107. } else if ('object' === typeof y && x.nodes && x.nodes.length) {
  1108. for (var i = 0; i < x.nodes.length; ++i) {
  1109. var yi = coersion({nodes:[y.nodes[i]]}), xc = coersion(x);
  1110. if (test(xc, yi))
  1111. return true;
  1112. }
  1113. return false;
  1114. } else {
  1115. var xc = coersion(x), yc = coersion(y);
  1116. return test(xc, yc);
  1117. }
  1118. }
  1119. var axes = xpath.axes = {
  1120. 'ancestor':
  1121. function ancestor(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1122. return ancestorHelper(
  1123. nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, false);
  1124. },
  1125. 'ancestor-or-self':
  1126. function ancestorOrSelf(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1127. return ancestorHelper(
  1128. nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, true);
  1129. },
  1130. 'attribute':
  1131. function attribute(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1132. // TODO: figure out whether positions should be undefined here.
  1133. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  1134. var nodeMultiSet = new NodeMultiSet(false);
  1135. if (null != nodeName) {
  1136. // TODO: with namespace
  1137. for (var i = 0; i < nodeList.length; ++i) {
  1138. var node = nodeList[i];
  1139. if (null == node.getAttributeNode)
  1140. continue; // only Element has .getAttributeNode
  1141. var attr = node.getAttributeNode(nodeName);
  1142. if (null != attr && matcher.matches(attr)) {
  1143. nodeMultiSet.pushSeries();
  1144. nodeMultiSet.addNode(attr);
  1145. nodeMultiSet.popSeries();
  1146. }
  1147. }
  1148. } else {
  1149. for (var i = 0; i < nodeList.length; ++i) {
  1150. var node = nodeList[i];
  1151. if (null != node.attributes) {
  1152. nodeMultiSet.pushSeries();
  1153. for (var j = 0; j < node.attributes.length; j++) { // all nodes have .attributes
  1154. var attr = node.attributes[j];
  1155. if (matcher.matches(attr)) // TODO: I think this check is unnecessary
  1156. nodeMultiSet.addNode(attr);
  1157. }
  1158. nodeMultiSet.popSeries();
  1159. }
  1160. }
  1161. }
  1162. return nodeMultiSet.finalize();
  1163. },
  1164. 'child':
  1165. function child(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1166. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  1167. var nodeMultiSet = new NodeMultiSet(false);
  1168. for (var i = 0; i < nodeList.length; ++i) {
  1169. var n = nodeList[i];
  1170. if (n.ownerElement) // skip attribute nodes' text child.
  1171. continue;
  1172. if (n.childNodes) {
  1173. nodeMultiSet.pushSeries();
  1174. var childList = 1 === nodeTypeNum && null != n.children ?
  1175. n.children : n.childNodes;
  1176. for (var j = 0; j < childList.length; ++j) {
  1177. var child = childList[j];
  1178. if (matcher.matches(child)) {
  1179. nodeMultiSet.addNode(child);
  1180. }
  1181. // don't have to do de-duping because children have parent,
  1182. // which are current context.
  1183. }
  1184. nodeMultiSet.popSeries();
  1185. }
  1186. }
  1187. nodeMultiSet.finalize();
  1188. r = sortNodeMultiSet(nodeMultiSet);
  1189. return r;
  1190. },
  1191. 'descendant':
  1192. function descenant(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1193. return descenantHelper(
  1194. nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, false);
  1195. },
  1196. 'descendant-or-self':
  1197. function descenantOrSelf(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1198. return descenantHelper(
  1199. nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase, true);
  1200. },
  1201. 'following':
  1202. function following(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1203. return followingHelper(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
  1204. },
  1205. 'following-sibling':
  1206. function followingSibling(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1207. return followingSiblingHelper(
  1208. nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase,
  1209. Array.prototype.shift, function() {return this[0];},
  1210. function(node) {return node.nextSibling;});
  1211. },
  1212. 'namespace':
  1213. function namespace(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1214. // TODO
  1215. },
  1216. 'parent':
  1217. function parent(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1218. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  1219. var nodes = [], pos = [];
  1220. for (var i = 0; i < nodeList.length; ++i) {
  1221. var parent = nodeList[i].parentNode || nodeList[i].ownerElement;
  1222. if (null == parent)
  1223. continue;
  1224. if (!matcher.matches(parent))
  1225. continue;
  1226. if (nodes.length > 0 && parent === nodes[nodes.length-1])
  1227. continue;
  1228. nodes.push(parent);
  1229. pos.push([1]);
  1230. }
  1231. return {nodes:nodes, pos:pos, lasts:pos};
  1232. },
  1233. 'preceding':
  1234. function preceding(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1235. return precedingHelper(
  1236. nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
  1237. },
  1238. 'preceding-sibling':
  1239. function precedingSibling(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1240. return followingSiblingHelper(
  1241. nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase,
  1242. Array.prototype.pop, function() {return this[this.length-1];},
  1243. function(node) {return node.previousSibling},
  1244. false, true);
  1245. },
  1246. 'self':
  1247. function self(nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase) {
  1248. var nodes = [], pos = [];
  1249. var matcher = new NodeMatcher(nodeTypeNum, nodeName, shouldLowerCase);
  1250. for (var i = 0; i < nodeList.length; ++i) {
  1251. if (matcher.matches(nodeList[i])) {
  1252. nodes.push(nodeList[i]);
  1253. pos.push([1]);
  1254. }
  1255. }
  1256. return {nodes: nodes, pos: pos, lasts: pos}
  1257. }
  1258. };
  1259. /***************************************************************************
  1260. * Evaluation: functions *
  1261. ***************************************************************************/
  1262. var fn = {
  1263. 'number': function number(optObject) {
  1264. if ('number' === typeof optObject)
  1265. return optObject;
  1266. if ('string' === typeof optObject)
  1267. return parseFloat(optObject); // note: parseFloat(' ') -> NaN, unlike +' ' -> 0.
  1268. if ('boolean' === typeof optObject)
  1269. return +optObject;
  1270. return fn.number(fn.string.call(this, optObject)); // for node-sets
  1271. },
  1272. 'string': function string(optObject) {
  1273. if (null == optObject)
  1274. return fn.string(this);
  1275. if ('string' === typeof optObject || 'boolean' === typeof optObject ||
  1276. 'number' === typeof optObject)
  1277. return '' + optObject;
  1278. if (0 == optObject.nodes.length) return '';
  1279. if (null != optObject.nodes[0].textContent)
  1280. return optObject.nodes[0].textContent;
  1281. return optObject.nodes[0].nodeValue;
  1282. },
  1283. 'boolean': function booleanVal(x) {
  1284. return 'object' === typeof x ? x.nodes.length > 0 : !!x;
  1285. },
  1286. 'last': function last() {
  1287. console.assert(Array.isArray(this.pos));
  1288. console.assert(Array.isArray(this.lasts));
  1289. console.assert(1 === this.pos.length);
  1290. console.assert(1 === this.lasts.length);
  1291. console.assert(1 === this.lasts[0].length);
  1292. return this.lasts[0][0];
  1293. },
  1294. 'position': function position() {
  1295. console.assert(Array.isArray(this.pos));
  1296. console.assert(Array.isArray(this.lasts));
  1297. console.assert(1 === this.pos.length);
  1298. console.assert(1 === this.lasts.length);
  1299. console.assert(1 === this.pos[0].length);
  1300. return this.pos[0][0];
  1301. },
  1302. 'count': function count(nodeSet) {
  1303. if ('object' !== typeof nodeSet)
  1304. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  1305. 'Position ' + stream.position() +
  1306. ': Function count(node-set) ' +
  1307. 'got wrong argument type: ' + nodeSet);
  1308. return nodeSet.nodes.length;
  1309. },
  1310. 'id': function id(object) {
  1311. var r = {nodes: []};
  1312. var doc = this.nodes[0].ownerDocument || this.nodes[0];
  1313. console.assert(doc);
  1314. var ids;
  1315. if ('object' === typeof object) {
  1316. // for node-sets, map id over each node value.
  1317. ids = [];
  1318. for (var i = 0; i < object.nodes.length; ++i) {
  1319. var idNode = object.nodes[i];
  1320. var idsString = fn.string({nodes:[idNode]});
  1321. var a = idsString.split(/[ \t\r\n]+/g);
  1322. Array.prototype.push.apply(ids, a);
  1323. }
  1324. } else {
  1325. var idsString = fn.string(object);
  1326. var a = idsString.split(/[ \t\r\n]+/g);
  1327. ids = a;
  1328. }
  1329. for (var i = 0; i < ids.length; ++i) {
  1330. var id = ids[i];
  1331. if (0 === id.length)
  1332. continue;
  1333. var node = doc.getElementById(id);
  1334. if (null != node)
  1335. r.nodes.push(node);
  1336. }
  1337. r.nodes = sortUniqDocumentOrder(r.nodes);
  1338. return r;
  1339. },
  1340. 'local-name': function(nodeSet) {
  1341. if (null == nodeSet)
  1342. return fn.name(this);
  1343. if (null == nodeSet.nodes) {
  1344. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  1345. 'argument to name() must be a node-set. got ' + nodeSet);
  1346. }
  1347. // TODO: namespaced version
  1348. return nodeSet.nodes[0].nodeName.toLowerCase(); // TODO: no toLowerCase for xml
  1349. },
  1350. 'namespace-uri': function(nodeSet) {
  1351. // TODO
  1352. throw new Error('not implemented yet');
  1353. },
  1354. 'name': function(nodeSet) {
  1355. if (null == nodeSet)
  1356. return fn.name(this);
  1357. if (null == nodeSet.nodes) {
  1358. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  1359. 'argument to name() must be a node-set. got ' + nodeSet);
  1360. }
  1361. return nodeSet.nodes[0].nodeName.toLowerCase(); // TODO: no toLowerCase for xml
  1362. },
  1363. 'concat': function concat(x) {
  1364. var l = [];
  1365. for (var i = 0; i < arguments.length; ++i) {
  1366. l.push(fn.string(arguments[i]));
  1367. }
  1368. return l.join('');
  1369. },
  1370. 'starts-with': function startsWith(a, b) {
  1371. var as = fn.string(a), bs = fn.string(b);
  1372. return as.substr(0, bs.length) === bs;
  1373. },
  1374. 'contains': function contains(a, b) {
  1375. var as = fn.string(a), bs = fn.string(b);
  1376. var i = as.indexOf(bs);
  1377. if (-1 === i) return false;
  1378. return true;
  1379. },
  1380. 'substring-before': function substringBefore(a, b) {
  1381. var as = fn.string(a), bs = fn.string(b);
  1382. var i = as.indexOf(bs);
  1383. if (-1 === i) return '';
  1384. return as.substr(0, i);
  1385. },
  1386. 'substring-after': function substringBefore(a, b) {
  1387. var as = fn.string(a), bs = fn.string(b);
  1388. var i = as.indexOf(bs);
  1389. if (-1 === i) return '';
  1390. return as.substr(i + bs.length);
  1391. },
  1392. 'substring': function substring(string, start, optEnd) {
  1393. if (null == string || null == start) {
  1394. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  1395. 'Must be at least 2 arguments to string()');
  1396. }
  1397. var sString = fn.string(string),
  1398. iStart = fn.round(start),
  1399. iEnd = optEnd == null ? null : fn.round(optEnd);
  1400. // Note that xpath string positions user 1-based index
  1401. if (iEnd == null)
  1402. return sString.substr(iStart - 1);
  1403. else
  1404. return sString.substr(iStart - 1, iEnd);
  1405. },
  1406. 'string-length': function stringLength(optString) {
  1407. return fn.string.call(this, optString).length;
  1408. },
  1409. 'normalize-space': function normalizeSpace(optString) {
  1410. var s = fn.string.call(this, optString);
  1411. return s.replace(/[ \t\r\n]+/g, ' ').replace(/^ | $/g, '');
  1412. },
  1413. 'translate': function translate(string, from, to) {
  1414. var sString = fn.string.call(this, string),
  1415. sFrom = fn.string(from),
  1416. sTo = fn.string(to);
  1417. var eachCharRe = [];
  1418. var map = {};
  1419. for (var i = 0; i < sFrom.length; ++i) {
  1420. var c = sFrom.charAt(i);
  1421. map[c] = sTo.charAt(i); // returns '' if beyond length of sTo.
  1422. // copied from goog.string.regExpEscape in the Closure library.
  1423. eachCharRe.push(
  1424. c.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1').
  1425. replace(/\x08/g, '\\x08'));
  1426. }
  1427. var re = new RegExp(eachCharRe.join('|'), 'g');
  1428. return sString.replace(re, function(c) {return map[c];});
  1429. },
  1430. /// Boolean functions
  1431. 'not': function not(x) {
  1432. var bx = fn['boolean'](x);
  1433. return !bx;
  1434. },
  1435. 'true': function trueVal() { return true; },
  1436. 'false': function falseVal() { return false; },
  1437. // TODO
  1438. 'lang': function lang(string) { throw new Error('Not implemented');},
  1439. 'sum': function sum(optNodeSet) {
  1440. if (null == optNodeSet) return fn.sum(this);
  1441. // for node-sets, map id over each node value.
  1442. var sum = 0;
  1443. for (var i = 0; i < optNodeSet.nodes.length; ++i) {
  1444. var node = optNodeSet.nodes[i];
  1445. var x = fn.number({nodes:[node]});
  1446. sum += x;
  1447. }
  1448. return sum;
  1449. },
  1450. 'floor': function floor(number) {
  1451. return Math.floor(fn.number(number));
  1452. },
  1453. 'ceiling': function ceiling(number) {
  1454. return Math.ceil(fn.number(number));
  1455. },
  1456. 'round': function round(number) {
  1457. return Math.round(fn.number(number));
  1458. }
  1459. };
  1460. /***************************************************************************
  1461. * Evaluation: operators *
  1462. ***************************************************************************/
  1463. var more = {
  1464. UnaryMinus: function(x) { return -fn.number(x); },
  1465. '+': function(x, y) { return fn.number(x) + fn.number(y); },
  1466. '-': function(x, y) { return fn.number(x) - fn.number(y); },
  1467. '*': function(x, y) { return fn.number(x) * fn.number(y); },
  1468. 'div': function(x, y) { return fn.number(x) / fn.number(y); },
  1469. 'mod': function(x, y) { return fn.number(x) % fn.number(y); },
  1470. '<': function(x, y) {
  1471. return comparisonHelper(function(x, y) { return fn.number(x) < fn.number(y);}, x, y, true);
  1472. },
  1473. '<=': function(x, y) {
  1474. return comparisonHelper(function(x, y) { return fn.number(x) <= fn.number(y);}, x, y, true);
  1475. },
  1476. '>': function(x, y) {
  1477. return comparisonHelper(function(x, y) { return fn.number(x) > fn.number(y);}, x, y, true);
  1478. },
  1479. '>=': function(x, y) {
  1480. return comparisonHelper(function(x, y) { return fn.number(x) >= fn.number(y);}, x, y, true);
  1481. },
  1482. 'and': function(x, y) { return fn['boolean'](x) && fn['boolean'](y); },
  1483. 'or': function(x, y) { return fn['boolean'](x) || fn['boolean'](y); },
  1484. '|': function(x, y) { return {nodes: mergeNodeLists(x.nodes, y.nodes)}; },
  1485. '=': function(x, y) {
  1486. // optimization for two node-sets case: avoid n^2 comparisons.
  1487. if ('object' === typeof x && 'object' === typeof y) {
  1488. var aMap = {};
  1489. for (var i = 0; i < x.nodes.length; ++i) {
  1490. var s = fn.string({nodes:[x.nodes[i]]});
  1491. aMap[s] = true;
  1492. }
  1493. for (var i = 0; i < y.nodes.length; ++i) {
  1494. var s = fn.string({nodes:[y.nodes[i]]});
  1495. if (aMap[s]) return true;
  1496. }
  1497. return false;
  1498. } else {
  1499. return comparisonHelper(function(x, y) {return x === y;}, x, y);
  1500. }
  1501. },
  1502. '!=': function(x, y) {
  1503. // optimization for two node-sets case: avoid n^2 comparisons.
  1504. if ('object' === typeof x && 'object' === typeof y) {
  1505. if (0 === x.nodes.length || 0 === y.nodes.length) return false;
  1506. var aMap = {};
  1507. for (var i = 0; i < x.nodes.length; ++i) {
  1508. var s = fn.string({nodes:[x.nodes[i]]});
  1509. aMap[s] = true;
  1510. }
  1511. for (var i = 0; i < y.nodes.length; ++i) {
  1512. var s = fn.string({nodes:[y.nodes[i]]});
  1513. if (!aMap[s]) return true;
  1514. }
  1515. return false;
  1516. } else {
  1517. return comparisonHelper(function(x, y) {return x !== y;}, x, y);
  1518. }
  1519. }
  1520. };
  1521. var nodeTypes = xpath.nodeTypes = {
  1522. 'node': 0,
  1523. 'attribute': 2,
  1524. 'comment': 8, // this.doc.COMMENT_NODE,
  1525. 'text': 3, // this.doc.TEXT_NODE,
  1526. 'processing-instruction': 7, // this.doc.PROCESSING_INSTRUCTION_NODE,
  1527. 'element': 1 //this.doc.ELEMENT_NODE
  1528. };
  1529. /** For debugging and unit tests: returnjs a stringified version of the
  1530. * argument. */
  1531. var stringifyObject = xpath.stringifyObject = function stringifyObject(ctx) {
  1532. var seenKey = 'seen' + Math.floor(Math.random()*1000000000);
  1533. return JSON.stringify(helper(ctx));
  1534. function helper(ctx) {
  1535. if (Array.isArray(ctx)) {
  1536. return ctx.map(function(x) {return helper(x);});
  1537. }
  1538. if ('object' !== typeof ctx) return ctx;
  1539. if (null == ctx) return ctx;
  1540. // if (ctx.toString) return ctx.toString();
  1541. if (null != ctx.outerHTML) return ctx.outerHTML;
  1542. if (null != ctx.nodeValue) return ctx.nodeName + '=' + ctx.nodeValue;
  1543. if (ctx[seenKey]) return '[circular]';
  1544. ctx[seenKey] = true;
  1545. var nicer = {};
  1546. for (var key in ctx) {
  1547. if (seenKey === key)
  1548. continue;
  1549. try {
  1550. nicer[key] = helper(ctx[key]);
  1551. } catch (e) {
  1552. nicer[key] = '[exception: ' + e.message + ']';
  1553. }
  1554. }
  1555. delete ctx[seenKey];
  1556. return nicer;
  1557. }
  1558. }
  1559. var Evaluator = xpath.Evaluator = function Evaluator(doc) {
  1560. this.doc = doc;
  1561. }
  1562. Evaluator.prototype = {
  1563. val: function val(ast, ctx) {
  1564. console.assert(ctx.nodes);
  1565. if ('number' === typeof ast || 'string' === typeof ast) return ast;
  1566. if (more[ast[0]]) {
  1567. var evaluatedParams = [];
  1568. for (var i = 1; i < ast.length; ++i) {
  1569. evaluatedParams.push(this.val(ast[i], ctx));
  1570. }
  1571. var r = more[ast[0]].apply(ctx, evaluatedParams);
  1572. return r;
  1573. }
  1574. switch (ast[0]) {
  1575. case 'Root': return {nodes: [this.doc]};
  1576. case 'FunctionCall':
  1577. var functionName = ast[1], functionParams = ast[2];
  1578. if (null == fn[functionName])
  1579. throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,
  1580. 'Unknown function: ' + functionName);
  1581. var evaluatedParams = [];
  1582. for (var i = 0; i < functionParams.length; ++i) {
  1583. evaluatedParams.push(this.val(functionParams[i], ctx));
  1584. }
  1585. var r = fn[functionName].apply(ctx, evaluatedParams);
  1586. return r;
  1587. case 'Predicate':
  1588. var lhs = this.val(ast[1], ctx);
  1589. var ret = {nodes: []};
  1590. var contexts = eachContext(lhs);
  1591. for (var i = 0; i < contexts.length; ++i) {
  1592. var singleNodeSet = contexts[i];
  1593. var rhs = this.val(ast[2], singleNodeSet);
  1594. var success;
  1595. if ('number' === typeof rhs) {
  1596. success = rhs === singleNodeSet.pos[0][0];
  1597. } else {
  1598. success = fn['boolean'](rhs);
  1599. }
  1600. if (success) {
  1601. var node = singleNodeSet.nodes[0];
  1602. ret.nodes.push(node);
  1603. // skip over all the rest of the same node.
  1604. while (i+1 < contexts.length && node === contexts[i+1].nodes[0]) {
  1605. i++;
  1606. }
  1607. }
  1608. }
  1609. return ret;
  1610. case 'PathExpr':
  1611. // turn the path into an expressoin; i.e., remove the position
  1612. // information of the last axis.
  1613. var x = this.val(ast[1], ctx);
  1614. // Make the nodeset a forward-direction-only one.
  1615. if (x.finalize) { // it is a NodeMultiSet
  1616. for (var i = 0; i < x.nodes.length; ++i) {
  1617. console.assert(null != x.nodes[i].nodeType);
  1618. }
  1619. return {nodes: x.nodes};
  1620. } else {
  1621. return x;
  1622. }
  1623. case '/':
  1624. // TODO: don't generate '/' nodes, just Axis nodes.
  1625. var lhs = this.val(ast[1], ctx);
  1626. console.assert(null != lhs);
  1627. var r = this.val(ast[2], lhs);
  1628. console.assert(null != r);
  1629. return r;
  1630. case 'Axis':
  1631. // All the axis tests from Step. We only get AxisSpecifier NodeTest,
  1632. // not the predicate (which is applied later)
  1633. var axis = ast[1],
  1634. nodeType = ast[2],
  1635. nodeTypeNum = nodeTypes[nodeType],
  1636. shouldLowerCase = true, // TODO: give option
  1637. nodeName = ast[3] && shouldLowerCase ? ast[3].toLowerCase() : ast[3];
  1638. nodeName = nodeName === '*' ? null : nodeName;
  1639. if ('object' !== typeof ctx) return {nodes:[], pos:[]};
  1640. var nodeList = ctx.nodes.slice(); // TODO: is copy needed?
  1641. var r = axes[axis](nodeList /*destructive!*/, nodeTypeNum, nodeName, shouldLowerCase);
  1642. return r;
  1643. }
  1644. }
  1645. };
  1646. var evaluate = xpath.evaluate = function evaluate(expr, doc, context) {
  1647. //var astFactory = new AstEvaluatorFactory(doc, context);
  1648. var stream = new Stream(expr);
  1649. var ast = parse(stream, astFactory);
  1650. var val = new Evaluator(doc).val(ast, {nodes: [context]});
  1651. return val;
  1652. }
  1653. /***************************************************************************
  1654. * DOM interface *
  1655. ***************************************************************************/
  1656. var XPathException = xpath.XPathException = function XPathException(code, message) {
  1657. var e = new Error(message);
  1658. this.__proto__ = e;
  1659. this.name = 'XPathException';
  1660. this.code = code;
  1661. }
  1662. XPathException.prototype = Error.prototype;
  1663. XPathException.prototype.__proto__ = XPathException;
  1664. XPathException.INVALID_EXPRESSION_ERR = 51;
  1665. XPathException.TYPE_ERR = 52;
  1666. var XPathEvaluator = xpath.XPathEvaluator = function XPathEvaluator() {}
  1667. XPathEvaluator.prototype = {
  1668. createExpression: function(expression, resolver) {
  1669. return new XPathExpression(expression, resolver);
  1670. },
  1671. createNSResolver: function(nodeResolver) {
  1672. // TODO
  1673. },
  1674. evaluate: function evaluate(expression, contextNode, resolver, type, result) {
  1675. var expr = new XPathExpression(expression, resolver);
  1676. return expr.evaluate(contextNode, type, result);
  1677. }
  1678. };
  1679. var XPathExpression = xpath.XPathExpression = function XPathExpression(expression, resolver, optDoc) {
  1680. var stream = new Stream(expression);
  1681. this._ast = parse(stream, astFactory);
  1682. this._doc = optDoc;
  1683. }
  1684. XPathExpression.prototype = {
  1685. evaluate: function evaluate(contextNode, type, result) {
  1686. if (null == contextNode.nodeType)
  1687. throw new Error('bad argument (expected context node): ' + contextNode);
  1688. var doc = contextNode.ownerDocument || contextNode;
  1689. if (null != this._doc && this._doc !== doc) {
  1690. throw new core.DOMException(
  1691. core.WRONG_DOCUMENT_ERR,
  1692. 'The document must be the same as the context node\'s document.');
  1693. }
  1694. var evaluator = new Evaluator(doc);
  1695. var value = evaluator.val(this._ast, {nodes: [contextNode]});
  1696. if (XPathResult.NUMBER_TYPE === type)
  1697. value = fn.number(value);
  1698. else if (XPathResult.STRING_TYPE === type)
  1699. value = fn.string(value);
  1700. else if (XPathResult.BOOLEAN_TYPE === type)
  1701. value = fn['boolean'](value);
  1702. else if (XPathResult.ANY_TYPE !== type &&
  1703. XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== type &&
  1704. XPathResult.ORDERED_NODE_ITERATOR_TYPE !== type &&
  1705. XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== type &&
  1706. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== type &&
  1707. XPathResult.ANY_UNORDERED_NODE_TYPE !== type &&
  1708. XPathResult.FIRST_ORDERED_NODE_TYPE !== type)
  1709. throw new core.DOMException(
  1710. core.NOT_SUPPORTED_ERR,
  1711. 'You must provide an XPath result type (0=any).');
  1712. else if (XPathResult.ANY_TYPE !== type &&
  1713. 'object' !== typeof value)
  1714. throw new XPathException(
  1715. XPathException.TYPE_ERR,
  1716. 'Value should be a node-set: ' + value);
  1717. return new XPathResult(doc, value, type);
  1718. }
  1719. }
  1720. var XPathResult = xpath.XPathResult = function XPathResult(doc, value, resultType) {
  1721. this._value = value;
  1722. this._resultType = resultType;
  1723. this._i = 0;
  1724. this._invalidated = false;
  1725. if (this.resultType === XPathResult.UNORDERED_NODE_ITERATOR_TYPE ||
  1726. this.resultType === XPathResult.ORDERED_NODE_ITERATOR_TYPE) {
  1727. doc.addEventListener('DOMSubtreeModified', invalidate, true);
  1728. var self = this;
  1729. function invalidate() {
  1730. self._invalidated = true;
  1731. doc.removeEventListener('DOMSubtreeModified', invalidate, true);
  1732. }
  1733. }
  1734. }
  1735. XPathResult.ANY_TYPE = 0;
  1736. XPathResult.NUMBER_TYPE = 1;
  1737. XPathResult.STRING_TYPE = 2;
  1738. XPathResult.BOOLEAN_TYPE = 3;
  1739. XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4;
  1740. XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5;
  1741. XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE = 6;
  1742. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE = 7;
  1743. XPathResult.ANY_UNORDERED_NODE_TYPE = 8;
  1744. XPathResult.FIRST_ORDERED_NODE_TYPE = 9;
  1745. XPathResult.prototype = {
  1746. // XPathResultType
  1747. get resultType() {
  1748. if (this._resultType) return this._resultType;
  1749. switch (typeof this._value) {
  1750. case 'number': return XPathResult.NUMBER_TYPE;
  1751. case 'string': return XPathResult.STRING_TYPE;
  1752. case 'boolean': return XPathResult.BOOLEAN_TYPE;
  1753. default: return XPathResult.UNORDERED_NODE_ITERATOR_TYPE;
  1754. }
  1755. },
  1756. get numberValue() {
  1757. if (XPathResult.NUMBER_TYPE !== this.resultType)
  1758. throw new XPathException(XPathException.TYPE_ERR,
  1759. 'You should have asked for a NUMBER_TYPE.');
  1760. return this._value;
  1761. },
  1762. get stringValue() {
  1763. if (XPathResult.STRING_TYPE !== this.resultType)
  1764. throw new XPathException(XPathException.TYPE_ERR,
  1765. 'You should have asked for a STRING_TYPE.');
  1766. return this._value;
  1767. },
  1768. get booleanValue() {
  1769. if (XPathResult.BOOLEAN_TYPE !== this.resultType)
  1770. throw new XPathException(XPathException.TYPE_ERR,
  1771. 'You should have asked for a BOOLEAN_TYPE.');
  1772. return this._value;
  1773. },
  1774. get singleNodeValue() {
  1775. if (XPathResult.ANY_UNORDERED_NODE_TYPE !== this.resultType &&
  1776. XPathResult.FIRST_ORDERED_NODE_TYPE !== this.resultType)
  1777. throw new XPathException(
  1778. XPathException.TYPE_ERR,
  1779. 'You should have asked for a FIRST_ORDERED_NODE_TYPE.');
  1780. return this._value.nodes[0] || null;
  1781. },
  1782. get invalidIteratorState() {
  1783. if (XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== this.resultType &&
  1784. XPathResult.ORDERED_NODE_ITERATOR_TYPE !== this.resultType)
  1785. return false;
  1786. return !!this._invalidated;
  1787. },
  1788. get snapshotLength() {
  1789. if (XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== this.resultType &&
  1790. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== this.resultType)
  1791. throw new XPathException(
  1792. XPathException.TYPE_ERR,
  1793. 'You should have asked for a ORDERED_NODE_SNAPSHOT_TYPE.');
  1794. return this._value.nodes.length;
  1795. },
  1796. iterateNext: function iterateNext() {
  1797. if (XPathResult.UNORDERED_NODE_ITERATOR_TYPE !== this.resultType &&
  1798. XPathResult.ORDERED_NODE_ITERATOR_TYPE !== this.resultType)
  1799. throw new XPathException(
  1800. XPathException.TYPE_ERR,
  1801. 'You should have asked for a ORDERED_NODE_ITERATOR_TYPE.');
  1802. if (this.invalidIteratorState)
  1803. throw new core.DOMException(
  1804. core.INVALID_STATE_ERR,
  1805. 'The document has been mutated since the result was returned');
  1806. return this._value.nodes[this._i++] || null;
  1807. },
  1808. snapshotItem: function snapshotItem(index) {
  1809. if (XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE !== this.resultType &&
  1810. XPathResult.ORDERED_NODE_SNAPSHOT_TYPE !== this.resultType)
  1811. throw new XPathException(
  1812. XPathException.TYPE_ERR,
  1813. 'You should have asked for a ORDERED_NODE_SNAPSHOT_TYPE.');
  1814. return this._value.nodes[index] || null;
  1815. }
  1816. };
  1817. // so you can access ANY_TYPE etc. from the instances:
  1818. XPathResult.prototype.__proto__ = XPathResult;
  1819. core.XPathException = XPathException;
  1820. core.XPathExpression = XPathExpression;
  1821. core.XPathResult = XPathResult;
  1822. core.XPathEvaluator = XPathEvaluator;
  1823. core.Document.prototype.createExpression =
  1824. XPathEvaluator.prototype.createExpression;
  1825. core.Document.prototype.createNSResolver =
  1826. XPathEvaluator.prototype.createNSResolver;
  1827. core.Document.prototype.evaluate = XPathEvaluator.prototype.evaluate;
  1828. })();