正規表現を使わないCSS3対応の「属性限定」セレクタ関数

category-icon

 ppBlogでは、不十分ですけどCSS3対応のセレクター関数を実装していたんですが、これを大幅に書き換えました。従来は、XPathが使えるブラウザであれば、CSS3のセレクタをXPath用に変換して、意中の要素を取り出したりしていました(スピード面でなかり有利)。でも、document.querySelectorAllをサポートするブラウザが当たり前になってきたので、少なくともppBlogでは、XPathは不要という結論に達しました(今やFirefoxのためだけに用意してる感じで、しかも次期v3.5では間違いなくquerySelectorAllがサポートされるでしょうから)。

 ppBlogで使っている要素抽出のためのセレクターは、大体以下のような感じです。

  • o("#ID") 系 // IDを持つ要素を取り出す
  • o(".className") 系 // 特定のクラス名を持つ要素を取り出す
  • o("a[rel=edit]") // 属性セレクター系。A要素でrel="edit"のやつを取り出す
  • o("textarea[id^=Page]") // 属性セレクター系。テキストエリア要素でID名がPageで始まる要素を取り出す

とまぁ、こんな感じで、ほぼ頻度順になってて、圧倒的にID名抽出が多いです。なので、別に高機能なセレクター関数は必要ないわけで。XPathへの変換にえらくファイル容量を割いていたんですが、それをばっさり削ったんで、だいぶシェイプアップされました(oParts.js)。

 ただ、属性セレクター(上のリストの下2つ)は、今後、色々使い道がありそうなので、これはスピードも考慮した関数を実装しました。以下のような関数です。正規表現を使っていないので、速度面で有利です。面白いことに、FirefoxよりIE6-8の方が高速に動きます。別に詳しく計測した訳ではありませんが、属性セレクターに限定すれば最速の部類ではないかと思っています(XPathやquerySelectorAllを使わないレガシーな系で)。

// cache = {}; // キャッシュ使うなら
function queryByAttributeSelector(rule){
 var apos = rule.indexOf('[');
 var tag = rule.slice(0, apos); // タグ名を抽出。
 var attr = rule.slice(apos + 1, -1); // 属性を抽出
 attr = (attr.indexOf('][') > 0) ? attr.split('][') : [attr];
 var s = '', e, k, ks, v, z, i = 0, l = attr.length;
 // if(!cache[rule]){ // キャッシュ使うなら
 for(; a = attr[i++];){
  e = (i != l ? ' && ' : '');
  z = a.split('=');
  if(z[1]){
   k = z[0], ks = k.slice(-1);
   v = z[1].indexOf('"')===0 ? z[1].slice(1, -1) : z[1];
   k = k.indexOf('class') !== -1 ? k.replace('class', 'className') : k;
   switch (ks){
    case '^' : k = k.slice(0, -1); s += 'n["'+k+'"].indexOf("'+v+'")===0' + e; break;
    case '$' : k = k.slice(0, -1); s += 'n["'+k+'"].lastIndexOf("'+v+'")===(n["'+k+'"].length - "'+v+'".length)' + e; break;
    case '~' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+' ")===0 || n["'+k+'"].indexOf(" '+v+' ")!==-1 || ((z=n["'+k+'"].indexOf(" '+v+'"))>0 && !n["'+k+'"].charAt(z+"'+v+'".length+1)))' + e; break;
    case '*' : k = k.slice(0, -1); s += 'n["'+k+'"] && n["'+k+'"].indexOf("'+v+'")!=-1' + e; break;
    case '|' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+'-")!= -1)' + e; break;
    default  : s += 'n["'+k+'"]=="'+ v +'"' + e;
   }
  } else {
   s += 'n["'+(a==='class'?'className':a)+'"]' + e;
  }
 }
 /*cache[rule] = s;   // キャッシュ使うなら
 } else {
  s = cache[rule];
 }*/
 var r = [];
 var items = document.getElementsByTagName(tag||'*'), j = 0, n;
 var v = new Function('n', 'return ('+s+')');
 for (;n = items[j++];){
  if(v(n)){
   r[r.length] = n;
  }
 }
 return r;
}

 工夫した点は、indexOfやらlastIndexOfの積極的活用です。split関数は、正規表現を用いない文字列での分割は、用いる場合より何倍も高速です。後、細かい速度の改善については、uupaaさんLinkエントリLink [latest log]が非常に参考になりました。

CSS3の「属性」セレクターをほぼ網羅していると思いますが、とりあえずhttp://mootools.net/slickspeed/Link にある属性セレクターはすべてパスします(ただし、div[class!=made_up]は、CSS3にはなさそうなので未対応)。

 上のソースコメントをみれば分かりますが、キャッシュを有効にするとより速くなります。でも別になくても良いかなと思ってます。細かい点を言えば、A要素のREL属性なんてのは、大文字小文字を区別しないようですが、そこまでは追ってません。要は、ppBlogで動けば良いので。

 ppBlog1.8.0での実装は、上のソースをベースにしたものになっています。

— posted by martin at 09:11 pm   commentComment [0]  pingTrackBack [0]

T: Y: ALL: Online:
Created in 0.0039 sec.
prev
2009.3
next
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31