Goのencoding/xmlの使い方について雑に紹介
xmlをパースする機会は減っているように思いますが、そういった雑用は突然やってくるものです。ここではGo言語の標準ライブラリencoding/xml
でxmlをパースする方法についてzennのRSSを使って雑に紹介します。
rssファイルをパースしてout.xml
として保存するというコードでパースできているか確認していきながら書いていきます。
package main
import (
"encoding/xml"
"log"
"os"
)
type ZennRss struct {
}
func main() {
r, err := os.Open("./feed.xml")
if err != nil {
log.Fatal(err)
}
defer r.Close()
var rec ZennRss
dec := xml.NewDecoder(r)
err = dec.Decode(&rec)
if err != nil {
log.Fatal(err)
}
w, err := os.Create("./out.xml")
if err != nil {
log.Fatal(err)
}
defer w.Close()
enc := xml.NewEncoder(w)
enc.Indent("", " ")
enc.Encode(&rec)
}
このコードを実行しても、エラーにはなりません。ただしout.xml
の内容は少し以外かもしれません。
<ZennRss></ZennRss>
以降ではZennRss
の定義のみ修正していきます。
トップレベルのタグ名を指定するようにします。
type ZennRss struct {
+ XMLName xml.Name `xml:"rss"`
}
こうすると保存されるファイルは次のように変わります。<?xml...
は抜けますが、少しましになりました。
<rss></rss>
じつはXMLName
はName
とかに変えるとうまく動きません。
<ZennRss>
<rss></rss>
</ZennRss>
属性をパースするときはxxx,attr
という感じで書いていきます。名前空間は省略しないと空文字列になるようです。
type ZennRss struct {
XMLName xml.Name `xml:"rss"`
+ XmlnsDC string `xml:"dc,attr"`
+ XmlnsContent string `xml:"content,attr"`
+ XmlnsAtom string `xml:"atom,attr"`
+ Version string `xml:"version,attr"`
}
out.xml
は次のようになり、名前空間が消えてしまいます。パースの目的からすると特に問題無いので気にしないことにします。
<rss dc="http://purl.org/dc/elements/1.1/" content="http://purl.org/rss/1.0/modules/content/" atom="http://www.w3.org/2005/Atom" version="2.0"></rss>
試しにxml:"rss"
をxml:"xmlns:dc
に変えると空になります。
<rss xmlns:dc="" content="http://purl.org/rss/1.0/modules/content/" atom="http://www.w3.org/2005/Atom" version="2.0"></rss>
channel
内のtitle
の部分を取ってみます。
@@ -12,6 +12,9 @@ type ZennRss struct {
XmlnsContent string `xml:"content,attr"`
XmlnsAtom string `xml:"atom,attr"`
Version string `xml:"version,attr"`
+ Channel struct {
+ Title string `xml:"title"`
+ } `xml:"channel"`
}
<![CDATA[..]]>
は特に意識しなくても読んでくれるようです。ただし、out.xml
には自動的にはCDATA
は付きません。
<rss dc="http://purl.org/dc/elements/1.1/" content="http://purl.org/rss/1.0/modules/content/" atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>Zennの「Zenn」のフィード</title>
</channel>
</rss>
色々飛ばしてitem
を取ってみます。これは連続してあるので配列とすると、すべての要素が取れます。
@@ -14,6 +14,15 @@ type ZennRss struct {
Version string `xml:"version,attr"`
Channel struct {
Title string `xml:"title"`
+ Items []struct {
+ Title string `xml:"title"`
+ Description string `xml:"description"`
+ Link string `xml:"link"`
+ GuID string `xml:"guid"`
+ PubDate string `xml:"pubDate"`
+ Enclosure string `xml:"enclosure"`
+ Creator string `xml:"creator"`
+ } `xml:"item"`
out.xml
を見ると、description
のCDATAが付いてないですね。パースのみを考えるとCDATA付ける必要ないですが、次のように変更するとCDATAで囲んでくれます。
@@ -16,12 +16,15 @@ type ZennRss struct {
Title string `xml:"title"`
Items []struct {
Title string `xml:"title"`
+ Description struct {
+ XMLName xml.Name `xml:"description"`
+ Value string `xml:",cdata"`
+ }
item
内のguid
のisPermaLink
が取れてないのが不満ですね。タグの中のデータと属性を同時に取るパターンは新しいですが、次のようにすれば取れます。
@@ -20,8 +20,12 @@ type ZennRss struct {
XMLName xml.Name `xml:"description"`
Value string `xml:",cdata"`
}
Link string `xml:"link"`
- GuID string `xml:"guid"`
+ GuID struct {
+ XMLName xml.Name `xml:"guid"`
+ IsPermaLink bool `xml:"isPermaLink,attr"`
+ Value string `xml:",chardata"`
+ }
PubDate string `xml:"pubDate"`
feed.xml
を見ながら、上に書いたのと同じパターンの繰り返しでZennRss
は出来ます。
package main
import (
"encoding/xml"
"log"
"os"
)
type ZennRss struct {
XMLName xml.Name `xml:"rss"`
XmlnsDC string `xml:"dc,attr"`
XmlnsContent string `xml:"content,attr"`
XmlnsAtom string `xml:"atom,attr"`
Version string `xml:"version,attr"`
Channel struct {
Title string `xml:"title"`
Description string `xml:"description"`
Link string `xml:"_ link"`
Image struct {
Url string `xml:"url"`
Title string `xml:"title"`
Link string `xml:"link"`
} `xml:"image"`
Generator string `xml:"generator"`
LastBuildDate string `xml:"lastBuildDate"`
AtomLink struct {
XMLName xml.Name `xml:"http://www.w3.org/2005/Atom link"`
Href string `xml:"href,attr"`
Rel string `xml:"rel,attr"`
Type string `xml:"type,attr"`
}
Language string `xml:"language"`
Items []struct {
Title string `xml:"title"`
Description struct {
XMLName xml.Name `xml:"description"`
Value string `xml:",cdata"`
}
Link string `xml:"link"`
GuID struct {
XMLName xml.Name `xml:"guid"`
IsPermaLink bool `xml:"isPermaLink,attr"`
Value string `xml:",chardata"`
}
PubDate string `xml:"pubDate"`
Enclosure struct {
XMLName string `xml:"enclosure"`
Url string `xml:"url,attr"`
Length int `xml:"length,attr"`
Type string `xml:"type,attr"`
}
Creator string `xml:"creator"`
} `xml:"item"`
} `xml:"channel"`
}
func main() {
r, err := os.Open("./feed.xml")
if err != nil {
log.Fatal(err)
}
defer r.Close()
var rec ZennRss
dec := xml.NewDecoder(r)
dec.DefaultSpace = "_"
err = dec.Decode(&rec)
if err != nil {
log.Fatal(err)
}
w, err := os.Create("./out.xml")
if err != nil {
log.Fatal(err)
}
defer w.Close()
enc := xml.NewEncoder(w)
enc.Indent("", " ")
enc.Encode(&rec)
}
xml:"_ link"
とxml:"http://www.w3.org/2005/Atom link"
は今までの書き方と違っていて、main関数でもdec.DefaultSpace = "_"
が1行追加されています。このように名前空間を扱わないとlink
同士でコンフリクトを起こします。出来上がるout.xml
は多少おかしいところもありますが、パースの方はうまくできています。
実用的にはxml:"item"
をxml:"channel>item"
などとすると構造体の階層を深く作らなくても大丈夫です。
type ZennRss struct {
XMLName xml.Name `xml:"rss"`
Items []struct {
Title string `xml:"title"`
Description struct {
XMLName xml.Name `xml:"description"`
Value string `xml:",cdata"`
}
Link string `xml:"link"`
GuID struct {
XMLName xml.Name `xml:"guid"`
IsPermaLink bool `xml:"isPermaLink,attr"`
Value string `xml:",chardata"`
}
PubDate string `xml:"pubDate"`
Enclosure struct {
XMLName string `xml:"enclosure"`
Url string `xml:"url,attr"`
Length int `xml:"length,attr"`
Type string `xml:"type,attr"`
}
Creator string `xml:"creator"`
} `xml:"channel>item"`
}