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 關注
- 156 瀏覽
添加回答
舉報