doucan957495 2013-09-29 14:08
浏览 30
已采纳

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

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条回答 默认 最新

  • douyigua5381 2013-10-02 13:41
    关注

    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:

    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
    

    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.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥60 求一个简单的网页(标签-安全|关键词-上传)
  • ¥35 lstm时间序列共享单车预测,loss值优化,参数优化算法
  • ¥15 基于卷积神经网络的声纹识别
  • ¥15 Python中的request,如何使用ssr节点,通过代理requests网页。本人在泰国,需要用大陆ip才能玩网页游戏,合法合规。
  • ¥100 为什么这个恒流源电路不能恒流?
  • ¥15 有偿求跨组件数据流路径图
  • ¥15 写一个方法checkPerson,入参实体类Person,出参布尔值
  • ¥15 我想咨询一下路面纹理三维点云数据处理的一些问题,上传的坐标文件里是怎么对无序点进行编号的,以及xy坐标在处理的时候是进行整体模型分片处理的吗
  • ¥15 一直显示正在等待HID—ISP
  • ¥15 Python turtle 画图