//
//  jquery.xmlns.js:  xml-namespace selector support for jQuery
//
//  This plugin modifies the jQuery tag and attribute selectors to
//  support optional namespace specifiers as defined in CSS 3:
//
//    $("elem")      - matches 'elem' nodes in the default namespace
//    $("|elem")     - matches 'elem' nodes that don't have a namespace
//    $("NS|elem")   - matches 'elem' nodes in declared namespace 'NS'
//    $("*|elem")    - matches 'elem' nodes in any namespace
//    $("NS|*")      - matches any nodes in declared namespace 'NS'
//
//  A similar synax is also supported for attribute selectors, but note
//  that the default namespace does *not* apply to attributes - a missing
//  or empty namespace selector selects only attributes with no namespace.
//
//  In a slight break from the W3C standards, and with a nod to ease of
//  implementation, the empty namespace URI is treated as equivalent to
//  an unspecified namespace.  Plenty of browsers seem to make the same
//  assumption...
//
//  Namespace declarations live in the $.xmlns object, which is a simple
//  mapping from namespace ids to namespace URIs.  The default namespace
//  is determined by the value associated with the empty string.
//
//    $.xmlns.D = "DAV:"
//    $.xmlns.FOO = "http://www.foo.com/xmlns/foobar"
//    $.xmlns[""] = "http://www.example.com/new/default/namespace/"
//
//  Unfortunately this is a global setting - I can't find a way to do
//  query-object-specific namespaces since the jQuery selector machinery
//  is stateless.  However, you can use the 'xmlns' function to push and
//  pop namespace delcarations with ease:
//
//    $().xmlns({D:"DAV:"})     // pushes the DAV: namespace
//    $().xmlns("DAV:")         // makes DAV: the default namespace
//    $().xmlns(false)          // pops the namespace we just pushed
//    $().xmlns(false)          // pops again, returning to defaults
//
//  To execute this as a kind of "transaction", pass a function as the
//  second argument.  It will be executed in the context of the current
//  jQuery object:
//
//    $().xmlns("DAV:",function() {
//      //  The default namespace is DAV: within this function,
//      //  but it is reset to the previous value on exit.
//      return this.find("response").each(...);
//    }).find("div")
//
//  If you pass a string as a function, it will be executed against the
//  current jQuery object using find(); i.e. the following will find all
//  "href" elements in the "DAV:" namespace:
//
//    $().xmlns("DAV:","href")
//
// 
//  And finally, the legal stuff:
//
//    Copyright (c) 2009, Ryan Kelly.
//    TAG and ATTR functions derived from jQuery's selector.js.
//    Dual licensed under the MIT and GPL licenses.
//    http://docs.jquery.com/License
//

(function($) {

var jquery_gt_18 = jQuery.fn.jquery >= "1.8";
//  Some common default namespaces, that are treated specially by browsers.
//
var default_xmlns = {
    "xml": "http://www.w3.org/XML/1998/namespace",
    "xmlns": "http://www.w3.org/2000/xmlns/",
    "html": "http://www.w3.org/1999/xhtml/"
};
 
rbackslash = /\\(?!\\)/g;

//  A reverse mapping for common namespace prefixes.
//
var default_xmlns_rev = {}
for(var k in default_xmlns) {
    default_xmlns_rev[default_xmlns[k]] = k;
}


//  $.xmlns is a mapping from namespace identifiers to namespace URIs.
//  The default default-namespace is "*", and we provide some additional
//  defaults that are specified in the XML Namespaces standard.
//
$.extend({xmlns: $.extend({},default_xmlns,{"":"*"})});


//  jQuery method to push/pop namespace declarations.
//
//  If a single argument is specified:
//    * if it's a mapping, push those namespaces onto the stack
//    * if it's a string, push that as the default namespace
//    * if it evaluates to false, pop the latest namespace
//
//  If two arguments are specified, the second is executed "transactionally"
//  using the namespace declarations found in the first.  It can be either a
//  a selector string (in which case it is passed to this.find()) or a function
//  (in which case it is called in the context of the current jQuery object).
//  The given namespace mapping is automatically pushed before executing and
//  popped afterwards.
//
var xmlns_stack = [];
$.fn.extend({xmlns: function(nsmap,func) {
    if(typeof nsmap == "string") {
        nsmap = {"":nsmap};
    }
    if(nsmap) {
        xmlns_stack.push($.xmlns);
        $.xmlns = $.extend({},$.xmlns,nsmap);
        if(func !== undefined) {
            if(typeof func == "string") {
                return this.find(func).xmlns(undefined)
            } else {
                var self = this;
                try {
                    self = func.call(this);
                    if(!self) {
                        self = this;
                    }
                } finally {
                    self.xmlns(undefined);
                }
                return self
            }
        } else {
            return this;
        }
    } else {
        $.xmlns = (xmlns_stack ? xmlns_stack.pop() : {});
        return this;
    }
}});


//  Convert a namespace prefix into a namespace URI, based
//  on the delcarations made in $.xmlns.
//
var getNamespaceURI = function(id) {
    // No namespace id, use the default.
    if(!id) {
        return $.xmlns[""];
    }
    // Strip the pipe character from the specifier
    id = id.substr(0,id.length-1);
    // Certain special namespaces aren't mapped to a URI
    if(id == "" || id == "*") {
        return id;
    }
    var ns = $.xmlns[id];
    if(typeof(ns) == "undefined") {
        throw "Syntax error, undefined namespace prefix '" + id + "'";
    }
    return ns;
};


//  Update the regex used by $.expr to parse selector components for a
//  particular type of selector (e.g. "TAG" or "ATTR").
//
//  This logic is taken straight from the jQuery/Sizzle sources.
//
var setExprMatchRegex = (jquery_gt_18) ? function(type,regex) {
		$.expr.match[type] = regex;
	} : function(type,regex) {
	  $.expr.match[type] = new RegExp(regex.source + /(?![^\[]*\])(?![^\(]*\))/.source);
	  if($.expr.leftMatch) {
	      $.expr.leftMatch[type] = new RegExp(/(^(?:.|\r|\n)*?)/.source + $.expr.match[type].source.replace(/\\(\d+)/g, function(all, num){
	          return "\\" + (num - 0 + 1);
	      }));
	  }
	};

//  Modify the TAG match regexp to include optional namespace selector.
//  This is basically (namespace|)?(tagname).
//
if (jquery_gt_18) {
	setExprMatchRegex("TAG",/^((?:((?:\\.|[-\w*]|[^\x00-\xa0])+\|)?((?:\\.|[-\w*]|[^\x00-\xa0])+)))/);
} else {
	setExprMatchRegex("TAG",/^((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)))/);
}

//  Perform some capability-testing.
//
var div = document.createElement("div");

//  Sometimes getElementsByTagName("*") will return comment nodes,
//  which we will have to remove from the results.
//
var gebtn_yields_comments = false;
div.appendChild(document.createComment(""));
if(div.getElementsByTagName("*").length > 0) {
    gebtn_yields_comments = true;
}

//  Some browsers return node.localName in upper case, some in lower case.
//
var localname_is_uppercase = true;
if(div.localName && div.localName == "div") {
    localname_is_uppercase = false;
}

//  Allow the testing div to be garbage-collected.
//
div = null;

function find_tag_17(match, context, isXML) {
    var ns = getNamespaceURI(match[2]);
    var ln = match[3];
    return find_tag(ns, ln, match, context, isXML);
}

function find_tag_18(match, context, isXML) {
	var index = match.indexOf("|");
	
	if (index == -1) {
		if ( typeof context.getElementsByTagName !== "undefined" ) {
			return context.getElementsByTagName(match);
		}
	}

    var ns = getNamespaceURI(match.substring(0,index + 1));
    var ln = match.substring(index + 1);
    
    return find_tag(ns, ln, match, context, isXML);
}

function find_tag(ns, ln, match, context, isXML) {
	var res;
    if(typeof context.getElementsByTagNameNS != "undefined") {
        //  Easy case - we have getElementsByTagNameNS
        res = context.getElementsByTagNameNS(ns,ln);
    } else if(typeof context.selectNodes != "undefined") {
        //  Use xpath if possible (not available on HTML DOM nodes in IE)
        if(context.ownerDocument) {
            context.ownerDocument.setProperty("SelectionLanguage","XPath");
        } else {
            context.setProperty("SelectionLanguage","XPath");
        }
        var predicate = "";
        if(ns != "*") {
            if(ln != "*") {
                predicate="namespace-uri()='"+ns+"' and local-name()='"+ln+"'";
            } else {
                predicate="namespace-uri()='"+ns+"'";
            }
        } else {
            if(ln != "*") {
                predicate="local-name()='"+ln+"'";
            }
        }
        if(predicate) {
            res = context.selectNodes("descendant-or-self::*["+predicate+"]");
        } else {
            res = context.selectNodes("descendant-or-self::*");
        }
    } else {
        //  Otherwise, we need to simulate using getElementsByTagName
        res = context.getElementsByTagName(ln); 
        if(gebtn_yields_comments && ln == "*") {
            var tmp = [];
            for(var i=0; res[i]; i++) {
                if(res[i].nodeType == 1) {
                    tmp.push(res[i]);
                }
            }
            res = tmp;
        }
        if(res && ns != "*") {
            var tmp = [];
            for(var i=0; res[i]; i++) {
               if(res[i].namespaceURI == ns || res[i].tagUrn == ns) {
                   tmp.push(res[i]);
               }
            }
            res = tmp;
        }
    }
    return res;
}

//  Modify the TAG find function to account for a namespace selector.
//
$.expr.find.TAG = (jquery_gt_18)? find_tag_18 : find_tag_17;

//  Check whether a node is part of an XML document.
//  Copied verbatim from jQuery sources, needed in TAG preFilter below.
//
var isXML = function(elem){
    return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
            !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
};

//  Modify the TAG preFilter function to work with modified match regexp.
//  This normalises case of the tag name if we're in a HTML document.
//
$.expr.preFilter.TAG = (jquery_gt_18)? function(match, curLoop, inplace, result, not, isXML) {
  var ln = match[3];
  if(!isXML) {
      if(localname_is_uppercase) {
          ln = ln.toUpperCase();
      } else {
          ln = ln.toLowerCase();
      }
  }
  return [match[0], match[0],getNamespaceURI(match[2]),ln];
} : function(match, curLoop, inplace, result, not, isXML) {
	  var ln = match[3];
	  if(!isXML) {
	      if(localname_is_uppercase) {
	          ln = ln.toUpperCase();
	      } else {
	          ln = ln.toLowerCase();
	      }
	  }
	  return [match[0],getNamespaceURI(match[2]),ln];
};

function filter_tag(ln, ns) {
	var index = ln.indexOf("|");
	if (index != -1)
		ln = ln.substring(index+1);
    return function( elem ) {
    	var e_ns = elem.namespaceURI ? elem.namespaceURI : elem.tagUrn;
        var e_ln = elem.localName ? elem.localName : elem.tagName;
        if(ns == "*" || e_ns == ns || (ns == "" && !e_ns)) {
            return ((ln == "*" && elem.nodeType == 1)  || e_ln == ln);
        }
        
        return false;
	};
};

//Modify the TAG filter function to account for a namespace selector.
//
$.expr.filter.TAG = (jquery_gt_18)? filter_tag : function(elem, match) {
	var ns = match[1];
    var ln = match[2];
    return filter_tag(ln, ns).call(this, elem, null);
};

//  Modify the ATTR match regexp to extract a namespace selector.
//  This is basically ([namespace|])(attrname)(op)(quote)(pattern)(quote)
//
if (jquery_gt_18) {
	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]*\]/);
} else {
	setExprMatchRegex("ATTR",/\[\s*((?:((?:[\w\u00c0-\uFFFF\*_-]*\|)?)((?:[\w\u00c0-\uFFFF_-]|\\.)+)))\s*(?:(\S?=)\s*(['"]*)(.*?)\5|)\s*\]/);
}

//  Modify the ATTR preFilter function to account for new regexp match groups,
//  and normalise the namespace URI.
//
$.expr.preFilter.ATTR = (jquery_gt_18)? function( match, context, isXml ) {
	var name = match[1].replace(rbackslash, "");
	
	if( match[4] == "~=" ) {
        match[6] = " " + match[6] + " ";
    }
	
	// Move the given value to match[5] whether quoted or unquoted
	match[5] = ( match[6] || match[7] || "" ).replace( rbackslash, "" );
	
	if(!match[2] || match[2] == "|") {
        match[2] = "";
    } else {
        match[2] = getNamespaceURI(match[2]);
    }
	
	return match.slice( 0, 6 );
} : function(match, curLoop, inplace, result, not, isXML) {
    var name = match[3].replace(/\\/g, "");
    if(!isXML && $.expr.attrMap[name]) {
        match[3] = $.expr.attrMap[name];
    }
    if( match[4] == "~=" ) {
        match[6] = " " + match[6] + " ";
    }
    if(!match[2] || match[2] == "|") {
        match[2] = "";
    } else {
        match[2] = getNamespaceURI(match[2]);
    }
    return match;
};



var attr_op_eval = function(result, operator, check) {
	if ( result == null ) {
		return operator === "!=";
	}
	
	if ( !operator ) {
		return true;
	}
	
	result += "";

	return operator === "=" ? result === check :
		operator === "!=" ? result !== check :
		operator === "^=" ? check && result.indexOf( check ) === 0 :
		operator === "*=" ? check && result.indexOf( check ) > -1 :
		operator === "$=" ? check && result.substr( result.length - check.length ) === check :
		operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
		operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
		false;
};

//Modify the ATTR filter function to account for namespace selector.
//Unfortunately this means factoring out the attribute-checking code
//into a separate function, since it might be called multiple times.
//
function filter_attr( prefixedName, ns, name, op, check ) {
	return function( elem, context ) {
		if(ns == "") {
			var result = $(elem).attr(name);
            return attr_op_eval(result, op, check);
		} else {
			if(ns != "*" && typeof elem.getAttributeNS != "undefined") {
		        return attr_op_eval(elem.getAttributeNS(ns,name), op, check);
		    }
			
			//  Need to iterate over all attributes, either because we couldn't
		    //  look it up or because we need to match all namespaces.
			var attrs = elem.attributes;
		    for (var i=0; attrs[i]; i++) {
		        var ln = attrs[i].localName;
		        if(!ln) {
		            ln = attrs[i].nodeName;
		            var idx = ln.indexOf(":");
		            if(idx >= 0) {
		                ln = ln.substr(idx+1);
		            }
		        }
		        if(ln == name) {
		            result = attrs[i].nodeValue;
		            if(ns == "*" || attrs[i].namespaceURI == ns) {
		                if(attr_op_eval(result, op, check)) {
		                    return true;
		                }
		            }
		            if(attrs[i].namespaceURI === "" && attrs[i].prefix) {
		                if(attrs[i].prefix == default_xmlns_rev[ns]) {
		                    if(attr_op_eval(result, op, check)) {
		                        return true;
		                    }
		                }
		            }
		        }
		    }
		    return false;
		}
	};
};

$.expr.filter.ATTR = (jquery_gt_18)? filter_attr : function(elem, match) {
	var ns = match[2];
    var name = match[3];
    var type = match[4];
    var check = match[6];
    return filter_attr(null, ns, name, type, check).call(this, elem, null);
};


})(jQuery);