このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

CSSセレクターを、「現在・過去・未来」の観点から分類する

タイトルの「現在・過去・未来」って、「歴史的な経緯のこと」とか思いましたか? 残念ながら違います。全然別な「現在・過去・未来」です。

http://www.w3.org/TR/css3-selectors/ のAbstractのところに、セレクター仕様は次のような関数を定義すると書いてあります*1

  • expression×element → boolean

もう少し分かりやすく書くと:

  • セレクター式の集合×要素ノードの集合 → 真偽値

ここで、「×」は集合の直積です。

セレクター式を1つ固定すると、文書ツリー(document tree)内の各要素ノードに対して「セレクター式にマッチするかどうか」の真偽値を割り当てることができるわけですね。

文書ツリーを深さ優先再帰的順序でトラバースしながら、要素ノードが「セレクター式にマッチするかどうか」を判断するとしましょう。このとき、判断に必要な情報を次のように分類します。

  1. 今見ているノードの情報(現在の情報)だけで判断できる。ノードの情報とは、タグ名(要素名)と属性のことです。
  2. 既に辿〈たど〉った他のノードの情報(過去の情報)で判断できる。
  3. まだ辿ってない他のノードの情報(未来の情報)が必要となる。

内容:

セレクター構文

セレクターは、単純セレクター(simple selector)の区切り記号なしの列を、コンビネータ(combinator)と呼ばれる区切り記号をはさんで並べたものです。さらに、カンマをはさんでセレクターを並べるとセレクターグループになります。

セレクターの一覧は http://www.w3.org/TR/css3-selectors/#selectors にあるので、これを元に話を進めましょう。

今回は関係ないセレクタ

まず、擬似要素(pseudo-elements)。これ要素じゃないから除外。

  1. E::first-line
  2. E::first-letter
  3. E::before
  4. E::after

動的擬似クラス(dynamic pseudo-classes)と呼ばれるセレクターは、ユーザーエージェントの対話状態に依存するので、ツリーの構造からは真偽判断はできません。これも除外。

  1. E:link
  2. E:visited
  3. E:active
  4. E:hover
  5. E:focus

UI要素の状態(UI element states pseudo-classes)に依存するセレクターも同様です。除外。

  1. E:enabled
  2. E:disabled
  3. E:checked

現在のノードだけを見て判断できるセレクタ

タグ名(要素名)と属性だけで判断できるセレクターは、全称セレクター(これは判断不要)、タイプセレクター、IDセレクター、クラスセレクター、各種属性セレクターです。http://www.w3.org/TR/css3-selectors/#selectors の一覧では、次のように記述されています。

  1. *
  2. E
  3. E#myid
  4. E.warning
  5. E[foo]
  6. E[foo="bar"]
  7. E[foo~="bar"]
  8. E[foo^="bar"]
  9. E[foo$="bar"]
  10. E[foo*="bar"]
  11. E[foo|="en"]

ツリーの構造を見るセレクタ

構造擬似クラス(structural pseudo-classes)と呼ばれるセレクターですね。次のセレクターは、「Eであること」が現在のノードから判断できるなら、あとは兄弟内でのノードの順序を勘定していれば判断できます。

  1. E:nth-child(n)
  2. E:nth-of-type(n)
  3. E:first-child
  4. E:first-of-type

ルート要素(文書ノードではないので注意)は、トラバースの最初に出会うノードなので、「ルートかどうか」は即座に判断可能とみなしてよいでしょう。

  • E:root

次の擬似クラスセレクターの判断(マッチング)は年下の兄弟達を見ないと出来ません。つまり、未来に関する情報を必要とします。

  1. E:nth-last-child(n)
  2. E:nth-last-of-type(n)
  3. E:last-child
  4. E:last-of-type
  5. E:only-child
  6. E:only-of-type

次は子ノードを見ないとわかりません。子ノードも未来の情報です。

  • E:empty

その他のセレクタ

参照のターゲットとなっているノードは、ツリー全体を舐めないと分かりません。現在や過去の情報だけでは全然無理ですね。

  • E:target

言語(自然言語)って、どうやって判断するのかな?

  • E:lang(fr)

論理否定の判断には、もとの条件と同じだけの情報が必要です。:notの内部には単純セレクター(全称、タイプ、クラス、ID、属性、擬似クラス)しか入れられません。:notの入れ子もできません。

  • E:not(s)

文脈を見るセレクタ

コンビネータ記号(空白、大なり、プラス、チルダ)を使うと文脈表現(contextual representations)が作れます。これらの文脈表現の判断は、過去を憶えておく必要がありますが未来の情報を使ってないのですよね。

  1. E F
  2. E > F
  3. E + F
  4. E ~ F

まとめ

以下のセレクター達は、過去と現在の情報だけでノードの真偽判断できます。

  1. *
  2. E
  3. E#myid
  4. E.warning
  5. E[foo]
  6. E[foo="bar"]
  7. E[foo~="bar"]
  8. E[foo^="bar"]
  9. E[foo$="bar"]
  10. E[foo*="bar"]
  11. E[foo|="en"]
  12. E:root
  13. E:nth-child(n)
  14. E:nth-of-type(n)
  15. E:first-child
  16. E:first-of-type
  17. E:not(s)
  18. E F
  19. E > F
  20. E + F
  21. E ~ F

*1:Selectors define the following function: [snip] That is, given an element and a selector, this specification defines whether that element matches the selector.