Javascriptのescape関数をperlで使う

 えー、最近この日記がおろそかになっているというか、あまり気合いが入っていないわけですが、その理由の一つとして「ラグナロクに復帰した」というのがあるのです。まあ他にも色々とあるんだけども。で、そっちで遊んでいると当然こっちに割り当てられる時間が減るわけで。それでもそれなりに書いてるつもりですが。

 まあ言い訳はこのくらいにして今回の内容。

 ラグナロクに復帰したことと関連してくるんだけど、固定パーティーで狩りをして出たレアアイテムを売ったり、その他「これ売っといてー」と言われて露店を出して売るのはいいんだけども、毎回売り切れればいいけどそういうわけにも行かず、何をどれだけ預かってそのうちどれだけ売れたのかが分からなくなってくる。また、売り上げの計算をするのもけっこう面倒くさい。

 というわけで、預かったり売ったりしたアイテムを管理する物は何か無いだろうかと。ちょっと調べてみたけれどもそんな変なツールは転がっていない。ならいっちょ作ってみようかということで、cgiとhtml/javascriptを組み合わせて作り始めた。

 まあ全てをcgi…というかperlに任せればいいのかもしれないけど、ローカルで済ませられる処理をわざわざcgiでしなくてもいいだろうということで、perljavascriptを連携させて動作させるようにした。

 ここで問題になったのがパラメータの受け渡し。通常、フォームからcgiにGETするとき、パラメータはurlencodeされた状態で送られる。これは問題ない。でも、javascriptでこのURLを作成するときにはどうすればいいか。当然escape関数を使うはず。

 しかしJavascriptは1.3になってから、このescape関数から返される文字がunicode形式に変更されている。unicodeでも出来るようになった、ということならともかくこれしか使えなくなったというのはちょっと…。

 で、perlで一般的に使われているurldecode方法というのは、そのままの文字コードで1バイトずつに分けてしまうやり方。%01%02…なんてやつね。だからここへescape関数で変換した%u1234なんていうのを渡してもきちんと変換してくれるわけがない。強制的にunicodeに変換されてしまってるし。

 どちらかをどちらかに合わせなければならないわけで、いずれにしろ変換関数を自分で作らなければならない。というわけで自由度が高いとの理由からperlに決定。というかjavascriptで作るのは無理があるような…。

 まずは同じ事を考えている人がいないかと思って適当に検索してみる。でも検索方法が悪かったのか見つけることはできなかった。もう検索するのも面倒だからさっさと作り始めてしまう。

 というわけで出来たのが以下。長い前置きですな。

use Encode;

$str = '%エ1ル2ニ3ウ4ム';

print "main>$str\n";
$str = myEscape($str);
print "main>$str\n";
$str = myUnescape($str);
print "main>$str\n";

exit;


sub myEscape {
	my $str = shift;

	Encode::from_to($str,"shiftjis","UTF-16");

	$str = substr($str,2);		# BOM
	$str =~ s/\x00%/\x00%\x002\x005/g;
	$str =~ s/(?<!\x00)([^\x00]{2})/'%u'.unpack('H4',$1)/ge;
	$str =~ s/\x00([^%0-9A-Za-z_])/'%'.unpack('H2',$1)/ge;
	$str =~ tr/\x00//d;

	$str;
}
sub myUnescape {
	my $str = shift;

	$str =~ s/([\x20-\x7f])/\x00$1/g;
	$str =~ s/\x00%((?:\x00[0-9a-fA-F]){2})/&zeropack($1)/eg;
	$str =~ s/\x00%\x00u((?:\x00[0-9a-fA-F]){4})/pack("H4",&delzero($1))/eg;
	$str = "\xfe\xff$str";		# BOM

	Encode::from_to($str,"UTF-16","shiftjis");

	$str;
}
sub delzero {
	my $str = shift;
	$str =~ tr/\x00//d;
	$str;
}
sub zeropack {
	my $str = shift;

	"\x00".pack("H2",&delzero($str));
}

 Encodeモジュールを使ってるため、5.8以降じゃないと動かんです。多分。Jcodeを使う場合はUnicode::Stringというモジュールを組み合わせないとutf16を扱えないような。そこらへんよくわからんので、まあ適当に検索してどうぞ。

 myEscapeの流れは、まず文字列をutf-16(be)に変換。入力元がshiftjisになってるけどここはまあ適当に。

 で、BOMは要らないから外して % の文字を%25に変換。そして次の行がすこしややこしいけど、前が\x00ではなく、その後に二つ続く\x00では無い文字2バイト分を変換。「幅ゼロの否定後読み」ってやつですな。

 そして、半角文字のうち、URLとしてふさわしくない文字(面倒だから変換する必要の無い物もまとめてやってるけど)を%xxの形に変換。

 最後に\x00を全て削除して変換終了。UTF-16では、全ての文字を2バイトで表現するため、たとえば "0" は "\x00\x35" という物に変換されているため、この不要な\x00を取り除くというわけ。

 今度は逆にjavascript側からCGIへ値を渡すときに必要になるかも知れない、perlで作ったjavascriptのunescape関数。

 まず、通常半角文字として扱われる文字の前に\x00を付けてしまう。

 続いて、%?? となっていた部分の処理。さっきの変換で、ここは\x00%\x00?\x00?という形になっている。そのためこの部分をマッチさせ、そこから\x00を取り除いてpackしている。出てくるのは半角文字のため、前に\x00を付けてunicode形式の半角文字としている。

 %u????についても同様の処理を行う。4バイト分取り込むのと、「前に\x00を付ける」処理がないだけだ。

 そしてBOMを先頭に付けて、ここではshiftjisに変換して終わり。

 本当は%u????や%??の部分ではない文字に対してこの処理を行いたかったんだけど、それを表す正規表現が思いつかなかったんで苦肉の策としてこういう方法を採ることになった。

 もっとエレガントな方法があるのかもしれないけど、これしか思いつかなかったし、そんなに長くもないからまあ良しとしてくださいな。