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