C.23. Parse & Generate XML (etree)
Pada chapter ini kita akan belajar cara parsing file xml, dan cara membuat xml baru. Library yang digunakan adalah etree, silakan go get
terlebih dahulu.
go get -u github.com/beevik/etree
C.23.1. Membaca dan Parsing File XML
Mari langsung kita praktekan, siapkan folder project baru. Buat satu buah file data.xml
, isinya sebagai berikut.
<?xml version="1.0" encoding="UTF-8"?>
<website>
<title>Noval Agung</title>
<url>https://novalagung.com</url>
<contents>
<article>
<category>Server</category>
<title>Connect to Oracle Server using Golang and Go-OCI8 on Ubuntu</title>
<url>/go-oci8-oracle-linux/</url>
</article>
<article>
<category>Server</category>
<title>Easy Setup OpenVPN Using Docker DockVPN</title>
<url>/easy-setup-openvpn-docker/</url>
</article>
<article info="popular article">
<category>Server</category>
<title>Setup Ghost v0.11-LTS, Ubuntu, Nginx, Custom Domain, and SSL</title>
<url>/ghost-v011-lts-ubuntu-nginx-custom-domain-ssl/</url>
</article>
</contents>
</website>
Silakan perhatikan xml di atas, akan kita ambil semua element article
beserta isinya, untuk kemudian ditampung dalam slice.
Buat file main, di dalamnya, buat objek dokumen bertipe etree.Document
lewat fungsi etree.NewDocument()
. Dari objek tersebut, baca file xml yang sudah dibuat, gunakan method .ReadFromFile()
untuk melakukan proses baca file.
package main
import (
"encoding/json"
"github.com/beevik/etree"
"log"
)
type M map[string]interface{}
func main() {
doc := etree.NewDocument()
if err := doc.ReadFromFile("./data.xml"); err != nil {
log.Fatal(err.Error())
}
// ...
}
Dari objek doc
, ambil root element <website/>
, lalu akses semua element <article/>
kemudian lakukan perulangan. Di dalam tiap perulangan, ambil informasi title
, url
, dan category
, tampung sebagai element slice rows
.
root := doc.SelectElement("website")
rows := make([]M, 0)
for _, article := range root.FindElements("//article") {
row := make(M)
row["title"] = article.SelectElement("title").Text()
row["url"] = article.SelectElement("url").Text()
categories := make([]string, 0)
for _, category := range article.SelectElements("category") {
categories = append(categories, category.Text())
}
row["categories"] = categories
if info := article.SelectAttr("info"); info != nil {
row["info"] = info.Value
}
rows = append(rows, row)
}
Objek element menyediakan beberapa method untuk keperluan seleksi dan pencarian element. Empat di antaranya sebagai berikut.
- Method
.SelectElement()
, untuk mengambil satu buah child element sesuai selector. - Method
.SelectElements()
, sama seperti.SelectElement()
, perbedannya yang dikembalikan adalah semua child elements (sesuai selector). - Method
.FindElement()
, untuk mencari elements dalam current element, bisa berupa child, grand child, atau level yang lebih dalam, sesuai selector. Yang dikembalikan satu buah element saja. - Method
.FindElements()
, sama seperti.FindElement()
, perbedannya yang dikembalikan adalah semua elements (sesuai selector).
Pada kode di atas, hasil dari statement root.FindElements("//article")
di looping. Statement tersebut mengembalikan banyak element sesuai selector pencarian. Arti selector //article
sendiri adalah melakukan pencarian element dengan nama article
secara rekursif.
Di tiap perulangan, child element title
dan url
diambil. Gunakan method .Text()
untuk mengambil isi element.
Sedangkan pada element <category/>
pencarian child elements dilakukan menggunakan method .SelectElements()
, karena beberapa artikel memiliki lebih dari satu category.
Untuk mengakses value dari atribut, gunakan method .SelectAttr()
.
Setelah perulangan selesai, data artikel ada dalam objek rows
. Tampilkan isinya sebagai JSON string.
bts, err := json.MarshalIndent(rows, "", " ")
if err != nil {
log.Fatal(err)
}
log.Println(string(bts))
Jalankan aplikasi, lihat hasilnya.
C.23.2. XML Query
XQuery atau XML Query adalah bahasa query untuk pengolahan XML. Spesifikasinya bisa dilihat di https://www.w3.org/TR/xquery-31.
Pada pembahasan di atas kita menggunakan query //article
untuk melakukan pencarian semua element artikel secara rekursif.
Berikut adalah contoh lain implementasi xquery yang lebih kompleks.
popularArticleText := root.FindElement(`//article[@info='popular article']/title`)
if popularArticleText != nil {
log.Println("Popular article", popularArticleText.Text())
}
Penjelasan mengenai xquery //article[@info='popular article']/title
dipecah menjadi 3 tahap agar mudah untuk dipahami.
- Selector bagian
//article
, artinya dilakukan pencarian rekursif dengan kriteria: element bernamaarticle
. - Selector bagian
[@info='popular article']
, artinya dilakukan pencarian dengan kriteria: element memiliki atributinfo
yang berisipopular article
. - Selector bagian
/title
, artinya dilakukan pencarian child element dengan kriteria: element bernamatitle
.
Jika 3 penjelasan bagian di atas digabungkan, maka kurang lebih arti dari //article[@info='popular article']/title
adalah, dilakukan pencarian secara rekursif dengan kriteria adalah: element bernama article
dan harus memiliki atribut info
yang berisi popular article
, setelah diketemukan, dicari child element-nya menggunakan kriteria: element bernama title
.
Berikut adalah hasil dari query di atas.
Silakan coba explore sendiri mengenai xquery untuk contoh lainnya.
C.23.3. Membuat XML dari Golang
Di atas kita telah mempelajari cara baca XML; kali ini kita akan coba buat file XML menggunakan etree. Informasi yang akan ditulis ke file xml datanya bersumber dari JSON string (yang nantinya di-decode terlebih dahulu ke bentuk objek sebelum digunakan).
Siapkan file baru, buat struct Document
. Nantinya sebuah objek dicetak lewat struk ini, tugasnya sendiri adalah menampung data hasil proses decoding json.
package main
import (
"encoding/json"
"github.com/beevik/etree"
"log"
)
type Document struct {
Title string
URL string
Content struct {
Articles []struct {
Title string
URL string
Categories []string
Info string
}
}
}
func main () {
// code here
}
Siapkan JSON string.
const jsonString = `{
"Title": "Noval Agung",
"URL": "https://novalagung.com",
"Content": {
"Articles": [{
"Categories": [ "Server" ],
"Title": "Connect to Oracle Server using Golang and Go-OCI8 on Ubuntu",
"URL": "/go-oci8-oracle-linux/"
}, {
"Categories": [ "Server", "VPN" ],
"Title": "Easy Setup OpenVPN Using Docker DockVPN",
"URL": "/easy-setup-openvpn-docker/"
}, {
"Categories": [ "Server" ],
"Info": "popular article",
"Title": "Setup Ghost v0.11-LTS, Ubuntu, Nginx, Custom Domain, and SSL",
"URL": "/ghost-v011-lts-ubuntu-nginx-custom-domain-ssl/"
}]
}
}`
Decode JSON string di atas ke objek cetakan Document
.
data := Document{}
err := json.Unmarshal([]byte(jsonString), &data)
if err != nil {
log.Fatal(err.Error())
}
Selanjutnya buat objek etree baru, siapkan root element website
. Di dalamnya buat 2 child elements: title
dan url
, nilai masing-masing didapat dari objek data
.
doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
website := doc.CreateElement("website")
website.CreateElement("title").SetText(data.Title)
website.CreateElement("url").SetText(data.URL)
Method .CreateElement()
digunakan untuk membuat child element baru. Pemanggilannya disertai dengan satu parameter, yang merupakan representasi dari nama element yang ingin dibuat.
Method .SetText()
digunakan untuk menge-set nilai element.
Siapkan satu element lagi di bawah root, namanya contents
. Loop objek slice artikel, dan di tiap perulangannya, buat element dengan nama article
, sisipkan sebagai child contents
.
content := website.CreateElement("contents")
for _, each := range data.Content.Articles {
article := content.CreateElement("article")
article.CreateElement("title").SetText(each.Title)
article.CreateElement("url").SetText(each.URL)
for _, category := range each.Categories {
article.CreateElement("category").SetText(category)
}
if each.Info != "" {
article.CreateAttr("info", each.Info)
}
}
Khusus untuk objek artikel yang property .Info
-nya tidak kosong, buat atribut dengan nama info
pada element article
yang bersangkutan, simpan nilai property sebagai nilai atribut tersebut.
Terakhir simpan objek dokumen etree sebagai file.
doc.Indent(2)
err = doc.WriteToFile("output.xml")
if err != nil {
log.Println(err.Error())
}
Method .Indent()
di atas digunakan untuk menentukan indentasi element dalam file.
Jalankan aplikasi, lihat hasilnya.
- etree, by Brett Vickers, BSD-2-Clause license