いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-258 (Non UTF-8 なXMLデータをデコード)(CharsetReader, Shift_JIS)

概要

知らなかったので、忘れないうちにメモメモ。。。

通常、XMLのエンコーディングはUTF-8となっているものがほとんどですが、業務とかだと稀に Shift_JIS とかなっている場合があります。

例えば、以下のような感じ。

<?xml version="1.0" encoding="Shift_JIS"?>
<data>
    <hello>へろー</hello>
    <world>ワールド</world>
</data>

当然、ファイルのエンコーディングはShift-JISで保存されてるので、VSCodeとかで見るとデフォでは化けます。

上記のようなXMLデータを xml.Unmarshal しようとすると、データを読み出す io.Reader がShift-JISに対応していても、以下のようにエラーが返ってきます。

xml: encoding "Shift_JIS" declared but Decoder.CharsetReader is nil

「エンコーディングのところが Shift_JIS ってなってるけど、 Decoder.CharsetReader が nil やで」

って言ってますね。最初、CharsetReader って何?ってなりました。

以下に記載がありました。

https://pkg.go.dev/encoding/xml@go1.19.2#Decoder

なるほど。なので、これを設定すれば良いということですね。

てことで、試してみました。

サンプル

データとして利用するのは上に挙げた XML です。

// XML宣言にてencodingの指定がUTF-8ではない場合のXMLデコードのサンプルです.
//
// # REFERENCES
//
//   - https://stackoverflow.com/questions/54915307/error-unmarshaling-a-simple-xml-in-golang
//   - https://qiita.com/bamchoh/items/a4c64ace78200bf0fa6e
//   - https://pkg.go.dev/encoding/xml@go1.19.2#Decoder
package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "log"

    "github.com/devlights/gomy/fileio"
    "github.com/devlights/gomy/fileio/jp"
)

type (
    xmlData struct {
        XMLName xml.Name `xml:"data"`
        Hello   string   `xml:"hello"`
        World   string   `xml:"world"`
    }
)

func (me xmlData) String() string {
    return fmt.Sprintf("hello=%s\tworld=%s", me.Hello, me.World)
}

func fail() error {
    var (
        r      io.Reader
        closer func() error
        err    error
    )

    r, closer, err = fileio.OpenRead("sample.xml", jp.ShiftJis)
    if err != nil {
        return err
    }
    defer closer()

    var (
        data    xmlData
        decoder = xml.NewDecoder(r)
    )

    // encoding="shift-jis" なデータをそのままUnmarshalするとエラーになる
    //
    // 以下のランタイムエラーが出る
    //   xml: encoding "shift-jis" declared but Decoder.CharsetReader is nil
    err = decoder.Decode(&data)
    if err != nil {
        fmt.Printf("[fail] %v\n", err)
        return nil
    }

    fmt.Printf("[fail] %v\n", data)

    return nil
}

func succ() error {
    var (
        r      io.Reader
        closer func() error
        err    error
    )

    r, closer, err = fileio.OpenRead("sample.xml", jp.ShiftJis)
    if err != nil {
        return err
    }
    defer closer()

    var (
        data    xmlData
        decoder = xml.NewDecoder(r)
    )

    // encoding="shift-jis" なデータをそのままUnmarshalするとエラーになる
    //
    // XML宣言にてUTF-8以外のエンコーディングが指定されている場合
    // CharsetReaderが呼び出されるため、設定する必要がある.
    decoder.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) {
        // 既に shift-jis で読み出せる io.Reader なので、そのまま返す.
        // そうではない場合は、ここでラップして返す.
        return input, nil
    }

    err = decoder.Decode(&data)
    if err != nil {
        return err
    }

    fmt.Printf("[succ] %v\n", data)

    return nil
}

func run() error {
    var (
        err error
    )

    err = fail()
    if err != nil {
        return err
    }

    err = succ()
    if err != nil {
        return err
    }

    return nil
}

func main() {
    err := run()
    if err != nil {
        log.Fatalln(err)
    }
}
version: "3"

tasks:
  default:
    cmds:
      - go run main.go

実行すると以下のようになります。

$ task -d examples/singleapp/xml_shiftjis/
task: [default] go run main.go
[fail] xml: encoding "Shift_JIS" declared but Decoder.CharsetReader is nil
[succ] hello=へろー     world=ワールド

参考情報

try-golang/examples/singleapp/xml_shiftjis at master · devlights/try-golang · GitHub

stackoverflow.com

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。