xml - 如何使用 golang encoding/xml 读取具有不同根目录的 xml 内容
问题描述
我当前的任务是使用 golang 读取进入系统的不同 xml 有效负载。xml 有效负载可能具有不同的根标记名称。不同的根标签名称意味着 xml 具有完全不同的含义。我一直在玩 encoding/xml 很长一段时间了,但是我发现没有办法通过一个 Unmarshal 调用来读取这些 xml 有效负载,而无需在之前进行操作或不进行预解析以提取根名称.
这是手头任务的简化示例,分别读取 2 个 xml:
package main
import (
"fmt"
"encoding/xml"
)
type Boxes struct {
Length []float64
}
type Bottles struct {
Diameter []float64
}
var Box_xml = []byte("<Boxes><Length>45</Length><Length>41</Length></Boxes>")
var Bottle_xml = []byte("<Bottles><Diameter>23</Diameter><Diameter>25</Diameter></Bottles>")
func main() {
box := Boxes{}
xml.Unmarshal(Box_xml, &box)
bottle := Bottles{}
xml.Unmarshal(Bottle_xml, &bottle)
fmt.Println(box, bottle)
}
此代码打印
{[45 41]} {[23 25]}
我目前在单个调用中处理任意传入消息的“最佳”解决方案是将根标记添加到这些 xml 并在中央结构中读取它们 - 如下所示:
package main
import (
"fmt"
"encoding/xml"
)
type Boxes struct {
Length []float64
}
type Bottles struct {
Diameter []float64
}
type Delivery struct {
Boxes Boxes
Bottles Bottles
}
var Box_xml = []byte("<Delivery><Boxes><Length>45</Length><Length>41</Length></Boxes></Delivery>")
var Bottle_xml = []byte("<Delivery><Bottles><Diameter>23</Diameter><Diameter>25</Diameter></Bottles></Delivery>")
func main() {
delivery := Delivery{}
xml.Unmarshal(Box_xml, &delivery)
fmt.Println(delivery )
delivery = Delivery{}
xml.Unmarshal(Bottle_xml, &delivery)
fmt.Println(delivery)
}
此代码打印
{{[45 41]} {[]}}
{{[]} {[23 25]}}
这将是构建结果以供以后处理的好方法。
如何在不向有效负载添加人工根标签的情况下做到这一点?
解决方案
您可以创建 xml 解码器并使用Token
方法来逐个节点解析 xml 输入。它不会解析所有文档。在这里查看更多。
令牌返回输入流中的下一个 XML 令牌。在输入流结束时,Token 返回 nil,io.EOF。
返回的令牌数据中的字节切片引用解析器的内部缓冲区,并且仅在下一次调用 Token 之前保持有效。要获取字节的副本,请调用 CopyToken 或令牌的 Copy 方法。
Token 将自闭合元素扩展为
连续调用返回的单独的开始和结束元素。Token 保证它返回的 StartElement 和 EndElement 标记正确嵌套和匹配:如果 Token 在所有预期的结束元素之前遇到意外的结束元素或 EOF,它将返回错误。
这只是一个示例,您如何确定您拥有什么类型的 xml 以及您需要解析成什么结构:
package main
import (
"encoding/xml"
"log"
"strings"
)
type Boxes struct {
Length []float64
}
type Bottles struct {
Diameter []float64
}
type DeliveryBoxes struct {
Boxes Boxes
}
type DeliveryBottles struct {
Bottles Bottles
}
const (
BoxXML = "<Delivery><Boxes><Length>45</Length><Length>41</Length></Boxes></Delivery>"
BottleXML = "<Delivery><Bottles><Diameter>23</Diameter><Diameter>25</Diameter></Bottles></Delivery>"
)
func main() {
var err error
var xmlReader = strings.NewReader(BoxXML)
var decoder = xml.NewDecoder(xmlReader)
// start with a first token 'Delivery'
t, err := decoder.Token()
if err != nil {
log.Fatalln(err)
}
// next token is a next node: 'Boxes' or 'Bottles'
t, err = decoder.Token()
if err != nil {
log.Fatalln(err)
}
startElement := t.(xml.StartElement)
// create new reader to start from the beginning
xmlReader = strings.NewReader(BoxXML)
switch startElement.Name.Local {
case "Boxes":
var delivery DeliveryBoxes
if err := xml.NewDecoder(xmlReader).Decode(&delivery); err != nil {
log.Fatalln(err)
}
log.Println(delivery)
case "Bottles":
var delivery DeliveryBottles
if err := xml.NewDecoder(xmlReader).Decode(&delivery); err != nil {
log.Fatalln(err)
}
log.Println(delivery)
}
}
通常,我们解析前两个节点以检查结构类型,然后使用定义的结构解析完整的 xml 输入。您可以将 switch 语句移动到单个函数并使用变量interface{}
类型。delivery
无论如何,这是一个示例,向您展示无需解析整个输入即可确定 xml 输入的数据格式。
推荐阅读
- c - 如何在没有 * 或 - 运算符的情况下否定 C 中的正整数?
- c++ - c++ std 库并行执行,for_each 比顺序循环慢
- envoyproxy - 上游连接错误或在标头 EnvoyProxy 之前断开/重置
- python-3.x - 为什么调用由 for 循环创建的短链 lambda 会导致 RecursionError,而如果手动创建它可以正常工作?
- date - 在 Jupyter Notebook 中使用 matplotlib.pyplot 自定义图表的日期范围
- gradle - 将生成的类和测试包含到 Sonar + Jacoco 测试覆盖率报告中
- python - 如何根据用户输入在 python 中打印 csv 文件的特定行?
- javascript - 循环遍历节点列表的有效方法?
- reactjs - Material UI Chip 数组如何像 Angular Chip Input 一样使用?
- java - 为什么我的程序没有打印出想要的结果?