bugfix
[redakcja.git] / src / 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 var jquery_gt_18 = jQuery.fn.jquery >= "1.8";
68 //  Some common default namespaces, that are treated specially by browsers.
69 //
70 var default_xmlns = {
71     "xml": "http://www.w3.org/XML/1998/namespace",
72     "xmlns": "http://www.w3.org/2000/xmlns/",
73     "html": "http://www.w3.org/1999/xhtml/"
74 };
75  
76 rbackslash = /\\(?!\\)/g;
77
78 //  A reverse mapping for common namespace prefixes.
79 //
80 var default_xmlns_rev = {}
81 for(var k in default_xmlns) {
82     default_xmlns_rev[default_xmlns[k]] = k;
83 }
84
85
86 //  $.xmlns is a mapping from namespace identifiers to namespace URIs.
87 //  The default default-namespace is "*", and we provide some additional
88 //  defaults that are specified in the XML Namespaces standard.
89 //
90 $.extend({xmlns: $.extend({},default_xmlns,{"":"*"})});
91
92
93 //  jQuery method to push/pop namespace declarations.
94 //
95 //  If a single argument is specified:
96 //    * if it's a mapping, push those namespaces onto the stack
97 //    * if it's a string, push that as the default namespace
98 //    * if it evaluates to false, pop the latest namespace
99 //
100 //  If two arguments are specified, the second is executed "transactionally"
101 //  using the namespace declarations found in the first.  It can be either a
102 //  a selector string (in which case it is passed to this.find()) or a function
103 //  (in which case it is called in the context of the current jQuery object).
104 //  The given namespace mapping is automatically pushed before executing and
105 //  popped afterwards.
106 //
107 var xmlns_stack = [];
108 $.fn.extend({xmlns: function(nsmap,func) {
109     if(typeof nsmap == "string") {
110         nsmap = {"":nsmap};
111     }
112     if(nsmap) {
113         xmlns_stack.push($.xmlns);
114         $.xmlns = $.extend({},$.xmlns,nsmap);
115         if(func !== undefined) {
116             if(typeof func == "string") {
117                 return this.find(func).xmlns(undefined)
118             } else {
119                 var self = this;
120                 try {
121                     self = func.call(this);
122                     if(!self) {
123                         self = this;
124                     }
125                 } finally {
126                     self.xmlns(undefined);
127                 }
128                 return self
129             }
130         } else {
131             return this;
132         }
133     } else {
134         $.xmlns = (xmlns_stack ? xmlns_stack.pop() : {});
135         return this;
136     }
137 }});
138
139
140 //  Convert a namespace prefix into a namespace URI, based
141 //  on the delcarations made in $.xmlns.
142 //
143 var getNamespaceURI = function(id) {
144     // No namespace id, use the default.
145     if(!id) {
146         return $.xmlns[""];
147     }
148     // Strip the pipe character from the specifier
149     id = id.substr(0,id.length-1);
150     // Certain special namespaces aren't mapped to a URI
151     if(id == "" || id == "*") {
152         return id;
153     }
154     var ns = $.xmlns[id];
155     if(typeof(ns) == "undefined") {
156         throw "Syntax error, undefined namespace prefix '" + id + "'";
157     }
158     return ns;
159 };
160
161
162 //  Update the regex used by $.expr to parse selector components for a
163 //  particular type of selector (e.g. "TAG" or "ATTR").
164 //
165 //  This logic is taken straight from the jQuery/Sizzle sources.
166 //
167 var setExprMatchRegex = (jquery_gt_18) ? function(type,regex) {
168                 $.expr.match[type] = regex;
169         } : function(type,regex) {
170           $.expr.match[type] = new RegExp(regex.source + /(?![^\[]*\])(?![^\(]*\))/.source);
171           if($.expr.leftMatch) {
172               $.expr.leftMatch[type] = new RegExp(/(^(?:.|\r|\n)*?)/.source + $.expr.match[type].source.replace(/\\(\d+)/g, function(all, num){
173                   return "\\" + (num - 0 + 1);
174               }));
175           }
176         };
177
178 //  Modify the TAG match regexp to include optional namespace selector.
179 //  This is basically (namespace|)?(tagname).
180 //
181 if (jquery_gt_18) {
182         setExprMatchRegex("TAG",/^((?:((?:\\.|[-\w*]|[^\x00-\xa0])+\|)?((?:\\.|[-\w*]|[^\x00-\xa0])+)))/);
183 } else {
184         setExprMatchRegex("TAG",/^((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)))/);
185 }
186
187 //  Perform some capability-testing.
188 //
189 var div = document.createElement("div");
190
191 //  Sometimes getElementsByTagName("*") will return comment nodes,
192 //  which we will have to remove from the results.
193 //
194 var gebtn_yields_comments = false;
195 div.appendChild(document.createComment(""));
196 if(div.getElementsByTagName("*").length > 0) {
197     gebtn_yields_comments = true;
198 }
199
200 //  Some browsers return node.localName in upper case, some in lower case.
201 //
202 var localname_is_uppercase = true;
203 if(div.localName && div.localName == "div") {
204     localname_is_uppercase = false;
205 }
206
207 //  Allow the testing div to be garbage-collected.
208 //
209 div = null;
210
211 function find_tag_17(match, context, isXML) {
212     var ns = getNamespaceURI(match[2]);
213     var ln = match[3];
214     return find_tag(ns, ln, match, context, isXML);
215 }
216
217 function find_tag_18(match, context, isXML) {
218         var index = match.indexOf("|");
219         
220         if (index == -1) {
221                 if ( typeof context.getElementsByTagName !== "undefined" ) {
222                         return context.getElementsByTagName(match);
223                 }
224         }
225
226     var ns = getNamespaceURI(match.substring(0,index + 1));
227     var ln = match.substring(index + 1);
228     
229     return find_tag(ns, ln, match, context, isXML);
230 }
231
232 function find_tag(ns, ln, match, context, isXML) {
233         var res;
234     if(typeof context.getElementsByTagNameNS != "undefined") {
235         //  Easy case - we have getElementsByTagNameNS
236         res = context.getElementsByTagNameNS(ns,ln);
237     } else if(typeof context.selectNodes != "undefined") {
238         //  Use xpath if possible (not available on HTML DOM nodes in IE)
239         if(context.ownerDocument) {
240             context.ownerDocument.setProperty("SelectionLanguage","XPath");
241         } else {
242             context.setProperty("SelectionLanguage","XPath");
243         }
244         var predicate = "";
245         if(ns != "*") {
246             if(ln != "*") {
247                 predicate="namespace-uri()='"+ns+"' and local-name()='"+ln+"'";
248             } else {
249                 predicate="namespace-uri()='"+ns+"'";
250             }
251         } else {
252             if(ln != "*") {
253                 predicate="local-name()='"+ln+"'";
254             }
255         }
256         if(predicate) {
257             res = context.selectNodes("descendant-or-self::*["+predicate+"]");
258         } else {
259             res = context.selectNodes("descendant-or-self::*");
260         }
261     } else {
262         //  Otherwise, we need to simulate using getElementsByTagName
263         res = context.getElementsByTagName(ln); 
264         if(gebtn_yields_comments && ln == "*") {
265             var tmp = [];
266             for(var i=0; res[i]; i++) {
267                 if(res[i].nodeType == 1) {
268                     tmp.push(res[i]);
269                 }
270             }
271             res = tmp;
272         }
273         if(res && ns != "*") {
274             var tmp = [];
275             for(var i=0; res[i]; i++) {
276                if(res[i].namespaceURI == ns || res[i].tagUrn == ns) {
277                    tmp.push(res[i]);
278                }
279             }
280             res = tmp;
281         }
282     }
283     return res;
284 }
285
286 //  Modify the TAG find function to account for a namespace selector.
287 //
288 $.expr.find.TAG = (jquery_gt_18)? find_tag_18 : find_tag_17;
289
290 //  Check whether a node is part of an XML document.
291 //  Copied verbatim from jQuery sources, needed in TAG preFilter below.
292 //
293 var isXML = function(elem){
294     return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
295             !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
296 };
297
298 //  Modify the TAG preFilter function to work with modified match regexp.
299 //  This normalises case of the tag name if we're in a HTML document.
300 //
301 $.expr.preFilter.TAG = (jquery_gt_18)? function(match, curLoop, inplace, result, not, isXML) {
302   var ln = match[3];
303   if(!isXML) {
304       if(localname_is_uppercase) {
305           ln = ln.toUpperCase();
306       } else {
307           ln = ln.toLowerCase();
308       }
309   }
310   return [match[0], match[0],getNamespaceURI(match[2]),ln];
311 } : function(match, curLoop, inplace, result, not, isXML) {
312           var ln = match[3];
313           if(!isXML) {
314               if(localname_is_uppercase) {
315                   ln = ln.toUpperCase();
316               } else {
317                   ln = ln.toLowerCase();
318               }
319           }
320           return [match[0],getNamespaceURI(match[2]),ln];
321 };
322
323 function filter_tag(ln, ns) {
324         var index = ln.indexOf("|");
325         if (index != -1)
326                 ln = ln.substring(index+1);
327     return function( elem ) {
328         var e_ns = elem.namespaceURI ? elem.namespaceURI : elem.tagUrn;
329         var e_ln = elem.localName ? elem.localName : elem.tagName;
330         if(ns == "*" || e_ns == ns || (ns == "" && !e_ns)) {
331             return ((ln == "*" && elem.nodeType == 1)  || e_ln == ln);
332         }
333         
334         return false;
335         };
336 };
337
338 //Modify the TAG filter function to account for a namespace selector.
339 //
340 $.expr.filter.TAG = (jquery_gt_18)? filter_tag : function(elem, match) {
341         var ns = match[1];
342     var ln = match[2];
343     return filter_tag(ln, ns).call(this, elem, null);
344 };
345
346 //  Modify the ATTR match regexp to extract a namespace selector.
347 //  This is basically ([namespace|])(attrname)(op)(quote)(pattern)(quote)
348 //
349 if (jquery_gt_18) {
350         setExprMatchRegex("ATTR",/^\[[\x20\t\r\n\f]*(((?:\\.|[-\w]|[^\x00-\xa0])+\|)?((?:\\.|[-\w]|[^\x00-\xa0])+))[\x20\t\r\n\f]*(?:([*^$|!~]?=)[\x20\t\r\n\f]*(?:(['"])((?:\\.|[^\\])*?)\5|((?:\\.|[-\w]|[^\x00-\xa0])+\|)?((?:\\.|[-\w#]|[^\x00-\xa0])+)|)|)[\x20\t\r\n\f]*\]/);
351 } else {
352         setExprMatchRegex("ATTR",/\[\s*((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF_-]|\\.)+)))\s*(?:(\S?=)\s*(['"]*)(.*?)\5|)\s*\]/);
353 }
354
355 //  Modify the ATTR preFilter function to account for new regexp match groups,
356 //  and normalise the namespace URI.
357 //
358 $.expr.preFilter.ATTR = (jquery_gt_18)? function( match, context, isXml ) {
359         var name = match[1].replace(rbackslash, "");
360         
361         if( match[4] == "~=" ) {
362         match[6] = " " + match[6] + " ";
363     }
364         
365         // Move the given value to match[5] whether quoted or unquoted
366         match[5] = ( match[6] || match[7] || "" ).replace( rbackslash, "" );
367         
368         if(!match[2] || match[2] == "|") {
369         match[2] = "";
370     } else {
371         match[2] = getNamespaceURI(match[2]);
372     }
373         
374         return match.slice( 0, 6 );
375 } : function(match, curLoop, inplace, result, not, isXML) {
376     var name = match[3].replace(/\\/g, "");
377     if(!isXML && $.expr.attrMap[name]) {
378         match[3] = $.expr.attrMap[name];
379     }
380     if( match[4] == "~=" ) {
381         match[6] = " " + match[6] + " ";
382     }
383     if(!match[2] || match[2] == "|") {
384         match[2] = "";
385     } else {
386         match[2] = getNamespaceURI(match[2]);
387     }
388     return match;
389 };
390
391
392
393 var attr_op_eval = function(result, operator, check) {
394         if ( result == null ) {
395                 return operator === "!=";
396         }
397         
398         if ( !operator ) {
399                 return true;
400         }
401         
402         result += "";
403
404         return operator === "=" ? result === check :
405                 operator === "!=" ? result !== check :
406                 operator === "^=" ? check && result.indexOf( check ) === 0 :
407                 operator === "*=" ? check && result.indexOf( check ) > -1 :
408                 operator === "$=" ? check && result.substr( result.length - check.length ) === check :
409                 operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
410                 operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
411                 false;
412 };
413
414 //Modify the ATTR filter function to account for namespace selector.
415 //Unfortunately this means factoring out the attribute-checking code
416 //into a separate function, since it might be called multiple times.
417 //
418 function filter_attr( prefixedName, ns, name, op, check ) {
419         return function( elem, context ) {
420                 if(ns == "") {
421                         var result = $(elem).attr(name);
422             return attr_op_eval(result, op, check);
423                 } else {
424                         if(ns != "*" && typeof elem.getAttributeNS != "undefined") {
425                         return attr_op_eval(elem.getAttributeNS(ns,name), op, check);
426                     }
427                         
428                         //  Need to iterate over all attributes, either because we couldn't
429                     //  look it up or because we need to match all namespaces.
430                         var attrs = elem.attributes;
431                     for (var i=0; attrs[i]; i++) {
432                         var ln = attrs[i].localName;
433                         if(!ln) {
434                             ln = attrs[i].nodeName;
435                             var idx = ln.indexOf(":");
436                             if(idx >= 0) {
437                                 ln = ln.substr(idx+1);
438                             }
439                         }
440                         if(ln == name) {
441                             result = attrs[i].nodeValue;
442                             if(ns == "*" || attrs[i].namespaceURI == ns) {
443                                 if(attr_op_eval(result, op, check)) {
444                                     return true;
445                                 }
446                             }
447                             if(attrs[i].namespaceURI === "" && attrs[i].prefix) {
448                                 if(attrs[i].prefix == default_xmlns_rev[ns]) {
449                                     if(attr_op_eval(result, op, check)) {
450                                         return true;
451                                     }
452                                 }
453                             }
454                         }
455                     }
456                     return false;
457                 }
458         };
459 };
460
461 $.expr.filter.ATTR = (jquery_gt_18)? filter_attr : function(elem, match) {
462         var ns = match[2];
463     var name = match[3];
464     var type = match[4];
465     var check = match[6];
466     return filter_attr(null, ns, name, type, check).call(this, elem, null);
467 };
468
469
470 })(jQuery);