likes
comments
collection
share

[golang] XML 处理

作者站长头像
站长
· 阅读数 14
<?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

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)
}

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"`
}
{{ 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 来完成的,这个过程是如何实现的? 第一个参数是 xml 数据流,第二个参数是存储的对应类型,目前支持 structslicestring。xml 包内部采用了反射来进行数据的映射,所以 v 里面的字段必须是可导出的(即首字母大写)。

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

解析 xml 到 struct 的时候遵循如下规则:

  • 如果 struct 的一个字段是 string 或者 []byte 类型,并且它的 tag 含有 xml:",innerxml",那么 xml.Unmarshal 将会将此字段对应层级元素的子级元素所有原始 xml 累加到此字段上,如上面例子中的 Description
  • 如果 struct 中有一个叫做 XMLName,且类型为 xml.Name 字段,那么在解析的时候就会保存这个 element 的名字到该字段,如上面例子中的 servers
  • 如果 struct 的字段 tag 中含有 ",attr",那么解析的时候就会将该结构同名属性的值赋给该字段,如上 version 定义;
  • 如果 struct 的字段 tag 中含有 "a>b>c",将会将此字段对应层级元素的子级元素 a 下面的 b 下面的 c 元素的值赋值给该字段;
  • 如果 struct 的字段 tag 中含有 "-",那么不会为该字段解析匹配任何 xml 数据;
  • 如果 struct 的字段 tag 中含有 ",any",将会将此字段对应层级不满足其他规则的 xml 数据匹配到这个字段;
  • 如果 struct 的字段 tag 中含有 ",comment",将会将此字段对应层级的注释累加到此字段上,这个字段的类型可以是 []bytestring

生成 XML

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

因为 xml.MarshalIndent 或者 xml.Marshal 生成的信息是不带 xml 头的,因此需要执行 os.Stdout.Write([]byte(xml.Header))

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)
}

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"`
}
<?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>

Marshal 接收的参数 vinterface{} 类型的,即它可以接受任意类型的参数。

  • 如果 varray 或者 slice,那么输出每一个元素,类似 value。
  • 如果 v 是指针,那么会输出指针指向的内容,如果指针为空,则什么都不输出。
  • 如果 vinterface,那么就处理 interface 所包含的数据。
  • 如果 v 是其他数据类型,就会输出这个数据类型所拥有的字段信息。 如何设置 struct 中字段的 tag 信息以控制最终 xml 文件的生成呢?(未指明部分可参考解析 xml 部分)
  • 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>