AutoHotkey:文字エンコーディングに対応する

ツール活用

Image by Stable Diffusion

ブログはObsidianを扱い出すようになってから再開したが、Obsidianを単体で利用するよりは、外部アプリの利便性も活かしたい。
そういう意味ではAutoHotkeyは、応用力の高い面白い言語だと思う。

日本語は2バイト文字だ。
文字コードは今やUTF-8(可変長マルチバイト文字)全盛で、言語圏に関わらず扱える点では日本人でもありがたい話だが、一昔前のWebでは日本語を表示するのに、Shift-JISやEUC-JPといった日本語専用2バイト文字コードを使っていた。

ObsidianにURLからWebブログカードを貼るのに、Auto Card Linkプラグインという便利なものがあるが、残念ながら一昔前のWebサイトが想定されておらず、Shift-JISやEUC-JPのWebサイトURLを貼ると文字化けしてしまう。

前置きが長くなったが、Auto Card Linkプラグインを使わずにAutoHotkeyから文字エンコーディング判別するスクリプトをChatGPT君とやりとりして提案してもらった。

WinHttpよりダウンロード

AutoHotkeyからWebにアクセスする手段はとりあえず二択。

  • ComObjCreateでWinHttpやXmlHttpでオブジェクトに取得
  • UrlDownloadToFileでローカルにダウンロードする

アクセスしたとしても一時ファイル的なものなので、WinHttpで何とかしようとしていたが、AutoHotkeyは取り込んだ時点でUTF-8として読み取ってしまうらしく、Shift-JISやEUC-JPのWebサイトは文字化けしてしまう。

ChatGPT君と30分くらいあーだこーだした末に提案されたのが、UrlDownloadToFileで一度ローカル保存してしまう方法。
渋々試すと、文字エンコーディングが保たれた状態のファイルだった。

一度ローカル保存してから、そのファイルを読み込んでUTF-8にエンコーディングすることで目的に達することができた。

ただし文字エンコーディングを判別する手段は「とりあえず何かの文字コードにエンコードして、特徴ある文字化けが含まれていたら次を試す」という力業だった。
他の言語やAPI使わずだとAutoHotkeyではこの辺りが限界の模様。

スクリプト例

以下はUTF-8、Shift-JIS、EUC-JPのWebサイトを文字化けすることなくエンコーディングするAutoHotkeyスクリプトのサンプルです。

文字コード判別に使う文字はサンプルのものでダメな場合もあるので、お手元でいろいろ試してみてください。
なお、ISO-2022-JPは特殊すぎるのと、それで書かれたWebはさすがにレアすぎるためスルーしました。

  • Shift-JISのWeサイト例
    KENT-WEB
    https://www.kent-web.com/pubc/garble.html
  • EUC-JPのWebサイト例
    一般財団法人 日本防火・防災協会
    https://www.n-bouka.or.jp/sitepolicy/
vURL := "https://~~"
vTmp := "<Your Local Folder>\tmp.html"
FileDelete, % vTmp
UrlDownloadToFile, % vURL, % vTmp

; ファイルのエンコーディングを判定し、UTF-8に変換する
vHTML := ""
if FileExist(vTmp) {
    ; UTF-8での変換に失敗した場合
    if InStr(vHTML := ConvertFileTo(vTmp, "UTF-8"), "�") {
        ; Shift-JISでの変換に失敗した場合
        if InStr(vHTML := ConvertFileTo(vTmp, "Shift-JIS"), "") {
            ; EUC-JPに変換
            vHTML := ConvertFileTo(vTmp, "EUC-JP") 
        }
    }
}
FileDelete, % vTmp

ConvertFileTo(vFilePath, vEncoding) {
    oStream := ComObjCreate("ADODB.Stream")
    oStream.Open()
    oStream.Type := 1 ; バイナリーモード
    oStream.LoadFromFile(vFilePath)
    oStream.Position := 0
    oStream.Type := 2 ; テキストをバイト列に変換
    oStream.Charset := vEncoding
    sText := oStream.ReadText()
    oStream.Close()
    Return sText
}