Perl のテンプレートエンジン Tenjin の文字化け対策

littlebuddha2010-05-26

Tenjin は他のテンプレートエンジンと比較して動作が速いということで、試しに使ってみた。
しかし、テンプレートに含まれる日本語が文字化けしてしまう。
Tenjin の特徴として、

Ability to set the encoding of your templates is added.

encoding - Another way to set the encoding of your template files (set to utf8 by default).

と謳われており、また Tenjin の設定でも、

$Tenjin::ENCODING = "utf8"; # set the encoding of your template files
                            # to utf8. This is the default encoding used
                            # so there's no need to do this if your
                            # templates really are utf8.

と説明されている。
だから、テンプレートも UTF-8 で記述をしておけば、日本語の取り扱いにも問題がないと思っていた。
しかし、テンプレートに含まれる日本語がどうしても下図のように文字化けしてしまう。

原因

Tenjin のオプションにテンプレートで使われている文字コードを指定できても、内部の処理では文字コードの変換は全くされていないためだ。

対策

手っ取り早い対応をする。
テンプレートを読み込む箇所で、テンプレートの文字コードを指定する処理を Tenjin.pm に「ハードコーティング」してしまう。実際の変更箇所は下記のようになる。
Tenjin.pm

# 下記モジュールを読み込むように追加する
use utf8;
use Encode;

そして、load_cachefle メソッドを編集する。

sub load_cachefile {
  my ($self, $cachename, $template) = @_;

  # my $cache = $template->_read_file($cachename, 1);
  # 上記の処理にコメントを加え、下記の処理に decode 処理を施す
  # 文字コードの指定はテンプレートの文字コードを指定する
  # ここの箇所に修正を加えたのはすぐに正規表現があるため
  my $cache = decode('utf-8', $template->_read_file($cachename, 1));
  if ($cache =~ s/\A\#\@ARGS (.*)\r?\n//) {
    my $argstr = $1;
    $argstr =~ s/\A\s+|\s+\Z//g;
    my @args = split(',', $argstr);
    $template->{args} = \@args;
  }
  $template->{script} = $cache;
}

修正後


このように日本語の文字化けが解消される。

実際に使ったコード。

Perl のコードもテンプレートに用いているコードも、UTF-8で記述している。

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Encode;
use Tenjin;

my $template_file = 'template/sample.html';
my @names = ('更新者A', '更新者B', '更新C');

my $template = Tenjin->new();
my $context = {
    site_name => 'littlebuddha の Blog',
    title => 'Tenjin Template の文字化け対策',
    url   => 'http://www.example.com/archives/20100526/',
    names => \@names,
    date  => '2010-05-26',
};

my $output = $template->render($template_file, $context);
open my $IN, '>', 'result.html';
print $IN encode('utf-8', $output);
close $IN;

テンプレートのコード。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>[= $title =] | littlebuddha のブログ</title>
</head>
<body>
<header>
<h1>[= $title =] | [== $site_name ==]</h1>
<nav>
<ul>
  <li><a href="/">HOME</a></li>
</ul>
</nav></header>
<section>
<article>
<dl>
  <dt>タイトル:</dt>
  <dd><a href="[= $url =]">[= $title =]</a></dd>
  <dt>更新者:</dt>
<?pl foreach my $name (@{${names}}) { ?>
  <dd>[= $name =]</dd>
<?pl } ?>
  <dt>更新日:</dt>
  <dd>[= $date =]</dd>
</dl>
</article>
</section>
<footer><nav>
<ul>
  <li><a href="/">HOME</a></li>
</ul>
</nav>
<p>&copy; Copyrights, All rights resereved.</p>
</footer>
</body>
</html>

テンプレートのキャッシュは次のようになっていた。

my $_buf = ""; my $_V;  $_buf .= q`<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>` . escape_xml( $title ) . q` | littlebuddha のブログ</title>
</head>
<body>
<header>
<h1>` . escape_xml( $title ) . q` | ` . ( $site_name ) . q`</h1>
<nav>
<ul>
  <li><a href="/">HOME</a></li>
</ul>
</nav></header>
<section>
<article>
<dl>
  <dt>タイトル:</dt>
  <dd><a href="` . escape_xml( $url ) . q`">` . escape_xml( $title ) . q`</a></dd>
  <dt>更新者:</dt>
`; foreach my $name (@{${names}}) {
 $_buf .= q`  <dd>` . escape_xml( $name ) . q`</dd>
`; }
 $_buf .= q`  <dt>更新日:</dt>
  <dd>` . escape_xml( $date ) . q`</dd>
</dl>
</article>
</section>
<footer><nav>
<ul>
  <li><a href="/">HOME</a></li>
</ul>
</nav>
<p>&copy; Copyrights, All rights resereved.</p>
</footer>
</body>
</html>
`;  $_buf;