Perl で utf8 文字列を byte サイズで split する
utf8 な文字列を特定のバイトサイズで切り分ける処理って Perl でどう書けば一番良いのかを長いこと考えてた(あまり困ってはなかった)んだけど、UTF-8文字列をバイト数でカットした時の末尾の処理 見たら簡単に書けた。
#!/usr/bin/perl use strict; use warnings; use Encode qw( is_utf8 decode _utf8_on ); require bytes; my $utf8 = decode( 'euc-jp', "この日本語テキストは euc-jp で書かれていますが utf8 に変換されます" ); my @splited = byte_split( $utf8, 12 ); binmode STDOUT, ':utf8'; printf "utf8 string = %s\n", $utf8; for my $str (@splited) { print $str, "\n"; printf "\tutf8 length = %s\n", length $str; printf "\tbyte length = %s\n", bytes::length($str); } sub byte_split { my $str = shift; my $byte_length = shift; return unless is_utf8($str); return unless $byte_length; return unless $byte_length > 2; my @result; my @strings = unpack "C*", $str; while (@strings) { my @spliced = splice @strings, 0, $byte_length; my( $round, $remainder ) = round_utf8( pack "C*", @spliced ); _utf8_on($round); push @result, $round; if ($remainder) { my @remainder_list = unpack "C*", $remainder; @strings = ( @remainder_list, @strings ); } } return @result; } sub round_utf8 { my $str = shift; my $remainder; if ( $str =~ /[\x00-\x7F]$/ ) { return $str; } if ( $str =~ s/([\xC0-\xFD])$// ) { $remainder = $1; } if ( $str =~ s/([\xE0-\xFD][\x80-\xBF])$// ) { $remainder = $1; } if ( $str =~ s/([\xF0-\xFD][\x80-\xBF]{2})$// ) { $remainder = $1; } # $str =~ s/[\xF8-\xFD][\x80-\xBF]{3}$//; #4バイト余った場合 # $str =~ s/[\xFC-\xFD][\x80-\xBF]{4}$//; #5バイト余った場合 return ( $str, $remainder ); }
実行するとこんな感じ
% perl test.pl
utf8 string = この日本語テキストは euc-jp で書かれていますが utf8 に変換されます
この日本
utf8 length = 4
byte length = 12
語テキス
utf8 length = 4
byte length = 12
トは euc-j
utf8 length = 8
byte length = 12
p で書か
utf8 length = 5
byte length = 11
れていま
utf8 length = 4
byte length = 12
すが utf8
utf8 length = 8
byte length = 12
に変換さ
utf8 length = 4
byte length = 12
れます
utf8 length = 3
byte length = 9スクリプトを EUC-JP で書いたので中で utf8 に変換しています。unpack, pack を使っているので処理も速い(と思う)。Lingua::JA::Jtruncate は EUC, sjis, jis しか対象にしてないんだよなあ。他に CPAN module で似た処理をするやつってあるんですかね。