Go言語で XML をパースするメモ
とあるxmlをgolangでパースしてデータを操作したいので、golangでxsdからstruct のコードを生成するツールを探したところ、次のようなのがあった
ところが、 リポジトリーの冒頭にも書いてあるように Stale since 2013 (= 2013 から古い)(stale には古い/新鮮ではないといった意味がある様子)とあり、最終更新が8ヶ月前なので、利用するのが少しためらわれた。
そこで、もう少し探してみたところ、次のようなライブラリーを見つけた。
更新も1ヶ月前なので多分大丈夫だろうということで、早速使ってみることにした。
インストール
go get aqwari.net/xml/...
コード生成
まず手元にパースしたいxmlのxsdを準備する
curl https://maven.apache.org/xsd/maven-4.0.0.xsd -o maven-4.0.0.xsd
次に xsdgen
コマンドを呼び出す
xsdgen -o pom.go -pkg pom maven-4.0.0.xsd
なお、主なオプションは次の通り
$ xsdgen --help Usage of xsdgen: -ns value target namespace(s) to generate types for -o string name of the output file (default "xsdgen_output.go") -pkg string name of the the generated package -r value replacement rule 'regex -> repl' (can be used multiple times) -v print verbose output -vv print debug output
コードを生成すると次のようなgoコードが生成される
package pom import "encoding/xml" // 4.0.0+ // // The conditions within the build runtime environment which will trigger the // automatic inclusion of the build profile. Multiple conditions can be defined, which must // be all satisfied to activate the profile. type Activation struct { ActiveByDefault bool `xml:"http://maven.apache.org/POM/4.0.0 activeByDefault,omitempty"` Jdk string `xml:"http://maven.apache.org/POM/4.0.0 jdk,omitempty"` Os ActivationOS `xml:"http://maven.apache.org/POM/4.0.0 os,omitempty"` Property ActivationProperty `xml:"http://maven.apache.org/POM/4.0.0 property,omitempty"` File ActivationFile `xml:"http://maven.apache.org/POM/4.0.0 file,omitempty"` } func (t *Activation) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { type T Activation var overlay struct { *T ActiveByDefault *bool `xml:"http://maven.apache.org/POM/4.0.0 activeByDefault,omitempty"` } overlay.T = (*T)(t) overlay.ActiveByDefault = (*bool)(&overlay.T.ActiveByDefault) return d.DecodeElement(&overlay, &start) }
パースする
では生成されたコードを使って適当なxmlをパースする
とりあえず、ここでは junit-jupiter-api-5.0.0-M3.pom
をパースしてみる
まず main
関数. ここは何もしていない
package main import ( "encoding/xml" "io" "log" "os" "reflect" ) func main() { err := run("cmd/go-xml-analyze/testdata/junit-jupiter-api-5.0.0-M3.pom") if err != nil { log.Fatalln("error", err) } }
run
関数は普通の xml の取り回し
func run(file string) error { log.Println("parse file", file) pomXml, err := os.Open(file) if err != nil { log.Println("failed to open pom file.", err) return err } defer pomXml.Close() decoder := xml.NewDecoder(pomXml) for { elem, err := read(decoder) if err != nil { return err } if elem.show() { return nil } } }
どうハンドリングすればよいかよくわからなかったので適当に作った struct たち
// ただハンドルするだけ // 終わったら true/終わってなければ false type Elem interface { show() bool } // pom の project 要素のハンドル // dependencies の dependency 要素を表示する func (m pom.Model) show() bool { for _, dep := range m.Dependencies.Dependency { log.Println("group:", dep.GroupId, "artifact:", dep.ArtifactId, "version:", dep.Version, "scope:", dep.Scope) } return true } // project 要素が出る前のハンドル type Another struct { token xml.Token } func (a *Another) show() bool { return false }
読み取りの処理
func read(decoder *xml.Decoder) (Elem, error) { token, err := decoder.Token() if err != nil { log.Println("failed to get token", err) return nil, err } switch tokenType := token.(type) { case xml.StartElement: var project Model err := project.UnmarshalXML(decoder, tokenType) if err != nil { log.Println("failed to parse pom", err) return nil, err } return project, nil default: return &Another{token: tokenType}, nil } }
ポイントとしては、 一番上の要素を宣言して、 xml.Decoder
と xml.StartElement
をその Unmarshal
メソッドに渡すとデータをすべてパースしてくれるらしい
というわけで実行結果は次の通り.
2018/11/21 22:39:33 group: org.opentest4j artifact: opentest4j version: 1.0.0-M1 scope: compile 2018/11/21 22:39:33 group: org.junit.platform artifact: junit-platform-commons version: 1.0.0-M3 scope: compile
必要そうなデータが読み取れたようだ