与encoding/json
包不同,您没有Unmarshaller
接口。在您的情况下,您将不得不Decoder
按照您自己的建议使用。
以下是一个有效的解决方案:
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)
}
}
}
输出:
Playing file
o/ https://host/somefile.mp3
o/ https://host/somefile.mp3
Done playing
操场
当然,这段代码并不完整,但应该足以让您清楚地了解如何解决问题。