Changeset 12533

Show
Ignore:
Timestamp:
02/19/08 14:38:17 (11 months ago)
Author:
pottedmeat
Message:

Refs #5602. Cleaning up documentation !strict

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • dojo/trunk/_base/query.js

    r12371 r12533  
    3737*/ 
    3838 
    39 /*===== 
     39;(function(){ 
     40        // define everything in a closure for compressability reasons. "d" is an 
     41        // alias to "dojo" since it's so frequently used. This seems a 
     42        // transformation that the build system could perform on a per-file basis. 
     43 
     44        //////////////////////////////////////////////////////////////////////// 
     45        // Utility code 
     46        //////////////////////////////////////////////////////////////////////// 
     47 
     48        var d = dojo; 
     49        var childNodesName = dojo.isIE ? "children" : "childNodes"; 
     50 
     51        var getQueryParts = function(query){ 
     52                // summary: state machine for query tokenization 
     53                if(">~+".indexOf(query.charAt(query.length-1)) >= 0){ 
     54                        query += " *" 
     55                } 
     56                query += " "; // ensure that we terminate the state machine 
     57 
     58                var ts = function(s, e){ 
     59                        return d.trim(query.slice(s, e)); 
     60                } 
     61 
     62                // the overall data graph of the full query, as represented by queryPart objects 
     63                var qparts = [];  
     64                // state keeping vars 
     65                var inBrackets = -1; 
     66                var inParens = -1; 
     67                var inMatchFor = -1; 
     68                var inPseudo = -1; 
     69                var inClass = -1; 
     70                var inId = -1; 
     71                var inTag = -1; 
     72                var lc = ""; // the last character 
     73                var cc = ""; // the current character 
     74                var pStart; 
     75                // iteration vars 
     76                var x = 0; // index in the query 
     77                var ql = query.length; 
     78                var currentPart = null; // data structure representing the entire clause 
     79                var _cp = null; // the current pseudo or attr matcher 
     80 
     81                var endTag = function(){ 
     82                        if(inTag >= 0){ 
     83                                var tv = (inTag == x) ? null : ts(inTag, x).toLowerCase(); 
     84                                currentPart[ (">~+".indexOf(tv) < 0) ? "tag" : "oper" ] = tv; 
     85                                inTag = -1; 
     86                        } 
     87                } 
     88 
     89                var endId = function(){ 
     90                        if(inId >= 0){ 
     91                                currentPart.id = ts(inId, x).replace(/\\/g, ""); 
     92                                inId = -1; 
     93                        } 
     94                } 
     95 
     96                var endClass = function(){ 
     97                        if(inClass >= 0){ 
     98                                currentPart.classes.push(ts(inClass+1, x).replace(/\\/g, "")); 
     99                                inClass = -1; 
     100                        } 
     101                } 
     102 
     103                var endAll = function(){ 
     104                        endId(); endTag(); endClass(); 
     105                } 
     106 
     107                for(; lc=cc, cc=query.charAt(x),x<ql; x++){ 
     108                        if(lc == "\\"){ continue; } 
     109                        if(!currentPart){ 
     110                                // NOTE: I hate all this alloc, but it's shorter than writing tons of if's 
     111                                pStart = x; 
     112                                currentPart = { 
     113                                        query: null, 
     114                                        pseudos: [], 
     115                                        attrs: [], 
     116                                        classes: [], 
     117                                        tag: null, 
     118                                        oper: null, 
     119                                        id: null 
     120                                }; 
     121                                inTag = x; 
     122                        } 
     123 
     124                        if(inBrackets >= 0){ 
     125                                // look for a the close first 
     126                                if(cc == "]"){ 
     127                                        if(!_cp.attr){ 
     128                                                _cp.attr = ts(inBrackets+1, x); 
     129                                        }else{ 
     130                                                _cp.matchFor = ts((inMatchFor||inBrackets+1), x); 
     131                                        } 
     132                                        var cmf = _cp.matchFor; 
     133                                        if(cmf){ 
     134                                                if(     (cmf.charAt(0) == '"') || (cmf.charAt(0)  == "'") ){ 
     135                                                        _cp.matchFor = cmf.substring(1, cmf.length-1); 
     136                                                } 
     137                                        } 
     138                                        currentPart.attrs.push(_cp); 
     139                                        _cp = null; // necessaray? 
     140                                        inBrackets = inMatchFor = -1; 
     141                                }else if(cc == "="){ 
     142                                        var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : ""; 
     143                                        _cp.type = addToCc+cc; 
     144                                        _cp.attr = ts(inBrackets+1, x-addToCc.length); 
     145                                        inMatchFor = x+1; 
     146                                } 
     147                                // now look for other clause parts 
     148                        }else if(inParens >= 0){ 
     149                                if(cc == ")"){ 
     150                                        if(inPseudo >= 0){ 
     151                                                _cp.value = ts(inParens+1, x); 
     152                                        } 
     153                                        inPseudo = inParens = -1; 
     154                                } 
     155                        }else if(cc == "#"){ 
     156                                endAll(); 
     157                                inId = x+1; 
     158                        }else if(cc == "."){ 
     159                                endAll(); 
     160                                inClass = x; 
     161                        }else if(cc == ":"){ 
     162                                endAll(); 
     163                                inPseudo = x; 
     164                        }else if(cc == "["){ 
     165                                endAll(); 
     166                                inBrackets = x; 
     167                                _cp = { 
     168                                        /*===== 
     169                                        attr: null, type: null, matchFor: null 
     170                                        =====*/ 
     171                                }; 
     172                        }else if(cc == "("){ 
     173                                if(inPseudo >= 0){ 
     174                                        _cp = {  
     175                                                name: ts(inPseudo+1, x),  
     176                                                value: null 
     177                                        } 
     178                                        currentPart.pseudos.push(_cp); 
     179                                } 
     180                                inParens = x; 
     181                        }else if(cc == " " && lc != cc){ 
     182                                // note that we expect the string to be " " terminated 
     183                                endAll(); 
     184                                if(inPseudo >= 0){ 
     185                                        currentPart.pseudos.push({ name: ts(inPseudo+1, x) }); 
     186                                } 
     187                                currentPart.hasLoops = (         
     188                                                currentPart.pseudos.length ||  
     189                                                currentPart.attrs.length ||  
     190                                                currentPart.classes.length      ); 
     191                                currentPart.query = ts(pStart, x); 
     192                                currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*"); 
     193                                qparts.push(currentPart); 
     194                                currentPart = null; 
     195                        } 
     196                } 
     197                return qparts; 
     198        }; 
     199         
     200 
     201        //////////////////////////////////////////////////////////////////////// 
     202        // XPath query code 
     203        //////////////////////////////////////////////////////////////////////// 
     204 
     205        // this array is a lookup used to generate an attribute matching function. 
     206        // There is a similar lookup/generator list for the DOM branch with similar 
     207        // calling semantics. 
     208        var xPathAttrs = { 
     209                "*=": function(attr, value){ 
     210                        return "[contains(@"+attr+", '"+ value +"')]"; 
     211                }, 
     212                "^=": function(attr, value){ 
     213                        return "[starts-with(@"+attr+", '"+ value +"')]"; 
     214                }, 
     215                "$=": function(attr, value){ 
     216                        return "[substring(@"+attr+", string-length(@"+attr+")-"+(value.length-1)+")='"+value+"']"; 
     217                }, 
     218                "~=": function(attr, value){ 
     219                        return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]"; 
     220                }, 
     221                "|=": function(attr, value){ 
     222                        return "[contains(concat(' ',@"+attr+",' '), ' "+ value +"-')]"; 
     223                }, 
     224                "=": function(attr, value){ 
     225                        return "[@"+attr+"='"+ value +"']"; 
     226                } 
     227        }; 
     228 
     229        // takes a list of attribute searches, the overall query, a function to 
     230        // generate a default matcher, and a closure-bound method for providing a 
     231        // matching function that generates whatever type of yes/no distinguisher 
     232        // the query method needs. The method is a bit tortured and hard to read 
     233        // because it needs to be used in both the XPath and DOM branches. 
     234        var handleAttrs = function(     attrList,  
     235                                                                query,  
     236                                                                getDefault,  
     237                                                                handleMatch){ 
     238                d.forEach(query.attrs, function(attr){ 
     239                        var matcher; 
     240                        // type, attr, matchFor 
     241                        if(attr.type && attrList[attr.type]){ 
     242                                matcher = attrList[attr.type](attr.attr, attr.matchFor); 
     243                        }else if(attr.attr.length){ 
     244                                matcher = getDefault(attr.attr); 
     245                        } 
     246                        if(matcher){ handleMatch(matcher); } 
     247                }); 
     248        } 
     249 
     250        var buildPath = function(query){ 
     251                var xpath = "."; 
     252                var qparts = getQueryParts(d.trim(query)); 
     253                while(qparts.length){ 
     254                        var tqp = qparts.shift(); 
     255                        var prefix; 
     256                        var postfix = ""; 
     257                        // FIXME: need to add support for ~ and + 
     258                        if(tqp.oper == ">"){ 
     259                                prefix = "/"; 
     260                                // prefix = "/child::*"; 
     261                                tqp = qparts.shift(); 
     262                        }else if(tqp.oper == "~"){ 
     263                                prefix = "/following-sibling::"; // get element following siblings 
     264                                tqp = qparts.shift(); 
     265                        }else if(tqp.oper == "+"){ 
     266                                // FIXME:  
     267                                //              fails when selecting subsequent siblings by node type 
     268                                //              because the position() checks the position in the list 
     269                                //              of matching elements and not the localized siblings 
     270                                prefix = "/following-sibling::"; 
     271                                postfix = "[position()=1]"; 
     272                                tqp = qparts.shift(); 
     273                        }else{ 
     274                                prefix = "//"; 
     275                                // prefix = "/descendant::*" 
     276                        } 
     277 
     278                        // get the tag name (if any) 
     279 
     280                        xpath += prefix + tqp.tag + postfix; 
     281                         
     282                        // check to see if it's got an id. Needs to come first in xpath. 
     283                        if(tqp.id){ 
     284                                xpath += "[@id='"+tqp.id+"'][1]"; 
     285                        } 
     286 
     287                        d.forEach(tqp.classes, function(cn){ 
     288                                var cnl = cn.length; 
     289                                var padding = " "; 
     290                                if(cn.charAt(cnl-1) == "*"){ 
     291                                        padding = ""; cn = cn.substr(0, cnl-1); 
     292                                } 
     293                                xpath +=  
     294                                        "[contains(concat(' ',@class,' '), ' "+ 
     295                                        cn + padding + "')]"; 
     296                        }); 
     297 
     298                        handleAttrs(xPathAttrs, tqp,  
     299                                function(condition){ 
     300                                                return "[@"+condition+"]"; 
     301                                }, 
     302                                function(matcher){ 
     303                                        xpath += matcher; 
     304                                } 
     305                        ); 
     306 
     307                        // FIXME: need to implement pseudo-class checks!! 
     308                }; 
     309                return xpath; 
     310        }; 
     311 
     312        var _xpathFuncCache = {}; 
     313        var getXPathFunc = function(path){ 
     314                if(_xpathFuncCache[path]){ 
     315                        return _xpathFuncCache[path]; 
     316                } 
     317 
     318                var doc = d.doc; 
     319                // var parent = d.body(); // FIXME 
     320                // FIXME: don't need to memoize. The closure scope handles it for us. 
     321                var xpath = buildPath(path); 
     322 
     323                var tf = function(parent){ 
     324                        // XPath query strings are memoized. 
     325                        var ret = []; 
     326                        var xpathResult; 
     327                        try{ 
     328                                xpathResult = doc.evaluate(xpath, parent, null,  
     329                                                                                                // XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null); 
     330                                                                                                XPathResult.ANY_TYPE, null); 
     331                        }catch(e){ 
     332                                console.debug("failure in exprssion:", xpath, "under:", parent); 
     333                                console.debug(e); 
     334                        } 
     335                        var result = xpathResult.iterateNext(); 
     336                        while(result){ 
     337                                ret.push(result); 
     338                                result = xpathResult.iterateNext(); 
     339                        } 
     340                        return ret; 
     341                } 
     342                return _xpathFuncCache[path] = tf; 
     343        }; 
     344 
     345        /* 
     346        d.xPathMatch = function(query){ 
     347                // XPath based DOM query system. Handles a small subset of CSS 
     348                // selectors, subset is identical to the non-XPath version of this 
     349                // function.  
     350 
     351                // FIXME: need to add support for alternate roots 
     352                return getXPathFunc(query)(); 
     353        } 
     354        */ 
     355 
     356        //////////////////////////////////////////////////////////////////////// 
     357        // DOM query code 
     358        //////////////////////////////////////////////////////////////////////// 
     359 
     360        var _filtersCache = {}; 
     361        var _simpleFiltersCache = {}; 
     362 
     363        // the basic building block of the yes/no chaining system. agree(f1, f2) 
     364        // generates a new function which returns the boolean results of both of 
     365        // the passed functions to a single logical-anded result. 
     366        var agree = function(first, second){ 
     367                if(!first){ return second; } 
     368                if(!second){ return first; } 
     369 
     370                return function(){ 
     371                        return first.apply(window, arguments) && second.apply(window, arguments); 
     372                } 
     373        } 
     374 
     375        var _childElements = function(root){ 
     376                var ret = []; 
     377                var te, x=0, tret = root[childNodesName]; 
     378                while(te=tret[x++]){ 
     379                        if(te.nodeType == 1){ ret.push(te); } 
     380                } 
     381                return ret; 
     382        } 
     383 
     384        var _nextSiblings = function(root, single){ 
     385                var ret = []; 
     386                var te = root; 
     387                while(te = te.nextSibling){ 
     388                        if(te.nodeType == 1){ 
     389                                ret.push(te); 
     390                                if(single){ break; } 
     391                        } 
     392                } 
     393                return ret; 
     394        } 
     395 
     396        var _filterDown = function(element, queryParts, matchArr, idx){ 
     397                // NOTE: 
     398                //              in the fast path! this function is called recursively and for 
     399                //              every run of a query. 
     400                var nidx = idx+1; 
     401                var isFinal = (queryParts.length == nidx); 
     402                var tqp = queryParts[idx]; 
     403 
     404                // see if we can constrain our next level to direct children 
     405                if(tqp.oper){ 
     406                        var ecn = (tqp.oper == ">") ?  
     407                                _childElements(element) : 
     408                                _nextSiblings(element, (tqp.oper == "+")); 
     409 
     410                        if(!ecn || !ecn.length){ 
     411                                return; 
     412                        } 
     413                        nidx++; 
     414                        isFinal = (queryParts.length == nidx); 
     415                        // kinda janky, too much array alloc 
     416                        var tf = getFilterFunc(queryParts[idx+1]); 
     417                        // for(var x=ecn.length-1, te; x>=0, te=ecn[x]; x--){ 
     418                        for(var x=0, ecnl=ecn.length, te; x<ecnl, te=ecn[x]; x++){ 
     419                                if(tf(te)){ 
     420                                        if(isFinal){ 
     421                                                matchArr.push(te); 
     422                                        }else{ 
     423                                                _filterDown(te, queryParts, matchArr, nidx); 
     424                                        } 
     425                                } 
     426                                /* 
     427                                if(x==0){ 
     428                                        break; 
     429                                } 
     430                                */ 
     431                        } 
     432                } 
     433 
     434                // otherwise, keep going down, unless we'er at the end 
     435                var candidates = getElementsFunc(tqp)(element); 
     436                if(isFinal){ 
     437                        while(candidates.length){ 
     438                                matchArr.push(candidates.shift()); 
     439                        } 
     440                        /* 
     441                        candidates.unshift(0, matchArr.length-1); 
     442                        matchArr.splice.apply(matchArr, candidates); 
     443                        */ 
     444                }else{ 
     445                        // if we're not yet at the bottom, keep going! 
     446                        while(candidates.length){ 
     447                                _filterDown(candidates.shift(), queryParts, matchArr, nidx); 
     448                        } 
     449                } 
     450        } 
     451 
     452        var filterDown = function(elements, queryParts){ 
     453                var ret = []; 
     454 
     455                // for every root, get the elements that match the descendant selector 
     456                // for(var x=elements.length-1, te; x>=0, te=elements[x]; x--){ 
     457                var x = elements.length - 1, te; 
     458                while(te = elements[x--]){ 
     459                        _filterDown(te, queryParts, ret, 0); 
     460                } 
     461                return ret; 
     462        } 
     463 
     464        var getFilterFunc = function(q){ 
     465                // note: query can't have spaces! 
     466                if(_filtersCache[q.query]){ 
     467                        return _filtersCache[q.query]; 
     468                } 
     469                var ff = null; 
     470 
     471                // does it have a tagName component? 
     472                if(q.tag){ 
     473                        if(q.tag == "*"){ 
     474                                ff = agree(ff,  
     475                                        function(elem){ 
     476                                                return (elem.nodeType == 1); 
     477                                        } 
     478                                ); 
     479                        }else{ 
     480                                // tag name match 
     481                                ff = agree(ff,  
     482                                        function(elem){ 
     483                                                return ( 
     484                                                        (elem.nodeType == 1) && 
     485                                                        (q.tag == elem.tagName.toLowerCase()) 
     486                                                ); 
     487                                                // return isTn; 
     488                                        } 
     489                                ); 
     490                        } 
     491                } 
     492 
     493                // does the node have an ID? 
     494                if(q.id){ 
     495                        ff = agree(ff,  
     496                                function(elem){ 
     497                                        return ( 
     498                                                (elem.nodeType == 1) && 
     499                                                (elem.id == q.id) 
     500                                        ); 
     501                                } 
     502                        ); 
     503                } 
     504 
     505                if(q.hasLoops){ 
     506                        // if we have other query param parts, make sure we add them to the 
     507                        // filter chain 
     508                        ff = agree(ff, getSimpleFilterFunc(q)); 
     509                } 
     510 
     511                return _filtersCache[q.query] = ff; 
     512        } 
     513 
     514        var getNodeIndex = function(node){ 
     515                // NOTE:  
     516                //              we could have a more accurate caching mechanism by invalidating 
     517                //              caches after the query has finished, but I think that'd lead to 
     518                //              significantly more cache churn than the cache would provide 
     519                //              value for in the common case. Generally, we're more 
     520                //              conservative (and therefore, more accurate) than jQuery and 
     521                //              DomQuery WRT node node indexes, but there may be corner cases 
     522                //              in which we fall down.  How much we care about them is TBD. 
     523 
     524                var pn = node.parentNode; 
     525                var pnc = pn.childNodes; 
     526 
     527                // check to see if we can trust the cache. If not, re-key the whole 
     528                // thing and return our node match from that. 
     529 
     530                var nidx = -1; 
     531                var child = pn.firstChild; 
     532                if(!child){ 
     533                        return nidx; 
     534                } 
     535 
     536                var ci = node["__cachedIndex"]; 
     537                var cl = pn["__cachedLength"]; 
     538 
     539                // only handle cache building if we've gone out of sync 
     540                if(((typeof cl == "number")&&(cl != pnc.length))||(typeof ci != "number")){ 
     541                        // rip though the whole set, building cache indexes as we go 
     542                        pn["__cachedLength"] = pnc.length; 
     543                        var idx = 1; 
     544                        do{ 
     545                                // we only assign indexes for nodes with nodeType == 1, as per: 
     546                                //              http://www.w3.org/TR/css3-selectors/#nth-child-pseudo 
     547                                // only elements are counted in the search order, and they 
     548                                // begin at 1 for the first child's index 
     549 
     550                                if(child === node){ 
     551                                        nidx = idx; 
     552                                } 
     553                                if(child.nodeType == 1){ 
     554                                        child["__cachedIndex"] = idx; 
     555                                        idx++; 
     556                                } 
     557                                child = child.nextSibling; 
     558                        }while(child); 
     559                }else{ 
     560                        // NOTE:  
     561                        //              could be incorrect in some cases (node swaps involving the 
     562                        //              passed node, etc.), but we ignore those due to the relative 
     563                        //              unlikelihood of that occuring 
     564                        nidx = ci; 
     565                } 
     566                return nidx; 
     567        } 
     568 
     569        var firedCount = 0; 
     570 
     571        var blank = ""; 
     572        var _getAttr = function(elem, attr){ 
     573                if(attr == "class"){ 
     574                        return elem.className || blank; 
     575                } 
     576                if(attr == "for"){ 
     577                        return elem.htmlFor || blank; 
     578                } 
     579                return elem.getAttribute(attr, 2) || blank; 
     580        } 
     581 
     582        var attrs = { 
     583                "*=": function(attr, value){ 
     584                        return function(elem){ 
     585                                // E[foo*="bar"] 
     586                                //              an E element whose "foo" attribute value contains 
     587                                //              the substring "bar" 
     588                                return (_getAttr(elem, attr).indexOf(value)>=0); 
     589                        } 
     590                }, 
     591                "^=": function(attr, value){ 
     592                        // E[foo^="bar"] 
     593                        //              an E element whose "foo" attribute value begins exactly 
     594                        //              with the string "bar" 
     595                        return function(elem){ 
     596                                return (_getAttr(elem, attr).indexOf(value)==0); 
     597                        } 
     598                }, 
     599                "$=": function(attr, value){ 
     600                        // E[foo$="bar"]         
     601                        //              an E element whose "foo" attribute value ends exactly 
     602                        //              with the string "bar" 
     603                        var tval = " "+value; 
     604                        return function(elem){ 
     605                                var ea = " "+_getAttr(elem, attr); 
     606                                return (ea.lastIndexOf(v