XML作為一種數據交換和信息傳遞的格式已經十分普及。而隨著Web服務日益廣泛的應用,現在XML在日常的開發工作中也扮演了愈發重要的角色。這一小節, 我們將就Go語言標準包中的XML相關處理的包進行介紹。
這個小節不會涉及XML規范相關的內容(如需了解相關知識請參考其他文獻),而是介紹如何用Go語言來編解碼XML文件相關的知識。
假如你是一名運維人員,你為你所管理的所有服務器生成了如下內容的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文檔描述了兩個服務器的信息,包含了服務器名和服務器的IP信息,接下來的Go例子以此XML描述的信息進行操作。
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/07.1.md#解析xml)解析XML
如何解析如上這個XML文件呢? 我們可以通過xml包的`Unmarshal`函數來達到我們的目的
~~~
func Unmarshal(data []byte, v interface{}) error
~~~
data接收的是XML數據流,v是需要輸出的結構,定義為interface,也就是可以把XML轉換為任意的格式。我們這里主要介紹struct的轉換,因為struct和XML都有類似樹結構的特征。
示例代碼如下:
~~~
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本質上是一種樹形的數據格式,而我們可以定義與之匹配的go 語言的 struct類型,然后通過xml.Unmarshal來將xml中的數據解析成對應的struct對象。如上例子輸出如下數據
~~~
{{ 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文件解析成對應的struct對象是通過`xml.Unmarshal`來完成的,這個過程是如何實現的?可以看到我們的struct定義后面多了一些類似于`xml:"serverName"`這樣的內容,這個是struct的一個特性,它們被稱為 struct tag,它們是用來輔助反射的。我們來看一下`Unmarshal`的定義:
~~~
func Unmarshal(data []byte, v interface{}) error
~~~
我們看到函數定義了兩個參數,第一個是XML數據流,第二個是存儲的對應類型,目前支持struct、slice和string,XML包內部采用了反射來進行數據的映射,所以v里面的字段必須是導出的。`Unmarshal`解析的時候XML元素和字段怎么對應起來的呢?這是有一個優先級讀取流程的,首先會讀取struct tag,如果沒有,那么就會對應字段名。必須注意一點的是解析的時候tag、字段名、XML元素都是大小寫敏感的,所以必須一一對應字段。
Go語言的反射機制,可以利用這些tag信息來將來自XML文件中的數據反射成對應的struct對象,關于反射如何利用struct tag的更多內容請參閱reflect中的相關內容。
解析XML到struct的時候遵循如下的規則:
* 如果struct的一個字段是string或者[]byte類型且它的tag含有`",innerxml"`,Unmarshal將會將此字段所對應的元素內所有內嵌的原始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中有一個叫做XMLName,且類型為xml.Name字段,那么在解析的時候就會保存這個element的名字到該字段,如上面例子中的servers。
* 如果某個struct字段的tag定義中含有XML結構中element的名稱,那么解析的時候就會把相應的element值賦值給該字段,如上servername和serverip定義。
* 如果某個struct字段的tag定義了中含有`",attr"`,那么解析的時候就會將該結構所對應的element的與字段同名的屬性的值賦值給該字段,如上version定義。
* 如果某個struct字段的tag定義 型如`"a>b>c"`,則解析的時候,會將xml結構a下面的b下面的c元素的值賦值給該字段。
* 如果某個struct字段的tag定義了`"-"`,那么不會為該字段解析匹配任何xml數據。
* 如果struct字段后面的tag定義了`",any"`,如果他的子元素在不滿足其他的規則的時候就會匹配到這個字段。
* 如果某個XML元素包含一條或者多條注釋,那么這些注釋將被累加到第一個tag含有",comments"的字段上,這個字段的類型可能是[]byte或string,如果沒有這樣的字段存在,那么注釋將會被拋棄。
上面詳細講述了如何定義struct的tag。 只要設置對了tag,那么XML解析就如上面示例般簡單,tag和XML的element是一一對應的關系,如上所示,我們還可以通過slice來表示多個同級元素。
> 注意: 為了正確解析,go語言的xml包要求struct定義中的所有字段必須是可導出的(即首字母大寫)
## [](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/07.1.md#輸出xml)輸出XML
假若我們不是要解析如上所示的XML文件,而是生成它,那么在go語言中又該如何實現呢? xml包中提供了`Marshal`和`MarshalIndent`兩個函數,來滿足我們的需求。這兩個函數主要的區別是第二個函數會增加前綴和縮進,函數的定義如下所示:
~~~
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
~~~
兩個函數第一個參數是用來生成XML的結構定義類型數據,都是返回生成的XML數據流。
下面我們來看一下如何輸出如上的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>
~~~
和我們之前定義的文件的格式一模一樣,之所以會有`os.Stdout.Write([]byte(xml.Header))`?這句代碼的出現,是因為`xml.MarshalIndent`或者`xml.Marshal`輸出的信息都是不帶XML頭的,為了生成正確的xml文件,我們使用了xml包預定義的Header變量。
我們看到`Marshal`函數接收的參數v是interface{}類型的,即它可以接受任意類型的參數,那么xml包,根據什么規則來生成相應的XML文件呢?
* 如果v是 array或者slice,那么輸出每一個元素,類似value
* 如果v是指針,那么會Marshal指針指向的內容,如果指針為空,什么都不輸出
* 如果v是interface,那么就處理interface所包含的數據
* 如果v是其他數據類型,就會輸出這個數據類型所擁有的字段信息
生成的XML文件中的element的名字又是根據什么決定的呢?元素名按照如下優先級從struct中獲取:
* 如果v是struct,XMLName的tag中定義的名稱
* 類型為xml.Name的名叫XMLName的字段的值
* 通過struct中字段的tag來獲取
* 通過struct的字段名用來獲取
* marshall的類型名稱
我們應如何設置struct 中字段的tag信息以控制最終xml文件的生成呢?
* XMLName不會被輸出
* tag中含有`"-"`的字段不會輸出
* tag中含有`"name,attr"`,會以name作為屬性名,字段值作為值輸出為這個XML元素的屬性,如上version字段所描述
* tag中含有`",attr"`,會以這個struct的字段名作為屬性名輸出為XML元素的屬性,類似上一條,只是這個name默認是字段名了。
* tag中含有`",chardata"`,輸出為xml的 character data而非element。
* tag中含有`",innerxml"`,將會被原樣輸出,而不會進行常規的編碼過程
* tag中含有`",comment"`,將被當作xml注釋來輸出,而不會進行常規的編碼過程,字段值中不能含有"--"字符串
* tag中含有`"omitempty"`,如果該字段的值為空值那么該字段就不會被輸出到XML,空值包括:false、0、nil指針或nil接口,任何長度為0的array, slice, map或者string
* tag中含有`"a>b>c"`,那么就會循環輸出三個元素a包含b,b包含c,例如如下代碼就會輸出
~~~
FirstName string `xml:"name>first"`
LastName string `xml:"name>last"`
<name>
<first>Asta</first>
<last>Xie</last>
</name>
~~~
上面我們介紹了如何使用Go語言的xml包來編/解碼XML文件,重要的一點是對XML的所有操作都是通過struct tag來實現的,所以學會對struct tag的運用變得非常重要,在文章中我們簡要的列舉了如何定義tag。更多內容或tag定義請參看相應的官方資料。
- 第一章 Go環境配置
- 1.1 Go安裝
- 1.2 GOPATH 與工作空間
- 1.3 Go 命令
- 1.4 Go開發工具
- 1.5 小結
- 第二章 Go語言基礎
- 2.1 你好,Go
- 2.2 Go基礎
- 2.3 流程和函數
- 2.4 struct類型
- 2.5 面向對象
- 2.6 interface
- 2.7 并發
- 2.8 總結
- 第三章 Web基礎
- 3.1 Web工作方式
- 3.2 Go搭建一個Web服務器
- 3.3 Go如何使得Web工作
- 3.4 Go的http包詳解
- 3.5 小結
- 第四章 表單
- 4.1 處理表單的輸入
- 4.2 驗證表單的輸入
- 4.3 預防跨站腳本
- 4.4 防止多次遞交表單
- 4.5 處理文件上傳
- 4.6 小結
- 第五章 訪問數據庫
- 5.1 database/sql接口
- 5.2 使用MySQL數據庫
- 5.3 使用SQLite數據庫
- 5.4 使用PostgreSQL數據庫
- 5.5 使用beedb庫進行ORM開發
- 5.6 NOSQL數據庫操作
- 5.7 小結
- 第六章 session和數據存儲
- 6.1 session和cookie
- 6.2 Go如何使用session
- 6.3 session存儲
- 6.4 預防session劫持
- 6.5 小結
- 第七章 文本處理
- 7.1 XML處理
- 7.2 JSON處理
- 7.3 正則處理
- 7.4 模板處理
- 7.5 文件操作
- 7.6 字符串處理
- 7.7 小結
- 第八章 Web服務
- 8.1 Socket編程
- 8.2 WebSocket
- 8.3 REST
- 8.4 RPC
- 8.5 小結
- 第九章 安全與加密
- 9.1 預防CSRF攻擊
- 9.2 確保輸入過濾
- 9.3 避免XSS攻擊
- 9.4 避免SQL注入
- 9.5 存儲密碼
- 9.6 加密和解密數據
- 9.7 小結
- 第十章 國際化和本地化
- 10.1 設置默認地區
- 10.2 本地化資源
- 10.3 國際化站點
- 10.4 小結
- 第十一章 錯誤處理,調試和測試
- 11.1 錯誤處理
- 11.2 使用GDB調試
- 11.3 Go怎么寫測試用例
- 11.4 小結
- 第十二章 部署與維護
- 12.1 應用日志
- 12.2 網站錯誤處理
- 12.3 應用部署
- 12.4 備份和恢復
- 12.5 小結
- 第十三章 如何設計一個Web框架
- 13.1 項目規劃
- 13.2 自定義路由器設計
- 13.3 controller設計
- 13.4 日志和配置設計
- 13.5 實現博客的增刪改
- 13.6 小結
- 第十四章 擴展Web框架
- 14.1 靜態文件支持
- 14.2 Session支持
- 14.3 表單及驗證支持
- 14.4 用戶認證
- 14.5 多語言支持
- 14.6 pprof支持
- 14.7 小結
- 附錄A 參考資料