Files
2017-06-10 11:58:20 +08:00

238 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 7.1 XML处理
XML作为一种数据交换和信息传递的格式已经十分普及。而随着Web服务日益广泛的应用现在XML在日常的开发工作中也扮演了愈发重要的角色。这一小节 我们将就Go语言标准包中的XML相关处理的包进行介绍。
这个小节不会涉及XML规范相关的内容如需了解相关知识请参考其他文献而是介绍如何用Go语言来编解码XML文件相关的知识。
假如你是一名运维人员你为你所管理的所有服务器生成了如下内容的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描述的信息进行操作。
## 解析XML
如何解析如上这个XML文件呢 我们可以通过xml包的`Unmarshal`函数来达到我们的目的
```Go
func Unmarshal(data []byte, v interface{}) error
```
data接收的是XML数据流v是需要输出的结构定义为interface也就是可以把XML转换为任意的格式。我们这里主要介绍struct的转换因为struct和XML都有类似树结构的特征。
示例代码如下:
```Go
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对象。如上例子输出如下数据
```xml
{{ 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`的定义:
```Go
func Unmarshal(data []byte, v interface{}) error
```
我们看到函数定义了两个参数第一个是XML数据流第二个是存储的对应类型目前支持struct、slice和stringXML包内部采用了反射来进行数据的映射所以v里面的字段必须是导出的。`Unmarshal`解析的时候XML元素和字段怎么对应起来的呢这是有一个优先级读取流程的首先会读取struct tag如果没有那么就会对应字段名。必须注意一点的是解析的时候tag、字段名、XML元素都是大小写敏感的所以必须一一对应字段。
Go语言的反射机制可以利用这些tag信息来将来自XML文件中的数据反射成对应的struct对象关于反射如何利用struct tag的更多内容请参阅reflect中的相关内容。
解析XML到struct的时候遵循如下的规则
- 如果struct的一个字段是string或者[]byte类型且它的tag含有`",innerxml"`Unmarshal将会将此字段所对应的元素内所有内嵌的原始xml累加到此字段上如上面例子Description定义。最后的输出是
```xml
<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定义中的所有字段必须是可导出的即首字母大写
## 输出XML
假若我们不是要解析如上所示的XML文件而是生成它那么在go语言中又该如何实现呢 xml包中提供了`Marshal``MarshalIndent`两个函数,来满足我们的需求。这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的定义如下所示:
```Go
func Marshal(v interface{}) ([]byte, error)
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
```
两个函数第一个参数是用来生成XML的结构定义类型数据都是返回生成的XML数据流。
下面我们来看一下如何输出如上的XML
```Go
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
<?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那么输出每一个元素类似<type>value</type>
- 如果v是指针那么会Marshal指针指向的内容如果指针为空什么都不输出
- 如果v是interface那么就处理interface所包含的数据
- 如果v是其他数据类型就会输出这个数据类型所拥有的字段信息
生成的XML文件中的element的名字又是根据什么决定的呢元素名按照如下优先级从struct中获取
- 如果v是structXMLName的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包含bb包含c例如如下代码就会输出
```xml
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定义请参看相应的官方资料。
## links
* [目录](<preface.md>)
* 上一节: [文本处理](<07.0.md>)
* 下一节: [Json处理](<07.2.md>)