文字コードとエンコーディング (2)

前へ << 文字コードとエンコーディング (1) 秘密情報の管理 >> 次へ

機種依存文字とは

JIS X 0208 には未定義領域が存在します。ここに ベンダが勝手に文字を定義したものを機種依存文字と言います。

機種依存文字の種類と文字コードについては http://www2d.biglobe.ne.jp/~msyk/charcode/kisyuizon/index.html が便利です。特に、こちらには Windows 環境がなかったので、 とても参考になりました。

半角カナとは

半角カナとは、JIS X 0201 の右半面にあるカタカナ文字です。 Shift_JIS、EUC-JP では、半角カナを使っても構いません。 しかし、ISO-2022-JP では使ってはいけないのです。
再度確認しておきますが、JIS X 0208 や JIS X 0201 などは 文字の番号を決めている文字集合です。一方、Shift_JIS・ EUC-JP、ISO-2022-JP などはエンコーディング、つまり JIS X 0208・JIS X 0201 などの何種類かの文字集合を 同時に扱うための方法を決めたものです。

自動変換

まずは機種依存文字から。 丸1やギリシャ文字などの機種依存文字は、 JIS X 0208 の未定義領域に割り当てられています。 UNIX 系 OS では、その部分に対応するフォントが用意されていないため、 ただの空白に見えます。Mac では全く別のフォントが割り当てられているため、 別の文字に見えます。

web なんてのはどの環境からでもアクセスできるのが魅力なのですから、 こういう障害は CGI プログラム側で取り除きましょう。

例えば丸1は EUC-JP では 0xADA1 になりますが、 Shift-JIS では 0x8740、ISO-2022-JP では 0x2D21 になります。 ですから、まず文字コードを統一してから機種依存文字を変換するのが お手軽です。 ここでは文字列を まず EUC-JP に変換して、それから 0xADA1 を (1) に、 0xADA2 を (2) に変換…というふうにしましょう。

まず思い付くのが

require 'jcode.pl';
$str = "丸1丸2"; # 「機種依存文字の丸1」「機種依存文字の丸2」だと思って下さい。
&jcode::convert(\$str,'euc');
$str =~ s/\xAD\xA1/(1)/g;
$str =~ s/\xAD\xA2/(2)/g;
....
という書き方ですが、こういう単純な変換ではダメです。 なぜかというと…解説し出すと長くなるので http://www.din.or.jp/~ohzaki/perl.htm#JP_Match を読んで下さい。
なお、このページはとても参考になります。perl や CGI の解説というと、 いいかげんで間違いだらけのページが多いのですが、 ここは数少ない「正しい知識を得られる」ページです。ぜひ読みましょう。
というわけで、自動変換は こんな感じになります。

conv-wrong-char.cgi (実行結果)

    1: #!/usr/local/bin/perl
    2: 
    3: # 自分自身のファイル名を取得
    4: $script_name = $ENV{SCRIPT_NAME};
    5: $script_name =~ s|.*/([^/]+)$|$1|;
    6: 
    7: print <<END;
    8: Content-type: text/html
    9: 
   10: <HTML><BODY BGCOLOR="#eeeeee">
   11: <CENTER><H1>半角カナ・機種依存文字変換フォーム</H1></CENTER>
   12: <HR NOSHADE><TABLE BORDER><TR><TD><PRE>
   13: --[半角カナ]----------------------------------------------------
   14: 。「」、・ヲァィゥェォャュョッアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフセホマミムメモヤユヨラリルレロワン゙゚
   15: --[13区]--------------------------------------------------------
   16: �´↓��きキΝЛ┃��������������悪鵜沖貝喚記境鍬賢広梱鮫辞臭将伸�
   17: �銑足鱈牒鏑騰猫鉢畢吻豊稔有劉廊亅儉勠哭圻姚岫廰忰扠抬朖椨櫚洵澂�
   18: �甅皚祗筬絖罩膈茘薛雖覘讚蹲遶鎰隲餃鬮鶚���������������������    
   19: --[92区]--------------------------------------------------------
   20: 
   21: �●����������������������������淫奥戒栢期亨釧懸厚骨匙慈囚除嘱頗�
   22: �像凧凋諦糖日莫琵復放万愈梨簾��傅劈哄囿奧屆廈惧戌撩曉棡檄沮滯燧�
   23: �癧磅笨糶纜脣苜蕣蝨褝譛踰逵鋺陷顫髴鴾龕���������������������    
   24: </PRE>
   25: </TABLE>
   26: ↑この辺の文字を下のテキストエリアにコピーして下さい。<BR>
   27: Windows 以外の環境では わからないでしょうが、[13区] 以降に機種依存文字が
   28: 表示されています。
   29: <P>
   30: <FORM METHOD=GET ACTION="$script_name">
   31: <TEXTAREA NAME=CONTENT COLS=60 ROWS=8>
   32: </TEXTAREA>
   33: <INPUT TYPE=SUBMIT VALUE=OK><INPUT TYPE=RESET VALUE=CLEAR>
   34: </FORM>
   35: END
   36: 
   37: #----------- 文字列を取得 ------------------
   38: require 'jcode.pl';
   39: 
   40: foreach ( split(/&/,$ENV{QUERY_STRING}) ){
   41:     ($name,$value) = split(/=/, $_);
   42:     $value =~ tr/+/ /;
   43:     $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex($1))/eg;
   44: 
   45:     &jcode::convert(\$value,'euc');
   46: 
   47:     $value =~ s/&/&amp;/g;
   48:     $value =~ s/</&lt;/g;
   49:     $value =~ s/>/&gt;/g;
   50:     $value =~ s/\n/<BR>\n/g;
   51: 
   52:     $FORM{$name} = $value;
   53: }
   54: 
   55: #----- %conv_table を初期化 ----
   56: &init_table;
   57: 
   58: #----- アスキーコード・EUC の2バイト・EUC の3バイトの正規表現 -----
   59: $ascii = '[\x00-\x7F]';
   60: $twoBytes = '[\xA1-\xFE][\xA1-\xFE]';
   61: $threeBytes = '\x8F[\xA1-\xFE][\xA1-\xFE]';
   62:    
   63: #----- 変換対象は -----
   64: #   半角カナ 8EA0-8EDF
   65: #   13区     ADA1-ADFE
   66: #   89区     F9A1-F9FE
   67: #   90区     FAA1-FAFE
   68: #   91区     FBA1-FBFE
   69: #   92区     FCA1-FCFE
   70: $pattern='\x8E[\xA0-\xDF]|[\xAD\xAE\xF9\xFA\xFB\xFC][\xA1-\xFE]';
   71: 
   72: #------- 変換前の文字列を表示 ---------
   73: print "<HR NOSHADE>\n変換前 <TABLE BORDER><TR><TD>$FORM{CONTENT}</TABLE>\n";
   74: 
   75: #-------- 変換 -------
   76: $found_count = $FORM{CONTENT} =~ 
   77:     s!\G(($ascii|$twoBytes|$threeBytes)*?)($pattern)!
   78:             $1<FONT COLOR=RED>$conv_table{$3}</FONT>!g;
   79: 
   80: #-------- 無駄なタグを消す ------------
   81: $FORM{CONTENT} =~ s|</FONT><FONT COLOR=RED>||g;
   82: 
   83: #------- 変換後の文字列を表示 ---------
   84: print "\n<P>変換後 (変換した機種依存文字は\n";
   85: print "<FONT COLOR=RED>赤色</FONT>で表示されます)\n";
   86: print "<TABLE BORDER><TR><TD>$FORM{CONTENT}</TABLE>\n<P>";
   87: 
   88:  if ( $found_count == 0 ){
   89:      print "半角カナ・機種依存文字は見付かりませんでした。";
   90:  } else {
   91:      print "半角カナ・機種依存文字が $found_count 個 見付かりました。";
   92:  }
   93: print "<P></BODY></HTML>\n";
   94: 
   95: exit;
   96: 
   97: 
   98: #------------- %conv_table を初期化 -------------------
   99: sub init_table {
  100:     %conv_data =(
  101:               # 半角カナ (0x8EA0 も一応定義していることに注意)
  102:               0x8EA0=>
  103:               qq( 。 「 」 、 ・ ヲ ァ ィ ゥ ェ ォ ャ ュ ョ ッ
  104:                  ー ア イ ウ エ オ カ キ ク ケ コ サ シ ス セ ソ
  105:                  タ チ ツ テ ト ナ ニ ヌ ネ ノ ハ ヒ フ ヘ ホ マ
  106:                  ミ ム メ モ ヤ ユ ヨ ラ リ ル レ ロ ワ ン ゛ ゜),
  107: 
  108:               # 13区
  109:               0xADA1=>
  110:               '(1) (2) (3) (4) (5) (6) (7) (8) (9)
  111:                (10) (11) (12) (13) (14) (15) (16)
  112:                (17) (18) (19) (20) I II III IV
  113:                V VI VII VIII IX X . ミリ
  114:                キロ センチ メートル グラム トン アール ヘクタール リットル
  115:                ワット カロリー ドル セント パーセント ミリバール ページ mm
  116:                cm km mg kg cc m^2 . . . . . . . . 平成 "
  117:                ,, No. K.K. Tel (上) (中) (下) (左)
  118:                (右) (株) (有) (代) 明治 大正 昭和
  119:                ≒ ≡ ∫ ∫ Σ √ ⊥
  120:                ∠ L △ ∵ ∩ ∪',
  121: 
  122:               # 89区
  123:               0xF9A1=>
  124:               '. . . . . . . . . . . . . . . .
  125:                . . . . . . . . . . . . . . . .
  126:                . . . . . . . . . . . . . . . .
  127:                . . . . . . . . . . . . . . . .
  128:                . . . . . . . . . . . . . . . .
  129:                . . . . . . . . . . . . . .',
  130: 
  131:               # 90区
  132:               0xFAA1=>
  133:               '. . . . . . . . . . . . . . . .
  134:                . . . . . . . . . . . . . . . .
  135:                . . . . . . . . . . . . . . . .
  136:                . . . . . . . . . . . . . . . .
  137:                . . . . . . . . . . . . . . . .
  138:                . . . . . . . . . . . . . .',
  139: 
  140:               # 91区
  141:               0xFBA1=>
  142:               '. . . . . . . . . . . . . . . .
  143:                . . . . . . . . . . . . . . . .
  144:                . . . . . . . . . . . . . . . .
  145:                . . . . . . . . . . . . . . . .
  146:                . . . . . . . . . . . . . . . .
  147:                . . . . . . . . . . . . . .',
  148: 
  149:               # 92区
  150:               0xFCA1=>
  151:              q{. . . . . . . . . . . . . . . .
  152:                . . . . . . . . . . . . . . . .
  153:                . . . . . . . . . . . . . . . .
  154:                . . . . . . . . . . . . . . . .
  155:                . . . . . . . . . . . . . . . .
  156:                i ii iii iv v vi vii viii ix x ¬ | ' ''},
  157:         );
  158: 
  159: 
  160:     #----- 変換用テーブル作成 ------
  161:     foreach ( keys %conv_data ){
  162:         my $code = $_;
  163:         foreach ( split(/[\n\s]+/,$conv_data{$_}) ){
  164:            my $char_code = pack('C*',$code/256,$code%256);
  165:            if ( $_ eq '.' ){
  166:                $_ = "〓"; # 未定義領域か漢字領域。ゲタに変換。
  167:            }
  168:            $conv_table{$char_code} = $_;
  169:            $code++;
  170:         }
  171:     }
  172: }
まずは
   56: &init_table;
から。&init_table の中ではいろいろやってますが、 最終的には
$conv_table{pack('C*',0x8E,0xA0)}="。";
$conv_table{pack('C*',0x8E,0xA1)}="、";
                 :
$conv_table{pack('C*',0x8E,0xB1)}="ア";
$conv_table{pack('C*',0x8E,0xB2)}="イ";
                 :
$conv_table{pack('C*',0xAD,0xA1)}="(1)";
$conv_table{pack('C*',0xAD,0xA2)}="(2)";
$conv_table{pack('C*',0xAD,0xA3)}="(3)";
                 :
$conv_table{pack('C*',0xAD,0xB5)}="I";
$conv_table{pack('C*',0xAD,0xB6)}="II";
$conv_table{pack('C*',0xAD,0xB7)}="III";
                 :
というふうに、機種依存文字の文字コードと変換後の文字をセットします。 例えば 0xADB7 は EUC のギリシャ数字の3 なので「III」に変換する、 ということです。


   59: $ascii = '[\x00-\x7F]';
   60: $twoBytes = '[\xA1-\xFE][\xA1-\xFE]';
   61: $threeBytes = '\x8F[\xA1-\xFE][\xA1-\xFE]';
正規表現で EUC コードを正しく扱うための前準備をします。 をそのまま正規表現に直すと先のような形になります。


   63: #----- 変換対象は -----
   64: #   半角カナ 8EA0-8EDF
   65: #   13区     ADA1-ADFE
   66: #   89区     F9A1-F9FE
   67: #   90区     FAA1-FAFE
   68: #   91区     FBA1-FBFE
   69: #   92区     FCA1-FCFE
   70: $pattern='\x8E[\xA0-\xDF]|[\xAD\xAE\xF9\xFA\xFB\xFC][\xA1-\xFE]';
変換対象である半角カナと機種依存文字の文字コードを記述します。 これもそのまんま正規表現に直せばよいのです。 となって、 となって、 と、真ん中に `|' を挟んで完成です。


   76: $found_count = $FORM{CONTENT} =~ 
   77:     s!\G(($ascii|$twoBytes|$threeBytes)*?)($pattern)!
   78:             $1<FONT COLOR=RED>$conv_table{$3}</FONT>!g;
よくわからない人は $text =~ s!foo!bar!g; を考えて下さい。これは理解できますね。 それを $count = $text =~ s!foo!bar!g; とすると、 マッチした回数が $count に代入されます。 で、その foo と bar をちょっと難しくすると上記の正規表現になります。


これで変換自体は終わったのですが、機種依存文字が連続していると

<FONT COLOR=RED>(1)</FONT><FONT COLOR=RED>(2)</FONT><FONT COLOR=RED>(3)</FONT>
となってしまうので、
   76: $found_count = $FORM{CONTENT} =~ 
   77:     s!\G(($ascii|$twoBytes|$threeBytes)*?)($pattern)!
   78:             $1<FONT COLOR=RED>$conv_table{$3}</FONT>!g;
で、
<FONT COLOR=RED>(1)(2)(3)</FONT>
と、無駄なタグを消します。半角カナ・機種依存文字を置換するなら このスクリプトのようにすればよいですし、半角カナ・機種依存文字が 存在するかどうかを調べたいだけなら $found_count の数を 見ればよいでしょう。

前へ << 文字コードとエンコーディング (1) 秘密情報の管理 >> 次へ

$Id: char-code-2.html,v 1.4 2004/12/04 15:33:40 68user Exp $