处理XML文件
XML(eXtensible Markup Language,可扩展标记语言)是一种数据表示格式,可以描述非常复杂的数据结构,常用于传输和存储数据。
1、解析XML文件
Go语言提供了xml包用于解析和生成XML。xml包中提供一个名为Unmarshal()函数来解析XML,该函数的定义如下:
func Unmarshal(data []byte,v interface{]) error
其中,data接收的是XML数据流,v是需要输出的结构(如将其定义为interface,则可以把XML转换为任意的格式)。Go在解析XML中的数据时,最主要的是处理XML到结构体的转换问题,结构体和XML都有类似树结构的特征。Go解析XML到结构体会遵循以下原则:
- 如果结构体的一个字段是string或者[]byte类型,且它的tag含有“,innerxml’”,则Unmarshal()函数会将此字段所对应的元素内所有内嵌的原始xml累加到该结构体中对应的字段中。
- 如果在结构体中有一个被称为XMLName,且类型为xml.Name的字段,则在解析时会保存这个元素的名字到该结构体中对应的字段中。
- 如果在某个结构体字段的tag定义中含有XML结构中元素的名称,则解析时会把相应的元素值赋值给该结构体中对应的字段。
- 如果在某个结构体字段的tag定义中含有“,attr”,则解析时会将该结构所对应的元素与字段同名的属性的值赋值给该结构体中对应的字段。
- 如果某个结构体字段的tag定形了形如“c>d>e”的字符串,则解析时会将xml结构c下面的d下面的元素的值赋值给该结构体中对应的字段。
- 如果某个结构体字段的tag定义了“-”,则不会为该字段解析匹配任何xml数据。
- 如果结构体字段后面的tag定义了“,any”,且它的子元素不满足其他的规则,则匹配到这个字段。
- 如果某个XML元素包含一条或者多条注释,则这些注释将被累加到第1个tag含有“,comments”的字段中。这个字段的类型可能是[]byte或string。如果没有这样的字段,则注释会被抛弃。
- 新建一个名为default.xml的配置文件,其内容如下:
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<smtpServer>smtp.163.com</smtpServer>
<smtpPort>25</smtpPort>
<sender>test@163.com</sender>
<senderPassword>123456</senderPassword>
<receivers flag="true">
<user>tom</user>
<user>jerry</user>
</receivers>
</config>
以上代码是一个xml配置文件,该配置以config为root标签,包含xml属性文本(比如smtpServer标签)、嵌套xml(receivers标签)、xml attribute属性文本(receivers标签的flag),以及类似数组的多行配置(user标签)。数据类型有字符串和数字两种类型。
- 读取default.xml配置文件,并解析打印到命令行终端
import (
"encoding/xml"
"fmt"
"io"
"log"
"os"
)
type EmailConfig struct {
XMLName xml.Name `xml:"config"`
SmtpServer string `xml:"smtpServer"`
SmtpPort int `xml:"smtpPort"`
Sender string `xml:"sender"`
SenderPassword string `xml:"senderPassword"`
Receivers EmailReceivers `xml:"receivers"`
}
type EmailReceivers struct {
Flag string `xml:"flag,attr"`
User []string `xml:"user"`
}
func main() {
file, err := os.Open("default.xml")
if err != nil {
log.Panicln(err)
}
data, err := io.ReadAll(file)
if err != nil {
log.Panicln(err)
}
v := EmailConfig{}
err = xml.Unmarshal(data, &v)
if err != nil {
log.Panicln(err)
}
fmt.Println(v)
fmt.Println("Name is:", v.XMLName)
fmt.Println("SmtpServer is:", v.SmtpServer)
fmt.Println("Sender is:", v.Sender)
fmt.Println("SenderPasswd is:", v.SenderPassword)
fmt.Println("Reveivers.Flag is:", v.Receivers.Flag)
for i, ele := range v.Receivers.User {
fmt.Println(i, ele)
}
}
{{ config} smtp.163.com 25 test@163.com 123456 {true [tom jerry]}}
Name is: { config}
SmtpServer is: smtp.163.com
Sender is: test@163.com
SenderPasswd is: 123456
Reveivers.Flag is: true
0 tom
1 jerry
2、生成XML文件
需要用到xml包中的Marshal()和Marshallndent()这两个函数。这两个函数主要的区别是:
- Marshallndent()函数会增加前缀和缩进
- Marshal()则不会
这两个函数的定义如下:
func Marshal(v interface())([]byte,error)
func MarshalIndent (v interface(),prefix,indent string)([]byte,error)
两个函数的第1个参数都用来生成XML文件的结构定义数据,都是返回生成的XML文件。生成XML文件的示例如下。
import (
"encoding/xml"
"log"
"os"
)
type Languages struct {
XMLName xml.Name `xml:"languages"`
Version string `xml:"version,attr"`
Lang []Language `xml:"language"`
}
type Language struct {
Name string `xml:"name"`
Site string `xml:"site"`
}
func main() {
v := &Languages{Version: "2"}
v.Lang = append(v.Lang, Language{"JAVA", "https://www.java.com/"})
v.Lang = append(v.Lang, Language{"Go", "https://golang.org/"})
output, err := xml.MarshalIndent(v, " ", " ")
if err != nil {
log.Panicln(err)
}
file, _ := os.Create("languages.xml")
file.Write([]byte(xml.Header))
file.Write(output)
}
生成的languages.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<languages version="2">
<language>
<name>JAVA</name>
<site>https://www.java.com/</site>
</language>
<language>
<name>Go</name>
<site>https://golang.org/</site>
</language>
</languages>
下面再分析一下Go语言代码。xml.Marshallndent()函数和xml.Marshal()函数输出的信息都是不带XML头的。为了生成正确的XML文件,需要使用XML包预定义的Header变量,所以需要加上file.Write([byte(xml.Header)
这行代码。
Marshal()函数接收的参数v是interface}类型的,即它可以接受任意类型的参数。
xml包会根据如下规则来生成相应的XML文件:
- 如果v是array或者slice,则输出每一个元素,类似value。
- 如果v是指针,则会输出Marshal指针指向的内容。如果指针为空,则什么都不输出。
- 如果v是interface,则处理interface所包含的数据。
- 如果v是其他数据类型,则输出这个数据类型所拥有的字段信息。
元素名按照如下优先级从结构体中获取:
- 如果v是结构体,则XMLName的tag中定义的名称优先被获取。
- 类型为xml.Name、名为XMLName的字段的值被优先被获取。
- 通过结构体中字段的tag来获取。
- 通过结构体的字段名来获取。
- marshall的类型名称。
设置结构体中字段的tag信息,以控制最终XML文件的生成:
- XMLName不会被输出。
- tag中含有“-”的字段不会被输出。
- 如果tag中含有“name,attr”,则会以name作为属性名,以字段值作为值输出为这个XML元素的属性。
- 如果tag中含有“,att”,则会以这个结构体的字段名作为属性名输出为XML元素的属性,类似上一条,只是这个name默认是字段名了。
- 如果tag中含有“,chardata”,则输出为XML元素的character data,而非element.
- 如果tag中含有“,innerxml’”,则它会被原样输出,而不会进行常规的编码过程。
- 如果tag中含有“,comment’”,则它将被当作XML元素的注释来输出,而不会进行常规的编码过程。字段值中不能含有“–”字符串。
- 如果tag中含有“omitempty”,若该字段的值为空值,则该字段就不会被输出到XML中。其中空值包括false,0,nil指针,nil接口,任何长度为O的array、slice、map或
string.
- 如果tag中含有“c>d>e”,则会循环输出这3个元素,其中c包含d,d包含e。
例如如下代码:
Ip string `xml:"address>ip"`
Port string `xml:"address>port"`
//生成结果
<address>
<ip>127.0.0.1</ip>
<port>8080</port>
</address>