1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/string" 5 ], 6 function(Str) { 7 8 // ********************************************************************************************* // 9 // Constants 10 11 var Ci = Components.interfaces; 12 var Cc = Components.classes; 13 var Cu = Components.utils; 14 15 var Xpath = {}; 16 17 // ********************************************************************************************* // 18 // XPATH 19 20 /** 21 * Gets an XPath for an element which describes its hierarchical location. 22 */ 23 Xpath.getElementXPath = function(element) 24 { 25 if (element && element.id) 26 return '//*[@id="' + element.id + '"]'; 27 else 28 return Xpath.getElementTreeXPath(element); 29 }; 30 31 Xpath.getElementTreeXPath = function(element) 32 { 33 var paths = []; 34 35 // Use nodeName (instead of localName) so namespace prefix is included (if any). 36 for (; element && element.nodeType == Node.ELEMENT_NODE; element = element.parentNode) 37 { 38 var index = 0; 39 for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) 40 { 41 // Ignore document type declaration. 42 if (sibling.nodeType == Node.DOCUMENT_TYPE_NODE) 43 continue; 44 45 if (sibling.nodeName == element.nodeName) 46 ++index; 47 } 48 49 var tagName = element.nodeName.toLowerCase(); 50 var pathIndex = (index ? "[" + (index+1) + "]" : ""); 51 paths.splice(0, 0, tagName + pathIndex); 52 } 53 54 return paths.length ? "/" + paths.join("/") : null; 55 }; 56 57 Xpath.cssToXPath = function(rule) 58 { 59 var regElement = /^([#.]?)([a-z0-9\\*_-]*)((\|)([a-z0-9\\*_-]*))?/i; 60 var regAttr1 = /^\[([^\]]*)\]/i; 61 var regAttr2 = /^\[\s*([^~=\s]+)\s*(~?=)\s*"([^"]+)"\s*\]/i; 62 var regPseudo = /^:([a-z_-])+/i; 63 var regCombinator = /^(\s*[>+\s])?/i; 64 var regComma = /^\s*,/i; 65 66 var index = 1; 67 var parts = ["//", "*"]; 68 var lastRule = null; 69 70 while (rule.length && rule != lastRule) 71 { 72 lastRule = rule; 73 74 // Trim leading whitespace 75 rule = Str.trim(rule); 76 if (!rule.length) 77 break; 78 79 // Match the element identifier 80 var m = regElement.exec(rule); 81 if (m) 82 { 83 if (!m[1]) 84 { 85 // XXXjoe Namespace ignored for now 86 if (m[5]) 87 parts[index] = m[5]; 88 else 89 parts[index] = m[2]; 90 } 91 else if (m[1] == '#') 92 parts.push("[@id='" + m[2] + "']"); 93 else if (m[1] == '.') 94 parts.push("[contains(concat(' ',normalize-space(@class),' '), ' " + m[2] + " ')]"); 95 96 rule = rule.substr(m[0].length); 97 } 98 99 // Match attribute selectors 100 m = regAttr2.exec(rule); 101 if (m) 102 { 103 if (m[2] == "~=") 104 parts.push("[contains(@" + m[1] + ", '" + m[3] + "')]"); 105 else 106 parts.push("[@" + m[1] + "='" + m[3] + "']"); 107 108 rule = rule.substr(m[0].length); 109 } 110 else 111 { 112 m = regAttr1.exec(rule); 113 if (m) 114 { 115 parts.push("[@" + m[1] + "]"); 116 rule = rule.substr(m[0].length); 117 } 118 } 119 120 // Skip over pseudo-classes and pseudo-elements, which are of no use to us 121 m = regPseudo.exec(rule); 122 while (m) 123 { 124 rule = rule.substr(m[0].length); 125 m = regPseudo.exec(rule); 126 } 127 128 // Match combinators 129 m = regCombinator.exec(rule); 130 if (m && m[0].length) 131 { 132 if (m[0].indexOf(">") != -1) 133 parts.push("/"); 134 else if (m[0].indexOf("+") != -1) 135 parts.push("/following-sibling::"); 136 else 137 parts.push("//"); 138 139 index = parts.length; 140 parts.push("*"); 141 rule = rule.substr(m[0].length); 142 } 143 144 m = regComma.exec(rule); 145 if (m) 146 { 147 parts.push(" | ", "//", "*"); 148 index = parts.length-1; 149 rule = rule.substr(m[0].length); 150 } 151 } 152 153 var xpath = parts.join(""); 154 return xpath; 155 }; 156 157 Xpath.getElementsBySelector = function(doc, css) 158 { 159 var xpath = Xpath.cssToXPath(css); 160 return Xpath.getElementsByXPath(doc, xpath); 161 }; 162 163 Xpath.getElementsByXPath = function(doc, xpath) 164 { 165 var result = Xpath.evaluateXPath(doc, xpath); 166 167 if (result instanceof Array) 168 return result; 169 170 return []; 171 }; 172 173 Xpath.evaluateXPath = function(doc, xpath, contextNode, resultType) 174 { 175 if (contextNode === undefined) 176 contextNode = doc; 177 178 if (resultType === undefined) 179 resultType = XPathResult.ANY_TYPE; 180 181 try 182 { 183 var result = doc.evaluate(xpath, contextNode, null, resultType, null); 184 } 185 catch (exc) 186 { 187 // If an invalid XPath expression was entered, it should be caught without exception 188 return; 189 } 190 191 switch (result.resultType) 192 { 193 case XPathResult.NUMBER_TYPE: 194 return result.numberValue; 195 196 case XPathResult.STRING_TYPE: 197 return result.stringValue; 198 199 case XPathResult.BOOLEAN_TYPE: 200 return result.booleanValue; 201 202 case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: 203 case XPathResult.ORDERED_NODE_ITERATOR_TYPE: 204 var nodes = []; 205 for (var item = result.iterateNext(); item; item = result.iterateNext()) 206 nodes.push(item); 207 return nodes; 208 209 case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE: 210 case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: 211 var nodes = []; 212 for (var i = 0; i < result.snapshotLength; ++i) 213 nodes.push(result.snapshotItem(i)); 214 return nodes; 215 216 case XPathResult.ANY_UNORDERED_NODE_TYPE: 217 case XPathResult.FIRST_ORDERED_NODE_TYPE: 218 return result.singleNodeValue; 219 } 220 }; 221 222 Xpath.getRuleMatchingElements = function(rule, doc) 223 { 224 var css = rule.selectorText; 225 var xpath = Xpath.cssToXPath(css); 226 return Xpath.getElementsByXPath(doc, xpath); 227 }; 228 229 // ********************************************************************************************* // 230 231 return Xpath; 232 233 // ********************************************************************************************* // 234 }); 235