Don't loose attributes of unknown tags
[redakcja.git] / redakcja / static / js / lib / jquery / jquery.xmlns.js
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