Go 的XML处理

作者: 小新

发布于 2020-01-14 | 最后更新 2020-01-14


前言

前往 https://studygolang.com/pkgdoc 了解golang语言中xml包的内容。 文中的内容主要来自于该网站。

XML生成

理论

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

可以使用Marshal函数和MarshalIndent函数返回XML编码。MarshalIndent函数功能类似于Marshal函数。区别在于有无缩进。

Marshal处理数组或者切片时会序列化每一个元素。Marshal处理指针时,会序列化其指向的值;如果指针为nil,则啥也不输出。Marshal处理接口时,会序列化其内包含的具体类型值,如果接口值为nil,也是不输出。Marshal处理其余类型数据时,会输出一或多个包含数据的XML元素。

将结构体转换为XML输出时,需要注意以下规则:

  • XMLName字段,如上所述,会省略
  • 具有标签”-“的字段会省略
  • 具有标签”name,attr”的字段会成为该XML元素的名为name的属性
  • 具有标签”,attr”的字段会成为该XML元素的名为字段名的属性
  • 具有标签”,chardata”的字段会作为字符数据写入,而非XML元素
  • 具有标签”,innerxml”的字段会原样写入,而不会经过正常的序列化过程
  • 具有标签”,comment”的字段作为XML注释写入,而不经过正常的序列化过程,该字段内不能有”–“字符串
  • 标签中包含”omitempty”选项的字段如果为空值会省略 空值为false、0、nil指针、nil接口、长度为0的数组、切片、映射
  • 匿名字段(其标签无效)会被处理为其字段是外层结构体的字段
  • 如果一个字段的标签为”a>b>c”,则元素c将会嵌套进其上层元素a和b中。如果该字段相邻的字段标签指定了同样的上层元素,则会放在同一个XML元素里。

实践

/*
`xml:"id,attr"`:Id字段会成为该XML元素的名为id的属性
`xml:"name>first"`与`xml:"name>last"`:元素first与last会嵌套进上层元素name中
`xml:"height,omitempty"`:含"omitempty"选项的字段如果为空值会省略
`xml:"-"`:含"-"的字段会省略
Age:该字段没有设置标签,可注意观察输出Age标签名为该字段名
`xml:",comment"`:",comment"的字段作为XML注释写入
 */
func Test_XMLMarshal(t *testing.T){
	type Address struct {
		City, State string
	}
	type Person struct {
		XMLName   xml.Name `xml:"person"`
		Id        int      `xml:"id,attr"`
		FirstName string   `xml:"name>first"`
		LastName  string   `xml:"name>last"`
		Age       int
		Height    float32  `xml:"height,omitempty"`
		Married   bool `xml:"-"`
		Address
		Comment string `xml:",comment"`
		Description string `xml:",innerxml"`
	}
	v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42, Married:true}
	v.Comment = " Need more details. "
	v.Address = Address{"Hanga Roa", "Easter Island"}
	//使用MarshalIndent函数,生成的XML格式有缩进
	//output, err := xml.MarshalIndent(v, "  ", "    ")
	//使用Marshal函数,生成的XML格式无缩进
	output,err:=xml.Marshal(v)
	if err != nil {
		fmt.Printf("error: %v\n", err)
	}
	os.Stdout.Write(output)
}

输出效果:

使用Marshal函数(无缩进):

<person id="13"><name><first>John</first><last>Doe</last></name><Age>42</Age><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>

使用MarshalIndent函数(有缩进):

  <person id="13">
      <name>
          <first>John</first>
          <last>Doe</last>
      </name>
      <Age>42</Age>
      <City>Hanga Roa</City>
      <State>Easter Island</State>
      <!-- Need more details. -->
  </person>

XML解析

理论

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

Unmarshal解析XML编码的数据并将结果存入v指向的值。v只能指向结构体、切片或者和字符串。

解析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会将该元素映射到该字段。
  • 匿名字段被处理为其字段好像位于外层结构体中一样。
  • 标签为”-“的结构体字段永不会被反序列化填写。

实践

/*
XMLName的标签为`xml:"Person"`,则给的XML编码中XML元素必须有给定的名Person,否则会报错error: expected element type <person> but have <Person>
Name的标签为`xml:"FullName"`,则给的XML编码中解析XML元素为FullName的值为字段Name对应的值
Age的标签为`xml:"-"`,则不会被反序列填写,可以观察出Age的值为0
Phone无标签,则解析时将XML元素为Phone的值为该字段对应的值
Groups的标签为`xml:"Group>Value"`,则解析时将XML元素为Group的子元素Value的值为该字段对应的值
Email结构的Where字段的标签为`xml:"where,attr"`,则解析时将XML元素为Email的属性Where的值赋给该字段
Description标签为",innerxml",通过打印Description的值,可以看出直接将对应原始XML文本写入该字段
补充说明:
%#v-值的Go语法表示
%v-值的默认格式表示。当输出结构体时,扩展标志(%+v)会添加字段名
%q-该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
 */
func Test_XMLUnMarshal(t *testing.T){
	type Email struct {
		Where string `xml:"where,attr"`
		Addr  string
	}
	type Address struct {
		City, State string
	}
	type Result struct {
		XMLName xml.Name `xml:"Person"`
		Name    string   `xml:"FullName"`
		Age     int       `xml:"-"`
		Phone   string
		Email   []Email
		Groups  []string `xml:"Group>Value"`
		Address
		Description string `xml:",innerxml"`
	}
	v := Result{Name: "none", Phone: "none"}
	data := `
		<Person>
			<FullName>Grace R. Emlin</FullName>
			<Company>Example Inc.</Company>
           <Age>20</Age>
			<Email where="home">
				<Addr>gre@example.com</Addr>
			</Email>
			<Email where='work'>
				<Addr>gre@work.com</Addr>
			</Email>
			<Group>
				<Value>Friends</Value>
				<Value>Squash</Value>
			</Group>
			<City>Hanga Roa</City>
			<State>Easter Island</State>
		</Person>
	`
	err := xml.Unmarshal([]byte(data), &v)
	if err != nil {
		fmt.Printf("error: %v", err)
		return
	}
	fmt.Printf("XMLName: %#v\n", v.XMLName)
	fmt.Printf("Name: %q\n", v.Name)
	fmt.Printf("Age: %v\n", v.Age)
	fmt.Printf("Phone: %q\n", v.Phone)
	fmt.Printf("Email: %v\n", v.Email)
	fmt.Printf("Email[0].Where: %v\n", v.Email[0].Where)
	fmt.Printf("Groups: %v\n", v.Groups)
	fmt.Printf("Address: %v\n", v.Address)
	fmt.Printf("Description: %v\n", v.Description)
}

输出结果:

XMLName: xml.Name{Space:"", Local:"Person"}
Name: "Grace R. Emlin"
Age: 0
Phone: "none"
Email: [{home gre@example.com} {work gre@work.com}]
Email[0].Where: home
Groups: [Friends Squash]
Address: {Hanga Roa Easter Island}
Description: 
			<FullName>Grace R. Emlin</FullName>
			<Company>Example Inc.</Company>
            <Age>20</Age>
			<Email where="home">
				<Addr>gre@example.com</Addr>
			</Email>
			<Email where='work'>
				<Addr>gre@work.com</Addr>
			</Email>
			<Group>
				<Value>Friends</Value>
				<Value>Squash</Value>
			</Group>
			<City>Hanga Roa</City>
			<State>Easter Island</State>