XML データの Unmarshalling
今回は XML の Unmarshalling について。 つっても Unmarshalling 自体は encoding/xml パッケージを使って
stdata := &StructData{}
if err := xml.Unmarshal([]byte(xmldata), stdata); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
などとすればよい。
問題は XML データを受け入れる構造体(上述のコードで言うなら StructData
)をどう定義するかだ。
今回のお題となる XML データとして以下を考える。
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:dc="http://purl.org/dc/terms/"
xmlns:cc="http://web.resource.org/cc/"
>
<foaf:Document rdf:about="https://text.baldanders.info/">
<dc:title lang="ja">text.Baldanders.info</dc:title>
<dc:creator>Spiegel</dc:creator>
<dc:date>2018-03-16T20:40:27+09:00</dc:date>
<cc:license rdf:resource="https://creativecommons.org/licenses/by-sa/4.0/"/>
</foaf:Document>
</rdf:RDF>
この XML データの特徴は以下の通り。
- 要素名や属性名が名前空間(namespace)を含んでいる
- 要素の値として属性値と要素の内容(要素タグで囲まれているエリア)がある
- 時刻情報(RFC 3339 形式)を含んでいる
これを struct タグでどのように記述するかを次節から検討する。
名前空間について
たとえば名前空間を含まない
<creator>Spiegel</creator>
であれば struct タグは単純に
Creator string `xml:"creator"`
と記述できる。 しかし実際には
<dc:creator>Spiegel</dc:creator>
と名前空間を含んでいるため, struct タグを厳密に記述するなら
Creator string `xml:"http://purl.org/dc/terms/ creator"`
とする必要がある。
名前空間の指定は短縮名の dc
ではなく URI http://purl.org/dc/terms/
を記述する。
なお(名前空間を除いた)要素名や属性の混濁がないのであれば
Creator string `xml:"creator"`
のままでも問題なく parse できる。 この場合,名前空間は無視されるようだ。
値の取得
<dc:title>
の値には属性値と要素の内容の複数が含まれている。
<dc:title lang="ja">text.Baldanders.info</dc:title>
これらの値を全て取得するなら struct タグの記述は以下のようになる。
Title struct {
Language string `xml:"lang,attr"`
Name string `xml:",chardata"`
} `xml:"http://purl.org/dc/terms/ title"`
ちなみに要素の内容が <![CDATA[ ... ]]>
で囲まれるデータであれば chardata
ではなく cdata
を指定するとよい。
時刻情報の処理
xml
.Unmarshal()
関数には時刻情報を time
.Time
型に変換するロジックは用意されていない(基本型ではないので当然だが)。
したがって文字列から time
.Time
型に変換するロジックを自前で組み込む必要がある。
今回はフォーマットが RFC 3339 であることを前提に以下のようにした。
type Time struct {
time.Time
}
func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v string
if err := d.DecodeElement(&v, &start); err != nil {
return err
}
tm, err := time.Parse(time.RFC3339, v)
if err != nil {
return err
}
*t = Time{tm}
return nil
}
独自定義の Time
型が time
.Time
型のラッパー・クラスになっている点に注目。
この Time
型に UnmarshalXML()
関数を定義している。
これで構造体の要素は以下のように記述できる。
Date Time `xml:"http://purl.org/dc/terms/ date"`
xml
.Unmarshal()
関数は Time
型を解析するために Time.UnmarshalXML()
関数を呼び出すわけだ。
encoding/xml パッケージでは UnmarshalXML()
関数は以下のように定義されている。
type Unmarshaler interface {
UnmarshalXML(d *Decoder, start StartElement) error
}
xml
.Unmarshaler
インタフェースを持つ型であれば XML Unmarshalling 可能である1。
サンプルコード
以上を踏まえてお題の XML データの Unmarshalling コードは以下のようになる。
package main
import (
"encoding/xml"
"fmt"
"os"
"time"
)
var xmldata = `
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:dc="http://purl.org/dc/terms/"
xmlns:cc="http://web.resource.org/cc/"
>
<foaf:Document rdf:about="https://text.baldanders.info/">
<dc:title lang="ja">text.Baldanders.info</dc:title>
<dc:creator>Spiegel</dc:creator>
<dc:date>2018-03-16T20:40:27+09:00</dc:date>
<cc:license rdf:resource="https://creativecommons.org/licenses/by-sa/4.0/"/>
</foaf:Document>
</rdf:RDF>
`
type Time struct {
time.Time
}
func (t *Time) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var v string
if err := d.DecodeElement(&v, &start); err != nil {
return err
}
tm, err := time.Parse(time.RFC3339, v)
if err != nil {
return err
}
*t = Time{tm}
return nil
}
type RDF struct {
XMLName xml.Name `xml:"http://www.w3.org/1999/02/22-rdf-syntax-ns# RDF"`
Document struct {
URI string `xml:"http://www.w3.org/1999/02/22-rdf-syntax-ns# about,attr"`
Title struct {
Language string `xml:"lang,attr,omitempty"`
Name string `xml:",chardata"`
} `xml:"http://purl.org/dc/terms/ title"`
Creator string `xml:"http://purl.org/dc/terms/ creator"`
Date Time `xml:"http://purl.org/dc/terms/ date"`
License struct {
URI string `xml:"http://www.w3.org/1999/02/22-rdf-syntax-ns# resource,attr"`
} `xml:"http://web.resource.org/cc/ license"`
} `xml:"http://xmlns.com/foaf/0.1/ Document"`
}
func main() {
rdf := &RDF{}
if err := xml.Unmarshal([]byte(xmldata), rdf); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
}
自前で解析するより遥かにマシだが,面倒くさいことには変わりない。 XML とか○ねばいいのに(笑)
たとえば <dc:title>
要素の内容だけ取れればええんじゃあ! という場合は
type RDF struct {
Title string `xml:"Document>title"`
}
と要素を >
で繋ぐ記述も可能。
ただこの記述では名前空間をうまく指定できなかった。
うーん。
XML の Marshalling については機会があれば。 つか,構造化されたデータを XML Marshalling するのは不毛な気がする。 フォーマットが決まってるのであればテンプレートを使ったほうが早いんじゃないかなぁ…
【おまけ】 xml.Unmarshal() 関数の中身
余談だが xml
.Unmarshal()
関数の中身は
func Unmarshal(data []byte, v interface{}) error {
return NewDecoder(bytes.NewReader(data)).Decode(v)
}
となっている。
したがって入力がバイトデータであれば,わざわざ自前で Reader を作って xml
.NewDecoder()
を呼び出す必要はない。
逆に入力が Reader であるなら xml
.NewDecoder()
で xml
.Decoder
を生成するほうがいいかもしれない。
状況で使い分けよう。
ブックマーク
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。
- セマンティック HTML/XHTML
- 神崎 正英 (著)
- 毎日コミュニケーションズ 2009-05-28
- 単行本(ソフトカバー)
- 483993195X (ASIN), 9784839931957 (EAN), 483993195X (ISBN)
- 評価
残念ながら紙の本は実質的に絶版なんですよねぇ。是非デジタル化を希望します。