Catalyst::View::ClearSilver を使いこなす

C言語で書かれた超高速なテンプレートエンジンの ClearSilver ですが、Catalyst 界隈ではあまり使われていないようなので、今回は TT では実現されていることを ClearSilver で実現してみます。

事前準備

ClearSilver をインストールしておいてください。Perl バインディングも忘れずにインストールしておきます。

MyApp/lib/MyApp/View/CS.pm

ビューは TT とまったく同じ方法で作れます。

# cpan Catalyst::View::ClearSilver
$ cd MyApp/script
$ ./myapp_create view CS ClearSilver

MyApp/lib/MyApp/I18N/ja.mo

カタログファイルは po から mo に変換しておきます。

$ cd MyApp/lib/MyApp/I18N
$ msgfmt -o ja.mo ja.po

MyApp/lib/MyApp/Controller/Root.pm

  • カレントディレクトリを File::chdir で一時的に変更しています(これをしないと include 構文が使えません)
  • ウェブアプリケーションの国際化のためにカタログファイルを読み込み、$c->stash->{loc} につっこんでいます
  • リクエストパラメータを $c->stash->{req_param} につっこんでいます
package MyApp::Controller::Root;

use strict;
use warnings;
use base 'Catalyst::Controller';
use File::chdir;

=head2 read_mo($)

read_mo: Subroutine to read and parse the MO file
         Refer to gettext documentation section 8.3

=cut

sub read_mo($) {
    local ($_, %_);
    my ($MOfile, $len, $FH, $content, $tmpl);
    $MOfile = $_[0];
    
    # Avild being stupid
    return unless -f $MOfile && -r $MOfile;
    # Read the MO file
    $len = (stat $MOfile)[7];
    open $FH, $MOfile   or return;  # GNU gettext never fails! ^_*'
    binmode $FH;
    defined($_ = read $FH, $content, $len)
                        or return;
    close $FH           or return;
    
    # Find the byte order of the MO file creator
    $_ = substr($content, 0, 4);
    # Little endian
    if ($_ eq "\xde\x12\x04\x95") {
    	$tmpl = "V";
    # Big endian
    } elsif ($_ eq "\x95\x04\x12\xde") {
        $tmpl = "N";
    # Wrong magic number.  Not a valid MO file.
    } else {
        return;
    }
    
    # Check the MO format revision number
    $_ = unpack $tmpl, substr($content, 4, 4);
    # There is only one revision now: revision 0.
    return if $_ > 0;
    
    my ($num, $offo, $offt);
    # Number of strings
    $num = unpack $tmpl, substr($content, 8, 4);
    # Offset to the beginning of the original strings
    $offo = unpack $tmpl, substr($content, 12, 4);
    # Offset to the beginning of the translated strings
    $offt = unpack $tmpl, substr($content, 16, 4);
    %_ = qw();
    for ($_ = 0; $_ < $num; $_++) {
        my ($len, $off, $stro, $strt);
        # The first word is the length of the string
        $len = unpack $tmpl, substr($content, $offo+$_*8, 4);
        # The second word is the offset of the string
        $off = unpack $tmpl, substr($content, $offo+$_*8+4, 4);
        # Original string
        $stro = substr($content, $off, $len);
        
        # The first word is the length of the string
        $len = unpack $tmpl, substr($content, $offt+$_*8, 4);
        # The second word is the offset of the string
        $off = unpack $tmpl, substr($content, $offt+$_*8+4, 4);
        # Translated string
        $strt = substr($content, $off, $len);
        
        # Hash it
        $_{$stro} = $strt;
    }
    
    return \%_;
}

=head2 end

Attempt to render a view, if needed.

=cut 

sub end : Private {
    my ( $self, $c ) = @_;

    local $CWD = '/home/hoge/public_html/MyApp/root';
    $c->stash->{loc} = &read_mo($CWD . '/../lib/MyApp/I18N/ja.mo');
    $c->stash->{req_param} = $c->request->parameters();
    $c->view('CS')->process($c);
}

MyApp/root/template/sample.cs

テンプレートはこんな感じになります。

<?cs include:"template/header.cs" ?>
<h1>TinyURL <?cs var:loc.New ?></h1>

<?cs if:create.error ?>
<font color="red"><?cs var:create.message ?></font>
<?cs /if ?>
<form name="tinyurl" method="post" action="/tinyurl/create">
<table>
  <tr>
    <td><?cs var:loc.TinyUrl_id ?></td><td><input type="text" name="id" size="25" value="<?cs var:req_param.id ?>"></td>
  </tr>
  <tr>
    <td><?cs var:loc.TinyUrl_disable ?></td><td><input type="text" name="disable" size="25" value="<?cs var:req_param.disable ?>"></td>
  </tr>
  <tr>
    <td><?cs var:loc.TinyUrl_long_url ?></td><td><input type="text" name="long_url" size="25" value="<?cs var:req_param.long_url ?>"></td>
  </tr>
  <tr>
    <td colspan="2" align="center"><input type="submit" name="btn_create" value="<?cs var:loc.Add ?>"></td>
  </tr>
</table>
</form>
<?cs include:"template/footer.cs" ?>