この章では、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 を使用したプログラム例を紹介します。複数バイト文字列操作 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 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) |
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 つ紹介します。ワイド文字列操作 API を使用する場合は、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 |