[サンスクリットページ雑感集・技術情報]

語彙検索CGIで用いるべき日本語コードについて

Since 2005/10/5 Last Updated 2007/2/23



  1. 問題の所在
     WEB上で用いられている日本語のコードは、主に、ISO-2022-JP、シフトJIS、EUC、Unicode(Unicodeにもいろいろあるが多くはUTF-8)の4種類である。
     今後はUnicodeが主流になるのかもしれないし、単にページを作るだけだったらエディタがUnicodeをサポートしていればすむのであるが、CGI(当サイトの語彙集や掲示板などのように、プログラム処理をしているページ)ではそうはいかない。CGI用のプログラム言語ではPerlという言語を用いることが多いが、PerlでCGIを作る場合、特にデータとして日本語を扱うときは、フリーで配付されているコード変換プログラムjcode.plがUnicodeをサポートしていない関係で、まだまだUnicodeを使うのはつらい。
     特に、当サイトのように、携帯電話端末をもサポートする場合は、携帯電話端末の多くがシフトJISしかサポートしていないので、内部的にどういうコードを用いようとも、最終的にはシフトJISに変換して表示しなければならない。そこでjcode.plのようなコード変換プログラムが必須なのだが、これがUnicodeをサポートしていないので、Unicodeは使いにくいわけである。
     jcode.plはISO-2022-JP、シフトJIS、EUCの三者間を自由にコンバートしてくれるので、ISO-2022-JPを用いてもいいのだが、ISO-2022-JPは漢字部と英数字部の間に「ここから漢字だよ」「ここからは英数字だよ」という標識のコードを挿入せねばならず、データ処理上はわずらわしい。そこで、データとして日本語を扱う場合は、シフトJISかEUCかということになる。
     パソコンではDOS→WindowsにかけてシフトJISが主流だったし、今でも主流であるが、プログラミングではこのシフトJISは非常に評判が悪い。C、Perl、PHPなど、多くのプログラミング言語では、\記号を特別な意味で用いるのであるが、シフトJISの中には、\と同じコードを用いている漢字がいくつかあり、これがイタズラをしてうまく処理ができないのである。そこで多くのプログラミング参考書ではEUCを用いることを推奨している。たとえば
    EUCで記述しておけばこのようなことがないので、できればEUCで書くことをおすすめします。(藤田郁+三島俊司著『CGI&Perl ポケットリファレンス』技術評論社)
    のように。
     ここに引用した本の記述は、CGI自体をどのコードで記述すべきかという話である。このときはたしかにEUCで書くほうが問題はない。
     しかし、データ処理となると別である。特に当サイトの語彙集CGIのように、文字列検索をする場合、EUCには非常に問題があるのである。具体的には当サイトのサンスクリットやウルドゥー語/ヒンディー語語彙集CGIでは、EUCを用いていたのだが、訳文の検索で漢字1文字の語を検索しようとするとたいていうまくいかず、無関係の語をずらずら表示してしまっていた。これを回避するのにずいぶん悩み、結局はシフトJISを用いることで擬似的に問題を解決することができた。
     この話はあまり多くの人が気づいていないようで、プログラミング参考書をずいぶん探したし、WEBサイトもいろいろ検索してみたのだが、あまり書かれていない。そこで、僭越ながらまんどぅーかが、このときの体験をもとに、EUCの問題点を指摘することとする。



  2. JIS X 0208(区点コード)系各コードのしくみと特徴
     上述のように、WEB上で用いられている日本語のコードは、主に、ISO-2022-JP、シフトJIS、EUC、Unicodeである。このうちUnicodeは完全に別物のコードであるが、他の3つは実は、JIS X 0208(通称「区点コード」)という同じ起源をもつコードである。以下、これらの関係を説明する。


    1. JIS X 0208(区点コード)
       従来、コンピュータで文字を扱うときは、8ビット(2進数で8ケタ)をひとまとまりにしていた。これを「1バイト」と呼ぶ。1バイト=8ビットで表される文字の種類は、2の8乗=256種類までである。英数字だけならこの中にすっぽり納まるので、1文字=1バイト=8ビットで処理をしてきたわけである。
       しかし、漢字(以下、ひらがなやカタカナや全角英数字を含む)はいっぱいあるので256種類では不足である。
       そこでJISでは、漢字1文字を2バイト分、16ビットでコード化した。これならば256×256で最大65536種類の文字を扱うことができるわけであるが、現実には後述のようないろいろな制約があり、256×256でなく、94×94で、最大8836種類の文字を扱うことができる。
       このように漢字1文字は2バイトで表すわけだが、最初の1バイトを「第1バイト」、次の1バイトを「第2バイト」と呼ぶ。たとえば「蛙」は、JIS X 0208では「1931」となる。前半の19が第1バイト、後半の31が第2バイトである。特にJIS X 0208ではこれを「19区31点」という呼び方をする。だからJIS X 0208を通称「区点コード」というのである。
       さて、この区点コードは、このままでは既存の英数字のコードと折り合いが悪い点がある。それは、
      1. コンピュータにとって便利な16進数ではなく、10進数になっている。
      2. 既存のコードで制御用に用いている0〜31(16進数00〜1F)を用いている。
      という2点である。このうち1.は、単に16進数表記に直せばいいだけの話であるが、2.については少々説明が必要であろう。
       既存の英数字のコードは、上記のように256種類まで使えるわけであるが、現実にはそのすべてを用いているわけでなく、概略次のような使い分けをしている。
      1. 0〜32(16進数00〜20)……改行などの制御用。一般の文字では使えない。
      2. 33〜126(16進数21〜7E)……英数字。
      3. 127(16進数7F)……制御用。
      4. 128〜255(16進数80〜FF)……未使用。ただし、パソコンでは草創期は次のような使い方をしていた。
        1. 128〜160(16進数80〜A0)……上記(16進数)00〜20に準拠して制御用。もしくは各機種独自の図形文字。
        2. 161〜223(16進数A0〜DF)……半角カタカナ
        3. 240〜254(16進数E0〜FE)……未使用。もしくは各機種独自の図形文字。
        4. 255(16進数FF)……上記(16進数)7Fに準じて制御用。
       10進数と16進数の併記がわずらわしいので、以下は断りない限りすべて16進数に統一する。
       つまり、00〜20というのは、特別な意味を持つコードとして使われてきた。たとえば1Aは「ファイルが終わり」、09は「タブ」、20は「スペース」など。区点コードは無造作にこの部分を用いているので、このままでは非常に紛らわしく、処理が面倒なわけである。
       そこで、区点コードに一定の演算をほどこして、既存のコードと折り合いをつける工夫がなされてきた。このやり方の違いで、ISO-2022-JP、EUC-JP、シフトJISという3種類のコードに分かれてしまうのである。


    2. ISO-2022-JP(いわゆるJISコード)
       まず、ISO-2022-JPは、区点コードの1〜94、16進数でいう01〜5Eに、そのまま20を加え、21〜7Eとしたものである。たとえば「蛙」は、区点コード19区31点、これを16進数で書けば131Fとなろうが、第1バイトにも第2バイトにも20を加えて、333Fとする。
       上で、「256×256で最大65536種類の文字を扱うことができるわけであるが、現実には後述のようないろいろな制約があり、256×256でなく、94×94で」と書いた。なんで94なのかというのが実はこれである。つまり、既存の英数字のコードが21〜7E、10進数では33〜126で、94種類というわけである。区点コードは実は最初からこのような使い方を想定して決められていたのだ。
       しかしこの部分は英数字コードそのものなので、何らかの手立てをしないと英数字との区別が付かない。
       そこでISO-2022-JPは、「ここからは漢字だよ」「ここからは英数字だよ」という標識のコードを挿入する。「ここから漢字」は1B 24 42という3バイト、「ここから英数字」は1B 28 42という3バイトである。
       なお、これをはしょって、「ここから漢字」を1B 24という2バイト、「ここから英数字」を1B 28という2バイトにしてしまったものが、かつてNEC PC-9801のDISK-BASICなど、パソコンで漢字を取り扱い始めたころに広く用いられていた漢字コードである。当時は単に「JISコード」と呼ばれていた。
       ISO-2022-JPの特徴は、よくも悪くも「ここから漢字」のような標識を用いるということ。標識さえ見逃さないでしっかり処理すれば、確実に既存の英数字と折り合いをつけられる。しかしこれがそのまま欠点にもなる。標識という目に見えない印があるとデータ処理が非常にわずらわしい。たとえば漢字と英数字が複雑にまじりあうデータでは、標識がやたらに増えてしまい、へたをすると本来の文字よりも標識のほうが多いなどということにもなりかねない。標識のおかげで、何文字だと何バイトなのかというのが確定しないというのが、データ処理上非常に困ることである。PC-9801のDISK-BASICを使っていたころはこの問題に非常に悩まされた。その後MS-DOSでは後述のシフトJISという手軽な方法が採用されたので、自然消滅してしまった。


    3. EUC-JP
       EUC-JP(以下EUC)は、区点コードの第1バイトにも第2バイトにもA0を加え、A1〜FEの領域を用いたコードである。たとえば「蛙」はB3BFとなる。
       A1〜FEの領域は英数字とは一切競合しないので、特別な標識を用いることなく、手軽に英数字と共存させることができる。しかし、半角カタカナとは競合するので、半角カタカナが使えなくなってしまう。EUCに対応していないソフトでEUCのデータを見ると半角カタカナが多く混じった文字列に見えるのはそのためである。
       パソコンでは従来半角カタカナを用いていたので、半角カタカナと共存させる必要上、このやり方は用いられてこなかったが、そんな必要のないUNIXでは主にこの方法が用いられてきた。
       標識を用いないので、漢字ならば1文字2バイト、英数字ならば1文字1バイトとなるし、まぎれることは絶対にないので、データ処理は非常にやりやすい。


    4. シフトJIS
       シフトJISは、半角カタカナとの競合を避けたコードである。第1バイトは81〜9FおよびEO〜EFを用いる。第2バイトは40〜FC(7Fを除く)の値をとりうる。区点コードとの対応は、コンピュータにやらせればたいした手間ではないが、人間がわかるような形で書くと非常に複雑である。重要なところではないので読み飛ばしてほしい。だから小さい字で書くことにする(下の部分のみすべて10進数)。
      • 第1バイトが奇数のときは……
        • 第2バイトが01〜63のとき……第2バイトに63を足す。
        • 第2バイトが64〜94のとき……第2バイトに64を足す。
        • 第1バイトが01〜61のとき……第1バイトに257を足し、2で割る。
        • 第1バイトが03〜93のとき……第1バイトに385を足し、2で割る。
      • 第1バイトが偶数のときは……
        • 第2バイトには158を足す。
        • 第1バイトが02〜62のとき……第1バイトに256を足し、2で割る。
        • 第1バイトが64〜94のとき……第1バイトに384を足し、2で割る。
       このコードならば、従来用いてきた各機種独自の図形文字の使用さえ断念すれば、従来の半角カタカナとの共存もできる。そんなわけでパソコンではMS-DOSをはじめとして多くのDOSで用いられてきたコードである。
       標識を用いないので、漢字ならば1文字2バイト、英数字ならば1文字1バイトとなる点はEUCと共通だが、第2バイトが英数字と競合してしまう点が大きな問題である。
       たとえば「蛙」は8A5Eとなる。第1バイトの8Aは他と競合することはないのだが、第2バイトの5Eは^(山形記号)のコードと同一である。そこで、プログラミング言語で^を特別の意味に用いている場合は、この文字を使うと誤動作するということになる。Perlでは正規表現およびフォーマット文字で使っているので、この部分に「蛙」という文字は使えない。このような文字は次のとおりである。
      /タЭ運蛙疑型洪賛戎真楚耽顛膿豹某与録竸喊嫂弯擔杰歐濘畤秧綽膺蘓訖躾鐃饋鷽^^
       もっと問題になるのは、次の文字である。
      ―ソЫ\噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭\\
       これらは第2バイトが5Cつまり\である。Perl、PHP、Cなど、多くのプログラミング言語では\を特別な意味として用いている。たとえば\nと書くと「改行」を意味するなど。だからこれらの文字を使ってしまうと、さまざまな問題を生じるわけである。
       冒頭の「問題の所在」で書いたように、プログラマはシフトJISを警戒しているのだが、それはこのように、第2バイトが英数字、特にプログラム上特別な意味で用いている記号類と重複してしまうという点なのである。



  3. EUCの欠点とシフトJISの利点
     以上、区点コード系の各種コードの特徴を駆け足でおさらいし、その中でもEUCの利点とシフトJISの問題点を指摘した。
     が、本稿で真に言いたいのはここからである。EUCの利点とシフトJISの問題点を指摘するあまり、逆にEUCの問題点とシフトJISの利点がちっとも語られていない。だからデータ処理CGIでは無批判的にEUCを用いる慣例がある。上記で上げたような参考書もそうだし、当サイトの語彙検索CGIを作るにあたって参考にした、某サイトでフリー配付している住所録CGIもそうである。だから当初は、当サイトの語彙検索CGIも、EUCを用いていた。
     しかしEUCを使うとまずい点もあるのである。当サイトの語彙検索CGIで以前から起こっていた症状だが、訳文を検索するとうまく検索できないことが多かったのである。たとえば「靴」をウルドゥー語でどういうかを調べるため、ウルドゥー語の語彙検索CGIで「靴」と入れると、訳文中に「靴」を含む語ばかりか、全然「靴」という文字が入っていない語もヒットする。しかもその数が半端ではない。なんとなんと509件! そのうち本当に「靴」が入っているのはたった9件、正ヒット率は2%である。これでは使い物にならない。
     では、「靴」が入っていないはずの500件は、どうしてヒットしたのだろうか。
     たとえば「親しみ」としか書かれていないデータも「靴」でヒットする。では「親しみ」はEUCでどういうコードになるかというと、BFC6 A4B7 A4DFである。一方「靴」はB7A4。これでおわかりのように、「し」の第2バイトのB7と、「み」の第1バイトのA4という連続が、「靴」のB7A4と一致してしまったのである。
     EUCではひらがなはすべてA4で始まるし、逆にA4で始まるものはすべてひらがなである。だから「し」+ひらがなという連続があれば、B7A4になってしまい、「靴」になってしまうのである。「し」+ひらがななどという連続は非常によく出てくる。これが、509件もヒットしてしまった理由である。
     こういう問題が起こるのは「靴」だけではない。漢字1文字の語を検索しようとすると、かなり高い確率でこういう問題が起こる。
     検索プログラムでは検索語の語長が短すぎると誤ヒットが多くなる。たとえば英語のデータベースでeのみを検索したら無数にヒットするだろう。だから「1文字の語の検索」をするほうが悪いといえるかもしれない。しかし、英語はともかく、日本語では1文字の語の検索をする機会は非常に多い。これでこういう問題が起こるようでは困るのである。
     EUCでは、第1バイトも第2バイトもA1〜FEなので、前の文字の第2バイトと次の文字の第1バイトを組み合わせると、別の漢字になってしまう。これが運悪く、検索しようとしている漢字1文字の語と合致してしまうことが多いのだ。
     一方、シフトJISでは第1バイトが81〜9FまたはEO〜EF、第2バイトは40〜FC。前の文字の第2バイトと次の文字の第1バイトの組み合わせが別の漢字になってしまう不運なケースはないわけではないが、かなり少ない。
     シフトJISでそのような不運な症状が起こるとすれば、第1バイトも第2バイトも「81〜9FまたはEO〜EF」の範囲内にはいってしまう文字である(あとは、訳文データに漢字と英数字が混在しているとき、その境界のデータが誤ヒットする可能性があるが、いまは置く)。検索語がすべてこういう文字であり、訳文内にこのような条件にあてはまる文字が連続する部分があるときに、誤ヒットが起こる可能性がある。では、そういう文字はどういうものだろうか。全部で1716字あり、全部あげると大変なので、ひらがな、カタカナ、第一水準漢字に限定して列挙する。
    ぁもゃやゅゆょよらりるれろゎわゐゑメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶ亜謂違遺医井亥域育郁磯一壱溢逸稲茨堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応押霞蚊俄峨我牙画臥芽蛾賀雅餓駕介会梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱粥癌眼岩翫贋雁頑顔願企伎危喜器基奇求汲泣灸球究窮笈級糾給旧牛去居巨拒拠挙渠虚許距鋸漁禦魚亨享京供金吟銀九倶句区狗玖矩苦躯駆駈駒具戟撃激隙桁傑欠決潔穴結血訣月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲検呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉香高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠黒獄漉腰甑忽惚骨狛込此財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾氏獅祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時次錫若寂弱惹主取守手朱殊狩珠種腫趣潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙女序徐恕鋤除傷償勝鐘障鞘上丈丞乗冗剰城場壌嬢常情擾吹垂帥推水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾澄説雪絶舌蝉仙先千占宣専尖川戦扇撰早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装走送遭鎗霜騒像増憎臓隊黛鯛代台大第醍題鷹滝瀧卓啄宅托秩窒茶嫡着中仲宙忠抽昼柱注虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵帖亭低停偵剃貞呈堤定帝底庭廷弟悌抵刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到董内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊白箔粕舶薄迫曝漠爆縛莫駁麦函扉批披斐比泌疲皮碑秘緋罷肥被誹費普浮父符腐膚芙譜負賦赴阜附侮撫武舞葡蕪部封楓風葺蕗伏副復幅服福歩甫補輔穂募墓慕戊暮母簿菩倣俸包磨魔麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣又抹末沫迄侭繭麿万慢満漫籾貰問悶紋門匁也冶夜爺耶野弥矢厄浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃痢伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦
     こうしてみるとずいぶん多いようだが、これらの文字が連続するケースとなるとあまり多くない。ためしにこれらの文字を適当に語彙検索CGIの訳文欄に入れて実験してもらいたい。これらの文字を入れても意外に誤ヒットはないか、あっても少ないことがわかるだろう。
     たしかに一部の文字では誤ヒットがある。いまのところ確認した一番不運なケースは、「牛」(8B8D)を入れると「結婚」(8C8B-8DA5)「結合」(8C8B-8D87)などとヒットしてしまうというものである。しかし、本来ヒットすべきもの10件、誤ヒット27件、正ヒット率は27%である。これならまぁ実用的な範囲内かとも思う。
     だからこういう語彙検索CGIでは、シフトJISを使うほうがいいのである。
     もちろん、正規表現内にシフトJISデータを用いると誤動作するので、検索は正規表現を用いず、たとえばindex関数を使うなどすればOKである。



  4. まとめ
     こういう問題は、プログラミング言語がちゃんと日本語に対応してくれれば解決する。実際、PerlでいえばJPerlといったものがある。しかし、自前でサーバーを運営するならともかく、レンタルサーバーを用いる身では、サーバー側の対応を待っていられず、こちら側で防衛策を立てねばならないことも多い。
     そして、プログラミングの教科書やフリーCGI集などは、理系のライターやプログラマが書いていることが多く、文系のこういう問題が認識されていないことが多い。
     それから、これは邪推かもしれないが、EUCはUNIXで用いられてきた方法なので、UNIXへの愛着の強いライターがパソコンのシフトJISを貶めて、不当にEUCの利点ばかり語っているということがあるかもしれない。UNIXやMacIntoshに関しては、こういう手合いが多いので警戒すべきである。
     最近では文系の研究者もコンピュータを用いたりプログラムを作ったりすることは珍しくないのだから、このような問題点があれば声を大にして言うべきであろう。コンピュータに関して何が問題か、それをどうかわしているか、実務ユーザーの声をもっと聞きたいところである。



  5. 追記(2007/2/23)
     この文章を書いた当時は、UTF-8を扱える携帯電話端末が少なかったのだが、その後徐々に増えてきたこともあり、2007年2月22日以降、語彙検索CGIは念のためシフトJIS版も残しながらもUTF-8に移行した。
     UTF-8はUnicodeの変種である。Unicodeは2バイト(16進数0000〜FFFF)で世界のすべての文字を表現しようというコードであるが、これでは扱いにくいので、Unicodeに次の演算をほどこしたUTF-8というコードがよく用いられる。すなわち、
    • Unicodeが0000〜007Fの文字……下2桁(00〜7F)の1バイトのみを用いる。
    • Unicodeが0080〜07FFの文字……Unicodeを2進数で表して00000xxxxxyyyyyyとするとき、2進数で110xxxxxを第1バイト、10yyyyyyを第2バイトとした2バイトで表現する。よって第1バイトは16進数でC0〜DF、第2バイトは16進数で80〜BFという値になる。
    • Unicodeが0800〜FFFFの文字……Unicodeを2進数で表してxxxxyyyyyyzzzzzzとするとき、2進数で1110xxxxを第1バイト、10yyyyyyを第2バイト、10zzzzzzを第3バイトとした3バイトで表現する。よって第1バイトは16進数でE0〜EF、第2・第3バイトは16進数で80〜BFという値になる。
     Unicodeでは漢字は4E00〜9FA5の領域に割り当てられているので、漢字は必ず3バイトということになる。
     上記のように、英数字コードは従来どおり00〜7Fの1バイトとなるが、漢字のコード中には00〜7Fが現れないので、シフトJISのように漢字コードの2バイト目が英数字コードと同一になってしまうということがないし、もちろん\や^と衝突することもない。さらに、第1バイトと第2・第3バイトとは全く異なる領域の値になるので、シフトJISやEUCなどのように、前の漢字の最後と次の漢字の最初との並びが偶然に別の漢字コードとバッティングすることも絶対にない。
     だからこのページでいろいろ書いてきた問題は、UTF-8を使えば一挙に解決してしまうのである。
     難点はjcode.plがUTF-8に対応していないことであるが、CGIや入力フォームページを最初からUTF-8で書き、しかもMETAタグでcharset=UTF-8と明示して強制的にブラウザをUTF-8にしてしまえば、ユーザがわざわざエンコードを変更しない限り、そこからの入力データは必ずUTF-8になるので、jcode.plを使う必要はないといえる。データファイルもあらかじめUTF-8に変更したものを用いればよい。シフトJISで書かれたデータをUTF-8に変換するのは、たとえばRTFCONV(この名前で検索エンジンで検索すれば見つかるはず)を用いれば簡単にできる。RTFCONVはDOSのコマンドラインでも使えるのでバッチファイルなどで記述すれば自動的に作成することも可能。
     そのようなわけでUTF-8は手軽に安全に用いることができるデータである。他の部分はシフトJISを用いるにしても検索CGIだけは必ずUTF-8を用いるようにすればよいであろう。


参考文献
安岡孝一、安岡素子『文字コードの世界』(東京電機大学出版局)1999


※ご意見、ご教示などは、に戻り、掲示板あるいはメールで賜るとありがたく思います。