XML作為一種數(shù)據(jù)交換和信息傳遞的格式已經(jīng)十分普及。而隨著Web服務(wù)日益廣泛的應(yīng)用,現(xiàn)在XML在日常的開(kāi)發(fā)工作中也扮演了愈發(fā)重要的角色。這一小節(jié), 我們將就Go語(yǔ)言標(biāo)準(zhǔn)包中的XML相關(guān)處理的包進(jìn)行介紹。
這個(gè)小節(jié)不會(huì)涉及XML規(guī)范相關(guān)的內(nèi)容(如需了解相關(guān)知識(shí)請(qǐng)參考其他文獻(xiàn)),而是介紹如何用Go語(yǔ)言來(lái)編解碼XML文件相關(guān)的知識(shí)。
假如你是一名運(yùn)維人員,你為你所管理的所有服務(wù)器生成了如下內(nèi)容的xml的配置文件:
<?xml version="1.0" encoding="utf-8"?>
<servers version="1">
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
</server>
<server>
<serverName>Beijing_VPN</serverName>
<serverIP>127.0.0.2</serverIP>
</server>
</servers>
上面的XML文檔描述了兩個(gè)服務(wù)器的信息,包含了服務(wù)器名和服務(wù)器的IP信息,接下來(lái)的Go例子以此XML描述的信息進(jìn)行操作。
如何解析如上這個(gè)XML文件呢? 我們可以通過(guò)xml包的Unmarshal
函數(shù)來(lái)達(dá)到我們的目的
func Unmarshal(data []byte, v interface{}) error
data接收的是XML數(shù)據(jù)流,v是需要輸出的結(jié)構(gòu),定義為interface,也就是可以把XML轉(zhuǎn)換為任意的格式。我們這里主要介紹struct的轉(zhuǎn)換,因?yàn)閟truct和XML都有類似樹(shù)結(jié)構(gòu)的特征。
示例代碼如下:
package main
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
)
type Recurlyservers struct {
XMLName xml.Name `xml:"servers"`
Version string `xml:"version,attr"`
Svs []server `xml:"server"`
Description string `xml:",innerxml"`
}
type server struct {
XMLName xml.Name `xml:"server"`
ServerName string `xml:"serverName"`
ServerIP string `xml:"serverIP"`
}
func main() {
file, err := os.Open("servers.xml") // For read access.
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
}
v := Recurlyservers{}
err = xml.Unmarshal(data, &v)
if err != nil {
fmt.Printf("error: %v", err)
return
}
fmt.Println(v)
}
XML本質(zhì)上是一種樹(shù)形的數(shù)據(jù)格式,而我們可以定義與之匹配的go 語(yǔ)言的 struct類型,然后通過(guò)xml.Unmarshal來(lái)將xml中的數(shù)據(jù)解析成對(duì)應(yīng)的struct對(duì)象。如上例子輸出如下數(shù)據(jù)
{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}]
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
</server>
<server>
<serverName>Beijing_VPN</serverName>
<serverIP>127.0.0.2</serverIP>
</server>
}
上面的例子中,將xml文件解析成對(duì)應(yīng)的struct對(duì)象是通過(guò)xml.Unmarshal
來(lái)完成的,這個(gè)過(guò)程是如何實(shí)現(xiàn)的?可以看到我們的struct定義后面多了一些類似于xml:"serverName"
這樣的內(nèi)容,這個(gè)是struct的一個(gè)特性,它們被稱為 struct tag,它們是用來(lái)輔助反射的。我們來(lái)看一下Unmarshal
的定義:
func Unmarshal(data []byte, v interface{}) error
我們看到函數(shù)定義了兩個(gè)參數(shù),第一個(gè)是XML數(shù)據(jù)流,第二個(gè)是存儲(chǔ)的對(duì)應(yīng)類型,目前支持struct、slice和string,XML包內(nèi)部采用了反射來(lái)進(jìn)行數(shù)據(jù)的映射,所以v里面的字段必須是導(dǎo)出的。Unmarshal
解析的時(shí)候XML元素和字段怎么對(duì)應(yīng)起來(lái)的呢?這是有一個(gè)優(yōu)先級(jí)讀取流程的,首先會(huì)讀取struct tag,如果沒(méi)有,那么就會(huì)對(duì)應(yīng)字段名。必須注意一點(diǎn)的是解析的時(shí)候tag、字段名、XML元素都是大小寫(xiě)敏感的,所以必須一一對(duì)應(yīng)字段。
Go語(yǔ)言的反射機(jī)制,可以利用這些tag信息來(lái)將來(lái)自XML文件中的數(shù)據(jù)反射成對(duì)應(yīng)的struct對(duì)象,關(guān)于反射如何利用struct tag的更多內(nèi)容請(qǐng)參閱reflect中的相關(guān)內(nèi)容。
解析XML到struct的時(shí)候遵循如下的規(guī)則:
如果struct的一個(gè)字段是string或者[]byte類型且它的tag含有",innerxml"
,Unmarshal將會(huì)將此字段所對(duì)應(yīng)的元素內(nèi)所有內(nèi)嵌的原始xml累加到此字段上,如上面例子Description定義。最后的輸出是
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
</server>
<server>
<serverName>Beijing_VPN</serverName>
<serverIP>127.0.0.2</serverIP>
</server>
如果struct中有一個(gè)叫做XMLName,且類型為xml.Name字段,那么在解析的時(shí)候就會(huì)保存這個(gè)element的名字到該字段,如上面例子中的servers。
",attr"
,那么解析的時(shí)候就會(huì)將該結(jié)構(gòu)所對(duì)應(yīng)的element的與字段同名的屬性的值賦值給該字段,如上version定義。"a>b>c"
,則解析的時(shí)候,會(huì)將xml結(jié)構(gòu)a下面的b下面的c元素的值賦值給該字段。"-"
,那么不會(huì)為該字段解析匹配任何xml數(shù)據(jù)。",any"
,如果他的子元素在不滿足其他的規(guī)則的時(shí)候就會(huì)匹配到這個(gè)字段。上面詳細(xì)講述了如何定義struct的tag。 只要設(shè)置對(duì)了tag,那么XML解析就如上面示例般簡(jiǎn)單,tag和XML的element是一一對(duì)應(yīng)的關(guān)系,如上所示,我們還可以通過(guò)slice來(lái)表示多個(gè)同級(jí)元素。
注意: 為了正確解析,go語(yǔ)言的xml包要求struct定義中的所有字段必須是可導(dǎo)出的(即首字母大寫(xiě))
假若我們不是要解析如上所示的XML文件,而是生成它,那么在go語(yǔ)言中又該如何實(shí)現(xiàn)呢? xml包中提供了Marshal
和MarshalIndent
兩個(gè)函數(shù),來(lái)滿足我們的需求。這兩個(gè)函數(shù)主要的區(qū)別是第二個(gè)函數(shù)會(huì)增加前綴和縮進(jìn),函數(shù)的定義如下所示:
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
兩個(gè)函數(shù)第一個(gè)參數(shù)是用來(lái)生成XML的結(jié)構(gòu)定義類型數(shù)據(jù),都是返回生成的XML數(shù)據(jù)流。
下面我們來(lái)看一下如何輸出如上的XML:
package main
import (
"encoding/xml"
"fmt"
"os"
)
type Servers struct {
XMLName xml.Name `xml:"servers"`
Version string `xml:"version,attr"`
Svs []server `xml:"server"`
}
type server struct {
ServerName string `xml:"serverName"`
ServerIP string `xml:"serverIP"`
}
func main() {
v := &Servers{Version: "1"}
v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"})
v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"})
output, err := xml.MarshalIndent(v, " ", " ")
if err != nil {
fmt.Printf("error: %v\n", err)
}
os.Stdout.Write([]byte(xml.Header))
os.Stdout.Write(output)
}
上面的代碼輸出如下信息:
<?xml version="1.0" encoding="UTF-8"?>
<servers version="1">
<server>
<serverName>Shanghai_VPN</serverName>
<serverIP>127.0.0.1</serverIP>
</server>
<server>
<serverName>Beijing_VPN</serverName>
<serverIP>127.0.0.2</serverIP>
</server>
</servers>
和我們之前定義的文件的格式一模一樣,之所以會(huì)有os.Stdout.Write([]byte(xml.Header))
這句代碼的出現(xiàn),是因?yàn)?code>xml.MarshalIndent或者xml.Marshal
輸出的信息都是不帶XML頭的,為了生成正確的xml文件,我們使用了xml包預(yù)定義的Header變量。
我們看到Marshal
函數(shù)接收的參數(shù)v是interface{}類型的,即它可以接受任意類型的參數(shù),那么xml包,根據(jù)什么規(guī)則來(lái)生成相應(yīng)的XML文件呢?
生成的XML文件中的element的名字又是根據(jù)什么決定的呢?元素名按照如下優(yōu)先級(jí)從struct中獲?。?/p>
我們應(yīng)如何設(shè)置struct 中字段的tag信息以控制最終xml文件的生成呢?
"-"
的字段不會(huì)輸出"name,attr"
,會(huì)以name作為屬性名,字段值作為值輸出為這個(gè)XML元素的屬性,如上version字段所描述",attr"
,會(huì)以這個(gè)struct的字段名作為屬性名輸出為XML元素的屬性,類似上一條,只是這個(gè)name默認(rèn)是字段名了。",chardata"
,輸出為xml的 character data而非element。",innerxml"
,將會(huì)被原樣輸出,而不會(huì)進(jìn)行常規(guī)的編碼過(guò)程",comment"
,將被當(dāng)作xml注釋來(lái)輸出,而不會(huì)進(jìn)行常規(guī)的編碼過(guò)程,字段值中不能含有"--"字符串"omitempty"
,如果該字段的值為空值那么該字段就不會(huì)被輸出到XML,空值包括:false、0、nil指針或nil接口,任何長(zhǎng)度為0的array, slice, map或者stringtag中含有"a>b>c"
,那么就會(huì)循環(huán)輸出三個(gè)元素a包含b,b包含c,例如如下代碼就會(huì)輸出
FirstName string `xml:"name>first"`
LastName string `xml:"name>last"`
<name>
<first>Asta</first>
<last>Xie</last>
</name>
上面我們介紹了如何使用Go語(yǔ)言的xml包來(lái)編/解碼XML文件,重要的一點(diǎn)是對(duì)XML的所有操作都是通過(guò)struct tag來(lái)實(shí)現(xiàn)的,所以學(xué)會(huì)對(duì)struct tag的運(yùn)用變得非常重要,在文章中我們簡(jiǎn)要的列舉了如何定義tag。更多內(nèi)容或tag定義請(qǐng)參看相應(yīng)的官方資料。
更多建議: