◆ Ajax で再取得して DOM 全体書き換える

57 からだめになった Chrome のエンコーディングの自動判別機能ですが 60 になっても相変わらずです
そこそこ大きな UTF-8 のテキストファイルを charset 指定なしで受け取ると Shift_JIS や gb18030 と判定されています

中国語のエンコーディングになるのは何か中国語の文字っぽいのがあったときのレアケースで 基本は Shift_JIS です

JavaScript で直したい

Chrome 自体にエンコーディング切り替え機能はないので これまで諦めて API のレスポンスをみるときなどは Firefox を使っていました

ですが 予想外のところでエンコーディングがおかしいってことがあるとそのたびにわざわざ Firefox を開くのもめんどうです
今のページを Firefox で開く拡張機能を入れてもいいですが このためにいれるのもなぁ って気分です
devtools でちょっと JavaScript 打つだけで 文字化けが直ってちゃんと見えるようになるのが理想です


色々あったのですが先に完成品です
fetch("", {credentials: "include"}).then(response => {
response.text().then(result => {
const type = response.headers.get("content-type")
if(type === "text/html") document.write(result)
else document.write(`<html><pre>${result}</pre></html>`)
})
})

自身のページを fetch して DOM をまるごと書き換えしてます
操作できたり HTML なページでは文字化けする現象が起きてなくて JSON や CSS や txt ファイルなど単純なテキストファイルだけなのでまるごと書き換えでも特に困ることはなく解決できました

再取得しないでやりたかったけど

上の方法だと fetch でもう一回通信しています
大した問題じゃないのですが やっぱり今の TextNode の値をどうこうするだけでできるならそうしたいので 最初はこういう方法を考えていました

;[...document.querySelectorAll("*")].forEach(elem => {
;[...elem.childNodes].forEach(node => {
if(node.nodeType === Node.TEXT_NODE){
node.textContent = fix文字化け(node.textContent)
}
})
})

この fix文字化け の関数で何をすればいいか がちょっとむずかしいです
エンコーディング変換なのでとりあえず encoding.js をロードしたものの 単純に SJIS と UTF-8 変換しても全然うまくいかないです


ちゃんと考えてみると UTF-8 としてエンコードされたバイナリデータを無理やり Shift_JIS として読み取った状態で表示しているから文字化けしているんです

なので Shift_JIS として読み取って表示されたテキストからもともとのバイナリデータに戻して それを UTF-8 として読み取った文字を表示すれば文字化けせずに表示されるはずです

Shift_JIS のバイナリが取れない

文字化けしたテキストの charCodeAt で文字コードを取得しても Shift_JIS の文字コードではなく JavaScript での Unicode の文字コードになります
なので 本来の UTF-8 のバイナリデータにするためには Unicode から Shift_JIS へ変換しないといけないです

結局ここで encoding.js が必要になりました

あいう 123

「あいう 123」という文字列で試してみます

無理やり Shift_JIS として表示された結果「縺ゅ>縺� 123」という文字化け表示になります


これを単純に encoding.js で
Encoding.convert("縺ゅ>縺� 123", {to: "UTF8", from: "SJIS", type: "string"})
としたときは
??123

と全く日本語に戻りませんでした


charCodeAt で Unicode の文字コードを取得して Shift_JIS に変換後 UTF-8 として読み取ると

const text = "縺ゅ>縺� 123"
const uniarr = text.split("").map(e => e.charCodeAt())
const u8binary = Encoding.convert(uniarr, {to: "SJIS", from: "UNICODE"})
console.log(u8binary)
const result = new TextDecoder().decode(Uint8Array.from(u8binary))
console.log(result)

[227, 129, 130, 227, 129, 132, 227, 129, 63, 32, 49, 50, 51]
あい�? 123

「あい」は日本語になってほとんどできているのですが 少しだけ変です

「あいう 123」を UTF-8 にしたときのバイナリは

new TextEncoder().encode("あいう 123")
[227, 129, 130, 227, 129, 132, 227, 129, 134, 32, 49, 50, 51]

なので 63 と 134 のここだけが違っています

他にも色々試してみたのですが なぜか 63 になってしまっているケースが色々ありました
文字化けの表記では 「?」 になっているところです

Shift_JIS 的に不正な文字だから強制的に置き換えられた結果が「?」になっているのように思えます
つまり 本来の受け取ったバイナリデータは消えてしまっているということです


確認のために エディタで「あいう 123」と書いて UTF-8 で保存後 同じファイルを Shift_JIS として開いて 2行目にブラウザで表示されたものをコピペして 文字ごとにバイナリで見比べると やっぱり「?」のところに違いが出ていました

というわけで文字化け後のテキストから本来の文字を取り出すことは無理そうです


fetch して元のデータを再取得が唯一の方法かと思います
なんだかんだ encoding.js とか TextDecoder とかいらずに単純に取得したら UTF-8 として文字列になってるので 一番楽だったりもするんですよね
せっかくがんばったのに