XPath の基礎 (4) です。

XPath では、CSS ではできないような強力な選択を行うことができます。

これはテキストノードを使用できることがその主な要因であると感じます。

個人的によく使用するのが、「ある文字列が要素に含まれているか」という判断です。
例えば「”マツタケ”という言葉を含む div タグを探す」などです。これは CSS で行うことはできません。

このケースでは、以下のような XPath で選択することができます。

//div[contains(text(),'マツタケ')]

text() は、そのノードの子テキストノードという意味でした。ですから上記で、「すべての div タグのうち、子テキストノードに”マツタケ”を含むもの」を選択することになります。

ところで、上記の XPath では、以下のどちらの div も選択されるでしょうか?

<div>
マツタケの土瓶蒸し
</div>
<div>
  <span>マツタケ</span>の土瓶蒸し
</div>

実は上記の XPath では、2つ目の div は選択されません。text() はあくまで子テキストノードを指すからです。
2つ目の div の子テキストノードは空であり、条件に一致しません。

こういった場合はどうするかというと、以下のようにします。

//div[contains(.,'マツタケ')]

text() を . に変えています。. は、self::node() の省略形です。つまり上記でいえばそれぞれの div を指しています。

これでなぜ2つ目の div も選択されるかというと、

  • 関数呼び出しにおいては、引数が文字列型の場合は文字列値に変換化される
  • 要素の文字列値は、その子孫ノードのすべての子テキストノードを連結したものである

からです。

つまり、contains(.,’マツタケ’) に渡される . は div 要素であり、contains の引数は文字列型なので引数は文字列化され、div 要素を文字列化したものはその div が囲っているすべてのテキストノードを連結したものであり、それはどちらの div も “マツタケの土瓶蒸し” なので、”マツタケ” を含むため条件に一致するということになります。

まとめると、タグのすぐ直下にある文字列だけを狙い撃ちしたいときは text() を使い、タグの下全部を対象にしたい場合は . を使うようにすればよいでしょう。