英文:
Can I use Go's xml.Unmarshall for Ordered Polymorphic Types?
问题
我想使用Go语言解析和序列化XML,但似乎Marshall/Unmarshall
只适用于结构化数据,对于有序指令效果不太好。我想做类似这样的事情:
type Play struct {
loops uint16
// 元素的内容是文件名
}
type Say struct {
loops uint16
voice string
}
func (p *Play) Execute() (err error) {
// 播放文件
}
xml := `<Root>
<Say>Playing file</Say>
<Play loops="2">https://host/somefile.mp3</Play>
<Say>Done playing</Say>
</Root>`
我想将其转换为一个切片,以便可以在其中运行方法。
for _, instruction := range actions {
instruction.Execute()
}
如何使用Unmarshall
实现这一点?
编辑:也许我可以使用Decoder
循环遍历,并根据标签名进行每个元素的Unmarshall
操作?
英文:
I want to parse and serialize xml with Go, but it's looking like Marshall/Unmarshall only works well for structured data and not so much for ordered instructions. I'd like to do something like this:
type Play struct {
loops uint16
// Body of element is file name
}
type Say struct {
loops uint16
voice string
}
func (p *Play) Execute() (err error) {
// Play the file
}
xml := `<Root>
<Say>Playing file</Say>
<Play loops="2">https://host/somefile.mp3</Play>
<Say>Done playing</Say>
</Root>`
I want to take that and essentially end up with a slice of these that I can run methods on.
for _, instruction := range actions {
instruction.Execute()
}
How can I do that using Unmarshall
?
EDIT: Maybe I could use the Decoder
to loop through and Unmarshall each one based on the tag name?
答案1
得分: 3
与encoding/json
包不同,您没有Unmarshaller
接口。在您的情况下,您将不得不使用Decoder
,就像您自己建议的那样。
下面是一个可行的解决方案:
package main
import (
"bytes"
"encoding/xml"
"fmt"
)
// 任何指令都需要的接口
type Executer interface {
Execute() error
}
var factoryMap map[string]func() Executer = make(map[string]func() Executer)
type Play struct {
Loops int `xml:"loops,attr"`
File string `xml:",innerxml"`
// 元素的正文是文件名
}
func (p *Play) Execute() error {
for i := 0; i < p.Loops; i++ {
fmt.Println(`o/ ` + p.File)
}
return nil
}
type Say struct {
Voice string `xml:",innerxml"`
}
func (s *Say) Execute() error {
fmt.Println(s.Voice)
return nil
}
// 让我们注册不同的指令
// 您可以将每个指令结构放在单独的文件中,让每个文件都有一个init函数
func init() {
factoryMap["Play"] = func() Executer { return new(Play) }
factoryMap["Say"] = func() Executer { return new(Say) }
}
func Unmarshal(b []byte) ([]Executer, error) {
d := xml.NewDecoder(bytes.NewReader(b))
var actions []Executer
// 查找第一个根标签
for {
v, err := d.Token()
if err != nil {
return nil, err
}
if _, ok := v.(xml.StartElement); ok {
break
}
}
// 循环遍历其余的标记,找到每个标记的开始。
for {
v, err := d.Token()
if err != nil {
return nil, err
}
switch t := v.(type) {
case xml.StartElement:
// 我们找到了一个指令的开始。
// 让我们在我们的factoryMap中检查名称
// 您应该检查指令名称是否实际存在。现在它会引发panic。
f := factoryMap[t.Name.Local]
instr := f()
// 我们将标记的其余部分解码到指令结构中
err := d.DecodeElement(instr, &t)
if err != nil {
return nil, err
}
// 添加填充的操作
actions = append(actions, instr)
case xml.EndElement:
// 我们找到了根标记的结束标记。我们完成了!
return actions, nil
}
}
return nil, nil
}
func main() {
xml := []byte(`<Root>
<Say>Playing file</Say>
<Play loops="2">https://host/somefile.mp3</Play>
<Say>Done playing</Say>
</Root>`)
actions, err := Unmarshal(xml)
if err != nil {
panic(err)
}
for _, instruction := range actions {
err = instruction.Execute()
if err != nil {
fmt.Println(err)
}
}
}
输出:
Playing file
o/ https://host/somefile.mp3
o/ https://host/somefile.mp3
Done playing
当然,这段代码并不完整,但应该足以让您清楚地了解如何解决您的问题。
英文:
Unlike the encoding/json
package, you have no Unmarshaller
interface. In your case, you will have to use the Decoder
as you have suggested yourself.
Below is a working solution:
<!-- language: scala -->
package main
import (
"bytes"
"encoding/xml"
"fmt"
)
// An interface required by any instruction
type Executer interface {
Execute() error
}
var factoryMap map[string]func() Executer = make(map[string]func() Executer)
type Play struct {
Loops int `xml:"loops,attr"`
File string `xml:",innerxml"`
// Body of element is file name
}
func (p *Play) Execute() error {
for i := 0; i < p.Loops; i++ {
fmt.Println(`o/ ` + p.File)
}
return nil
}
type Say struct {
Voice string `xml:",innerxml"`
}
func (s *Say) Execute() error {
fmt.Println(s.Voice)
return nil
}
// Let's register the different instructions
// You can have each Instruction struct in separate files, letting each file having an init
func init() {
factoryMap["Play"] = func() Executer { return new(Play) }
factoryMap["Say"] = func() Executer { return new(Say) }
}
func Unmarshal(b []byte) ([]Executer, error) {
d := xml.NewDecoder(bytes.NewReader(b))
var actions []Executer
// Finding the first Root tag
for {
v, err := d.Token()
if err != nil {
return nil, err
}
if _, ok := v.(xml.StartElement); ok {
break
}
}
// Looping through the rest of the tokens
// finding the start of each.
for {
v, err := d.Token()
if err != nil {
return nil, err
}
switch t := v.(type) {
case xml.StartElement:
// We found a start of an instruction.
// Let's check the name in our factoryMap
// You should check that the Instruction name actually exists. Now it panics.
f := factoryMap[t.Name.Local]
instr := f()
// We decode the rest of the tag into the instruction struct
err := d.DecodeElement(instr, &t)
if err != nil {
return nil, err
}
// Appending the populated action
actions = append(actions, instr)
case xml.EndElement:
// We found the end tag of the Root. We are done!
return actions, nil
}
}
return nil, nil
}
func main() {
xml := []byte(`<Root>
<Say>Playing file</Say>
<Play loops="2">https://host/somefile.mp3</Play>
<Say>Done playing</Say>
</Root>`)
actions, err := Unmarshal(xml)
if err != nil {
panic(err)
}
for _, instruction := range actions {
err = instruction.Execute()
if err != nil {
fmt.Println(err)
}
}
}
Output:
Playing file
o/ https://host/somefile.mp3
o/ https://host/somefile.mp3
Done playing
Of course, this code is not complete, but it should be enough to give you a clear picture on how you can solve your problem.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论