覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Go言語 - XMLを読んで特定の要素を削って出力

Golangではencoding/xmlパッケージでXMLを簡単に扱えそうなので、Hello World的に試してみました。

今回のエントリでは、XMLファイルを読んで特定のエレメントを削ったXMLを再出力するということをやってみました。
(といっても、Unmarshal(XML⇒構造体への変換)する構造体に対象要素に紐づくメンバを用意しないだけで実現できます)


前提

今回は以下のようなXMLが書かれたファイルがあるとして、その中からdescriptionというエレメントを除外したいとします

<?xml version="1.0" encoding="UTF-8"?>
<servers>
  <server>
    <id>s0001</id>
    <name>サーバA-001</name>
    <port>443</port>
    <description>試験用のサーバです</description>
    <subscriber_id>sub000002</subscriber_id>
    <contract_period>
      <start_date>2017-10-01</start_date>
      <end_date>2021-09-30</end_date>
    </contract_period>
  </server>
  <server>
    <id>s0002</id>
    <name>サーバA-002</name>
    <port>8080</port>
    <description>ECサイトで利用するための</description>
    <subscriber_id>sub000001</subscriber_id>
    <contract_period>
      <start_date>2012-04-01</start_date>
      <end_date>2024-03-31</end_date>
    </contract_period>
  </server>
  <server>
    <id>s0003</id>
    <name>サーバA-002</name>
    <port>8080</port>
    <description>利用用途未定</description>
    <subscriber_id>sub000003</subscriber_id>
    <contract_period>
      <start_date>2015-04-01</start_date>
      <end_date>2019-03-31</end_date>
    </contract_period>
  </server>
</servers>


Golang側のコードでは、上記XMLをUnmarshalして紐づける構造体を以下のように用意しました。
(server構造体に、descriptionのエレメントを紐づける変数がありません)

除外する要素以外は構造体のタグで紐づけを行っています

type ServerList struct {
    XMLName xml.Name `xml:"servers"`
    Svs     []server `xml:"server"`
}

type server struct {
    XMLName                   xml.Name `xml:"server"`
    ID                        string   `xml:"id"`
    ServerName                string   `xml:"name"`
    Port                      string   `xml:"port"`
    SubscriberID              string   `xml:"subscriber_id"`
    ContractStartDate         string   `xml:"contract_period>start_date"`
    ContractEndDate           string   `xml:"contract_period>end_date"`
}


実行サンプル

以下XMLファイルを読んで、エレメント削って出力するGoのプログラムです。
読み込むXMLファイルをコマンドラインで -f パラメータに与えて、エレメントを除外した結果を標準出力に出力するようにしています。
(そのため、結果をリダイレクトれば結果をファイルに出力できます)

package main

import (
    "encoding/xml"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
)

type ServerList struct {
    XMLName xml.Name `xml:"servers"`
    Svs     []server `xml:"server"`
}

type server struct {
    XMLName                   xml.Name `xml:"server"`
    ID                        string   `xml:"id"`
    ServerName                string   `xml:"name"`
    Port                      string   `xml:"port"`
    SubscriberID              string   `xml:"subscriber_id"`
    ContractStartDate         string   `xml:"contract_period>start_date"`
    ContractEndDate           string   `xml:"contract_period>end_date"`
}

func main() {
    var (
        srcXML string
    )

    flag.StringVar(&srcXML, "f", "servers.xml.", "src xml path.")

    flag.Parse()

    file, err := os.Open(srcXML)
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }
    defer file.Close()
    data, err := ioutil.ReadAll(file)
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }
    serverList := ServerList{}
    err = xml.Unmarshal(data, &serverList)
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }

    output, err := xml.MarshalIndent(serverList, "  ", "    ")
    if err != nil {
        fmt.Printf("error: %v\n", err)
    }
    os.Stdout.Write([]byte(xml.Header))
    os.Stdout.Write(output)
}


ビルド(go build)後に以下のようにコマンドを実行(original.xmlが元のXMLファイル)

xmlconv.exe -f original.xml > dest.xml


結果、dest.xmlには以下のような内容が出力されています(descriptionというエレメントが除外されています)

<?xml version="1.0" encoding="UTF-8"?>
<servers>
    <server>
        <id>s0001</id>
        <name>サーバA-001</name>
        <port>443</port>
        <subscriber_id>sub000002</subscriber_id>
        <contract_period>
            <start_date>2017-10-01</start_date>
            <end_date>2021-09-30</end_date>
        </contract_period>
    </server>
    <server>
        <id>s0002</id>
        <name>サーバA-002</name>
        <port>8080</port>
        <subscriber_id>sub000001</subscriber_id>
        <contract_period>
            <start_date>2012-04-01</start_date>
            <end_date>2024-03-31</end_date>
        </contract_period>
    </server>
    <server>
        <id>s0003</id>
        <name>サーバA-002</name>
        <port>8080</port>
        <subscriber_id>sub000003</subscriber_id>
        <contract_period>
            <start_date>2015-04-01</start_date>
            <end_date>2019-03-31</end_date>
        </contract_period>
    </server>
</servers>


というわけで簡単にXMLファイルを扱うことができました