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(¤tBucket.BucketName, &se); err != nil {
return err
}
case "item-name":
currentItem = &Item{}
if err := d.DecodeElement(¤tItem.Name, &se); err != nil {
return err
}
case "weight":
if err := d.DecodeElement(¤tItem.Weight, &se); err != nil {
return err
}
case "quantity":
if err := d.DecodeElement(¤tItem.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
当
t
finally 变为 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的工作示例
TA贡献1801条经验 获得超8个赞
我同意 mkopriva。Go 的注释针对用于相同结构的数据记录的 XML 进行了优化。将它们用于混合内容就像给牛套上鞍座。plug:我已经在 GitHub 上编写了处理混合内容的代码,欢迎提供反馈。
- 2 回答
- 0 关注
- 116 浏览
添加回答
举报