3行まとめ
- テキスト/ログファイルの文字コードを Shift_JIS・EUC-JP・UTF-8 の3方向で相互変換するブラウザ完結ツールを作った。肝は書き出しをブラウザ標準APIでは実現できないという制約
-
TextEncoderはUTF-8しか吐けず、TextDecoderは読めても書けない。Shift_JISやEUC-JPにエンコードして書き出すにはencoding-japaneseが要る - 自動判定はUTF-8 BOM(
EF BB BF)を最優先し、BOMなしはバイトパターンのスコアリングで推定。変換時はBOMを剥がしてから変換し、非UTF-8出力は一度UTF-8へ戻してプレビューする
業務システムが吐くShift_JISのCSV、古いLinuxサーバーのEUC-JPログ、UTF-8で作ってWindowsのメモ帳で開いたら文字化けしたテキスト——文字コードの変換は、日本語で開発しているとどうしても付きまとう。VSCodeで開き直せば読めることも多いが、「変換して保存し直したい」となると意外と面倒。
ぱんだツールズのテキスト文字コード変換は、テキストファイルを Shift_JIS・EUC-JP・UTF-8 の間で相互変換するツール。処理はブラウザ内で完結し、ファイルはサーバーに送られない。社内ログや個人情報を含むファイルも安全に変換できる。
この記事では、なぜブラウザ標準のAPIだけでは文字コード変換が完結しないのか、そして自動判定とBOM処理をどう実装したかを、実装ベースで書く。
ブラウザ標準APIでは「書き出し」ができない
まず一番大事な制約から。ブラウザには文字コードを扱う標準APIとして TextEncoder と TextDecoder があるが、これだけでは Shift_JIS や EUC-JP への変換はできない。
-
TextEncoder… 文字列 → バイト列。ただし出力はUTF-8固定。new TextEncoder('shift_jis')のような指定はできず、仕様上UTF-8しか吐けない -
TextDecoder… バイト列 → 文字列。こちらはnew TextDecoder('shift_jis')で読み込みはできる。だが逆(文字列をSJISバイト列にする)はできない
つまり「SJISを読む」ことはできても、「SJISで書き出す」手段がブラウザ標準には存在しない。UTF-8への一方通行しかできないのだ。日本語の実務で必要なのは「UTF-8 → Shift_JIS」だったりするのに、そこが標準APIの穴になっている。
そこで使うのが encoding-japanese(MITライセンス)。日本語の文字コード変換に特化したライブラリで、検出・変換の両方を Uint8Array ベースで扱える。
import Encoding from 'encoding-japanese'
このライブラリが「SJIS/EUC-JPへの書き出し」という標準APIの穴を埋めてくれる。だから3方向の相互変換が成立する。
自動判定:BOMを最優先し、なければスコアリング
ファイルを読み込んだら、まず文字コードを推定する。ここは2段構え。
function detectCharsetFromBuffer(buffer: ArrayBuffer): CharsetLabel {
const bytes = new Uint8Array(buffer)
// ① UTF-8 BOM(EF BB BF)があれば確定
if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
return 'UTF-8'
}
// ② BOMがなければバイトパターンで推定
const detected = Encoding.detect(bytes)
if (detected === 'UTF8' || detected === 'SJIS' || detected === 'EUCJP') {
return encodingToLabel[detected]
}
return 'UTF-8' // フォールバック
}
BOM(バイトオーダーマーク)があれば確定。UTF-8のBOMは先頭3バイトが EF BB BF と決まっているので、これが付いていればUTF-8で間違いない。BOMは「確実」に判定できる唯一の手がかり。
BOMがないファイルは、Encoding.detect にバイト列を渡してパターンから推定する。UTF-8・Shift_JIS・EUC-JPはそれぞれ多バイト文字の組み立て方(先行バイト・後続バイトが取りうる範囲)に固有の癖があり、バイト列全体がどのコードの並びに適合するかを見て最も可能性の高いものを返す仕組み。日本語を多く含む文書なら高精度だが、ASCII文字だけのファイルはどのコードでも同じバイト列なので判定できない(ASCII が返る)。その場合はUTF-8にフォールバックする。判定に自信がないときは変換元を手動で選べるようにもしてある。
変換:BOMを剥がしてから変換する
変換元と変換先が決まったら、Encoding.convert に流す。ここでのポイントは入力のUTF-8 BOMを先に取り除くこと。
function convertEncoding(buffer, fromCharset, toCharset, fileName) {
const inputBytes = new Uint8Array(buffer)
// 変換元がUTF-8でBOM付きなら、先頭3バイトを落とす
const hasBom =
fromCharset === 'UTF-8' &&
inputBytes.length >= 3 &&
inputBytes[0] === 0xef && inputBytes[1] === 0xbb && inputBytes[2] === 0xbf
const bytesToConvert = hasBom ? inputBytes.slice(3) : inputBytes
const converted = Encoding.convert(bytesToConvert, {
to: charsetToEncoding[toCharset], // 'UTF8' | 'SJIS' | 'EUCJP'
from: charsetToEncoding[fromCharset],
})
const resultBytes = new Uint8Array(converted)
// ...
}
BOMを剥がさずにSJISやEUC-JPへ変換すると、BOMの3バイト(EF BB BF)が本文の一部として化けて混入してしまう。UTF-8由来のBOMは変換前に落とすのが正解。Encoding.convert に渡す to / from は encoding-japanese の内部名(UTF8 / SJIS / EUCJP)で、UI表示用のラベル(UTF-8 / Shift_JIS / EUC-JP)とは別に持って変換テーブルでマップしている。
ちなみに変換元と変換先が同じだと意味がないので、その場合はエラーを出して弾いている。
非UTF-8の出力を「読める形」でプレビューする
変換結果はダウンロードできるが、その前にプレビューを出したい。ここで地味な問題が出る。変換先がShift_JISやEUC-JPのとき、そのバイト列をそのまま画面に表示しても人間には読めない(ブラウザの表示はUTF-8前提)。
そこで、非UTF-8に変換した結果は、プレビュー用にもう一度UTF-8へ戻してから表示する。
let previewText: string
if (toCharset === 'UTF-8') {
const decoder = new TextDecoder('utf-8', { fatal: false })
previewText = decoder.decode(resultBytes).slice(0, 300)
} else {
// Shift_JIS / EUC-JP → 一度UTF-8に変換し直してからデコード
const forPreview = Encoding.convert(resultBytes, { to: 'UTF8', from: charsetToEncoding[toCharset] })
const decoder = new TextDecoder('utf-8', { fatal: false })
previewText = decoder.decode(new Uint8Array(forPreview)).slice(0, 300)
}
ダウンロードされる実ファイルは変換先の文字コード(SJIS/EUC-JP)そのままだが、画面プレビューだけはUTF-8に戻して TextDecoder で読める文字列にする。{ fatal: false } にしているのは、変換の過程で表現できない文字があっても例外で止めず、置換文字で流すため。先頭300文字だけ出して、変換が意図通りかを目視で確認してからダウンロードできる。
CSV専用ツール・判定専用ツールとの住み分け
このツールは .txt / .log / .tsv / .md など汎用テキストを対象にしている。CSVも変換できるが、Excelとの往復を強く意識するなら Shift_JIS↔UTF-8 に特化した CSV文字コード変換 の方が向いている。また「変換する前に、そもそもこのファイルは何のコードなのか確認だけしたい」なら、判定に特化した 文字コード判定 が使える。1ツール1機能に割って、変換・判定・CSV特化を分けている。
まとめ
- ブラウザ標準の
TextEncoderはUTF-8しか吐けず、TextDecoderは読めても書けない。Shift_JIS/EUC-JPへ書き出す変換は標準APIでは不可能で、encoding-japaneseが要る - 自動判定は2段構え。UTF-8 BOM(
EF BB BF)があれば確定、なければEncoding.detectのバイトパターンスコアリングで推定。ASCIIのみは判定不能でUTF-8にフォールバック - 変換前にUTF-8 BOMを剥がしてから
Encoding.convertする(剥がさないとBOMが本文に化けて混入する) - 非UTF-8の出力はそのままだと画面で読めないので、プレビューだけ一度UTF-8へ戻して
TextDecoderで表示する
Shift_JISやEUC-JPのファイルをUTF-8に、あるいはその逆に変換したいときにどうぞ。ファイルはブラウザの外に出ない。
ぱんだツールズ では他にも CSV文字コード変換・文字コード判定・銀行明細CSV変換・PDF処理など、日本語の実務ファイルに強いブラウザ完結ツールを多数公開中。全部無料・登録不要・ファイルはサーバーに送られない。
https://sakutto-panda.com
この記事は Zenn にも同じ内容を投稿しています。