我可以使用Go的xml.Unmarshall来处理有序的多态类型吗?

huangapple go评论82阅读模式
英文:

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 := `&lt;Root&gt;
	&lt;Say&gt;Playing file&lt;/Say&gt;
	&lt;Play loops=&quot;2&quot;&gt;https://host/somefile.mp3&lt;/Play&gt;
	&lt;Say&gt;Done playing&lt;/Say&gt;
&lt;/Root&gt;`

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

Playground

当然,这段代码并不完整,但应该足以让您清楚地了解如何解决您的问题。

英文:

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 (
&quot;bytes&quot;
&quot;encoding/xml&quot;
&quot;fmt&quot;
)
// 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:&quot;loops,attr&quot;`
File  string `xml:&quot;,innerxml&quot;`
// Body of element is file name
}
func (p *Play) Execute() error {
for i := 0; i &lt; p.Loops; i++ {
fmt.Println(`o/ ` + p.File)
}
return nil
}
type Say struct {
Voice string `xml:&quot;,innerxml&quot;`
}
func (s *Say) Execute() error {
fmt.Println(s.Voice)
return nil
}
// Let&#39;s register the different instructions
// You can have each Instruction struct in separate files, letting each file having an init
func init() {
factoryMap[&quot;Play&quot;] = func() Executer { return new(Play) }
factoryMap[&quot;Say&quot;] = 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&#39;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, &amp;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(`&lt;Root&gt;
&lt;Say&gt;Playing file&lt;/Say&gt;
&lt;Play loops=&quot;2&quot;&gt;https://host/somefile.mp3&lt;/Play&gt;
&lt;Say&gt;Done playing&lt;/Say&gt;
&lt;/Root&gt;`)
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

Playground

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.

huangapple
  • 本文由 发表于 2013年9月29日 22:08:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/19078977.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定