2 // jquery.xmlns.js: xml-namespace selector support for jQuery
4 // This plugin modifies the jQuery tag and attribute selectors to
5 // support optional namespace specifiers as defined in CSS 3:
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'
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.
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
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.
27 // $.xmlns.FOO = "http://www.foo.com/xmlns/foobar"
28 // $.xmlns[""] = "http://www.example.com/new/default/namespace/"
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:
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
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
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(...);
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:
54 // $().xmlns("DAV:","href")
57 // And finally, the legal stuff:
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
67 // Some common default namespaces, that are treated specially by browsers.
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/"
76 // A reverse mapping for common namespace prefixes.
78 var default_xmlns_rev = {}
79 for(var k in default_xmlns) {
80 default_xmlns_rev[default_xmlns[k]] = k;
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.
88 $.extend({xmlns: $.extend({},default_xmlns,{"":"*"})});
91 // jQuery method to push/pop namespace declarations.
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
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.
105 var xmlns_stack = [];
106 $.fn.extend({xmlns: function(nsmap,func) {
107 if(typeof nsmap == "string") {
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)
119 self = func.call(this);
124 self.xmlns(undefined);
132 $.xmlns = (xmlns_stack ? xmlns_stack.pop() : {});
138 // Convert a namespace prefix into a namespace URI, based
139 // on the delcarations made in $.xmlns.
141 var getNamespaceURI = function(id) {
142 // No namespace id, use the default.
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 == "*") {
152 var ns = $.xmlns[id];
153 if(typeof(ns) == "undefined") {
154 throw "Syntax error, undefined namespace prefix '" + id + "'";
160 // Update the regex used by $.expr to parse selector components for a
161 // particular type of selector (e.g. "TAG" or "ATTR").
163 // This logic is taken straight from the jQuery/Sizzle sources.
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);
176 // Modify the TAG match regexp to include optional namespace selector.
177 // This is basically (namespace|)?(tagname).
179 setExprMatchRegex("TAG",/^((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)))/);
182 // Perform some capability-testing.
184 var div = document.createElement("div");
186 // Sometimes getElementsByTagName("*") will return comment nodes,
187 // which we will have to remove from the results.
189 var gebtn_yields_comments = false;
190 div.appendChild(document.createComment(""));
191 if(div.getElementsByTagName("*").length > 0) {
192 gebtn_yields_comments = true;
195 // Some browsers return node.localName in upper case, some in lower case.
197 var localname_is_uppercase = true;
198 if(div.localName && div.localName == "div") {
199 localname_is_uppercase = false;
202 // Allow the testing div to be garbage-collected.
207 // Modify the TAG find function to account for a namespace selector.
209 $.expr.find.TAG = function(match,context,isXML) {
210 var ns = getNamespaceURI(match[2]);
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");
221 context.setProperty("SelectionLanguage","XPath");
226 predicate="namespace-uri()='"+ns+"' and local-name()='"+ln+"'";
228 predicate="namespace-uri()='"+ns+"'";
232 predicate="local-name()='"+ln+"'";
236 res = context.selectNodes("descendant-or-self::*["+predicate+"]");
238 res = context.selectNodes("descendant-or-self::*");
241 // Otherwise, we need to simulate using getElementsByTagName
242 res = context.getElementsByTagName(ln);
243 if(gebtn_yields_comments && ln == "*") {
245 for(var i=0; res[i]; i++) {
246 if(res[i].nodeType == 1) {
252 if(res && ns != "*") {
254 for(var i=0; res[i]; i++) {
255 if(res[i].namespaceURI == ns || res[i].tagUrn == ns) {
266 // Check whether a node is part of an XML document.
267 // Copied verbatim from jQuery sources, needed in TAG preFilter below.
269 var isXML = function(elem){
270 return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
271 !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
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.
278 $.expr.preFilter.TAG = function(match, curLoop, inplace, result, not, isXML) {
281 if(localname_is_uppercase) {
282 ln = ln.toUpperCase();
284 ln = ln.toLowerCase();
287 return [match[0],getNamespaceURI(match[2]),ln];
291 // Modify the TAG filter function to account for a namespace selector.
293 $.expr.filter.TAG = function(elem,match) {
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);
305 // Modify the ATTR match regexp to extract a namespace selector.
306 // This is basically ([namespace|])(attrname)(op)(quote)(pattern)(quote)
308 setExprMatchRegex("ATTR",/\[\s*((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF_-]|\\.)+)))\s*(?:(\S?=)\s*(['"]*)(.*?)\5|)\s*\]/);
311 // Modify the ATTR preFilter function to account for new regexp match groups,
312 // and normalise the namespace URI.
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];
319 if( match[4] == "~=" ) {
320 match[6] = " " + match[6] + " ";
322 if(!match[2] || match[2] == "|") {
325 match[2] = getNamespaceURI(match[2]);
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.
335 var filter_attr = function(result,type,check) {
336 var value = result + "";
337 return result == null ?
342 value.indexOf(check) >= 0 :
344 (" " + value + " ").indexOf(check) >= 0 :
346 value && result !== false :
350 value.indexOf(check) === 0 :
352 value.substr(value.length - check.length) === check :
354 value === check || value.substr(0,check.length+1)===check+"-" :
359 $.expr.filter.ATTR = function(elem, match) {
363 var check = match[6];
365 // No namespace, just use ordinary attribute lookup.
367 result = $.expr.attrHandle[name] ?
368 $.expr.attrHandle[name](elem) :
371 elem.getAttribute(name);
372 return filter_attr(result,type,check);
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);
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;
384 ln = attrs[i].nodeName
385 var idx = ln.indexOf(":");
387 ln = ln.substr(idx+1);
391 result = attrs[i].nodeValue;
392 if(ns == "*" || attrs[i].namespaceURI == ns) {
393 if(filter_attr(result,type,check)) {
397 if(attrs[i].namespaceURI === "" && attrs[i].prefix) {
398 if(attrs[i].prefix == default_xmlns_rev[ns]) {
399 if(filter_attr(result,type,check)) {