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使います! ごめんなさい!