UnicodeのライブラリICUを使ってUnicodeの正規化を行ってみる。
Unicodeの正規化とは、簡単に言うと見た目が「が」の文字を1つのコード「が」で表す場合と「か」+「゛」(濁点)の2つで表す場合がある。前者を合成文字で後者が結合文字って言う。これらを変換することを正規化っていう。(かなり適当な説明なんで詳しくはググってくれw)

とりあえず、ソースを示す。

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include "unicode/ustring.h"
#include "unicode/unorm2.h"

void dump(char *mem, int size);

int main()
{
	char *src = "がぎぐげご";
	UErrorCode err;
	int32_t destLen;
	UChar *dest, *dest2;
	int32_t numSubstitutions;
	
	/* (1) UTF8からUnicodeへの変換後の文字数を取得する。 */
	err = U_ZERO_ERROR;
	u_strFromUTF8WithSub(NULL, 0, &destLen, src, -1, 0xfffd, &numSubstitutions, &err);
	printf("destLen=%d\n", destLen);
	
	/* (2) 変換後の文字列を入れるメモリを確保 */
	dest = (UChar*)calloc(destLen + 1, sizeof(UChar));
	if(!dest) {
		fprintf(stderr, "malloc error\n");
		exit(1);
	}
	
	/* (3) UTF8からUnicodeへ変換 */
	err = U_ZERO_ERROR;
	u_strFromUTF8WithSub(dest, destLen + 1, &destLen, src, -1, 0xfffd, &numSubstitutions, &err);
	if(U_FAILURE(err)) {
		fprintf(stderr, "err=%d\n", err);
		exit(1);
	}
	printf("destLen=%d\n", destLen);	
	dump((char*)dest, (u_strlen(dest) + 1) * sizeof(UChar));

	/* (4) Unicodeの正規化(NFD) */
	err = U_ZERO_ERROR;
	const UNormalizer2 *normalizer = unorm2_getNFDInstance(&err);
	if(U_FAILURE(err)) {
		fprintf(stderr, "err=%d\n", err);
		exit(1);
	}
	/* (5) 正規化後のサイズを取得 */
	err = U_ZERO_ERROR;
	int32_t destLen2 = unorm2_normalize(normalizer, dest, -1, NULL, 0, &err);
	printf("destLen2=%d\n", destLen2);
	
	/* (6) 正規化後の文字列を入れるメモリを確保 */		
	dest2 = (UChar*)calloc(destLen2 + 1, sizeof(UChar));
	if(!dest2) {
		fprintf(stderr, "malloc error\n");
		exit(1);
	}
	/* (7) 正規化する */
	err = U_ZERO_ERROR;
	destLen2 = unorm2_normalize(normalizer, dest, -1, dest2, destLen2 + 1, &err);
	if(U_FAILURE(err)) {
		fprintf(stderr, "err=%d\n", err);
		exit(1);
	}
	dump((char *)dest2, (u_strlen(dest2) + 1) * sizeof(UChar));
	
	free(dest2);
	free(dest);
	
	return 0;
}

void dump(char *mem, int size)
{
	int addr, offset;
	
	for(addr = 0; addr < size; addr += 16) {
		printf("%04X ", addr & 0xffff);
		for(offset = 0; offset < 16; offset++) {
			if(addr + offset >= size) {
				break;
			}
			printf("%02X ", mem[addr + offset] & 0xff);
		}
		printf("\n");
	}	
}

ソースはUTF-8で保存しておいて、コンパイルはこんな感じでする。

$ gcc -o unicode -I/opt/local/include -L/opt/local/lib -licuuc unicode.c

ICUは、MacPortsを使ってインストールしているものとする。

(1) UTF8からUnicodeへ変換した時の文字数を取得。変換できない文字があった場合は0xfffdにして変換するように指定している。また、ICUのAPIで渡すエラーコードが入る変数はU_ZERO_ERRORで初期化しておかないといけない。
(2) メモリ確保。
(3) UTF8からUnicodeへ変換
(4) 正規化の準備。ほんとは、合成文字列への変換を試したかったんだが結合文字列の「がぎぐげご」のUTF8を用意するのが面倒くさかったので、結合文字列の変換するとこにした。合成文字列への変換する場合はunorm2_getNFCInstance()を使う。
(5) 正規化後のサイズ取得。ここでやってる方法はドキュメントに書いてないので注意。
(6) メモリ確保。
(7) 正規化する。

(追記)
icu-configってのが用意されているようなので、コンパイルはこんな感じでする。

$ gcc -Wall -o unicode `icu-config --cflags --ldflags` unicode.c