テンプレートShift_JISでそれ以外UTF-8の場合
っていうのがなかなかできなくて。
テンプレートファイルはShift_JISなんだけどソースコードやDBはUTF-8でやりたい。
でさらに最終出力は携帯用サイトなのでShift_JISでっていうレアなケースを実現したいと。
テンプレートをUTF-8にさえすればCatalyst::Plugin::Charsets::Japaneseで一発OKなんだけどね。
テンプレートをクライアントが触りたいとかってケースでUTF-8とかわからんからShift_JISでお願いとかって結構あるのでそーゆー場合のお話ですね。
まぁクライアントが触る部分だけをうまく切り離してなんとかするってのが普通のやり方なのかもしれないけどとりあえず。
ってことでまずはShift_JISで書かれたテンプレートを動的にUTF-8に変える必要があります。
それはTemplate::Provider::Encodeでできるわけです。
my $tt = Template->new( LOAD_TEMPLATES => [ Template::Provider::Encode->new({ ie => 'shiftjis', oe => 'utf8' }), ], );
こんな感じで。
Providerを使うことで[%%]タグを展開するよりも前にテンプレートの内容をUTF8に変更できるので文字コードがごちゃまぜ状態にならずにすみます。
でこれをCatalystから使えるようにCatalyst::View::TT::ForceUTF8を参考にCatalyst::View::TT::Encodeってのを作ってみました。
package Catalyst::View::TT::Encode; use strict; use base 'Catalyst::View::TT'; our $VERSION = '0.01'; use Template::Provider::Encode; use Template::Stash::ForceUTF8; use Path::Class; sub _coerce_paths { my ($paths, $dlim) = @_; return () if (!$paths); return @{$paths} if (ref $paths eq 'ARRAY'); unless (defined $dlim) { $dlim = ($^O eq 'MSWin32') ? ':(?!\\/)' : ':'; } return split(/$dlim/, $paths); } sub new { my ( $class, $c, $arguments ) = @_; my $config = {%{$class->config}, %{$arguments}}; # XXX: copied from View::TT if (!(ref $config->{INCLUDE_PATH} eq 'ARRAY')) { my $delim = $config->{DELIMITER}; my @include_path = _coerce_paths($config->{INCLUDE_PATH}, $delim); if ( !@include_path ) { my $root = $c->config->{root}; my $base = Path::Class::dir($root, 'base'); @include_path = ("$root", "$base"); } $config->{INCLUDE_PATH} = \@include_path; } my $encoding = $c->config->{'View::TT'}->{CHARSETS_ENCODING}; $class->config->{PROVIDERS} = [ { name => 'Encode', args => { ie => $encoding->{template} || 'utf8', oe => $encoding->{output} || 'utf8', INCLUDE_PATH => $config->{INCLUDE_PATH}, }, }]; if ( ($encoding->{output}||'') =~ /^utf-?8$/i ) { $class->config->{STASH} = Template::Stash::ForceUTF8->new; } $class->SUPER::new($c, $arguments); } 1;
でYAMLは
name: MyApp View::TT: INCLUDE_PATH: - __path_to(templates)__ CHARSETS_ENCODING: template: shiftjis output: utf8
CHARSETS_ENCODINGという項目を追加してテンプレートの文字コードをtemplateに、出力文字コードをoutputに指定。
これで一旦すべてがUTF8な状態になります。
でここでPCサイトとかならコレでほぼ終了ですが、今回は携帯サイト用ってことで出力をShift_JISにしたいのでCatalyst::Plugin::Charsets::Japaneseを使います。
name: MyApp View::TT: INCLUDE_PATH: - __path_to(templates)__ CHARSETS_ENCODING: template: shiftjis output: utf8 charsets: in: UTF-8 out: Shift_JIS
これで完了。
よし!表示させてみるぞ!とおもって表示させてみたら何故か文字化け・・・。
調べてみるとTemplate::Provider::Encodeの時点では文字化けせず、Catalyst::Plugin::Charsets::Japaneseまでいくと文字化けすることが判明。
さらにでぃーぷに調べていくとどうやらTemplate::Provider::Encodeで文字コードをUTF8に変換した場合にUTF8フラグがたってないせいでCatalyst::Plugin::Charsets::Japaneseの時におかしくなるということが判明。
ってことでTemplate::Provider::Encodeの時点でUTF8フラグ立てる処理を急ぎで対応させてみた。
--- Encode.pm Fri Dec 30 11:42:40 2005 +++ Encode_patch.pm Fri Apr 13 16:23:15 2007 @@ -8,15 +8,18 @@ our $VERSION = '0.02'; our $INPUT_ENCODING; our $OUTPUT_ENCODING; +our $UTF8_ON; sub new { my $class = shift; my $options = shift; - $INPUT_ENCODING = exists $options->{ie} ? $options->{ie} : undef; - $OUTPUT_ENCODING = exists $options->{oe} ? $options->{oe} : undef; + $INPUT_ENCODING = exists $options->{ie} ? $options->{ie} : undef; + $OUTPUT_ENCODING = exists $options->{oe} ? $options->{oe} : undef; + $UTF8_ON = exists $options->{utf8_on} ? $options->{utf8_on} : undef; delete $options->{ie}; delete $options->{oe}; + delete $options->{utf8_on}; return $class->SUPER::new($options); } @@ -27,6 +30,9 @@ if ($INPUT_ENCODING and $OUTPUT_ENCODING) { Encode::from_to($data->{text}, $INPUT_ENCODING, $OUTPUT_ENCODING ); + if ( $UTF8_ON && $OUTPUT_ENCODING eq 'utf8' && !Encode::is_utf8($data->{text}) ) { + Encode::_utf8_on($data->{text}); + } } return ($data, $error);
下位互換性のためにオプションとしてutf8_onフラグがたってる場合のみに_utf8_onを呼ぶようにした。
で同じくCatalyst::View::TT::Encodeもutf8_onを渡すように変更
package Catalyst::View::TT::Encode; use strict; use base 'Catalyst::View::TT'; our $VERSION = '0.01'; use Template::Provider::Encode; use Template::Stash::ForceUTF8; use Path::Class; sub _coerce_paths { my ($paths, $dlim) = @_; return () if (!$paths); return @{$paths} if (ref $paths eq 'ARRAY'); unless (defined $dlim) { $dlim = ($^O eq 'MSWin32') ? ':(?!\\/)' : ':'; } return split(/$dlim/, $paths); } sub new { my ( $class, $c, $arguments ) = @_; my $config = {%{$class->config}, %{$arguments}}; # XXX: copied from View::TT if (!(ref $config->{INCLUDE_PATH} eq 'ARRAY')) { my $delim = $config->{DELIMITER}; my @include_path = _coerce_paths($config->{INCLUDE_PATH}, $delim); if ( !@include_path ) { my $root = $c->config->{root}; my $base = Path::Class::dir($root, 'base'); @include_path = ("$root", "$base"); } $config->{INCLUDE_PATH} = \@include_path; } my $encoding = $c->config->{'View::TT'}->{CHARSETS_ENCODING}; $class->config->{PROVIDERS} = [ { name => 'Encode', args => { ie => $encoding->{template} || 'utf8', oe => $encoding->{output} || 'utf8', utf8_on => $encoding->{utf8_on}, ####この一行を追加 INCLUDE_PATH => $config->{INCLUDE_PATH}, }, }]; if ( ($encoding->{output}||'') =~ /^utf-?8$/i ) { $class->config->{STASH} = Template::Stash::ForceUTF8->new; } $class->SUPER::new($c, $arguments); } 1;
YAMLは
name: MyApp View::TT: INCLUDE_PATH: - __path_to(templates)__ CHARSETS_ENCODING: template: shiftjis output: utf8 utf8_on: 1 charsets: in: UTF-8 out: Shift_JIS
これでちゃんと表示されるようになりました。
がむしゃらに書いてみたんだけどこんな感じでいいのかな・・・
別の方法とかあったりする?ちょっと調べてみたけどそれらしい記事とかなかったもので。
あとTemplate::Provider::EncodeでUTF8フラグを立ててないのは何故なんだろう?
UTF8フラグのこととかをあまり詳しく知らないのでちょっとわかんないですがデフォルトでオンにしちゃえばいいのにとか思うのですが。
まぁとりあえず問題解決ということで。
まだCatalystについてよくわかってないので突っ込み大歓迎です!
2007/04/14 追記 : それTemplate::Provider::Encodingで一応できるよ - Unknown::Programming