0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

テキストの文字コードをブラウザで変換する — Shift_JIS/EUC-JP/UTF-8とencoding-japanese

0
Posted at

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として TextEncoderTextDecoder があるが、これだけでは 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 / fromencoding-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 にも同じ内容を投稿しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?