Kanasan.JS Prototype.js CodeReading #5
2008-06-20


// this.expression に CSS セレクタ
// (この例では "#foo") が収められている。
//
// ps は CSS セレクタの各トークンに
// マッチする正規表現を収めた連想配列。
// ps = {
//   tagName: /^\s*(\*|[\w\-]+)(\b|$)?/,
//   id:      /^#([\w\-\*]+)(\b|$)/,
//   ...
// }
//
// c は CSS セレクタの各トークンに相当する
// 処理を行うソース文字列を収めた連想配列。
// c = {
//   tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;',
//   id:      'n = h.id(n, r, "#{1}", c);      c = false;',
//   ...
// }
//
// h はここでは使用しない。(消し忘れか?)
var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
    c = Selector.criteria, le, p, m;

// すでに同じ CSS セレクタから関数を
// 生成したことがあれば、それをそのまま使う。
if (Selector._cache[e]) {
  this.matcher = Selector._cache[e];
  return;
}

// 関数のソース文字列を収める配列。
// これは this.matcher ではなく
// ローカル変数を使ったほうがわかりやすいかも。
this.matcher = ["this.matcher = function(root) {",
                "var r = root, h = Selector.handlers, c = false, n;"];

while (e && le != e && (/\S/).test(e)) {
  le = e;
  // 正規表現を収めた連想配列をなめていく。
  // i には "tagName"、"id" といった文字列が入る。
  for (var i in ps) {
    // i == "id" のとき、p には正規表現オブジェクト
    // /^#([\w\-\*]+)(\b|$)/ が入る
    p = ps[i];
    if (m = e.match(p)) {
      // "#foo".match(/^#([\w\-\*]+)(\b|$)/)
      // が成功すると、ソース文字列に c["id"]、すなわち
      // 'n = h.id(n, r, "#{1}", c); c = false;'
      // を追加する。ここで、Template を使うことにより
      // #{1} が m[1]、すなわち最初の後方参照に
      // 置換されるので、最終的にソース文字列には
      // 'n = h.id(n, r, "foo", c); c = false;'
      // が追加される。
      this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
        new Template(c[i]).evaluate(m));
      // CSS セレクタから正規表現のマッチ部分を
      // 削除する。マッチ部分は "#foo" なので、
      // この場合 e は空文字列になる。
      e = e.replace(m[0], '');
      break;
    }
  }
}

この、正規表現オブジェクトを収めた連想配列をなめていき、マッチしたらそれに対応する文字列なり処理なりを別の連想配列から引き出すという流れは、CSS セレクタから XPath 式を作成するときなどにも使われています。

正規表現オブジェクトのメソッド

正規表現オブジェクトには、文字列を引数にとり、正規表現パターンがその文字列にマッチしたかを真偽値で返す test メソッドがあります。文字列の match メソッドとは異なり、マッチが成功したとき、結果を収めた配列を作成するコストがかかりません。

/(a)(b)(c)/.test("abcde");
// => true

"abcde".match(/(a)(b)(c)/);
// => abc,a,b,c
// マッチが成功すると、最初の要素にマッチした
// 文字列全体、続いて各後方参照の結果を
// 収めた配列が返る。失敗すると null が返る。

なお、Prototype.js では、test メソッドの別名として match メソッドを定義しています。

/(a)(b)(c)/.match("abcde");
// => true
// Prototype.js を使った場合のみ。

また、Firefox や Opera では正規表現オブジェクトを関数のように呼び出せます。ゴルフのときなんかお世話になりますね。これは exec メソッドを呼び出したのと同じに扱われ、次のマッチ部分を探します。グローバルフラグ付きの正規表現に対しては、Perl でいうところのスカラコンテキストにおける m 演算子と同じように動作します。

# Perl
$str = 'abc';
while ($str =~ m/(.)/g) {
  print $1, "\n";
}
# => a
# => b
# => c
// JavaScript
var str = "abc";
var re = /(.)/g;
var match;
// Firefox/Opera では re(str) でも OK。
while ((match = re.exec(str)))
  print(match[1]);
// => a
// => b
// => c

compileXPathMatcher (2757 行目)

CSS セレクタから XPath 式を生成します。CSS セレクタと XPath での表現の対応表が参考になるかもしれません。


続きを読む
戻る
[JavaScript]
[Web 関連技術]

コメント(全0件)
コメントをする


記事を書く
powered by ASAHIネット