1 // 2 // jquery.xmlns.js: xml-namespace selector support for jQuery 3 // 4 // This plugin modifies the jQuery tag and attribute selectors to 5 // support optional namespace specifiers as defined in CSS 3: 6 // 7 // $("elem") - matches 'elem' nodes in the default namespace 8 // $("|elem") - matches 'elem' nodes that don't have a namespace 9 // $("NS|elem") - matches 'elem' nodes in declared namespace 'NS' 10 // $("*|elem") - matches 'elem' nodes in any namespace 11 // $("NS|*") - matches any nodes in declared namespace 'NS' 12 // 13 // A similar synax is also supported for attribute selectors, but note 14 // that the default namespace does *not* apply to attributes - a missing 15 // or empty namespace selector selects only attributes with no namespace. 16 // 17 // In a slight break from the W3C standards, and with a nod to ease of 18 // implementation, the empty namespace URI is treated as equivalent to 19 // an unspecified namespace. Plenty of browsers seem to make the same 20 // assumption... 21 // 22 // Namespace declarations live in the $.xmlns object, which is a simple 23 // mapping from namespace ids to namespace URIs. The default namespace 24 // is determined by the value associated with the empty string. 25 // 26 // $.xmlns.D = "DAV:" 27 // $.xmlns.FOO = "http://www.foo.com/xmlns/foobar" 28 // $.xmlns[""] = "http://www.example.com/new/default/namespace/" 29 // 30 // Unfortunately this is a global setting - I can't find a way to do 31 // query-object-specific namespaces since the jQuery selector machinery 32 // is stateless. However, you can use the 'xmlns' function to push and 33 // pop namespace delcarations with ease: 34 // 35 // $().xmlns({D:"DAV:"}) // pushes the DAV: namespace 36 // $().xmlns("DAV:") // makes DAV: the default namespace 37 // $().xmlns(false) // pops the namespace we just pushed 38 // $().xmlns(false) // pops again, returning to defaults 39 // 40 // To execute this as a kind of "transaction", pass a function as the 41 // second argument. It will be executed in the context of the current 42 // jQuery object: 43 // 44 // $().xmlns("DAV:",function() { 45 // // The default namespace is DAV: within this function, 46 // // but it is reset to the previous value on exit. 47 // return this.find("response").each(...); 48 // }).find("div") 49 // 50 // If you pass a string as a function, it will be executed against the 51 // current jQuery object using find(); i.e. the following will find all 52 // "href" elements in the "DAV:" namespace: 53 // 54 // $().xmlns("DAV:","href") 55 // 56 // 57 // And finally, the legal stuff: 58 // 59 // Copyright (c) 2009, Ryan Kelly. 60 // TAG and ATTR functions derived from jQuery's selector.js. 61 // Dual licensed under the MIT and GPL licenses. 62 // http://docs.jquery.com/License 63 // 64 65 (function($) { 66 67 // Some common default namespaces, that are treated specially by browsers. 68 // 69 var default_xmlns = { 70 "xml": "http://www.w3.org/XML/1998/namespace", 71 "xmlns": "http://www.w3.org/2000/xmlns/", 72 "html": "http://www.w3.org/1999/xhtml/" 73 }; 74 75 76 // A reverse mapping for common namespace prefixes. 77 // 78 var default_xmlns_rev = {} 79 for(var k in default_xmlns) { 80 default_xmlns_rev[default_xmlns[k]] = k; 81 } 82 83 84 // $.xmlns is a mapping from namespace identifiers to namespace URIs. 85 // The default default-namespace is "*", and we provide some additional 86 // defaults that are specified in the XML Namespaces standard. 87 // 88 $.extend({xmlns: $.extend({},default_xmlns,{"":"*"})}); 89 90 91 // jQuery method to push/pop namespace declarations. 92 // 93 // If a single argument is specified: 94 // * if it's a mapping, push those namespaces onto the stack 95 // * if it's a string, push that as the default namespace 96 // * if it evaluates to false, pop the latest namespace 97 // 98 // If two arguments are specified, the second is executed "transactionally" 99 // using the namespace declarations found in the first. It can be either a 100 // a selector string (in which case it is passed to this.find()) or a function 101 // (in which case it is called in the context of the current jQuery object). 102 // The given namespace mapping is automatically pushed before executing and 103 // popped afterwards. 104 // 105 var xmlns_stack = []; 106 $.fn.extend({xmlns: function(nsmap,func) { 107 if(typeof nsmap == "string") { 108 nsmap = {"":nsmap}; 109 } 110 if(nsmap) { 111 xmlns_stack.push($.xmlns); 112 $.xmlns = $.extend({},$.xmlns,nsmap); 113 if(func !== undefined) { 114 if(typeof func == "string") { 115 return this.find(func).xmlns(undefined) 116 } else { 117 var self = this; 118 try { 119 self = func.call(this); 120 if(!self) { 121 self = this; 122 } 123 } finally { 124 self.xmlns(undefined); 125 } 126 return self 127 } 128 } else { 129 return this; 130 } 131 } else { 132 $.xmlns = (xmlns_stack ? xmlns_stack.pop() : {}); 133 return this; 134 } 135 }}); 136 137 138 // Convert a namespace prefix into a namespace URI, based 139 // on the delcarations made in $.xmlns. 140 // 141 var getNamespaceURI = function(id) { 142 // No namespace id, use the default. 143 if(!id) { 144 return $.xmlns[""]; 145 } 146 // Strip the pipe character from the specifier 147 id = id.substr(0,id.length-1); 148 // Certain special namespaces aren't mapped to a URI 149 if(id == "" || id == "*") { 150 return id; 151 } 152 var ns = $.xmlns[id]; 153 if(typeof(ns) == "undefined") { 154 throw "Syntax error, undefined namespace prefix '" + id + "'"; 155 } 156 return ns; 157 }; 158 159 160 // Update the regex used by $.expr to parse selector components for a 161 // particular type of selector (e.g. "TAG" or "ATTR"). 162 // 163 // This logic is taken straight from the jQuery/Sizzle sources. 164 // 165 var setExprMatchRegex = function(type,regex) { 166 $.expr.match[type] = new RegExp(regex.source + /(?![^\[]*\])(?![^\(]*\))/.source); 167 if($.expr.leftMatch) { 168 $.expr.leftMatch[type] = new RegExp(/(^(?:.|\r|\n)*?)/.source + $.expr.match[type].source.replace(/\\(\d+)/g, function(all, num){ 169 return "\\" + (num - 0 + 1); 170 })); 171 } 172 } 173 174 175 176 // Modify the TAG match regexp to include optional namespace selector. 177 // This is basically (namespace|)?(tagname). 178 // 179 setExprMatchRegex("TAG",/^((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)))/); 180 181 182 // Perform some capability-testing. 183 // 184 var div = document.createElement("div"); 185 186 // Sometimes getElementsByTagName("*") will return comment nodes, 187 // which we will have to remove from the results. 188 // 189 var gebtn_yields_comments = false; 190 div.appendChild(document.createComment("")); 191 if(div.getElementsByTagName("*").length > 0) { 192 gebtn_yields_comments = true; 193 } 194 195 // Some browsers return node.localName in upper case, some in lower case. 196 // 197 var localname_is_uppercase = true; 198 if(div.localName && div.localName == "div") { 199 localname_is_uppercase = false; 200 } 201 202 // Allow the testing div to be garbage-collected. 203 // 204 div = null; 205 206 207 // Modify the TAG find function to account for a namespace selector. 208 // 209 $.expr.find.TAG = function(match,context,isXML) { 210 var ns = getNamespaceURI(match[2]); 211 var ln = match[3]; 212 var res; 213 if(typeof context.getElementsByTagNameNS != "undefined") { 214 // Easy case - we have getElementsByTagNameNS 215 res = context.getElementsByTagNameNS(ns,ln); 216 } else if(typeof context.selectNodes != "undefined") { 217 // Use xpath if possible (not available on HTML DOM nodes in IE) 218 if(context.ownerDocument) { 219 context.ownerDocument.setProperty("SelectionLanguage","XPath"); 220 } else { 221 context.setProperty("SelectionLanguage","XPath"); 222 } 223 var predicate = ""; 224 if(ns != "*") { 225 if(ln != "*") { 226 predicate="namespace-uri()='"+ns+"' and local-name()='"+ln+"'"; 227 } else { 228 predicate="namespace-uri()='"+ns+"'"; 229 } 230 } else { 231 if(ln != "*") { 232 predicate="local-name()='"+ln+"'"; 233 } 234 } 235 if(predicate) { 236 res = context.selectNodes("descendant-or-self::*["+predicate+"]"); 237 } else { 238 res = context.selectNodes("descendant-or-self::*"); 239 } 240 } else { 241 // Otherwise, we need to simulate using getElementsByTagName 242 res = context.getElementsByTagName(ln); 243 if(gebtn_yields_comments && ln == "*") { 244 var tmp = []; 245 for(var i=0; res[i]; i++) { 246 if(res[i].nodeType == 1) { 247 tmp.push(res[i]); 248 } 249 } 250 res = tmp; 251 } 252 if(res && ns != "*") { 253 var tmp = []; 254 for(var i=0; res[i]; i++) { 255 if(res[i].namespaceURI == ns || res[i].tagUrn == ns) { 256 tmp.push(res[i]); 257 } 258 } 259 res = tmp; 260 } 261 } 262 return res; 263 }; 264 265 266 // Check whether a node is part of an XML document. 267 // Copied verbatim from jQuery sources, needed in TAG preFilter below. 268 // 269 var isXML = function(elem){ 270 return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || 271 !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; 272 }; 273 274 275 // Modify the TAG preFilter function to work with modified match regexp. 276 // This normalises case of the tag name if we're in a HTML document. 277 // 278 $.expr.preFilter.TAG = function(match, curLoop, inplace, result, not, isXML) { 279 var ln = match[3]; 280 if(!isXML) { 281 if(localname_is_uppercase) { 282 ln = ln.toUpperCase(); 283 } else { 284 ln = ln.toLowerCase(); 285 } 286 } 287 return [match[0],getNamespaceURI(match[2]),ln]; 288 }; 289 290 291 // Modify the TAG filter function to account for a namespace selector. 292 // 293 $.expr.filter.TAG = function(elem,match) { 294 var ns = match[1]; 295 var ln = match[2]; 296 var e_ns = elem.namespaceURI ? elem.namespaceURI : elem.tagUrn; 297 var e_ln = elem.localName ? elem.localName : elem.tagName; 298 if(ns == "*" || e_ns == ns || (ns == "" && !e_ns)) { 299 return ((ln == "*" && elem.nodeType == 1) || e_ln == ln); 300 } 301 return false; 302 }; 303 304 305 // Modify the ATTR match regexp to extract a namespace selector. 306 // This is basically ([namespace|])(attrname)(op)(quote)(pattern)(quote) 307 // 308 setExprMatchRegex("ATTR",/\[\s*((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF_-]|\\.)+)))\s*(?:(\S?=)\s*(['"]*)(.*?)\5|)\s*\]/); 309 310 311 // Modify the ATTR preFilter function to account for new regexp match groups, 312 // and normalise the namespace URI. 313 // 314 $.expr.preFilter.ATTR = function(match, curLoop, inplace, result, not, isXML) { 315 var name = match[3].replace(/\\/g, ""); 316 if(!isXML && $.expr.attrMap[name]) { 317 match[3] = $.expr.attrMap[name]; 318 } 319 if( match[4] == "~=" ) { 320 match[6] = " " + match[6] + " "; 321 } 322 if(!match[2] || match[2] == "|") { 323 match[2] = ""; 324 } else { 325 match[2] = getNamespaceURI(match[2]); 326 } 327 return match; 328 }; 329 330 331 // Modify the ATTR filter function to account for namespace selector. 332 // Unfortunately this means factoring out the attribute-checking code 333 // into a separate function, since it might be called multiple times. 334 // 335 var filter_attr = function(result,type,check) { 336 var value = result + ""; 337 return result == null ? 338 type === "!=" : 339 type === "=" ? 340 value === check : 341 type === "*=" ? 342 value.indexOf(check) >= 0 : 343 type === "~=" ? 344 (" " + value + " ").indexOf(check) >= 0 : 345 !check ? 346 value && result !== false : 347 type === "!=" ? 348 value != check : 349 type === "^=" ? 350 value.indexOf(check) === 0 : 351 type === "$=" ? 352 value.substr(value.length - check.length) === check : 353 type === "|=" ? 354 value === check || value.substr(0,check.length+1)===check+"-" : 355 false; 356 } 357 358 359 $.expr.filter.ATTR = function(elem, match) { 360 var ns = match[2]; 361 var name = match[3]; 362 var type = match[4]; 363 var check = match[6]; 364 var result; 365 // No namespace, just use ordinary attribute lookup. 366 if(ns == "") { 367 result = $.expr.attrHandle[name] ? 368 $.expr.attrHandle[name](elem) : 369 elem[name] != null ? 370 elem[name] : 371 elem.getAttribute(name); 372 return filter_attr(result,type,check); 373 } 374 // Directly use getAttributeNS if applicable and available 375 if(ns != "*" && typeof elem.getAttributeNS != "undefined") { 376 return filter_attr(elem.getAttributeNS(ns,name),type,check); 377 } 378 // Need to iterate over all attributes, either because we couldn't 379 // look it up or because we need to match all namespaces. 380 var attrs = elem.attributes; 381 for(var i=0; attrs[i]; i++) { 382 var ln = attrs[i].localName; 383 if(!ln) { 384 ln = attrs[i].nodeName 385 var idx = ln.indexOf(":"); 386 if(idx >= 0) { 387 ln = ln.substr(idx+1); 388 } 389 } 390 if(ln == name) { 391 result = attrs[i].nodeValue; 392 if(ns == "*" || attrs[i].namespaceURI == ns) { 393 if(filter_attr(result,type,check)) { 394 return true; 395 } 396 } 397 if(attrs[i].namespaceURI === "" && attrs[i].prefix) { 398 if(attrs[i].prefix == default_xmlns_rev[ns]) { 399 if(filter_attr(result,type,check)) { 400 return true; 401 } 402 } 403 } 404 } 405 } 406 return false; 407 }; 408 409 410 })(jQuery); 411 412 413