ASTERIA Warpで文字化けを防ぐ:Windows-31J・Shift_JIS・UTF-8の違いと注意点

ASTERIA Warpのマッパー関数「StringReplace」のヘルプに載っている「Windows-31JとJIS系文字コードの相互変換について」の項を読み、なぜこのような変換が必要になるのかを確認してみました。あわせて、Shift_JISとUTF-8を変換するときの注意点と、バックスラッシュ(\)と円マーク(¥)の扱いについても整理しました。

********************************************

ヘルプに載っている「7つの文字」

ヘルプの「Windows-31JとJIS系文字コードの相互変換について」の項には、次の7文字が挙げられています。

・―(ダッシュ):バイト値 815C → Windows-31JではU+2015、Shift_JISではU+2014
・~(波ダッシュ):バイト値 8160 → Windows-31JではU+FF5E、Shift_JISではU+301C
・∥(双柱):バイト値 8161 → Windows-31JではU+2225、Shift_JISではU+2016
・-(マイナス):バイト値 817C → Windows-31JではU+FF0D、Shift_JISではU+2212
・¢(セント):バイト値 8191 → Windows-31JではU+FFE0、Shift_JISではU+00A2
・£(ポンド):バイト値 8192 → Windows-31JではU+FFE1、Shift_JISではU+00A3
・¬(否定):バイト値 81CA → Windows-31JではU+FFE2、Shift_JISではU+00AC

ポイントは、ファイル上のバイト値は同じなのに、どの文字コード名で読むかによってUnicodeの値が変わることです。

なぜこのような変換が必要なのか

ASTERIA WarpはJavaで動いています。Javaの中では、文字列はいったんすべてUnicodeとして扱われ、ファイルの読み書きやDB・メール送信のときに、指定した文字コードとの間で変換されます。

ここで問題になるのが、日本語の「Shift_JIS」には実は2種類の対応表があることです。

・JIS系(Shift_JIS、EUC-JP、ISO-2022-JP):JIS規格の文字とUnicodeの対応表に従います。「~」は波ダッシュ(U+301C)になります。
・Windows-31J(MS932、CP932とも呼ばれます):Microsoftが定めた対応表に従います。「~」は全角チルダ(U+FF5E)になります。

Windowsで作ったCSVやExcelの文字は、実態としてはWindows-31Jです。名前は「Shift_JIS」と呼ばれていても、中身はWindows-31Jであることがほとんどです。

そのため、例えば次のような流れで問題が起きます。

1. Windows-31Jとして読み込んだCSVの「~」は、ASTERIA Warpの中でU+FF5Eになる
2. それをISO-2022-JP(JIS系)のメールやEUC-JPのファイルに出力しようとする
3. JIS系の対応表にはU+FF5Eが無いため、変換に失敗する(「?」になる、エラーになる)

逆方向も同じです。JIS系で読み込んだ「~」(U+301C)をWindows-31Jで出力しようとすると、Windows-31Jの対応表にU+301Cが無いため失敗します。

どちらも「同じ文字のつもりなのに、Unicodeの値が違う」ことが原因です。StringReplaceで出力前にUnicodeの値を相手側の対応表にある値へ置き換えておけば、変換が通るようになります。

変換テーブル

ヘルプには、出力の前にUnicodeの値を置き換えるための対応表が載っています(\uXXXXはUnicodeのコードポイントを表します)。出力先の文字コードに合わせて使い分けます。

・Windows-31Jで読んだデータを、JIS系(Shift_JIS、EUC-JP、ISO-2022-JP)で出力する場合

\u2015=\u2014
\uFF5E=\u301C
\u2225=\u2016
\uFF0D=\u2212
\uFFE0=\u00A2
\uFFE1=\u00A3
\uFFE2=\u00AC

・JIS系で読んだデータを、Windows-31Jで出力する場合

\u2014=\u2015
\u301C=\uFF5E
\u2016=\u2225
\u2212=\uFF0D
\u00A2=\uFFE0
\u00A3=\uFFE1
\u00AC=\uFFE2

※Windows-31Jの外部システムとJIS系の外部システムをつなぐ場合や、Windowsで作ったデータをISO-2022-JPのメールで送る場合は、出力の直前にこの置き換えを入れておくと安心です。

********************************************

補足:Shift_JIS(Windows-31J)とUTF-8を変換するときの注意点

最近はクラウドサービスやAPIとの連携が増え、Shift_JISのCSVをUTF-8で渡す(またはその逆)という場面がよくあります。このときにも考慮しておくべき点があります。

1. 「~」問題はUTF-8でも起きる

UTF-8はUnicodeをそのままバイト列にしたものなので、上の7文字の違いがそのまま残ります。

・Windows-31Jから変換した「~」:U+FF5E(UTF-8ではEF BD 9E)
・JIS系やMac・Linuxの環境から来た「~」:U+301C(UTF-8ではE3 80 9C)

画面上は同じ「~」に見えますが、別の文字です。そのため、検索や突き合わせ(「10~20」で検索してもヒットしない、マスタと一致しない)でずれが出ます。また、U+301Cを含むUTF-8のデータをWindows-31Jに変換すると、変換に失敗します。UTF-8からWindows-31Jへ出力するフローでは、上の「JIS系→Windows-31J」のテーブルを通しておくと安全です。

2. Windows-31Jに無い文字がある

UTF-8(Unicode)はWindows-31Jよりはるかに多くの文字を扱えます。次のような文字は、UTF-8からWindows-31Jに変換すると「?」になったりエラーになったりします。

・絵文字
・「𠮷」(つちよし)のような、Windows-31Jに無い漢字
・アクセント付きのアルファベット(é、ö など)、ハングル、簡体字の一部

逆に、①やⅠ、㈱のような機種依存文字(NEC特殊文字)や「髙」「﨑」のようなIBM拡張文字は、Windows-31JやUTF-8には存在しますが、JIS系のShift_JIS、EUC-JP、ISO-2022-JPには存在しません。JIS系に出力するときは、置き換えるか除外するかを決めておく必要があります。

3. バイト数が変わる

日本語の全角文字は、Shift_JISでは1文字2バイトですが、UTF-8では多くが3バイトになります(半角カナも1バイトから3バイトになります)。

・固定長ファイルを作るとき、バイト数で桁を数えていると桁がずれる
・DBの列の長さがバイト単位の場合、Shift_JISでは入っていた値がUTF-8では入りきらない

連携先の桁数がバイト単位か文字数単位かは、事前に確認しておきます。

4. BOMの有無

UTF-8には、先頭にBOM(EF BB BF)を付ける形式と付けない形式があります。ExcelでUTF-8のCSVを文字化けせずに開くにはBOM付きが必要な一方、システム間連携ではBOMがあると1列目の項目名が一致しないといった問題が起きます。連携先がどちらを期待しているかを確認しておきます。

5. 文字コード名は「Windows-31J」を選ぶ

Windowsで作られたファイルを読み書きするときは、エンコーディングに「Shift_JIS」ではなく「Windows-31J」を指定するのが基本です。「Shift_JIS」を指定すると、機種依存文字やIBM拡張文字が読めず、上の7文字もJIS系の値になります。

********************************************

補足:バックスラッシュ(\)と円マーク(¥)

文字コードの話でもう一つ混乱しやすいのが、バックスラッシュと円マークです。

なぜ同じ場所に2つの文字があるのか

半角の0x5Cというバイト値は、ASCII(米国の規格)ではバックスラッシュ「\」ですが、日本のJIS X 0201では円マーク「¥」に割り当てられていました。日本語のShift_JISはJIS X 0201をもとにしているため、0x5Cは「¥」として表示する習慣が定着しました。

一方、Windows-31Jでは0x5CはUnicodeのU+005C(バックスラッシュ)に対応付けられていて、日本語フォントでは円マークの形で表示されることが多い、という扱いになっています。つまりWindowsでは、中身はバックスラッシュ、見た目は円マークです。Windowsのパス区切り「C:¥Users¥…」が円マークに見えるのはこのためで、プログラム上はバックスラッシュと同じ文字です。

UTF-8では別々の文字になる

Unicodeでは、バックスラッシュと円マークは別の文字です。

・\(バックスラッシュ):U+005C(UTF-8では5C)
・¥(円マーク):U+00A5(UTF-8ではC2 A5)
・\(全角バックスラッシュ):U+FF3C(Windows-31Jでは815F)
・¥(全角円マーク):U+FFE5(Windows-31Jでは818F)

このため、次のような問題が起きます。

・Windowsで入力した「¥1,000」は、UTF-8に変換すると「\1,000」(U+005C)になります。Webやクラウドサービスの画面では、バックスラッシュとして表示されることがあります。
・Macや一部のWebフォームから入力された「¥」はU+00A5のことがあります。これをWindows-31Jに変換しようとすると、実装によっては「?」になったり0x5Cに寄せられたりします。
・半角の「¥」と全角の「¥」も別の文字なので、金額の文字列から通貨記号を取り除く処理では両方を考慮する必要があります。

UTF-8で受け取ったデータをWindows-31Jで出力する場合は、円マーク(U+00A5)をバックスラッシュ(U+005C)に置き換えてから出力すると、変換の失敗を防げます。ただし、円マークを金額の記号として扱いたいのか、パスの区切りとして扱いたいのかで、どちらに揃えるべきかは変わります。連携先でどう表示・解釈されるかを確認してから決めます。

********************************************

Shift_JISとUTF-8で文字化けさせないためのチェックポイント

最後に、Shift_JISとUTF-8の間でデータをやり取りするときに気を付けるところをまとめます。

1. 入力と出力の文字コードを正しく指定する

・連携元・連携先それぞれの文字コードを最初に確認します。「Shift_JIS」と言われても、Windowsで作ったデータなら実態はWindows-31Jです。
・読み込み時に実際と違う文字コードを指定すると、その時点で文字化けします。読み込みと書き出しの両方で指定を確認します。

2. 見た目が同じでコードが違う文字を揃える

・「~」「-」「―」「∥」「¢」「£」「¬」の7文字は、Windows-31JとJIS系でUnicodeの値が違います。UTF-8に変換しても違いは残ります。
・出力先の文字コードに合わせて、出力の直前に置き換えておきます。

3. 出力先に無い文字の扱いを決めておく

・UTF-8からShift_JISへ:絵文字、Windows-31Jに無い漢字、アクセント付きのアルファベットなどは変換できません。
・Windows-31JからJIS系へ:①や㈱などの機種依存文字、「髙」「﨑」などのIBM拡張文字は変換できません。
・置き換える、除外する、エラーにして止める、のどれにするかを事前に決めておきます。

4. バイト数の違いを考慮する

・全角文字はShift_JISで2バイト、UTF-8で多くが3バイトです。
・固定長ファイルやバイト単位のDB列では、桁あふれや桁ずれが起きないか確認します。

5. BOMの有無を合わせる

・Excelで開くCSVはBOM付き、システム間連携ではBOMなしが求められることが多いです。連携先の仕様に合わせます。

6. 円マークとバックスラッシュを区別する

・Windows-31Jの「¥」(0x5C)は、UTF-8ではバックスラッシュ(U+005C)になります。
・UTF-8の円マーク(U+00A5)はShift_JISに変換できないことがあります。金額やパスなど、用途に合わせてどちらに揃えるかを決めます。

7. 変換しにくい文字を含むテストデータで確認する

・「~」「-」、機種依存文字、IBM拡張文字、絵文字、円マーク、半角カナを含むテストデータを用意し、出力結果をバイナリエディタなどでバイト値まで確認しておくと安心です。

まとめ

同じ「Shift_JIS」でも、JIS系とWindows-31Jでは一部の文字のUnicodeの値が違うため、Javaで動くASTERIA Warpでは、出力時の変換に失敗することがあります。ヘルプに載っている対応表で、出力の直前に相手側の値へ揃えておけば失敗を防げます。UTF-8との変換でも「~」の違いは残るほか、出力先に無い文字、バイト数の違い、BOM、円マークとバックスラッシュの違いにも注意が必要です。文字コードが絡む連携では、入力と出力の文字コードを最初に確認しておくことをおすすめします。

最後までお読みいただき、ありがとうございました。

少しでもご参考になれば幸いです。

参考:
・StringReplace(ASTERIA Warp 4.9 ヘルプ)
・windows-31J charset registration(IANA)