Gentleちゃれんじ


関数を利用したXPath式

XPath」は、 既に昔の正規表現によるHTMLからの情報抽出を形骸化させるほどに流行っています。 ネット上にも、XPathを紹介しているページが沢山ありますが、ここでは、 普段あまりネット上で見ないXPathの関数を使って、 より強力なXPath式を紹介したいと思います。

レファレンス

  1. テキストノードではなくテキストを取り出す
  2. perlでいうtrを、XPathで使う
  3. テキストを X 文字以上含む要素の取り出す
  4. 特定の拡張子を持つリンクのみを取り出す
  5. まとめ

テキストノードではなくテキストを取り出す

使用する関数の紹介

Function: string string(object?)

(example) string(id('hoge')) = id が hoge の要素内のテキストを返す

案外知られてないですが、要素内にあるテキストをそのまま文字列の形で取り出したい場合は、 string()関数を使います。複数の要素が引数として入力された場合は、その中でも一番最初の 要素のテキストが返ります。引数を省略するとカレントノードのテキストが返ります。

Perlでいうtrを、XPathで使う

使用する関数の紹介

Function: string translate(string, string, string)

(example) translate(id('hoge'), 'abc', 'ABC') 
          = id が hoge の要素内のテキストを aをA, bをB, cをCに置き換えたものを返す

HTMLに対して、XPathを使う場合は、とりわけ使いどころが少ないですが、 Perlのtrと同じ事をXPathのみでできます。 translateの第一引数は変換したい文字列、 第二引数は置き換えたい文字の集合、第三引数は置き換え後の文字の集合になります。

(example) translate('abcdefg', 'acefg', 'XYZ') = 'XbYdZ'

第二引数が第三引数の数より大きい場合、対応する文字がないとき、 その文字は削除されます。第一引数は、手入力だとエラーが起こります。 XMLやHTMLから取り出した文字列を入力しましょう。因に、1つめのexampleでは、 第一引数に要素を入れていますが、XPathの仕様で、 stringが必要な時に要素が渡された場合、string型に変換 (要は、要素内のテキストが対象となる)されます。

応用

(example) //li[translate(string(), '1234567890', 'XXXXXXXXXX') = 'XXX-XXXX-XXXX']
          = 電話番号が記述された li 要素を返す

HTMLでこういう使い方をする事は、稀でしょうが、translateを使う理由としては、 上にあげたような特定の形式にそったものを取り出す場合に使われるでしょう。 頑張れば、電話番号や、メールアドレスをXPath式で取り出す事も可能でしょう。 XMLの場合は、そもそもphone要素とかを作っちゃうと思うので、結局使う事は少ないでしょうが…。

テキストを X 文字以上含む要素の取り出す

使用する関数の紹介

Function: number string-length(string?)

(example) string-length(id('hoge')) = id が hoge の要素内のテキスト長を返す

string-length()関数を使えば、テキスト長を得ることが出来ます。 Unicode stringで数を数えるので日本語でも問題ありません。 但し、気をつけないといけない事があります。 「string-length()は、空白も全てカウントしたテキスト長を返します。」 HTMLは、とりわけ整形の為にインデントを入れるので、 テキスト長が非常に大きくなってしまいます。そこで、次ぎに紹介する関数を併用します。

Function: normalize-space(string?)

(example) normalize-space(id('hoge')) = id が hoge の要素内のテキストを空白、改行を取り除いて返す

normalize-space()関数は、入力されたテキストの 「文の前後の空白、改行を削除し、文中の空白を半角スペース1個に置き換える」 という操作を行います。文の前後だけでなく、文中の空白も取り除かれるので、 整形時に用いたインデントを気にする事がなくなります。
というわけで、これを使えば、空白をほぼ無視したテキスト長を得ることが出来ます。

(example) string-length(normalize-space(id('hoge')))

応用

(example) //p[string-length(normalize-space()) > 100]
          = テキストを 100 文字以上含む p 要素を取り出す

空白、改行を除去したテキスト長が100以上という条件を[]内に入れてやる事で、 テキスト長を制限した要素を取り出すことが出来ます。 因に、normalize-space()関数は引数が無いとカレントノードのテキストを対象にするので、 string()関数を使わなくても大丈夫です。

特定の拡張子を持つリンクのみを取り出す

使用する関数の紹介

Function: boolean contains(string, string)

(example) //a[contains(@href, '.jpg')] = href 属性に .jpg を含む a 要素を返す

contains()関数は、第一引数に入力されたテキスト中に、 第二引数の文字列が含まれるかを確認する為の関数です。 似たような関数にstarts-with()関数があります。

Functon: boolean starts-with(string, string)

(example) //p[starts-with(@id, 'section')] = id 属性の頭文字が section の p 要素を返す

starts-with()関数は、第一引数に入力されたテキストの頭文字が、 第二引数の文字列かどうかを返す関数です。 但し、ends-with()関数はないので、頭文字は調べられても、末尾の文字の確認は出来ません。 (末尾文字の確認は出来ない事はありませんが、なかなか面倒くさいです。)

応用

(example) //a[contains(@href, '.jpg')]/@href
          = .jpg 拡張子のリンクを取り出す
(example) //a[starts-with(@href, 'http://')]/@href
          = 頭文字が http:// のリンクを取り出す
(example) //a[not (starts-with(@href, 'javascript'))]/@href
          = javascriptのダミーリンクを取り除いたリンクを取り出す

contains()関数や、starts-with()関数を使えば、 主に属性の値を制限するのに便利です。XPathには not や and といった 演算も用意されているので、これを併用する事でさらに柔軟に要素を取り出せます。

まとめ

XPathを使う際、よく「//a」みたいな単純なXPathで済ました後、プログラム側で、 条件をかけてフィルタリングを行う事もあると思いますが、 XPath側で簡単にできる操作であれば、 XPathで制限した方がスマートで無駄な処理も減ります。 関数は、ここで紹介したもの以外にも沢山あるので、気になる方は、 「XPath仕様」をご覧になってください。 わりかし短いので、読み易い方だと思います。 まだまだ自分もXPathで出来る可能性を分かりきってはいないですが、 ちょっとした関数を知っておく事で断然プログラミング楽になりました。 是非、関数を使って、パワフルなXPathを作ってみてください。

それでは、今回のTipsはこれまで!

ページのトップへ戻る