首页 > 解决方案 > 如何使用 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]}}

这将是构建结果以供以后处理的好方法。

如何在不向有效负载添加人工根标签的情况下做到这一点?

标签: xmlgo

解决方案


您可以创建 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 输入的数据格式。


推荐阅读