📄 query.js
字号:
if(!dojo._hasResource["dojo._base.query"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.dojo._hasResource["dojo._base.query"] = true;dojo.provide("dojo._base.query");dojo.require("dojo._base.NodeList");/* dojo.query() architectural overview: dojo.query is a relatively full-featured CSS3 query library. It is designed to take any valid CSS3 selector and return the nodes matching the selector. To do this quickly, it processes queries in several steps, applying caching where profitable. The steps (roughly in reverse order of the way they appear in the code): 1.) check to see if we already have a "query dispatcher" - if so, use that with the given parameterization. Skip to step 4. 2.) attempt to determine which branch to dispatch the query to: - JS (optimized DOM iteration) - xpath (for browsers that support it and where it's fast) - native (not available in any browser yet) 3.) tokenize and convert to executable "query dispatcher" - this is where the lion's share of the complexity in the system lies. In the DOM version, the query dispatcher is assembled as a chain of "yes/no" test functions pertaining to a section of a simple query statement (".blah:nth-child(odd)" but not "div div", which is 2 simple statements). Individual statement dispatchers are cached (to prevent re-definition) as are entire dispatch chains (to make re-execution of the same query fast) - in the xpath path, tokenization yeilds a concatenation of parameterized xpath selectors. As with the DOM version, both simple selector blocks and overall evaluators are cached to prevent re-defintion 4.) the resulting query dispatcher is called in the passed scope (by default the top-level document) - for DOM queries, this results in a recursive, top-down evaluation of nodes based on each simple query section - xpath queries can, thankfully, be executed in one shot 5.) matched nodes are pruned to ensure they are unique*/;(function(){ // define everything in a closure for compressability reasons. "d" is an // alias to "dojo" since it's so frequently used. This seems a // transformation that the build system could perform on a per-file basis. //////////////////////////////////////////////////////////////////////// // Utility code //////////////////////////////////////////////////////////////////////// var d = dojo; var childNodesName = dojo.isIE ? "children" : "childNodes"; var caseSensitive = false; var getQueryParts = function(query){ // summary: state machine for query tokenization if(">~+".indexOf(query.charAt(query.length-1)) >= 0){ query += " *" } query += " "; // ensure that we terminate the state machine var ts = function(s, e){ return d.trim(query.slice(s, e)); } // the overall data graph of the full query, as represented by queryPart objects var qparts = []; // state keeping vars var inBrackets = -1; var inParens = -1; var inMatchFor = -1; var inPseudo = -1; var inClass = -1; var inId = -1; var inTag = -1; var lc = ""; // the last character var cc = ""; // the current character var pStart; // iteration vars var x = 0; // index in the query var ql = query.length; var currentPart = null; // data structure representing the entire clause var _cp = null; // the current pseudo or attr matcher var endTag = function(){ if(inTag >= 0){ var tv = (inTag == x) ? null : ts(inTag, x).toLowerCase(); currentPart[ (">~+".indexOf(tv) < 0) ? "tag" : "oper" ] = tv; inTag = -1; } } var endId = function(){ if(inId >= 0){ currentPart.id = ts(inId, x).replace(/\\/g, ""); inId = -1; } } var endClass = function(){ if(inClass >= 0){ currentPart.classes.push(ts(inClass+1, x).replace(/\\/g, "")); inClass = -1; } } var endAll = function(){ endId(); endTag(); endClass(); } for(; lc=cc, cc=query.charAt(x),x<ql; x++){ if(lc == "\\"){ continue; } if(!currentPart){ // NOTE: I hate all this alloc, but it's shorter than writing tons of if's pStart = x; currentPart = { query: null, pseudos: [], attrs: [], classes: [], tag: null, oper: null, id: null }; inTag = x; } if(inBrackets >= 0){ // look for a the close first if(cc == "]"){ if(!_cp.attr){ _cp.attr = ts(inBrackets+1, x); }else{ _cp.matchFor = ts((inMatchFor||inBrackets+1), x); } var cmf = _cp.matchFor; if(cmf){ if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){ _cp.matchFor = cmf.substring(1, cmf.length-1); } } currentPart.attrs.push(_cp); _cp = null; // necessaray? inBrackets = inMatchFor = -1; }else if(cc == "="){ var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : ""; _cp.type = addToCc+cc; _cp.attr = ts(inBrackets+1, x-addToCc.length); inMatchFor = x+1; } // now look for other clause parts }else if(inParens >= 0){ if(cc == ")"){ if(inPseudo >= 0){ _cp.value = ts(inParens+1, x); } inPseudo = inParens = -1; } }else if(cc == "#"){ endAll(); inId = x+1; }else if(cc == "."){ endAll(); inClass = x; }else if(cc == ":"){ endAll(); inPseudo = x; }else if(cc == "["){ endAll(); inBrackets = x; _cp = { /*===== attr: null, type: null, matchFor: null =====*/ }; }else if(cc == "("){ if(inPseudo >= 0){ _cp = { name: ts(inPseudo+1, x), value: null } currentPart.pseudos.push(_cp); } inParens = x; }else if(cc == " " && lc != cc){ // note that we expect the string to be " " terminated endAll(); if(inPseudo >= 0){ currentPart.pseudos.push({ name: ts(inPseudo+1, x) }); } currentPart.hasLoops = ( currentPart.pseudos.length || currentPart.attrs.length || currentPart.classes.length ); currentPart.query = ts(pStart, x); currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*"); qparts.push(currentPart); currentPart = null; } } return qparts; }; //////////////////////////////////////////////////////////////////////// // XPath query code //////////////////////////////////////////////////////////////////////// // this array is a lookup used to generate an attribute matching function. // There is a similar lookup/generator list for the DOM branch with similar // calling semantics. var xPathAttrs = { "*=": function(attr, value){ return "[contains(@"+attr+", '"+ value +"')]"; }, "^=": function(attr, value){ return "[starts-with(@"+attr+", '"+ value +"')]"; }, "$=": function(attr, value){ return "[substring(@"+attr+", string-length(@"+attr+")-"+(value.length-1)+")='"+value+"']"; }, "~=": function(attr, value){ return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]"; }, "|=": function(attr, value){ return "[contains(concat(' ',@"+attr+",' '), ' "+ value +"-')]"; }, "=": function(attr, value){ return "[@"+attr+"='"+ value +"']"; } }; // takes a list of attribute searches, the overall query, a function to // generate a default matcher, and a closure-bound method for providing a // matching function that generates whatever type of yes/no distinguisher // the query method needs. The method is a bit tortured and hard to read // because it needs to be used in both the XPath and DOM branches. var handleAttrs = function( attrList, query, getDefault, handleMatch){ d.forEach(query.attrs, function(attr){ var matcher; // type, attr, matchFor if(attr.type && attrList[attr.type]){ matcher = attrList[attr.type](attr.attr, attr.matchFor); }else if(attr.attr.length){ matcher = getDefault(attr.attr); } if(matcher){ handleMatch(matcher); } }); } var buildPath = function(query){ var xpath = "."; var qparts = getQueryParts(d.trim(query)); while(qparts.length){ var tqp = qparts.shift(); var prefix; var postfix = ""; if(tqp.oper == ">"){ prefix = "/"; // prefix = "/child::*"; tqp = qparts.shift(); }else if(tqp.oper == "~"){ prefix = "/following-sibling::"; // get element following siblings tqp = qparts.shift(); }else if(tqp.oper == "+"){ // FIXME: // fails when selecting subsequent siblings by node type // because the position() checks the position in the list // of matching elements and not the localized siblings prefix = "/following-sibling::"; postfix = "[position()=1]"; tqp = qparts.shift(); }else{ prefix = "//"; // prefix = "/descendant::*" } // get the tag name (if any) xpath += prefix + tqp.tag + postfix; // check to see if it's got an id. Needs to come first in xpath. if(tqp.id){ xpath += "[@id='"+tqp.id+"'][1]"; } d.forEach(tqp.classes, function(cn){ var cnl = cn.length; var padding = " "; if(cn.charAt(cnl-1) == "*"){ padding = ""; cn = cn.substr(0, cnl-1); } xpath += "[contains(concat(' ',@class,' '), ' "+ cn + padding + "')]"; }); handleAttrs(xPathAttrs, tqp, function(condition){ return "[@"+condition+"]"; }, function(matcher){ xpath += matcher; } ); // FIXME: need to implement pseudo-class checks!! }; return xpath; }; var _xpathFuncCache = {}; var getXPathFunc = function(path){ if(_xpathFuncCache[path]){ return _xpathFuncCache[path]; } var doc = d.doc; // don't need to memoize. The closure scope handles it for us. var xpath = buildPath(path); var tf = function(parent){ // XPath query strings are memoized. var ret = []; var xpathResult; try{ xpathResult = doc.evaluate(xpath, parent, null, // XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); XPathResult.ANY_TYPE, null); }catch(e){ console.debug("failure in exprssion:", xpath, "under:", parent); console.debug(e); } var result = xpathResult.iterateNext(); while(result){ ret.push(result); result = xpathResult.iterateNext(); } return ret; } return _xpathFuncCache[path] = tf; }; /* d.xPathMatch = function(query){ // XPath based DOM query system. Handles a small subset of CSS // selectors, subset is identical to the non-XPath version of this // function. return getXPathFunc(query)(); } */ //////////////////////////////////////////////////////////////////////// // DOM query code //////////////////////////////////////////////////////////////////////// var _filtersCache = {}; var _simpleFiltersCache = {}; // the basic building block of the yes/no chaining system. agree(f1, f2) // generates a new function which returns the boolean results of both of // the passed functions to a single logical-anded result. var agree = function(first, second){ if(!first){ return second; } if(!second){ return first; } return function(){ return first.apply(window, arguments) && second.apply(window, arguments); } } var _childElements = function(root){ var ret = []; var te, x=0, tret = root[childNodesName]; while(te=tret[x++]){ if(te.nodeType == 1){ ret.push(te); } } return ret; } var _nextSiblings = function(root, single){ var ret = []; var te = root; while(te = te.nextSibling){ if(te.nodeType == 1){ ret.push(te); if(single){ break; } } } return ret; } var _filterDown = function(element, queryParts, matchArr, idx){ // NOTE: // in the fast path! this function is called recursively and for
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -