Posted at

Cybozu ガルーン API のレスポンスのXMLを golang でパースする

More than 5 years have passed since last update.

前回取得したXMLをパースする部分を書いてみる


encoding/xmlでのパース

encoding/xmlでは、XMLの構造に一致するように構造体を作ると簡単にパースできるようです

レスポンスのXMLは以下のような感じ

<?xml version="1.0" encoding="utf-8"?>

<soap:Envelope
xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:schedule="http://wsdl.cybozu.co.jp/schedule/2008">
<soap:Header>・・・・・</soap:Header>
<soap:Body>
<schedule:ScheduleGetEventsResponse>
<returns>
<schedule_event id="1351459" event_type="repeat" public_type="public" plan="プラン名" detail="タイトル" version="1387216782" timezone="Asia/Tokyo" allday="false" start_only="false">
<members xmlns="http://schemas.cybozu.co.jp/schedule/2008">
<member>
<user id="1111" name="名前" order="0"/>
</member>
</members>
<repeat_info xmlns="http://schemas.cybozu.co.jp/schedule/2008">
<condition type="weekday" day="1"
week="3" start_date="2014-01-01" end_date="2014-01-31"
start_time="10:00:00" end_time="10:30:00"/>
<exclusive_datetimes>
</exclusive_datetimes>
</repeat_info>
</schedule_event>
</returns>
</schedule:ScheduleGetEventsResponse>
</soap:Body>
</soap:Envelope>

こちらをパースするための構造体を定義

type XMLexclusive struct {

XMLName xml.Name `xml:"exclusive_datetime"`
Start string `xml:"start,attr"`
End string `xml:"end,attr"`
}
type XMLrepeat_condition struct {
XMLName xml.Name `xml:"condition"`
Type string `xml:"type,attr"`
Day string `xml:"day,attr"`
Week string `xml:"week,attr"`
StartDate string `xml:"start_date,attr"`
EndDate string `xml:"end_date,attr"`
StartTime string `xml:"start_time,attr"`
EndTime string `xml:"end_time,attr"`
}
type XMLrepeat struct {
XMLName xml.Name `xml:"repeat_info"`
Condition *XMLrepeat_condition `xml:"condition"`
Exclusive []*XMLexclusive `xml:"exclusive_datetimes>exclusive_datetime"`
}

type XMLdatetime struct {
XMLName xml.Name `xml:"datetime"`
Start string `xml:"start,attr"`
End string `xml:"end,attr"`
}
type XMLschedule_event struct {
XMLName xml.Name `xml:"schedule_event"`
Id int `xml:"id,attr"`
EventType string `xml:"event_type,attr"`
PublicType string `xml:"public_type,attr"`
Plan string `xml:"plan,attr"`
Detail string `xml:"detail,attr"`
TimeZone string `xml:"timezone,attr"`
Version int `xml:"version,attr"`
Allday bool `xml:"allday,attr"`
StartOnly bool `xml:"start_only,attr"`
Datetime *XMLdatetime `xml:"when>datetime"`
Repeat *XMLrepeat `xml:"repeat_info"`
}
type XMLSoap struct {
XMLName xml.Name `xml:"Envelope"`
Schedule_event []*XMLschedule_event `xml:"Body>ScheduleGetEventsResponse>returns>schedule_event"` // 「>」で接続することでelementツリーをその順にたどってくれるようです
}

こんな感じで。

ちょっと構造をネストで定義していくのが面倒ですが、

取得する必要の無いエレメントやアトリビュート等は記述しなくてもよいので楽です。

また、上記のように変数に対応するエレメントを表す文字列に、xml:"Body>ScheduleGetEventsResponse>returns>schedule_event"

のような書き方をすると、

取り出したい値がネストの深い所にある場合、関係ない階層をすっ飛ばして取得することが可能です。


パース処理

あとは定義した構造体をパーサーに渡すだけです。


// Soapレスポンスをパースする
func ReadSoap(reader io.Reader) ([]*XMLschedule_event, error) {
xmlSoap := &XMLSoap{}
decoder := xml.NewDecoder(reader)

if err := decoder.Decode(xmlSoap); err != nil {
return nil, err
}

return xmlSoap.Schedule_event, nil
}

func main() {
var xmlSoapBody []*XMLschedule_event
var file *os.File

defer func() {
if file != nil {
file.Close()
}
}()
strapsFilePath, err := filepath.Abs("test_schedule_event.xml")
if err != nil {
panic(err.Error())
}
file, err = os.Open(strapsFilePath)
if err != nil {
panic(err.Error())
}
xmlSoapBody, err = ReadSoap(file)
if err != nil {
panic(err.Error())
}
pretty.Printf("xmlSoapBody:%# v\n", xmlSoapBody)
}

これで必要な情報のデコードができました。