棘手的 Go xml.Unmarshal() 案例

Sco*_*ter 5 go xml-parsing hl7-fhir

我正在尝试在 Go 中像这样解组 XML:

<property>
  <code value="abc"/>
  <valueBoolean value="true"/>
</property>
Run Code Online (Sandbox Code Playgroud)

或这个

<property>
  <code value="abc"/>
  <valueString value="apple"/>
</property>
Run Code Online (Sandbox Code Playgroud)

或这个

<property>
  <code value="abc"/>
  <valueDecimal value="3.14159"/>
</property>
Run Code Online (Sandbox Code Playgroud)

等等,进入这个:

type Property struct {
    Code  string      `xml:"code>value,attr"`
    Value interface{}
}
Run Code Online (Sandbox Code Playgroud)

其中标记(valueBooleanvalueString等) 告诉我 value 属性的类型是什么。我试图解析的 XML 是国际标准的一部分,所以我对其定义没有任何控制权。实现解析这些东西并不难,比如:

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)
...
}
Run Code Online (Sandbox Code Playgroud)

但我不知道如何告诉 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"`
}
Run Code Online (Sandbox Code Playgroud)

但这非常低效,特别是考虑到我将有大量这些东西的实例,这使我无法在不添加另一个属性来指示类型的情况下派生类型。

我可以以某种方式将它与普通的 XML 解组方法联系起来,只是手动处理棘手的部分,还是我需要从头开始为这种类型编写整个解组器?

Sco*_*ter 5

多亏了 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

a is true
b = apple
c = 3.14159
Run Code Online (Sandbox Code Playgroud)