Home > Latest topics

Latest topics > CSS3セレクタとXPathでの表現の対応表

宣伝1。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

宣伝2。Firefox Hacks Rebooted発売中。本書の1/3を使って、再起動不要なアドオンの作り方のテクニックや非同期処理の効率のいい書き方などを解説しています。既刊のFirefox 3 Hacks拡張機能開発チュートリアルと併せてどうぞ。

Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
浅井 智也 池田 譲治 小山田 昌史 五味渕 大賀 下田 洋志 寺田 真 松澤 太郎
オライリージャパン

CSS3セレクタとXPathでの表現の対応表 - Sep 13, 2007

拡張機能勉強会の時に焚き付けられたText Shadowのコード(textshadow.js)を教材にして拡張機能開発のノウハウを解説していくシリーズ。

XPathをノードの検索に活用する方法を紹介したけど、肝心のXPathが書けなきゃ意味がないわけで。でもXPathって、ノードセットがどうとかノードテストがどうとか軸がどうとか修飾がどうとか、いざ勉強しようとしてもこれ専用の用語がやたらたくさん登場してきてものすごく萎える。CSSのセレクタの方が、機能は限られてるけどまだ分かりやすい。CSSのセレクタとXPath式の対応表があればいいのになあ、ということを、だいぶ前から僕は思ってた。

実は何年か前、哀さんのサイト(Black Box)でそういうコンテンツがあったんだけど、移転のゴタゴタか何かで消滅したままになってる。しかも、今「CSS XPath」みたいなキーワードでGoogleで検索してみて上位にくるエントリは、情報が不十分だったり間違いが含まれてたりする。

というわけで、CSS3セレクタ(このエントリを書いた時点ではワーキングドラフト)とXPath式の対応表で、詳細な物を作ってみた。

基本
CSSのセレクタ 対応するXPath式 備考
*(セレクタの最初に書く場合) /descendant::*
EE要素/セレクタの最初に書く場合) /descendant::E Geckoでは、text/htmlなHTML文書だとXPath式内での要素名は大文字で書く必要がある。text/htmlとapplication/xml+htmlの両方に対応させたければ、/descendant::*[local-name()="DIV" or local-name()="div"]のように書く必要がある。
*(子セレクタ、子孫セレクタ、隣接セレクタ、後続セレクタに書く場合) *
EE要素/子セレクタ、子孫セレクタ、隣接セレクタ、後続セレクタに書く場合) E Geckoでは、text/htmlなHTML文書だとXPath式内での要素名は大文字で書く必要がある。text/htmlとapplication/xml+htmlの両方に対応させたければ、/descendant::*[local-name()="DIV" or local-name()="div"]のように書く必要がある。
ここから下の例のXPath式で、頭にEや*がある物は、式の途中に出てくる場合を想定している。式の最初に書く場合は、いずれも/descendant::E/descendant::*と書くことになる。
E[foo] E[@foo]
E[foo="bar"] E[@foo="bar"]
E[foo~="bar"] E[contains(concat(" ",@foo," "), " bar " )] 「空白区切りのリストの中で、対応する値がある」という条件に対して、属性値と検索したい文字列の両方の前後に空白文字を加えた上で、contains()で検索している。
E[foo^="bar"] E[starts-with(@foo, "bar" )]
E[foo$="bar"] E[substring(@foo, string-length(@foo) - string-length("bar") + 1) = "bar"] XPath 1.0にはend-with()が無いので、文字列操作を使う必要がある。XPath 1.0では文字の位置は0からではなく1から始まることに注意が必要。なお、検索対象の文字列が固定なら、- string-length("bar") + 1の部分は- 2のように直接数値を書いてもよい。
E[foo*="bar"] E[contains(@foo, "bar" )]
E[hreflang|="en"] E[@hreflang="en" or starts-with(@hreflang, "en-")]
E:root /E
または
E[not(parent::*)]
E:nth-child(n) E[count(preceding-sibling::*) = n - 1]
E:nth-last-child(n) E[count(following-sibling::*) = n - 1]
E:nth-of-type(n) E[count(preceding-sibling::E) = n - 1]
E:nth-last-of-type(n) E[count(following-sibling::E) = n - 1]
E:first-child E[not(preceding-sibling::*)]
E:last-child E[not(following-sibling::*)]
E:first-of-type E[not(preceding-sibling::E)]
E:last-of-type E[not(following-sibling::E)]
E:only-child E[count(parent::*/child::*) = 1]
または
*[parent::* and last() = 1 and self::E]
または
*[parent::* and last() = 1 and local-name()="E"]
child::は省略することもできる(短縮表記)。
E:only-of-type E[count(parent::*/child::E) = 1]
または
E[parent::* and last() = 1]
child::は省略することもできる(短縮表記)。
E:empty E[not(*) and not(text())]
:link
:visited
[@href and contains(" a A area AREA ", concat(" ",local-name()," "))]
または
[@href and (self::a or self::area)]
要素名を条件にしているので、HTML以外の場合はその文書が使っているXMLの語彙に合わせて書き換える必要がある。:visitedの識別は、JavaScriptなどを使わないと不可能。
E:link
E:visited
E[@href]
E:active
E:hover
E:focus
XPathのみでは再現不可能。JavaScriptなどを使って識別する必要がある。
E:target XPathのみでは再現不可能。JavaScriptなどを使って識別する必要がある。
E:lang(fr) E[ancestor-or-self::*[@lang = "fr" or starts-with(@lang, "fr-")] and count(ancestor-or-self::*[@lang][1]/ancestor::*) = count(ancestor-or-self::*[@lang = "fr" or starts-with(@lang, "fr-")][1]/ancestor::*)] この式ではxml:langのことは考慮していないので、注意が必要。
E:enabled E[(@enabled and (@enabled = "true" or @enabled = "enabled")) or (@disabled and @disabled = "false")]
E:disabled E[(@disabled and (@disabled = "true" or @disabled = "disabled")) or (@enabled and @enabled = "false")]
E:checked E[(@checked and (@checked = "true" or @checked = "checked")) or (@selected and (@selected = "true" or @selected = "selected"))] selected属性についても評価するのは余計かも……
E::first-line XPathのみでは再現不可能。JavaScriptなどを使って識別する必要がある。
E::first-letter XPathのみでは再現不可能。JavaScriptなどを使って識別する必要がある。
E::selection XPathのみでは再現不可能。JavaScriptなどを使って識別する必要がある。
E::before XPathのみでは再現不可能。
E::after XPathのみでは再現不可能。
E.warning E[contains(concat(" ",normalize-space(@class)," "), " warning ")] 属性セレクタの場合と同様に、「空白区切りのリストの中で、対応する値がある」という条件に対して、class属性の属性値と検索したいクラス名の両方の前後に空白文字を加えた上で、contains()で検索している。また、@classにはタブ文字や2つ以上のスペースも登場し得るので、normalize-space()で正規化している。
E#myid E[@id="myid"] 属性名を条件にしているので、HTMLやXULやSVGなど「id属性=ID」となっている言語以外の場合はその文書が使っている言語の仕様に合わせて書き換える必要がある。
E:not(s) E[not(s)]
E F E/descendant::F
E > F E/child::F child::は省略することもできる(短縮表記)。
E + F E/following-sibling::*[1][local-name()="F"]
または
E/following-sibling::*[1][self::F]
E ~ F E/following-sibling::F

以上の基本の例を組み合わせて使うことになる。以下に応用例を示す。

応用
CSSのセレクタ 対応するXPath式 備考
div.note.column /descendant::div[contains(concat(" ",@class," "), " note ") and contains(concat(" ",@class," "), " column ")]
section > h, section > body /descendant::section/child::h | /descendant::section/child::body
または
/descendant::section/child::*[local-name()="h" or local-name()="body"]
または
/descendant::section/child::*[self::h or self::body]
XPath式では、|演算子で複数の式の評価結果の和集合を得られる。また、条件を入れ子にできるので、似た条件のCSSセレクタは一つのXPath式にまとめることもできる。
div p:first-child > em /descendant::div/descendant::p[not(preceding-sibling::*)]/em
:link[href$=".pdf"], :visited[href$=".pdf"] /descendant::*[contains(" a A area AREA ", concat(" ",local-name()," ")) and @href and substring(@href, string-length(@href) - 3) = ".pdf"]
html em:not(.highlight) /descendant::html/descendant::em[not([contains(concat(" ",@class," "), " highlight ")])] Geckoでtext/htmlとapplication/xml+htmlの両方で有効にしようと思ったら、/self::*[local-name()="HTML" or local-name()="html"]/descendant::*[(local-name()="EM" or local-name()="em") and not([contains(concat(" ",@class," "), " highlight ")])]と書く必要がある。
tr:nth-child(even)
tr:nth-child(2n)
/descendant::tr[count(preceding-sibling::*) mod 2 = 1]
tr:nth-child(odd)
tr:nth-child(2n + 1)
/descendant::tr[count(preceding-sibling::*) mod 2 = 0]
tr:nth-last-child(even)
tr:nth-last-child(2n)
/descendant::tr[count(following-sibling::*) mod 2 = 1]
tr:nth-child(3n) /descendant::tr[count(preceding-sibling::*) mod 3 = 1]
tr:nth-child(5n + 2) /descendant::tr[(count(preceding-sibling::*) + 2) mod 5 = 1]

手作業で変換するのがめんどい人は、Text Shadowのコードを抜き出して作ったselector.jsを使って下さいってことで……

XPathをちゃんと使うなら、横着せずにきちんと勉強するのがおすすめです。XPathはちゃんと使えばCSSセレクタでできる範囲よりもずっと柔軟な表現ができますんで。(なので、同じ結果を得られる式でも様々な書き方ができるし、上の表に挙げた例も、これら以外にまだまだ色々な書き方があり得る。)

分類:Mozilla > XUL, , , , , , , , , 時刻:20:30 | Comments/Trackbacks (6) | Edit

Comments/Trackbacks

no title

むかーしxsltごりごり書いてたのを思い出した。
なんつーかXpathって正規表現っぽいよね。
使いこなすと便利だけどやりすぎると意味がわからんよーになるとことかw

Commented by tani at 2007/09/13 (Thu) 23:25:57

no title

'/' が表すのはルートノード (DOM でいう Document) であってルート要素ノード (DOM でいう Document#documentElement) ではありません。よって E:root に対する /self::E というのは誤りで、/child::E としなくてはなりません。また、同様の理由により、*、E に対応するのは /descendant::*、/descendant::E で十分だと思います。

また、属性セレクタに関しては以下がより正しく CSS の指定を再現できると思います。(元のままだと hreflang="end" なども引っかかってしまう)

E[hreflang|="en"] →
E[@hreflang = "en" or starts-with(@hreflang, "en-")]

リンク擬似クラスに関しても、E:link のように要素名を指定しているなら、E[@href] で十分かと思います。*:link に対してなら表の書き方で、かつ E を * に置き換えたものでもいけますが。(厳密に言うと link 要素も加えるべきかもしれません)

さらに、lang 擬似クラスに関しては次の式で表現可能かと思います。(xml:lang も考慮に入れるともっと複雑になりますが)

E:lang(fr) →
E[ancestor-or-self::*[@lang = "fr" or starts-with(@lang, "fr-")] and count(ancestor-or-self::*[@lang][1]/ancestor::*) = count(ancestor-or-self::*[@lang = "fr" or starts-with(@lang, "fr-")][1]/ancestor::*)]

ほかにも、効率がいいかどうかは知りませんが、このような書き方ができますね。

E:nth-child(n) →
/descendant-or-self::node()/child::*[position() = n and self::E] (文書要素も含める場合)
/descendant::*/child::*[position() = n and self::E] (文書要素を含めなくてもいい場合。以下同様)

E F:nth-child(n) →
E/descendant-or-self::*/child::*[position() = n and self::F]

E:nth-last-child(n) →
/descendant::*/child::*[position() = last() - n + 1 and self::E]

E:nth-of-type(n) →
/descendant::*/child::E[position() = n]
/descendant::*/child::E[n]

E:nth-last-of-type(n) →
/descendant::*/child::E[position() = last() - n + 1]
/descendant::*/child::E[last() - n + 1]

E:(first|last)-(child|of-type) も n に 1 を代入することで同様。

E:only-child →
/descendant::*/child::*[last() = 1 and self::E]

E:only-of-type →
/descendant::*/child::E[last() = 1]

*:link *:visited →
*[@href and (self::a or self::area)]

E + F →
E/following-sibling::*[position() = 1 and self::F]

Commented by nanto_vi at 2007/09/14 (Fri) 03:10:58

no title

ご指摘ありがとうございます。さっそく表にいくつか反映させました。

Commented by Piro at 2007/09/14 (Fri) 03:45:51

lang 擬似クラスに対応する XPath 式

xml:lang 属性に関しては lang 関数というそのものずばりの関数があったのですね。元の式にも効率の悪い部分があったので、あわせて以下でいけると思います。

E:lang(fr) →
E[lang("fr") or ancestor-or-self::*[@lang][position() = 1 and starts-with(concat(@lang, "-"), "fr-")]]

Commented by nanto_vi at 2007/09/27 (Thu) 17:09:29

XPath/DOM : Class属性による要素抽出 - クロスブラウザ document.getE

... 車輪の再実装ですがタグ名とクラス名を指定してDOM要素抽出を行なうという用途のための小さなコードを書きました。既存ライブラリを使わない小さなサイズのスクリプトでちょっと...

Trackback from juce6ox at 2007/11/16 (Fri) 13:00:12

自分でAutoPagerize対応のスクリプトを書く簡単な方法

Greasemonkeyで、AutoPagerize対応のスクリプトを自作する時の注意点を2つメモ。 自分はひよっこですが、これからGreasemonkeyスクリプト書いてみようかなという人の助けに少しでもなれば嬉しいです。 継ぎ足されたページに適用する方法 AutoPagerizeで継ぎ足された部分に

Trackback from blooo at 2009/10/12 (Mon) 14:11:42

TrackBack ping me at


の末尾に2014年1月19日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2007-09-13_selector-to-xpath.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2014年1月19日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

オススメ

Mozilla Firefox ブラウザ無料ダウンロード