1 /** 2 * @fileoverview Node utilities. 3 */ 4 5 goog.provide('xrx.xpath.Node'); 6 7 goog.require('goog.array'); 8 goog.require('goog.dom.NodeType'); 9 goog.require('goog.userAgent'); 10 11 12 /** @typedef {!xrx.xpath.Node} */ 13 xrx.xpath.Node = {}; 14 15 16 /** 17 * Returns whether two nodes are equal. 18 * 19 * @param {xrx.xpath.Node} a The first node. 20 * @param {xrx.xpath.Node} b The second node. 21 * @return {boolean} Whether the nodes are equal. 22 */ 23 xrx.xpath.Node.equal = function(a, b) { 24 return (a == b) || (a.getNode() == b.getNode()); 25 }; 26 27 28 /** 29 * Returns the string-value of the required type from a node. 30 * 31 * @param {!xrx.xpath.Node} node The node to get value from. 32 * @return {string} The value required. 33 */ 34 xrx.xpath.Node.getValueAsString = function(node) { 35 var t = null, type = node.nodeType; 36 // Old IE title problem. 37 var needTitleFix = function(node) { 38 return false; 39 }; 40 // goog.dom.getTextContent doesn't seem to work 41 if (type == goog.dom.NodeType.ELEMENT) { 42 t = node.textContent; 43 t = (t == undefined || t == null) ? node.innerText : t; 44 t = (t == undefined || t == null) ? '' : t; 45 } 46 if (typeof t != 'string') { 47 if (needTitleFix(node) && type == goog.dom.NodeType.ELEMENT) { 48 t = node.text; 49 } else if (type == goog.dom.NodeType.DOCUMENT || 50 type == goog.dom.NodeType.ELEMENT) { 51 node = (type == goog.dom.NodeType.DOCUMENT) ? 52 node.documentElement : node.firstChild; 53 var i = 0, stack = []; 54 for (t = ''; node;) { 55 do { 56 if (node.nodeType != goog.dom.NodeType.ELEMENT) { 57 t += node.nodeValue; 58 } 59 if (needTitleFix(node)) { 60 t += node.text; 61 } 62 stack[i++] = node; // push 63 } while (node = node.firstChild); 64 while (i && !(node = stack[--i].nextSibling)) {} 65 } 66 } else { 67 t = node.nodeValue; 68 } 69 } 70 return '' + t; 71 }; 72 73 74 /** 75 * Returns the string-value of the required type from a node, casted to number. 76 * 77 * @param {!xrx.xpath.Node} node The node to get value from. 78 * @return {number} The value required. 79 */ 80 xrx.xpath.Node.getValueAsNumber = function(node) { 81 return +xrx.xpath.Node.getValueAsString(node); 82 }; 83 84 85 /** 86 * Returns the string-value of the required type from a node, casted to boolean. 87 * 88 * @param {!xrx.xpath.Node} node The node to get value from. 89 * @return {boolean} The value required. 90 */ 91 xrx.xpath.Node.getValueAsBool = function(node) { 92 return !!xrx.xpath.Node.getValueAsString(node); 93 }; 94 95 96 /** 97 * Returns if the attribute matches the given value. 98 * 99 * @param {!xrx.xpath.Node} node The node to get value from. 100 * @param {?string} name The attribute name to match, if any. 101 * @param {?string} value The attribute value to match, if any. 102 * @return {boolean} Whether the node matches the attribute, if any. 103 */ 104 xrx.xpath.Node.attrMatches = function(node, name, value) { 105 // No attribute. 106 if (goog.isNull(name)) { 107 return true; 108 } 109 try { 110 if (!node.getAttribute) { 111 return false; 112 } 113 } catch (e) { 114 return false; 115 } 116 return value == null ? !!node.getAttribute(name) : 117 (node.getAttribute(name, 2) == value); 118 }; 119 120 121 /** 122 * Returns the descendants of a node. 123 * 124 * @param {!xrx.xpath.NodeTest} test A NodeTest for matching nodes. 125 * @param {!xrx.xpath.Node} node The node to get descendants from. 126 * @param {?string=} opt_attrName The attribute name to match, if any. 127 * @param {?string=} opt_attrValue The attribute value to match, if any. 128 * @param {!xrx.xpath.NodeSet=} opt_nodeset The node set to add descendants to. 129 * @return {!xrx.xpath.NodeSet} The nodeset with descendants. 130 */ 131 xrx.xpath.Node.getDescendantNodes = function(test, node, opt_attrName, 132 opt_attrValue, opt_nodeset) { 133 var nodeset = opt_nodeset || new xrx.xpath.NodeSet(); 134 var func = xrx.xpath.Node.getDescendantNodesGeneric_; 135 var attrName = goog.isString(opt_attrName) ? opt_attrName : null; 136 var attrValue = goog.isString(opt_attrValue) ? opt_attrValue : null; 137 return func.call(null, test, node, attrName, attrValue, nodeset); 138 }; 139 140 141 142 /** 143 * Returns the descendants of a node for browsers other than IE. 144 * 145 * @private 146 * @param {!xrx.xpath.NodeTest} test A NodeTest for matching nodes. 147 * @param {!xrx.xpath.Node} node The node to get descendants from. 148 * @param {?string} attrName The attribute name to match, if any. 149 * @param {?string} attrValue The attribute value to match, if any. 150 * @param {!xrx.xpath.NodeSet} nodeset The node set to add descendants to. 151 * @return {!xrx.xpath.NodeSet} The nodeset with descendants. 152 */ 153 xrx.xpath.Node.getDescendantNodesGeneric_ = function(test, node, 154 attrName, attrValue, nodeset) { 155 if (node.getElementsByName && attrValue && attrName == 'name' && 156 !goog.userAgent.IE) { 157 var nodes = node.getElementsByName(attrValue); 158 goog.array.forEach(nodes, function(node) { 159 if (test.matches(node)) { 160 nodeset.add(node); 161 } 162 }); 163 } else if (node.getElementsByClassName && attrValue && attrName == 'class') { 164 var nodes = node.getElementsByClassName(attrValue); 165 goog.array.forEach(nodes, function(node) { 166 if (node.className == attrValue && test.matches(node)) { 167 nodeset.add(node); 168 } 169 }); 170 } else if (test instanceof xrx.xpath.KindTest) { 171 xrx.xpath.Node.doRecursiveAttrMatch_(test, node, attrName, 172 attrValue, nodeset); 173 } else if (node.getElementsByTagName) { 174 var nodes = node.getElementsByTagName(test.getName()); 175 goog.array.forEach(nodes, function(node) { 176 if (xrx.xpath.Node.attrMatches(node, attrName, attrValue)) { 177 nodeset.add(node); 178 } 179 }); 180 } 181 return nodeset; 182 }; 183 184 185 /** 186 * Returns the child nodes of a node. 187 * 188 * @param {!xrx.xpath.NodeTest} test A NodeTest for matching nodes. 189 * @param {!xrx.xpath.Node} node The node to get child nodes from. 190 * @param {?string=} opt_attrName The attribute name to match, if any. 191 * @param {?string=} opt_attrValue The attribute value to match, if any. 192 * @param {!xrx.xpath.NodeSet=} opt_nodeset The node set to add child nodes to. 193 * @return {!xrx.xpath.NodeSet} The nodeset with child nodes. 194 */ 195 xrx.xpath.Node.getChildNodes = function(test, node, 196 opt_attrName, opt_attrValue, opt_nodeset) { 197 var nodeset = opt_nodeset || new xrx.xpath.NodeSet(); 198 var func = xrx.xpath.Node.getChildNodesGeneric_; 199 var attrName = goog.isString(opt_attrName) ? opt_attrName : null; 200 var attrValue = goog.isString(opt_attrValue) ? opt_attrValue : null; 201 return func.call(null, test, node, attrName, attrValue, nodeset); 202 }; 203 204 205 206 /** 207 * Returns the child nodes of a node genericly. 208 * 209 * @private 210 * @param {!xrx.xpath.NodeTest} test A NodeTest for matching nodes. 211 * @param {!xrx.xpath.Node} node The node to get child nodes from. 212 * @param {?string} attrName The attribute name to match, if any. 213 * @param {?string} attrValue The attribute value to match, if any. 214 * @param {!xrx.xpath.NodeSet} nodeset The node set to add child nodes to. 215 * @return {!xrx.xpath.NodeSet} The nodeset with child nodes. 216 */ 217 xrx.xpath.Node.getChildNodesGeneric_ = function(test, node, attrName, 218 attrValue, nodeset) { 219 for (var current = node.firstChild; current; current = current.nextSibling) { 220 if (xrx.xpath.Node.attrMatches(current, attrName, attrValue)) { 221 if (test.matches(current)) { 222 nodeset.add(current); 223 } 224 } 225 } 226 return nodeset; 227 }; 228 229 230 /** 231 * Returns whether a getting descendants/children call 232 * needs special handling on IE browsers. 233 * 234 * @private 235 * @param {!xrx.xpath.NodeTest} test A NodeTest for matching nodes. 236 * @param {!xrx.xpath.Node} node The root node to start the recursive call on. 237 * @param {?string} attrName The attribute name to match, if any. 238 * @param {?string} attrValue The attribute value to match, if any. 239 * @param {!xrx.xpath.NodeSet} nodeset The NodeSet to add nodes to. 240 */ 241 xrx.xpath.Node.doRecursiveAttrMatch_ = function(test, node, 242 attrName, attrValue, nodeset) { 243 for (var n = node.firstChild; n; n = n.nextSibling) { 244 if (xrx.xpath.Node.attrMatches(n, attrName, attrValue) && 245 test.matches(n)) { 246 nodeset.add(n); 247 } 248 xrx.xpath.Node.doRecursiveAttrMatch_(test, n, attrName, 249 attrValue, nodeset); 250 } 251 }; 252