package main
import (
"bytes"
"encoding/xml"
"fmt"
"io"
)
var xmlstring = `
<Person>
<FullName>Grace R. Emlin</FullName>
<!-- this is the comment for Company -->
<Company>Example Inc.</Company>
<City>Hanga Roa</City>
<State>Easter Island</State>
</Person>
`
func main() {
decoder := xml.NewDecoder(bytes.NewReader([]byte(xmlstring)))
for {
t, err := decoder.Token()
if err != nil {
if err == io.EOF {
fmt.Printf("Parse XML finished!\n")
} else {
fmt.Printf("Failed to Parse XML with the error of %v\n", err)
}
break
}
t = xml.CopyToken(t)
switch t := t.(type) {
case xml.StartElement:
fmt.Printf("StartElement: <%v>\n", t.Name.Local)
case xml.EndElement:
fmt.Printf("EndElement: <%v>\n", t.Name.Local)
case xml.CharData:
fmt.Printf("CharData: %v\n", string(t))
case xml.Comment:
fmt.Printf("Comment: <!--%v-->\n", string(t))
}
}
}
输出是:
CharData:
StartElement: <Person>
CharData:
StartElement: <FullName>
CharData: Grace R. Emlin
EndElement: <FullName>
CharData:
Comment: <!-- this is the comment for Company -->
CharData:
StartElement: <Company>
CharData: Example Inc.
EndElement: <Company>
CharData:
StartElement: <City>
CharData: Hanga Roa
EndElement: <City>
CharData:
StartElement: <State>
CharData: Easter Island
EndElement: <State>
CharData:
EndElement: <Person>
CharData:
Parse XML finished!
根据输出可以看到,原 XML 中的换行符被 Go XML Stream API 解析为独立的xml.Token(实质是:xml.CharData),这是需要特别注意的地方。
总结
本文简单介绍了我要定制实现 XML Unmarshal 的原因,和 Go XML Stream API 的一些基础,后续在我完成 oVirt Go SDK 的 XML Unmarshal 重构后,会将更详细的实现细节补充上来。
前言
由于 oVirt 的 API 接口的数据格式是 XML,所以在实现 oVirt Go SDK 时,需要对接口响应 XML 数据进行解析。接口的XML 数据格式非常标准,所以非常适合用
xml:"***"
struct tag 来实现 Unmarshal 操作。Go 自带的 encoding/xml 库提供了非常便捷的 XML Marshal 和 Unmarshal 功能,只需在 struct 中定义 tag 即可。但后来在完善 SDK 的过程中,我发现了 struct tag 方案的一些局限性,于是决定自行实现 SDK 中的 XML-Unmarshal。
encoding/xml Unmarshal
的局限无法判断 XML 与 struct 对应
encoding/xml.Unmarshal
方法返回一个error
,但当 XML 数据与传入的 struct 不对应时,返回的error
仍然是nil
,以下面为例:Playground上面的例子中,Customers 和 Customer 中的 tag 定义与 XML 中的 element 定义完全不一样,但
xml.Unmarshal
函数不返回任何错误,custs 仍然是零值。这种情况导致,当 XML 数据可能是两种完全不同的格式(即对应两个完全不同的 struct)时,无法判断到底是哪个。我尝试了一个解决方案,但很丑陋,而且还不能保证完全正确,即:
struct 的属性必须 exported
由于
encoding/xml
使用reflect
反射来实现 Unmarshal,所以要求 struct 的属性都必须是 exported 的,比如:对于 XML 中的标签<name>
而言,对应的属性一般定义为Name
。在我实现 oVirt Go SDK 时,就出现了问题:name
作为属性名,访问和设值则使用 getter/setter 函数所以这就导致 unexported 的属性是无法使用
xml.Unmarshal
的;即便可以继续使用Name
作为属性名,但getter 函数只能用GetName
,但这样不符合 Go 的编码规范。关于 Go XML Stream API
对于 Go 中的 XML Stream API,官方和网上的资料很少,因为绝大多数的 XML Unmarshal 都是直接使用上面提到的 struct tag 方式,找不到可以直接参考的例子。
通过查看
encoding/xml
中的marshal.go
、read.go
和xml.go
三个源文件,找到了一些基础概念和简单操作。xml.Decoder
代表了一个XML的 Stream,xml.Decoder.Token()
函数即返回 XML Stream 中的下一个元素,返回值是xml.Token
xml.Token
代表了 XML 中每一项元素,会被解析成xml.StartElement
、xml.EndElement
、xml.CharData
、xml.Comment
、xml.ProcInst
、xml.Directive
中的一种xml.CharData
,即xml.Token
中的一种下面使用一段代码来描述 Go XML Stream API 的简单用法。playgroud
输出是:
总结
本文简单介绍了我要定制实现 XML Unmarshal 的原因,和 Go XML Stream API 的一些基础,后续在我完成 oVirt Go SDK 的 XML Unmarshal 重构后,会将更详细的实现细节补充上来。