この章では、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) |
s2 を s1 に追加する。追加後の s1 が返る |
|
strncat(s1,s2,n) |
s2 のうち最大 n バイトを s1 に追加する。追加後の s1 が返る |
|
strcmp(s1,s2) |
s1 と s2 の大小関係を調べる。 (順序情報に基づかない) |
|
strncmp(s1,s2,n) |
s1 と s2 の最大 n バイトの大小関係を調べる。 (順序情報に基づかない) |
|
strcoll(s1,s2) |
順序情報に基づき s1 と s2 の大小関係を調べる |
|
strcpy(s1,s2) |
s2 を s1 にコピーする。コピー後の s1 が返る |
|
strncpy(s1,s2,n) |
s2 の最大 n バイトを s1 にコピーする。コピー後の s1 が返る |
|
strlen(s) |
s の長さをバイト数で返す |
|
strxfrm(s1,s2,n) |
大小関係を調べるための文字列の変形 |
このほか文字列に含まれる文字を検出する strchr() および strrchr() がありますが、複数バイト文字列に対してこれらの API は正しく動作しません。複数バイト文字列に含まれる文字を検出する場合は、文字列を mbstowcs() (後述) などでワイド文字列に変換してから wcschr() または wcsrchr() を使用してください。
複数バイト文字列操作の API を用いたプログラム例を紹介します。使用する場合には string.h ヘッダファイルを取り込むこと、また setlocale() を処理の最初の段階で呼び出して動作ロケールを適切に設定することが必要です。
この例では文字列を比較しています。一般に文字列の順序関係は文字列を構成する各文字の順序関係で決まり、文字の順序関係はロケールごとに LC_COLLATE カテゴリに定義されています。strcmp() は比較文字列をバイト単位で比較するため、正しい順序関係を構成しない場合があります。順序情報に基づく比較を行うには strcoll() を使用する必要がありますが、一般には strcmp() に比べて低速です。strxfrm() は、本来 strcoll() で比較すべき文字列を変形させます。変形は、変形されたあとの文字列同士を strcmp() で比較した結果が変形する前の文字列同士をstrcoll() で比較した結果と同一になるように行なわれます。データベースを管理するなど多くのデータを順序関係に基づいて並べ換える場合には、効率化が期待できます。
例 2-1 において、main() 関数から my_strcoll() を呼び出していますが、システムが提供する strcoll() を呼び出すように変更しても全く同じ結果が得られます。
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./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
ワイド文字表現の文字列を操作するための主な国際化 API を表 2-2 に紹介します。その他にも API が用意されています。詳しくはマニュアルページ (Intro(3) など) を参照してください。
表 2-2 主なワイド文字列操作 API|
インタフェース名 |
作用 |
|---|---|
|
wcscat(ws1,ws2) |
ws2 を ws1 に追加する。追加後の ws1 が返る |
|
wcsncat(ws1,ws2,n) |
ws2 のうち最大 n ワイド文字を ws1 に追加する。追加後の ws1 が返る |
|
wcschr(ws,wc) |
ws の先頭から調べ、最初に出現する wc の位置が返る |
|
wcsrchr(ws,wc) |
ws の先頭から調べ、最後に出現する wc の位置が返る |
|
wcscmp(ws1,ws2) |
ws1 と ws2 の大小関係を調べる (順序情報に基づかない) |
|
wcsncmp(ws1,ws2,n) |
ws1 と ws2 の最大 n ワイド文字の大小関係を調べる (順序情報に基づかない |
|
wcscoll(ws1,ws2) |
順序情報に基づき ws1 と ws2 の大小関係を調べる |
|
wcscpy(ws1,ws2) |
ws2 を ws1 にコピーする。コピー後の 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 つ紹介します。使用する場合には wchar.h ヘッダファイルを取り込むこと、また setlocale() を処理の最初の段階で呼び出して動作ロケールを適切に設定することが必要です。
例 2-2 はワイド文字列の比較です。例 2-1 で複数バイト表現の例として strxfrm() と strcmp() で strcoll() を簡単にシミュレートしましたが、ここではワイド文字表現の場合でのプログラミング例を紹介します。
例 2-2 において、main() 関数から my_wcscoll() を呼び出していますが、システムが提供する wcscoll() を呼び出すように変更しても全く同じ結果が得られます。
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 カラムを最大とし、入力ファイルの行末を均等に削除します。複数バイト表現の途中で文字列が分断されないように調整するために、ワイド文字列表現にして処理します。
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 |