HTML-Tinysiz-0.03

テストで HTML の要素をチェックするのに使う目的で作った、単純機能で短い要素選択モジュールです。POD を除く、コード本体は 133 行。 短かいため、テストの一部としてパッケージに含めて配布するのに向いています。

https://gist.github.com/tociyuki/7626735

2013-11-25 V0.03 改訂: セレクタに属性の組み合わせを使えるようにした。
2013-11-25 V0.02 改訂: getall メソッドの戻り値をリストにする。V0.01 では配列リファレンスにしていた。

HTML::Tinysiz->new の引数に HTML の文字列を渡すと、 LISP 界で使われている SXML に類似した配列リファレンスによる入れ子木構造に変換します。 配列リファレンスの第一要素はタグ名、 第二要素はハッシュリファレンスによる属性、 第三要素以降は子ノードで、 平文テキストは単なるスカラー文字列、 入れ子の要素は配列リファレンスになります。 配列リファレンスは、 便宜のためにブレスしてあり、 tagname メソッドがタグ名、 attr メソッドが属性ハッシュリファレンス、 child が子ノードのリストの、それぞれの読み取り専用のアクセッサとして使えます。

要素を選択するのは、 get と getall の 2 つのメソッドです。 両方とも、 単純化した CSS 風のセレクタを与えて要素を選び出します。セレクタに使えるのは、次のいずれかをスペースで区切ったものに限定しています。

    tagname
    tagname#id
    tagname.class
    tagname[attrname]
    tagname[attrname="value"]
    tagname[attrname~="value"]
    tagname[attrname|="value"]
    tagname[attrname^="value"]
    tagname[attrname$="value"]
    tagname[attrname*="value"]
    tagname#id.class[attrname="value"][attrname="value"]
    tagname.class[attrname="value"][attrname="value"]
    tagname[attrname="value"][attrname="value"]

    #id
    .class
    [attrname]
    [attrname="value"]
    [attrname~="value"]
    [attrname|="value"]
    [attrname^="value"]
    [attrname$="value"]
    [attrname*="value"]
    #id.class[attrname="value"][attrname="value"]
    .class[attrname="value"][attrname="value"]
    [attrname="value"][attrname="value"]

    *
    *#id
    *.class
    *[attrname]
    *[attrname="value"]
    *[attrname~="value"]
    *[attrname|="value"]
    *[attrname^="value"]
    *[attrname$="value"]
    *[attrname*="value"]
    *#id.class[attrname="value"][attrname="value"]
    *.class[attrname="value"][attrname="value"]
    *[attrname="value"][attrname="value"]

以下は不可です。

    E:first-child などのコロン系統
    E > F や E + F などの位置関係指定

CSS 1 時代に毛が生えた程度のセレクタですけど、 テストで使う分には十分でしょう。

use HTML::Tinysiz;
use Test::More 'no_plan';

my $doc = HTML::Tinysiz->new(<<'EOS');
<!DOCTYPE html>
<html>
<head><title>Foo</title></head>
<body>
<h1><a href="/wiki/?Foo">Foo</a></h1>
<p>paragraph 1</p>
<p>paragraph 2 <a href="/wiki/?Bar">Bar</a>.</p>
<p class="posted"><a href="/rev/5">2013-11-24 09:19</a></p>
<input name="q" value="Foo" style="display: none" />
</body>
</html>
EOS

my $h1_a = $doc->get('h1 a');
is $h1_a->tagname, 'a';
is $h1_a->attr->{'href'}, '/wiki/?Foo';
my $posted_a = $doc->get('.posted a');
is $posted_a->tagname, 'a';
is $posted_a->attr->{'href'}, '/rev/5';
is_deeply [$posted_a->child], ['2013-11-24 09:19'];
ok defined $doc->get('input[name="q"][value="Foo"]');

my @all = $doc->getall('a');
is $all[0]->attr->{'href'}, '/wiki/?Foo';
is $all[1]->attr->{'href'}, '/wiki/?Bar';
is $all[2]->attr->{'href'}, '/rev/5';