JFP 開発ガイド

第 2 章 国際化 API での日本語処理

この章では、XPG で仕様が決定された国際化文字列処理プログラミングインタフェースを紹介し、日本語文字列を処理する方法を説明します。

概説

「Solaris 日本語環境」とは、Solaris に JFP と呼ばれる日本語に固有の情報を付加したものです。この環境では、基になる Solaris が XPG に適合しているため、XPG で規定されているすべてのインタフェースを利用できます。XPG には、国際化プログラミングに必要な API が豊富に用意されており、Solaris でもこれらの API を最大限に活用して日本語処理を行うアプリケーションを開発することができます。

アプリケーションプログラマは、適切なロケール設定の下でこれらの API を介して文字列を処理し、アプリケーションを開発することができます。このとき、プログラム中に直接日本語文字列を埋め込まない限り、日本語ロケール間の違いを意識する必要はありません。この章では、XPG に規定されている文字列操作の主な API を、複数バイト表現とワイド文字表現向けにそれぞれ表形式でまとめ、実際にこれらを使用したプログラム例を紹介します。

各 API の仕様の詳細については、該当するマニュアルページを参照してください。

複数バイト表現での文字列処理

表 2-1 に、複数バイト表現の文字列操作に使用する主な国際化 API を紹介します。API は、これ以外にも用意されています。詳しくはマニュアルページ (Intro(3) など) を参照してください。

表 2-1 主な複数バイト文字列操作 API

インタフェース名

作用

strcat(s1,s2)

s2s1 に追加する。追加後の s1 が返る

strncat(s1,s2,n)

s2 のうち最大 n バイトを s1 に追加する。追加後の s1 が返る

strcmp(s1,s2)

s1s2 の大小関係を調べる (順序情報に基づかない)

strncmp(s1,s2,n)

s1s2 の最大 n バイトの大小関係を調べる (順序情報に基づかない)

strcoll(s1,s2)

順序情報に基づき s1s2 の大小関係を調べる

strcpy(s1,s2)

s2s1 にコピーする。コピー後の s1 が返る

strncpy(s1,s2,n)

s2 の最大 n バイトを s1 にコピーする。コピー後の s1 が返る

strlen(s)

s の長さをバイト数で返す

strxfrm(s1,s2,n)

大小関係を調べるための文字列の変形 


注 -

このほか、文字列に含まれる文字を検出する strchr()strrchr() がありますが、これらの API は複数バイト文字列に対しては正しく動作しません。複数バイト文字列に含まれる文字を検出する場合は、文字列を mbstowcs() などでワイド文字列に変換してから、wcschr() または wcsrchr() を使用してください。


プログラム例

ここでは、複数バイト文字列操作 API を使用したプログラム例を紹介します。複数バイト文字列操作 API を使用する場合は、string.h ヘッダーファイルを取り込み、処理の最初の段階で setlocale() を呼び出して、動作ロケールを適切に設定する必要があります。

次の例では、文字列を比較しています。一般に、文字列の順序関係は文字列を構成する各文字の順序関係で決まり、文字の順序関係はロケールごとに LC_COLLATE カテゴリに定義されています。strcmp() は比較文字列をバイト単位で比較するため、正しい順序関係を構成しない場合があります。順序情報に基づいて比較をする場合は strcoll() を使用する必要がありますが、一般には strcmp() に比べて低速です。strxfrm() は、本来 strcoll() で比較するべき文字列を変形させます。変形は、変形されたあとの文字列同士を strcmp() で比較した結果が、変形する前の文字列同士を strcoll() で比較した結果と同一になるように行われます。データベースを管理する場合など、多くのデータを順序関係に基づいて並べ換える場合には、効率化が期待できます。

例 2-1 では、main() 関数から my_strcoll() を呼び出していますが、システムが提供する strcoll() を呼び出すように変更してもまったく同じ結果が得られます。


例 2-1 複数バイト文字列操作 API

sun% cat my_strcoll.c
/*
 * Read lines from two files, and return the
 * order that is the same as they are compared
 * by strcoll().
 * Comparing will stop if either file reaches EOF.
 * It is assumed that each line has at most BUFSIZ - 1
 * byte length.
 *
 * Actual processing is done by my_strcoll(), which
 * does the followings.
 *	1.	Call strxfrm() to get the size of
 *		transformed string.
 *	2.	Dynamically allocate the memory the
 *		buffer. It will be big enough to contain
 *		the transformed string and terminating NULL.
 *	3.	Call strxfrm() again to get
 *		the transformed string. To verify if
 *		the error happens, it must clear `errno'
 *		then call strxfrm(). After that, check
 *		the value of `errno.'
 *	4.	Call strcmp() with the transformed strings.
 *		Since these strings are artificialy created,
 *		they are not allowed to display.
 */

#include <stdio.h>
#include <locale.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

static int my_strcoll(const char *, const char *);

int
main(int argc, char *argv[])
{
	FILE *fp1, *fp2;
	char buf1[BUFSIZ], buf2[BUFSIZ];
	char *cp1, *cp2;
	int retval;

	setlocale(LC_ALL, "");

	if (argc != 3) {
		fprintf(stderr, "¥tUsage: %s file1 file2¥n", argv[0]);
		exit(-1);
	}
 fp1 = fopen((const char *)argv[1], "r");
	fp2 = fopen((const char *)argv[2], "r");
	if ((fp1 == (FILE *)NULL) || (fp2 == (FILE *)NULL)) {
		fprintf(stderr, "%s: Couldn't open %s ¥n",
			argv[0],
			((fp1 == (FILE *)NULL) ? argv[1] : argv[2]));
		exit(-1);
	}
	for (;;) {
		cp1 = fgets(buf1, BUFSIZ, fp1);
		cp2 = fgets(buf2, BUFSIZ, fp2);
		if (!cp1 && !cp2) {
				exit(0);
		} else if (!cp1 || !cp2) {
					fprintf(stderr, "%s: No more contents in %s¥n",
							argv[0], (cp1 ? argv[2] : argv[1]));
					exit(0);
		}
		retval = my_strcoll((const char *)buf1, (const char *)buf2);
		if (retval == 0) {
			fprintf(stdout, "The same collation order.¥n");
		} else if (retval > 0) {
			fprintf(stdout,
				"%s is bigger than %s in terms of collation order.¥n",
				argv[1], argv[2]);
		} else {
			fprintf(stdout,
				"%s is bigger than %s in terms of collation order.¥n",
				argv[2], argv[1]);
		}
	}
	return (0);
}

static int
my_strcoll(const char *cp1, const char *cp2)
{
	char *transform_1, *transform_2;
	size_t xfrm_len1, xfrm_len2;
	int ret_coll;

	xfrm_len1 = strxfrm((char *)NULL, cp1, (size_t)0);
	xfrm_len2 = strxfrm((char *)NULL, cp2, (size_t)0);
	transform_1 = (char *)malloc(xfrm_len1 + 1);
	transform_2 = (char *)malloc(xfrm_len2 + 1);

	errno = 0;
strxfrm(transform_1, cp1, (xfrm_len1 + 1));
	if (errno != 0) {
		perror("my_strcoll(): Error in transforming 1st string");
		exit(-1);
	}
	strxfrm(transform_2, cp2, (xfrm_len2 + 1));
	if (errno != 0) {
		perror("my_strcoll(): Error in transforming 2nd string");
		exit(-1);
	}
	ret_coll = strcmp((const char *)transform_1, (const char *)transform_2);

	free(transform_1);
	free(transform_2);

	return (ret_coll);
}
sun% cat file 1
入力サンプル 1 です。
This line is identical.
短いです。
sun% cat file 2
入力サンプル 2 です。
This line is identical.
こちらの行は長くなっています。
sun% cc -o my_strcoll my_strcoll.c
sun% ./my_strcoll file1 file2
file2 is bigger than file1 in terms of collation order.
The same collation order.
file1 is bigger than file2 in terms of collation order.
./my_strcoll: No more contents in file1	

ワイド文字表現での文字列処理

表 2-2 に、ワイド文字表現の文字列操作に使用する主な国際化 API を紹介します。API は、これ以外にも用意されています。詳しくはマニュアルページ (Intro(3) など) を参照してください。

表 2-2 主なワイド文字列操作 API

インタフェース名

作用

wcscat(ws1,ws2)

ws2ws1 に追加する。追加後の ws1 が返る

wcsncat(ws1,ws2,n)

ws2 のうち最大 n ワイド文字を ws1 に追加する。追加後の ws1 が返る

wcschr(ws,wc)

ws の先頭から調べ、最初に出現する wc の位置が返る

wcsrchr(ws,wc)

ws の先頭から調べ、最後に出現する wc の位置が返る

wcscmp(ws1,ws2)

ws1ws2 の大小関係を調べる (順序情報に基づかない)

wcsncmp(ws1,ws2,n)

ws1ws2 の最大 n ワイド文字の大小関係を調べる (順序情報に基づかない

wcscoll(ws1,ws2)

順序情報に基づき ws1ws2 の大小関係を調べる

wcscpy(ws1,ws2)

ws2ws1 にコピーする。コピー後の ws1 が返る

wcsncpy(ws1,ws2,n)

ws2 の最大 n ワイド文字を ws1 にコピーする。コピー後の ws1 が返る

wcslen(ws)

ws の長さをワイド文字数で返す

wcsxfrm(ws1,ws2,n)

大小関係を調べるための文字列の変形 

wcwidth(wc)

wc の表示に必要なカラム数

wcswidth(ws, n)

ws 中の最大 n ワイド文字の表示に必要なカラム数

プログラム例

ここでは、ワイド文字列操作 API を使用したプログラム例を 2 つ紹介します。ワイド文字列操作 API を使用する場合は、wchar.h ヘッダーファイルを取り込み、処理の最初の段階で setlocale() を呼び出して、動作ロケールを適切に設定する必要があります。

例 2-2 では、ワイド文字列を比較しています。例 2-1 で、複数バイト表現の例として strxfrm()strcmp() を使って strcoll() を簡単にシミュレートしましたが、ここではワイド文字表現の場合のプログラミング例を紹介します。

例 2-2 では、main() 関数から my_wcscoll() を呼び出していますが、システムが提供する wcscoll() を呼び出すように変更してもまったく同じ結果が得られます。


例 2-2 ワイド文字列の比較

sun% cat my_wcscoll.c
/*
 * Read lines from two files, and return the
 * order that is the same as they are compared
 * by wcscoll().
 * Comparing will stop if either file reaches EOF.
 * It is assumed that each line has at most BUFSIZ - 1
 * wide char length.
 *
 * Actual processing is done by my_wcscoll(), which
 * does the followings.
 *	1.	Call wcsxfrm() to get the size of
 *		transformed wide string.
 *	2.	Dynamically allocate the memory the
 *		buffer. It will be big enough to contain
 *		the transformed wide string and terminating (wchar_t)NULL.
 *	3.	Call wcsxfrm() again to get
 *		the transformed wide string. To verify if
 *		the error happens, it must clear `errno'
 *		then call wcsxfrm(). After that, check
 *		the value of `errno.'
 *	4.	Call wcscmp() with the transformed wide strings.
 *		Since these strings are artificialy created,
 *		they are not allowed to display.
 */

#include <stdio.h>
#include <locale.h>
#include <wchar.h>
#include <stdlib.h>
#include <errno.h>

static int my_wcscoll(const wchar_t *, const wchar_t *);

int
main(int argc, char *argv[])
{
	FILE *fp1, *fp2;
	wchar_t buf1[BUFSIZ], buf2[BUFSIZ];
	wchar_t*wcp1, *wcp2;
	int retval;

	setlocale(LC_ALL, "");
if (argc != 3) {
		fprintf(stderr, "¥tUsage: %s file1 file2¥n", argv[0]);
		exit(-1);
	}
	fp1 = fopen((const char *)argv[1], "r");
	fp2 = fopen((const char *)argv[2], "r");
	if ((fp1 == (FILE *)NULL) || (fp2 == (FILE *)NULL)) {
		fprintf(stderr, "%s: Couldn't open %s ¥n",
			argv[0],
			((fp1 == (FILE *)NULL) ? argv[1] : argv[2]));
		exit(-1);
	for (;;) {
		wcp1 = fgets(buf1, BUFSIZ, fp1);
		wcp2 = fgets(buf2, BUFSIZ, fp2);
		if ((wcp1 == (wchar_t)NULL) && (wcp2 == (wchar_t)NULL)) {
			exit(0);
		} else if ((wcp1 == (wchar_t)NULL) || (wcp2 == (wchar_t)NULL)) {
			fprintf(stderr, "%s: No more contents in %s¥n",
					argv[0], (wcp1 ? argv[2] : argv[1]));
			exit(0);
		}
		retval = my_wcscoll((const wchar_t *)buf1, (const wchar_t *)buf2);
		if (retval == 0) {
			fprintf(stdout, "The same collation order.¥n");
		} else if (retval > 0) {
			fprintf(stdout,
				"%s is bigger than %s in terms of collation order.¥n",
				argv[1], argv[2]);
		} else {
			fprintf(stdout,
				"%s is bigger than %s in terms of collation order.¥n",
				argv[2], argv[1]);
		}
	}
	return (0);
}

static int
my_wcscoll(const wchar_t *wcp1, const wchar_t *wcp2)
{
xfrm_len1 = wcsxfrm((wchar_t *)NULL, wcp1, (size_t)0);
	xfrm_len2 = wcsxfrm((wchar_t *)NULL, wcp2, (size_t)0);
	transform_1 = (wchar_t *)malloc((xfrm_len1 + 1) * sizeof(wchar_t));
	transform_2 = (wchar_t *)malloc((xfrm_len2 + 1) * sizeof(wchar_t));

	errno = 0;

	wcsxfrm(transform_1, wcp1, (xfrm_len1 + 1));
	if (errno != 0) {
		perror("my_wcscoll(): Error in transforming 1st string");
		exit(-1);
	}
	wcsxfrm(transform_2, wcp2, (xfrm_len2 + 1));
	if (errno != 0) {
		perror("my_wcscoll(): Error in transforming 2nd string");
		exit(-1);
	}
	ret_coll =
		wcscmp((const wchar_t *)transform_1, (const wchar_t *)transform_2);

	free(transform_1);
	free(transform_2);

	return (ret_coll);
}
sun% cat file1
入力サンプル 1 です。
This line is identical.
短いです。
sun% cat file2
入力サンプル 2 です。
This line is identical.
こちらの行は長くなっています。
sun% cc -o my_wcscoll my_wcscoll.c
sun% ./my_wcscoll file1 file2
file2 is bigger than file1 in terms of collation order.
The same collation order.
file1 is bigger than file2 in terms of collation order.
./my_wcscoll: No more contents in file1	

例 2-3 は、2 つのファイルを読み込んで、2 段組にして表示する例です。表示の際は、1 行あたり 80 カラムを最大とし、入力ファイルの行末が均等に削除されます。複数バイト表現の途中で文字列が分断されないように調整するために、ワイド文字列表現にして処理します。


例 2-3 ワイド文字列の比較 (2 段組表示)

sun% cat my_pr.c
/*
 * Read lines from two files and merge them into
 * one line so that it doesn't exceed 80 columns.
 * When either file reaches EOF, empty lines will be
 * shown.
 * It is assumed each line has at most BUFSIZ - 1 byte chars
 * and, ASCII space (` `) and vertical bar (`|') character have
 * one display column width.
 * Open files and read lines as wide strings by fgetws().
 * Then call my_pr().
 */

#include <stdio.h>
#include <locale.h>
#include <wchar.h>
#include <stdlib.h>

#define		OUTBUFSIZ					80
#define		LIMIT					((OUTBUFSIZ - 3) / 2)
#define		WSPC_STR					L" "
#define		WSPC_CHR					L' `
#define		WSEPARATOR					L" | "
#define		WTAB					L'¥t'
#define		TABS					8

static void my_pr(wchar_t *, const wchar_t *, const wchar_t *);
static void put_spc(wchar_t *, int *);

int
main(int argc, char *argv[])
{
	FILE *fp1, *fp2;
	wchar_t buf1[BUFSIZ], buf2[BUFSIZ];
	wchar_t outbuf[(OUTBUFSIZ + 1)] = { 0x0 };
	int retval;

	setlocale(LC_ALL, "");

	if (argc != 3) {
		fprintf(stderr, "¥tUsage: %s file1 file2¥n", argv[0]);
		exit(-1);
	}
fp1 = fopen((const char *)argv[1], "r");
	fp2 = fopen((const char *)argv[2], "r");
	if ((fp1 == (FILE *)NULL) || (fp2 == (FILE *)NULL)) {
		fprintf(stderr, "%s: Couldn't open %s ¥n",
				argv[0],
				((fp1 == (FILE *)NULL) ? argv[1] : argv[2]));
		exit(-1);
	}
	for (;;) {
		if (fgetws(buf1, BUFSIZ, fp1) == (wchar_t *)NULL) {
			buf1[0] = `¥0';
		}
		if (fgetws(buf2, BUFSIZ, fp2) == (wchar_t *)NULL) {
			buf2[0] = `¥0';
		}
		if ((buf1[0] == `¥0') && (buf2[0] == `¥0')) {
			return(0);
		}
		my_pr(outbuf, (const wchar_t *)buf1, (const wchar_t *)buf2);
		fprintf(stdout, "%S¥n", outbuf);
		outbuf[0] = `¥0';
	}

	return(0);
}

static void
put_spc(wchar_t *outbufp, int *total)
{
	wcsncat(outbufp, WSPC_STR, 1);
	*total += wcwidth(WSPC_CHR);
}

static void
my_pr(wchar_t *outbufp, const wchar_t *wcp1, const wchar_t *wcp2)
{
	int collen;
	int subtotal1, subtotal2;

for (subtotal1 = 0;
		(collen = wcwidth(*wcp1)) <= (LIMIT - subtotal1); wcp1++) {
		if (collen == -1) {
			if (*wcp1 == WTAB) {
				while ((subtotal1 < LIMIT) &&
					(subtotal1 % TABS) != 1) {
					put_spc(outbufp, &subtotal1);
				}
			} else {
				continue;
			}
		} else if (collen == 0) {
			break;
		} else {
			wcsncat(outbufp, wcp1, 1);
			subtotal1 += collen;
		}
	}

	while (subtotal1 < LIMIT) {
		put_spc(outbufp, &subtotal1);
	}
	wcscat (outbufp, WSEPARATOR);
	subtotal1 += wcswidth(WSEPARATOR, wcslen(WSEPARATOR));

	for (subtotal2 = 0;
		(collen = wcwidth(*wcp2)) <= (LIMIT - subtotal2); wcp2++) {
		if (collen == -1) {
			if (*wcp2 == WTAB) {
				while ((subtotal2 < LIMIT) &&
					(subtotal2 % TABS) != 1) {
					put_spc(outbufp, &subtotal2);
				}
			} else {
				continue;
			}
		} else if (collen == 0) {
			outbufp[(subtotal1 + subtotal2)] = (wchar_t)NULL;
			break;
		} else {
			wcsncat(outbufp, wcp2, 1);
			subtotal2 += collen;
		}
	}

	return;
}
sun% cat file
01234567890123456789012345678901234567890123456789012345678901234567890123456789
0123456789012345678901234567890123456789
アイウエオカキクケコアイウエオカキクケコアイウエオカキクケコアイウエオカキクケコ
アイウエオカキクケコアイウエオカキクケコアイウエオカキクケコアイウエオカキクケコ
010ア010ア010ア010ア010ア010ア010ア010ア010ア010ア010ア010ア010
ア010ア010ア010ア
sun% cat file1
入力サンプル 1 です。
This line is identical.
短いです。
sun% cc -o my_pr my_pr.c
sun% ./my_pr file file1
01234567890123456789012345678901234567	| 入力サンプル 1 です。
0123456789012345678	| This line is identical.
アイウエオカキクケコアイウエオカキクケコアイウエオカキクケコアイウエオカキク	| 短いです。
010ア010ア010ア010ア010ア010ア010ア01	|