ziguzagu.org

HTML::Split

HTML を指定の文字数で分割するための HTMLS::Split なるモジュールを CPAN に up しましたので、さっくり使い方なんかをつらつら。

なにするモジュール?

最初に書いたとおり、HTML を指定の文字数で分割するためのモジュールです。

携帯端末などで HTMLタグを含むテキストデータが 10KB 未満など、きびしい制限があるデバイス向けに HTML を出力する際には、PC ブラウザで見せている長文をそのまま出力するわけにはいきません(容量オーバーってことできれてしまう)。

そこで、HTML を分割する必要があるのですが、ただ、単純に HTML を分割といっても、

  • タグの途中できれたらどうしようもない
  • A タグのテキストノードなんかが分割されるのはあんまりうれしくない。分割してもいいけど、次のページはどうやってはじめるのさ( なしに突然 がやってくる)?
  • CSS を当てたいので改ページされても DOM ツリーを壊したくない

とかいった問題・課題があります。このモジュールではその辺を考慮して、

  • タグの途中で切れたりしないように
  • 改ページしても DOM ツリーを残す
  • Aタグは終了タグまでをまとめて出す

とかをやりつつ、分割していきます。

基本的な使い方

perldoc や t/01-split.t あたりを見ていただくとつかめるかと思いますが、基本はクラスメソッド1個だけです。

use HTML::Split;

my $html = <<HTML;
<div class="pkg">
<h1>HTML::Split</h1>
<p>Splitting HTML by number of characters.
</div>
HTML;

my @pages = HTML::Split->split(html => $html, length => 50);

こうすると、$html が、

<div class="pkg">
<h1>HTML::Split</h1>
<p>Splittin</p></div>

これと、

<div class="pkg">
<p>g HTML by number of characters.</p></div>

これに分割されます。 p のテキストの途中で改ページされても、続きのテキストは本来いるべきはずの div の中に再び p として現れてくれます。

簡易的な Pager

簡易的なものですが Pager の機能もあったり。t/02-pager.t あたりが参考になるかと。先ほどの HTML をつかって、

my $pager = HTML::Split->new(
    html   => $html,
    length => 50,
);

とすると、

$pager->current_page; # 現在のページ番号を get (先頭は1)
$pager->next_page;    # 次のページ番号を get(ないと undef)
$pager->prev_page;    # 前のページ番号を get(ないと undef)
$pager->text;         # 今のページの text を get
$pager->current_page(2)->text; # 2ページ目に移動しつつ text を get

とかできるようになります。まぁ、なくてもよかったんですが。。。

オレオレ記法のページ末処理

あと、はてな記法(の一部)や、各ブログサービスにある絵文字記法のような、HTML ではない独自の記法がページ分割されてしまうことを避ける仕組みもあったりします。

たとえば、TypePad では以下のような絵文字記法が使われています。

[E:絵文字名]

これは途中で分割されてしまうと、あとで絵文字に変換しようとおもっても変換できません。こういう独自の記法を見つけるために、始まり、終わり、完全、3つの正規表現をしてできるようになってます。

この絵文字記法の例では、

my @pages = HTML::Split->split(
    html        => $html,
    length      => $len,
    extend_tags => [
        {
            full  => qr/\[E:[\w\-]+\]/,
            begin => qr/\[[^\]]*?/,
            end   => qr/[^\]]+\]/,
        },
    ]
);

と指定します。内部的には、

  • とりあえず指定文字数で分割
  • begin に指定された正規表現で現ページ末を検索
  • 見つかったら、end に指定された正規表現で次のページ先頭を検索
  • 見つかったら、その文字列をつなげて、full に指定した正規表現でしらべる
  • 一致したら、次のページにいってしまっていた文字列を、現ページ末にもってくる(次のページ先頭からは削除)。

というような処理をやってます(このへんはもうちょっとシンプルにできんもんかとおもった)。

ToDo

現在のバージョン(0.01)では、

  • 単語の途中で普通に分割されてしまってみっともない
  • 日本語が変なところで分割されてしまって読みづらい

という問題があります。今後はこの辺を改良して、もう少しいい感じに分割できるようにできたらと思ってます。具体的には、

  • 区切り文字を検出して単語途中で分割されないように
  • Text::Mecab を使って自然な形で分割できるように

とか。あとは、空白やら改行をどうするとか、文字数じゃなくてバイト数で切りたいとか、もっとまともな Pager がほしいとかとか…。

ところで

唐突に TypePad の絵文字の例がでてきましたが、このモジュールは TypePad Japan で使われているモジュールがベースとなっています。

来週の YAPC::Asia 2008『OpenSource TypePad Mobile』といセッションで、TypePad で使っているモバイル関連モジュールのオープンソース化とそれにまつわるエトセトラの解説を行う予定ですが、オープンソース化するにあたって CPAN モジュールとして切り出したほうが有用なものはやってまおう、というのがいくつかあったりなかったりします。このモジュールはそのうちのひとつです。

TypePad では多少の改良を加えながら、約1年くらい前(だったっけ…)から使われてますが、今回はそれをベースに TypePad への依存が若干あった絵文字の処理なんかを汎用的にしつつ、CPAN モジュール化してます(TypePad のほうでも近いうちにこれに変えてしまおう)。

まぁ、それなりに運用実績あるよ、ということで、お試しください。。。

(ソースはあとで coderepos のアカウントもらってそっちにいれよう)