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