为了账号安全,请及时绑定邮箱和手机立即绑定

将平面 XML 解组为数据结构

将平面 XML 解组为数据结构

Go
慕无忌1623718 2022-11-28 17:07:55
我有一个平面 XML 结构,我试图将其解组为一个 go 数据结构。我试图找到一种方法从下面的 XML ie- bucket1 = [apple,orange,grapes], bucket2= [apple,mangoes] 中获取每个桶中的项目列表(项目名称)。当我尝试将 xml 解组为下面的 go 数据结构时,我能够获得桶名和项目的列表,但我无法将项目列表映射到它们各自的桶,因为每个桶可以有很多项目。有没有办法通过更改go数据结构来从这个xml中实现这个需求?我无法控制 XML 的结构,因此无法更改它以满足我的要求。我是新来的,我很感激这里的任何意见。type buckets struct {    XMLName    xml.Name `xml:"buckets"`    BucketName []string `xml:"bucket-name"`    ItemName   []string `xml:"item-name"`    Weight     []string `xml:"weight"`    Quantity   []string `xml:"quantity"`}                <?xml version="1.0" encoding="UTF-8"?>    <buckets>       <bucket-name>bucket1</bucket-name>       <item-name>apple</item-name>       <weight>500</weight>       <quantity>3</quantity>       <item-name>orange</item-name>       <weight>500</weight>       <quantity>2</quantity>       <item-name>grapes</item-name>       <weight>800</weight>       <quantity>1</quantity>       <bucket-name>bucket2</bucket-name>       <item-name>apple</item-name>       <weight>500</weight>       <quantity>3</quantity>       <item-name>mangoes</item-name>       <weight>400</weight>       <quantity>2</quantity>    </buckets>
查看完整描述

2 回答

?
有只小跳蛙

TA贡献1824条经验 获得超8个赞

您可以通过使用自定义xml.UnmarshalXML并手动将存储桶映射到 Go 结构来实现您正在尝试做的事情。


下面描述的代码假定 XML 元素的顺序与所提供的示例相同。


首先,我们有问题中描述的结构:


type Buckets struct {

    XMLName xml.Name `xml:"buckets"`

    Buckets []*Bucket

}


type Bucket struct {

    BucketName string `xml:"Bucket-name"`

    Items      []*Item

}


type Item struct {

    Name     string `xml:"item-name"`

    Weight   int    `xml:"weight"`

    Quantity int    `xml:"quantity"`

}

接下来我们需要通过实现结构的方法来实现Unmarshaler接口。当我们调用并将结构作为目标传递时,将调用此方法。UnmarshalXMLBucketsxml.UnmarhsalBuckets


func (b *Buckets) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {

    b.XMLName = start.Name


    var currentBucket *Bucket

    var currentItem *Item

    for {

        t, err := d.Token()

        if t == nil {

            // append the last bucket before exiting

            b.Buckets = append(b.Buckets, currentBucket)

            break

        }

        if err != nil {

            return err

        }

        switch se := t.(type) {

        case xml.StartElement:

            switch se.Name.Local {

            case "Bucket-name":

                // check if currentBucket is nil, it is necessary for the first time that

                // is going to run. Otherwise, append the last bucket to the slice and reset it

                if currentBucket != nil {

                    b.Buckets = append(b.Buckets, currentBucket)

                }

                currentBucket = &Bucket{}


                if err := d.DecodeElement(&currentBucket.BucketName, &se); err != nil {

                    return err

                }

            case "item-name":

                currentItem = &Item{}

                if err := d.DecodeElement(&currentItem.Name, &se); err != nil {

                    return err

                }

            case "weight":

                if err := d.DecodeElement(&currentItem.Weight, &se); err != nil {

                    return err

                }

            case "quantity":

                if err := d.DecodeElement(&currentItem.Quantity, &se); err != nil {

                    return err

                }


                // since quantity comes last append the item to the bucket,  and reset it

                currentBucket.Items = append(currentBucket.Items, currentItem)

                currentItem = &Item{}

            }

        }

    }


    return nil

}

我们实质上所做的是遍历 XML 元素并使用我们的自定义逻辑将它们映射到我们的结构。我不会详细介绍d.Token()and xml.StartElement,您可以随时阅读文档了解更多信息。

现在我们来分解一下上面的方法:

  • 当我们遇到一个名为 name 的元素时,Bucket-name我们知道后面有一个新的桶,所以将已经处理过的元素(我们必须检查,nil因为第一次不会有任何处理)添加到切片并设置currentBucket为一个新的桶(我们要处理的那个)。

  • 当我们遇到一个带有名称的元素时,item-name我们知道后面有一个新项目,因此设置currentItem为一个新项目。

  • 当我们遇到一个名为 name 的元素时,quantity我们知道这是属于 的最后一个元素currentItem,因此将它附加到currentBucket.Items

  • tfinally 变为 nil 时,它表示输入流结束,但由于每当遇到新桶时我们都会追加一个桶,最后一个桶(或者如果只有一个桶)将不会被追加。所以,在我们break需要附加最后一个处理的之前。

笔记:

  • 您可以完全避免Buckets结构并创建一个函数来处理解组,方法是使用xml.Decoder类似的方法:

func UnmarshalBuckets(rawXML []byte) []*Bucket {

    // or any io.Reader that points to the xml data

    d := xml.NewDecoder(bytes.NewReader(rawXML))

    ...

}

免责声明:

  • 我知道上面的代码感觉有点粗略,我相信您可以改进它。随意使用它并以更具可读性的方式实现自定义逻辑。

  • 应该有一些我没有涵盖或在提供的示例中不存在的边缘情况。您应该分析您的 XML 并尝试(如果可能)覆盖它们。

  • 如前所述,代码在很大程度上依赖于 XML 元素的顺序。

Go Playground的工作示例


查看完整回答
反对 回复 2022-11-28
?
蝴蝶刀刀

TA贡献1801条经验 获得超8个赞

我同意 mkopriva。Go 的注释针对用于相同结构的数据记录的 XML 进行了优化。将它们用于混合内容就像给牛套上鞍座。plug:我已经在 GitHub 上编写了处理混合内容的代码,欢迎提供反馈。



查看完整回答
反对 回复 2022-11-28
  • 2 回答
  • 0 关注
  • 116 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信