2010-09-03
Template中のURLを自動で賢くアンカーテキストにしたい
まだText::XslateでもText::MicroTemplateでもなくTemplate::Toolkitを主に使っているわけですが。。
やりたいこと
テンプレート中に出てくるURLを自動でアンカーテキストにしたい。
http://example.com/
が自動的に
<a href="http://example.com/">http://example.com/</a>
になってほしい。このはてなダイアリーでURL書くだけでhttp://example.com/みたいに自動でリンクになるように。
Template::Plugin::AutoLink
Template::Plugin::AutoLink - search.cpan.org
というモジュールがあります。これを使えば、期待通りの動作をします。
use Test::More; use strict; use warnings; use Template; use Template::Plugin::AutoLink; my $t = Template->new; $t->process(\'[% USE AutoLink %]'); my $url = 'http://example.com/'; my $text = "$url"; my $template = '[% text | auto_link %]'; ok $t->process(\$template, { text => $text }, \my $output); cmp_ok $output, '=~', qr!<a\s+href="$url">$url</a>!; done_testing;
$target = "hoge $url fuga"のように無関係な文字列が前後にあっても大抵の場合はURLの部分だけを変換してくれます。
ところがhtmlフィルタと組み合わせると
TTは自動エスケープ機能がないので、よくhtmlフィルタを使用します。
use Test::More; use strict; use warnings; use Template; my $t = Template->new; my $text = '<script>alert("こんにちはこんにちは")</script>'; my $template = '[% text | html %]'; # エスケープされて # <script>alert("こんにちはこんにちは")</script> # となる ok $t->process(\$template, { text => $text }, \my $output); cmp_ok $output, 'ne', $text; done_testing;
で、このフィルタとT::P::AutoLinkを併用すると、以下が通らなくなります。
use Test::More; use strict; use warnings; use Template; use Template::Plugin::AutoLink; my $t = Template->new; $t->process(\'[% USE AutoLink %]'); my $url = 'http://example.com/'; my $text = "<$url>"; my $template = '[% text | html | auto_link %]'; ok $t->process(\$template, { text => $text }, \my $output); cmp_ok $output, '=~', qr!<a\s+href="$url">$url</a>!; done_testing;
URLの末尾にそのまま " や < > が繋がっていると、htmlフィルタを通った後が
<http://example.com/>
のようになり、"&"でクエリパラメータとしてURLが続くような文字列になり、T::P::AutoLinkはそれを検知できずに丸ごとアンカーテキストにしてしまい
'<<a href="http://example.com/>">http://example.com/></a>'
という結果になってしまいます。
Text::AutoLink?
では自分でフィルタを作成して使うのが良い?
TTとは別にText::AutoLinkというモジュールがあります。
Text::AutoLink - search.cpan.org
これは中でウマいことparseしてくれるので、
use Test::More; use strict; use warnings; use Template; use Text::AutoLink; my $t = Template->new; my $url = 'http://example.com/'; my $text = qq!"$url"!; my $template = '[% text | html %]'; ok $t->process(\$template, { text => $text }, \my $output); $output = Text::AutoLink->new->parse_string($output); cmp_ok $output, '=~', qr!<a\s+href="$url">$url</a>!; done_testing;
とやると無事に通ります。が、これは中の仕組み上htmlフィルタでエスケープしたものが元に戻ってしまうようで htmlフィルタと組み合わせるにはちょっと微妙かも。
自前で置換する
正規表現を使って、
という変換をテキスト全体に施す。
use Test::More; use strict; use warnings; use Regexp::Common 'URI'; use Template::Filters; my $url = 'http://example.com/'; my $text = qq!"$url"!; my $output = autolink_and_escape($text); cmp_ok $output, '=~', qr!<a\s+href="$url">$url</a>!; done_testing; sub autolink_and_escape { my $text = shift; my $re_uri = qr/($RE{URI}{HTTP})/; my @arr = split $re_uri, $text; for (0..$#arr) { if ($_ % 2) { $arr[$_] = qq!<a href="$arr[$_]">$arr[$_]</a>!; } else { $arr[$_] = $Template::Filters::FILTERS->{html}->($arr[$_]); } } return join '', @arr; }
実行効率は良くないかもしれないけど、このautolink_and_escapeの機能を持つフィルタをTemplate::Pluginで作ってやれば、とりあえずやりたいと思っていたことは実現できそう。
他のTemplateエンジンではどうする?
T::MTやT::Xslateでは、こういうことをやろうとするとどうなるのだろう? デフォルトでhtmlエスケープをするとなると、HTMLタグを敢えて挿入するような処理は向いてなさそうな…?
むしろテンプレートやサーバー側での処理は行わず、表示させてからJavaScriptで変換させるとかいう処理になるのかな…?
はてなダイアリーはどうやってこれを実現しているのだろう?
余談
URLの末尾に"#hoge"とか(フラグメント識別子 fragment identifier というらしい)がついている場合、この部分はRegexp::Common::URIでは無視されてしまう。ので、この部分まで含めたい場合はhttp URIの正規表現に付け足してやる必要があるみたい。
my $hex = q{[0-9A-Fa-f]};
my $escaped = qq{%$hex$hex};
my $uric = q{(?:[-_.!~*'()a-zA-Z0-9;/?:@&=+$,]} . qq{|$escaped)};
my $fragment = qq{$uric*};
my $re_uri = qq{$RE{URI}{HTTP}(?:#$fragment)?};
(参考:http://www.din.or.jp/~ohzaki/perl.htm#httpURL)
追記
@tokuhiromさんからコメントいただきました。ありがとうございます! TMTなどの場合 encoded_string()をつかって
use Test::More; use strict; use warnings; use Text::AutoLink; use Text::MicroTemplate qw/render_mt encoded_string/; my $url = 'http://example.com/'; my $text = qq!<"$url">!; my $template = '<?= encoded_string(Text::AutoLink->new->parse_string($_[0])) ?>'; my $output = render_mt($template, $text); cmp_ok $output, '=~', qr!<a\s+href="$url">$url</a>!; done_testing;
のようにするとdouble encodedにはならない、とのこと。
あれ、でもこの場合 $outputは
<"<a href="http://example.com/">http://example.com/</a> ">
となってしまう。。 URLはaタグで囲むとして、それ以外の先頭、末尾の'<"', '">' はエスケープしてもらって
<"<a href="http://example.com/">http://example.com/</a>">
になってほしいのだけど… ソースをみた限りではencoded_string()つかってもそういう処理は難しそうな…! よくわからなくなってきた! ><
- 20 http://wiki.15cc.net/index.php?Twitter Botの作り方まとめ
- 16 http://www.google.co.jp/search?hl=ja&safe=off&client=firefox-a&hs=gVi&rls=org.mozilla:en-US:official&q=16進数+リスト&aq=f&aqi=&aql=&oq=&gs_rfai=
- 14 http://reader.livedoor.com/reader/
- 10 http://twitter.com/
- 9 http://www.google.co.jp/search?hl=ja&lr=lang_ja&tbs=lr:lang_1ja&q=Objective-C+OAuth&aq=f&aqi=g1&aql=&oq=&gs_rfai=
- 9 http://www.google.co.jp/search?sourceid=chrome&ie=UTF-8&q=twitter+コメント+api
- 7 http://blog.moe-project.com/2010/09/04/re-template中のurlを自動で賢くアンカーテキストにしたい/
- 7 http://www.google.co.jp/search?hl=ja&client=firefox-a&rls=org.mozilla:ja:official&q=PERL+net::twitter+OAuth+timeline&btnG=検索&aq=f&aqi=&aql=&oq=&gs_rfai=
- 7 http://www.google.co.jp/search?q=ChirpUserStreams&ie=utf-8&oe=utf-8&aq=t&hl=ja&client=firefox-a&rlz=1R1GGGL_ja___JP346
- 7 http://www.google.co.jp/search?q=jquery+validate&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja-JP-mac:official&hl=ja&client=firefox-a
