北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

Jcode Respect

jcode.plとJcode.pmに感謝の気持ちを込めて。*1

Jcodeにはtr()の機能が付いてますが、ハイフンが入ると弱いようです。っていうか、常々そう思っていたので、ちょっと深く調べてみました。

やりたいこと

電話番号っぽい全角数字+記号を半角にしたい。全角ハイフン(っぽいもの)は3種に対応。括弧は削除でいい。

特に、全角ハイフン(っぽいもの)を半角ハイフンに変換したい。

先に結論

perl5.8.X以降の人は、Jcodeのtrメソッドは使わずきちんとunicode化してtr///を使いましょう。

それ以前のバージョンのPerlを止むなく利用する場合、Jcode.pmやjcode.plのtrメソッドを利用しますが、全角ハイフンの置換だけ他の全角文字と分け、 tr('―−ー', '-') のように置換先の文字列がハイフンだけになるように書くと、ハマらずに済みます。


ユニコード文字列編

まずは、ユニコード文字列で普通にtrを使ってみます。

use utf8;
use strict;
binmode STDOUT, ':utf8';

my $phone = '(0―1ー2−3)';
$phone =~ tr/0-9―−ー()/0-9\-\-\-/d;
print $phone, "\n";
[結果]
0-1-2-3

期待通りの動きです。

Jcode.pm編

じゃあ、次は Jcode.pm してみましょう。実体はEncode.pmのラッパーだから動くかな?

use strict;
use Jcode;
my $phone = '(0―1ー2−3)';
$phone = jcode($phone, 'euc')->tr('0-9―−ー()', '0-9\-\-\-', 'd')->utf8();
print $phone, "\n";
[結果]
Malformed UTF-8 character (byte 0xff) in eval "string" at (eval 6) line 1, <DATA> line 855.
-0\1\23

惨敗orz。tr()のコード見ると、渡された文字列を decode してtr///かけてるんですが、そのときにバックスラッシュをエスケープしているみたいです*2。うまく内部のtr/// へ '\-' を渡す方法は思い浮かびませんでした( ´△`)。

一応解決法はあります。ださいですけど。

use strict;
use Jcode;
my $phone = '(0―1ー2−3)';
$phone = jcode($phone, 'euc')->tr('0-9()', '0-9', 'd')
                             ->tr('―−ー', '-')->utf8();
print $phone, "\n";

Jcode::_Classic編

さて、さらに時代を戻ってみましょう。Jcode::_ClassicのJcode::Trを利用してみます。

use strict;
use Jcode::Tr;
use Jcode;

my $phone = '(0―1ー2−3)';
Jcode::Tr::tr(\$phone, '0-9―−ー()', '0-9\-\-\-', 'd');

print jcode($phone, 'euc')->utf8(), "\n";
[結果]
-0\1\2-3

・・・残念(ノ_-;)。正規表現でハイフン展開をやらせてるんで、「\-\」と「-」と「\」と「-」の4文字で解釈されてるっぽいです。今のJcode.pmと違ってバックスラッシュは完全に無視。

そこで、この完全に無視ってことを逆手に取れば、次のような解決法があります。ダサイですけど。*3

use strict;
use Jcode::Tr;
use Jcode;

my $phone = '(0―1ー2−3)';
Jcode::Tr::tr(\$phone, '0-9―−ー()', '0-9---------', 'd');

print jcode($phone, 'euc')->utf8(), "\n";

jcode.pl編

さらにさらに時代を戻って、jcode.plを見てみましょう。

use strict;
require 'jcode.pl';
use Jcode;

my $phone = '(0―1ー2−3)';
jcode::tr(\$phone, '0-9―−ー()', '0-9\-\-\-', 'd');
print jcode($phone, 'euc')->utf8(), "\n";
[結果]
-0\1\2-3\

やっぱ駄目です。こちらは、「\-」と「\\」をきちんと1文字のasciiとして扱ってるんですが、残念ながらそのルールは「\--/」のようにハイフンでの展開の両辺にしか使われません。

これも、解決できます。さっきのJcode::Trと同等か、もしくは以下のコードです。超ださいですが。

use strict;
require 'jcode.pl';
use Jcode;

my $phone = '(0―1ー2−3)';
jcode::tr(\$phone, '0-9―−ー()', '0-9\--\-\--\-\--\-', 'd');
print jcode($phone, 'euc')->utf8(), "\n";

まとめ

レガシーな部分のコード書き換えるの面倒なので、まだまだJcode.pm使います! ごめんなさい!

*1:Encode.pmもね!

*2:これは、Jcode::_Classicのtrがエスケープ文字を無視する仕様だったからの実装だと思われますが、結果として旧バージョンと動きが一致しなくなっちゃってます。後述するように_Classicのtrもバグってるので、動きが一致しても困りますけど。

*3:Unicode対応のtr/// は裸のハイフンを食わせると Malformed UTF-8 って言いやがるので、この方法は最新の Jcode.pm には使えません。