こんにちは、たびとです。
いまだに Shift-JIS で動作しているレガシーなシステムは、意外と多いと思います。 例えば Web システムだと、ブラウザは Unicode を受け付けるのに、 Web サーバ側のアプリが Shift-JIS だと色々と問題が発生します。
今回は、Unicode で入力された文字列を Shift-JIS に変換する必要がある場合、 どのように JavaScript を使って実装すればよいのかを説明したいと思います。
この記事の対象者
- レガシーなシステムの面倒をみる必要があり、文字コードに Shift-JIS を使っている方
- Unicode と Shift-JIS など、文字コードの判定について興味のある方
- JavaScript で文字コードを変換する必要のある方
文字コードについて
文字コードは、現在は Unicode 一択ですが、かつては色々な文字コードが存在し、 Web アプリが登場し始めたときは、それぞれの文字コードを意識していました。
かつて Windows でアプリを開発する場合、Shift-JIS を採用することが主流でした。 そのとき、UNIX システムは EUC を採用していたので、 Web システムは、文字コードを判別するロジックをよく入れていたものです。 現在、UNIX を見ることはなく Linux が主流になってしまいました。
各文字コードの調べ方
Windows の IME パッドを表示し、左メニューから文字一覧 (検索アイコン) を選択し、 調べたい文字にマウスを移動させると、対応する文字コードが表示されます。
例えば、「愛」という漢字の場合、IMEモードの漢字一覧にマウスを移動させると、 Shift-JIS の文字コードを調べることができます。
サロゲート文字
JavaScript は Unicode で動作するのですが、詳しくは UTF-16 で動作します。 この UTF-16 は、2 バイトで文字コードを表現しますが、2 バイトでは足りない文字があります。 このとき、2 倍の 4 バイトで文字コードを表現した文字をサロゲート文字といいます (実際には 5 バイトを4バイトで表現しているようですが)。
このサロゲート文字は上位サロゲートと下位サロゲートから構成されるため、 サロゲートペアと呼ぶこともあります。
当然ながら、サロゲート文字も Shift-JIS では表現できないため、 今回の判定ロジックでは変換できない文字として処理する必要があります。
サロゲート文字の文字コードも IMEパッドを使って確認することができます。 例えば、𠮷(土よし)は、UTF-16 コードで上位サロゲートが 0xD842 、 下位サロゲートが 0xDFB7 であることを確認することができます。
古い ASP.NET Form の Shift-JIS
今回の Shift-JIS も実際に Internet Explorer 11 より以前を対象とした ASP.NET Form (2.0かな) で Visual Studio 2013 でコンパイルされていました。 流石に JavaScript を使って判定する箇所は IE 11 で動作するように Meta タグを変更しました。
Shift-JIS 判定のアプローチ
以下の 2 通りの方法を考えました。
- JavaScript で対応可能なら、クライアント側で文字コードを判定する。
- JavaScript がダメなら、Base64 エンコードして、サーバ(ASP.NET)側で判定をする。
今回は、JavaScript が使えたので、クライアント側で判定できました。
制約事項
Internet Explorer を使うと以下の制約事項があります。
また、Visual Studio 2013 だと、let で定義すると JavaScript の実行エラーになります。
方式設計
JavaScript が UTF-16 で動作していることを利用し、 以下の方式で Shift-JIS を判定します。
- JavaScript 内の UTF-16 の文字列を Shift-JIS に変換する。
- Shift-JIS に変換できなかった文字を UTF-16 で集計する。
- 変換 NG の文字列があれば、alert で画面に表示する。
このとき、文字コードを変換するのを自前で作るのは大変なので、 encoding.js ライブラリを使うことにします。
今回の ASP.NET は Visual Studio が古すぎて npm は使わなかったので、 git で落として、 encoding.js, encoding.min.js をコピーして使いました。
encoding.js を使った場合、変換できない文字コードは、 クエスチョンマーク (?) に置き換えられます。
JavaScript の実装
まずは、HTML のヘッダに encoding.min.js を指定します。
<script src="encoding.min.js"></script>
次に JavaScript を実装します。 今回は、サンプル用に「a?1②㉑㊿𠮷💛」の文字列を使って確認します。 後半の 4 文字が Shift-JIS では表現できなくて、末尾の 2 文字がサロゲート文字です。
const SampleText = "a?1②㉑㊿𠮷💛"; // 8文字 console.log("length:" + SampleText.length); // length=10 // サロゲート文字を1文字として配列に再格納する var items = SampleText.split(''); var utf16 = []; for (var i = 0; i < items.length; i++) { var code1 = items[i].charCodeAt(0); if (0xd800 <= code1 && code1 <= 0xdbff) { i++; var code2 = items[i].charCodeAt(0); utf16.push(String.fromCharCode(code1, code2)); } else { utf16.push(items[i]); } } console.log('Unicode: ', utf16) // UTF-16 の文字列を Shift-JIS に変換する var sjis = []; var ngWords = []; for (var i = 0; i < utf16.length; i++) { var code = utf16[i].charCodeAt(0); var buff = Encoding.convert([code], { to: 'SJIS', from: 'UNICODE' }); if (buff.length == 0) { ngWords.push(utf16[i]); console.log('NG1:' + utf16[i]); // サロゲート文字 } else if (buff[0] == 0x3f && code != 0x3f) { ngWords.push(utf16[i]); console.log('NG2:' + utf16[i]); // Shift-JISに存在しない } sjis.push(buff[0]); } console.log('Shift-JIS: ', sjis); console.log('NG Words: ', ngWords); // Shift-JIS に変換できない if (0 < ngWords.length) { alert('Shift-JIS に変換できない文字が見つかりました。\n' + ngWords); }
Edge の IE モードで実行すると以下のように表示されます。
実装のポイント
指定された文字列は、1 文字づつに分解し、1 文字づつ文字コードを変換して OK / NG を確認します。 ただし、サロゲート文字と変換失敗のクエスチョンに気を付ける必要があります。
サロゲート文字
Array.form が使えないので、サロゲート文字を自前で用意する必要があります。 split() で分割すると、上位サロゲートと下位サロゲートに分離するため、 これらをまとめて、サロゲート文字にする必要があります。
また、サロゲート文字を文字コードに変換する場合、I E縛りで codePointAt を使えないため、 charCodeAt を使うことになり、上位サロゲートしか取れなくなります。
この時、上位サロゲートのみを Unicode から Shift-JIS に変換するライブラリの Encoding.convert() を使うと、NULL が返却されるため、長さが ゼロ になります。
クエスチョンマーク (0x3f)
変換後にクエスチョンマークだった場合、変換前がクエスチョンマークなのかを判断する必要があります。 このため、変換前の文字コード(code) が クエスチョンマーク(0x3f) でないことを確認しています。
まとめ
文字コードで悩んだ場合、検索しても日本語サイトしかヒットしないため、 なかなか思い通りの情報が得られなくて困ることが多いですよね。
今回の情報も散々調べましたが、ヒントしか見つけることができず、 最初は第三水準と第四水準の文字コードからスタートしましたが、 色々調べているうちに、それじゃダメだということに気付きました。
結局、入力された文字が Unicode で、出力する文字が Shift-JIS なのだから、 Unicode から Shift-JIS に変換できるかどうか にたどり着いたので、 自然と答えを導き出すことができました。
最後に参考サイトを掲載しておきます。 では、皆さん、よい旅を。