Not only is the Internet dead, it's starting to smell really bad.:2015年06月下旬

2015/06/20(Sat)

[Windows][I18N] Windows 10 の国際化についての違和感 (その2)

前回の続き、Windows 10 のスタートメニューの国際化について検証していきたいと思います、今回も途中で飽きて放置しそうですが(ぉ

@日本語版のスタートメニューの違和感

再掲、「あ~ん(=かな)」でインデックス化されていますが、漢字のメニューはすべて「漢字」の項に追いやられています。 こんな国語辞典とかアドレス帳アプリが存在したら使い物になりませんですやね。

@Windows7までのスタートメニューの仕様であれば、とりあえず文字コード順に並べるだけでも見栄えはよかった

こちらも再掲ですが

と文字コードの昇順に列挙されてるだけです、いや違うこれ正しくは文字コード順ではないんですけどね…

よくみると「ネットワーク~」の次に「はじめに」がきて、その次は「ファイル名~」です。これが文字コード順だったら

  • は(U+306f)じめに
  • ネ(U+30cd)ットワーク~
  • フ(U+30d5)ァイル名~

の順番になるはずなのですが、ひらがなカタカナを無視して50音順になっています。

実はこれ I18N の世界には「文字列照合順序(Collation)」というものがありまして、それによって定められたルールに従って並び替えてるんですな。

@文字列照合順序とは?

文字列をソートする時、プログラミング幻語 C なら

$ cat >sortstr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int
cmp(const void *p1, const void *p2)
{
	const char *s1, *s2;
	s1 = *((const char **)p1);
	s2 = *((const char **)p2);
	return strcmp(s1, s2);
}

int
main(int argc, char **argv)
{
	int i;

	qsort(&argv[1], argc - 1, sizeof(*argv), cmp);
	for (i = 1; i < argc; ++i)
		printf("%s\n", argv[i]);
}
^D

のような qsort(3)strcmp(3)を使ったコードを書くかと思います。

はいそこのアルゴリズム狂の方、今の話題においてソートの安定・不安定とか速度とかメモリ使用量とかどうでもいいので、そのチャックからボロンと取り出した TimSortとか BogoSortとか汚いものは速やかにズボンにおしまいになってご着席ください、座れっていってんだろ(怒)!

しかし困ったことにこのプログラムでは文字列の比較を strcmp(3)でやってるので、文字コード順でしかソートできません。

$ make sortstr
$ ./sortstr.exe ネットワーク はじめに ファイル名
./sortstr
はじめに
ネットワーク
ファイル名

さっきのスタートメニューとは異なり「ひらがな→カタカナ」の順になってしまっております。

@文字列照合順序に従ったソートロジックを書く

ある特定の言語地域において「自然な」文字列の大小関係を返すAPIとして、POSIX localeには strcoll(3)という関数があります。

ここで注意、*BSD では LC_COLLATE がまともに実装されておりませんので Linux とか Cygwin とか実装済の環境でテストしてくだちい。FreeBSD は 一応 collation サポート入ってるはずですが、multibyte な locale でも正しく動作するかは知りません。NetBSD と OpenBSD に関しては strcoll(3) は strcmp(3) とまったく同じ動作をするので期待した結果は得られません。

さっきの sortstr.c に以下の差分を適用します。

--- sortstr.c.orig	2015-06-20 05:11:37.582263100 +0900
+++ sortstr.c	2015-06-20 05:12:44.659099700 +0900
@@ -1,3 +1,4 @@
+#include <locale.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -8,7 +9,7 @@
 	const char *s1, *s2;
 	s1 = *((const char **)p1);
 	s2 = *((const char **)p2);
-	return strcmp(s1, s2);
+	return strcoll(s1, s2);
 }
 
 int
@@ -16,6 +17,7 @@
 {
 	int i;
 
+	setlocale(LC_COLLATE, "");
 	qsort(&argv[1], argc - 1, sizeof(*argv), cmp);
 	for (i = 1; i < argc; ++i)
 		printf("%s\n", argv[i]);

そんでビルドして実行すると?

$ make sortstr
$ LC_COLLATE=ja_JP.UTF-8 ./sortstr.exe ネットワーク はじめに ファイル名
./sortstr
ネットワーク
はじめに
ファイル名

おお、ちゃんとひらがなとカタカナの違いを無視して50音順にソートされました!

ちなみに

strcoll(s1, s2);

は s1 と s2 を strxfrm(3)によって変換したもの同士で strcmp(3)した結果と等価になります、よって照合順序の肝はこの関数ですが、実装の中身については今回は扱わず、また後の回にて説明します。

@次回予告

今回の説明で Windows 7 までのスタートメニューの実装であれば 文字列照合順序(Collation)APIを使えば簡単に実装できることはお判りいただけたと思います。

次回は Windows 10 の新仕様の実装の難しさについてを

  • 日本語における文字列照合順序の工業規格
  • C/POSIX localeにおける実装の制限

について解説しつつ確認していきたいと思います。

2015/06/21(Sun)

[Windows][I18N] Windows 10 の国際化についての違和感 (その3)

前回は Windows 7 のスタートメニューの並びは

並べているというお話でした、今回はこの「文字列照合順序」とはどのような処理なのかについてざっくりと触れてみます。

@文字列照合順序

日本語における文字列照合順序が、実際にどのような処理を行うかは

などとして工業規格化されておりますので、まずはそれを読みましょう。

…でもですね、内容について大変に複雑かつ難解です。こ↑こ↓で全てをご紹介することは到底無理なので、いくつかポイント絞って解説していきます。

@基底文字(Base Character)への丸め

基底文字とは、元々の用語としては 基本ラテン文字の 26 字を指します。

英語ではこの基本字のみしか使いませんが、その他(主にヨーロッパとその植民地)の言語ではこの基本字に ダイアクリティカルマークを組み合わせた文字を使用します。

ダイアクリティカルマークの有名どころだと

名称 基本字 + ダイアクリティカルマーク 合成済 Unicode(UCS4) Latin-1(ISO8859-1) 備考
ダイエリシス(ウムラウト) a + U+0061 + U+0308 / U+00E4 0xE4 ドイツ語など
グレイヴアクセント a + U+0061 + U+0300 / U+00E0 0xE0 フランス語など
アキュートアクセント a + U+0061 + U+0301 / U+00E1 0xE1 スペイン語、イタリア語など
サーカムフレックスアクセント a + U+0061 + U+0302 / U+00E2 0xE2 フランス語、ベトナム語など

などは目にしたことがあるかと思います。これらの文字は「基本ラテン文字 + ダイアクリティカルマーク」のリガチャで表現することも可能ですが、たいていの符号化文字集合においては合成済文字として収録されていますやね。

上の例で挙げた文字の基底はすべて「a」です、コードポイント的には a とは離れた場所にあることが多いのですが、文字列照合順序では同じ順序として扱うわけです。

前回作成した sortstr.c で試してみましょう。

ダイアクリティカルマーク付の a たちが、コードポイントでは前になるはずの o より前にソートされていることがお判りいただけますでしょうか。

これはつまり

  • 合成済文字をバラして分解して
  • 「丸め」して順序を比較した

結果、この並び順になったわけです。

ここまで読んで

「丸め」ってつまりは「Unicode正規化」のことか!

とヘウレーカして全裸で路上に飛び出して股間の愛・昆布をたなびかせた人、残念ですがそれは間違いです。 Unicode 文字列照合ジュンジュワー において Unicode 性器化 は最も重要なテクニックですが、イコールではありません。

一例をあげると、Unicode正規化では

  • (アッシュ) … ae と等価
  • (エスツェット) … ss と等価
  • (ストローク付きオー) … o と等価

などの文字については合成文字ではないとして分解はしないんですよな、詳しいことは

を見て下さい。

しかーし文字列照合順序では、こいつらはきっちり等価として扱うんですね、いやはや。

@ラテン語圏においてはこの基本ラテン文字26文字でインデックス化 / グループ化される

えー Windows 10 のスタートメニューなんですが「全てのプログラム」にメニューを追加する方法がよくわからんので(できないっぽい)、しゃーないから代わりとして「People」というアドレス帳アプリに、ダイアクリティカルマーク付の文字を登録して、どういうインデックスが作られるか、確認してみましょ。

ウムラウトな a は「A」に、エスツェットは「S」に、インデックス化 / グループ化されています。

@次回

今回はラテン文字における基底文字を説明しましたが、次回はいよいよ日本語です。