webui-aria2/js/libs/dojo/selector/lite.js.uncompressed.js
2012-05-01 19:52:07 +08:00

266 lines
8.3 KiB
JavaScript

//>>built
define("dojo/selector/lite", ["../has", "../_base/kernel"], function(has, dojo){
"use strict";
// summary:
// A small lightweight query selector engine that implements CSS2.1 selectors
// minus pseudo-classes and the sibling combinator, plus CSS3 attribute selectors
var testDiv = document.createElement("div");
var matchesSelector = testDiv.matchesSelector || testDiv.webkitMatchesSelector || testDiv.mozMatchesSelector || testDiv.msMatchesSelector || testDiv.oMatchesSelector; // IE9, WebKit, Firefox have this, but not Opera yet
var querySelectorAll = testDiv.querySelectorAll;
has.add("dom-matches-selector", !!matchesSelector);
has.add("dom-qsa", !!querySelectorAll);
// this is a simple query engine. It has handles basic selectors, and for simple
// common selectors is extremely fast
var liteEngine = function(selector, root){
if(combine && selector.indexOf(',') > -1){
return combine(selector, root);
}
var match = (querySelectorAll ?
/^([\w]*)#([\w\-]+$)|^(\.)([\w\-\*]+$)|^(\w+$)/ : // this one only matches on simple queries where we can beat qSA with specific methods
/^([\w]*)#([\w\-]+)(?:\s+(.*))?$|(?:^|(>|.+\s+))([\w\-\*]+)(\S*$)/) // this one matches parts of the query that we can use to speed up manual filtering
.exec(selector);
root = root || document;
if(match){
// fast path regardless of whether or not querySelectorAll exists
if(match[2]){
// an #id
// use dojo.byId if available as it fixes the id retrieval in IE
var found = dojo.byId ? dojo.byId(match[2]) : document.getElementById(match[2]);
if(!found || (match[1] && match[1] != found.tagName.toLowerCase())){
// if there is a tag qualifer and it doesn't match, no matches
return [];
}
if(root != document){
// there is a root element, make sure we are a child of it
var parent = found;
while(parent != root){
parent = parent.parentNode;
if(!parent){
return [];
}
}
}
return match[3] ?
liteEngine(match[3], found)
: [found];
}
if(match[3] && root.getElementsByClassName){
// a .class
return root.getElementsByClassName(match[4]);
}
var found;
if(match[5]){
// a tag
found = root.getElementsByTagName(match[5]);
if(match[4] || match[6]){
selector = (match[4] || "") + match[6];
}else{
// that was the entirety of the query, return results
return found;
}
}
}
if(querySelectorAll){
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
if (root.nodeType === 1 && root.nodeName.toLowerCase() !== "object"){
return useRoot(root, selector, root.querySelectorAll);
}else{
// we can use the native qSA
return root.querySelectorAll(selector);
}
}else if(!found){
// search all children and then filter
found = root.getElementsByTagName("*");
}
// now we filter the nodes that were found using the matchesSelector
var results = [];
for(var i = 0, l = found.length; i < l; i++){
var node = found[i];
if(node.nodeType == 1 && jsMatchesSelector(node, selector, root)){
// keep the nodes that match the selector
results.push(node);
}
}
return results;
};
var useRoot = function(context, query, method){
// this function creates a temporary id so we can do rooted qSA queries, this is taken from sizzle
var oldContext = context,
old = context.getAttribute( "id" ),
nid = old || "__dojo__",
hasParent = context.parentNode,
relativeHierarchySelector = /^\s*[+~]/.test( query );
if(relativeHierarchySelector && !hasParent){
return [];
}
if(!old){
context.setAttribute("id", nid);
}else{
nid = nid.replace(/'/g, "\\$&");
}
if(relativeHierarchySelector && hasParent){
context = context.parentNode;
}
try {
return method.call(context, "[id='" + nid + "'] " + query );
} finally {
if ( !old ) {
oldContext.removeAttribute( "id" );
}
}
};
if(!has("dom-matches-selector")){
var jsMatchesSelector = (function(){
// a JS implementation of CSS selector matching, first we start with the various handlers
var caseFix = testDiv.tagName == "div" ? "toLowerCase" : "toUpperCase";
function tag(tagName){
tagName = tagName[caseFix]();
return function(node){
return node.tagName == tagName;
}
}
function className(className){
var classNameSpaced = ' ' + className + ' ';
return function(node){
return node.className.indexOf(className) > -1 && (' ' + node.className + ' ').indexOf(classNameSpaced) > -1;
}
}
var attrComparators = {
"^=": function(attrValue, value){
return attrValue.indexOf(value) == 0;
},
"*=": function(attrValue, value){
return attrValue.indexOf(value) > -1;
},
"$=": function(attrValue, value){
return attrValue.substring(attrValue.length - value.length, attrValue.length) == value;
},
"~=": function(attrValue, value){
return (' ' + attrValue + ' ').indexOf(' ' + value + ' ') > -1;
},
"|=": function(attrValue, value){
return (attrValue + '-').indexOf(value + '-') == 0;
},
"=": function(attrValue, value){
return attrValue == value;
},
"": function(attrValue, value){
return true;
}
};
function attr(name, value, type){
if(value.match(/['"]/)){
// it is quoted, do an eval to parse the string (CSS and JS parsing are close enough)
value = eval(value);
}
var comparator = attrComparators[type || ""];
return function(node){
var attrValue = node.getAttribute(name);
return attrValue && comparator(attrValue, value);
}
}
function ancestor(matcher){
return function(node, root){
while((node = node.parentNode) != root){
if(matcher(node, root)){
return true;
}
}
};
}
function parent(matcher){
return function(node, root){
node = node.parentNode;
return matcher ?
node != root && matcher(node, root)
: node == root;
};
}
var cache = {};
function and(matcher, next){
return matcher ?
function(node, root){
return next(node) && matcher(node, root);
}
: next;
}
return function(node, selector, root){
// this returns true or false based on if the node matches the selector (optionally within the given root)
var matcher = cache[selector]; // check to see if we have created a matcher function for the given selector
if(!matcher){
// create a matcher function for the given selector
// parse the selectors
if(selector.replace(/(?:\s*([> ])\s*)|(\.)?([\w-]+)|\[([\w-]+)\s*(.?=)?\s*([^\]]*)\]/g, function(t, combinator, type, value, attrName, attrType, attrValue){
if(value){
if(type == "."){
matcher = and(matcher, className(value));
}
else{
matcher = and(matcher, tag(value));
}
}
else if(combinator){
matcher = (combinator == " " ? ancestor : parent)(matcher);
}
else if(attrName){
matcher = and(matcher, attr(attrName, attrValue, attrType));
}
return "";
})){
throw new Error("Syntax error in query");
}
if(!matcher){
return true;
}
cache[selector] = matcher;
}
// now run the matcher function on the node
return matcher(node, root);
};
})();
}
if(!has("dom-qsa")){
var combine = function(selector, root){
// combined queries
selector = selector.split(/\s*,\s*/);
var indexed = [];
// add all results and keep unique ones, this only runs in IE, so we take advantage
// of known IE features, particularly sourceIndex which is unique and allows us to
// order the results
for(var i = 0; i < selector.length; i++){
var results = liteEngine(selector[i], root);
for(var j = 0, l = results.length; j < l; j++){
var node = results[j];
indexed[node.sourceIndex] = node;
}
}
// now convert from a sparse array to a dense array
var totalResults = [];
for(i in indexed){
totalResults.push(indexed[i]);
}
return totalResults;
};
}
liteEngine.match = matchesSelector ? function(node, selector, root){
if(root){
// doesn't support three args, use rooted id trick
return useRoot(root, selector, function(query){
return matchesSelector.call(node, query);
});
}
// we have a native matchesSelector, use that
return matchesSelector.call(node, selector);
} : jsMatchesSelector; // otherwise use the JS matches impl
return liteEngine;
});