Perl の String::Filter を移植してみた

本当は PHPCon の懇親会 LT とかでネタでやろうと思ってたけど、間に合わなかった…。


サイボウズ・ラボの奥さん(id:kazuhooku さん)が作られた String::Filter という CPAN モジュールがある。
詳細はこちら


これを PHP にビミョー*1に移植してみた。
GitHub - heavenshell/php-string-filter: Porting p5-string-filter to PHP
元のモジュールの動作を簡単に書くと、文章をハッシュのキーに設定された正規表現のルールで分割して、エスケープをするというもの。


String::Filter は Regexp::Assemble というモジュールを使用していて、当然このモジュールは PHP には無い。
これは何をやっているかというと、複数の正規表現をよろしく一つの正規表現にしてくれるらしい。
ソースをチラ見したけど今の自分の実力じゃ移植できる気が全くしなかった。
# 誰か移植してw


とりあえず '|' で正規表現を連結すりゃ良いかーと思いそういう作りにした。
で、次に一つにした正規表現で一致した箇所で文章を分割するという箇所(filter())で、preg_split という関数がありそれを使えば行けると思ったが、期待した動きをしなかった。
# 具体的にはマッチしない単語で分割をした。


しょうがないので preg_match_all() でマッチした単語と、preg_split() でマッチしなかった単語を配列にいれ、それぞれフィルター処理を行うようにした。
マッチした位置をオフセット値として持ってるので配列のキーとして利用し、最終的にはキーでソートをし、一つにくっつけて返している。


無名関数や goto を使用しているので必然的に PHP 5.3 以降じゃないと動かない。
初めてまともに PHP 5.3 で書いたけど、やはり無名関数の存在はありがたい。
# もう PHP 5.2 系には戻れない。

<?php
require_once dirname(__DIR__) . '/src/String/Filter.php';
$list = array(
    'rules' => array(
        'http://[A-Za-z0-9_\-\~\.\%\?\#\@/]+' => function($url) {
            $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
            return sprintf('<a href="%s">%s</a>', $url, $url);
        },
        '(?:^|\s)\@[A-Za-z0-9_]+' => function($value) {
            if (preg_match('/^(.*?\@)(.*)$/', $value, $_)) {
                $prefix = $_[1];
                $user   = $_[2];
                $ret    = sprintf(
                    '%s<a href="http://twitter.com/%s">%s</a>',
                    $prefix,
                    htmlspecialchars($user, ENT_QUOTES, 'UTF-8'),
                    $user
                );

                return $ret;
            }
        },
        '(?:^|\s)#[A-Za-z0-9_]+' => function($value) {
            if (preg_match('/^(.?)(#.*)$/', $value, $_)) {
                $prefix  = $_[1];
                $hashtag = $_[2];
                $ret     = sprintf(
                    '%s<a href="http://twitter.com/search?q=%s"><b>%s</b></a>',
                    $prefix,
                    htmlspecialchars(urlencode($hashtag), ENT_QUOTES, 'UTF-8'),
                    $hashtag
                );
                return $ret;
            }
        },
    ),
    'defaultRule' => function($text) {
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
    }
);

$sf = new \String\Filter($list);
echo $sf->filter('@heavenshell @heavenshell foo@bar http://hello.com/ yesyes <b> #hash') . PHP_EOL;

結果。

@<a href="http://twitter.com/heavenshell">heavenshell</a> @<a href="http://twitter.com/heavenshell">heavenshell</a> foo@bar <a href="http://hello.com/">http://hello.com/</a> yesyes &lt;b&gt; <a href="http://twitter.com/search?q=%23hash"><b>#hash</b></a>'

という訳であまりにも実験的すぎるモジュールなので OpenPear にもあげてない。
誰か添削して下さい…。

*1:preg_ 系の動作が期待した通りに行かなかった。