query.js 52 KB

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