]>
git.wh0rd.org - tt-rss.git/blob - lib/dojo/selector/acme.js.uncompressed.js
1 define("dojo/selector/acme", ["../_base/kernel", "../has", "../dom", "../_base/sniff", "../_base/array", "../_base/lang", "../_base/window"], function(dojo
, has
, dom
){
5 // This module defines the Acme selector engine
8 acme architectural overview:
10 acme is a relatively full-featured CSS3 query library. It is
11 designed to take any valid CSS3 selector and return the nodes matching
12 the selector. To do this quickly, it processes queries in several
13 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
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
36 5.) matched nodes are pruned to ensure they are unique (if necessary)
40 ////////////////////////////////////////////////////////////////////////
42 ////////////////////////////////////////////////////////////////////////
44 // if you are extracting acme 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
49 var each
= dojo
.forEach
;
51 // d.isSafari; // float
52 // d.isOpera; // float
53 // d.isWebKit; // float
54 // d.doc ; // document element
56 var getDoc = function(){ return dojo
.doc
; };
57 // NOTE(alex): the spec is idiotic. CSS queries should ALWAYS be case-sensitive, but nooooooo
58 var cssCaseBug
= ((dojo
.isWebKit
||dojo
.isMozilla
) && ((getDoc().compatMode
) == "BackCompat"));
60 ////////////////////////////////////////////////////////////////////////
62 ////////////////////////////////////////////////////////////////////////
67 // global thunk to determine whether we should treat the current query as
68 // case sensitive or not. This switch is flipped by the query evaluator
69 // based on the document passed as the context to search.
70 var caseSensitive
= false;
73 var yesman = function(){ return true; };
75 ////////////////////////////////////////////////////////////////////////
77 ////////////////////////////////////////////////////////////////////////
79 var getQueryParts = function(query
){
81 // state machine for query tokenization
83 // instead of using a brittle and slow regex-based CSS parser,
84 // acme implements an AST-style query representation. This
85 // representation is only generated once per query. For example,
86 // the same query run multiple times or under different root nodes
87 // does not re-parse the selector expression but instead uses the
88 // cached data structure. The state machine implemented here
89 // terminates on the last " " (space) character and returns an
90 // ordered array of query component structures (or "parts"). Each
91 // part represents an operator or a simple CSS filtering
92 // expression. The structure for parts is documented in the code
97 // this code is designed to run fast and compress well. Sacrifices
98 // to readability and maintainability have been made. Your best
99 // bet when hacking the tokenizer is to put The Donnas on *really*
100 // loud (may we recommend their "Spend The Night" release?) and
101 // just assume you're gonna make mistakes. Keep the unit tests
102 // open and run them frequently. Knowing is half the battle ;-)
103 if(specials
.indexOf(query
.slice(-1)) >= 0){
104 // if we end with a ">", "+", or "~", that means we're implicitly
105 // searching all children, so make it explicit
108 // if you have not provided a terminator, one will be provided for
113 var ts = function(/*Integer*/ s
, /*Integer*/ e
){
116 // take an index to start a string slice from and an end position
117 // and return a trimmed copy of that sub-string
118 return trim(query
.slice(s
, e
));
121 // the overall data graph of the full query, as represented by queryPart objects
125 // state keeping vars
126 var inBrackets
= -1, inParens
= -1, inMatchFor
= -1,
127 inPseudo
= -1, inClass
= -1, inId
= -1, inTag
= -1,
128 lc
= "", cc
= "", pStart
;
131 var x
= 0, // index in the query
133 currentPart
= null, // data structure representing the entire clause
134 _cp
= null; // the current pseudo or attr matcher
136 // several temporary variables are assigned to this structure during a
137 // potential sub-expression match:
139 // a string representing the current full attribute match in a
140 // bracket expression
142 // if there's an operator in a bracket expression, this is
143 // used to keep track of it
145 // the internals of parenthetical expression for a pseudo. for
146 // :nth-child(2n+1), value might be "2n+1"
148 var endTag = function(){
149 // called when the tokenizer hits the end of a particular tag name.
150 // Re-sets state variables for tag matching and sets up the matcher
151 // to handle the next type of token (tag or operator).
153 var tv
= (inTag
== x
) ? null : ts(inTag
, x
); // .toLowerCase();
154 currentPart
[ (specials
.indexOf(tv
) < 0) ? "tag" : "oper" ] = tv
;
159 var endId = function(){
160 // called when the tokenizer might be at the end of an ID portion of a match
162 currentPart
.id
= ts(inId
, x
).replace(/\\/g
, "");
167 var endClass = function(){
168 // called when the tokenizer might be at the end of a class name
169 // match. CSS allows for multiple classes, so we augment the
170 // current item with another class in its list
172 currentPart
.classes
.push(ts(inClass
+ 1, x
).replace(/\\/g
, ""));
177 var endAll = function(){
178 // at the end of a simple fragment, so wall off the matches
184 var endPart = function(){
187 currentPart
.pseudos
.push({ name
: ts(inPseudo
+ 1, x
) });
189 // hint to the selector engine to tell it whether or not it
190 // needs to do any iteration. Many simple selectors don't, and
191 // we can avoid significant construction-time work by advising
192 // the system to skip them
193 currentPart
.loops
= (
194 currentPart
.pseudos
.length
||
195 currentPart
.attrs
.length
||
196 currentPart
.classes
.length
);
198 currentPart
.oquery
= currentPart
.query
= ts(pStart
, x
); // save the full expression as a string
201 // otag/tag are hints to suggest to the system whether or not
202 // it's an operator or a tag. We save a copy of otag since the
203 // tag name is cast to upper-case in regular HTML matches. The
204 // system has a global switch to figure out if the current
205 // expression needs to be case sensitive or not and it will use
206 // otag or tag accordingly
207 currentPart
.otag
= currentPart
.tag
= (currentPart
["oper"]) ? null : (currentPart
.tag
|| "*");
210 // if we're in a case-insensitive HTML doc, we likely want
211 // the toUpperCase when matching on element.tagName. If we
212 // do it here, we can skip the string op per node
214 currentPart
.tag
= currentPart
.tag
.toUpperCase();
217 // add the part to the list
218 if(queryParts
.length
&& (queryParts
[queryParts
.length
-1].oper
)){
219 // operators are always infix, so we remove them from the
220 // list and attach them to the next match. The evaluator is
221 // responsible for sorting out how to handle them.
222 currentPart
.infixOper
= queryParts
.pop();
223 currentPart
.query
= currentPart
.infixOper
.query
+ " " + currentPart
.query
;
225 console.debug( "swapping out the infix",
226 currentPart.infixOper,
227 "and attaching it to",
231 queryParts
.push(currentPart
);
236 // iterate over the query, character by character, building up a
237 // list of query part objects
238 for(; lc
=cc
, cc
=query
.charAt(x
), x
< ql
; x
++){
239 // cc: the current character in the match
240 // lc: the last character (if any)
242 // someone is trying to escape something, so don't try to match any
243 // fragments. We assume we're inside a literal.
244 if(lc
== "\\"){ continue; }
245 if(!currentPart
){ // a part was just ended or none has yet been created
246 // NOTE: I hate all this alloc, but it's shorter than writing tons of if's
248 // rules describe full CSS sub-expressions, like:
250 // .className:first-child
252 // thinger > div.howdy[type=thinger]
253 // the indidual components of the previous query would be
254 // split into 3 parts that would be represented a structure
262 // query: "div.howdy[type=thinger]",
263 // classes: ["howdy"],
271 query
: null, // the full text of the part's rule
272 pseudos
: [], // CSS supports multiple pseud-class matches in a single rule
273 attrs
: [], // CSS supports multi-attribute match, so we need an array
274 classes
: [], // class matches may be additive, e.g.: .thinger.blah.howdy
275 tag
: null, // only one tag...
276 oper
: null, // ...or operator per component. Note that these wind up being exclusive.
277 id
: null, // the id component of a rule
279 return (caseSensitive
) ? this.otag
: this.tag
;
283 // if we don't have a part, we assume we're going to start at
284 // the beginning of a match, which should be a tag name. This
285 // might fault a little later on, but we detect that and this
286 // iteration will still be fine.
291 // look for a the close first
292 if(cc
== "]"){ // if we're in a [...] clause and we end, do assignment
294 // no attribute match was previously begun, so we
295 // assume this is an attribute existence match in the
296 // form of [someAttributeName]
297 _cp
.attr
= ts(inBrackets
+1, x
);
299 // we had an attribute already, so we know that we're
300 // matching some sort of value, as in [attrName=howdy]
301 _cp
.matchFor
= ts((inMatchFor
||inBrackets
+1), x
);
303 var cmf
= _cp
.matchFor
;
305 // try to strip quotes from the matchFor value. We want
306 // [attrName=howdy] to match the same
307 // as [attrName = 'howdy' ]
308 if( (cmf
.charAt(0) == '"') || (cmf
.charAt(0) == "'") ){
309 _cp
.matchFor
= cmf
.slice(1, -1);
312 // end the attribute by adding it to the list of attributes.
313 currentPart
.attrs
.push(_cp
);
314 _cp
= null; // necessary?
315 inBrackets
= inMatchFor
= -1;
317 // if the last char was an operator prefix, make sure we
318 // record it along with the "=" operator.
319 var addToCc
= ("|~^$*".indexOf(lc
) >=0 ) ? lc
: "";
320 _cp
.type
= addToCc
+cc
;
321 _cp
.attr
= ts(inBrackets
+1, x
-addToCc
.length
);
324 // now look for other clause parts
325 }else if(inParens
>= 0){
326 // if we're in a parenthetical expression, we need to figure
327 // out if it's attached to a pseudo-selector rule like
331 _cp
.value
= ts(inParens
+1, x
);
333 inPseudo
= inParens
= -1;
336 // start of an ID match
340 // start of a class match
344 // start of a pseudo-selector match
348 // start of an attribute match.
351 // provide a new structure for the attribute match to fill-in
354 attr: null, type: null, matchFor: null
358 // we really only care if we've entered a parenthetical
359 // expression if we're already inside a pseudo-selector match
361 // provide a new structure for the pseudo match to fill-in
363 name
: ts(inPseudo
+1, x
),
366 currentPart
.pseudos
.push(_cp
);
371 // if it's a space char and the last char is too, consume the
372 // current one without doing more work
382 ////////////////////////////////////////////////////////////////////////
383 // DOM query infrastructure
384 ////////////////////////////////////////////////////////////////////////
386 var agree = function(first
, second
){
387 // the basic building block of the yes/no chaining system. agree(f1,
388 // f2) generates a new function which returns the boolean results of
389 // both of the passed functions to a single logical-anded result. If
390 // either are not passed, the other is used exclusively.
391 if(!first
){ return second
; }
392 if(!second
){ return first
; }
395 return first
.apply(window
, arguments
) && second
.apply(window
, arguments
);
399 var getArr = function(i
, arr
){
400 // helps us avoid array alloc when we don't need it
401 var r
= arr
||[]; // FIXME: should this be 'new d._NodeListCtor()' ?
406 var _isElement = function(n
){ return (1 == n
.nodeType
); };
408 // FIXME: need to coalesce _getAttr with defaultGetter
410 var _getAttr = function(elem
, attr
){
411 if(!elem
){ return blank
; }
413 return elem
.className
|| blank
;
416 return elem
.htmlFor
|| blank
;
419 return elem
.style
.cssText
|| blank
;
421 return (caseSensitive
? elem
.getAttribute(attr
) : elem
.getAttribute(attr
, 2)) || blank
;
425 "*=": function(attr
, value
){
426 return function(elem
){
428 // an E element whose "foo" attribute value contains
429 // the substring "bar"
430 return (_getAttr(elem
, attr
).indexOf(value
)>=0);
433 "^=": function(attr
, value
){
435 // an E element whose "foo" attribute value begins exactly
436 // with the string "bar"
437 return function(elem
){
438 return (_getAttr(elem
, attr
).indexOf(value
)==0);
441 "$=": function(attr
, value
){
443 // an E element whose "foo" attribute value ends exactly
444 // with the string "bar"
445 return function(elem
){
446 var ea
= " "+_getAttr(elem
, attr
);
447 return (ea
.lastIndexOf(value
)==(ea
.length
-value
.length
));
450 "~=": function(attr
, value
){
452 // an E element whose "foo" attribute value is a list of
453 // space-separated values, one of which is exactly equal
456 // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]";
457 var tval
= " "+value
+" ";
458 return function(elem
){
459 var ea
= " "+_getAttr(elem
, attr
)+" ";
460 return (ea
.indexOf(tval
)>=0);
463 "|=": function(attr
, value
){
465 // an E element whose "hreflang" attribute has a
466 // hyphen-separated list of values beginning (from the
468 var valueDash
= value
+"-";
469 return function(elem
){
470 var ea
= _getAttr(elem
, attr
);
473 (ea
.indexOf(valueDash
)==0)
477 "=": function(attr
, value
){
478 return function(elem
){
479 return (_getAttr(elem
, attr
) == value
);
484 // avoid testing for node type if we can. Defining this in the negative
485 // here to avoid negation in the fast path.
486 var _noNES
= (typeof getDoc().firstChild
.nextElementSibling
== "undefined");
487 var _ns
= !_noNES
? "nextElementSibling" : "nextSibling";
488 var _ps
= !_noNES
? "previousElementSibling" : "previousSibling";
489 var _simpleNodeTest
= (_noNES
? _isElement
: yesman
);
491 var _lookLeft = function(node
){
493 while(node
= node
[_ps
]){
494 if(_simpleNodeTest(node
)){ return false; }
499 var _lookRight = function(node
){
501 while(node
= node
[_ns
]){
502 if(_simpleNodeTest(node
)){ return false; }
507 var getNodeIndex = function(node
){
508 var root
= node
.parentNode
;
510 tret
= root
.children
|| root
.childNodes
,
511 ci
= (node
["_i"]||-1),
512 cl
= (root
["_l"]||-1);
514 if(!tret
){ return -1; }
517 // we calculate the parent length as a cheap way to invalidate the
518 // cache. It's not 100% accurate, but it's much more honest than what
519 // other libraries do
520 if( cl
== l
&& ci
>= 0 && cl
>= 0 ){
521 // if it's legit, tag and release
525 // else re-key things
528 for(var te
= root
["firstElementChild"]||root
["firstChild"]; te
; te
= te
[_ns
]){
529 if(_simpleNodeTest(te
)){
533 // shortcutting the return at this step in indexing works
534 // very well for benchmarking but we avoid it here since
535 // it leads to potential O(n^2) behavior in sequential
536 // getNodexIndex operations on a previously un-indexed
537 // parent. We may revisit this at a later time, but for
538 // now we just want to get the right answer more often
547 var isEven = function(elem
){
548 return !((getNodeIndex(elem
)) % 2);
551 var isOdd = function(elem
){
552 return ((getNodeIndex(elem
)) % 2);
556 "checked": function(name
, condition
){
557 return function(elem
){
558 return !!("checked" in elem
? elem
.checked
: elem
.selected
);
561 "first-child": function(){ return _lookLeft
; },
562 "last-child": function(){ return _lookRight
; },
563 "only-child": function(name
, condition
){
564 return function(node
){
565 return _lookLeft(node
) && _lookRight(node
);
568 "empty": function(name
, condition
){
569 return function(elem
){
570 // DomQuery and jQuery get this wrong, oddly enough.
571 // The CSS 3 selectors spec is pretty explicit about it, too.
572 var cn
= elem
.childNodes
;
573 var cnl
= elem
.childNodes
.length
;
574 // if(!cnl){ return true; }
575 for(var x
=cnl
-1; x
>= 0; x
--){
576 var nt
= cn
[x
].nodeType
;
577 if((nt
=== 1)||(nt
== 3)){ return false; }
582 "contains": function(name
, condition
){
583 var cz
= condition
.charAt(0);
584 if( cz
== '"' || cz
== "'" ){ //remove quote
585 condition
= condition
.slice(1, -1);
587 return function(elem
){
588 return (elem
.innerHTML
.indexOf(condition
) >= 0);
591 "not": function(name
, condition
){
592 var p
= getQueryParts(condition
)[0];
593 var ignores
= { el
: 1 };
597 if(!p
.classes
.length
){
600 var ntf
= getSimpleFilterFunc(p
, ignores
);
601 return function(elem
){
605 "nth-child": function(name
, condition
){
607 // avoid re-defining function objects if we can
608 if(condition
== "odd"){
610 }else if(condition
== "even"){
613 // FIXME: can we shorten this?
614 if(condition
.indexOf("n") != -1){
615 var tparts
= condition
.split("n", 2);
616 var pred
= tparts
[0] ? ((tparts
[0] == '-') ? -1 : pi(tparts
[0])) : 1;
617 var idx
= tparts
[1] ? pi(tparts
[1]) : 0;
621 idx
= (idx
% pred
) && (pred
+ (idx
% pred
));
624 lb
= idx
- idx
% pred
;
630 // idx has to be greater than 0 when pred is negative;
631 // shall we throw an error here?
638 return function(elem
){
639 var i
= getNodeIndex(elem
);
640 return (i
>=lb
) && (ub
<0 || i
<=ub
) && ((i
% pred
) == idx
);
646 var ncount
= pi(condition
);
647 return function(elem
){
648 return (getNodeIndex(elem
) == ncount
);
653 var defaultGetter
= (dojo
.isIE
&& (dojo
.isIE
< 9 || dojo
.isQuirks
)) ? function(cond
){
654 var clc
= cond
.toLowerCase();
655 if(clc
== "class"){ cond
= "className"; }
656 return function(elem
){
657 return (caseSensitive
? elem
.getAttribute(cond
) : elem
[cond
]||elem
[clc
]);
660 return function(elem
){
661 return (elem
&& elem
.getAttribute
&& elem
.hasAttribute(cond
));
665 var getSimpleFilterFunc = function(query
, ignores
){
666 // generates a node tester function based on the passed query part. The
667 // query part is one of the structures generated by the query parser
668 // when it creates the query AST. The "ignores" object specifies which
669 // (if any) tests to skip, allowing the system to avoid duplicating
670 // work where it may have already been taken into account by other
671 // factors such as how the nodes to test were fetched in the first
673 if(!query
){ return yesman
; }
674 ignores
= ignores
||{};
678 if(!("el" in ignores
)){
679 ff
= agree(ff
, _isElement
);
682 if(!("tag" in ignores
)){
683 if(query
.tag
!= "*"){
684 ff
= agree(ff
, function(elem
){
685 return (elem
&& (elem
.tagName
== query
.getTag()));
690 if(!("classes" in ignores
)){
691 each(query
.classes
, function(cname
, idx
, arr
){
692 // get the class name
694 var isWildcard = cname.charAt(cname.length-1) == "*";
696 cname = cname.substr(0, cname.length-1);
698 // I dislike the regex thing, even if memoized in a cache, but it's VERY short
699 var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)");
701 var re
= new RegExp("(?:^|\\s)" + cname
+ "(?:\\s|$)");
702 ff
= agree(ff
, function(elem
){
703 return re
.test(elem
.className
);
709 if(!("pseudos" in ignores
)){
710 each(query
.pseudos
, function(pseudo
){
711 var pn
= pseudo
.name
;
713 ff
= agree(ff
, pseudos
[pn
](pn
, pseudo
.value
));
718 if(!("attrs" in ignores
)){
719 each(query
.attrs
, function(attr
){
722 // type, attr, matchFor
723 if(attr
.type
&& attrs
[attr
.type
]){
724 matcher
= attrs
[attr
.type
](a
, attr
.matchFor
);
726 matcher
= defaultGetter(a
);
729 ff
= agree(ff
, matcher
);
734 if(!("id" in ignores
)){
736 ff
= agree(ff
, function(elem
){
737 return (!!elem
&& (elem
.id
== query
.id
));
743 if(!("default" in ignores
)){
750 var _nextSibling = function(filterFunc
){
751 return function(node
, ret
, bag
){
752 while(node
= node
[_ns
]){
753 if(_noNES
&& (!_isElement(node
))){ continue; }
755 (!bag
|| _isUnique(node
, bag
)) &&
766 var _nextSiblings = function(filterFunc
){
767 return function(root
, ret
, bag
){
770 if(_simpleNodeTest(te
)){
771 if(bag
&& !_isUnique(te
, bag
)){
784 // get an array of child *elements*, skipping text and comment nodes
785 var _childElements = function(filterFunc
){
786 filterFunc
= filterFunc
||yesman
;
787 return function(root
, ret
, bag
){
788 // get an array of child elements, skipping text and comment nodes
789 var te
, x
= 0, tret
= root
.children
|| root
.childNodes
;
790 while(te
= tret
[x
++]){
792 _simpleNodeTest(te
) &&
793 (!bag
|| _isUnique(te
, bag
)) &&
805 var itemIsAfterRoot = d.isIE ? function(item, root){
806 return (item.sourceIndex > root.sourceIndex);
807 } : function(item, root){
808 return (item.compareDocumentPosition(root) == 2);
812 // test to see if node is below root
813 var _isDescendant = function(node
, root
){
814 var pn
= node
.parentNode
;
824 var _getElementsFuncCache
= {};
826 var getElementsFunc = function(query
){
827 var retFunc
= _getElementsFuncCache
[query
.query
];
828 // if we've got a cached dispatcher, just use that
829 if(retFunc
){ return retFunc
; }
830 // else, generate a new on
833 // this function returns a function that searches for nodes and
834 // filters them. The search may be specialized by infix operators
835 // (">", "~", or "+") else it will default to searching all
836 // descendants (the " " selector). Once a group of children is
837 // found, a test function is applied to weed out the ones we
838 // don't want. Many common cases can be fast-pathed. We spend a
839 // lot of cycles to create a dispatcher that doesn't do more work
840 // than necessary at any point since, unlike this function, the
841 // dispatchers will be called every time. The logic of generating
842 // efficient dispatchers looks like this in pseudo code:
844 // # if it's a purely descendant query (no ">", "+", or "~" modifiers)
845 // if infixOperator == " ":
848 // return d.byId(id, root);
852 // return filter(d.byId(id, root));
854 // elif cssClass && getElementsByClassName:
856 // return filter(root.getElementsByClassName(cssClass));
860 // return root.getElementsByTagName(tagName);
863 // # search by tag name, then filter
865 // return filter(root.getElementsByTagName(tagName||"*"));
867 // elif infixOperator == ">":
868 // # search direct children
870 // return filter(root.children);
872 // elif infixOperator == "+":
873 // # search next sibling
875 // return filter(root.nextElementSibling);
877 // elif infixOperator == "~":
878 // # search rightward siblings
880 // return filter(nextSiblings(root));
882 var io
= query
.infixOper
;
883 var oper
= (io
? io
.oper
: "");
884 // the default filter func which tests for all conditions in the query
885 // part. This is potentially inefficient, so some optimized paths may
886 // re-define it to test fewer things.
887 var filterFunc
= getSimpleFilterFunc(query
, { el
: 1 });
889 var wildcardTag
= ("*" == qt
);
890 var ecs
= getDoc()["getElementsByClassName"];
893 // if there's no infix operator, then it's a descendant query. ID
894 // and "elements by class name" variants can be accelerated so we
895 // call them out explicitly:
897 // testing shows that the overhead of yesman() is acceptable
898 // and can save us some bytes vs. re-defining the function
900 filterFunc
= (!query
.loops
&& wildcardTag
) ?
902 getSimpleFilterFunc(query
, { el
: 1, id
: 1 });
904 retFunc = function(root
, arr
){
905 var te
= dom
.byId(query
.id
, (root
.ownerDocument
||root
));
906 if(!te
|| !filterFunc(te
)){ return; }
907 if(9 == root
.nodeType
){ // if root's a doc, we just return directly
908 return getArr(te
, arr
);
909 }else{ // otherwise check ancestry
910 if(_isDescendant(te
, root
)){
911 return getArr(te
, arr
);
917 // isAlien check. Workaround for Prototype.js being totally evil/dumb.
918 /\{\s*\[native code\]\s*\}/.test(String(ecs
)) &&
919 query
.classes
.length
&&
922 // it's a class-based query and we've got a fast way to run it.
924 // ignore class and ID filters since we will have handled both
925 filterFunc
= getSimpleFilterFunc(query
, { el
: 1, classes
: 1, id
: 1 });
926 var classesString
= query
.classes
.join(" ");
927 retFunc = function(root
, arr
, bag
){
928 var ret
= getArr(0, arr
), te
, x
=0;
929 var tret
= root
.getElementsByClassName(classesString
);
930 while((te
= tret
[x
++])){
931 if(filterFunc(te
, root
) && _isUnique(te
, bag
)){
938 }else if(!wildcardTag
&& !query
.loops
){
939 // it's tag only. Fast-path it.
940 retFunc = function(root
, arr
, bag
){
941 var ret
= getArr(0, arr
), te
, x
=0;
942 var tret
= root
.getElementsByTagName(query
.getTag());
943 while((te
= tret
[x
++])){
944 if(_isUnique(te
, bag
)){
952 // a descendant selector without a fast path. By now it's got
953 // to have a tag selector, even if it's just "*" so we query
954 // by that and filter
955 filterFunc
= getSimpleFilterFunc(query
, { el
: 1, tag
: 1, id
: 1 });
956 retFunc = function(root
, arr
, bag
){
957 var ret
= getArr(0, arr
), te
, x
=0;
958 // we use getTag() to avoid case sensitivity issues
959 var tret
= root
.getElementsByTagName(query
.getTag());
960 while((te
= tret
[x
++])){
961 if(filterFunc(te
, root
) && _isUnique(te
, bag
)){
969 // the query is scoped in some way. Instead of querying by tag we
970 // use some other collection to find candidate nodes
971 var skipFilters
= { el
: 1 };
975 filterFunc
= getSimpleFilterFunc(query
, skipFilters
);
977 retFunc
= _nextSibling(filterFunc
);
978 }else if("~" == oper
){
979 retFunc
= _nextSiblings(filterFunc
);
980 }else if(">" == oper
){
981 retFunc
= _childElements(filterFunc
);
984 // cache it and return
985 return _getElementsFuncCache
[query
.query
] = retFunc
;
988 var filterDown = function(root
, queryParts
){
990 // this is the guts of the DOM query system. It takes a list of
991 // parsed query parts and a root and finds children which match
992 // the selector represented by the parts
993 var candidates
= getArr(root
), qp
, x
, te
, qpl
= queryParts
.length
, bag
, ret
;
995 for(var i
= 0; i
< qpl
; i
++){
998 x
= candidates
.length
- 1;
1000 // if we have more than one root at this level, provide a new
1001 // hash to use for checking group membership but tell the
1002 // system not to post-filter us since we will already have been
1003 // gauranteed to be unique
1007 var gef
= getElementsFunc(qp
);
1008 for(var j
= 0; (te
= candidates
[j
]); j
++){
1009 // for every root, get the elements that match the descendant
1010 // selector, adding them to the "ret" array and filtering them
1011 // via membership in this level's bag. If there are more query
1012 // parts, then this level's return will be used as the next
1013 // level's candidates
1016 if(!ret
.length
){ break; }
1022 ////////////////////////////////////////////////////////////////////////
1024 ////////////////////////////////////////////////////////////////////////
1026 // these are the primary caches for full-query results. The query
1027 // dispatcher functions are generated then stored here for hash lookup in
1029 var _queryFuncCacheDOM
= {},
1030 _queryFuncCacheQSA
= {};
1032 // this is the second level of spliting, from full-length queries (e.g.,
1033 // "div.foo .bar") into simple query expressions (e.g., ["div.foo",
1035 var getStepQueryFunc = function(query
){
1036 var qparts
= getQueryParts(trim(query
));
1038 // if it's trivial, avoid iteration and zipping costs
1039 if(qparts
.length
== 1){
1040 // we optimize this case here to prevent dispatch further down the
1041 // chain, potentially slowing things down. We could more elegantly
1042 // handle this in filterDown(), but it's slower for simple things
1043 // that need to be fast (e.g., "#someId").
1044 var tef
= getElementsFunc(qparts
[0]);
1045 return function(root
){
1046 var r
= tef(root
, []);
1047 if(r
){ r
.nozip
= true; }
1052 // otherwise, break it up and return a runner that iterates over the parts recursively
1053 return function(root
){
1054 return filterDown(root
, qparts
);
1059 // * we can't trust QSA for anything but document-rooted queries, so
1060 // caching is split into DOM query evaluators and QSA query evaluators
1061 // * caching query results is dirty and leak-prone (or, at a minimum,
1062 // prone to unbounded growth). Other toolkits may go this route, but
1063 // they totally destroy their own ability to manage their memory
1064 // footprint. If we implement it, it should only ever be with a fixed
1065 // total element reference # limit and an LRU-style algorithm since JS
1066 // has no weakref support. Caching compiled query evaluators is also
1067 // potentially problematic, but even on large documents the size of the
1068 // query evaluators is often < 100 function objects per evaluator (and
1069 // LRU can be applied if it's ever shown to be an issue).
1070 // * since IE's QSA support is currently only for HTML documents and even
1071 // then only in IE 8's "standards mode", we have to detect our dispatch
1072 // route at query time and keep 2 separate caches. Ugg.
1074 // we need to determine if we think we can run a given query via
1075 // querySelectorAll or if we'll need to fall back on DOM queries to get
1076 // there. We need a lot of information about the environment and the query
1077 // to make the determiniation (e.g. does it support QSA, does the query in
1078 // question work in the native QSA impl, etc.).
1079 var nua
= navigator
.userAgent
;
1080 // some versions of Safari provided QSA, but it was buggy and crash-prone.
1081 // We need te detect the right "internal" webkit version to make this work.
1085 (nua
.indexOf(wk
) > 0) &&
1086 (parseFloat(nua
.split(wk
)[1]) > 528)
1089 // IE QSA queries may incorrectly include comment nodes, so we throw the
1090 // zipping function into "remove" comments mode instead of the normal "skip
1091 // it" which every other QSA-clued browser enjoys
1092 var noZip
= dojo
.isIE
? "commentStrip" : "nozip";
1094 var qsa
= "querySelectorAll";
1098 (!dojo
.isSafari
|| (dojo
.isSafari
> 3.1) || is525
)
1101 //Don't bother with n+3 type of matches, IE complains if we modify those.
1102 var infixSpaceRe
= /n\+\d|([^ ])?([>~+])([^ =])?/g;
1103 var infixSpaceFunc = function(match
, pre
, ch
, post
){
1104 return ch
? (pre
? pre
+ " " : "") + ch
+ (post
? " " + post
: "") : /*n+3*/ match
;
1107 var getQueryFunc = function(query
, forceDOM
){
1108 //Normalize query. The CSS3 selectors spec allows for omitting spaces around
1109 //infix operators, >, ~ and +
1110 //Do the work here since detection for spaces is used as a simple "not use QSA"
1112 query
= query
.replace(infixSpaceRe
, infixSpaceFunc
);
1115 // if we've got a cached variant and we think we can do it, run it!
1116 var qsaCached
= _queryFuncCacheQSA
[query
];
1117 if(qsaCached
&& !forceDOM
){ return qsaCached
; }
1120 // else if we've got a DOM cached variant, assume that we already know
1121 // all we need to and use it
1122 var domCached
= _queryFuncCacheDOM
[query
];
1123 if(domCached
){ return domCached
; }
1126 // today we're caching DOM and QSA branches separately so we
1127 // recalc useQSA every time. If we had a way to tag root+query
1128 // efficiently, we'd be in good shape to do a global cache.
1130 var qcz
= query
.charAt(0);
1131 var nospace
= (-1 == query
.indexOf(" "));
1133 // byId searches are wicked fast compared to QSA, even when filtering
1135 if( (query
.indexOf("#") >= 0) && (nospace
) ){
1140 qsaAvail
&& (!forceDOM
) &&
1141 // as per CSS 3, we can't currently start w/ combinator:
1142 // http://www.w3.org/TR/css3-selectors/#w3cselgrammar
1143 (specials
.indexOf(qcz
) == -1) &&
1144 // IE's QSA impl sucks on pseudos
1145 (!dojo
.isIE
|| (query
.indexOf(":") == -1)) &&
1147 (!(cssCaseBug
&& (query
.indexOf(".") >= 0))) &&
1150 // need to tighten up browser rules on ":contains" and "|=" to
1151 // figure out which aren't good
1152 // Latest webkit (around 531.21.8) does not seem to do well with :checked on option
1153 // elements, even though according to spec, selected options should
1154 // match :checked. So go nonQSA for it:
1155 // http://bugs.dojotoolkit.org/ticket/5179
1156 (query
.indexOf(":contains") == -1) && (query
.indexOf(":checked") == -1) &&
1157 (query
.indexOf("|=") == -1) // some browsers don't grok it
1161 // if we've got a descendant query (e.g., "> .thinger" instead of
1162 // just ".thinger") in a QSA-able doc, but are passed a child as a
1163 // root, it should be possible to give the item a synthetic ID and
1164 // trivially rewrite the query to the form "#synid > .thinger" to
1165 // use the QSA branch
1169 var tq
= (specials
.indexOf(query
.charAt(query
.length
-1)) >= 0) ?
1170 (query
+ " *") : query
;
1171 return _queryFuncCacheQSA
[query
] = function(root
){
1173 // the QSA system contains an egregious spec bug which
1174 // limits us, effectively, to only running QSA queries over
1175 // entire documents. See:
1176 // http://ejohn.org/blog/thoughts-on-queryselectorall/
1177 // despite this, we can also handle QSA runs on simple
1178 // selectors, but we don't want detection to be expensive
1179 // so we're just checking for the presence of a space char
1180 // right now. Not elegant, but it's cheaper than running
1181 // the query parser when we might not need to
1182 if(!((9 == root
.nodeType
) || nospace
)){ throw ""; }
1183 var r
= root
[qsa
](tq
);
1184 // skip expensive duplication checks and just wrap in a NodeList
1188 // else run the DOM branch on this query, ensuring that we
1189 // default that way in the future
1190 return getQueryFunc(query
, true)(root
);
1195 var parts
= query
.split(/\s*,\s*/);
1196 return _queryFuncCacheDOM
[query
] = ((parts
.length
< 2) ?
1197 // if not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher
1198 getStepQueryFunc(query
) :
1199 // if it *is* a complex query, break it up into its
1200 // constituent parts and return a dispatcher that will
1201 // merge the parts when run
1203 var pindex
= 0, // avoid array alloc for every invocation
1206 while((tp
= parts
[pindex
++])){
1207 ret
= ret
.concat(getStepQueryFunc(tp
)(root
));
1218 // this function is Moo inspired, but our own impl to deal correctly
1220 var _nodeUID
= dojo
.isIE
? function(node
){
1222 // XML docs don't have uniqueID on their nodes
1223 return (node
.getAttribute("_uid") || node
.setAttribute("_uid", ++_zipIdx
) || _zipIdx
);
1226 return node
.uniqueID
;
1230 return (node
._uid
|| (node
._uid
= ++_zipIdx
));
1233 // determine if a node in is unique in a "bag". In this case we don't want
1234 // to flatten a list of unique items, but rather just tell if the item in
1235 // question is already in the bag. Normally we'd just use hash lookup to do
1236 // this for us but IE's DOM is busted so we can't really count on that. On
1237 // the upside, it gives us a built in unique ID function.
1238 var _isUnique = function(node
, bag
){
1239 if(!bag
){ return 1; }
1240 var id
= _nodeUID(node
);
1241 if(!bag
[id
]){ return bag
[id
] = 1; }
1245 // attempt to efficiently determine if an item in a list is a dupe,
1246 // returning a list of "uniques", hopefully in doucment order
1247 var _zipIdxName
= "_zipIdx";
1248 var _zip = function(arr
){
1249 if(arr
&& arr
.nozip
){
1253 if(!arr
|| !arr
.length
){ return ret
; }
1257 if(arr
.length
< 2){ return ret
; }
1261 // we have to fork here for IE and XML docs because we can't set
1262 // expandos on their nodes (apparently). *sigh*
1263 if(dojo
.isIE
&& caseSensitive
){
1264 var szidx
= _zipIdx
+"";
1265 arr
[0].setAttribute(_zipIdxName
, szidx
);
1266 for(var x
= 1, te
; te
= arr
[x
]; x
++){
1267 if(arr
[x
].getAttribute(_zipIdxName
) != szidx
){
1270 te
.setAttribute(_zipIdxName
, szidx
);
1272 }else if(dojo
.isIE
&& arr
.commentStrip
){
1274 for(var x
= 1, te
; te
= arr
[x
]; x
++){
1279 }catch(e
){ /* squelch */ }
1281 if(arr
[0]){ arr
[0][_zipIdxName
] = _zipIdx
; }
1282 for(var x
= 1, te
; te
= arr
[x
]; x
++){
1283 if(arr
[x
][_zipIdxName
] != _zipIdx
){
1286 te
[_zipIdxName
] = _zipIdx
;
1292 // the main executor
1293 var query = function(/*String*/ query
, /*String|DOMNode?*/ root
){
1295 // Returns nodes which match the given CSS3 selector, searching the
1296 // entire document by default but optionally taking a node to scope
1297 // the search by. Returns an array.
1299 // dojo.query() is the swiss army knife of DOM node manipulation in
1300 // Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
1301 // "$" function, dojo.query provides robust, high-performance
1302 // CSS-based node selector support with the option of scoping searches
1303 // to a particular sub-tree of a document.
1305 // Supported Selectors:
1306 // --------------------
1308 // acme supports a rich set of CSS3 selectors, including:
1310 // * class selectors (e.g., `.foo`)
1311 // * node type selectors like `span`
1312 // * ` ` descendant selectors
1313 // * `>` child element selectors
1314 // * `#foo` style ID selectors
1315 // * `*` universal selector
1316 // * `~`, the preceded-by sibling selector
1317 // * `+`, the immediately preceded-by sibling selector
1318 // * attribute queries:
1319 // | * `[foo]` attribute presence selector
1320 // | * `[foo='bar']` attribute value exact match
1321 // | * `[foo~='bar']` attribute value list item match
1322 // | * `[foo^='bar']` attribute start match
1323 // | * `[foo$='bar']` attribute end match
1324 // | * `[foo*='bar']` attribute substring match
1325 // * `:first-child`, `:last-child`, and `:only-child` positional selectors
1326 // * `:empty` content emtpy selector
1327 // * `:checked` pseudo selector
1328 // * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
1329 // * `:nth-child(even)`, `:nth-child(odd)` positional selectors
1330 // * `:not(...)` negation pseudo selectors
1332 // Any legal combination of these selectors will work with
1333 // `dojo.query()`, including compound selectors ("," delimited).
1334 // Very complex and useful searches can be constructed with this
1335 // palette of selectors and when combined with functions for
1336 // manipulation presented by dojo.NodeList, many types of DOM
1337 // manipulation operations become very straightforward.
1339 // Unsupported Selectors:
1340 // ----------------------
1342 // While dojo.query handles many CSS3 selectors, some fall outside of
1343 // what's reasonable for a programmatic node querying engine to
1344 // handle. Currently unsupported selectors include:
1346 // * namespace-differentiated selectors of any form
1347 // * all `::` pseduo-element selectors
1348 // * certain pseduo-selectors which don't get a lot of day-to-day use:
1349 // | * `:root`, `:lang()`, `:target`, `:focus`
1350 // * all visual and state selectors:
1351 // | * `:root`, `:active`, `:hover`, `:visisted`, `:link`,
1352 // `:enabled`, `:disabled`
1353 // * `:*-of-type` pseudo selectors
1355 // dojo.query and XML Documents:
1356 // -----------------------------
1358 // `dojo.query` (as of dojo 1.2) supports searching XML documents
1359 // in a case-sensitive manner. If an HTML document is served with
1360 // a doctype that forces case-sensitivity (e.g., XHTML 1.1
1361 // Strict), dojo.query() will detect this and "do the right
1362 // thing". Case sensitivity is dependent upon the document being
1363 // searched and not the query used. It is therefore possible to
1364 // use case-sensitive queries on strict sub-documents (iframes,
1365 // etc.) or XML documents while still assuming case-insensitivity
1366 // for a host/root document.
1368 // Non-selector Queries:
1369 // ---------------------
1371 // If something other than a String is passed for the query,
1372 // `dojo.query` will return a new `dojo.NodeList` instance
1373 // constructed from that parameter alone and all further
1374 // processing will stop. This means that if you have a reference
1375 // to a node or NodeList, you can quickly construct a new NodeList
1376 // from the original by calling `dojo.query(node)` or
1377 // `dojo.query(list)`.
1380 // The CSS3 expression to match against. For details on the syntax of
1381 // CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors>
1383 // A DOMNode (or node id) to scope the search from. Optional.
1386 // search the entire document for elements with the class "foo":
1387 // | dojo.query(".foo");
1388 // these elements will match:
1389 // | <span class="foo"></span>
1390 // | <span class="foo bar"></span>
1391 // | <p class="thud foo"></p>
1393 // search the entire document for elements with the classes "foo" *and* "bar":
1394 // | dojo.query(".foo.bar");
1395 // these elements will match:
1396 // | <span class="foo bar"></span>
1397 // while these will not:
1398 // | <span class="foo"></span>
1399 // | <p class="thud foo"></p>
1401 // find `<span>` elements which are descendants of paragraphs and
1402 // which have a "highlighted" class:
1403 // | dojo.query("p span.highlighted");
1404 // the innermost span in this fragment matches:
1405 // | <p class="foo">
1407 // | <span class="highlighted foo bar">...</span>
1411 // set an "odd" class on all odd table rows inside of the table
1412 // `#tabular_data`, using the `>` (direct child) selector to avoid
1413 // affecting any nested tables:
1414 // | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd");
1416 // remove all elements with the class "error" from the document
1417 // and store them in a list:
1418 // | var errors = dojo.query(".error").orphan();
1420 // add an onclick handler to every submit button in the document
1421 // which causes the form to be sent via Ajax instead:
1422 // | dojo.query("input[type='submit']").onclick(function(e){
1423 // | dojo.stopEvent(e); // prevent sending the form
1424 // | var btn = e.target;
1426 // | form: btn.form,
1427 // | load: function(data){
1428 // | // replace the form with the response
1429 // | var div = dojo.doc.createElement("div");
1430 // | dojo.place(div, btn.form, "after");
1431 // | div.innerHTML = data;
1432 // | dojo.style(btn.form, "display", "none");
1437 root
= root
||getDoc();
1438 var od
= root
.ownerDocument
||root
.documentElement
;
1440 // throw the big case sensitivity switch
1443 // Opera in XHTML mode doesn't detect case-sensitivity correctly
1444 // and it's not clear that there's any way to test for it
1445 caseSensitive
= (root
.contentType
&& root
.contentType
=="application/xml") ||
1446 (dojo
.isOpera
&& (root
.doctype
|| od
.toString() == "[object XMLDocument]")) ||
1448 (dojo
.isIE
? od
.xml
: (root
.xmlVersion
|| od
.xmlVersion
));
1451 // adding "true" as the 2nd argument to getQueryFunc is useful for
1452 // testing the DOM branch without worrying about the
1453 // behavior/performance of the QSA branch.
1454 var r
= getQueryFunc(query
)(root
);
1457 // need to investigate this branch WRT #8074 and #8075
1461 return _zip(r
); // dojo.NodeList
1463 query
.filter = function(/*Node[]*/ nodeList
, /*String*/ filter
, /*String|DOMNode?*/ root
){
1465 // function for filtering a NodeList based on a selector, optimized for simple selectors
1466 var tmpNodeList
= [],
1467 parts
= getQueryParts(filter
),
1469 (parts
.length
== 1 && !/[^\w#\.]/.test(filter
)) ?
1470 getSimpleFilterFunc(parts
[0]) :
1472 return dojo
.query(filter
, root
).indexOf(node
) != -1;
1474 for(var x
= 0, te
; te
= nodeList
[x
]; x
++){
1475 if(filterFunc(te
)){ tmpNodeList
.push(te
); }
1480 });//end defineQuery