テンプレート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