😊
[Go言語]encoding/xmlパッケージの使い方
はじめに
GoでXML処理を実装する機会があり、そのときに調べて理解したことをまとめました。
XML関連の処理はencoding/xml
パッケージを使います。
XMLとは
Extensible Markup Language (XML) を使用すると、共有可能な方法でデータを定義および保存できます。XML は、ウェブサイト、データベース、サードパーティーアプリケーションなどのコンピュータシステム間における情報のやり取りをサポートします。事前に定義されたルールを使用すると、受信者はこれらのルールを使用してデータを正確かつ効率的に読み取ることができるため、任意のネットワーク上でデータを XML ファイルとして簡単に送信できます。
参考:XML とは何ですか?
XMLと似ているものとしてHTMLがあります。
両者の特徴は次の通りです。
HTML
- HTMLはデータの提示と表示を目的
- 表示タグは事前に定義されたもの
- タグには大文字小文字の区別なし
XML
- データの格納と送信を目的
- 独自のタグを定義可能
- タグには大文字小文字の区別あり(一致しないと構文エラーになる)
構造体からXMLへ変換
Marshal関数もしくはMarshalIndent関数のどちらかでXMLを生成できます。
関数の主な違いは,MarshalIndentが生成するXMLの見やすさをを調整できます。
Marshal
func Marshal(v any) ([]byte, error)
サンプルコード
main.go
package main
import (
"encoding/xml"
"fmt"
"os"
)
type Foods struct {
XMLName xml.Name `xml:"foods"`
Food []food `xml:"food"`
Category string `xml:"category,attr"`
}
type food struct {
Name string `xml:"name"`
Price string `xml:"price"`
}
func main() {
v := &Foods{Category: "1"}
v.Food = append(v.Food, food{Name: "ラーメン", Price: "700円"})
v.Food = append(v.Food, food{Name: "パスタ", Price: "850円"})
v.Food = append(v.Food, food{Name: "うどん", Price: "500円"})
output, err := xml.Marshal(v)
if err != nil {
fmt.Printf("error: %v\n", err)
}
os.Stdout.Write([]byte(xml.Header))
os.Stdout.Write(output)
}
生成されたXML
<?xml version="1.0" encoding="UTF-8"?>
<foods category="1"><food><name>ラーメン</name><price>700円</price></food><food><name>パスタ</name><price>850円</price></food><food><name>うどん</name><price>500円</price></food></foods>
Marshal関数は何をしているのか
func Marshal(v any) ([]byte, error) {
var b bytes.Buffer
enc := NewEncoder(&b)
if err := enc.Encode(v); err != nil {
return nil, err
}
if err := enc.Close(); err != nil {
return nil, err
}
return b.Bytes(), nil
}
MarshalIndent
func MarshalIndent(v any, prefix, indent string) ([]byte, error)
サンプルコード
main.go
package main
import (
"encoding/xml"
"fmt"
"os"
)
type Foods struct {
XMLName xml.Name `xml:"foods"`
Food []food `xml:"food"`
Category string `xml:"category,attr"`
}
type food struct {
Name string `xml:"name"`
Price string `xml:"price"`
}
func main() {
v := &Foods{Category: "1"}
v.Food = append(v.Food, food{Name: "ラーメン", Price: "700円"})
v.Food = append(v.Food, food{Name: "パスタ", Price: "850円"})
v.Food = append(v.Food, food{Name: "うどん", Price: "500円"})
output, err := xml.MarshalIndent(v, " ", " ")
if err != nil {
fmt.Printf("error: %v\n", err)
}
os.Stdout.Write([]byte(xml.Header))
os.Stdout.Write(output)
}
生成されたXML
<?xml version="1.0" encoding="UTF-8"?>
<foods category="1">
<food>
<name>ラーメン</name>
<price>700円</price>
</food>
<food>
<name>パスタ</name>
<price>850円</price>
</food>
<food>
<name>うどん</name>
<price>500円</price>
</food>
</foods>
MarshalIndent関数は何をしているのか
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
var b bytes.Buffer
enc := NewEncoder(&b)
enc.Indent(prefix, indent)
if err := enc.Encode(v); err != nil {
return nil, err
}
if err := enc.Close(); err != nil {
return nil, err
}
return b.Bytes(), nil
}
補足
XML処理で知っておくべきこと
XMLヘッダーの定義
[]byte(xml.Header)
で定義可能。
MarshalやMarshalIndentの関数ではXMLヘッダー情報を持ちません。
そのため意図的にXMLヘッダーを定義する必要があります。
構造体フィールドタグ
type Foods struct {
XMLName xml.Name `xml:"foods"`
Food []food `xml:"food"`
Category string `xml:"category,attr"`
}
type food struct {
Name string `xml:"name"`
Price string `xml:"price"`
}
- XMLNameフィールドは、その構造体で表すXML要素名を定義します
- XML要素名を構造体のフィールド名ではなく、タグで指定した名前で表示できる(
xml:"food"
で要素名をfoodに指定) - 構造体のフィールド名をXML要素ではなく、XML属性に定義できる(
xml:"category,attr"
のattrで属性宣言し、属性名をcategoryとする) - XML要素の出力順を指定できる(入れ子のときにどの順番で表示するか)
- フィールドタグによってはXMLには非表示、コメントを出力することなどできます
XMLから構造体へ変換
Unmarshal関数でXMLから構造体を生成できます(構造体以外も生成可)。
Unmarshal
func Unmarshal(data []byte, v any) error
サンプルコード
main.go
package main
import (
"encoding/xml"
"fmt"
)
type Foods struct {
XMLName xml.Name `xml:"foods"`
Food []food `xml:"food"`
Category string `xml:"category,attr"`
}
type food struct {
Name string `xml:"name"`
Price string `xml:"price"`
}
func main() {
data := `
<?xml version="1.0" encoding="UTF-8"?>
<foods category="1">
<food>
<name>ラーメン</name>
<price>700円</price>
</food>
<food>
<name>パスタ</name>
<price>850円</price>
</food>
<food>
<name>うどん</name>
<price>500円</price>
</food>
</foods>
`
v := Foods{}
err := xml.Unmarshal([]byte(data), &v)
if err != nil {
fmt.Printf("error: %v", err)
return
}
fmt.Printf("XMLName: %#v\n", v.XMLName)
fmt.Printf("Food: %q\n", v.Food)
fmt.Printf("Category: %q\n", v.Category)
}
出力結果
XMLName: xml.Name{Space:"", Local:"foods"}
Food: [{"ラーメン" "700円"} {"パスタ" "850円"} {"うどん" "500円"}]
Category: "1"
Unmarshal関数は何をしているのか
func Unmarshal(data []byte, v any) error {
return NewDecoder(bytes.NewReader(data)).Decode(v)
}
(おまけ) zennのRSSフィードからトレンドの記事を出力する
zennのRSSフィードはこちら
出力内容は次の通りで実装しています
- 作成日時
- 作成者
- 記事タイトル
- 記事のリンク
コード
main.go
package main
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"time"
)
type XML struct {
Articles []struct {
Title string `xml:"title"`
Link string `xml:"link"`
Date string `xml:"pubDate"`
Creator string `xml:"creator"`
} `xml:"channel>item"`
}
func main() {
data := httpGet("https://zenn.dev/feed")
result := XML{}
err := xml.Unmarshal([]byte(data), &result)
if err != nil {
fmt.Printf("error: %v", err)
return
}
for _, article := range result.Articles {
datetime, _ := time.Parse("Mon, 02 Jan 2006 15:04:05 MST", article.Date)
fmt.Printf("%v\n", datetime.Format("2006/01/02 15:04:05"))
fmt.Printf("%s\n", article.Creator)
fmt.Printf("%s\n", article.Title)
fmt.Printf("%v\n", article.Link)
fmt.Println()
}
}
func httpGet(url string) string {
res, _ := http.Get(url)
body, _ := io.ReadAll(res.Body)
defer res.Body.Close()
return string(body)
}
出力結果(一部)
2023/06/25 02:24:22
LisRas
TestFlight リリースversionの変更のやり方 iOS
https://zenn.dev/lisras/articles/bf02fff7fb2760
・・・省略
Discussion