タグ「Unicode」が付けられているもの

正規化を使いたくても

あるときJavaプログラムで、入力されたテキストデータに含まれる所謂「全角・半角」の区別を無視したいことがありました。例えば「RAM」という言葉が含まれていたら、所謂「全角」であろうが「半角」であろうがそれは同じ文字なので、重複符号化のせいにすぎないそういう非本質的な区別は無視したいわけです。

本当ならこれぞまさに正規化処理の出番で、JavaではUnicode正規化が簡単に使えるようになっているのですが、しかしUnicodeの正規化仕様はちょっと使いづらいところがある。

というのは、通常の正規化形式であるNFC, NFDはいずれも、「全角・半角」の区別をそろえてくれないので私の目的の役には立たない。一方、NFKC, NFKDはというと、「全角・半角」をそろえてくれるのはいいのだけど、三点リーダがピリオド3つになってしまうとか、記号類について余計なお節介をしてくれて何がどうなるのか正直私も覚えてないので、できれば触りたくない。

そのときどうしたかというと、入力データがもっぱら言語表記用の文字であって、記号類はほぼなかったので、NFKCを使いました。互換漢字もなかったのでその辺の問題もない。

入力データの性質・内容や、自分がそこで何をしたいのかという目的・用途によって、どういう措置をとるのがいいのか考える必要があります。

一般的にいって、日本語処理上では、正規化処理は次のようになっているのが使いやすいと考えます。

  1. 英数字はいわゆる半角にそろえる
  2. 片仮名はいわゆる全角にそろえる
  3. 結合文字は使わない方に(つまり合成済みの符号位置に)そろえる
  4. それ以外の余計なことはしない

最初の2つは、要するにJIS X 0208:1997で(ASCII/JIS X 0201と共用する場合に)いっていることと同じ話す。残りの2つは、つまるところ、JIS X 0208/0213に対応する符号位置はそのままいじらずに残しておいてくれよということです。

UnicodeにはUnicodeなりの方針があるのでしょうが、それが結果として使いやすくなっているかは一概にいえないものがあると思います。

BOM 付き UTF-8のトラブル

入力としてUTF-8のテキストファイルをとるJavaプログラムでうまくいかないことがありました。

テキスト形式で入力されたデータを処理するプログラムなのですが、ファイル中に存在するはずのデータがないといってエラーになる。

テキストエディタで開いても、ExcelやLibreOffice Calcで開いてみても、ファイルに異常は見当たらないし、問題のデータもきちんと記述されているようにしか見えない。

実はこのエラーの原因は、入力のテキストファイルにBOMが付いていることでした。BOMがどういうものかは『プログラマのための文字コード技術入門』をご覧ください。

Javaで書かれた処理プログラムがUTF-8のテキストを読み込む際に、BOMを消費せずに単なるUnicode文字のように扱うため、1行目のデータの先頭にゴミが付いた状態になっていたのです。それで、見えないゴミ付きのデータになってしまい、意図どおりに動作しないし、テキストエディタで開いてみても異常に気付かなかったわけです。

JavaのライブラリでUTF-8のテキストを読み込むとき、先頭にBOMがあると、それを単なるU+FEFFという符号位置の文字データとして読み込んでしまいます。本来のBOMは、Byte Order Markという名前のとおり、バイト順を示す役割のものであって、文字を符号化したデータの一部ではありません。UTF-16/32と違ってバイト順の問題のないUTF-8にBOMを付けるべきかという問題は議論が分かれるところかもしれません。しかし現実にはBOM付きUTF-8は生成されることがあります。

ウェブ検索で「java utf-8 bom」などとしてみると、BOM付きUTF-8をJavaで読み込んでトラブルになった話が複数出てきたり、また互換性のためにJavaではUTF-8読み込み時の挙動を変える予定はなさそうだという話が出てきたりします。

自分で対策をするしかないようです。

ちなみに、iconvのUTF-8も同じようにデータ先頭のBOMを単なる文字として扱うということを、以前当ブログの記事に書きました。

2つ前の記事では、ScalaプログラムからShift_JIS-2004のテキストデータを読み込んでUTF-8にして出力してみました。これで、Shift_JIS-2004を読み込めることが分かったわけです。

では、Shift_JIS-2004で出力する方はどうか。おそらく問題ないのでしょうけど、一応やってみないことには、どんな落とし穴があるか分かりません。

文字コードとしてShift_JIS-2004を指定してファイルを読み込むよう開き、1行読み込んではまたShift_JIS-2004として別のファイルに書き出してみます。内部コードがUnicodeなので、いったんUnicodeに変換したのちに元のSJISにするということになります。往復変換 (round-trip conversion) がうまくいくかを見たいわけです。

import scala.io.Source
import java.io.PrintWriter

val inputFile = Source.fromFile("hoge.sjis.txt", "x-SJIS_0213")
val outputFile = new PrintWriter("hoge-out.sjis.txt", "x-SJIS_0213")

try {
    for (line <- inputFile.getLines) {
        outputFile.println(line)
    }
} finally {
    inputFile.close
    outputFile.close
}

今回は前回と違って行番号なしで、元のまま出力します。これを実行した後で、

diff hoge.sjis.txt hoge-out.sjis.txt

とすると、差分なし、つまり綺麗に元通りに戻ったということでした。

念のため、常用漢字人名用漢字のCSVのSJIS版について同じことをして、うまくいくことを確認しました。

全部の文字を試したわけではないけれども、問題の起こりそうなポイントはおさえているので、多分大丈夫でないかと思います。

ちなみに、Shift_JIS-2004に対応している言語はほかにはPythonやGaucheがあります。これらはEUC-JIS-2004にも対応しています。また、OSでいうとMac OS XはShift_JIS-2004に対応しており、OS添付のテキストエディタでも扱うことができます。

Scala と Java 7 で Shift_JIS-2004を読む

Java 7ではShift_JIS-2004への対応が追加されています(OracleのサイトのSupported Encodings参照)。前から気付いてはいたのですが、今まで何となく試す機会がないままいました。

文字コードの名前として "x-SJIS_0213" という文字列で登録されています。文字コードを指定できる場面でこの文字列を指定してやればOKです。ご承知のようにJavaの内部コードはUnicodeなので、この場合の対応というのはUnicodeとのコード変換が実装されているということです。

Javaで対応したということは、Scalaでも使えるんだろうと思って、Scalaで簡単なプログラムを書いて試してみました。

// ファイルからテキストを読み込んで別のファイルに行番号をつけて出力する
import scala.io.Source
import java.io.PrintWriter

val inputFile = Source.fromFile("hoge.sjis.txt", "x-SJIS_0213")
val outputFile = new PrintWriter("hoge-out.utf8.txt", "UTF-8")

try {
    var i = 1
    for (line <- inputFile.getLines) {
        outputFile.println(i + ": " + line)
	i += 1
    }
} finally {
    inputFile.close
    outputFile.close
}

hoge.sjis.txtという名前のShift_JIS-2004のファイルを読み込んで、行番号をつけた上でUTF-8にしてhoge-out.utf8.txtというファイルに出力します。

試しに作ったhoge.sjis.txtを同じディレクトリに置いておいて scalaコマンドで上のプログラムを実行すると、hoge-out.utf8.txtが作成されます。Scala 2.10.0とOpenJDK 1.7.0をインストールしたUbuntu Linux 12.10で試しています。

出力ファイルを見たところでは期待通りに動作しているようです。以下のようなファイルができます。

1: test file.
2: あいう
3: αβ
4: 鷗     # 第3水準、BMP、一般名詞
5: 米芾   # 第3水準、BMP、人名
6: 褚遂良 # 第3水準、BMP、人名
7: 蔡邕   # 第3水準、BMP、人名
8: 𩸽  # 第4水準、SIP、一般名詞
9: 渞  # 第4水準、BMP、名字
10: 氵  # 第4水準、BMP、字体記述要素
11: 剝離  # 第3水準、BMP、サ変名詞、JIS2004追加、常用漢字
12: 𠮟咤  # 第3水準、SIP、サ変名詞、JIS2004追加、常用漢字
13: 神  # 第3水準、BMP、互換漢字
14: ㇰ  # アイヌ語用片仮名
15: ㇷ゚  # アイヌ語用片仮名、要結合文字
16: 〜  # WAVE DASH U+301C

なお、Java 6でも、途中のUpdateから対応しています。現時点の最新版では使えるはずです。

個人的には、JIS X 0213の符号化方式としてはEUC-JIS-2004に対応してくれた方が嬉しいのですが、SJISももちろん必要な措置ではあります。

国旗の絵文字はどうなったのか

プログラマのための文字コード技術入門』を書いたときにはUnicodeの携帯絵文字はまだ標準化作業の途中だったので、その時点での情報を記しておきました。最終的にできた仕様は本に書いた時点の案とは違うものになっています。ここで簡単にふれておきましょう。

国旗の絵文字を表すために、"Regional Indicator" という特殊な記号を使います。U+1F1E6からU+1F1FFまでの範囲に、例示字形としては点線で囲まれたAからZまでのアルファベットが用意されています。このアルファベットを使って、例えば日本なら「JP」というISO 3166の国コードを表す。つまり符号位置としてはU+1F1EF U+1F1F5という列になります。絵文字に対応した環境でこの列を表示すると、JPすなわち日本の国旗の絵文字を出力する、という具合です。

HTMLの文字参照で書けば、日本の国旗は「&#x1f1ef;&#x1f1f5;」ということになります。実際にこのブログ記事のHTMLの中に書けば「🇯🇵」になるのですが、見えるでしょうか。iPadやMacでは見えるようです。iPod Touch第2世代では点線に囲まれたJとPが見えていて、国旗としては出力しないけれどもregional indicator自体は認識されているような見た目です。全く見えない環境も多いでしょう。まあ、あまり気に病むことはありません。

Unicodeの仕様書を見ると、別にどの国旗に対応してないといけないと規定されているわけではないし(それを言い出したらいろいろ大変でしょう)、枠組みは用意しておいたのであとはよろしく、といった感じです。Unicodeコンソーシアムのサイトでダウンロードできるcode chartにはこのregional indicator symbolについての説明があるのですが、とても弱気な感じが味わい深い。

In some emoji implementations, certain pairs may be recognized and displayed by alternate means; for instance, an implementation might recognize F + R and display this combination with a symbol representing the flag of France.

someとかmayとかmightとかいってて、これ書いた人は本当は実装してほしくないんじゃないかとさえ思えてきます。

今日は2月11日、建国記念日にちなんだお話でした。(?)

Javaのメソッド名に中黒は使えるか

プログラムの中の変数名やメソッド名、関数名、クラス名といった識別子には英数字のみを使うことが多いですが、Java言語では漢字や平仮名・片仮名などを使うこともできます。変数名を「年齢」だとかメソッド名を「賞与計算」だとかいう風にすることもできるわけです。

Java言語仕様の中で識別子に使える文字が規定されています。もっとも、使える文字の一覧がこの言語仕様書に示されているわけではなく、実際にはCharacter.isJavaIdentifierStart(int) と Character.isJavaIdentifierPart(int) がtrueを返す(前者が識別子先頭、後者が2文字め以降に対応)文字という風に、Characterクラスに丸投げしています。

どんな文字が使えるのかはAPIリファレンスのCharacterクラスの上記メソッドの説明にざっくり書かれています。簡単にいえば、記号類でなく言語表記のための文字はなんでもいけます。ラテン文字はもちろん、ギリシャ文字、キリル文字、漢字、平仮名、などなど。数字も(2文字め以降は)もちろん大丈夫。

分かりにくいのは記号類です。通貨記号はいいとして、「連結句読点文字」(connecting punctuation character)というあまり一般的と思われない用語が素っ気なく使われています。例として '_' が挙げられていて、これが「連結句読点」かといわれればそんな気もしますが、ほかに何が該当するのかこれで分かる人は少ないのではないでしょうか。ここで意味するところはUnicodeDataでいうカテゴリConnector Punctuation (省略形Pc)に相当するらしく、ほかにはU+203F UNDERTIEなど少数があるようです。

さて、この前ちょっと調べ物をしているとき、これに関連して、興味深いブログ記事をふとしたはずみで見つけました。

Java 6まではJIS X 0208にある中黒(中点)「・」をメソッド名の中に使えていたのに、Java 7になったらコンパイルエラーになった。これはどうしたことだと。

このブログで説明されているところでは、Javaの言語仕様が変わったわけではなく、Unicodeの文字カテゴリの定義で、中黒は上で触れた「Pc」として以前は分類されていたのに、Java 7の対応するUnicodeのバージョンでは「Po」(Other Punctuation)に変更になったと。このため、以前は中黒を識別子に使うことができたのに、Java 7ではできなくなったというのです。このブログ記事は大変丁寧に調査・説明されています。

文字コード表の例示字形に変更がなくても、属性の定義の変更によって、影響があり得るわけですね。

ちなみに、Java 6の文字情報はUnicodeバージョン4.0、Java 7は6.0に基づいています。APIリファレンスのCharacterクラスのところに記載があります。また上記ブログによると、中点のカテゴリがPcからPoに変更されたのはUnicode 4.1だそうです。

JIS X 0208/0213の波ダッシュ「〜」(面区点1-01-33、SJIS 8160)はUnicodeではU+301C WAVE DASHが相当します。ですが、これをU+FF5E FULLWIDTH TILDEにうつしてしまう実装があって、以前から問題になっています。

以前のWindowsに同梱のフォント、Windows XPまではそうだったと思いますが、MSゴシックなどではWAVE DASHのグリフが通常とは位相が逆になったような形になっていて、表示されると違和感がありました。これはUnicode仕様の例示字形がそうなっていたためですが、Windows Vista以降に同梱のフォントでは一般的な波ダッシュの形に直りました。

ただ、FULLWIDTH TILDEも同じような(あるいは全く同じ?)デザインになっているので、どちらのコードが入力されているのか、見た目に区別がつきません。

それで思うのですが、U+FF5E FULLWIDTH TILDEのグリフを、上付きというか、上に寄った形にできないものでしょうか。

そもそもtilde (チルダ、チルド、等)というのは、「ñ」のようにアルファベットの上につけられる記号です。ですから、上寄りにすることには十分な理由があります。

使っているフォントにもよるでしょうが、私のEmacs環境では、WAVE DASHは一般的な波ダッシュの形で、FULLWIDTH TILDEは全角幅で上寄りの形で表示されます。これなら、自分が適切なコードを入力しているかどうかがはっきり分かるわけです。これが良い。

MS Pゴシックやメイリオ、IPA PゴシックなどのU+FF5Eのグリフだけ差し替えるようなプログラムとかあればいいんじゃないかなと思うのですが、そういう素敵なものを作っている方はいないでしょうかね。

Unicode 6.2

先週、Unicode 6.2というのが出ました。

文字の追加は1つだけ、トルコの通貨リラの記号U+20BAだけだそうです。

いまになって追加されたというなら、これまでどうしてたのかという疑問がわいてきますが、この記号は今年の3月から使われ始めた新しい記号なのだそうです。

近年トルコは、ヨーロッパにもアフリカにもアジアにも近い地理的条件や、着実な経済成長、平均年齢が若く、人口が多くて(7500万人)なお増加中、などの要因で注目を集めています。

もともと日本との関係は良好で、モスクや宮殿、カッパドキアの奇岩などの観光地もよく知られています。歴史的にも興味深い所です。この機会にトルコに注目してみるのもいいですね。

Unicode Standardを見ていたら、"Tabe 15-6. Japanese Era Names" (p. 533)という表で、目が違和感を検知しました。

U+337B    SQUARE ERA NAME HEISEI    1989-01-07 to present day

いやいやいや、平成になったのは1月8日でしょう。

そう思って下の行を見るとこうなっています。

U+337C    SQUARE ERA NAME SYOUWA  1926-12-24 to 1989-01-06

終端は1月7日でしょう、というのは上の平成のと連動した話ですが、念のため辞書(広辞苑)で昭和の始まりの日を確かめてみたら、1926年12月25日となっています。1日違う。

では大正・明治はどうか。

U+337D    SQUARE ERA NAME TAISYOU    1912-07-29 to 1926-12-23
U+337E    SQUARE ERA NAME MEIZI       1867 to 1912-07-28

明治のはじめだけ扱いが雑なのはおいとくとしても (陰暦のせい?)、辞書では明治の終わりは1912年7月30日、大正は1912年7月30日から1926年12月25日となっています。

微妙にずれているのは何なのでしょう? 私が何か勘違いしている? うーん...。

Version 6.1のPDFと、5.0の紙版で確認しました。

文字数制限

この前、ちょっと古めかしい感じのするウェブアプリを使ったら、テキスト入力欄の脇に「100バイトまで」のような注記が添えられていて、なんともいえない気持ちになりました。

まあ、意図するところはわかります(少なくとも私には。そして多分これをお読みの貴方にも)。わかるけれども、もうさすがにこれはないんじゃないか。

これは、「コンピュータで使える文字には1バイトのハンカクモジと2バイトのゼンカクモジとがある」という、古き良き時代の、今では通用しない発想に基づいているのでしょう。

そもそも原則的なことをいえば、1文字が何バイトかなんていうことは、利用者は知りようがない。かつては、全角に見える文字は2バイト、半角に見える文字は1バイト、という概念があったわけですが、それは過去のある時期のコンピュータの実装にすぎないわけです。

用いる文字コードによって、同じ文字でも要するバイト数は異なります。念のため具体例を見てみましょう。

  • 「亜」はUTF-16では2バイト、UTF-8では3バイト、UTF-32では4バイト
  • 「÷」はISO/IEC 8859-1では1バイト、EUC-JIS-2004では2バイト
  • 「𩸽」は、Shift_JIS-2004では2バイト、EUC-JIS-2004では(SS3制御文字こみで数えると) 3バイト、UTF-{8|16|32}では4バイト

UTF-8やUTF-16が盛んに使われている現在、1文字何バイト、と、単に文字の種類だけから決め打ちにすることはできません。

さらに、Unicodeの場合は結合文字があるので話がややこしくなります。「が」というのは普通は1文字と数えますしUnicodeにもそれ用の符号位置U+304Cがありますが、一方で、「か」U+304Bと合成用濁点U+3099の2つの符号位置の列でもって「が」を表すことも可能です。UTF-16でいえば、「が」に対応するバイト列が2バイトかもしれないし、4バイトかもしれないということです。

こうなると、「何バイト以内」を人に数えさせるというのは全く意味をなさなくなります。

(余談ながら、Unicode 2.0の仕様書では、第1章の1行目で高らかに「The Unicode Standard is a fixed-width, uniform encoding scheme」とうたいあげており、同じページにはUnicodeの特徴を表す図として「Wide ASCII」とキャプションの付いた図を掲げて、いかにもUnicodeが16ビット固定長、16ビット版のASCIIであるかのように印象付けていますし、2.2 Unicode Design Principlesという節では「Sixteen bit characters: Unicode character codes have a uniform width of 16 bits.」と断言しています。ちなみにこのバージョンではサロゲート領域が定義されています)

利用者に見せる文言として「何バイト以内」という書き方はさすがにあまりしないにしても、データの格納領域の都合などで制限をしたいことはあるでしょう。そういうときは「何文字以内」のように書きたいわけです。しかし上記のようにUnicodeでは見かけの文字数とバイト数との間に明確な対応関係はないので、ASCIIとJIS X 0208だけ考えていればよかった昔のようにはいきません。

Twitterはご存じのように「140字以内」といっているわけですが、ここに同様の問題があります。これに関して、興味深い投稿を見かけました。

チベット文字はインド系文字に分類され、中心となる字の周囲にいろいろ文字や記号をくっつける構成をとります。その小さな構成要素のひとつひとつがUnicodeでは合成用の符号位置であるわけです。なので、1文字分の幅のところに3つも4つも符号位置が組み合わさっていることがあります。そのため、140文字といっても、チベット語を書くにはずいぶん少ない。中国語はあんな複雑な漢字が「1文字」扱いされてるのに...という不満です。(興味のある方は、上のリンク先のウェブページの中のチベット文字をテキストエディタなどにコピーして、バックスペースキーで1文字ずつ消してみたりすると面白いでしょう)

私の記憶では、以前Twitterの入力欄は、残り文字カウントが「140」のところに「𩸽」を入力すると一気にカウントが2減って「138」になりました。サロゲートを使う文字は2文字分に数えられていたわけです。しかし今では、この字は「1文字」ぶんとしてカウントされるようになっています。改良したのでしょうか。

しかし結合文字を使う場合は、やはり、1つの符号位置が「1文字」扱いされているように見えます。それで上記のチベット文字の不満も出てくるわけです。日本語の関係でいえば、鼻濁音の「か゚」のような字、これはUnicodeでは合成用の半濁点を使って2つの符号位置で表すのですが、これもTwitterでは「2文字」扱いになります。本当に「人が見て認識する1文字」単位にするには、結合文字の扱いを考慮する必要があるわけです。なお、チベット文字の「1文字」がチベット語話者の立場から通常どのようにとらえられているのかは私には分かりません。

片仮名と正規表現

あるウェブサイトで、文字列が片仮名かどうかを判定する正規表現として /^[ァ-ヶ]+$/ のようにしているのを見て、あれれと思いました。(「ヶ」はそもそも片仮名かという問題はありますが、それはここでは除外)

分かってやっているのならいいですが、これでは長音の「ー」が含まれません。JIS順でもUnicode順でも、「ー」はこの範囲に含まれません。JIS X 0208/0213では、片仮名が5区にあるのに対し「ー」は1区28点にあります。Unicodeでは、「ァ」がU+30A1、「ヶ」がU+30F6なのに対して「ー」はU+30FCですから上記の範囲に含まれません。Unicodeの場合は連続したKatakanaブロックの中にあるので、例えば [ァ-ヾ] (ここで「ヾ」はU+30FE)とすると「ー」も含まれることになります(この場合は中点「・」も含むことになります。それが適当かどうかは用途次第)。

もっというと、Unicodeの場合、濁点・半濁点に結合文字を使うと、上のやり方ではマッチしなくなります。「ザ」を表すのに単一の符号位置U+30B6でなく、「サ」U+30B5と合成用濁点の連続を用いていたというケースです。合成用濁点はU+3099、合成用半濁点はU+309Aと、Hiraganaブロックにあるためです。結合文字なんて使わないと思われるかもしれませんが、どこかからコピペして入力したときに、コピペ元がたまたまNFDで正規化されていたなんていうことはあり得ることです。

さらに、上の正規表現では、JIS X 0213で追加されたアイヌ語表記用の片仮名も外れています。アイヌ語なんて使わないと思われるかもしれませんが、用途としては別段アイヌ語に限定されてはいません。以前書いたように(「JIS X 0213の小書きの仮名」)、韓国語の発音を片仮名で表すのにもある程度使えますし、また例えばイタリア語のCampanellaという名前を「カㇺパネㇽラ」と書くことも考えられなくはありません。この「ㇺ」「ㇽ」などは、UnicodeではU+31F0からU+31FFのKatakana Phonetic Extensionsにあり、コード範囲は基本的な片仮名と連続していません。

どこかのサイトに書いてあったことを鵜呑みにするのでなく、用途や状況に応じて詳しく検討する必要があります。

これからの文字コード

細かい背景などを抜きにして結論だけ言ってしまうと、これからの文字コードの使い方は次のように考えればいいのではないかと思います。ただしここではファイル等に用いる外部コードを問題にしており、内部コードは対象外とします。

  1. 特に理由が無ければUTF-8を使う。
  2. ただし、EUCの符号構造が必要なときはEUC-JIS-2004を使う。
  3. ただし、SJISの符号構造が必要なときはShift_JIS-2004を使う。

UTF-8といっているのは、ソフトウェアの国際展開を考えたらUnicodeを使うのが最も手っ取り早いからという理由が大きい。私は別にUnicodeを礼賛するつもりはないし色々問題を持ってはいますが、現時点では仕方ないと思います。Unicodeの符号化方式でも、外部コードとして使うならUTF-16などでなくASCII互換のUTF-8の方が扱いやすいだろうということです。

また単にUTF-8といっても対応度合いが様々なのが問題です。4バイトのUTF-8に対応しているかどうか、結合文字を含むときに問題が起きないかどうか、確認する必要があります。文字の範囲としてはJIS X 0213の文字を含むことを必須とすると良いでしょう。

さて、EUCの符号構造が必要なときというのは、プログラムやデータフォーマット等がEUCの構造を要求している場合ということです。私もEUCの構造を前提にしたプログラムをいくつも持っています。そのときはEUC-JIS-2004を使っています。SJISの場合も同様で、SJISの符号構造が必要ならばShift_JIS-2004を使えば現代日本の文字をよくサポートできます。

SJISなんてこれから無くなるんじゃないの、と思われる方もいるかもしれませんが、11年前、JIS X 0213が制定されたときだって、これからはUnicodeの時代だからSJISなんて必要ないと思われていたのです。しかし現実は違いました。11年経った今でもSJISは盛んに使われています。それなら、2000年の時点で覚悟を決めてShift_JISX0213 (2004年改正でShift_JIS-2004となる) を採用しておけば良かったのです。

COBOLという言語は古くさいものだと思っている人が多いかもしれませんが、業務用には現在でも盛んに使われています (ある人がCOBOLを馬鹿にする発言をしたところ、「君の給料もCOBOLで計算されているんだよ」とたしなめられたという話があります)。古いものが案外しぶとく使われ続けることもあるわけです。SJISが文字コード界のCOBOLになるかどうかは分かりませんが、使うのならばきちんと現代日本の文字に対応したバージョンのSJIS、つまりShift_JIS-2004を使うのがものの道理というものでしょう。

ホッケという魚と漢字

ホッケという魚は北海道のうまい魚というようにみなされることがありますが、元来は高い評価を得る魚ではなかったようです。

Wikipedia のホッケの項を見るとかつては不味い魚とされていたように書かれています。まあWikipediaのことなのでどれだけ本当かは分かりませんが。一方、北海道大学に勤めて世界初の人工雪を作った中谷宇吉郎博士の随筆には、この魚を「愚魚」と表現しているものがあり(「貝殻の歌」、昭和36年、『中谷宇吉郎随筆集』収載)、やはり上等なものとは考えられていない節があります。しかし、鮮度のいいものはうまいというようなことも書かれています。

今でも実際庶民的な魚ではありますが、うまいものは大変にうまいことがあるというのは、以前このブログに「函館で食べたホッケ」として記しました。

この魚はだいたい片仮名でホッケと書くことが多いのですが、漢字もあります。魚へんに花、「𩸽」と書かれます。これは魚へん漢字に詳しい人の間では割合よく知られたもののようで、以前テレビのクイズ番組に出題されていて回答者が正答していたということを、このブログ「テレビで見た第4水準漢字」に記しました。また、最近文庫で出版された本、『魚偏(うおへん)漢字の話』にも説明があります。この字は中国にはなく、ホッケの表面に美しい斑紋があることから魚偏に花となったとしています。

この字はどうも割とあちこちで見掛けるようだということは、このブログの過去の記事、「北海道で見た第4水準漢字」や「札幌・二条市場で見た文字」に記したとおりです。

この𩸽という字、文字コードとしてはJIS第4水準にあり、面区点番号2-93-44です。Unicodeでは、BMPでなく面02にあり、符号位置U+29E3Dです。

この、JISでは第4水準にあり、Unicodeでは面02にあるというのは、私が文字コードの実装をテストするうえでは大変重宝しています。JIS第4水準というのは、EUCの符号化ではSS3という制御文字の対応が必要であり、SJISでは区点番号からの計算式が第3水準までとは別の式を使わないといけないという特徴があります。きちんと第3・第4水準に対応しているかどうかのテストに使えます。また同時に、UnicodeではBMP外だということは、UTF-16ではサロゲート・ペア、UTF-8では4バイトのUTF-8に対応している必要があるので、これもやはりテストに丁度良い。

私がこの前このブログで「Kindleはホッケを食べられるか」という記事を書いたのも、そのあらわれのひとつです。

また、漢字自体が、「ああ、魚のホッケのことだな」と、意味が理解されやすいというのも、馴染みやすいということでは利点のひとつに数えられます。

こうしたことから、拙著『プログラマのための文字コード技術入門』では、第4章とAppendixの文字符号化の例として、この𩸽という字を便利に活用させていただきました。ホッケさまさまです。

Kindleはホッケを食べられるか

最近PDF読み機として使える機械に興味を持っていて、KindleやiPadなどをウェブで調べていたりしたのですが、その中で面白いブログ記事を見掛けました。

文字通り、Kindle 3でJIS X 0213の文字がどれだけ使えるかを調べたレポートです。

詳しくは上記のリンク先を見てほしいのですが、かいつまんでいうと、BMPの漢字は大丈夫だが面02のCJK拡張Bの文字は全滅ということです。また非漢字は一概にいえないそうですが、結合文字を使うものは駄目なようです。Kindle 3.1はBMP内でも互換漢字は表示できないということです。

Kindleでは、JIS第4水準でUnicodeの面02にある「𩸽 (ほっけ)」という字(U+29E3D)は表示できないということになります。

こういうややこしい状況がなくなって、いつでもどこでもJIS X 0213の全文字が扱えるようになると良いと思います。

CP932変換表の問題が顕在化する例

JIS X 0208のシフトJISとUnicodeとの変換の問題として、CP932 (Windows-31J)の問題はよく知られています。書籍『プログラマのための文字コード技術入門』にも記しました。Windowsに実装されているUnicodeへの変換表が、標準の定義とずれている問題です。

この問題は、使用頻度の高さから、波ダッシュ「〜」(1面1区33点、SJIS 8160)が化け る問題としてよく知られています。

しかし、波ダッシュ以外にも大きな影響を受けている文字(記号)があります。

双柱「‖」1面1区34点 (SJIS 8161)は、垂直線が2本並んだ格好をしている記号です。文字名はDOUBLE VERTICAL LINEといいます。Unicodeで対応する符号位置はU+2016です。この記号はCP932変換表の影響を受けています。CP932変換表では、U+2016でなく平行記号 (PARALLEL TO, U+2225, 「∥」) に変換してしまうのです。

平行記号というのは、義務教育の算数だか数学だかの時間に出てきたと思うのですが、2つの辺が平行であることを表す記号です。AB∥CD、のようなやつです。この記号は2本の斜線で表すか2本の垂直線で表すか、どちらの形もあるようです。斜線でなく垂直線で表すこともあることから、CP932変換表では双柱記号と混同されたようです。JIS X 0213では、既存の双柱とは別の学術記号 (数学記号) として平行記号が採用されました。1面2区52点です。

双柱という記号は、一種の区切り記号として使われます。例えば、私が見たあるウェブサイトではタイトルの中にこんな使われ方をしていました。

  春のわくわく宿泊プラン‖大崩れホテル

これは区切り記号としてごく妥当な使い方です。小学館の『句読点、記号・符号活用辞典。』にはこの記号の使用例として辞書の用例が載っています。

ところが、CP932変換表によってこの記号がUnicodeの平行記号に変換されてしまうと、二重の縦線でなく二重の斜線として表示されてしまうことがあります。これはフォントに依存します。

Windows XPまでの同梱フォントは平行記号の字形が縦線の形に作られていたので問題が顕在化しなかったのですが、Vistaに付属のフォントでは斜線の形になりました。これによって、それまでは双柱つまり二重の縦線に見えていたシフトJISの記号が、Vistaでは二重の斜線になってしまったのです。二重の斜線を区切り記号として使うことは一般的ではありませんから、見た目に困ったことになります。双柱を斜線にしてしまうのは明白におかしい。符号位置のセマンティクスを逸脱した形になってしまっており、文字化けといってよい現象です。

先のサイトを最近見てみたら、区切り符号として双柱でなく単なる縦線「|」を使うよう変更されていました。想像ですが、このホテルではPCを入れ替えてOSも新しくなったら区切り記号が予想外の形に文字化けしたので記号を変えたのかもしれません。

CP932変換表の弊害というのはこういうところにもあるわけです。

コード変換のときにこの問題を避けるには、iconvでCP932を指定しないことです。シフトJISからUTF-8に変換するのに、

  $ iconv -f CP932 -t UTF-8 hoge.txt

のようにしてしまうと、上記の双柱の文字化けの問題が発生します。この問題が発生しないようにするには、

  $ iconv -f SHIFT_JIS -t UTF-8 hoge.txt

のようにすればOKです。ただしこれだと、Windowsを使って生成したシフトJISの場合、機種依存文字の丸付き数字やローマ数字などに対応しません。そうした記号を救済したいなら、Shift_JIS-2004を使って以下のようにします。

  $ iconv -f SHIFT_JISX0213 -t UTF-8 hoge.txt

これで、双柱や波ダッシュがきちんと変換されるだけでなく、①②のような丸付き数字、ⅡやⅢのようなローマ数字、℡や㈱のような組み文字にも対応します。機種依存文字の漢字は文字化けしますが、丸付き数字に比べて頻度はずっと低いですし機種依存文字はそもそも文字化けするものなのであまり問題にならないでしょう。

札幌・二条市場で見た文字

この前札幌に行ったときは、この4月に新しくできた創成川公園にも足を運んでみました。

札幌の真ん中を東西に走っているのが大通公園であるのはよく知られていますが、一方南北に走っていて市街地を東西に分けているのが創成川です。南2条西3丁目、とかいう住所の東西の基点がこの川です。

今まで創成川の両岸はびっちりと自動車が走っていたのですが、それをアンダーパス化して車道だったところの一部を公園にしたのが創成川公園です。

創成川公園

車道とのかねあいもあってか、大通公園と比べるとあまり広い公園とはいえません。しかし、歩きやすくなったことで、街の東西が自然につながった印象を受けました。歩いて気持ち良い空間が増えるのは良いことです。

創成川公園をぶらぶら歩いていたら二条市場に着きました。文字通り、南2条にある市場です。ちょっと中を見てみました。

その中に、ホッケが売られていたのですが、紙に手書きされた文字が「縞𩸽」という漢字になっていました。これでシマホッケと読みます。

この「𩸽」という漢字はJIS第4水準、面区点番号2-93-44、UnicodeではU+29E3Dにあります。『プログラマのための文字コード技術入門』に出てきたのをご記憶の方もいらっしゃるでしょう。

JIS X 0213で第4水準に分類されたということはかなり頻度が低いと認定されたものと考えられます。しかし、市場をぶらぶら歩いていたら出食わす程度には、頻繁に使われているのだとも言えます。去年の夏に北海道に行ったときには、この字を3回も見かけたと、このブログに書いていました (「北海道で見た第4水準漢字」)。

第4水準とはいえ、結構あちこちで使われている字であるような印象を受けました。

最近発売された本、『魚偏(うおへん)漢字の話』(加納喜光、中公文庫)にも当然のようにこの字は登場しています。この本によると、𩸽という字は中国にはないのだそうです。

さて、余談ながら、創成川公園ではこんな光景も見て楽しみました。

水だいすき

犬が喜んで水に入って遊んでいます。このあと陸に上がって、おきまりの体ブルブル。

ISO/IEC 10646:2011

ISOの一部の国際標準が無料でダウンロードできるサイト Publicly Available Standards から、ISO/IEC 10646:2011がダウンロードできるようになっています。

これはUnicodeの方でいうとバージョン6.0に対応します。ただし、インドの新しい通貨記号 U+20B9 INDIAN RUPEE SIGN の有無だけが異なるようです(Unicode 6.0には入っている)。

UnicodeコンソーシアムのサイトにあるUnicodeの文書によると、10646のこの新しい版においてUCS-4はUTF-32と同じになり、UCS-2はobsoleteなのだそうです。

Java 6 でIVSを比較すると何が起こるか

Java 6が対応しているUnicodeのバージョンは4.1だそうです 【追記: 後で確かめたら4.0だそうです。私の思い違いでした _o_】。IVSに使われる面0Eのvariation selector (異体字セレクタ)はこのバージョンには既に入っています。なので、最新IVDの知識は期待できないとしても、variation selectorを無視するくらいの処理はひょっとしたら入っていてくれないかな、と思ってちょっと試してみました。

まず、Stringクラスの挙動から。まあ、何が起こるかは大体想像できるのですが、一応確認しておきましょう。

こんな風な文字列があるとします。

    String nonIvs = "与太郎";
    String ivs1 = "与\uDB40\uDD00太郎";
    String ivs2 = "与\uDB40\uDD02太郎";

ここで、\uDB40\uDD00というのはU+E0100の、\uDB40\uDD02というのはU+E0102のエスケープ表現です。つまり、この3つの文字列はいずれも「与太郎」という文字列を表しています。ivs1とivs2の「与」にはそれぞれU+E0100とU+E0102という異体字セレクタがついていますが、現在のIVDによるとこれはいずれも常用漢字体の (つまり、普通の)「与」の形であり、見た目に区別がつきません。つまり、この三つの文字列はIVS対応の環境で表示するといずれも「与太郎」と見えます。

この文字列をまずはString#equals()で比較してみましょう。

    System.out.println("\"" + nonIvs + "\".equals(\"" + ivs1 + "\") = "
        + nonIvs.equals(ivs1));
    System.out.println("\"" + ivs1 + "\".equals(\"" + ivs2 + "\") = " +
        ivs1.equals(ivs2));

結果はこうなります。

    "与太郎".equals("与[0E0100]太郎") = false
    "与[0E0100]太郎".equals("与[0E0102]太郎") = false

ただし、[0E0100] は、U+E0100のバイト列を表します。私の実験環境では四角の中に 0E0100 という文字が入った格好で表示されています。

これは想像通りですね。単にcharの列を比較しただけの結果になっています。つまり、見た目の区別がつかなくても容赦なく別々の文字列として扱っています。これが何をもたらすかというと、Stringクラスで比較しているプログラムに対しては、"与" と "与[0E0100]" と "与[0E0102]" を、見た目の区別がないにもかかわらず、人が入力し分けてやらないと困ることになるということです。

String#compareTo() も試しておきましょう。比較のためにもう一つ文字列を定義しておきます。

    String nonIvsNext = "与那国";

「那」はUnicode順でもJISコード順でも「太」より後になります。つまり、「与太郎」と「与那国」を比較すると「与那国」の方が後にくることが期待されます。実際、IVSを含まない文字列同士での比較、

    System.out.println("\"" + nonIvs + "\".compareTo(\"" + nonIvsNext + "\") = " +
        nonIvs.compareTo(nonIvsNext));

を実行すると、負の値が出力されます。これは期待通りです。

IVSが絡むとどうなるか。

    System.out.println("\"" + nonIvsNext + "\".compareTo(\"" + ivs1 + "\") = " +
        nonIvsNext.compareTo(ivs1));

を実行すると、

    "与那国".compareTo("与[0E0100]太郎") = -1910

と、負の値になります。「与那国」の方が「与[0E0100]太郎」より先だといっているのです。これは、見た目の期待に反します。残念。(まあ、Stringクラスにそこまで期待しない方がいいのですが)

気をとりなおして、Collatorでうまいこと処理してくれないものか、実験してみます。

JavaにはCollatorというものがあります。存在理由や効果などは『プログラマのための文字コード技術入門』の第7章を参照してほしいのですが、これを使うと文字コード順でなく言語に応じた適切な照合(collation)を実現できます。

準備として、デフォルトロケール (この場合は日本語環境) のCollatorオブジェクトを取得しておきます。

    Collator c = Collator.getInstance();

このCollatorで比較するとどうなるか。人が同じと認識する文字列は同じだという結果を返してほしいわけです。

    System.out.println("Compare " + ivs1 + " with " + nonIvs + ": " +
        c.compare(ivs1, nonIvs));

を実行すると、

    Compare 与[0E0100]太郎 with 与太郎: 1

となります。等しい文字列のときには0という値が返却されるのですが、ここで1と出ているのは、「与[0E0100]太郎」の方が大きい文字列だといっているわけです。念を押すようですが、見た目の区別はつきません。

IVS同士だとどうか。

    System.out.println("Compare " + ivs1 + " with " + ivs2 + ": " +
        c.compare(ivs1, ivs2));

この結果はこうです。

    Compare 与[0E0100]太郎 with 与[0E0102]太郎: -1

「与[0E0100]太郎」の方が小さいといっています。同じとはみなしてくれません。この二つは、見た目の区別のつかないIVS同士です。

どうも、Collatorを使っても、異体字セレクタを無視して比較するような賢いことはしてくれず、単に漢字の後ろに面0Eの文字があるような処理がされています。

だめおしにもうひとつ。

    System.out.println("Compare " + nonIvs + " with " + nonIvsNext + ": " +
        c.compare(nonIvs, nonIvsNext));
    System.out.println("Compare " + nonIvsNext + " with " + ivs1 + ": " +
        c.compare(nonIvsNext, ivs1));

この2行の実行結果は下のとおり。

    Compare 与太郎 with 与那国: -1
    Compare 与那国 with 与[0E0100]太郎: -1

IVSなしの「与太郎」と「与那国」では常識的に負の値が返却されています。一方、「与那国」と「与[0E0100]太郎」とでは、前者の方が小さいといっています。見た目の文字列としては「与那国」と「与太郎」の比較なので正の値がかえってほしいところですが、そうはなってくれません。もし「与太郎」(IVSなし)「与那国」「与[0E0100]太郎」の三つの文字列をソートすると、Collatorを使ったとしても、異体字セレクタの入るものが一番後にくるという格好悪い結果になってしまいます。見た目のソート結果としては「与太郎」「与那国」「与太郎」という風になり、なぜ「与那国」の後に「与太郎」がひとつだけ来ているのか、人が見てもさっぱり分からないという状況になります。

結論としては、予想されたことではありますが、Java 6はIVSについて特別なことは何もしてくれません。なので、IVSを含むデータを考えなしに垂れ流すと、いろいろ困ったことになるでしょう。UTS #37のIVDの仕様には「variation selectors are default ignorable」と書いてありますが、Javaはignoreしてくれないようです。

文字列がIVSを含む場合の照合(collation)についてUnicodeの人が何も考えていないとは思えないのですが、UTS #10 「Unicode Collation Algorithm」には異体字セレクタへの言及が何もありません。このあたりがどうなっているのか、もしご存じの方がいたら教えていただけるとうれしいです。(まあ、variation selectorは単に無視せよということなのかもしれませんが、どこかでそう明言されていないと実装者は何も考えなさそうな気がするので)

IVSは文字コードではない

IVSを使うと、常用漢字体の「与」は以下の異なる符号化表現で表し得ます。

  • U+4E0E U+E0100
  • U+4E0E U+E0102
  • U+4E0E (※通常の日本語環境では上2つと同じように見える筈。中国語環境などでは異なる)

これが何を意味するかというと、画面上で同じ「与」という漢字が見えていても、その背後にある符号化表現は上の3つのいずれでもあり得るわけです。これがどのような不都合をもたらすかはいうまでもないでしょう。

文字コードというものは、文字を一意に符号化するものです。しかしIVSでは一意に符号化することは最初から考えられていないようです。つまり、IVSは文字コードではありません。

文字コードでないものをUnicodeのレベルで扱うのが適切なのか、再考を要するかもしれません。たとえばルビタグや言語タグのような文字コードでないものがUnicodeにはあって、こういうのはXMLなどで実現すべきだと主張する人たちがいます。IVSもそれと同じようなことになる可能性も考えられます。

それをIVSと呼ぶのか

Unicodeの「IVS」というものの普及を目指す協議会が設立されたというニュースが出ていました。例えば、ITmedia Newsの「「書き手と読み手の字体の一致」を保証する「IVS」普及へ、MSやアドビなど協力」などの記事があります。

内容以前に気になったのが、IVS という用語の使い方。Ideographic Variation Sequenceという名のとおり、これはsequenceを表す言葉です。どういうsequenceなのかというと、UnicodeのCJK統合漢字の後ろにU+E0100のような符号位置 (variation selector) を付けたものです。これによって漢字の異体字 (とひとまず呼んでおくが、異体字というより活字のデザイン差程度のものが多い) を示すものです。

つまり例えば 「U+4E08 U+E0100」 のような列のことを本来はIVSと呼ぶわけです。

ただし、「variation selectorによってCJK統合漢字の形のバリエーションを示す技術」のことも(便宜的に?) IVSと呼ぶことがあって、上記記事の見出しではこの意味で使われています。

細かいことをと言われるかもしれませんが、これはちょっと気持ち悪いなあと思います。

私の本では、これをどう書くか迷った結果、「異体字セレクタ」という言葉で表しておきました。同書ではIVSという言葉は、上記のごとく、「U+4E08 U+E0100」のような(sequence)を表すものとして使っています。でも私の主観としては、それを書いた後くらいから、IVSという言葉で「variation selectorを使う方式」のことをも示す言い方が広まってきたような感じがします。ううむ。

まあ、あまり気に病むようなことではないのでしょうか。そんなことよりも、IVSが本当に使いものになるのかどうか、運用上の問題はどうか、などを気にした方がいいのかもしれません。

  1 2 3 4  

最近のブログ記事

朝鮮半島の訃報の第3水準漢字
朝鮮戦争で韓国軍として活躍した白善燁氏が…
テレワークの環境改善〜CO2濃度をチェックする
テレワークの問題点 新型コロナウイルスの…
エンジニアHubにて「文字コード再入門─ Unicodeでのサロゲートペア、結合文字、正規化、書記素クラスタを理解しよう!」公開
「エンジニアHub」にて記事を執筆しまし…
National Geographic Your Shot で Daily Dozen に選ばれた話
ブログをさぼっている間に時間がずいぶん経…
情報処理学会から山下記念研究賞を頂いた話
ブログをさぼっている間に時間がずいぶん経…

広告