一尘不染

我可以将Go的xml.Unmarshall用于有序多态类型吗?

go

我想用Go解析和序列化xml,但看起来Marshall / Unmarshall仅适用于结构化数据,而不适用于有序指令。我想做这样的事情:

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>`

我想接受这一点,并从本质上讲是可以在上面运行方法的一部分。

for _, instruction := range actions {
    instruction.Execute()
}

我该如何使用Unmarshall呢?

编辑:也许我可以Decoder根据标签名称使用循环遍历和解组?


阅读 199

收藏
2020-07-02

共1个答案

一尘不染

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

操场

当然,此代码并不完整,但是足以让您清楚地了解如何解决问题。

2020-07-02