今回はC言語で文字コード変換をしてみます。思えばこのブログでJava、php、python関連はちょくちょく書いてたけど、C言語の記事が無かったのです。他の言語に比べてコーディングの手間がかかるのであまり使わないからなんですけどね。。
僕はC言語を知ってから、もう十数年経ちます。そして、最初に覚えたコンピュータ言語はC言語でした。
最近では、仕事でもあまり使うこともなく気がつけばもう数年間まともに新規で作成していませんでした。
僕にとってC言語は他の言語に旅立つための基本を植えつけてくれた言語でもあります。
最近出てきた言語は色々なことが簡単に出来て負荷を軽減してくれるのでC言語を使うよりもよりも他の言語が第一選択肢に入ってしまうんですけどね、、最近の言語は本当に理解して使ってるの?ブラックボックスだからこれでいいんだよ!、と思いながら使うことも多々あります。
やっぱね、原点って大事だと思うんです。
今日は忘れかけてる記憶を呼び出してC言語で文字コード変換のプログラムを書いてみようと思います。
●プログラムの概要
sjis←→utf8の文字コード変換を行う
文字列を入力して、変換不可能な文字があった場合「?」に置き換える。
文字コード変換にはiconv関数を利用する。
全角文字を1文字ずつ取得するためには「mblen関数」を利用します。
何故かと言うと、
SJISは1文字を1Byte〜2Byteで表現していますが、
UTF8は1文字を1Byte〜6Byteで表現しています(※1)。
この、面倒くさい感じの1文字取得を「mblen関数」に手伝ってもらうことにします。
(※1)UTF8の1文字は1Byteから3Byteで表現していますが、
BOM(バイトオーダマーク)付きの場合は先頭に3Byteが付加されています。
※文字コード変換は「utf8←→cp932」を行うため、変換不可文字は「cp932→utf8」の変換では存在しない
早速始めたいとこですが、まずは、
今回のプログラムでは「SJIS」と「UTF8」の両方の文字コードを使用する必要があるので「ロケール(※2)」の確認をします。
今回ような処理は入力データの文字コード体系によってロケールを「sjis」と「utf8」をプログラム中で切り替えて使う必要があります。何故かというと、mblen関数で1文字のバイト数が知りたいからです。mblen関数はロケールによって出してくる結果が異なります。
例えば、「あ」の文字はsjisでは2Byte、utf8では3byte使って表現しています。
(※2)そもそもロケールっていうのは
このコンピュータは何処の国の言語でどの文字コード体系を使うのかが設定されています。
Ubuntuを日本語でインストールした場合は文字コードが「ja_JP.utf8」で設定されます。
Windowsでの文字コードは「SJIS(pc932)」が使われています。
LinuxやUNIXでは必要に応じて各国の様々なロケールを扱うことが出来ます。
ロケールの確認「locale -a」で確認出来る
$locale -a C C.UTF-8 POSIX en_AG en_AG.utf8 en_AU.utf8 en_BW.utf8 en_CA.utf8 en_DK.utf8 en_GB.utf8 en_HK.utf8 en_IE.utf8 en_IN en_IN.utf8 en_NG en_NG.utf8 en_NZ.utf8 en_PH.utf8 en_SG.utf8 en_US.utf8 en_ZA.utf8 en_ZM en_ZM.utf8 en_ZW.utf8 ja_JP.utf8 |
そもそも、SJISのロケールは入ってないようですので追加します。
ロケールの追加
sudo localedef -f SHIFT_JIS -i ja_JP ja_JP.SJIS |
ロケールの確認
locale -a C C.UTF-8 POSIX en_AG en_AG.utf8 en_AU.utf8 en_BW.utf8 en_CA.utf8 en_DK.utf8 en_GB.utf8 en_HK.utf8 en_IE.utf8 en_IN en_IN.utf8 en_NG en_NG.utf8 en_NZ.utf8 en_PH.utf8 en_SG.utf8 en_US.utf8 en_ZA.utf8 en_ZM en_ZM.utf8 en_ZW.utf8 ja_JP.sjis ja_JP.utf8 |
で、「ja_JP.sjis」が入ったので、このコンピュータでSJISが扱えるようになりました。
じゃあ次はプログラムです。
「iconv_Chk.c」
#include #include #include #include #include #include /* fgetsの指定バイト数 */ #define READ_SIZE 4096 /* 置き換え文字のサイズ */ #define CONTENT_SIZE 10 /*ダメ文字置き換え文字*/ #define DAMEMOJI "?" char outbuf[CONTENT_SIZE]; int convert(char const *src, char const *dest, char const *text, char *buf, size_t bufsize); void chr_conv(char const *str,char *hairtetu[],int cnt); /* --------------------------------------------------------------- 文字コード変換を行う 1文字ずつ文字コード変換を行う。変換不可文字があった場合は「?」に置き換える 【引数】 1:utf8-->cp932 2:cp932-->utf8 ex)入力文字列をUTF8からcp932に変換して表示 echo "変換文字"|./iconv_Chk 1 ex)入力ファイルをUTF8からcp932に変換して表示 cat aaa.txt|./iconv_Chk 1 【コンパイル】 gcc -o iconv_Chk iconv_Chk.c 【UTF8ロケールで実行】 export LANG=ja_JP.utf8 && echo "日本語表示だよこれ〠と、この3つ㊽㊾㊿はcp932では表示出来ないよ!"|./iconv_Chk 1 実行&確認(結果をUFT8に変換して表示) export LANG=ja_JP.utf8 && echo "日本語表示だよこれ〠と、この3つ㊽㊾㊿はcp932では表示出来ないよ!"|./iconv_Chk 1|iconv -f cp932 -t utf8 【SJISロケールで実行】 export LANG=ja_JP.sjis && echo "日本語表示だよこれ〜と、この3つ|=¥はcp932でも表示出来る!"|./iconv_Chk 2 【コンパイル&実行】 gcc -o iconv_Chk iconv_Chk.c && export LANG=ja_JP.utf8 && echo "日本語表示aああbいcうdえ"|./iconv_Chk 1 ------テストケース-------------- ①UTF8→cp932変換 ・データ[test_utf8.txt] 「日本語表示だよこれ〠と、この3つ㊽㊾㊿はcp932では表示出来ないよ!」 を文字コードUTF8で作成 ・UTF8環境で実行 UTF8→cp932変換 結果をiconvにパイプして変換不可文字「〠㊽㊾㊿」が「?」に変換されていること ・環境設定 export LANG=ja_JP.utf8 ・実行 cat test_utf8.txt|./iconv_Chk 1|iconv -f cp932 -t utf8 ・SJIS環境で実行 UTF8→cp932変換 結果をiconvにパイプして変換不可文字「〠㊽㊾㊿」が「?」に変換されていること ・環境設定 export LANG=ja_JP.sjis ・実行 cat test_utf8.txt|./iconv_Chk 1|iconv -f cp932 -t utf8 ②cp932→UTF8変換 ・データ[test_sjis.txt] 「日本語表示だよこれ〜と、この3つ|=¥はcp932でも表示出来る!」 を文字コードcp932で作成 ・UTF8環境で実行 cp932→UTF8変換 全て変換されていること ・環境設定 export LANG=ja_JP.utf8 ・実行 cat test_sjis.txt|./iconv_Chk 2 ・SJIS環境で実行 cp932→UTF8変換 変換 全て変換されていること ・環境設定 export LANG=ja_JP.sjis ・実行 cat test_sjis.txt|./iconv_Chk 2 テスト実行 echo "-------------------------------------------------------------" export LANG=ja_JP.utf8 cat test_utf8.txt|./iconv_Chk 1|iconv -f cp932 -t utf8 echo "-------------------------------------------------------------" export LANG=ja_JP.sjis cat test_utf8.txt|./iconv_Chk 1|iconv -f cp932 -t utf8 echo "-------------------------------------------------------------" export LANG=ja_JP.utf8 cat test_sjis.txt|./iconv_Chk 2 echo "-------------------------------------------------------------" export LANG=ja_JP.sjis cat test_sjis.txt|./iconv_Chk 2 export LANG=ja_JP.utf8 --------------------------------------------------------------- */ main(int argc, char **argv) { int ret; char *moji; char *moji_to; /*ロケールの設定*/ char *loc; //loc = setlocale( LC_ALL, "ja_JP.sjis" ); loc = setlocale( LC_ALL, "" ); /*システムのlocale設定を使用する*/ printf("LOCALE====%s\n",loc); /*引数の判定*/ int proc = atoi(argv[1]); //1:utf8-->cp932 //2:cp932-->utf8 char *ifrom=""; char *ito=""; switch(proc){ case 1: loc = setlocale( LC_ALL, "ja_JP.utf8" ); /*ロケールをUTF8に変更*/ ifrom="UTF-8"; ito="cp932"; break; case 2: loc = setlocale( LC_ALL, "ja_JP.sjis" ); /*ロケールをSJISに変更*/ ifrom="cp932"; ito="UTF-8"; break; default: break; } /* 標準入力からデータを取得(パイプ)-----------------------------------S */ char *buf; char *buf2; int r=2; int inMojiSize=0; buf = (char *)calloc(READ_SIZE,sizeof(char)); buf2 = (char *)calloc(READ_SIZE,sizeof(char)); while (fgets(buf,READ_SIZE,stdin) != NULL){ /* 容量が足りなくなったら再確保 */ buf2 = (char *)realloc(buf2,READ_SIZE * r ); /* 1行又は指定バイト単位で読み込んだデータを蓄積 */ strcat(buf2,buf); r++; } moji = buf2; /*入力のサイズ取得*/ inMojiSize = strlen(moji); /* 標準入力からデータを取得(パイプ)-----------------------------------E */ int i; int cnt; char *remoji[inMojiSize]; char *retstr[inMojiSize]; /*1文字ずつ配列に格納する*/ chr_conv(moji,remoji,inMojiSize); puts("***************************"); for(cnt=0;remoji[cnt]!='\0';cnt++){ #ifdef DEBUG1 printf("--変換前[%02s] cnt:%d ",remoji[cnt],cnt); #endif /*UTF8->cp932に変換(1文字ずつ)*/ ret = convert(ifrom, ito, remoji[cnt], outbuf, sizeof(outbuf)); if (ret) { #ifdef DEBUG1 printf("変換後[%02s] \n", outbuf); #endif } else { /*変換不能文字があった場合「!NG!」に変換*/ strcpy(outbuf,DAMEMOJI); #ifdef DEBUG1 printf("変換後[%02s] \n", outbuf); #endif } /*1文字ずつをつなぎ合わせる*/ strcat((char *)retstr,outbuf); } puts((char *)retstr); #ifdef DEBUG1 printf("1文字変換後>---len--:%d [%s]\n",strlen(retstr),retstr); puts("\n----------------1文字convert-------------------E"); #endif puts("***************************"); free(buf); free(buf2); } /* --------------------------------------------------------------- 引数で渡された文字列「char const *str」を 1文字ずつにして配列「char *hairtetu[]」に格納して返却する --------------------------------------------------------------- */ void chr_conv(char const *str,char *hairtetu[],int cnt){ int i=0; int j,len; int h=0; char *rep; rep = (char *)malloc(sizeof(char) * cnt ); while(str[i]!='\0'){ /*マルチバイト文字のバイト数を取得(1文字)*/ len= mblen( &str[i], MB_CUR_MAX ); /*ワークをクリア*/ for(j=0;j<cnt;j++){ rep[j]='\0'; } /*文字数を指定してコピー*/ strncpy(rep,str+i,len); if(len == 1){ /*シングルバイト文字*/ } else{ /*マルチバイト文字*/ } /*配列要素を確保して1文字入れる*/ hairtetu[h]=malloc(sizeof(rep)); strcpy(hairtetu[h],rep); h++; i+=len; } /*最終位置の設定*/ hairtetu[h]=malloc(sizeof(rep)); hairtetu[h]='\0'; free(rep); } /* --------------------------------------------------------------- 文字コード変換処理。iconvを利用 --------------------------------------------------------------- */ int convert(char const *src, char const *dest, char const *intext, char *outbuf, size_t bufsize) { iconv_t cd; size_t insrclen, outsrclen; size_t ret; cd = iconv_open(dest, src); if (cd == (iconv_t)-1) { #ifdef DEBUG1 perror("iconv open"); #endif return 0; } insrclen = strlen(intext); outsrclen = bufsize - 1; memset(outbuf, '\0', bufsize); ret = iconv(cd, &intext, &insrclen, &outbuf, &outsrclen); if (ret == -1) { #ifdef DEBUG1 perror("iconv"); #endif return 0; } iconv_close(cd); return 1; } |
上のソースをコピペしても文字が欠落していて使えないので下記を参照
https://gist.github.com/brokendish/6404612
ソース中にも書いてあるけど、コンパイル方法、実行方法
【コンパイル】
gcc -o iconv_Chk iconv_Chk.c
【実行方法】
【引数】
1:utf8–>cp932
2:cp932–>utf8
ex)入力文字列をUTF8からcp932に変換して表示
echo “変換文字”|./iconv_Chk 1
ex)入力ファイルをUTF8からcp932に変換して表示
cat aaa.txt|./iconv_Chk 1
・データ[test_utf8.txt]
「日本語表示だよこれ〠と、この3つ㊽㊾㊿はcp932では表示出来ないよ!」
を文字コードUTF8で作成
・データ[test_sjis.txt]
「日本語表示だよこれ〜と、この3つ|=¥はcp932でも表示出来る!」
を文字コードcp932で作成
【実行】
UTF8→cp932変換 結果をiconvにパイプしてUTF8で表示
cat test_utf8.txt|./iconv_Chk 1|iconv -f cp932 -t utf8
————————————————————-
cp932→UTF8変換して表示
cat test_sjis.txt|./iconv_Chk 2
————————————————————-
【結果】
————————————————————-
hoge@aaa:~/tool/C/iconv$
UTF8→cp932変換 結果をiconvにパイプしてUTF8で表示
hoge@aaa:~/tool/C/iconv$ cat test_utf8.txt|./iconv_Chk 1|iconv -f cp932 -t utf8
LOCALE====ja_JP.utf8
***************************
日本語表示だよこれ?と、この3つ???はcp932では表示出来ないよ!
***************************
————————————————————-
hoge@aaa:~/tool/C/iconv$
cp932→UTF8変換して表示
hoge@aaa:~/tool/C/iconv$ cat test_sjis.txt|./iconv_Chk 2
LOCALE====ja_JP.utf8
***************************
日本語表示だよこれ~と、この3つ|=¥はcp932でも表示出来る!
***************************
※1文字ずつの変換では処理が遅いのでiconvのエラー位置でのポインター操作版はここ
文字コード変換を行い、変換不可文字があった場合は「@」に置き換える。このモジュールは、1文字ずつの変換ではなく全文を変換に通し、変換エラーがあった場合はエラーポインターの位置に「@」を入れ込む
https://gist.github.com/brokendish/6485151