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

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

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

参照用 記事

CSSセレクタによるデータ抽出

HTML文書のDOMツリーから、CSSセレクタによりデータを取り出す操作が必要だと感じました。例を見れば(分かる人は)分かるでしょうが、Microformatsと関係があります。[追記 date="翌日"]多少の修正と追加をしました。[/追記]

最初に、取り扱うデータの型を決めておきます。

  • Element -- DOMの要素ノード
  • ElementList -- 要素ノードのリスト(配列)
  • NodeList -- 要素に限定しないDOMノードのリスト
  • string -- 文字列
  • selector -- CSSセレクターである文字列
  • name -- 名前(HTML属性名)である文字列
  • 他に整数、真偽値、nullなど。

DOM関係以外のデータ型の名前は http://www.w3.org/TR/xmlschema-2/ に従うことにします。

次に、基本的な操作を関数として定義します。形は関数ですが、ひとつのDOMツリーを暗黙の入力データとして持つので、メソッドと考えた方がいいかもしれません。これらの関数は(例えばJavaScriptなら)jQueryのようなライブラリを用いれば容易に実装できるでしょう。

  • elements(selector) -- セレクターにマッチする要素のリスト。空リストかもしれない。
  • element(selector) -- セレクターにマッチする最初の要素。マッチする要素がないならnull。
  • attrVal(selector, name) -- element(selector) で表される要素の、nameという名前の属性の値。
  • content(selector) -- element(selector) で表される要素の内容。
  • childElements(selector) -- element(selector) で表される要素の子要素のリスト。

表の形にまとめると:

関数の名前 引数の型 戻り値の型
elements selector ElementList
element selector Element OR null
attrVal selector, name string OR null
content selector NodeList
childElements selector ElementList

データ型の名前が関数として使われたときは、型変換を表すとします。

MicroformatshCard, hCalendarからの例を挙げます。

  1. string(content(".vcard .n .family-name")) -- 個人の姓
  2. anyURI(attrVal(".vcard a.url", "href")) -- 個人または組織のWebサイトURL
  3. anyURI(attrVal(".vcard img.photo", "src")) -- 写真画像のURL
  4. dateTime(attrVal(".vevent abbr.dtstart", "title")) -- 催しの開始日時

次のルールで値を真偽値とみなすことにします。

  • null, [](空リスト), ホワイトスペース文字しか含まない文字列は偽とみなす。
  • その他は、まー常識的に。

[]や" \n " を偽とみなすのは、多くのプログラミング言語の習慣とは違うので注意してください。

さらに、短絡的評価を行う論理演算子 orelse を導入します。すると、次のような例も記述できます。splitは文字列処理関数です。

  • decimal(content(".geo .latitude") orelse split(attrVal("abbr.geo", "title"), ";")[0]) -- 位置情報の緯度

位置情報のマークアップは次のような2種類があるんですね。

<div class="geo">
  <p>緯度<span class="latitude">0</span></p>
  <p>経度<span class="longitude">71.123456</span></p>
</div>

<p>
  場所は<abbr class="geo" title="0;71.123456">緯度ゼロ、経度71.123456</abbr>です。
</p>

[追記]

個人の姓を抜き出すところで、string(content(".vcard .n .family-name")) という式を使っています。DOMのノードリストを文字列に変換するにはどうしたらいいでしょう? とかいうことは昔さんざんやったなー。随分昔なんで「忘れているか?」と思ったのですが、そうでもないので以下に記しておきます。

文字参照実体参照は展開しておきます。CDATAセクションもテキストノードに置き換えておきます。処理命令(PI)は理解できるものなら処理結果で置き換え、理解できないなら削除。コメントノードも削除。すると、テキストノードと要素ノードの並びになります。テキストノードだけなら、そのノード値(これは文字列)を連接すればいいわけです。問題は、要素ノードが混じっているときです。選択肢は次の3つでしょう。

  1. 要素ノードがあるときはエラー(変換出来ない)とする。
  2. 要素ノードは無視する(削除してしまう)。
  3. 再帰的に処理する。

どれがいいかは目的・用途によりけりです。
[/追記]