1 /**
  2  * @fileoverview A streaming XPath 3.0 implementation forked
  3  * from Google's Wicked Good XPath library. The implementation
  4  * is not yet complete.
  5  */
  6 /**
  7  * The MIT License
  8  * 
  9  * Copyright (c) 2007 Cybozu Labs, Inc.
 10  * Copyright (c) 2012 Google Inc.
 11  * 
 12  * Permission is hereby granted, free of charge, to any person obtaining a copy
 13  * of this software and associated documentation files (the "Software"), to deal
 14  * in the Software without restriction, including without limitation the rights
 15  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 16  * copies of the Software, and to permit persons to whom the Software is
 17  * furnished to do so, subject to the following conditions:
 18 
 19  * The above copyright notice and this permission notice shall be included in
 20  * all copies or substantial portions of the Software.
 21 
 22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 23  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 24  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 25  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 26  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 27  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 28  * THE SOFTWARE.
 29 */
 30 
 31 goog.provide('xrx.xpath');
 32 
 33 goog.require('xrx.xpath.Context');
 34 goog.require('xrx.xpath.FunctionCall');
 35 goog.require('xrx.xpath.Lexer');
 36 goog.require('xrx.xpath.NodeSet');
 37 goog.require('xrx.xpath.Parser');
 38 
 39 
 40 
 41 xrx.xpath.XPathFunctions = {
 42   
 43 };
 44 
 45 
 46 /**
 47  * XPath namespace resolver.
 48  *
 49  * @enum {string}
 50  */
 51 xrx.xpath.XPathNSResolver = {
 52   xml: 'http://www.w3.org/XML/1998/namespace',
 53   xmlns: 'http://www.w3.org/2000/xmlns/',
 54   xs: 'http://www.w3.org/2001/XMLSchema',
 55   fn: 'http://www.w3.org/2005/xpath-functions',
 56   err: 'http://www.w3.org/2005/xqt-errors'
 57 };
 58 
 59 
 60 
 61 /**
 62  * Adds a new prefix and URI to the static XPath context.
 63  * 
 64  * @param {!string} prefix The namespace prefix.
 65  * @param {!string} uri The namespace uri.
 66  */
 67 xrx.xpath.declareNamespace = function(prefix, uri) {
 68   if (xrx.xpath.XPathNSResolver[prefix]) throw Error('Namespace is already defined.');
 69   xrx.xpath.XPathNSResolver[prefix] = uri;
 70 };
 71 
 72 
 73 
 74 /**
 75  * Adds a custom function to the static XPath context.
 76  * 
 77  * @param {!xrx.xpath.Function} The function.
 78  */
 79 xrx.xpath.declareFunction = function(func) {
 80   var prefix = func.name.substr(0, func.name.indexOf(':'));
 81 
 82   if (prefix === '') throw Error('The empty namespace is not allowed for custom functions.');
 83   if (!xrx.xpath.XPathNSResolver[prefix]) throw Error('Namespace <' + prefix + '> is not defined.');
 84 
 85   if (xrx.xpath.XPathNSResolver[func.name]) throw Error('Function <' + func.name + '> is already defined.');
 86   xrx.xpath.XPathNSResolver[func.name] = func;
 87 
 88   xrx.xpath.FunctionCall.createFunc(func.name, func.retrunType,
 89       true, true, true, func.evaluate, func.minArgs, func.maxArgs);
 90 };
 91 
 92 
 93 
 94 /**
 95  * Enum for XPathResult types.
 96  *
 97  * @private
 98  * @enum {number}
 99  */
100 xrx.xpath.XPathResultType = {
101   ANY_TYPE: 0,
102   NUMBER_TYPE: 1,
103   STRING_TYPE: 2,
104   BOOLEAN_TYPE: 3,
105   UNORDERED_NODE_ITERATOR_TYPE: 4,
106   ORDERED_NODE_ITERATOR_TYPE: 5,
107   UNORDERED_NODE_SNAPSHOT_TYPE: 6,
108   ORDERED_NODE_SNAPSHOT_TYPE: 7,
109   ANY_UNORDERED_NODE_TYPE: 8,
110   FIRST_ORDERED_NODE_TYPE: 9
111 };
112 
113 
114 
115 /**
116  * The exported XPathExpression type.
117  *
118  * @constructor
119  * @extends {XPathExpression}
120  * @param {string} expr The expression string.
121  * @param {?(XPathNSResolver|function(string): ?string)} nsResolver
122  *     XPath namespace resolver.
123  * @private
124  */
125 xrx.xpath.XPathExpression = function(expr, nsResolver) {
126   if (!expr.length) {
127     throw Error('Empty XPath expression.');
128   }
129   var lexer = xrx.xpath.Lexer.tokenize(expr);
130   if (lexer.empty()) {
131     throw Error('Invalid XPath expression.');
132   }
133 
134   // nsResolver may either be an XPathNSResolver, which has a lookupNamespaceURI
135   // function, a custom function, or null. Standardize it to a function.
136   if (!nsResolver) {
137     nsResolver = function(string) {return null;};
138   } else if (!goog.isFunction(nsResolver)) {
139     nsResolver = goog.bind(nsResolver.lookupNamespaceURI, nsResolver);
140   }
141 
142   var gexpr = new xrx.xpath.Parser(lexer, nsResolver).parseExpr();
143   if (!lexer.empty()) {
144     throw Error('Bad token: ' + lexer.next());
145   }
146   this['evaluate'] = function(node, type) {
147     var value = gexpr.evaluate(new xrx.xpath.Context(node));
148     return new xrx.xpath.XPathResult(value, type);
149   };
150 };
151 
152 
153 
154 /**
155  * The exported XPathResult type.
156  *
157  * @constructor
158  * @extends {XPathResult}
159  * @param {(!xrx.xpath.NodeSet|number|string|boolean)} value The result value.
160  * @param {number} type The result type.
161  * @private
162  */
163 xrx.xpath.XPathResult = function(value, type) {
164   if (type == xrx.xpath.XPathResultType.ANY_TYPE) {
165     if (value instanceof xrx.xpath.NodeSet) {
166       type = xrx.xpath.XPathResultType.UNORDERED_NODE_ITERATOR_TYPE;
167     } else if (typeof value == 'string') {
168       type = xrx.xpath.XPathResultType.STRING_TYPE;
169     } else if (typeof value == 'number') {
170       type = xrx.xpath.XPathResultType.NUMBER_TYPE;
171     } else if (typeof value == 'boolean') {
172       type = xrx.xpath.XPathResultType.BOOLEAN_TYPE;
173     } else {
174       throw Error('Unexpected evaluation result.');
175     }
176   }
177   if (type != xrx.xpath.XPathResultType.STRING_TYPE &&
178       type != xrx.xpath.XPathResultType.NUMBER_TYPE &&
179       type != xrx.xpath.XPathResultType.BOOLEAN_TYPE &&
180       !(value instanceof xrx.xpath.NodeSet)) {
181     throw Error('value could not be converted to the specified type');
182   }
183   this['resultType'] = type;
184   var nodes;
185   switch (type) {
186     case xrx.xpath.XPathResultType.STRING_TYPE:
187       this['stringValue'] = (value instanceof xrx.xpath.NodeSet) ?
188           value.string() : '' + value;
189       break;
190     case xrx.xpath.XPathResultType.NUMBER_TYPE:
191       this['numberValue'] = (value instanceof xrx.xpath.NodeSet) ?
192           value.number() : +value;
193       break;
194     case xrx.xpath.XPathResultType.BOOLEAN_TYPE:
195       this['booleanValue'] = (value instanceof xrx.xpath.NodeSet) ?
196           value.getLength() > 0 : !!value;
197       break;
198     case xrx.xpath.XPathResultType.UNORDERED_NODE_ITERATOR_TYPE:
199     case xrx.xpath.XPathResultType.ORDERED_NODE_ITERATOR_TYPE:
200     case xrx.xpath.XPathResultType.UNORDERED_NODE_SNAPSHOT_TYPE:
201     case xrx.xpath.XPathResultType.ORDERED_NODE_SNAPSHOT_TYPE:
202       var iter = value.iterator();
203       nodes = [];
204       for (var node = iter.next(); node; node = iter.next()) {
205         nodes.push(node);
206       }
207       this['snapshotLength'] = value.getLength();
208       this['invalidIteratorState'] = false;
209       break;
210     case xrx.xpath.XPathResultType.ANY_UNORDERED_NODE_TYPE:
211     case xrx.xpath.XPathResultType.FIRST_ORDERED_NODE_TYPE:
212       var firstNode = value.getFirst();
213       this['singleNodeValue'] = firstNode;
214       break;
215     default:
216       throw Error('Unknown XPathResult type.');
217   }
218   var index = 0;
219   this['iterateNext'] = function() {
220     if (type != xrx.xpath.XPathResultType.UNORDERED_NODE_ITERATOR_TYPE &&
221         type != xrx.xpath.XPathResultType.ORDERED_NODE_ITERATOR_TYPE) {
222       throw Error('iterateNext called with wrong result type');
223     }
224     return (index >= nodes.length) ? null : nodes[index++];
225   };
226   this['snapshotItem'] = function(i) {
227     if (type != xrx.xpath.XPathResultType.UNORDERED_NODE_SNAPSHOT_TYPE &&
228         type != xrx.xpath.XPathResultType.ORDERED_NODE_SNAPSHOT_TYPE) {
229       throw Error('snapshotItem called with wrong result type');
230     }
231     return (i >= nodes.length || i < 0) ? null : nodes[i];
232   };
233 };
234 
235 
236 
237 xrx.xpath.evaluate = function(expr, context, nsResolver, type, result) {
238   return new xrx.xpath.XPathExpression(expr, nsResolver).
239       evaluate(context, type);
240 };
241 
242