1 /** 2 * @fileoverview Class for a step in a path expression. 3 */ 4 5 goog.provide('xrx.xpath.Step'); 6 7 goog.require('goog.array'); 8 goog.require('xrx.xpath.DataType'); 9 goog.require('xrx.xpath.Expr'); 10 goog.require('xrx.xpath.KindTest'); 11 goog.require('xrx.node'); 12 goog.require('xrx.xpath.Predicates'); 13 14 15 16 /** 17 * Class for a step in a path expression 18 * http://www.w3.org/TR/xpath20/#id-steps. 19 * 20 * @extends {xrx.xpath.Expr} 21 * @constructor 22 * @param {!xrx.xpath.Step.Axis} axis The axis for this Step. 23 * @param {!xrx.xpath.NodeTest} test The test for this Step. 24 * @param {!xrx.xpath.Predicates=} opt_predicates The predicates for this 25 * Step. 26 * @param {boolean=} opt_descendants Whether descendants are to be included in 27 * this step ('//' vs '/'). 28 */ 29 xrx.xpath.Step = function(axis, test, opt_predicates, opt_descendants) { 30 var axisCast = /** @type {!xrx.xpath.Step.Axis_} */ (axis); 31 xrx.xpath.Expr.call(this, xrx.xpath.DataType.NODESET); 32 33 /** 34 * @type {!xrx.xpath.Step.Axis_} 35 * @private 36 */ 37 this.axis_ = axisCast; 38 39 40 /** 41 * @type {!xrx.xpath.NodeTest} 42 * @private 43 */ 44 this.test_ = test; 45 46 /** 47 * @type {!xrx.xpath.Predicates} 48 * @private 49 */ 50 this.predicates_ = opt_predicates || new xrx.xpath.Predicates([]); 51 52 53 /** 54 * Whether decendants are included in this step 55 * 56 * @private 57 * @type {boolean} 58 */ 59 this.descendants_ = !!opt_descendants; 60 61 var quickAttrInfo = this.predicates_.getQuickAttr(); 62 if (axis.supportsQuickAttr_ && quickAttrInfo) { 63 var attrName = quickAttrInfo.name; 64 var attrValueExpr = quickAttrInfo.valueExpr; 65 this.setQuickAttr({ 66 name: attrName, 67 valueExpr: attrValueExpr 68 }); 69 } 70 this.setNeedContextPosition(this.predicates_.doesNeedContextPosition()); 71 }; 72 goog.inherits(xrx.xpath.Step, xrx.xpath.Expr); 73 74 75 /** 76 * @override 77 * @return {!xrx.xpath.NodeSet} The nodeset result. 78 */ 79 xrx.xpath.Step.prototype.evaluate = function(ctx) { 80 var node = ctx.getNode(); 81 var nodeset = null; 82 var quickAttr = this.getQuickAttr(); 83 var attrName = null; 84 var attrValue = null; 85 var pstart = 0; 86 if (quickAttr) { 87 attrName = quickAttr.name; 88 attrValue = quickAttr.valueExpr ? 89 quickAttr.valueExpr.asString(ctx) : null; 90 pstart = 1; 91 } 92 if (this.descendants_) { 93 if (!this.doesNeedContextPosition() && 94 this.axis_ == xrx.xpath.Step.Axis.CHILD) { 95 nodeset = node.getDescendantNodes(this.test_); 96 nodeset = this.predicates_.evaluatePredicates(nodeset, pstart); 97 } else { 98 var step = new xrx.xpath.Step(xrx.xpath.Step.Axis.DESCENDANT_OR_SELF, 99 new xrx.xpath.KindTest('node')); 100 var iter = step.evaluate(ctx).iterator(); 101 var n = iter.next(); 102 if (!n) { 103 nodeset = new xrx.xpath.NodeSet(); 104 } else { 105 nodeset = this.evaluate_(/** @type {!xrx.node} */ (n), 106 attrName, attrValue, pstart); 107 while ((n = iter.next()) != null) { 108 nodeset = xrx.xpath.NodeSet.merge(nodeset, 109 this.evaluate_(/** @type {!xrx.node} */ (n), attrName, 110 attrValue, pstart)); 111 } 112 } 113 } 114 } else { 115 nodeset = this.evaluate_(ctx.getNode(), attrName, attrValue, pstart); 116 } 117 return nodeset; 118 }; 119 120 121 /** 122 * Evaluates this step on the given context to a node-set. 123 * (assumes this.descendants_ = false) 124 * 125 * @private 126 * @param {!xrx.node} node The context node. 127 * @param {?string} attrName The name of the attribute. 128 * @param {?string} attrValue The value of the attribute. 129 * @param {number} pstart The first predicate to evaluate. 130 * @return {!xrx.xpath.NodeSet} The node-set from evaluating this Step. 131 */ 132 xrx.xpath.Step.prototype.evaluate_ = function( 133 node, attrName, attrValue, pstart) { 134 var nodeset = this.axis_.func_(this.test_, node, attrName, attrValue); 135 nodeset = this.predicates_.evaluatePredicates(nodeset, pstart); 136 return nodeset; 137 }; 138 139 140 /** 141 * Returns whether the step evaluation should include descendants. 142 * 143 * @return {boolean} Whether descendants are included. 144 */ 145 xrx.xpath.Step.prototype.doesIncludeDescendants = function() { 146 return this.descendants_; 147 }; 148 149 150 /** 151 * Returns the step's axis. 152 * 153 * @return {!xrx.xpath.Step.Axis} The axis. 154 */ 155 xrx.xpath.Step.prototype.getAxis = function() { 156 return /** @type {!xrx.xpath.Step.Axis} */ (this.axis_); 157 }; 158 159 160 /** 161 * Returns the test for this step. 162 * 163 * @return {!xrx.xpath.NodeTest} The test for this step. 164 */ 165 xrx.xpath.Step.prototype.getTest = function() { 166 return this.test_; 167 }; 168 169 170 /** 171 * @override 172 */ 173 xrx.xpath.Step.prototype.toString = function() { 174 var text = 'Step:'; 175 text += xrx.xpath.Expr.indent('Operator: ' + (this.descendants_ ? '//' : '/')); 176 if (this.axis_.name_) { 177 text += xrx.xpath.Expr.indent('Axis: ' + this.axis_); 178 } 179 text += xrx.xpath.Expr.indent(this.test_); 180 if (this.predicates_.getLength()) { 181 var predicates = goog.array.reduce(this.predicates_.getPredicates(), 182 function(prev, curr) { 183 return prev + xrx.xpath.Expr.indent(curr); 184 }, 'Predicates:'); 185 text += xrx.xpath.Expr.indent(predicates); 186 } 187 return text; 188 }; 189 190 191 192 /** 193 * A step axis. 194 * 195 * @constructor 196 * @param {string} name The axis name. 197 * @param {function(!xrx.xpath.NodeTest, xrx.node, ?string, ?string): 198 * !xrx.xpath.NodeSet} func The function for this axis. 199 * @param {boolean} reverse Whether to iterate over the node-set in reverse. 200 * @param {boolean} supportsQuickAttr Whether quickAttr should be enabled for 201 * this axis. 202 * @private 203 */ 204 xrx.xpath.Step.Axis_ = function(name, func, reverse, supportsQuickAttr) { 205 206 /** 207 * @private 208 * @type {string} 209 */ 210 this.name_ = name; 211 212 /** 213 * @private 214 * @type {function(!xrx.xpath.NodeTest, xrx.node, ?string, ?string): 215 * !xrx.xpath.NodeSet} 216 */ 217 this.func_ = func; 218 219 /** 220 * @private 221 * @type {boolean} 222 */ 223 this.reverse_ = reverse; 224 225 /** 226 * @private 227 * @type {boolean} 228 */ 229 this.supportsQuickAttr_ = supportsQuickAttr; 230 }; 231 232 233 /** 234 * Returns whether the nodes in the step should be iterated over in reverse. 235 * 236 * @return {boolean} Whether the nodes should be iterated over in reverse. 237 */ 238 xrx.xpath.Step.Axis_.prototype.isReverse = function() { 239 return this.reverse_; 240 }; 241 242 243 /** 244 * @override 245 */ 246 xrx.xpath.Step.Axis_.prototype.toString = function() { 247 return this.name_; 248 }; 249 250 251 /** 252 * A map from axis name to Axis. 253 * 254 * @type {!Object.<string, !xrx.xpath.Step.Axis>} 255 * @private 256 */ 257 xrx.xpath.Step.nameToAxisMap_ = {}; 258 259 260 /** 261 * Creates an axis and maps the axis's name to that axis. 262 * 263 * @param {string} name The axis name. 264 * @param {function(!xrx.xpath.NodeTest, xrx.node, ?string, ?string): 265 * !xrx.xpath.NodeSet} func The function for this axis. 266 * @param {boolean} reverse Whether to iterate over nodesets in reverse. 267 * @param {boolean=} opt_supportsQuickAttr Whether quickAttr can be enabled 268 * for this axis. 269 * @return {!xrx.xpath.Step.Axis} The axis. 270 * @private 271 */ 272 xrx.xpath.Step.createAxis_ = 273 function(name, func, reverse, opt_supportsQuickAttr) { 274 if (name in xrx.xpath.Step.nameToAxisMap_) { 275 throw Error('Axis already created: ' + name); 276 } 277 // The upcast and then downcast for the JSCompiler. 278 var axis = /** @type {!Object} */ (new xrx.xpath.Step.Axis_( 279 name, func)); 280 axis = /** @type {!xrx.xpath.Step.Axis} */ (axis); 281 xrx.xpath.Step.nameToAxisMap_[name] = axis; 282 return axis; 283 }; 284 285 286 /** 287 * Returns the axis for this axisname or null if none. 288 * 289 * @param {string} name The axis name. 290 * @return {xrx.xpath.Step.Axis} The axis. 291 */ 292 xrx.xpath.Step.getAxis = function(name) { 293 return xrx.xpath.Step.nameToAxisMap_[name] || null; 294 }; 295 296 297 /** 298 * Axis enumeration. 299 */ 300 xrx.xpath.Step.Axis = { 301 ANCESTOR: xrx.xpath.Step.createAxis_('ancestor', 302 function(test, node) { 303 return node.getAncestorNodes(test); 304 }), 305 ANCESTOR_OR_SELF: xrx.xpath.Step.createAxis_('ancestor-or-self', 306 function(test, node) { 307 var nodeset = node.getAncestorNodes(test); 308 if (test.matches(node)) nodeset.add(node); 309 return nodeset; 310 }), 311 ATTRIBUTE: xrx.xpath.Step.createAxis_('attribute', 312 function(test, node) { 313 return node.getAttributeNodes(test); 314 }), 315 CHILD: xrx.xpath.Step.createAxis_('child', 316 function(test, node) { 317 return node.getChildNodes(test); 318 }), 319 DESCENDANT: xrx.xpath.Step.createAxis_('descendant', 320 function(test, node) { 321 return node.getDescendantNodes(test); 322 }), 323 DESCENDANT_OR_SELF: xrx.xpath.Step.createAxis_('descendant-or-self', 324 function(test, node) { 325 var nodeset = node.getDescendantNodes(test); 326 if (test.matches(node)) nodeset.unshift(node); 327 return nodeset; 328 }), 329 FOLLOWING: xrx.xpath.Step.createAxis_('following', 330 function(test, node) { 331 return node.getFollowingNodes(test); 332 }), 333 FOLLOWING_SIBLING: xrx.xpath.Step.createAxis_('following-sibling', 334 function(test, node) { 335 return node.getFollowingSiblingNodes(test); 336 }), 337 NAMESPACE: xrx.xpath.Step.createAxis_('namespace', 338 function(test, node) { 339 // not implemented 340 return new xrx.xpath.NodeSet(); 341 }), 342 PARENT: xrx.xpath.Step.createAxis_('parent', 343 function(test, node) { 344 return node.getParentNodes(test); 345 }), 346 PRECEDING: xrx.xpath.Step.createAxis_('preceding', 347 function(test, node) { 348 return node.getPrecedingNodes(test); 349 }), 350 PRECEDING_SIBLING: xrx.xpath.Step.createAxis_('preceding-sibling', 351 function(test, node) { 352 return node.getPrecedingSiblingNodes(test); 353 }), 354 SELF: xrx.xpath.Step.createAxis_('self', 355 function(test, node) { 356 var nodeset = new xrx.xpath.NodeSet(); 357 if (test.matches(node)) nodeset.add(node); 358 return nodeset; 359 }) 360 }; 361