Golang:解组自动关闭标签

So I'm trying to Unmarshal an XML file generated as a save file by another program in Google Go. It seems to be going great as the documentation on this is quite extensive: http://golang.org/pkg/encoding/xml/#Unmarshal

Still I'm running into a problem. The output in the save file is like this:

<location id="id4" x="-736" y="-544">
    <committed />
</location>

Instead of committed, a location can also be urgent or neither. The locations can also have a name and different labels, but these seem to be parsing just fine. In my Go code I'm using the following struct:

type Location struct {
    Id string `xml:"id,attr"`
    Committed bool `xml:"commited"`
    Urgent bool `xml:"urgent"`
    Labels []Label `xml:"label"`
}

And although the Unmarshal function of the encoding/xml package runs without errors and the shown example is present in the data, all the values of both committed and urgent are "false".

What should I change to get the right values for these two fields?

(Unmarshalling is done using the following piece of code)

xmlFile, err := os.Open("model.xml")
if err != nil {
    fmt.Println("Error opening file:", err)
    return
}
defer xmlFile.Close()

b, _ := ioutil.ReadAll(xmlFile)

var xmlScheme uppaal.UppaalXML
err = xml.Unmarshal(b, &xmlScheme)
fmt.Println(err)

According to this discussion this behaviour is not supported and the only reason you don't see an error is that you mispelled committed in the struct definition. If you write it correctly you will get a parse error because an empty string (the contents of a closed tag) is not a boolean value (example on play).

In the linked golang-nuts thread rsc suggests to use *struct{} (a pointer to an empty struct) and check if that value is nil (example on play):

type Location struct {
    Id        string    `xml:"id,attr"`
    Committed *struct{} `xml:"committed"`
    Urgent    bool      `xml:"urgent"`
}

if l.Committed != nil {
    // handle not committed
}

An easy way to solve this issue of empty self closing tags or empty tag is to use https://github.com/guregu/null package.

I am not a big fan of using packages for small stuff but this package have saved a lot of time for me

Here is how I am using this

package main

import (
    "encoding/xml"
    "fmt"

    "github.com/guregu/null"
)

func main() {
    type Result struct {
        XMLName xml.Name   `xml:"Person"`
        Name    string     `xml:"FullName"`
        Id      null.Int   `xml:"Id"`
        Height  null.Float `xml:"Height"`
    }
    v := Result{}

    data := `
        <Person>
            <FullName>Grace R. Emlin</FullName>
            <Id></Id>
            <Height />
        </Person>
    `
    err := xml.Unmarshal([]byte(data), &v)

    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }

    fmt.Printf("XMLName: %#v
", v.XMLName)
    fmt.Printf("Name: %q
", v.Name)

    if !v.Id.IsZero() {
        fmt.Printf("Id: %d
", v.Id.Int64)
    }
}

http://play.golang.org/p/xGKeIUM6NO

Full credit to guregu/null

For simple boolean values, i.e. value is true when an element is present, I have solved it this way:

Example XML:

<struct>
    <hide/>
    <data>Value</data>
</struct>

Data structure in Go:

type XMLValues struct {
    Hide BoolIfElementPresent `xml:"hide"`
    Data string               `xml:"data"`
}

type BoolIfElementPresent struct {
    bool
}

func (c *BoolIfElementPresent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var v string
    d.DecodeElement(&v, &start)
    *c = BoolIfElementPresent{true}
    return nil
}

This way, whenever <hide/> is present and unmarshalling is attempted, it just returns true. If <hide/> is not present, no unmarshalling is attempted and the default value false remains in the struct.

Note that you have to wrap your boolean values in the custom struct every time you work with it, though.

The d.DecodeElement(&v, &start) part seems unnecessary, but if you omit that piece of code you will receive an error message:

xml: (*mypackage.BoolIfElementPresent).UnmarshalXML did not consume entire element

Edit: Simplified version curtesy of @ShogunPanda:

type XMLValues struct {
    Hide BoolIfElementPresent `xml:"hide"`
    Data string               `xml:"data"`
}

type BoolIfElementPresent bool

func (c *BoolIfElementPresent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    var v string
    d.DecodeElement(&v, &start)
    *c = true
    return nil
}

Note that you have to compare the booleans by using if xyz == true.

My case is different: I create app in golang as a forwarder to submit from api-a to api-b. While api-a may generating a tag without closing and golang will not able to read it, which will not be read by api-b. Therefore I create two classes which one is reader and other is poster(sender).

maybe this will give some insight to others.

package main

import (
    "encoding/xml"
    "fmt"
)

type MyDataReader struct {
    Name string `xml:"name"`
    Lulus *struct{} `xml:"lulus"`
}

type MyDataPoster struct {
    Name string `xml:"name"`
    Lulus string `xml:"lulus"`
}

func ToPoster(m MyDataReader) MyDataPoster {
    res := MyDataPoster{}
    res.Name = m.Name
    if m.Lulus != nil {
        res.Lulus = "1"
    }
    return res
}

func main() {
    xmlString := `<data>
        <name>richard</name>
        <lulus />
    </data>`


    m := MyDataReader{}
    err := xml.Unmarshal([]byte(xmlString), &m)
    fmt.Println(err, m)

    mpost := ToPoster(m)
    output, errenc := xml.MarshalIndent(mpost, "", "    ")
    fmt.Println(errenc, string(output))
}

I create two class. One for retrieving and parsing and one for submit the xml.