漢字かな混じり平文表組み

平文表組み作成モジュールは CPAN に幾種類も登録されているものの、漢字かな混じりで飾り気のない左詰め専用テーブルは需要が少ないのか、 はたまた Unicode::EastAsianWidth を使って簡単に記述できるためなのか、これといったものが見当たりません。

試しに Text::SimpleTable に近い使い勝手のものをでっちあげてみました。非破壊操作形式のオブジェクトになっているので、ヘッダ部分を保持するオブジェクトを作っておいてから、データ集合ごとの表示をする使い方ができます。

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use Encode;
BEGIN { $Unicode::EastAsianWidth::EastAsian = 1; } # あいまいな幅の文字はフル幅とする
use Text::TableEastAsian;

my $th = Text::TableEastAsian->new
    ->row('名前', '種類')
    ->hr;
my $table = $th->row('身かきにしん', '乾物 (魚介類)')
               ->row('だいこん', '野菜 (根菜)');
print encode_utf8 $table->output;

これを実行すると、次のような表組の出力が得られます。

名前         種類         
------------ -------------
身かきにしん 乾物 (魚介類)
だいこん     野菜 (根菜)  

パブリックなメソッドは 3 つだけです。

  • row メソッドは行を追加したオブジェクトを作って返します。
  • hr メソッドは水平区切り行を追加したオブジェクトを作って返します。文字列を引数に渡したときは、それの一文字目で区切り線を作ります。これは半幅文字でなければなりません。
  • output メソッドは出力を返します。

row と hr でオブジェクトを作るとき、表組みルーチンをその都度呼び出すように書いています。表組みは、2 パスでおこないます。1 パス目で全行のカラム幅を求めて、2 パス目で空白をおぎないながら出力を組み立てます。

package Text::TableEastAsian;
use strict;
use warnings;
use List::Util qw(max sum);
use Unicode::EastAsianWidth;
use Class::Accessor::Fast qw(moose-like);

has input  => (is => 'ro', isa => 'ArrayRef');
has output => (is => 'ro', isa => 'Str');

sub hr {
    my($self, $hr) = @_;
    return $self->new({%{$self}, 'input' => [@{$self->input || []}, $hr || '---']})->_render;
}

sub row {
    my($self, @data) = @_;
    return $self->new({%{$self}, 'input' => [@{$self->input || []}, [@data]]})->_render;
}

sub _render {
    my($self) = @_;
    my $input = $self->input || [];
    return $self->new({%{$self}, 'output' => q()}) if ! @{$input};
    my @width;
    for my $form (@{$input}) {
        next if ! ref $form;
        for my $h (0 .. $#{$form}) {
            my $n = sum map { m/\p{InFullwidth}/msx ? 2 : 1 } split //, $form->[$h];
            $width[$h] = max $n, $width[$h] || 0;
        }
    }
    my $t = q();
    for my $form (@{$input}) {
        if (! ref $form) {
            my $c = substr $form, 0, 1;
            $t .= join q( ), map { $c x $_ } @width;
            $t .= "\n";
            next;
        }
        for my $h (0 .. $#{$form}) {
            my $n = sum map { m/\p{InFullwidth}/msx ? 2 : 1 } split //, $form->[$h];
            $h and $t .= q( );
            $t .= $form->[$h] . (q( ) x ($width[$h] - $n));
        }
        $t .= "\n";
    }
    return $self->new({%{$self}, 'output' => $t});
}

1;