日本語漢字コードの変換

日本人相手のCGIでは必ず日本語をデータとして処理しなくてはなりません。 本書で配布している form.cc は日本語を自動的に適切に処理する ようにしてありますが、いちおう日本語の処理の仕方も学んでおきましょう。

ASCII文字(英数記号等)以外の文字のコンピュータでの指定方法には色々あります。 ここでは日本語の文字の指定方法の3大主流である EUC,SJIS,JIS の 日本語コード間での変換プログラムを作成することを考えます。

単に日本語と言っても、漢字、ひらがら、カタカナの他にも全角の英数字や ギリシャ文字、キリル文字、数学記号、グラフ記号、罫線、等があります。 さらに半角カナや半角句読点もあるので厄介です。


● 漢字コードの種類と特徴


■ JISコード

お馴染み日本工業規格 JIS(Japanse Industrial Standards) の JIS X0208 が定めた情報交換用漢字符合系です。通称として単にJISコードと呼ばれます。 JISコードはASCIIコードと同じく1byteのうち下位7bitしか使いません。 なので古い通信回線を流したり、signed charでの処理ができるので、 電子メールの転送に今でも使われています。

下位7bitしか使わない代わりに 文字列中で以下の特殊な escape sequence で ASCII、全角日本語、半角日本語の3種のモード間の切替えを行って 文字を表現します。

JISのEscape Sequence
ASCIIモードへ切替え \x1b(B
全角日本語モードへ切替え \x1b$B
半角日本語モードへ切替え \x1b(I
注: C言語文字 '\x1b' はこれで1文字で ESCの特殊文字を表す。この16進コードは 0x1b。

Escape Sequenceの挿入の概念図

JIS \x1b$B コード \x1b(J \0

全角日本語モードでは文字1文字を 2 byteで表します。 各バイトの値の範囲は以下の通りです。

--------21===================74-----------------------------------: JIS 全角 上位
--------21=====================7E---------------------------------: JIS 全角 下位
この範囲のすべてに文字が割り当てられているのではなく、 割り当てられていないところも散発的に見受けられます。
日本語かな漢字変換ソフトの「かんな」では F1キーと上下キーで このコードと対応する文字の一覧を眺めることができます。

半角日本語モードでは文字1文字を 1 byteで表します。 バイトの値の範囲は以下の通りです。

--------21=============5F-----------------------------------------: JIS 半角

JISコードで「JISコード」と出力する例。

char str[] = { 'J', 'I', 'S',    // ASCIIで「JIS」と書く
               0x1b, '$', 'B',   // 全角日本語モードへ
               0x25, 0x33,       // コ
               0x21, 0x3C,       // ー
               0x25, 0x49,       // ド
               0x1b, '(', 'B',   // ASCIIモードへ
               '\0' };           // NULL終端
puts(str);

このようにプログラムでJISを扱うのは面倒ですし、 文字列を確保するメモリには escape sequenceのために余分に必要です。


■ 日本語 EUC コード

EUCはExtended Unix Codeの略。UNIX派生のOSで広く一般的に用いられています。 EUCコードは1byteの8bitすべてを用います。 別に韓国語 EUC、中国語 EUCもありますが触れません。

EUCコードでは全角日本語文字1文字を 2 byte で表します。 各バイトの値の範囲は以下の通りです。

----------------------------------------A1===================F4---: EUC 全角 上位
----------------------------------------A1=====================FE-: EUC 全角 下位
この範囲のすべてに文字が割り当てられているのではなく、 割り当てられていないところも散発的に見受けられます。
この範囲はJISコードの最上位bitを1にするだけです。 各文字の割り当ても同じなので、EUCとJISの間の全角文字の変換は 最上位bitを1にするか0にするかだけで行えます。

EUCコードでは半角日本語文字1文字も 2 byte で表します。 各バイトの値の範囲は以下の通りです。

-----------------------------------8E-----------------------------: EUC 半角 上位
--------21=============5F-----------------------------------------: EUC 半角 下位
半角日本語の上位バイトは必ず 0x8E です。下位バイトの範囲はJISと同じで 各文字の割り当てもJISと同じです。

このようにEUCの文字の割り当て方はJISと同じなので EUCはJISのUNIX版とも言えます。

EUCコードで「EUCコード」と出力する例。

  char str[] = { 'E', 'U', 'C',  // ASCIIでEUC
               0xA5, 0xB3,       // コ
               0xA1, 0xBC,       // ー
               0xA5, 0xC9,       // ド
               '\0' };           // NULL終端
puts(str);

■ SJISコード

SJISのSはShiftの略。その名の通りJISコードの文字割り当てをずらしたもの。 SJISは日本製パソコンで日本語を扱えるように考案され、後にMS-DOS日本語版に 取り入れられ、今でも MS-Windows のシリーズで標準的に使われています。 SJISコードは1byteの8bitすべてを用います。

SJISコードでは全角日本語文字1文字を 2 byte で表します。 各バイトの値の範囲は以下の通りです。

--------------------------------81=====9F---------------E0=====FC-: SJIS全角 上位
----------------40=============================================FC-: SJIS全角 下位
SJIS全角日本語には2つの範囲があります。

SJISコードでは半角日本語文字1文字は 1 byte で表します。 バイトの値の範囲は以下の通りです。

----------------------------------------A1=============DF---------: SJIS半角

SJISはJISのコードを複雑な手続きで移動したため、 JISやEUCからの変換は少々面倒です。半角日本語を1 byteにしたため、 全角日本語の上位バイトで使える範囲が狭く、扱える文字数が 少ないです。長所は見ための文字列の長さとバイト数が一致することくらいです。

SJISコードで「SJISコード」と出力する例。

  char str[] = { 'S', 'J', 'I', 'S', // ASCIIでSJIS
               0x83, 0x52,       // コ
               0x81, 0x5B,       // ー
               0x83, 0x68,       // ド
               '\0' };           // NULL終端
puts(str);

● 漢字コードの見分け方

ある日本語文章の漢字コードの判別は以下のようにして行えます。

簡単なのはJISで、文章中に "\x1b$" があれば JIS コードです。 単に "\x1b" だけだとテキストの色や位置を調整する escape sequenceと 混同するので "$" が後続することを確認しなくてはなりません。

EUCとSJISの上位バイトの値の可能な範囲を示すと以下のようになります。

--------------------------------81=====9F---------------E0=====FC-: SJIS全角
----------------------------------------A1=============DF---------: SJIS半角
----------------------------------------A1===================F4---: EUC 全角
-----------------------------------8E-----------------------------: EUC 半角
困ったことはEUCの範囲がSJISの範囲に覆われてEUCの判定ができないことです。 下位バイトもほとんど覆われています。 逃げ道は半角日本語を無視することです。

[0x81,0x9F] の範囲の文字があれば SJIS です。

[0xA1,0xDF] の範囲の文字があれば EUC です。

[0x80,0xFF] の範囲のうち上記以外で、次の文字が A0 以下であれば SJIS です。

この方法で漢字コードを判定する関数を KanjiCode()関数としてCプログラムで実現すると次のようになります。

#define ASCII    0x00
#define EUC      0x01
#define SJIS     0x02
#define JIS      0x04
#define JAPANESE 0xff

//---- 半角カナの存在を無視した漢字コードの判定関数
int KanjiCode( char* text )
{
  for( u_char* ptr=(u_char*)text; *ptr; ptr++ ){
    if( *ptr == 0x1b && *(ptr+1) == '$' ) return  JIS;
    if( *ptr < 0x80 ) continue;
    if( 0x81 <= *ptr && *ptr <= 0x9F ) return SJIS;
    if( 0xA1 <= *ptr && *ptr <= 0xDF ) return  EUC;
    if( *(ptr+1) <= 0xA0 )             return SJIS;
  }
  return ASCII;
}
注意点は0x80以上の数の大小を議論するので unsigned charで比較しなければ ならないことです。


● 全角日本語文字1文字のコード変換

半角日本語を無視して、全角日本語1文字の漢字コードを JIS, EUC, SJISの間で変換する関数を作成します。
u_char型変数 knj1 に上位バイト、knj2に下位バイトが格納されているとします。

■ JIStoEUC

//---- JIS文字をEUC文字に変換する関数
inline void JIStoEUC( u_char& knj1, u_char& knj2 )
{
  knj1 |= 0x80;  knj2 |= 0x80;
}

■ EUCtoJIS

//---- EUC文字をJIS文字に変換する関数
inline void EUCtoJIS( u_char& knj1, u_char& knj2 )
{
  knj1 &= ~0x80;  knj2 &= ~0x80;
}

■ JIStoSJIS

//---- JIS文字をSJIS文字に変換する関数
inline void JIStoSJIS( u_char& knj1, u_char& knj2 )
{
  if( knj1 & 0x01 ){
    knj1 >>= 1;
    if( knj1 < 0x2F ) knj1 += 0x71; else knj1 -= 0x4F;
    if( knj2 > 0x5F ) knj2 += 0x20; else knj2 += 0x1F;
  }else{
    knj1 >>= 1;
    if( knj1 < 0x2F ) knj1 += 0x70; else knj1 -= 0x50;
    knj2 += 0x7E;
  }
}

■ SJIStoJIS

//---- SJIS文字をJIS文字に変換する関数
inline void SJIStoJIS( u_char& knj1, u_char& knj2 )
{
  knj1 <<= 1;
  if( knj2 < 0x9F ){
    if( knj1 < 0x3F ) knj1 += 0x1F; else knj1 -= 0x61;
    if( knj2 > 0x7E ) knj2 -= 0x20; else knj2 -= 0x1F;
  }else{
    if( knj1 < 0x3F ) knj1 += 0x20; else knj1 -= 0x60;
    knj2 -= 0x7E;
  }
}

■ EUCtoSJIS

//---- EUC文字をSJIS文字に変換する関数
inline void EUCtoSJIS( u_char& knj1, u_char& knj2 )
{
  if( knj1 & 0x01 ){
    knj1 >>= 1;
    if( knj1 < 0x6F ) knj1 += 0x31; else knj1 += 0x71;
    if( knj2 > 0xDF ) knj2 -= 0x60; else knj2 -= 0x61;
  }else{
    knj1 >>= 1;
    if( knj1 < 0x6F ) knj1 += 0x30; else knj1 += 0x70;
    knj2 -= 0x02;
  }
}

■ SJIStoEUC

//---- SJIS文字をEUC文字に変換する関数
inline void SJIStoEUC( u_char& knj1, u_char& knj2 )
{
  knj1 <<= 1;
  if( knj2 < 0x9F ){
    if( knj1 < 0x3F ) knj1 -= 0x61; else knj1 += 0x1F;
    if( knj2 > 0x7E ) knj2 += 0x60; else knj2 += 0x61;
  }else{
    if( knj1 < 0x3F ) knj1 -= 0x60; else knj1 += 0x20;
    knj2 += 0x02;
  }
}

● 日本語文章のコード変換

上記の関数を用いて日本語文章の漢字コードを JIS, EUC, SJISの間で変換する関数を作成します。
関数の引数に文章へのアドレスを受け取ることにして、 そのアドレスには十分な量のメモリが確保されていることを仮定して、 同じアドレスに変換語の文章を格納することにしましょう。JISへ変換すると ESCのためサイズが太るのです。


■ JIStoEUC

void JIStoEUC( char* text )
{
  int mode = ASCII;

  u_char *wr, *re;
  for( wr=re=(u_char*)text; *re; re++ ){
    if( (re[0]=='\x1b' && re[1]=='$' && re[2] == 'B' ) ||
        (re[0]=='\x1b' && re[1]=='$' && re[2] == '@' ) ){
      re+=2; 
      mode = JAPANESE;
      continue;
    }else if( (re[0]=='\x0f') ||
        (re[0]=='\x1b' && re[1]=='(' && re[2] == 'B' ) ||
        (re[0]=='\x1b' && re[1]=='(' && re[2] == 'J' ) ){
      re+=2; 
      mode = ASCII;
      continue;
    }else if( (re[0]=='\x0e') ||
        (re[0]=='\x1b' && re[1]=='(' && re[2] == 'I' ) ){
      re+=2; 
      mode = ASCII; // hankaku IGNORE
      continue;
    }

    if( mode == ASCII ){
      *wr++ = *re;
      continue;
    }
    *wr++ = *re;
    if( !(*wr = *++re) ) break;
    JIStoEUC( *(wr-1), *wr );
    wr++;
  }
  *wr='\0';
}

■ EUCtoJIS

void EUCtoJIS( char* text )
{
  int mode=ASCII;

  char* buf2 = strdup(text);
  u_char *wr, *re;
  for( wr=(u_char*)text, re=(u_char*)buf2; *re; re++ ){
    if( *re < 0x80 ){
      if( mode != ASCII ){
	mode  = ASCII;
	*wr++ = '\x1b';	*wr++ = '(';	*wr++ = 'B';
      }
      *wr++ = *re;
    }else{
      if( mode != JAPANESE ){
	mode  = JAPANESE;
	*wr++ = '\x1b';	*wr++ = '$';	*wr++ = 'B';
      }
      *wr++ = *re;
      if( !(*wr = *++re) ) break;
      EUCtoJIS( *(wr-1), *wr );
      wr++;
    }
  }
  if( mode != ASCII ){
    mode  = ASCII;
    *wr++ = '\x1b';  *wr++ = '(';  *wr++ = 'B';
  }
  *wr = '\0';

  free(buf2);
}

■ JIStoSJIS

void JIStoSJIS( char* text )
{
  int mode = ASCII;

  u_char *wr, *re;
  for( wr=re=(u_char*)text; *re; re++ ){
    if( (re[0]=='\x1b' && re[1]=='$' && re[2] == 'B' ) ||
        (re[0]=='\x1b' && re[1]=='$' && re[2] == '@' ) ){
      re+=2; 
      mode = JAPANESE;
      continue;
    }else if( (re[0]=='\x0f') ||
        (re[0]=='\x1b' && re[1]=='(' && re[2] == 'B' ) ||
        (re[0]=='\x1b' && re[1]=='(' && re[2] == 'J' ) ){
      re+=2; 
      mode = ASCII;
      continue;
    }else if( (re[0]=='\x0e') ||
        (re[0]=='\x1b' && re[1]=='(' && re[2] == 'I' ) ){
      re+=2; 
      mode = ASCII; // hankaku IGNORE
      continue;
    }

    if( mode == ASCII ){
      *wr++ = *re;
      continue;
    }
    *wr++ = *re;
    if( !(*wr = *++re) ) break;
    JIStoSJIS( *(wr-1), *wr );
    wr++;
  }
  *wr='\0';
}

■ SJIStoJIS

void SJIStoJIS( char* text )
{
  int mode = ASCII;

  char* buf2 = strdup(text);
  u_char *wr, *re;
  for( wr=(u_char*)text, re=(u_char*)buf2; *re; re++ ){
    if( *re < 0x80 ){
      if( mode != ASCII ){
	mode  = ASCII;
	*wr++ = '\x1b';	*wr++ = '(';	*wr++ = 'B';
      }
      *wr++ = *re;
    }else{
      if( mode != JAPANESE ){
	mode  = JAPANESE;
	*wr++ = '\x1b';	*wr++ = '$';	*wr++ = 'B';
      }
      *wr++ = *re;
      if( !(*wr = *++re) ) break;
      SJIStoJIS( *(wr-1), *wr );
      wr++;
    }
  }
  if( mode != ASCII ){
    mode  = ASCII;
    *wr++ = '\x1b';  *wr++ = '(';  *wr++ = 'B';
  }
  *wr = '\0';

  free(buf2);
}

■ EUCtoSJIS

void EUCtoSJIS( char* text )
{
  for( u_char* ptr=(u_char*)text; *ptr; ptr++ ){
    if( *ptr < 0x80 ) continue;
    if( !(*++ptr) ) break;
    EUCtoSJIS( *(ptr-1), *ptr );
  }
}

■ SJIStoEUC

void SJIStoEUC( char* text )
{
  for( u_char* ptr=(u_char*)text; *ptr; ptr++ ){
    if( *ptr < 0x80 ) continue;
    if( !(*++ptr) ) break;
    SJIStoEUC( *(ptr-1), *ptr );
  }
}

● 日本語文章の指定コードへの統一変換

プログラムが必要なコードはひとつなのに ユーザーが入力するコードがばらばらであることが問題なので、 ここで任意の漢字コードをプログラムが必要なひとつのコードに 変換する関数を用意しましょう。

void ANYtoJIS( char* text )
{
  switch( KanjiCode( text ) ){
    case JIS  :                    break;
    case EUC  : EUCtoJIS( text );  break;
    case SJIS : SJIStoJIS( text ); break;
    case ASCII:                    break;
  }
}

void ANYtoEUC( char* text )
{
  switch( KanjiCode( text ) ){
    case JIS  : JIStoEUC( text );  break;
    case EUC  :                    break;
    case SJIS : SJIStoEUC( text ); break;
    case ASCII:                    break;
  }
}

void ANYtoSJIS( char* text )
{
  switch( KanjiCode( text ) ){
    case JIS  : JIStoSJIS( text ); break;
    case EUC  : EUCtoSJIS( text ); break;
    case SJIS :                    break;
    case ASCII:                    break;
  }
}

● 半角カナの問題

半角カナの面倒まで見ようとなるとかなり大変です。 半角カナが嫌われていることはだいぶ知られているようですが、 句読点が半角日本語で書かれてしまう場合があります。 本書の form.cc では半角カナを扱っていませんが、 もし、どうしても半角カナを扱いたい人は、以下に用意した関数を ヒントに御自分でなんとかしてください。 以下の関数は各日本語コードでの半角文字を全角文字に変換する関数です。 半角カナはEUCでは2バイトですが、SJIS、JISでは1バイトです。 全角カナはどのコードでも2バイトです。 EUCの場合は関数 EUC_HtoZ の 第1引数に半角カナの第1バイトを格納した変数を与え、 第2引数に半角カナの第2バイトを格納した変数を与えてください。 両引数の変数には全角カナでのコードが格納されます。 SJIS,JISの場合は関数 SJIS_HtoZ, JIS_HtoZ の 第1引数に半角カナの第1バイトを格納した変数を与え、 第2引数には何もいれていない変数を与えてください。 両引数の変数には全角カナでのコードが格納されます。

void EUC_HtoZ( u_char& kana0, u_char& kana1 ){
  static u_char kana_table[] = {
    0xA1,0xA3,0xA1,0xD6,0xA1,0xD7,0xA1,0xA2,
    0xA1,0xA6,0xA5,0xF2,0xA5,0xA1,0xA5,0xA3,
    0xA5,0xA5,0xA5,0xA7,0xA5,0xA9,0xA5,0xE3,
    0xA5,0xE5,0xA5,0xE7,0xA5,0xC3,0xA1,0xBC,
    0xA5,0xA2,0xA5,0xA4,0xA5,0xA6,0xA5,0xA8,
    0xA5,0xAA,0xA5,0xAB,0xA5,0xAD,0xA5,0xAF,
    0xA5,0xB1,0xA5,0xB3,0xA5,0xB5,0xA5,0xB7,
    0xA5,0xB9,0xA5,0xBB,0xA5,0xBD,0xA5,0xBF,
    0xA5,0xC1,0xA5,0xC4,0xA5,0xC6,0xA5,0xC8,
    0xA5,0xCA,0xA5,0xCB,0xA5,0xCC,0xA5,0xCD,
    0xA5,0xCE,0xA5,0xCF,0xA5,0xD2,0xA5,0xD5,
    0xA5,0xD8,0xA5,0xDB,0xA5,0xDE,0xA5,0xDF,
    0xA5,0xE0,0xA5,0xE1,0xA5,0xE2,0xA5,0xE4,
    0xA5,0xE6,0xA5,0xE8,0xA5,0xE9,0xA5,0xEA,
    0xA5,0xEB,0xA5,0xEC,0xA5,0xED,0xA5,0xEF,
    0xA5,0xF3,0xA1,0xAB,0xA1,0xAC };

  if( kana0!=0x8E ) return;
  if( !(0x21<=kana1 && kana1<=0x5F ) ) return;

  const int index = 2*(kana1-0x21);
  kana0 = kana_table[index+0];
  kana1 = kana_table[index+1];
}

void SJIS_HtoZ( u_char& kana0, u_char& kana1 ){
  static u_char kana_table[] = {
    0x81,0x42,0x81,0x75,0x81,0x76,0x81,0x41,
    0x81,0x45,0x83,0x92,0x83,0x40,0x83,0x42,
    0x83,0x44,0x83,0x46,0x83,0x48,0x83,0x83,
    0x83,0x85,0x83,0x87,0x83,0x62,0x81,0x5B,
    0x83,0x41,0x83,0x43,0x83,0x45,0x83,0x47,
    0x83,0x49,0x83,0x4A,0x83,0x4C,0x83,0x4E,
    0x83,0x50,0x83,0x52,0x83,0x54,0x83,0x56,
    0x83,0x58,0x83,0x5A,0x83,0x5C,0x83,0x5E,
    0x83,0x60,0x83,0x63,0x83,0x65,0x83,0x67,
    0x83,0x69,0x83,0x6A,0x83,0x6B,0x83,0x6C,
    0x83,0x6D,0x83,0x6E,0x83,0x71,0x83,0x74,
    0x83,0x77,0x83,0x7A,0x83,0x7D,0x83,0x7E,
    0x83,0x80,0x83,0x81,0x83,0x82,0x83,0x84,
    0x83,0x86,0x83,0x88,0x83,0x89,0x83,0x8A,
    0x83,0x8B,0x83,0x8C,0x83,0x8D,0x83,0x8F,
    0x83,0x93,0x81,0x4A,0x81,0x4B };


  if( !(0xA1<=kana0 && kana0<=0xDF ) ) return;

  const int index = 2*(kana0-0xA1);
  kana0 = kana_table[index+0];
  kana1 = kana_table[index+1];
}

void JIS_HtoZ( u_char& kana0, u_char& kana1 ){
  static u_char kana_table[] = {
    0x21,0x23,0x21,0x56,0x21,0x57,0x21,0x22,
    0x21,0x26,0x25,0x72,0x25,0x21,0x25,0x23,
    0x25,0x25,0x25,0x27,0x25,0x29,0x25,0x63,
    0x25,0x65,0x25,0x67,0x25,0x43,0x21,0x3C,
    0x25,0x22,0x25,0x24,0x25,0x26,0x25,0x28,
    0x25,0x2A,0x25,0x2B,0x25,0x2D,0x25,0x2F,
    0x25,0x31,0x25,0x33,0x25,0x35,0x25,0x37,
    0x25,0x39,0x25,0x3B,0x25,0x3D,0x25,0x3F,
    0x25,0x41,0x25,0x44,0x25,0x46,0x25,0x48,
    0x25,0x4A,0x25,0x4B,0x25,0x4C,0x25,0x4D,
    0x25,0x4E,0x25,0x4F,0x25,0x52,0x25,0x55,
    0x25,0x58,0x25,0x5B,0x25,0x5E,0x25,0x5F,
    0x25,0x60,0x25,0x61,0x25,0x62,0x25,0x64,
    0x25,0x66,0x25,0x68,0x25,0x69,0x25,0x6A,
    0x25,0x6B,0x25,0x6C,0x25,0x6D,0x25,0x6F,
    0x25,0x73,0x21,0x2B,0x21,0x2C };

  if( !(0x21<=kana0 && kana0<=0x5F ) ) return;

  const int index = 2*(kana0-0x21);
  kana0 = kana_table[index+0];
  kana1 = kana_table[index+1];
}

目次

Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp