Perl内部文字列をバイト数でカットしてみる

以前に、UTF-8文字列をバイト数でカットした時の末尾の処理というのを考えたのですが、この記事はUTF-8文字列をPerlの内部コードとしてではなく、単なるバイト列として扱っていたものでした。しかしUnicodeを内部文字列としてサポートしたPerlのことを考えると、将来的にはあまりよろしくない方法のように思えてきました(今更ではありますが)。

実際、文字列をPerlの内部文字列(utf8フラグの付いた文字列)として扱うと、lengthsubstr等の関数や正規表現などでもバイト単位でなく文字単位で操作することができて便利です。ただ出力する際に文字数ではなくデータ長(バイト単位)で制限したいこともあるので、内部文字列として扱いつつバイト数でカットする方法はないかと考えてみました。

で、色々試してみて、そのようなサブルーチンを比較的短く書くことができました。

use strict;
use Encode;
use utf8;

#binmode STDOUT, ":utf8";
binmode STDOUT, ":encoding(shiftjis)";

my $str = 'ユニコード (Unicode) 3.0 文字集合は 16 ビットのコード空間を占める';
my $enc = 'utf-8';

my $length = length(encode($enc, $str));
for (my $i = $length; $i >= 0; $i--) {
  my $out = strcutbytes($str, $i, $enc);
  print "$out\[EOT\] <= ${i}B\n";
}

sub strcutbytes {
  my ($arg, $len, $enc) = @_;
  $enc ||= 'utf8';
  my $count = 0;
  my $result = '';
  while ($arg =~ /(.)/g) {
    $count += length(encode($enc, $1));
    last if ($count > $len);
    $result .= $1;
  }
  $result;
}

こうして書いてみると、サブルーチンに渡す文字コード名を変更すれば、UTF-8だけでなくEUC-JPやShift_JISで出力する場合にも対応できるわけで、やはり文字列は内部コードとして扱った方が柔軟に対応できるなと認識したのでありました。

参考: Perl 5.8.x Unicode関連