正規表現を使わない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]

この記事に対する TrackBack URL:

設定によりTB元のページに、こちらの記事への言及(この記事へのリンク)がなければ、TB受付不可となりますのであらかじめご了承下さい。

コメントをどうぞ。 名前(ペンネーム)と画像認証のひらがな4文字は必須で、ウェブサイトURLはオプションです。

ウェブサイト (U):

タグは使えません。http://・・・ は自動的にリンク表示となります

:) :D 8-) ;-) :P :E :o :( (TT) ):T (--) (++!) ?;w) (-o-) (**!) ;v) f(--; :B l_P~

     
T: Y: ALL: Online:
Created in 0.0063 sec.
prev
2021.9
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