Perl: 2011年2月アーカイブ

例えば、

「は~い!電話番号は0120-931-931です」

という Unicode(UTF-8) で書かれたテキストがあった時、

use Jcode;

open(IN, "utf8.txt");
$str = join '', (<IN>);
close(IN);

$str = Jcode->new($str)->h2z->sjis;

open(OUT, ">sjis.txt");
print OUT $str;
close(OUT);

という具合に、Jcodeモジュールを使って UTF-8 から Shift_JIS コードへの変換を行うと、結果は、

「は?い!電話番号は0120?931?931です」

みたいに化ける・・・というか、一部の文字が変換出来なくて ? マークに置き換わってる。

「え?Shift_JIS にだって、'~'や'-'はあるじゃん?普通に変換出来るんじゃねえの?」と思うでしょ?それが正常な反応。
そう、何故か Unicode の'~'を、Shift_JIS の'~'に変換するという当たり前のことが出来ないのである。

原因は Unicode の訳の分からない設計、というか実装(の自由度?というか曖昧さ?)にある。

実は、'~'という文字が Unicode(UTF-8)には2つあるのだ。
'WAVE DASH'と'FULLWIDTH TILDE'である。
一方、Shift_JIS の'~'は'WAVE DASH'だけである。'FULLWIDTH TILDE'はない。
つまり、UTF-8 の'~'が'WAVE DASH'として保存されていれば、Shift_JIS に変換する時も何の問題もないのである。

さあ、わかったね。
そう。UTF-8 では、君が画面から'~'を入力したら、それは'FULLWIDTH TILDE'として保存されるのである。
'-'もまたしかり。
Shift_JIS で'-'であれば'MINUS SIGN'なのだが、UTF-8 では'FULLWIDTH HYPHEN-MINUS'なのである。
当然、Shift_JIS には'FULLWIDTH TILDE'や'FULLWIDTH HYPHEN-MINUS'に相当する文字はないので(見た目は'~'や'-'でも、コンピュータ上は別の文字として扱われる)Shift_JIS に変換したところで「変換先のコードがわかんない!」ってことになり ? に置き換えられるのである。(同じ文字でコードが2つあることが問題ではなく、他の文字セット(Shift_JIS や EUC-JP)には'MINUS SIGN'しかないのに、ディフォルトで'FULLWIDTH HYPHEN-MINUS'として保存する Unicode の実装がおかしいという話しね)

じゃあ、どうしたら良いのか?

要は、「まず Shift_JIS に存在する文字に相当する文字に変換して、それから Unicode から Shift_JIS への変換を行う」とすれば良いわけだ。

Unicode のままで、「FULLWIDTH HYPHEN-MINUS から MINUS SIGN に変換」「FULLWIDTH TILDE から WAVE DASH に変換」を行い、それから Shift_JIS に変換するのである。
そうすれば、例えば UTF-8 の'MINUS SIGN'を Shift_JIS の'MINUS SIGN'に変換するだけなので、当然正常に変換は行われる。

具体的には、上記のソースの

$str = Jcode->new($str)->h2z->sjis;

の前に、

$str =~ s/\xEF\xBC\x8D/\xE2\x88\x92/g; # - FULLWIDTH HYPHEN-MINUS→MINUS SIGN
$str =~ s/\xEF\xBD\x9E/\xE3\x80\x9C/g; # ~ FULLWIDTH TILDE→WAVE DASH

という2行を置いてやる。

これで、'~'や'-'が ? に変換されることはなくなるね。
ああ、面倒くせえ、Unicode。(^^;
もちろん頭からお尻まで全部 Unicode で作ってしまえば話は簡単なのだが、世の中にはまだまだ EUC-JP や Shift_JIS で作られたデータも多い。
この妙な例外処理は、まだまだ必要なようだ。

ある、RSSフィード(下の例はAtomフィードだが)からタイトルとリンク先の情報だけを抜き出す処理であれば、Perl で正規表現を使えば以下のように簡単に作成できる。
たった12行だよ。正規表現が簡単に書ける Perl はこういうちょっとした処理を書くと無敵の強さだな。(いや、もっと短く書けるシェルスクリプトはあるかもしれんけど、ソースのわかりやすさが Perl とは雲泥の差であろう。宗教戦争になるからあまり追求せんけど(笑))

use LWP::Simple;

my $feed    = LWP::Simple::get('http://blog.netandfield.com/shar/atom.xml');

for(;;) {
    if ($feed =~ /(<entry>.*?<title>(.*?)<\/title>.*?<link .*?href=\"(.*?)\".*?<\/entry>)(.*)/s) {
        print 'URL: ', $3, "\n";
        print 'Title: ', $2, "\n";
        $feed = $4;
    }
    else {
        last;
    }
}

つーことで、いつもこんな感じで済ませてたのだが、今回は XML::FeedPP モジュールを使ってみた。
他のモジュールとの依存関係がなくて、Perl モジュールをユーザーがインストール出来ない環境でも、XML/FeedPP.pm と XML/TreePP.pm をアップするだけで使えるようなので、それならOKだなっと。
(まあ、うちのテストサーバには普通に CPAN 使ってインストールしたけど:-P)

で、XML::FeedPP モジュール使って上記の処理と同じものを書くと、

use XML::FeedPP;

my $source  = 'http://blog.netandfield.com/shar/atom.xml';
my $feed    = XML::FeedPP->new( $source );

foreach my $item ($feed->get_item()) {
    print 'URL: ', $item->link(), "\n";
    print 'Title: ', $item->title(), "\n";
}

こうなる。

短っ!たった 7行だ。(笑)

ほんま、Perl4 の頃からずっと Perl 使ってるので、(本当にメジャーなもの以外)モジュール使わずついつい自分で処理を書いてしまうんだけど(LWP モジュール使わずにずっと自前の http 通信関数使ってたしな(^^;)、やっぱモジュールは便利だな。

某 WebService に Perl プログラムから SOAP で接続する案件があった。

で、何度やっても VersionMismatch(SOAPエンベロープの名前空間が合わない・・・というエラー)が発生する。

なんか、SOAP::Lite モジュールではよくあることのようで、ググってたら SOAP::Data で明示的に SOAP要素に値、名前を指定してやったらOKになった・・・なんて記述もあったのでやってみたのだが、今度は namespace mismatch なエラーが。
つーか、これも名前空間が合わないってエラーじゃん。

エラー時に Only SOAP 1.1 or SOAP 1.2 messages are supported in the system って言われたので、SOAP v1.1 でずっと通信してたのだが、ここで、SOAP v1.2 での処理に変えてみると・・・

・・・あっけなく上手くいってしまった・・・

例えば、ID,Password を投げてユーザ情報を取ってくる処理なら、

use SOAP::Lite +trace;

my $client = SOAP::Lite->service('https://service.exsample.com/ServiceHandler?wsdl');

# SOAP 1.2 で接続
$client->soapversion('1.2');
$client->envprefix('soap12');
$client->readable('true');

$result = $client->getUserInfo('hogehoge', 'hogepass');

print "Name=" . $result->{'name'} . "\n";
print "Phone=" . $result->{'phone'} . "\n";
print "Address=" . $result->{'address'} . "\n";

みたいな感じ。

まあ、上手くいったので取りあえずはOKなのだが、SOAP v1.1 の時は何がいけなかったのか気になってしまう、45の冬。

このアーカイブについて

このページには、2011年2月以降に書かれたブログ記事のうちPerlカテゴリに属しているものが含まれています。

前のアーカイブはPerl: 2010年7月です。

次のアーカイブはPerl: 2011年3月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。


月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら