混合 XML 以 golang 保留顺序解码
mixed XML decoding in golang preserving order
我需要从 XML 中提取报价,但考虑到节点顺序:
<items>
<offer/>
<product>
<offer/>
<offer/>
</product>
<offer/>
<offer/>
</items>
以下结构将对值进行解码,但会分成两个不同的切片,这将导致原始顺序丢失:
type Offers struct {
Offers []offer `xml:"items>offer"`
Products []offer `xml:"items>product>offer"`
}
有什么想法吗?
一种方法是覆盖UnmarshalXML
方法。假设我们的输入如下所示:
<doc>
<head>My Title</head>
<p>A first paragraph.</p>
<p>A second one.</p>
</doc>
我们要反序列化文档并保留标题和段落的顺序。为了订购,我们需要一片。为了同时容纳 head
和 p
,我们需要一个接口。我们可以这样定义我们的文档:
type Document struct {
XMLName xml.Name `xml:"doc"`
Contents []Mixed `xml:",any"`
}
,any
注释会将 任何 元素收集到 Contents
中。是Mixed
类型,我们需要定义为类型:
type Mixed struct {
Type string // just keep "head" or "p" in here
Value interface{} // keep the value, we could use string here, too
}
我们需要对反序列化过程进行更多控制,因此我们通过实现 UnmarshalXML
将 Mixed
变成 xml.Unmashaler
。我们根据起始元素的名称来决定代码路径,例如head
或 p
。在这里,我们只用一些值填充我们的 Mixed
结构,但你基本上可以在这里做任何事情:
func (m *Mixed) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
switch start.Name.Local {
case "head", "p":
var e string
if err := d.DecodeElement(&e, &start); err != nil {
return err
}
m.Value = e
m.Type = start.Name.Local
default:
return fmt.Errorf("unknown element: %s", start)
}
return nil
}
将它们放在一起,上述结构的用法可能如下所示:
func main() {
s := `
<doc>
<head>My Title</head>
<p>A first paragraph.</p>
<p>A second one.</p>
</doc>
`
var doc Document
if err := xml.Unmarshal([]byte(s), &doc); err != nil {
log.Fatal(err)
}
fmt.Printf("#%v", doc)
}
哪个会打印。
#{{ doc} [{head My Title} {p A first paragraph.} {p A second one.}]}
我们保留了顺序并保留了一些类型信息。您可以使用许多不同的类型来反序列化,而不是像 Mixed
这样的单一类型。这种方法的代价是您的容器 - 这里是文档的 Contents
字段 - 是一个接口。要执行任何操作 element-specific,您将需要一个类型断言或一些辅助方法。
我需要从 XML 中提取报价,但考虑到节点顺序:
<items> <offer/> <product> <offer/> <offer/> </product> <offer/> <offer/> </items>
以下结构将对值进行解码,但会分成两个不同的切片,这将导致原始顺序丢失:
type Offers struct { Offers []offer `xml:"items>offer"` Products []offer `xml:"items>product>offer"` }
有什么想法吗?
一种方法是覆盖UnmarshalXML
方法。假设我们的输入如下所示:
<doc>
<head>My Title</head>
<p>A first paragraph.</p>
<p>A second one.</p>
</doc>
我们要反序列化文档并保留标题和段落的顺序。为了订购,我们需要一片。为了同时容纳 head
和 p
,我们需要一个接口。我们可以这样定义我们的文档:
type Document struct {
XMLName xml.Name `xml:"doc"`
Contents []Mixed `xml:",any"`
}
,any
注释会将 任何 元素收集到 Contents
中。是Mixed
类型,我们需要定义为类型:
type Mixed struct {
Type string // just keep "head" or "p" in here
Value interface{} // keep the value, we could use string here, too
}
我们需要对反序列化过程进行更多控制,因此我们通过实现 UnmarshalXML
将 Mixed
变成 xml.Unmashaler
。我们根据起始元素的名称来决定代码路径,例如head
或 p
。在这里,我们只用一些值填充我们的 Mixed
结构,但你基本上可以在这里做任何事情:
func (m *Mixed) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
switch start.Name.Local {
case "head", "p":
var e string
if err := d.DecodeElement(&e, &start); err != nil {
return err
}
m.Value = e
m.Type = start.Name.Local
default:
return fmt.Errorf("unknown element: %s", start)
}
return nil
}
将它们放在一起,上述结构的用法可能如下所示:
func main() {
s := `
<doc>
<head>My Title</head>
<p>A first paragraph.</p>
<p>A second one.</p>
</doc>
`
var doc Document
if err := xml.Unmarshal([]byte(s), &doc); err != nil {
log.Fatal(err)
}
fmt.Printf("#%v", doc)
}
哪个会打印。
#{{ doc} [{head My Title} {p A first paragraph.} {p A second one.}]}
我们保留了顺序并保留了一些类型信息。您可以使用许多不同的类型来反序列化,而不是像 Mixed
这样的单一类型。这种方法的代价是您的容器 - 这里是文档的 Contents
字段 - 是一个接口。要执行任何操作 element-specific,您将需要一个类型断言或一些辅助方法。