天天看點

go語言encoding/xml标準庫

作者:幹飯人小羽
go語言encoding/xml标準庫

導入方式:

import "encoding/xml"           

實作的簡單的了解XML命名空間的XML 1.0編譯器

funcUnmarshal —— 用于解析XML檔案

func Unmarshal(data []byte, v interface{}) error           

Unmarshal解析XML編碼的資料并将結果存入v指向的值。v隻能指向結構體、切片或者和字元串。良好格式化的資料如果不能存入v,會被丢棄。

因為Unmarshal使用reflect包,它隻能填寫導出字段。本函數好似用大小寫敏感的比較來比對XML元素名和結構體的字段名/标簽鍵名。

Unmarshal函數使用如下規則将XML元素映射到結構體字段上。這些規則中,字段标簽指的是結構體字段的标簽鍵'xml'對應的值(參見上面的例子):

go語言encoding/xml标準庫
* 如果結構體字段的類型為字元串或者[]byte,且标簽為",innerxml",
  Unmarshal函數直接将對應原始XML文本寫入該字段,其餘規則仍适用。
* 如果結構體字段類型為xml.Name且名為XMLName,Unmarshal會将元素名寫入該字段
* 如果字段XMLName的标簽的格式為"name"或"namespace-URL name",
  XML元素必須有給定的名字(以及可選的名字空間),否則Unmarshal會傳回錯誤。
* 如果XML元素的屬性的名字比對某個标簽",attr"為字段的字段名,或者比對某個标簽為"name,attr"
  的字段的标簽名,Unmarshal會将該屬性的值寫入該字段。
* 如果XML元素包含字元資料,該資料會存入結構體中第一個具有标簽",chardata"的字段中,
  該字段可以是字元串類型或者[]byte類型。如果沒有這樣的字段,字元資料會丢棄。
* 如果XML元素包含注釋,該資料會存入結構體中第一個具有标簽",comment"的字段中,
  該字段可以是字元串類型或者[]byte類型。如果沒有這樣的字段,字元資料會丢棄。
* 如果XML元素包含一個子元素,其名稱比對格式為"a"或"a>b>c"的标簽的字首,反序列化會深入
  XML結構中尋找具有指定名稱的元素,并将最後端的元素映射到該标簽所在的結構體字段。
  以">"開始的标簽等價于以字段名開始并緊跟着">" 的标簽。
* 如果XML元素包含一個子元素,其名稱比對某個結構體類型字段的XMLName字段的标簽名,
  且該結構體字段本身沒有顯式指定标簽名,Unmarshal會将該元素映射到該字段。
* 如果XML元素的包含一個子元素,其名稱比對夠格結構體字段的字段名,且該字段沒有任何模式選項
  (",attr"、",chardata"等),Unmarshal會将該元素映射到該字段。
* 如果XML元素包含的某個子元素不比對以上任一條,而存在某個字段其标簽為",any",
  Unmarshal會将該元素映射到該字段。
* 匿名字段被處理為其字段好像位于外層結構體中一樣。
* 标簽為"-"的結構體字段永不會被反序列化填寫。           
go語言encoding/xml标準庫

Unmarshal函數将XML元素寫入string或[]byte時,會将該元素的字元資料串聯起來作為值,目标[]byte不能是nil。

Unmarshal函數将屬性寫入string或[]byte時,會将屬性的值以字元串/切片形式寫入。

Unmarshal函數将XML元素寫入切片時,會将切片擴充并将XML元素的子元素映射入建立的值裡。

Unmarshal函數将XML元素/屬性寫入bool值時,會将對應的字元串轉化為布爾值。

Unmarshal函數将XML元素/屬性寫入整數或浮點數類型時,會将對應的字元串解釋為十進制數字。不會檢查溢出。

Unmarshal函數将XML元素寫入xml.Name類型時,會記錄元素的名稱。

Unmarshal函數将XML元素寫入指針時,會申請一個新值并将XML元素映射入該值。

舉例:

xml檔案為:

go語言encoding/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>           
go語言encoding/xml标準庫

舉例:

go語言encoding/xml标準庫
package main 
import(
    "fmt"
    "encoding/xml"
    "io/ioutil"
    "os"
    "log"
)
type Recurlyservers struct {//後面的内容是struct tag,标簽,是用來輔助反射的
    XMLName xml.Name `xml:"servers"` //将元素名寫入該字段
    Version string `xml:"version,attr"` //将version該屬性的值寫入該字段
    Svs []server `xml:"server"`
    Description string `xml:",innerxml"` //Unmarshal函數直接将對應原始XML文本寫入該字段
}

type server struct{
    XMLName xml.Name `xml:"server"`
    ServerName string `xml:"serverName"`
    ServerIP string `xml:"serverIP"`
}
func main() {
    file, err := os.Open("servers.xml")
    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()
    data, err := ioutil.ReadAll(file)
    if err != nil {
        log.Fatal(err)
    }

    v := Recurlyservers{}
    err = xml.Unmarshal(data, &v)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(v) 

    fmt.Printf("XMLName: %#v\n", v.XMLName)
    fmt.Printf("Version: %q\n", v.Version)

    fmt.Printf("Server: %v\n", v.Svs)
    for i, svs := range v.Svs{
        fmt.Println(i)
        fmt.Printf("Server XMLName: %#v\n", svs.XMLName)
        fmt.Printf("Server ServerName: %q\n", svs.ServerName)
        fmt.Printf("Server ServerIP: %q\n", svs.ServerIP)        
    }
    fmt.Printf("Description: %q\n", v.Description)

}           
go語言encoding/xml标準庫

傳回:

go語言encoding/xml标準庫
userdeMBP:go-learning user$ go run test.go
{{ 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>
}
XMLName: xml.Name{Space:"", Local:"servers"}
Version: "1"
Server: [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}]
0
Server XMLName: xml.Name{Space:"", Local:"server"}
Server ServerName: "Shanghai_VPN"
Server ServerIP: "127.0.0.1"
1
Server XMLName: xml.Name{Space:"", Local:"server"}
Server ServerName: "Beijing_VPN"
Server ServerIP: "127.0.0.2"
Description: "\n    <server>\n        <serverName>Shanghai_VPN</serverName>\n        <serverIP>127.0.0.1</serverIP>\n    </server>\n    <server>\n        <serverName>Beijing_VPN</serverName>\n        <serverIP>127.0.0.2</serverIP>\n    </server>\n"           
go語言encoding/xml标準庫

生成XML檔案使用下面的兩個函數:

funcMarshal

func Marshal(v interface{}) ([]byte, error)           

Marshal函數傳回v的XML編碼。

Marshal處理數組或者切片時會序列化每一個元素。Marshal處理指針時,會序列化其指向的值;如果指針為nil,則啥也不輸出。Marshal處理接口時,會序列化其内包含的具體類型值,如果接口值為nil,也是不輸出。Marshal處理其餘類型資料時,會輸出一或多個包含資料的XML元素。

XML元素的名字按如下優先順序擷取:

- 如果資料是結構體,其XMLName字段的标簽
- 類型為xml.Name的XMLName字段的值
- 資料是某結構體的字段,其标簽
- 資料是某結構體的字段,其字段名
- 被序列化的類型的名字           

一個結構體的XML元素包含該結構體所有導出字段序列化後的元素,有如下例外:

go語言encoding/xml标準庫
- XMLName字段,如上所述,會省略
- 具有标簽"-"的字段會省略
- 具有标簽"name,attr"的字段會成為該XML元素的名為name的屬性
- 具有标簽",attr"的字段會成為該XML元素的名為字段名的屬性
- 具有标簽",chardata"的字段會作為字元資料寫入,而非XML元素
- 具有标簽",innerxml"的字段會原樣寫入,而不會經過正常的序列化過程
- 具有标簽",comment"的字段作為XML注釋寫入,而不經過正常的序列化過程,該字段内不能有"--"字元串
- 标簽中包含"omitempty"選項的字段如果為空值會省略
  空值為false、0、nil指針、nil接口、長度為0的數組、切片、映射
- 匿名字段(其标簽無效)會被處理為其字段是外層結構體的字段           
go語言encoding/xml标準庫

如果一個字段的标簽為"a>b>c",則元素c将會嵌套進其上層元素a和b中。如果該字段相鄰的字段标簽指定了同樣的上層元素,則會放在同一個XML元素裡。

參見MarshalIndent的例子。如果要求Marshal序列化通道、函數或者映射會傳回錯誤。

funcMarshalIndent

func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)           

MarshalIndent功能類似Marshal。但每個XML元素會另起一行并縮進,該行以prefix起始,後跟一或多個indent的拷貝(根據嵌套層數)。它隻是多了縮進的設定,使得生成的XML檔案可讀性更高

兩個函數第一個參數是用來生成XML的結構定義類型資料,都是傳回生成的XML資料流

舉例生成上面解析的XML檔案:

go語言encoding/xml标準庫
package main 
import(
    "fmt"
    "encoding/xml"
    "os"
)
type Servers struct {//後面的内容是struct tag,标簽,是用來輔助反射的
    XMLName xml.Name `xml:"servers"` //将元素名寫入該字段
    Version string `xml:"version,attr"` //将version該屬性的值寫入該字段
    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"})
    //每個XML元素會另起一行并縮進,每行以prefix(這裡為兩個空格)起始,後跟一或多個indent(這裡為四個空格)的拷貝(根據嵌套層數)
    //即第一層嵌套隻遞進四個空格,第二層嵌套則遞進八個空格
    output, err := xml.MarshalIndent(v,"  ", "    ")
    if err != nil{
        fmt.Printf("error : %v\n", err)
    }
    os.Stdout.Write([]byte(xml.Header)) //輸出預定義的xml頭  <?xml version="1.0" encoding="UTF-8"?>
    os.Stdout.Write(output)
}           
go語言encoding/xml标準庫

傳回:

go語言encoding/xml标準庫
userdeMBP:go-learning user$ go run test.go
<?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>           
go語言encoding/xml标準庫

需要os.Stdout.Write([]byte(xml.Header))這句代碼是因為上面的兩個函數輸出的資訊都是不帶XML頭的,為了生成正确的xml檔案,需要使用xml包預定義的Header變量

Constants

const (
    // 适用于本包Marshal輸出的一般性XML header
    // 本常數并不會自動添加到本包的輸出裡,這裡提供主要是出于便利的目的
    Header = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
)           

另一個例子:

go語言encoding/xml标準庫
package main 
import(
    "fmt"
    "encoding/xml"
    "os"
)
type Address struct {
    City, State string
}
type Person struct {
    XMLName   xml.Name `xml:"person"`   //該XML檔案的根元素為person
    Id        int      `xml:"id,attr"` //該值會作為person元素的屬性
    FirstName string   `xml:"name>first"` //first為name的子元素
    LastName  string   `xml:"name>last"`  //last
    Age       int      `xml:"age"`
    Height    float32  `xml:"height,omitempty"` //含omitempty選項的字段如果為空值會省略
    Married   bool  //預設為false
    Address         //匿名字段(其标簽無效)會被處理為其字段是外層結構體的字段,是以沒有Address這個元素,而是直接顯示City, State這兩個元素
    Comment string `xml:",comment"` //注釋
}

func main() {
    v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42}
    v.Comment = " Need more details. "
    v.Address = Address{"Hanga Roa", "Easter Island"}
    output, err := xml.MarshalIndent(v, "  ", "    ")
    if err != nil {
        fmt.Printf("error: %v\n", err)
    }
    os.Stdout.Write(output)
}           
go語言encoding/xml标準庫

傳回:

go語言encoding/xml标準庫
userdeMBP:go-learning user$ go run test.go
  <person id="13">
      <name>
          <first>John</first>
          <last>Doe</last>
      </name>
      <age>42</age>
      <Married>false</Married>
      <City>Hanga Roa</City>
      <State>Easter Island</State>
      <!-- Need more details. -->
  </person>           
go語言encoding/xml标準庫

如果是用的是xml.Marshal(v),傳回為:

userdeMBP:go-learning user$ go run test.go
<person id="13"><name><first>John</first><last>Doe</last></name><age>42</age><Married>false</Married><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>           

可讀性就會變得差很多

繼續閱讀