古い記事
ランダムジャンプ
新しい記事
UTF-8 な文字列から半角N文字分取る方法についてです。EUC-JP なころは「端のビットが1なら2つ、0なら1つ」みたいな深く考えない実装でごまかしてきたのですが、最近はそうも言ってられなく。

同じことを行うモジュールがありそうだけど、練習がてら実装してみました。車輪の再発明とか気にしない!
(とはいえ、ご存知でしたら教えてくださいませ……)

■コード (hkcut.pl):
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use open ':utf8';
binmode STDOUT, ":utf8";
binmode STDIN, ":utf8";

my $n = shift @ARGV;

while (<>) {
    chomp;
    my ($str, $hlen) = get_first_n_hkchars($_, $n);
    my $rest = $n - $hlen;
    my $fill = "#" x $rest;
    print "[$str$fill] from [$_]\n";
}

# 先頭から半角N文字分取る
# 取った文字列と実際の長さ(半角文字数)を返す
sub get_first_n_hkchars {
    my ($str, $n) = @_;
    my $s = "";
    my $slen = 0;
    foreach my $c (split(//, $str)) {
        my $clen = 2;
        if (
            $c =~ /\p{InBasicLatin}/
            or 
            ($c =~ /\p{InHalfwidthAndFullwidthForms}/ and $c =~ /\p{Katakana}/)
            ) {
            $clen = 1;
        }
        last if $slen + $clen > $n;
        $slen += $clen;
        $s .= $c;
    }
    return ($s, $slen);
}

半角カタカナに注意しながらUnicodeブロックで指定。見知らぬ記号・文字でうまくいかないことがありそうだけど、そうなったらそのつどブロック指定で条件式追加していく予定です(適宜追記します)。

プログラム中で定義されてる関数では指定された半角文字数分の文字列を返すのですが、足りない場合や全角文字の途中だったりした場合もあるので、実際の長さ(半角文字数)も返します。上記コードでは不足分を「#」で埋めて表示しています。

■実行例:
% cat hkcut-test.txt
123456
198273
1に3し4ろく
【ひらがなと漢字】
カナカナascii
ハンカクカナabc全角仮名
% ./hkcut.pl 5 hkcut-test.txt
[12#] from [123456]
[198#] from [198273]
[1に3#] from [1に3し4ろく]
[【ひ#] from [【ひらがなと漢字】]
[カナカ] from [カナカナascii]
[ハンカクカ] from [ハンカクカナabc全角仮名]
% ./hkcut.pl 8 hkcut-test.txt
[1234] from [123456]
[19827#] from [198273]
[1に3し4#] from [1に3し4ろく]
[【ひらが] from [【ひらがなと漢字】]
[カナカナas] from [カナカナascii]
[ハンカクカナab] from [ハンカクカナabc全角仮名]
% ./hkcut.pl 13 hkcut-test.txt
[123456#] from [123456]
[198273####] from [198273]
[1に3し4ろく##] from [1に3し4ろく]
[【ひらがなと#] from [【ひらがなと漢字】]
[カナカナascii##] from [カナカナascii]
[ハンカクカナabc全角] from [ハンカクカナabc全角仮名]

ref.
- Unicodeでの正規表現 (ぱせらんメモ)
http://d.hatena.ne.jp/pasela/20081003/ll_unicode
- perlの正規表現でUnicodeブロックを使う (sasata299's blog)
http://blog.livedoor.jp/sasata299/archives/51194035.html

更新履歴


追記140623: get_first_n_hkchars()の仕様変更。「取った文字列と残り半角文字数を返す」を「取った文字列と実際の長さ(半角文字数)を返す」に。

追記140628: fold系のモジュールで対応しているのがありました。
- 文字列の折り返し@Perlにこれ一本! Lingua::JA::Fold: CRUSADER'S ROOM分室
http://blog.cru-jp.com/crusaders_room/2007/04/perllinguajafol_a8f3.html
- CPAN Lingua::JA::Fold
http://cpansearch.perl.org/src/HATA/Lingua-JA-Fold-0.08/Fold.pm
(length_full() で半角文字数を計算してる)