Changeset 7457

Show
Ignore:
Timestamp:
02/27/07 11:30:40 (23 months ago)
Author:
alex
Message:

Extend xpath code path to enable xpath-based attribute queries. Fixes #2506

Files:
1 modified

Legend:

Unmodified
Added
Removed
  • trunk/src/query.js

    r7447 r7457  
    88        var h = d.render.html; 
    99 
     10        //////////////////////////////////////////////////////////////////////// 
     11        // Utility code 
     12        //////////////////////////////////////////////////////////////////////// 
     13 
    1014        var _getIndexes = function(q){ 
    1115                return [ q.indexOf("#"), q.indexOf("."), q.indexOf("["), q.indexOf(":") ]; 
     16        } 
     17 
     18        var _lowestFromIndex = function(query, index){ 
     19                // spammy and verbose, but fast 
     20                var ql = query.length; 
     21                var i = _getIndexes(query); 
     22                var end = ql; 
     23                for(var x=index; x<i.length; x++){ 
     24                        if(i[x] >= 0){ 
     25                                if(i[x] < end){ 
     26                                        end = i[x]; 
     27                                } 
     28                        } 
     29                } 
     30                return (end < 0) ? ql : end; 
     31        } 
     32 
     33        var getIdEnd = function(query){ 
     34                return _lowestFromIndex(query, 1); 
    1235        } 
    1336 
     
    2144        } 
    2245 
    23         var getIdEnd = function(query){ 
    24                 return _lowestFromIndex(query, 1); 
    25         } 
    26  
    2746        //////////////////////////////////////////////////////////////////////// 
    2847        // XPath query code 
    2948        //////////////////////////////////////////////////////////////////////// 
     49 
     50        var xPathAttrs = [ 
     51                // FIXME: need to re-order in order of likelyness to be used in matches 
     52                { 
     53                        key: "|=", 
     54                        match: function(attr, value){ 
     55                                return "[contains(concat(' ',@"+attr+",' '), ' "+ value +"-')]"; 
     56                        } 
     57                }, 
     58                { 
     59                        key: "^=", 
     60                        match: function(attr, value){ 
     61                                return "[starts-with(@"+attr+", '"+ value +"')]"; 
     62                        } 
     63                }, 
     64                { 
     65                        key: "*=", 
     66                        match: function(attr, value){ 
     67                                return "[contains(@"+attr+", '"+ value +"')]"; 
     68                        } 
     69                }, 
     70                { 
     71                        key: "$=", 
     72                        match: function(attr, value){ 
     73                                return "[substring(@"+attr+", string-length(@"+attr+")-"+(value.length-1)+")='"+value+"']"; 
     74                        } 
     75                }, 
     76                { 
     77                        key: "!=", 
     78                        match: function(attr, value){ 
     79                                return "[not(@"+attr+"='"+ value +"')]"; 
     80                        } 
     81                }, 
     82                // NOTE: the "=" match MUST come last! 
     83                { 
     84                        key: "=", 
     85                        match: function(attr, value){ 
     86                                return "[@"+attr+"='"+ value +"']"; 
     87                        } 
     88                } 
     89        ]; 
     90 
     91        var handleAttrs = function(     attrList,  
     92                                                                query,  
     93                                                                getDefault,  
     94                                                                handleMatch){ 
     95                var matcher; 
     96                var i = _getIndexes(query); 
     97                if(i[2] >= 0){ 
     98                        var lBktIdx = query.indexOf("]", i[2]); 
     99                        var condition = query.substring(i[2]+1, lBktIdx); 
     100                        while(condition && condition.length){ 
     101                                if(condition.charAt(0) == "@"){ 
     102                                        condition = condition.slice(1); 
     103                                } 
     104 
     105                                matcher = null; 
     106                                // http://www.w3.org/TR/css3-selectors/#attribute-selectors 
     107                                for(var x=0; x<attrList.length; x++){ 
     108                                        var ta = attrList[x]; 
     109                                        var tci = condition.indexOf(ta.key); 
     110                                        if(tci >= 0){ 
     111                                                var attr = condition.substring(0, tci); 
     112                                                var value = condition.substring(tci+ta.key.length); 
     113                                                if(     (value.charAt(0) == "\"")|| 
     114                                                        (value.charAt(0) == "\'")){ 
     115                                                        value = value.substring(1, value.length-1); 
     116                                                } 
     117                                                matcher = ta.match(attr, value); 
     118                                                break; 
     119                                        } 
     120                                } 
     121                                if((!matcher)&&(condition.length)){ 
     122                                        matcher = getDefault(condition); 
     123                                } 
     124                                if(matcher){ 
     125                                        handleMatch(matcher); 
     126                                        // ff = agree(ff, matcher); 
     127                                } 
     128 
     129                                condition = null; 
     130                                var nbktIdx = query.indexOf("[", lBktIdx); 
     131                                if(0 <= nbktIdx){ 
     132                                        lBktIdx = query.indexOf("]", nbktIdx); 
     133                                        if(0 <= lBktIdx){ 
     134                                                condition = query.substring(nbktIdx+1, lBktIdx); 
     135                                        } 
     136                                } 
     137                        } 
     138                } 
     139        } 
    30140 
    31141        var buildPath = function(query){ 
     
    64174                        } 
    65175 
    66                         // FIXME: need to implement attribute and pseudo-class checks!! 
    67  
     176                        handleAttrs(xPathAttrs, tqp,  
     177                                function(condition){ 
     178                                                return "[@"+condition+"]"; 
     179                                }, 
     180                                function(matcher){ 
     181                                        xpath += matcher; 
     182                                } 
     183                        ); 
     184 
     185                        // FIXME: need to implement pseudo-class checks!! 
    68186                }; 
     187                // dojo.debug(xpath); 
    69188                return xpath; 
    70189        }; 
     
    76195                } 
    77196 
    78                 var doc = dojo.doc(); 
    79                 var parent = dojo.body(); // FIXME 
     197                var doc = d.doc(); 
     198                var parent = d.body(); // FIXME 
    80199                // FIXME: don't need to memoize. The closure scope handles it for us. 
    81200                var xpath = buildPath(path); 
     
    188307        } 
    189308 
    190         var _lowestFromIndex = function(query, index){ 
    191                 // spammy and verbose, but fast 
    192                 var ql = query.length; 
    193                 var i = _getIndexes(query); 
    194                 var end = ql; 
    195                 for(var x=index; x<i.length; x++){ 
    196                         if(i[x] >= 0){ 
    197                                 if(i[x] < end){ 
    198                                         end = i[x]; 
    199                                 } 
    200                         } 
    201                 } 
    202                 return (end < 0) ? ql : end; 
    203         } 
    204  
    205309        var getTagNameEnd = function(query){ 
    206310                var i = _getIndexes(query); 
     
    363467                { 
    364468                        key: "|=", 
    365                         getMatcher: function(attr, value){ 
     469                        match: function(attr, value){ 
    366470                                // E[hreflang|="en"] 
    367471                                //              an E element whose "hreflang" attribute has a 
     
    380484                { 
    381485                        key: "^=", 
    382                         getMatcher: function(attr, value){ 
     486                        match: function(attr, value){ 
    383487                                return function(elem){ 
    384488                                        return (_getAttr(elem, attr).indexOf(value)==0); 
     
    388492                { 
    389493                        key: "*=", 
    390                         getMatcher: function(attr, value){ 
     494                        match: function(attr, value){ 
    391495                                return function(elem){ 
    392496                                        return (_getAttr(elem, attr).indexOf(value)>=0); 
     
    396500                { 
    397501                        key: "$=", 
    398                         getMatcher: function(attr, value){ 
     502                        match: function(attr, value){ 
    399503                                return function(elem){ 
    400504                                        var ea = _getAttr(elem, attr); 
     
    405509                { 
    406510                        key: "!=", 
    407                         getMatcher: function(attr, value){ 
     511                        match: function(attr, value){ 
    408512                                return function(elem){ 
    409513                                        return (_getAttr(elem, attr) != value); 
     
    414518                { 
    415519                        key: "=", 
    416                         getMatcher: function(attr, value){ 
     520                        match: function(attr, value){ 
    417521                                return function(elem){ 
    418522                                        return (_getAttr(elem, attr) == value); 
     
    425529                { 
    426530                        key: "first-child", 
    427                         getMatcher: function(name, condition){ 
     531                        match: function(name, condition){ 
    428532                                return function(elem){ 
    429533                                        if(elem.nodeType != 1){ return false; } 
     
    439543                { 
    440544                        key: "last-child", 
    441                         getMatcher: function(name, condition){ 
     545                        match: function(name, condition){ 
    442546                                return function(elem){ 
    443547                                        if(elem.nodeType != 1){ return false; } 
     
    453557                { 
    454558                        key: "empty", 
    455                         getMatcher: function(name, condition){ 
     559                        match: function(name, condition){ 
    456560                                return function(elem){ 
    457561                                        // DomQuery and jQuery get this wrong, oddly enough. 
     
    471575                { 
    472576                        key: "contains", 
    473                         getMatcher: function(name, condition){ 
     577                        match: function(name, condition){ 
    474578                                return function(elem){ 
    475579                                        // FIXME: I dislike this version of "contains", as 
     
    484588                { 
    485589                        key: "not", 
    486                         getMatcher: function(name, condition){ 
     590                        match: function(name, condition){ 
    487591                                var ntf = getFilterFunc(condition); 
    488592                                return function(elem){ 
     
    498602                { 
    499603                        key: "nth-child", 
    500                         getMatcher: function(name, condition){ 
     604                        match: function(name, condition){ 
     605                                var pi = parseInt; 
    501606                                if(condition == "odd"){ 
    502607                                        return function(elem){ 
     
    511616                                        } 
    512617                                }else if(condition.indexOf("0n+") == 0){ 
    513                                         var ncount = parseInt(condition.substr(3)); 
     618                                        var ncount = pi(condition.substr(3)); 
    514619                                        return function(elem){ 
    515620                                                return (elem.parentNode.childNodes[ncount-1] === elem); 
     
    518623                                                        (condition.length > 3) ){ 
    519624                                        var tparts = condition.split("n+", 2); 
    520                                         var pred = parseInt(tparts[0]); 
    521                                         var idx = parseInt(tparts[1]); 
     625                                        var pred = pi(tparts[0]); 
     626                                        var idx = pi(tparts[1]); 
    522627                                        return function(elem){ 
    523628                                                return ((getNodeIndex(elem) % pred) == idx); 
    524629                                        } 
    525630                                }else if(condition.indexOf("n") == -1){ 
    526                                         var ncount = parseInt(condition); 
     631                                        var ncount = pi(condition); 
    527632                                        return function(elem){ 
    528633                                                // removeChaffNodes(elem.parentNode); 
     
    573678                        }); 
    574679 
    575                         /* 
    576                         var spcCC = " ".charCodeAt(0); 
    577                         var cnl = className.length; 
    578                         ff = agree(ff, function(elem){ 
    579                                 var ecn = elem.className; 
    580                                 var ecnl = ecn.length; 
    581                                 var cni = ecn.indexOf(className); 
    582                                 if(cni == 0){ 
    583                                         // if it's at the front... 
    584                                         return ( 
    585                                                 // and it's the whole thing... 
    586                                                 (ecnl == cnl) || 
    587                                                 // or it's a full match 
    588                                                 (ecn.charCodeAt(cnl) == spcCC) 
    589                                         ); 
    590                                 }else if(ecnl == (cni+cnl)){ 
    591                                         // or it's at the back... 
    592                                         return ( 
    593                                                 // and it's the whole thing... 
    594                                                 (ecnl == cnl) || 
    595                                                 // or it's a full match 
    596                                                 (ecn.charCodeAt(cni-1) == spcCC) 
    597                                         ); 
    598                                 }else{ 
    599                                         return ( 
    600                                                 (ecn.charCodeAt(cni-1) == spcCC) && 
    601                                                 (ecn.charCodeAt(cni+cnl) == spcCC) 
    602                                         ); 
    603                                 } 
    604                         }); 
    605                         */ 
    606  
    607680                } 
    608681                // [ "#", ".", "[", ":" ]; 
     682                var defaultGetter = (h.ie) ? 
     683                        function(cond){ 
     684                                return function(elem){ 
     685                                        return elem[cond]; 
     686                                } 
     687                        } : function(cond){ 
     688                                return function(elem){ 
     689                                        return elem.hasAttribute(cond); 
     690                                } 
     691                        }; 
     692                handleAttrs(attrs, query, defaultGetter, 
     693                        function(tmatcher){ 
     694                                ff = agree(ff, tmatcher); 
     695                        } 
     696                ); 
     697 
    609698                var matcher; 
    610                 if(i[2] >= 0){ 
    611                         // FIXME: need to implement chained attribute searches!! 
    612                         // var lBktIdx = query.lastIndexOf("]"); 
    613                         var lBktIdx = query.indexOf("]", i[2]); 
    614                         var condition = query.substring(i[2]+1, lBktIdx); 
    615                         while(condition && condition.length){ 
    616                                 if(condition.charAt(0) == "@"){ 
    617                                         condition = condition.slice(1); 
    618                                 } 
    619  
    620                                 matcher = null; 
    621                                 // http://www.w3.org/TR/css3-selectors/#attribute-selectors 
    622                                 for(var x=0; x<attrs.length; x++){ 
    623                                         var ta = attrs[x]; 
    624                                         var tci = condition.indexOf(ta.key); 
    625                                         if(tci >= 0){ 
    626                                                 var attr = condition.substring(0, tci); 
    627                                                 var value = condition.substring(tci+ta.key.length); 
    628                                                 if(     (value.charAt(0) == "\"")|| 
    629                                                         (value.charAt(0) == "\'")){ 
    630                                                         value = value.substring(1, value.length-1); 
    631                                                 } 
    632                                                 matcher = ta.getMatcher(attr, value); 
    633                                                 break; 
    634                                         } 
    635                                 } 
    636                                 if((!matcher)&&(condition.length)){ 
    637                                         matcher = (function(cond){ 
    638                                                 if(dojo.render.html.ie){ 
    639                                                         return function(elem){ 
    640                                                                 return elem[cond]; 
    641                                                         } 
    642                                                 }else{ 
    643                                                         return function(elem){ 
    644                                                                 return elem.hasAttribute(cond); 
    645                                                         } 
    646                                                 } 
    647                                         })(condition); 
    648                                 } 
    649                                 if(matcher){ 
    650                                         ff = agree(ff, matcher); 
    651                                 } 
    652  
    653                                 condition = null; 
    654                                 var nbktIdx = query.indexOf("[", lBktIdx); 
    655                                 if(0 <= nbktIdx){ 
    656                                         lBktIdx = query.indexOf("]", nbktIdx); 
    657                                         if(0 <= lBktIdx){ 
    658                                                 condition = query.substring(nbktIdx+1, lBktIdx); 
    659                                         } 
    660                                 } 
    661                         } 
    662                 } 
    663699                if(i[3]>= 0){ 
    664700                        // NOTE: we count on the pseudo name being at the end 
     
    682718                                var ta = pseudos[x]; 
    683719                                if(ta.key == pseudoName){ 
    684                                         matcher = ta.getMatcher(pseudoName, condition); 
     720                                        matcher = ta.match(pseudoName, condition); 
    685721                                        break; 
    686722                                } 
     
    894930                //              potentially optimized variant or if we should try something 
    895931                //              new. 
    896                 (document["evaluate"] && !dojo.render.html.safari) ?  
     932                (document["evaluate"] && !h.safari) ?  
    897933                function(query){ 
    898934                        // has xpath support that's faster than DOM 
    899935                        var qparts = query.split(" "); 
     936                        // can we handle it? 
    900937                        if(     (document["evaluate"])&& 
    901938                                (query.indexOf(":") == -1)&& 
    902                                 (query.indexOf("[") == -1) ){ 
     939                                ( 
     940                                        (true) // || 
     941                                        // (query.indexOf("[") == -1) || 
     942                                        // (query.indexOf("=") == -1) 
     943                                ) 
     944                        ){ 
     945                                // dojo.debug(query); 
     946                                // should we handle it? 
     947                                var gtIdx = query.indexOf(">") 
    903948                                // kind of a lame heuristic, but it works 
    904                                 var gtIdx = query.indexOf(">") 
    905949                                if(      
    906950                                        // a "div div div" style query 
     
    908952                                        // or something else with moderate complexity. kinda janky 
    909953                                        (qparts.length > 3)|| 
     954                                        (query.indexOf("[")>=0)|| 
    910955                                        // or if it's a ".thinger" query 
    911956                                        ((1 == qparts.length)&&(0 <= query.indexOf("."))) 
     
    915960                                        return getXPathFunc(query); 
    916961                                } 
     962                                // return getXPathFunc(query); 
    917963                        } 
    918964 
     
    9641010        var _zipIdx = 0; 
    9651011        var _zip = function(arr){ 
    966                 var ret = new dojo.NodeList(); 
     1012                var ret = new d.NodeList(); 
    9671013                if(!arr){ return ret; } 
    9681014                ret.push(arr[0]); 
     
    9851031                // NOTE: ignores xpath-ish queries for now 
    9861032                if(typeof query != "string"){ 
    987                         return new dojo.NodeList(query); 
     1033                        return new d.NodeList(query); 
    9881034                } 
    9891035 
     
    9921038        } 
    9931039 
     1040        /* 
     1041        // exposing these was a mistake 
    9941042        d.query.attrs = attrs; 
    9951043        d.query.pseudos = pseudos; 
     1044        */ 
    9961045 
    9971046        d._filterQueryResult = function(nodeList, simpleFilter){ 
    998                 var tnl = new dojo.NodeList(); 
     1047                var tnl = new d.NodeList(); 
    9991048                var ff = (simpleFilter) ? getFilterFunc(simpleFilter) : function(){ return true; }; 
    10001049                // dojo.debug(ff);