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 |
データ型の名前が関数として使われたときは、型変換を表すとします。
MicroformatsのhCard, hCalendarからの例を挙げます。
- string(content(".vcard .n .family-name")) -- 個人の姓
- anyURI(attrVal(".vcard a.url", "href")) -- 個人または組織のWebサイトURL
- anyURI(attrVal(".vcard img.photo", "src")) -- 写真画像のURL
- 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つでしょう。
- 要素ノードがあるときはエラー(変換出来ない)とする。
- 要素ノードは無視する(削除してしまう)。
- 再帰的に処理する。
どれがいいかは目的・用途によりけりです。
[/追記]