1 /**
  2  * @fileoverview A class representing operations on path expressions.
  3  */
  4 
  5 goog.provide('xrx.xpath.PathExpr');
  6 
  7 
  8 goog.require('goog.array');
  9 goog.require('xrx.node');
 10 goog.require('xrx.xpath.DataType');
 11 goog.require('xrx.xpath.Expr');
 12 goog.require('xrx.xpath.NodeSet');
 13 
 14 
 15 
 16 /**
 17  * Constructor for PathExpr.
 18  *
 19  * @param {!xrx.xpath.Expr} filter A filter expression.
 20  * @param {!Array.<!xrx.xpath.Step>} steps The steps in the location path.
 21  * @extends {xrx.xpath.Expr}
 22  * @constructor
 23  */
 24 xrx.xpath.PathExpr = function(filter, steps) {
 25   xrx.xpath.Expr.call(this, filter.getDataType());
 26 
 27   /**
 28    * @type {!xrx.xpath.Expr}
 29    * @private
 30    */
 31   this.filter_ = filter;
 32 
 33   /**
 34    * @type {!Array.<!xrx.xpath.Step>}
 35    * @private
 36    */
 37   this.steps_ = steps;
 38 
 39   this.setNeedContextPosition(filter.doesNeedContextPosition());
 40   this.setNeedContextNode(filter.doesNeedContextNode());
 41   if (this.steps_.length == 1) {
 42     var firstStep = this.steps_[0];
 43     if (!firstStep.doesIncludeDescendants() &&
 44         firstStep.getAxis() == xrx.xpath.Step.Axis.ATTRIBUTE) {
 45       var test = firstStep.getTest();
 46       if (test.getName() != '*') {
 47         this.setQuickAttr({
 48           name: test.getName(),
 49           valueExpr: null
 50         });
 51       }
 52     }
 53   }
 54 };
 55 goog.inherits(xrx.xpath.PathExpr, xrx.xpath.Expr);
 56 
 57 
 58 
 59 /**
 60  * Constructor for RootHelperExpr.
 61  *
 62  * @extends {xrx.xpath.Expr}
 63  * @constructor
 64  */
 65 xrx.xpath.PathExpr.RootHelperExpr = function() {
 66   xrx.xpath.Expr.call(this, xrx.xpath.DataType.NODESET);
 67 };
 68 goog.inherits(xrx.xpath.PathExpr.RootHelperExpr, xrx.xpath.Expr);
 69 
 70 
 71 /**
 72  * Evaluates the root-node helper expression.
 73  *
 74  * @param {!xrx.xpath.Context} ctx The context to evaluate the expression in.
 75  * @return {!xrx.xpath.NodeSet} The evaluation result.
 76  */
 77 xrx.xpath.PathExpr.RootHelperExpr.prototype.evaluate = function(ctx) {
 78   var nodeset = new xrx.xpath.NodeSet();
 79   var node = ctx.getNode();
 80   if (node.type() === xrx.node.DOCUMENT) {
 81     nodeset.add(node);
 82   } else {
 83     nodeset.add(/** @type {!Node} */ (node.ownerDocument));
 84   }
 85   return nodeset;
 86 };
 87 
 88 
 89 /**
 90  * @override
 91  */
 92 xrx.xpath.PathExpr.RootHelperExpr.prototype.toString = function() {
 93   return 'Root Helper Expression';
 94 };
 95 
 96 
 97 
 98 /**
 99  * Constructor for ContextHelperExpr.
100  *
101  * @extends {xrx.xpath.Expr}
102  * @constructor
103  */
104 xrx.xpath.PathExpr.ContextHelperExpr = function() {
105   xrx.xpath.Expr.call(this, xrx.xpath.DataType.NODESET);
106 };
107 goog.inherits(xrx.xpath.PathExpr.ContextHelperExpr, xrx.xpath.Expr);
108 
109 
110 /**
111  * Evaluates the context-node helper expression.
112  *
113  * @param {!xrx.xpath.Context} ctx The context to evaluate the expression in.
114  * @return {!xrx.xpath.NodeSet} The evaluation result.
115  */
116 xrx.xpath.PathExpr.ContextHelperExpr.prototype.evaluate = function(ctx) {
117   var nodeset = new xrx.xpath.NodeSet();
118   nodeset.add(ctx.getNode());
119   return nodeset;
120 };
121 
122 
123 /**
124  * @override
125  */
126 xrx.xpath.PathExpr.ContextHelperExpr.prototype.toString = function() {
127   return 'Context Helper Expression';
128 };
129 
130 
131 /**
132  * Returns whether the token is a valid PathExpr operator.
133  *
134  * @param {string} token The token to be checked.
135  * @return {boolean} Whether the token is a valid operator.
136  */
137 xrx.xpath.PathExpr.isValidOp = function(token) {
138   return token == '/' || token == '//';
139 };
140 
141 
142 /**
143  * @override
144  * @return {!xrx.xpath.NodeSet} The nodeset result.
145  */
146 xrx.xpath.PathExpr.prototype.evaluate = function(ctx) {
147   var nodeset = this.filter_.evaluate(ctx);
148   if (!(nodeset instanceof xrx.xpath.NodeSet)) {
149     throw Error('Filter expression must evaluate to nodeset.');
150   }
151   var steps = this.steps_;
152   for (var i = 0, l0 = steps.length; i < l0 && nodeset.getLength(); i++) {
153     var step = steps[i];
154     var reverse = step.getAxis().isReverse();
155     var iter = nodeset.iterator(reverse);
156     nodeset = null;
157     var node, next;
158     if (!step.doesNeedContextPosition() &&
159         step.getAxis() == xrx.xpath.Step.Axis.FOLLOWING) {
160       for (node = iter.next(); next = iter.next(); node = next) {
161         if (node.contains && !node.contains(next)) {
162           break;
163         } else {
164           if (!(next.compareDocumentPosition(/** @type {!Node} */ (node)) &
165               8)) {
166             break;
167           }
168         }
169       }
170       nodeset = step.evaluate(new
171           xrx.xpath.Context(/** @type {xrx.node} */ (node)));
172     } else if (!step.doesNeedContextPosition() &&
173         step.getAxis() == xrx.xpath.Step.Axis.PRECEDING) {
174       node = iter.next();
175       nodeset = step.evaluate(new
176           xrx.xpath.Context(/** @type {xrx.node} */ (node)));
177     } else {
178       node = iter.next();
179       nodeset = step.evaluate(new
180           xrx.xpath.Context(/** @type {xrx.node} */ (node)));
181       while ((node = iter.next()) != null) {
182         var result = step.evaluate(new
183             xrx.xpath.Context(/** @type {xrx.node} */ (node)));
184         nodeset = xrx.xpath.NodeSet.merge(nodeset, result);
185       }
186     }
187   }
188   return /** @type {!xrx.xpath.NodeSet} */ (nodeset);
189 };
190 
191 
192 /**
193  * @override
194  */
195 xrx.xpath.PathExpr.prototype.toString = function() {
196   var text = 'Path Expression:';
197   text += xrx.xpath.Expr.indent(this.filter_);
198   if (this.steps_.length) {
199     var steps = goog.array.reduce(this.steps_, function(prev, curr) {
200       return prev + xrx.xpath.Expr.indent(curr);
201     }, 'Steps:');
202     text += xrx.xpath.Expr.indent(steps);
203   }
204   return text;
205 };
206