Tricky Go xml.Unmarshal()案例

时间:2016-04-29 13:38:33

标签: go xml-parsing hl7-fhir

我试图在Go中解组这样的XML:

<property>
  <code value="abc"/>
  <valueBoolean value="true"/>
</property>

或者

<property>
  <code value="abc"/>
  <valueString value="apple"/>
</property>

或者

<property>
  <code value="abc"/>
  <valueDecimal value="3.14159"/>
</property>
等等,进入这个:

type Property struct {
    Code  string      `xml:"code>value,attr"`
    Value interface{}
}

其中标记(valueBooleanvalueString等)告诉我value属性的类型是什么。我尝试解析的XML是an international standard的一部分,因此我无法控制其定义。解析这些东西并不困难,例如:

var value string
for a := range se.Attr {
    if a.Name.Local == "value" {
        value = a.Value
    } else {
        // Invalid attribute
    }
}
switch se.Name.Local {
case "code":
case "valueBoolean":
    property.Value = value == "true"
case "valueString":
    property.Value = value
case "valueInteger":
    property.Value, err = strconv.ParseInteger(value)
case "valueDecimal":
    property.Value, err = strconv.ParseFloat(value)
...
}

但是我无法弄清楚如何告诉XML包找到它,而这些东西都隐藏在我真正宁愿使用xml.Unmarshal处理的其他XML中。或者,我可以将类型重新定义为:

type Property struct {
    Code         string `xml:"code>value,attr"`
    ValueBoolean bool   `xml:"valueBoolean>value,attr"`
    ValueString  string `xml:"valueString>value,attr"`
    ValueInteger int    `xml:"valueInteger>value,attr"`
    ValueDecimal float  `xml:"valueDecimal>value,attr"`
}

但效率非常低,特别是考虑到我会有大量这些事情的实例,这使我无法在不添加其他属性来指示类型的情况下派生类型。

我能否以某种方式将其与普通的XML解组方法联系起来,只是手工处理棘手的部分,或者我是否需要从头开始为这种类型编写整个unmarshaller?

1 个答案:

答案 0 :(得分:4)

感谢OneOfOne的指针,这里有一个适用于标准XML解组器的实现:

package main

import (
    "encoding/xml"
    "fmt"
    "strconv"
    "strings"
)

type Property struct {
    Code  string `xml:"code"`
    Value interface{}
}

const xmldata = `<properties>
  <property>
<code value="a"/>
<valueBoolean value="true"/>
  </property>
  <property>
<code value="b"/>
<valueString value="apple"/>
  </property>
  <property>
<code value="c"/>
<valueDecimal value="3.14159"/>
  </property>
</properties>
`

func (p *Property) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    if start.Name.Local != "property" {
        return fmt.Errorf("Invalid start tag for Property")
    }

    for {
        tok, err := d.Token()

        if tok == nil {
            break
        }

        if err != nil {
            return err
        }

        switch se := tok.(type) {
        case xml.StartElement:
            var value string
            var valueAssigned bool

            for _, attr := range se.Attr {
                if attr.Name.Local == "value" {
                    value = attr.Value
                    valueAssigned = true
                } else {
                    return fmt.Errorf("Invalid attribute %s", attr.Name.Local)
                }
            }

            if !valueAssigned {
                return fmt.Errorf("Valid attribute missing")
            }

            switch se.Name.Local {
            case "code":
                p.Code = value
            case "valueBoolean":
                if value == "true" {
                    p.Value = true
                } else if value == "false" {
                    p.Value = false
                } else {
                    return fmt.Errorf("Invalid string %s for Boolean value", value)
                }
            case "valueString", "valueCode", "valueUri":
                p.Value = value
            case "valueInteger":
                if ival, err := strconv.ParseInt(value, 10, 32); err != nil {
                    return err
                } else {
                    p.Value = ival
                }
            case "valueDecimal":
                if dval, err := strconv.ParseFloat(value, 64); err != nil {
                    return err
                } else {
                    p.Value = dval
                }
            default:
                return fmt.Errorf("Invalid tag %s for property", se.Name.Local)
            }
        }
    }

    return nil
}

func main() {
    r := strings.NewReader(xmldata)

    type Properties struct {
        List []Property `xml:"property"`
    }

    var properties Properties

    d := xml.NewDecoder(r)

    if err := d.Decode(&properties); err != nil {
        fmt.Println(err.Error())
    }

    for _, p := range properties.List {
        switch p.Value.(type) {
        case bool:
            if p.Value.(bool) {
                fmt.Println(p.Code, "is true")
            } else {
                fmt.Println(p.Code, "is false")
            }
        default:
            fmt.Println(p.Code, "=", p.Value)
        }
    }
}

输出是:

a is true
b = apple
c = 3.14159