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