先日職場で、「𡈽」(土に「`」が付いたような文字)の16進数のバイト表現は何になるのか?ということが話題になりました。
どうも、手元のIMEでその文字を変換することができず、インターネットに接続して、ネット上でこの文字を使っているサイトを見つけてコピペしてきたいんだけど、都合でそれをすることができない。スマホからならそのページを見つけることはできるけど、スマホでは16進数のバイト表現を求めることはできない。(専用のアプリでも開発すればできるんだろうけど)
というわけで、そういうのはPHPでサイト上に構築しておいて、スマホから閲覧できるようにしておくのが一番スマートですかね。ということで、PHPでそれを行う方法を調べてみました。どうやら、PHPでは bin2hex() という関数を使えば、簡単に16進表現を得ることができる様子。早速作ってみました。
16進表現を求めたい文字列を入力して下さい。
面白そうなので、UTF-8以外の場合も作ってみました。UTF-8以外の場合、「𡈽」(土に「`」が付いたような文字)やアラビア語 (العربية)やハングル文字(한글)のような、受け入れ不可な文字コードの場合は、"?"(クエスチョンマーク)に変換されるため、"?" の16進表現である 3F が羅列されるようですね。
JIS(ISO-2022-JP-MS)も作りましたが、いわゆる全角文字が入る場合は、その前後に KI-KO コードが挟まるので、16進表現は少し長めになるようです。
ソースコードはこんな感じで。
<P>16進表現を求めたい文字列を入力して下さい。</P>
<form action="h201406a.html" method="post">
<INPUT TYPE="TEXT" NAME='ptext' VALUE='<?php if (isset($_POST["ptext"])) { echo $_POST["ptext"]; } ?>' SIZE='16' /> <INPUT TYPE="SUBMIT" VALUE="送信" />
</form>
<?php
if (isset($_POST["ptext"]) && mb_strlen($_POST["ptext"]) > 0) {
$str = $_POST["ptext"];
$mstr = htmlspecialchars($str); # サニタイジング
echo "<hr>'$mstr' の16進表現は、<br /><br />";
echo "<table border='1'>";
echo " <tr><td>UTF-8の場合</td><td>".strtoupper(bin2hex($str))."</td></tr>\n";
echo " <tr><td>SJIS[CP932]の場合</td><td>".strtoupper(bin2hex(mb_convert_encoding($str,"CP932")))."</td></tr>\n";
echo " <tr><td>EUC-JPの場合</td><td>".strtoupper(bin2hex(mb_convert_encoding($str,"EUC-JP")))."</td></tr>\n";
echo " <tr><td>UTF-16[BE]の場合</td><td>".strtoupper(bin2hex(mb_convert_encoding($str,"UTF-16BE")))."</td></tr>\n";
echo " <tr><td>UTF-16[LE]の場合</td><td>".strtoupper(bin2hex(mb_convert_encoding($str,"UTF-16LE")))."</td></tr>\n";
echo " <tr><td>UCS-4の場合</td><td>".strtoupper(bin2hex(mb_convert_encoding($str,"UCS-4")))."</td></tr>\n";
echo " <tr><td>JISの場合</td><td>".strtoupper(bin2hex(mb_convert_encoding($str,"ISO-2022-JP-MS")))."</td></tr>\n";
echo "</table>";
}
?>
※エラー処理は省略しています。なお、文字列のサニタイジング処理はhtmlspecialchars()を用いています。実際に文字列を表示する処理を扱う際には、これらの必要な処理を組み込むのを忘れないようにして下さい。
PHPによる方法では、サーバ側にPHPの使用が許可されていないと使えません。もうちょっと別の手法として、JavaScriptを用いた方法についても考えておくことにしました。
調べてみると、JavaScriptは文字列のための内部処理としてUTF-16を用いているため、UTF-16の文字コードを16進数として得ることは容易いです。また、URLエンコードの機能を使うことにより、UTF-8の16進コードを得ることも可能です。
ただ、それ以外のコードについては、方法が準備されていないようです。そこで、文字コード変換ライブラリの力を頼ることにより実現してみることにします。
前に書いたPHPによる方法と違い、サーバに送信せずとも、その場で結果を得ることができます。
(サーバに送信する方法でも、Ajaxを使えばもっとスマートになるかも知れないですが、なんだか大げさなのでやりません)
この方法の欠点としては、巨大なコード変換テーブルをクライアント側で処理しなければならないため、巨大なコード変換テーブルを含んだJavaScriptのコードをクライアント側に転送しないと行けないことでしょうかね。それでも、今回使わせて頂いている encoding.js (http://polygon-planet-log.blogspot.jp/2012/04/javascript.html) は、258KB なので、画像を掲載しているサイトの転送量に比べれば十分小さく、十分通用するサイズだと思います。
ソースコードはこんな感じで。
<form name="qt">
<div style="color:black; background-color:#a0ffa0; border: 1px solid gray; width: 100%; margin-top: 2px; padding: 4px">
<P>16進表現を求めたい文字列を入力して下さい。</P>
<INPUT TYPE='TEXT' NAME='qtext' ID='qtext' VALUE='' SIZE='16' /> <INPUT TYPE="BUTTON" VALUE="実行" OnClick="char2code16()" /><br>
<hr />
<INPUT TYPE='TEXT' NAME='qtext2' ID='qtext2' VALUE='' SIZE='16' readonly="readonly" /> の16進表現は、<br /><br />
<table border='1'>
<tr><td>UTF-16の場合</td><td><INPUT TYPE='TEXT' NAME='qtext3_u16' ID='qtext3_u16' VALUE='' SIZE='48' readonly="readonly" /></td></tr>
<tr><td>UTF-8の場合</td><td><INPUT TYPE='TEXT' NAME='qtext3_u8' ID='qtext3_u8' VALUE='' SIZE='48' readonly="readonly" /></td></tr>
<tr><td>SJISの場合</td><td><INPUT TYPE='TEXT' NAME='qtext3_sj' ID='qtext3_sj' VALUE='' SIZE='48' readonly="readonly" /></td></tr>
<tr><td>EUC-JPの場合</td><td><INPUT TYPE='TEXT' NAME='qtext3_ej' ID='qtext3_ej' VALUE='' SIZE='48' readonly="readonly" /></td></tr>
<tr><td>JISの場合</td><td><INPUT TYPE='TEXT' NAME='qtext3_js' ID='qtext3_js' VALUE='' SIZE='48' readonly="readonly" /></td></tr>
</table>
</form>
<script src="h201406a/encoding.js"></script>
<script language='JavaScript'><!--
function char2code16()
{
// テキストボックスから文字列の取得
var str = document.qt.qtext.value;
// まずは取得した文字列をそのまま表示
document.qt.qtext2.value = str;
// Unicodeの16進数へ1文字ずつ変換
var len = str.length;
var i, code;
var buf_u16 = "";
var array = [];
for (i=0 ; i<len ; ++i) {
code = str.charCodeAt(i);
buf_u16 += ("000"+code.toString(16)).slice(-4).toUpperCase();
// 補足:"000"+ ~ .slice() は、表示時の桁数を揃えてゼロパディングするためのテクニックです
// 後の UTF-8以降のコード変換のための準備
// (文字コード変換ライブラリが、arrayを入出力に用いるため)
array.push(code);
}
// 変換結果を表示(UTF16)
document.qt.qtext3_u16.value = buf_u16;
// 変換結果を文字列化(UTF-8)
document.qt.qtext3_u8.value = "";
var utf8_array = Encoding.convert(array, "UTF8", "UNICODE");
len = utf8_array.length;
for(i=0 ; i<len ; ++i) {
document.qt.qtext3_u8.value += utf8_array[i].toString(16).toUpperCase();
}
// 変換結果を文字列化(SJIS)
document.qt.qtext3_sj.value = "";
var sjis_array = Encoding.convert(array, "SJIS", "UNICODE");
len = sjis_array.length;
for(i=0 ; i<len ; ++i) {
document.qt.qtext3_sj.value += ("0"+sjis_array[i].toString(16)).slice(-2).toUpperCase();
}
// 変換結果を文字列化(EUC-JP)
document.qt.qtext3_ej.value = "";
var eucjp_array = Encoding.convert(array, "EUCJP", "UNICODE");
len = eucjp_array.length;
for(i=0 ; i<len ; ++i) {
document.qt.qtext3_ej.value += ("0"+eucjp_array[i].toString(16)).slice(-2).toUpperCase();
}
// 変換結果を文字列化(JIS)
document.qt.qtext3_js.value = "";
var jis_array = Encoding.convert(array, "JIS", "UNICODE");
len = jis_array.length;
for(i=0 ; i<len ; ++i) {
document.qt.qtext3_js.value += ("0"+jis_array[i].toString(16)).slice(-2).toUpperCase();
}
}
//-->
</script>
最近、あまり使わなくなってしまったPerlですが、以前はCGIといえばPerlだと言っても過言ではない時期もあったりとかして、その名残で度々目にすることも多いPerlです。というわけで、Perlでも試してみることにしました。
かなり久々にPerlに触れたような気がします。
ソースコードはこんな感じで。
#!(perlへのパスを記載)
use Encode 'from_to';
use CGI;
# HTTPヘッダ・HTMLヘッダの出力については省略
print "<BODY bgcolor='#ffffff'><b>Perl 文字コードテスト - INASOFT 管理人のふたこと[2014/06]</b><br><br>\n\n";
# postメソッドにより入力したクエリの受け取り
read( STDIN, $buf, $ENV{'CONTENT_LENGTH'} );
# 初期化
my $ptext = "";
my $cgi = CGI->new;
$cgi->charset("utf-8");
# フォームには、必ず "ptext=XXX" というクエリが含まれる
if ( $buf =~ /ptext.*/s ) {
# クエリをアンバサントで分割する。
my @pairs = split(/&/,$buf);
foreach $pair (@pairs) {
# キーと値に分割する(key=value&key=value...)。
my($name, $value) = split(/=/, $pair);
# '+' → ' '
$value =~ tr/+/ /;
# '\r\n' → '\n'
$value =~ s/%0D%0A/%0A/ig;
# 16進数にエンコードされた文字列をデコードする。
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
# クエリをクエリ格納用変数へ格納
if ( $name eq "ptext" ) {
$ptext = $value;
last;
}
}
if ($ptext ne "") {
# サニタイジングしつつ、元の文字列を表示
print "'".$cgi->escapeHTML($ptext)."'の16進表現は…<br><br>\n";
# unpackで16進表現を求め、ucでアルファベット部分を大文字化
$ostr_u8 = uc unpack("H*", $ptext );
print "UTF8の場合:".$ostr_u8."<br>\n";
$buf = $ptext;
# UTF-8⇒CP932(SJIS)文字コード変換
from_to($buf, 'UTF-8', 'cp932');
$ostr_sj = uc unpack("H*", $buf );
print "SJIS(CP932)の場合:".$ostr_sj."<br>\n";
$buf = $ptext;
# UTF-8⇒EUC-JP文字コード変換
from_to($buf, 'UTF-8', 'euc-jp');
$ostr_ej = uc unpack("H*", $buf );
print "EUC-JPの場合:".$ostr_ej."<br>\n";
$buf = $ptext;
# UTF-8⇒UTF-16文字コード変換
from_to($buf, 'UTF-8', 'UTF-16BE');
$ostr_u16 = uc unpack("H*", $buf );
print "UTF16(BE)の場合:".$ostr_u16."<br>\n";
}
}
print "</BODY></HTML>";
16進表記にアルファベット小文字(a~f)が混ざっているのが嫌だったので、アルファベットは大文字化(A~F)しているわけですが、この「uc」といきなり登場しているのが、大文字化の部分ですね。
ネットでググって見つけたのをそのまま書いて、どうしても慣れない表記だなぁと悩んでいたのですが、uc xxx; と書かずに uc(xxx); と書いてしまっても良かったみたいですね。
(print("xxx"); と print "xxx"; みたいなもんか。引数を括弧付で書くか、コマンドラインみたいにスペース空けて書くかの違いみたいな、そんな感じのか)
現在、目下勉強中のRubyです。まだ実用で使ったことは一度もないのですが、日本人ならコレを使っておくべきだろうということで、Rubyによる方法も追記しました。
Rubyで書いたプログラムを外向けに公開するのはこれが初めてですね。
ソースコードはこんな感じで。
#!(rubyへのパスを記載)
require 'cgi' # CGIライブラリを読み込み
require 'nkf'
cgi = CGI.new # 初期化
print cgi.header("type"=>"text/html")
# HTMLヘッダの出力は省略
print "<BODY bgcolor='#ffffff'><b>Ruby 文字コードテスト - INASOFT 管理人のふたこと[2014/06]</b><br><br>\n\n"
ptext = cgi["ptext"]
if ptext != "" then
# サニタイジングしつつ、元の文字列を表示
print "'" + CGI.escapeHTML(ptext) + "'の16進表現は…<br><br>\n"
# unpackで16進表現を求め、upcaseでアルファベット部分を大文字化
print "UTF8の場合:" + ptext.unpack("H*")[0].upcase + "<br>\n"
# 文字コード変換については、すぐに利用できた環境のRubyのバージョンが1.8系だったので、NKFによる文字コード変換を使っています。
# 1.9系では、String::encode が使えます。
# UTF-8⇒UTF-16文字コード変換
ptext_u16 = NKF.nkf('-W -w16', ptext)
print "UTF16の場合:" + ptext_u16.unpack("H*")[0].upcase + "<br>\n"
# UTF-8⇒SJIS文字コード変換
ptext_sj = NKF.nkf('-W -s', ptext)
print "SJISの場合:" + ptext_sj.unpack("H*")[0].upcase + "<br>\n"
# UTF-8⇒EUC-JP文字コード変換
ptext_ej = NKF.nkf('-W -e', ptext)
print "EUC-JPの場合:" + ptext_ej.unpack("H*")[0].upcase + "<br>\n"
# UTF-8⇒JIS文字コード変換
ptext_js = NKF.nkf('-W -j', ptext)
print "JISの場合:" + ptext_js.unpack("H*")[0].upcase + "<br>\n"
end
print "</BODY></HTML>"
さて、ここまでPHP, JavaScript, Perl, Rubyとチャレンジしてきて、これで一段落だろう(まさか、Web環境にgccがあるからって、C++言語を試す気は無い)……と思っていたのですが、ここでまさかの「次はPythonですかね?」というご意見を頂戴してしまいました。
ん? Python って何だ? っていうか、なんて読むの? ピソン?(thなので舌先を噛んでみたりなど)
今まで日常生活の中で Python に触れたことがなかったんですが、さくらサーバのステータスを確認すると、確かに「 Python 」なるものが実行環境に存在しているんですね。なんて読むのが正しいのだろうと思ってWikipediaを見てみたら「パイソン」で、「ニシキヘビ」の英語と同じ読み方ですね。
(似たようなので、パーサジェネレータのbison(バイソン)ってのがありましたっけ。あれは、「yacc(ヤク)より強いから」とかいう理由での凄まじいネーミングだったと聞いていますが、こちらpythonはイギリスのコメディ番組に起因しているんだとか。ちなみにperlに対するrubyのネーミングも、yaccとbisonの関係みたいなもんでしょうかね)
今まで一度も聞いたことも見たこともなかったPythonですが、とりあえずインターネットで仕入れた情報で記述してみました。
ソースコードはこんな感じで。
#!(pythonへのパスを記載)
# -*- coding: utf-8 -*-
import cgi
import os
import binascii
print "Content-Type: text/html\n\n"
# HTMLヘッダの出力
print "<HTML><HEAD><TITLE>Python 文字コードテスト - INASOFT 管理人のふたこと[2014/06]</TITLE></HEAD>"
print "<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>"
print "<meta name='robots' content='noindex,follow'>"
print "<meta name='viewport' content='width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no' />"
print "<BODY bgcolor='#ffffff'><b>Python 文字コードテスト - INASOFT 管理人のふたこと[2014/06]</b><br><br>"
if os.environ['REQUEST_METHOD'] == "POST":
form = cgi.FieldStorage()
# 得られた文字列はUTF-8のはずなので、いったん内部表現(unicode)にして保持
ptext = unicode(form["ptext"].value, 'utf-8')
if ptext != "":
# サニタイジングしつつ入力文字列を表示
# この際、画面出力はUTF-8で行うので、UTF-8化する
print "'"+cgi.escape(ptext.encode('utf-8'))+"'の16進表現は…<br><br>"
# UTF-8の場合
str = ptext.encode('utf-8', 'ignore')
ostr_8 = binascii.hexlify(str).upper()
print "UTF-8の場合:"+ostr_8+"<br>"
# UTF-16の場合
str = ptext.encode('utf-16', 'ignore')
ostr_16 = binascii.hexlify(str).upper()
print "UTF-16の場合:"+ostr_16+"<br>"
# CP932(SJIS)の場合
# ignoreを付けているので、変換不可な文字が現れた場合は無視される
str = ptext.encode('cp932', 'ignore')
ostr_s = binascii.hexlify(str).upper()
print "SJIS(CP932)の場合:"+ostr_s+"<br>"
# EUC-JPの場合
# ignoreを付けているので、変換不可な文字が現れた場合は無視される
str = ptext.encode('euc-jp', 'ignore')
ostr_e = binascii.hexlify(str).upper()
print "EUC-JPの場合:"+ostr_e+"<br>"
# JISの場合
# ignoreを付けているので、変換不可な文字が現れた場合は無視される
str = ptext.encode('iso2022_jp', 'ignore')
ostr_j = binascii.hexlify(str).upper()
print "JIS(iso2022_jp)の場合:"+ostr_j+"<br>"
print "<hr><a href='../h201406a.html#pyt'>戻る</a></BODY></HTML>"
JISにKI-KOコードが付くように、UTF-16に対しては BOM (FFFE) が付くみたいですね。これはビックリ。
ちなみによくよく考えてみたら、Pythonの名前を見たのはこれが初めてではなくて、Boost C++ Libraryのインストール中に幾度となく出てくる名前だったんですね。
Boost.Pythonという非常に強力なライブラリがあって、C++ のクラスや関数をラップする Python モジュールを簡単に書けてしまうものとのこと。今までPythonの存在を気にしていなかったので、脳がこの単語を無視していたようです。
これまで扱ってきた他の言語と異なり、ifなどのブロックなど範囲はインデントで表現されます。スペースは自由記法における見やすさのための存在ではなく、ブロックを表現するための必須要素として登場するというわけで、ここが他の言語と大きく違うところようです。見栄えと実利の一石二鳥が狙える文法ですが、反面、他の言語を習得済みの人が初見でプログラムを書きづらいのかなぁと。とはいえ、インデントそのものですから、初見で把握することは可能ですね。初見でCOBOLを見たときの気分って、こんな感じだったかも。
それにしても、文末にセミコロンを付ける言語と付けない言語が、・・・もう覚えらんない