acme.js 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508
  1. define("dojo/selector/acme", ["../_base/kernel", "../has", "../dom", "../_base/sniff", "../_base/array", "../_base/lang", "../_base/window"], function(dojo, has, dom){
  2. // module:
  3. // dojo/selector/acme
  4. // summary:
  5. // This module defines the Acme selector engine
  6. /*
  7. acme architectural overview:
  8. acme is a relatively full-featured CSS3 query library. It is
  9. designed to take any valid CSS3 selector and return the nodes matching
  10. the selector. To do this quickly, it processes queries in several
  11. steps, applying caching where profitable.
  12. The steps (roughly in reverse order of the way they appear in the code):
  13. 1.) check to see if we already have a "query dispatcher"
  14. - if so, use that with the given parameterization. Skip to step 4.
  15. 2.) attempt to determine which branch to dispatch the query to:
  16. - JS (optimized DOM iteration)
  17. - native (FF3.1+, Safari 3.1+, IE 8+)
  18. 3.) tokenize and convert to executable "query dispatcher"
  19. - this is where the lion's share of the complexity in the
  20. system lies. In the DOM version, the query dispatcher is
  21. assembled as a chain of "yes/no" test functions pertaining to
  22. a section of a simple query statement (".blah:nth-child(odd)"
  23. but not "div div", which is 2 simple statements). Individual
  24. statement dispatchers are cached (to prevent re-definition)
  25. as are entire dispatch chains (to make re-execution of the
  26. same query fast)
  27. 4.) the resulting query dispatcher is called in the passed scope
  28. (by default the top-level document)
  29. - for DOM queries, this results in a recursive, top-down
  30. evaluation of nodes based on each simple query section
  31. - for native implementations, this may mean working around spec
  32. bugs. So be it.
  33. 5.) matched nodes are pruned to ensure they are unique (if necessary)
  34. */
  35. ////////////////////////////////////////////////////////////////////////
  36. // Toolkit aliases
  37. ////////////////////////////////////////////////////////////////////////
  38. // if you are extracting acme for use in your own system, you will
  39. // need to provide these methods and properties. No other porting should be
  40. // necessary, save for configuring the system to use a class other than
  41. // dojo.NodeList as the return instance instantiator
  42. var trim = dojo.trim;
  43. var each = dojo.forEach;
  44. // d.isIE; // float
  45. // d.isSafari; // float
  46. // d.isOpera; // float
  47. // d.isWebKit; // float
  48. // d.doc ; // document element
  49. var getDoc = function(){ return dojo.doc; };
  50. // NOTE(alex): the spec is idiotic. CSS queries should ALWAYS be case-sensitive, but nooooooo
  51. var cssCaseBug = (getDoc().compatMode) == "BackCompat";
  52. ////////////////////////////////////////////////////////////////////////
  53. // Global utilities
  54. ////////////////////////////////////////////////////////////////////////
  55. var specials = ">~+";
  56. // global thunk to determine whether we should treat the current query as
  57. // case sensitive or not. This switch is flipped by the query evaluator
  58. // based on the document passed as the context to search.
  59. var caseSensitive = false;
  60. // how high?
  61. var yesman = function(){ return true; };
  62. ////////////////////////////////////////////////////////////////////////
  63. // Tokenizer
  64. ////////////////////////////////////////////////////////////////////////
  65. var getQueryParts = function(query){
  66. // summary:
  67. // state machine for query tokenization
  68. // description:
  69. // instead of using a brittle and slow regex-based CSS parser,
  70. // acme implements an AST-style query representation. This
  71. // representation is only generated once per query. For example,
  72. // the same query run multiple times or under different root nodes
  73. // does not re-parse the selector expression but instead uses the
  74. // cached data structure. The state machine implemented here
  75. // terminates on the last " " (space) character and returns an
  76. // ordered array of query component structures (or "parts"). Each
  77. // part represents an operator or a simple CSS filtering
  78. // expression. The structure for parts is documented in the code
  79. // below.
  80. // NOTE:
  81. // this code is designed to run fast and compress well. Sacrifices
  82. // to readability and maintainability have been made. Your best
  83. // bet when hacking the tokenizer is to put The Donnas on *really*
  84. // loud (may we recommend their "Spend The Night" release?) and
  85. // just assume you're gonna make mistakes. Keep the unit tests
  86. // open and run them frequently. Knowing is half the battle ;-)
  87. if(specials.indexOf(query.slice(-1)) >= 0){
  88. // if we end with a ">", "+", or "~", that means we're implicitly
  89. // searching all children, so make it explicit
  90. query += " * "
  91. }else{
  92. // if you have not provided a terminator, one will be provided for
  93. // you...
  94. query += " ";
  95. }
  96. var ts = function(/*Integer*/ s, /*Integer*/ e){
  97. // trim and slice.
  98. // take an index to start a string slice from and an end position
  99. // and return a trimmed copy of that sub-string
  100. return trim(query.slice(s, e));
  101. };
  102. // the overall data graph of the full query, as represented by queryPart objects
  103. var queryParts = [];
  104. // state keeping vars
  105. var inBrackets = -1, inParens = -1, inMatchFor = -1,
  106. inPseudo = -1, inClass = -1, inId = -1, inTag = -1, currentQuoteChar,
  107. lc = "", cc = "", pStart;
  108. // iteration vars
  109. var x = 0, // index in the query
  110. ql = query.length,
  111. currentPart = null, // data structure representing the entire clause
  112. _cp = null; // the current pseudo or attr matcher
  113. // several temporary variables are assigned to this structure during a
  114. // potential sub-expression match:
  115. // attr:
  116. // a string representing the current full attribute match in a
  117. // bracket expression
  118. // type:
  119. // if there's an operator in a bracket expression, this is
  120. // used to keep track of it
  121. // value:
  122. // the internals of parenthetical expression for a pseudo. for
  123. // :nth-child(2n+1), value might be "2n+1"
  124. var endTag = function(){
  125. // called when the tokenizer hits the end of a particular tag name.
  126. // Re-sets state variables for tag matching and sets up the matcher
  127. // to handle the next type of token (tag or operator).
  128. if(inTag >= 0){
  129. var tv = (inTag == x) ? null : ts(inTag, x); // .toLowerCase();
  130. currentPart[ (specials.indexOf(tv) < 0) ? "tag" : "oper" ] = tv;
  131. inTag = -1;
  132. }
  133. };
  134. var endId = function(){
  135. // called when the tokenizer might be at the end of an ID portion of a match
  136. if(inId >= 0){
  137. currentPart.id = ts(inId, x).replace(/\\/g, "");
  138. inId = -1;
  139. }
  140. };
  141. var endClass = function(){
  142. // called when the tokenizer might be at the end of a class name
  143. // match. CSS allows for multiple classes, so we augment the
  144. // current item with another class in its list
  145. if(inClass >= 0){
  146. currentPart.classes.push(ts(inClass + 1, x).replace(/\\/g, ""));
  147. inClass = -1;
  148. }
  149. };
  150. var endAll = function(){
  151. // at the end of a simple fragment, so wall off the matches
  152. endId();
  153. endTag();
  154. endClass();
  155. };
  156. var endPart = function(){
  157. endAll();
  158. if(inPseudo >= 0){
  159. currentPart.pseudos.push({ name: ts(inPseudo + 1, x) });
  160. }
  161. // hint to the selector engine to tell it whether or not it
  162. // needs to do any iteration. Many simple selectors don't, and
  163. // we can avoid significant construction-time work by advising
  164. // the system to skip them
  165. currentPart.loops = (
  166. currentPart.pseudos.length ||
  167. currentPart.attrs.length ||
  168. currentPart.classes.length );
  169. currentPart.oquery = currentPart.query = ts(pStart, x); // save the full expression as a string
  170. // otag/tag are hints to suggest to the system whether or not
  171. // it's an operator or a tag. We save a copy of otag since the
  172. // tag name is cast to upper-case in regular HTML matches. The
  173. // system has a global switch to figure out if the current
  174. // expression needs to be case sensitive or not and it will use
  175. // otag or tag accordingly
  176. currentPart.otag = currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*");
  177. if(currentPart.tag){
  178. // if we're in a case-insensitive HTML doc, we likely want
  179. // the toUpperCase when matching on element.tagName. If we
  180. // do it here, we can skip the string op per node
  181. // comparison
  182. currentPart.tag = currentPart.tag.toUpperCase();
  183. }
  184. // add the part to the list
  185. if(queryParts.length && (queryParts[queryParts.length-1].oper)){
  186. // operators are always infix, so we remove them from the
  187. // list and attach them to the next match. The evaluator is
  188. // responsible for sorting out how to handle them.
  189. currentPart.infixOper = queryParts.pop();
  190. currentPart.query = currentPart.infixOper.query + " " + currentPart.query;
  191. /*
  192. console.debug( "swapping out the infix",
  193. currentPart.infixOper,
  194. "and attaching it to",
  195. currentPart);
  196. */
  197. }
  198. queryParts.push(currentPart);
  199. currentPart = null;
  200. };
  201. // iterate over the query, character by character, building up a
  202. // list of query part objects
  203. for(; lc=cc, cc=query.charAt(x), x < ql; x++){
  204. // cc: the current character in the match
  205. // lc: the last character (if any)
  206. // someone is trying to escape something, so don't try to match any
  207. // fragments. We assume we're inside a literal.
  208. if(lc == "\\"){ continue; }
  209. if(!currentPart){ // a part was just ended or none has yet been created
  210. // NOTE: I hate all this alloc, but it's shorter than writing tons of if's
  211. pStart = x;
  212. // rules describe full CSS sub-expressions, like:
  213. // #someId
  214. // .className:first-child
  215. // but not:
  216. // thinger > div.howdy[type=thinger]
  217. // the indidual components of the previous query would be
  218. // split into 3 parts that would be represented a structure
  219. // like:
  220. // [
  221. // {
  222. // query: "thinger",
  223. // tag: "thinger",
  224. // },
  225. // {
  226. // query: "div.howdy[type=thinger]",
  227. // classes: ["howdy"],
  228. // infixOper: {
  229. // query: ">",
  230. // oper: ">",
  231. // }
  232. // },
  233. // ]
  234. currentPart = {
  235. query: null, // the full text of the part's rule
  236. pseudos: [], // CSS supports multiple pseud-class matches in a single rule
  237. attrs: [], // CSS supports multi-attribute match, so we need an array
  238. classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy
  239. tag: null, // only one tag...
  240. oper: null, // ...or operator per component. Note that these wind up being exclusive.
  241. id: null, // the id component of a rule
  242. getTag: function(){
  243. return caseSensitive ? this.otag : this.tag;
  244. }
  245. };
  246. // if we don't have a part, we assume we're going to start at
  247. // the beginning of a match, which should be a tag name. This
  248. // might fault a little later on, but we detect that and this
  249. // iteration will still be fine.
  250. inTag = x;
  251. }
  252. // Skip processing all quoted characters.
  253. // If we are inside quoted text then currentQuoteChar stores the character that began the quote,
  254. // thus that character that will end it.
  255. if(currentQuoteChar){
  256. if(cc == currentQuoteChar){
  257. currentQuoteChar = null;
  258. }
  259. continue;
  260. }else if (cc == "'" || cc == '"'){
  261. currentQuoteChar = cc;
  262. continue;
  263. }
  264. if(inBrackets >= 0){
  265. // look for a the close first
  266. if(cc == "]"){ // if we're in a [...] clause and we end, do assignment
  267. if(!_cp.attr){
  268. // no attribute match was previously begun, so we
  269. // assume this is an attribute existence match in the
  270. // form of [someAttributeName]
  271. _cp.attr = ts(inBrackets+1, x);
  272. }else{
  273. // we had an attribute already, so we know that we're
  274. // matching some sort of value, as in [attrName=howdy]
  275. _cp.matchFor = ts((inMatchFor||inBrackets+1), x);
  276. }
  277. var cmf = _cp.matchFor;
  278. if(cmf){
  279. // try to strip quotes from the matchFor value. We want
  280. // [attrName=howdy] to match the same
  281. // as [attrName = 'howdy' ]
  282. if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){
  283. _cp.matchFor = cmf.slice(1, -1);
  284. }
  285. }
  286. // remove backslash escapes from an attribute match, since DOM
  287. // querying will get attribute values without backslashes
  288. if(_cp.matchFor){
  289. _cp.matchFor = _cp.matchFor.replace(/\\/g, "");
  290. }
  291. // end the attribute by adding it to the list of attributes.
  292. currentPart.attrs.push(_cp);
  293. _cp = null; // necessary?
  294. inBrackets = inMatchFor = -1;
  295. }else if(cc == "="){
  296. // if the last char was an operator prefix, make sure we
  297. // record it along with the "=" operator.
  298. var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : "";
  299. _cp.type = addToCc+cc;
  300. _cp.attr = ts(inBrackets+1, x-addToCc.length);
  301. inMatchFor = x+1;
  302. }
  303. // now look for other clause parts
  304. }else if(inParens >= 0){
  305. // if we're in a parenthetical expression, we need to figure
  306. // out if it's attached to a pseudo-selector rule like
  307. // :nth-child(1)
  308. if(cc == ")"){
  309. if(inPseudo >= 0){
  310. _cp.value = ts(inParens+1, x);
  311. }
  312. inPseudo = inParens = -1;
  313. }
  314. }else if(cc == "#"){
  315. // start of an ID match
  316. endAll();
  317. inId = x+1;
  318. }else if(cc == "."){
  319. // start of a class match
  320. endAll();
  321. inClass = x;
  322. }else if(cc == ":"){
  323. // start of a pseudo-selector match
  324. endAll();
  325. inPseudo = x;
  326. }else if(cc == "["){
  327. // start of an attribute match.
  328. endAll();
  329. inBrackets = x;
  330. // provide a new structure for the attribute match to fill-in
  331. _cp = {
  332. /*=====
  333. attr: null, type: null, matchFor: null
  334. =====*/
  335. };
  336. }else if(cc == "("){
  337. // we really only care if we've entered a parenthetical
  338. // expression if we're already inside a pseudo-selector match
  339. if(inPseudo >= 0){
  340. // provide a new structure for the pseudo match to fill-in
  341. _cp = {
  342. name: ts(inPseudo+1, x),
  343. value: null
  344. };
  345. currentPart.pseudos.push(_cp);
  346. }
  347. inParens = x;
  348. }else if(
  349. (cc == " ") &&
  350. // if it's a space char and the last char is too, consume the
  351. // current one without doing more work
  352. (lc != cc)
  353. ){
  354. endPart();
  355. }
  356. }
  357. return queryParts;
  358. };
  359. ////////////////////////////////////////////////////////////////////////
  360. // DOM query infrastructure
  361. ////////////////////////////////////////////////////////////////////////
  362. var agree = function(first, second){
  363. // the basic building block of the yes/no chaining system. agree(f1,
  364. // f2) generates a new function which returns the boolean results of
  365. // both of the passed functions to a single logical-anded result. If
  366. // either are not passed, the other is used exclusively.
  367. if(!first){ return second; }
  368. if(!second){ return first; }
  369. return function(){
  370. return first.apply(window, arguments) && second.apply(window, arguments);
  371. }
  372. };
  373. var getArr = function(i, arr){
  374. // helps us avoid array alloc when we don't need it
  375. var r = arr||[]; // FIXME: should this be 'new d._NodeListCtor()' ?
  376. if(i){ r.push(i); }
  377. return r;
  378. };
  379. var _isElement = function(n){ return (1 == n.nodeType); };
  380. // FIXME: need to coalesce _getAttr with defaultGetter
  381. var blank = "";
  382. var _getAttr = function(elem, attr){
  383. if(!elem){ return blank; }
  384. if(attr == "class"){
  385. return elem.className || blank;
  386. }
  387. if(attr == "for"){
  388. return elem.htmlFor || blank;
  389. }
  390. if(attr == "style"){
  391. return elem.style.cssText || blank;
  392. }
  393. return (caseSensitive ? elem.getAttribute(attr) : elem.getAttribute(attr, 2)) || blank;
  394. };
  395. var attrs = {
  396. "*=": function(attr, value){
  397. return function(elem){
  398. // E[foo*="bar"]
  399. // an E element whose "foo" attribute value contains
  400. // the substring "bar"
  401. return (_getAttr(elem, attr).indexOf(value)>=0);
  402. }
  403. },
  404. "^=": function(attr, value){
  405. // E[foo^="bar"]
  406. // an E element whose "foo" attribute value begins exactly
  407. // with the string "bar"
  408. return function(elem){
  409. return (_getAttr(elem, attr).indexOf(value)==0);
  410. }
  411. },
  412. "$=": function(attr, value){
  413. // E[foo$="bar"]
  414. // an E element whose "foo" attribute value ends exactly
  415. // with the string "bar"
  416. return function(elem){
  417. var ea = " "+_getAttr(elem, attr);
  418. var lastIndex = ea.lastIndexOf(value);
  419. return lastIndex > -1 && (lastIndex==(ea.length-value.length));
  420. }
  421. },
  422. "~=": function(attr, value){
  423. // E[foo~="bar"]
  424. // an E element whose "foo" attribute value is a list of
  425. // space-separated values, one of which is exactly equal
  426. // to "bar"
  427. // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]";
  428. var tval = " "+value+" ";
  429. return function(elem){
  430. var ea = " "+_getAttr(elem, attr)+" ";
  431. return (ea.indexOf(tval)>=0);
  432. }
  433. },
  434. "|=": function(attr, value){
  435. // E[hreflang|="en"]
  436. // an E element whose "hreflang" attribute has a
  437. // hyphen-separated list of values beginning (from the
  438. // left) with "en"
  439. var valueDash = value+"-";
  440. return function(elem){
  441. var ea = _getAttr(elem, attr);
  442. return (
  443. (ea == value) ||
  444. (ea.indexOf(valueDash)==0)
  445. );
  446. }
  447. },
  448. "=": function(attr, value){
  449. return function(elem){
  450. return (_getAttr(elem, attr) == value);
  451. }
  452. }
  453. };
  454. // avoid testing for node type if we can. Defining this in the negative
  455. // here to avoid negation in the fast path.
  456. var _noNES = (typeof getDoc().firstChild.nextElementSibling == "undefined");
  457. var _ns = !_noNES ? "nextElementSibling" : "nextSibling";
  458. var _ps = !_noNES ? "previousElementSibling" : "previousSibling";
  459. var _simpleNodeTest = (_noNES ? _isElement : yesman);
  460. var _lookLeft = function(node){
  461. // look left
  462. while(node = node[_ps]){
  463. if(_simpleNodeTest(node)){ return false; }
  464. }
  465. return true;
  466. };
  467. var _lookRight = function(node){
  468. // look right
  469. while(node = node[_ns]){
  470. if(_simpleNodeTest(node)){ return false; }
  471. }
  472. return true;
  473. };
  474. var getNodeIndex = function(node){
  475. var root = node.parentNode;
  476. var i = 0,
  477. tret = root.children || root.childNodes,
  478. ci = (node["_i"]||-1),
  479. cl = (root["_l"]||-1);
  480. if(!tret){ return -1; }
  481. var l = tret.length;
  482. // we calculate the parent length as a cheap way to invalidate the
  483. // cache. It's not 100% accurate, but it's much more honest than what
  484. // other libraries do
  485. if( cl == l && ci >= 0 && cl >= 0 ){
  486. // if it's legit, tag and release
  487. return ci;
  488. }
  489. // else re-key things
  490. root["_l"] = l;
  491. ci = -1;
  492. for(var te = root["firstElementChild"]||root["firstChild"]; te; te = te[_ns]){
  493. if(_simpleNodeTest(te)){
  494. te["_i"] = ++i;
  495. if(node === te){
  496. // NOTE:
  497. // shortcutting the return at this step in indexing works
  498. // very well for benchmarking but we avoid it here since
  499. // it leads to potential O(n^2) behavior in sequential
  500. // getNodexIndex operations on a previously un-indexed
  501. // parent. We may revisit this at a later time, but for
  502. // now we just want to get the right answer more often
  503. // than not.
  504. ci = i;
  505. }
  506. }
  507. }
  508. return ci;
  509. };
  510. var isEven = function(elem){
  511. return !((getNodeIndex(elem)) % 2);
  512. };
  513. var isOdd = function(elem){
  514. return ((getNodeIndex(elem)) % 2);
  515. };
  516. var pseudos = {
  517. "checked": function(name, condition){
  518. return function(elem){
  519. return !!("checked" in elem ? elem.checked : elem.selected);
  520. }
  521. },
  522. "first-child": function(){ return _lookLeft; },
  523. "last-child": function(){ return _lookRight; },
  524. "only-child": function(name, condition){
  525. return function(node){
  526. return _lookLeft(node) && _lookRight(node);
  527. };
  528. },
  529. "empty": function(name, condition){
  530. return function(elem){
  531. // DomQuery and jQuery get this wrong, oddly enough.
  532. // The CSS 3 selectors spec is pretty explicit about it, too.
  533. var cn = elem.childNodes;
  534. var cnl = elem.childNodes.length;
  535. // if(!cnl){ return true; }
  536. for(var x=cnl-1; x >= 0; x--){
  537. var nt = cn[x].nodeType;
  538. if((nt === 1)||(nt == 3)){ return false; }
  539. }
  540. return true;
  541. };
  542. },
  543. "disabled": function(name, condition){
  544. return function(elem){
  545. return elem.disabled;
  546. };
  547. },
  548. "enabled": function(name, condition){
  549. return function(elem){
  550. return !elem.disabled;
  551. };
  552. },
  553. "contains": function(name, condition){
  554. var cz = condition.charAt(0);
  555. if( cz == '"' || cz == "'" ){ //remove quote
  556. condition = condition.slice(1, -1);
  557. }
  558. return function(elem){
  559. return (elem.innerHTML.indexOf(condition) >= 0);
  560. }
  561. },
  562. "not": function(name, condition){
  563. var p = getQueryParts(condition)[0];
  564. var ignores = { el: 1 };
  565. if(p.tag != "*"){
  566. ignores.tag = 1;
  567. }
  568. if(!p.classes.length){
  569. ignores.classes = 1;
  570. }
  571. var ntf = getSimpleFilterFunc(p, ignores);
  572. return function(elem){
  573. return (!ntf(elem));
  574. }
  575. },
  576. "nth-child": function(name, condition){
  577. var pi = parseInt;
  578. // avoid re-defining function objects if we can
  579. if(condition == "odd"){
  580. return isOdd;
  581. }else if(condition == "even"){
  582. return isEven;
  583. }
  584. // FIXME: can we shorten this?
  585. if(condition.indexOf("n") != -1){
  586. var tparts = condition.split("n", 2);
  587. var pred = tparts[0] ? ((tparts[0] == '-') ? -1 : pi(tparts[0])) : 1;
  588. var idx = tparts[1] ? pi(tparts[1]) : 0;
  589. var lb = 0, ub = -1;
  590. if(pred > 0){
  591. if(idx < 0){
  592. idx = (idx % pred) && (pred + (idx % pred));
  593. }else if(idx>0){
  594. if(idx >= pred){
  595. lb = idx - idx % pred;
  596. }
  597. idx = idx % pred;
  598. }
  599. }else if(pred<0){
  600. pred *= -1;
  601. // idx has to be greater than 0 when pred is negative;
  602. // shall we throw an error here?
  603. if(idx > 0){
  604. ub = idx;
  605. idx = idx % pred;
  606. }
  607. }
  608. if(pred > 0){
  609. return function(elem){
  610. var i = getNodeIndex(elem);
  611. return (i>=lb) && (ub<0 || i<=ub) && ((i % pred) == idx);
  612. }
  613. }else{
  614. condition = idx;
  615. }
  616. }
  617. var ncount = pi(condition);
  618. return function(elem){
  619. return (getNodeIndex(elem) == ncount);
  620. }
  621. }
  622. };
  623. var defaultGetter = (dojo.isIE < 9 || dojo.isIE == 9 && dojo.isQuirks) ? function(cond){
  624. var clc = cond.toLowerCase();
  625. if(clc == "class"){ cond = "className"; }
  626. return function(elem){
  627. return (caseSensitive ? elem.getAttribute(cond) : elem[cond]||elem[clc]);
  628. }
  629. } : function(cond){
  630. return function(elem){
  631. return (elem && elem.getAttribute && elem.hasAttribute(cond));
  632. }
  633. };
  634. var getSimpleFilterFunc = function(query, ignores){
  635. // generates a node tester function based on the passed query part. The
  636. // query part is one of the structures generated by the query parser
  637. // when it creates the query AST. The "ignores" object specifies which
  638. // (if any) tests to skip, allowing the system to avoid duplicating
  639. // work where it may have already been taken into account by other
  640. // factors such as how the nodes to test were fetched in the first
  641. // place
  642. if(!query){ return yesman; }
  643. ignores = ignores||{};
  644. var ff = null;
  645. if(!("el" in ignores)){
  646. ff = agree(ff, _isElement);
  647. }
  648. if(!("tag" in ignores)){
  649. if(query.tag != "*"){
  650. ff = agree(ff, function(elem){
  651. return (elem && (elem.tagName == query.getTag()));
  652. });
  653. }
  654. }
  655. if(!("classes" in ignores)){
  656. each(query.classes, function(cname, idx, arr){
  657. // get the class name
  658. /*
  659. var isWildcard = cname.charAt(cname.length-1) == "*";
  660. if(isWildcard){
  661. cname = cname.substr(0, cname.length-1);
  662. }
  663. // I dislike the regex thing, even if memoized in a cache, but it's VERY short
  664. var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)");
  665. */
  666. var re = new RegExp("(?:^|\\s)" + cname + "(?:\\s|$)");
  667. ff = agree(ff, function(elem){
  668. return re.test(elem.className);
  669. });
  670. ff.count = idx;
  671. });
  672. }
  673. if(!("pseudos" in ignores)){
  674. each(query.pseudos, function(pseudo){
  675. var pn = pseudo.name;
  676. if(pseudos[pn]){
  677. ff = agree(ff, pseudos[pn](pn, pseudo.value));
  678. }
  679. });
  680. }
  681. if(!("attrs" in ignores)){
  682. each(query.attrs, function(attr){
  683. var matcher;
  684. var a = attr.attr;
  685. // type, attr, matchFor
  686. if(attr.type && attrs[attr.type]){
  687. matcher = attrs[attr.type](a, attr.matchFor);
  688. }else if(a.length){
  689. matcher = defaultGetter(a);
  690. }
  691. if(matcher){
  692. ff = agree(ff, matcher);
  693. }
  694. });
  695. }
  696. if(!("id" in ignores)){
  697. if(query.id){
  698. ff = agree(ff, function(elem){
  699. return (!!elem && (elem.id == query.id));
  700. });
  701. }
  702. }
  703. if(!ff){
  704. if(!("default" in ignores)){
  705. ff = yesman;
  706. }
  707. }
  708. return ff;
  709. };
  710. var _nextSibling = function(filterFunc){
  711. return function(node, ret, bag){
  712. while(node = node[_ns]){
  713. if(_noNES && (!_isElement(node))){ continue; }
  714. if(
  715. (!bag || _isUnique(node, bag)) &&
  716. filterFunc(node)
  717. ){
  718. ret.push(node);
  719. }
  720. break;
  721. }
  722. return ret;
  723. }
  724. };
  725. var _nextSiblings = function(filterFunc){
  726. return function(root, ret, bag){
  727. var te = root[_ns];
  728. while(te){
  729. if(_simpleNodeTest(te)){
  730. if(bag && !_isUnique(te, bag)){
  731. break;
  732. }
  733. if(filterFunc(te)){
  734. ret.push(te);
  735. }
  736. }
  737. te = te[_ns];
  738. }
  739. return ret;
  740. }
  741. };
  742. // get an array of child *elements*, skipping text and comment nodes
  743. var _childElements = function(filterFunc){
  744. filterFunc = filterFunc||yesman;
  745. return function(root, ret, bag){
  746. // get an array of child elements, skipping text and comment nodes
  747. var te, x = 0, tret = root.children || root.childNodes;
  748. while(te = tret[x++]){
  749. if(
  750. _simpleNodeTest(te) &&
  751. (!bag || _isUnique(te, bag)) &&
  752. (filterFunc(te, x))
  753. ){
  754. ret.push(te);
  755. }
  756. }
  757. return ret;
  758. };
  759. };
  760. /*
  761. // thanks, Dean!
  762. var itemIsAfterRoot = d.isIE ? function(item, root){
  763. return (item.sourceIndex > root.sourceIndex);
  764. } : function(item, root){
  765. return (item.compareDocumentPosition(root) == 2);
  766. };
  767. */
  768. // test to see if node is below root
  769. var _isDescendant = function(node, root){
  770. var pn = node.parentNode;
  771. while(pn){
  772. if(pn == root){
  773. break;
  774. }
  775. pn = pn.parentNode;
  776. }
  777. return !!pn;
  778. };
  779. var _getElementsFuncCache = {};
  780. var getElementsFunc = function(query){
  781. var retFunc = _getElementsFuncCache[query.query];
  782. // if we've got a cached dispatcher, just use that
  783. if(retFunc){ return retFunc; }
  784. // else, generate a new on
  785. // NOTE:
  786. // this function returns a function that searches for nodes and
  787. // filters them. The search may be specialized by infix operators
  788. // (">", "~", or "+") else it will default to searching all
  789. // descendants (the " " selector). Once a group of children is
  790. // found, a test function is applied to weed out the ones we
  791. // don't want. Many common cases can be fast-pathed. We spend a
  792. // lot of cycles to create a dispatcher that doesn't do more work
  793. // than necessary at any point since, unlike this function, the
  794. // dispatchers will be called every time. The logic of generating
  795. // efficient dispatchers looks like this in pseudo code:
  796. //
  797. // # if it's a purely descendant query (no ">", "+", or "~" modifiers)
  798. // if infixOperator == " ":
  799. // if only(id):
  800. // return def(root):
  801. // return d.byId(id, root);
  802. //
  803. // elif id:
  804. // return def(root):
  805. // return filter(d.byId(id, root));
  806. //
  807. // elif cssClass && getElementsByClassName:
  808. // return def(root):
  809. // return filter(root.getElementsByClassName(cssClass));
  810. //
  811. // elif only(tag):
  812. // return def(root):
  813. // return root.getElementsByTagName(tagName);
  814. //
  815. // else:
  816. // # search by tag name, then filter
  817. // return def(root):
  818. // return filter(root.getElementsByTagName(tagName||"*"));
  819. //
  820. // elif infixOperator == ">":
  821. // # search direct children
  822. // return def(root):
  823. // return filter(root.children);
  824. //
  825. // elif infixOperator == "+":
  826. // # search next sibling
  827. // return def(root):
  828. // return filter(root.nextElementSibling);
  829. //
  830. // elif infixOperator == "~":
  831. // # search rightward siblings
  832. // return def(root):
  833. // return filter(nextSiblings(root));
  834. var io = query.infixOper;
  835. var oper = (io ? io.oper : "");
  836. // the default filter func which tests for all conditions in the query
  837. // part. This is potentially inefficient, so some optimized paths may
  838. // re-define it to test fewer things.
  839. var filterFunc = getSimpleFilterFunc(query, { el: 1 });
  840. var qt = query.tag;
  841. var wildcardTag = ("*" == qt);
  842. var ecs = getDoc()["getElementsByClassName"];
  843. if(!oper){
  844. // if there's no infix operator, then it's a descendant query. ID
  845. // and "elements by class name" variants can be accelerated so we
  846. // call them out explicitly:
  847. if(query.id){
  848. // testing shows that the overhead of yesman() is acceptable
  849. // and can save us some bytes vs. re-defining the function
  850. // everywhere.
  851. filterFunc = (!query.loops && wildcardTag) ?
  852. yesman :
  853. getSimpleFilterFunc(query, { el: 1, id: 1 });
  854. retFunc = function(root, arr){
  855. var te = dom.byId(query.id, (root.ownerDocument||root));
  856. if(!te || !filterFunc(te)){ return; }
  857. if(9 == root.nodeType){ // if root's a doc, we just return directly
  858. return getArr(te, arr);
  859. }else{ // otherwise check ancestry
  860. if(_isDescendant(te, root)){
  861. return getArr(te, arr);
  862. }
  863. }
  864. }
  865. }else if(
  866. ecs &&
  867. // isAlien check. Workaround for Prototype.js being totally evil/dumb.
  868. /\{\s*\[native code\]\s*\}/.test(String(ecs)) &&
  869. query.classes.length &&
  870. !cssCaseBug
  871. ){
  872. // it's a class-based query and we've got a fast way to run it.
  873. // ignore class and ID filters since we will have handled both
  874. filterFunc = getSimpleFilterFunc(query, { el: 1, classes: 1, id: 1 });
  875. var classesString = query.classes.join(" ");
  876. retFunc = function(root, arr, bag){
  877. var ret = getArr(0, arr), te, x=0;
  878. var tret = root.getElementsByClassName(classesString);
  879. while((te = tret[x++])){
  880. if(filterFunc(te, root) && _isUnique(te, bag)){
  881. ret.push(te);
  882. }
  883. }
  884. return ret;
  885. };
  886. }else if(!wildcardTag && !query.loops){
  887. // it's tag only. Fast-path it.
  888. retFunc = function(root, arr, bag){
  889. var ret = getArr(0, arr), te, x=0;
  890. var tret = root.getElementsByTagName(query.getTag());
  891. while((te = tret[x++])){
  892. if(_isUnique(te, bag)){
  893. ret.push(te);
  894. }
  895. }
  896. return ret;
  897. };
  898. }else{
  899. // the common case:
  900. // a descendant selector without a fast path. By now it's got
  901. // to have a tag selector, even if it's just "*" so we query
  902. // by that and filter
  903. filterFunc = getSimpleFilterFunc(query, { el: 1, tag: 1, id: 1 });
  904. retFunc = function(root, arr, bag){
  905. var ret = getArr(0, arr), te, x=0;
  906. // we use getTag() to avoid case sensitivity issues
  907. var tret = root.getElementsByTagName(query.getTag());
  908. while((te = tret[x++])){
  909. if(filterFunc(te, root) && _isUnique(te, bag)){
  910. ret.push(te);
  911. }
  912. }
  913. return ret;
  914. };
  915. }
  916. }else{
  917. // the query is scoped in some way. Instead of querying by tag we
  918. // use some other collection to find candidate nodes
  919. var skipFilters = { el: 1 };
  920. if(wildcardTag){
  921. skipFilters.tag = 1;
  922. }
  923. filterFunc = getSimpleFilterFunc(query, skipFilters);
  924. if("+" == oper){
  925. retFunc = _nextSibling(filterFunc);
  926. }else if("~" == oper){
  927. retFunc = _nextSiblings(filterFunc);
  928. }else if(">" == oper){
  929. retFunc = _childElements(filterFunc);
  930. }
  931. }
  932. // cache it and return
  933. return _getElementsFuncCache[query.query] = retFunc;
  934. };
  935. var filterDown = function(root, queryParts){
  936. // NOTE:
  937. // this is the guts of the DOM query system. It takes a list of
  938. // parsed query parts and a root and finds children which match
  939. // the selector represented by the parts
  940. var candidates = getArr(root), qp, x, te, qpl = queryParts.length, bag, ret;
  941. for(var i = 0; i < qpl; i++){
  942. ret = [];
  943. qp = queryParts[i];
  944. x = candidates.length - 1;
  945. if(x > 0){
  946. // if we have more than one root at this level, provide a new
  947. // hash to use for checking group membership but tell the
  948. // system not to post-filter us since we will already have been
  949. // gauranteed to be unique
  950. bag = {};
  951. ret.nozip = true;
  952. }
  953. var gef = getElementsFunc(qp);
  954. for(var j = 0; (te = candidates[j]); j++){
  955. // for every root, get the elements that match the descendant
  956. // selector, adding them to the "ret" array and filtering them
  957. // via membership in this level's bag. If there are more query
  958. // parts, then this level's return will be used as the next
  959. // level's candidates
  960. gef(te, ret, bag);
  961. }
  962. if(!ret.length){ break; }
  963. candidates = ret;
  964. }
  965. return ret;
  966. };
  967. ////////////////////////////////////////////////////////////////////////
  968. // the query runner
  969. ////////////////////////////////////////////////////////////////////////
  970. // these are the primary caches for full-query results. The query
  971. // dispatcher functions are generated then stored here for hash lookup in
  972. // the future
  973. var _queryFuncCacheDOM = {},
  974. _queryFuncCacheQSA = {};
  975. // this is the second level of spliting, from full-length queries (e.g.,
  976. // "div.foo .bar") into simple query expressions (e.g., ["div.foo",
  977. // ".bar"])
  978. var getStepQueryFunc = function(query){
  979. var qparts = getQueryParts(trim(query));
  980. // if it's trivial, avoid iteration and zipping costs
  981. if(qparts.length == 1){
  982. // we optimize this case here to prevent dispatch further down the
  983. // chain, potentially slowing things down. We could more elegantly
  984. // handle this in filterDown(), but it's slower for simple things
  985. // that need to be fast (e.g., "#someId").
  986. var tef = getElementsFunc(qparts[0]);
  987. return function(root){
  988. var r = tef(root, []);
  989. if(r){ r.nozip = true; }
  990. return r;
  991. }
  992. }
  993. // otherwise, break it up and return a runner that iterates over the parts recursively
  994. return function(root){
  995. return filterDown(root, qparts);
  996. }
  997. };
  998. // NOTES:
  999. // * we can't trust QSA for anything but document-rooted queries, so
  1000. // caching is split into DOM query evaluators and QSA query evaluators
  1001. // * caching query results is dirty and leak-prone (or, at a minimum,
  1002. // prone to unbounded growth). Other toolkits may go this route, but
  1003. // they totally destroy their own ability to manage their memory
  1004. // footprint. If we implement it, it should only ever be with a fixed
  1005. // total element reference # limit and an LRU-style algorithm since JS
  1006. // has no weakref support. Caching compiled query evaluators is also
  1007. // potentially problematic, but even on large documents the size of the
  1008. // query evaluators is often < 100 function objects per evaluator (and
  1009. // LRU can be applied if it's ever shown to be an issue).
  1010. // * since IE's QSA support is currently only for HTML documents and even
  1011. // then only in IE 8's "standards mode", we have to detect our dispatch
  1012. // route at query time and keep 2 separate caches. Ugg.
  1013. // we need to determine if we think we can run a given query via
  1014. // querySelectorAll or if we'll need to fall back on DOM queries to get
  1015. // there. We need a lot of information about the environment and the query
  1016. // to make the determiniation (e.g. does it support QSA, does the query in
  1017. // question work in the native QSA impl, etc.).
  1018. var nua = navigator.userAgent;
  1019. // some versions of Safari provided QSA, but it was buggy and crash-prone.
  1020. // We need te detect the right "internal" webkit version to make this work.
  1021. var wk = "WebKit/";
  1022. var is525 = (
  1023. dojo.isWebKit &&
  1024. (nua.indexOf(wk) > 0) &&
  1025. (parseFloat(nua.split(wk)[1]) > 528)
  1026. );
  1027. // IE QSA queries may incorrectly include comment nodes, so we throw the
  1028. // zipping function into "remove" comments mode instead of the normal "skip
  1029. // it" which every other QSA-clued browser enjoys
  1030. var noZip = dojo.isIE ? "commentStrip" : "nozip";
  1031. var qsa = "querySelectorAll";
  1032. var qsaAvail = (
  1033. !!getDoc()[qsa] &&
  1034. // see #5832
  1035. (!dojo.isSafari || (dojo.isSafari > 3.1) || is525 )
  1036. );
  1037. //Don't bother with n+3 type of matches, IE complains if we modify those.
  1038. var infixSpaceRe = /\\[>~+]|n\+\d|([^ \\])?([>~+])([^ =])?/g;
  1039. var infixSpaceFunc = function(match, pre, ch, post){
  1040. return ch ? (pre ? pre + " " : "") + ch + (post ? " " + post : "") : /*n+3*/ match;
  1041. };
  1042. //Don't apply the infixSpaceRe to attribute value selectors
  1043. var attRe = /([^[]*)([^\]]*])?/g;
  1044. var attFunc = function(match, nonAtt, att) {
  1045. return nonAtt.replace(infixSpaceRe, infixSpaceFunc) + (att||"");
  1046. };
  1047. var getQueryFunc = function(query, forceDOM){
  1048. //Normalize query. The CSS3 selectors spec allows for omitting spaces around
  1049. //infix operators, >, ~ and +
  1050. //Do the work here since detection for spaces is used as a simple "not use QSA"
  1051. //test below.
  1052. query = query.replace(attRe, attFunc);
  1053. if(qsaAvail){
  1054. // if we've got a cached variant and we think we can do it, run it!
  1055. var qsaCached = _queryFuncCacheQSA[query];
  1056. if(qsaCached && !forceDOM){ return qsaCached; }
  1057. }
  1058. // else if we've got a DOM cached variant, assume that we already know
  1059. // all we need to and use it
  1060. var domCached = _queryFuncCacheDOM[query];
  1061. if(domCached){ return domCached; }
  1062. // TODO:
  1063. // today we're caching DOM and QSA branches separately so we
  1064. // recalc useQSA every time. If we had a way to tag root+query
  1065. // efficiently, we'd be in good shape to do a global cache.
  1066. var qcz = query.charAt(0);
  1067. var nospace = (-1 == query.indexOf(" "));
  1068. // byId searches are wicked fast compared to QSA, even when filtering
  1069. // is required
  1070. if( (query.indexOf("#") >= 0) && (nospace) ){
  1071. forceDOM = true;
  1072. }
  1073. var useQSA = (
  1074. qsaAvail && (!forceDOM) &&
  1075. // as per CSS 3, we can't currently start w/ combinator:
  1076. // http://www.w3.org/TR/css3-selectors/#w3cselgrammar
  1077. (specials.indexOf(qcz) == -1) &&
  1078. // IE's QSA impl sucks on pseudos
  1079. (!dojo.isIE || (query.indexOf(":") == -1)) &&
  1080. (!(cssCaseBug && (query.indexOf(".") >= 0))) &&
  1081. // FIXME:
  1082. // need to tighten up browser rules on ":contains" and "|=" to
  1083. // figure out which aren't good
  1084. // Latest webkit (around 531.21.8) does not seem to do well with :checked on option
  1085. // elements, even though according to spec, selected options should
  1086. // match :checked. So go nonQSA for it:
  1087. // http://bugs.dojotoolkit.org/ticket/5179
  1088. (query.indexOf(":contains") == -1) && (query.indexOf(":checked") == -1) &&
  1089. (query.indexOf("|=") == -1) // some browsers don't grok it
  1090. );
  1091. // TODO:
  1092. // if we've got a descendant query (e.g., "> .thinger" instead of
  1093. // just ".thinger") in a QSA-able doc, but are passed a child as a
  1094. // root, it should be possible to give the item a synthetic ID and
  1095. // trivially rewrite the query to the form "#synid > .thinger" to
  1096. // use the QSA branch
  1097. if(useQSA){
  1098. var tq = (specials.indexOf(query.charAt(query.length-1)) >= 0) ?
  1099. (query + " *") : query;
  1100. return _queryFuncCacheQSA[query] = function(root){
  1101. try{
  1102. // the QSA system contains an egregious spec bug which
  1103. // limits us, effectively, to only running QSA queries over
  1104. // entire documents. See:
  1105. // http://ejohn.org/blog/thoughts-on-queryselectorall/
  1106. // despite this, we can also handle QSA runs on simple
  1107. // selectors, but we don't want detection to be expensive
  1108. // so we're just checking for the presence of a space char
  1109. // right now. Not elegant, but it's cheaper than running
  1110. // the query parser when we might not need to
  1111. if(!((9 == root.nodeType) || nospace)){ throw ""; }
  1112. var r = root[qsa](tq);
  1113. // skip expensive duplication checks and just wrap in a NodeList
  1114. r[noZip] = true;
  1115. return r;
  1116. }catch(e){
  1117. // else run the DOM branch on this query, ensuring that we
  1118. // default that way in the future
  1119. return getQueryFunc(query, true)(root);
  1120. }
  1121. }
  1122. }else{
  1123. // DOM branch
  1124. var parts = query.match(/([^\s,](?:"(?:\\.|[^"])+"|'(?:\\.|[^'])+'|[^,])*)/g);
  1125. return _queryFuncCacheDOM[query] = ((parts.length < 2) ?
  1126. // if not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher
  1127. getStepQueryFunc(query) :
  1128. // if it *is* a complex query, break it up into its
  1129. // constituent parts and return a dispatcher that will
  1130. // merge the parts when run
  1131. function(root){
  1132. var pindex = 0, // avoid array alloc for every invocation
  1133. ret = [],
  1134. tp;
  1135. while((tp = parts[pindex++])){
  1136. ret = ret.concat(getStepQueryFunc(tp)(root));
  1137. }
  1138. return ret;
  1139. }
  1140. );
  1141. }
  1142. };
  1143. var _zipIdx = 0;
  1144. // NOTE:
  1145. // this function is Moo inspired, but our own impl to deal correctly
  1146. // with XML in IE
  1147. var _nodeUID = dojo.isIE ? function(node){
  1148. if(caseSensitive){
  1149. // XML docs don't have uniqueID on their nodes
  1150. return (node.getAttribute("_uid") || node.setAttribute("_uid", ++_zipIdx) || _zipIdx);
  1151. }else{
  1152. return node.uniqueID;
  1153. }
  1154. } :
  1155. function(node){
  1156. return (node._uid || (node._uid = ++_zipIdx));
  1157. };
  1158. // determine if a node in is unique in a "bag". In this case we don't want
  1159. // to flatten a list of unique items, but rather just tell if the item in
  1160. // question is already in the bag. Normally we'd just use hash lookup to do
  1161. // this for us but IE's DOM is busted so we can't really count on that. On
  1162. // the upside, it gives us a built in unique ID function.
  1163. var _isUnique = function(node, bag){
  1164. if(!bag){ return 1; }
  1165. var id = _nodeUID(node);
  1166. if(!bag[id]){ return bag[id] = 1; }
  1167. return 0;
  1168. };
  1169. // attempt to efficiently determine if an item in a list is a dupe,
  1170. // returning a list of "uniques", hopefully in doucment order
  1171. var _zipIdxName = "_zipIdx";
  1172. var _zip = function(arr){
  1173. if(arr && arr.nozip){
  1174. return arr;
  1175. }
  1176. var ret = [];
  1177. if(!arr || !arr.length){ return ret; }
  1178. if(arr[0]){
  1179. ret.push(arr[0]);
  1180. }
  1181. if(arr.length < 2){ return ret; }
  1182. _zipIdx++;
  1183. // we have to fork here for IE and XML docs because we can't set
  1184. // expandos on their nodes (apparently). *sigh*
  1185. if(dojo.isIE && caseSensitive){
  1186. var szidx = _zipIdx+"";
  1187. arr[0].setAttribute(_zipIdxName, szidx);
  1188. for(var x = 1, te; te = arr[x]; x++){
  1189. if(arr[x].getAttribute(_zipIdxName) != szidx){
  1190. ret.push(te);
  1191. }
  1192. te.setAttribute(_zipIdxName, szidx);
  1193. }
  1194. }else if(dojo.isIE && arr.commentStrip){
  1195. try{
  1196. for(var x = 1, te; te = arr[x]; x++){
  1197. if(_isElement(te)){
  1198. ret.push(te);
  1199. }
  1200. }
  1201. }catch(e){ /* squelch */ }
  1202. }else{
  1203. if(arr[0]){ arr[0][_zipIdxName] = _zipIdx; }
  1204. for(var x = 1, te; te = arr[x]; x++){
  1205. if(arr[x][_zipIdxName] != _zipIdx){
  1206. ret.push(te);
  1207. }
  1208. te[_zipIdxName] = _zipIdx;
  1209. }
  1210. }
  1211. return ret;
  1212. };
  1213. // the main executor
  1214. var query = function(/*String*/ query, /*String|DOMNode?*/ root){
  1215. // summary:
  1216. // Returns nodes which match the given CSS3 selector, searching the
  1217. // entire document by default but optionally taking a node to scope
  1218. // the search by. Returns an array.
  1219. // description:
  1220. // dojo.query() is the swiss army knife of DOM node manipulation in
  1221. // Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
  1222. // "$" function, dojo.query provides robust, high-performance
  1223. // CSS-based node selector support with the option of scoping searches
  1224. // to a particular sub-tree of a document.
  1225. //
  1226. // Supported Selectors:
  1227. // --------------------
  1228. //
  1229. // acme supports a rich set of CSS3 selectors, including:
  1230. //
  1231. // * class selectors (e.g., `.foo`)
  1232. // * node type selectors like `span`
  1233. // * ` ` descendant selectors
  1234. // * `>` child element selectors
  1235. // * `#foo` style ID selectors
  1236. // * `*` universal selector
  1237. // * `~`, the preceded-by sibling selector
  1238. // * `+`, the immediately preceded-by sibling selector
  1239. // * attribute queries:
  1240. // | * `[foo]` attribute presence selector
  1241. // | * `[foo='bar']` attribute value exact match
  1242. // | * `[foo~='bar']` attribute value list item match
  1243. // | * `[foo^='bar']` attribute start match
  1244. // | * `[foo$='bar']` attribute end match
  1245. // | * `[foo*='bar']` attribute substring match
  1246. // * `:first-child`, `:last-child`, and `:only-child` positional selectors
  1247. // * `:empty` content emtpy selector
  1248. // * `:checked` pseudo selector
  1249. // * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
  1250. // * `:nth-child(even)`, `:nth-child(odd)` positional selectors
  1251. // * `:not(...)` negation pseudo selectors
  1252. //
  1253. // Any legal combination of these selectors will work with
  1254. // `dojo.query()`, including compound selectors ("," delimited).
  1255. // Very complex and useful searches can be constructed with this
  1256. // palette of selectors and when combined with functions for
  1257. // manipulation presented by dojo.NodeList, many types of DOM
  1258. // manipulation operations become very straightforward.
  1259. //
  1260. // Unsupported Selectors:
  1261. // ----------------------
  1262. //
  1263. // While dojo.query handles many CSS3 selectors, some fall outside of
  1264. // what's reasonable for a programmatic node querying engine to
  1265. // handle. Currently unsupported selectors include:
  1266. //
  1267. // * namespace-differentiated selectors of any form
  1268. // * all `::` pseduo-element selectors
  1269. // * certain pseduo-selectors which don't get a lot of day-to-day use:
  1270. // | * `:root`, `:lang()`, `:target`, `:focus`
  1271. // * all visual and state selectors:
  1272. // | * `:root`, `:active`, `:hover`, `:visisted`, `:link`
  1273. // * `:*-of-type` pseudo selectors
  1274. //
  1275. // dojo.query and XML Documents:
  1276. // -----------------------------
  1277. //
  1278. // `dojo.query` (as of dojo 1.2) supports searching XML documents
  1279. // in a case-sensitive manner. If an HTML document is served with
  1280. // a doctype that forces case-sensitivity (e.g., XHTML 1.1
  1281. // Strict), dojo.query() will detect this and "do the right
  1282. // thing". Case sensitivity is dependent upon the document being
  1283. // searched and not the query used. It is therefore possible to
  1284. // use case-sensitive queries on strict sub-documents (iframes,
  1285. // etc.) or XML documents while still assuming case-insensitivity
  1286. // for a host/root document.
  1287. //
  1288. // Non-selector Queries:
  1289. // ---------------------
  1290. //
  1291. // If something other than a String is passed for the query,
  1292. // `dojo.query` will return a new `dojo.NodeList` instance
  1293. // constructed from that parameter alone and all further
  1294. // processing will stop. This means that if you have a reference
  1295. // to a node or NodeList, you can quickly construct a new NodeList
  1296. // from the original by calling `dojo.query(node)` or
  1297. // `dojo.query(list)`.
  1298. //
  1299. // query:
  1300. // The CSS3 expression to match against. For details on the syntax of
  1301. // CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors>
  1302. // root:
  1303. // A DOMNode (or node id) to scope the search from. Optional.
  1304. // returns: Array
  1305. // example:
  1306. // search the entire document for elements with the class "foo":
  1307. // | dojo.query(".foo");
  1308. // these elements will match:
  1309. // | <span class="foo"></span>
  1310. // | <span class="foo bar"></span>
  1311. // | <p class="thud foo"></p>
  1312. // example:
  1313. // search the entire document for elements with the classes "foo" *and* "bar":
  1314. // | dojo.query(".foo.bar");
  1315. // these elements will match:
  1316. // | <span class="foo bar"></span>
  1317. // while these will not:
  1318. // | <span class="foo"></span>
  1319. // | <p class="thud foo"></p>
  1320. // example:
  1321. // find `<span>` elements which are descendants of paragraphs and
  1322. // which have a "highlighted" class:
  1323. // | dojo.query("p span.highlighted");
  1324. // the innermost span in this fragment matches:
  1325. // | <p class="foo">
  1326. // | <span>...
  1327. // | <span class="highlighted foo bar">...</span>
  1328. // | </span>
  1329. // | </p>
  1330. // example:
  1331. // set an "odd" class on all odd table rows inside of the table
  1332. // `#tabular_data`, using the `>` (direct child) selector to avoid
  1333. // affecting any nested tables:
  1334. // | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd");
  1335. // example:
  1336. // remove all elements with the class "error" from the document
  1337. // and store them in a list:
  1338. // | var errors = dojo.query(".error").orphan();
  1339. // example:
  1340. // add an onclick handler to every submit button in the document
  1341. // which causes the form to be sent via Ajax instead:
  1342. // | dojo.query("input[type='submit']").onclick(function(e){
  1343. // | dojo.stopEvent(e); // prevent sending the form
  1344. // | var btn = e.target;
  1345. // | dojo.xhrPost({
  1346. // | form: btn.form,
  1347. // | load: function(data){
  1348. // | // replace the form with the response
  1349. // | var div = dojo.doc.createElement("div");
  1350. // | dojo.place(div, btn.form, "after");
  1351. // | div.innerHTML = data;
  1352. // | dojo.style(btn.form, "display", "none");
  1353. // | }
  1354. // | });
  1355. // | });
  1356. root = root || getDoc();
  1357. // throw the big case sensitivity switch
  1358. var od = root.ownerDocument || root; // root is either Document or a node inside the document
  1359. caseSensitive = (od.createElement("div").tagName === "div");
  1360. // NOTE:
  1361. // adding "true" as the 2nd argument to getQueryFunc is useful for
  1362. // testing the DOM branch without worrying about the
  1363. // behavior/performance of the QSA branch.
  1364. var r = getQueryFunc(query)(root);
  1365. // FIXME:
  1366. // need to investigate this branch WRT #8074 and #8075
  1367. if(r && r.nozip){
  1368. return r;
  1369. }
  1370. return _zip(r); // dojo.NodeList
  1371. };
  1372. query.filter = function(/*Node[]*/ nodeList, /*String*/ filter, /*String|DOMNode?*/ root){
  1373. // summary:
  1374. // function for filtering a NodeList based on a selector, optimized for simple selectors
  1375. var tmpNodeList = [],
  1376. parts = getQueryParts(filter),
  1377. filterFunc =
  1378. (parts.length == 1 && !/[^\w#\.]/.test(filter)) ?
  1379. getSimpleFilterFunc(parts[0]) :
  1380. function(node){
  1381. return dojo.query(filter, root).indexOf(node) != -1;
  1382. };
  1383. for(var x = 0, te; te = nodeList[x]; x++){
  1384. if(filterFunc(te)){ tmpNodeList.push(te); }
  1385. }
  1386. return tmpNodeList;
  1387. };
  1388. return query;
  1389. });//end defineQuery