32.Go XML

标准库中的encoding/xml包提供了XML格式的序列化功能.

将XML解析为struct

解析XML与解析JSON类似。可以从[]byte, slice或io.Reader获取XML数据。

var xmlStr = `
<people>
    <person age="34">
        <first-name>John</first-name>
        <address>
            <city>San Francisco</city>
            <state>CA</state>
        </address>
    </person>

    <person age="23">
        <first-name>Julia</first-name>
    </person>
</people>`

type People struct {
    Person []Person `xml:"person"`
}

type Person struct {
    Age       int     `xml:"age,attr"`
    FirstName string  `xml:"first-name"`
    Address   Address `xml:"address"`
}

type Address struct {
    City  *string `xml:"city"`
    State string  `xml:"state"`
}

var people People
data := []byte(xmlStr)
err := xml.Unmarshal(data, &people)
if err != nil {
    log.Fatalf("xml.Unmarshal failed with '%s'\n", err)
}
fmt.Printf("%#v\n\n", people)

main.People{Person:[]main.Person{main.Person{Age:34, FirstName:"John", Address:main.Address{City:(string)(0xc000010430), State:"CA"}}, main.Person{Age:23, FirstName:"Julia", Address:main.Address{City:(string)(nil), State:""}}}}

与序列化不同,当解组为结构时,必须传递指向结构的指针。否则,xml.Unmarshal将接收并修改该结构的副本,而不是结构本身。从xml.Unmarshal返回后,该副本将被丢弃。

在XML中,值可以表示为元素(<state> CA </ state>)或属性(<person age =“ 34”>)。

在xml中,struct tags元素是默认值。要切换到属性,请像为Age一样,将,attr添加到struct xml标记中。

所有结构字段都是可选的。当不以XML文本形式出现时,其值将保持不变。当解码为新初始化的struct时,对于给定类型,其值为零。

Field City显示XML解码器可以自动将其解码为指向值的指针。

当您需要知道XML中是否存在值时,这很有用。如果我们在“城市”字段中使用字符串,则不知道空字符串是否意味着:

XML中不存在city元素
元素存在但值为空(<city> </ city>)
通过使用指向字符串的指针,我们知道nil表示没有值。

将struct序列化为XML

type People struct {
    XMLName        xml.Name `xml:"people"`
    Person         []Person `xml:"person"`
    noteSerialized int
}

type Person struct {
    Age       int     `xml:"age,attr"`
    FirstName string  `xml:"first-name"`
    Address   Address `xml:"address"`
}

type Address struct {
    City  string `xml:"city"`
    State string `xml:"state"`
}

people := People{
    Person: []Person{
        Person{
            Age:       34,
            FirstName: "John",
            Address:   Address{City: "San Francisco", State: "CA"},
        },
    },
    noteSerialized: 8,
}
d, err := xml.Marshal(&people)
if err != nil {
    log.Fatalf("xml.Marshal failed with '%s'\n", err)
}
fmt.Printf("Compact XML: %s\n\n", string(d))

d, err = xml.MarshalIndent(&people, "", "  ")
if err != nil {
    log.Fatalf("xml.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Pretty printed XML:\n%s\n", string(d))

Compact XML: <people><person age="34"><first-name>John</first-name><address><city>San Francisco</city><state>CA</state></address></person></people>

Pretty printed XML:
<people>
<person age="34">
<first-name>John</first-name>
<address>
<city>San Francisco</city>
<state>CA</state>
</address>
</person>
</people>

xml.Marshal和xml.MarshalIndent都将interface {}作为第一个参数。我们可以传递任何Go值,并将其类型包装到interface {}中。

Marshaller将使用反射来检查传递的值并将其编码为XML字符串。

在序列化结构时,仅对导出的字段(其名称以大写字母开头)进行序列化/反序列化。

在我们的示例中,noteSerialized未序列化。

结构被序列化为XML元素。

简单类型可以序列化为XML元素或属性(Person结构中的Age字段)

默认情况下,元素/属性的名称与字段的名称相同。

我们可以提供带有struct标签的自定义映射。

xml:“ city”告诉XML编码器将名称city用于字段City。

序列化结构时,将值或指针传递给xml.Marshal会生成相同的结果。

传递指针效率更高,因为按值传递会创建不必要的副本。

xml.MarshallIndent允许漂亮地打印嵌套结构。结果占用更多空间,但更易于阅读。

XMLName允许控制顶级元素的名称。在我们的示例中,如果不提供XML元素,则将根据结构名称对数据进行序列化:

<People>
    ....
</People>

Pull (streaming) XML parsing

Parsing into a struct is convenient but requires a lot of memory to hold the whole decoded document in memory.

In some cases XML files are so large that it’s not possible to decode the whole file into memory. For example XML dumps of Wikipedia content are several gigabytes in size.

Pull parsing is more efficient but API is harder to use.

// :g
Pull parsing requests next token from stream of XML tokens.

For start tag like <person> we get xml.StartElement token.

For end tag like </person> we get xml.EndElemnt token.

For data inside the element <person>data</person> we get xml.CharData token.

When decoder reaches the end, it returns error io.EOF.

In the above example we print age attribute of <person> element and char data inside <city> element.

This is a very basic example. In real programs you might need to remember more state.

For example, if your XML is:

<foo>
  <bar>
    <foo></foo>
  </bar>
</foo>

If you look just at the xml.StartElement token, you don’t know if foo is for the top-level element or is it a child of <bar> element.

Hybrid XML parsing: combine pull and unmarshal

Pull parsing is efficient but tedious.

We can combine pull parsing with decoding into a struct.

var xmlStr = `
<people>
    <person age="34">
        <first-name>John</first-name>
        <address>
            <city>San Francisco</city>
            <state>CA</state>
        </address>
    </person>
    <person age="23">
        <address>
            <city>Austin</city>
            <state>TX</state>
        </address>
    </person>
</people>`

type Address struct {
    City  string `xml:"city"`
    State string `xml:"state"`
}

r := bytes.NewBufferString(xmlStr)
decoder := xml.NewDecoder(r)
for {
    t, err := decoder.Token()
    if err == io.EOF {
        // io.EOF is a successful end
        break
    }
    if err != nil {
        fmt.Printf("decoder.Token() failed with '%s'\n", err)
        break
    }

    switch v := t.(type) {

    case xml.StartElement:
        if v.Name.Local == "address" {
            var address Address
            err = decoder.DecodeElement(&address, &v)
            if err != nil {
                fmt.Printf("decoder.DecodeElement() failed with '%s'\n", err)
                break
            }
            fmt.Printf("%+#v\n", address)
        }
    }
}

main.Address{City:"San Francisco", State:"CA"}
main.Address{City:"Austin", State:"TX"}

从文件解析XML

func decodeFromReader(r io.Reader) (*People, error) {
    var people People
    decoder := xml.NewDecoder(r)
    err := decoder.Decode(&people)
    if err != nil {
        return nil, err
    }
    return &people, nil
}

func decodeFromString(s string) (*People, error) {
    r := bytes.NewBufferString(s)
    return decodeFromReader(r)
}

func decodeFromFile(path string) (*People, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    return decodeFromReader(f)
}

&main.People{Person:[]main.Person{main.Person{Age:34, FirstName:"John", Address:main.Address{City:(string)(0xc000010430), State:"CA"}}, main.Person> {Age:23, FirstName:"Julia", Address:main.Address{City:(string)(nil), State:""}}}}

By writing a helper function decodeFromReader, we can easily write wrappers that will work on files, strings or network connections.

Configuring XML parsing and serialization

You can modify how a struct is serialized to XML by annotating fields with tags.

By providing XMLName, this will be serialized as <data>...</data> and not <ShowXMLName>..</ShowXMLName>.

type ShowXMLName struct {
    XMLName xml.Name `xml:"data"`
    N       int      `xml:"n"`
}

v := &ShowXMLName{
    N: 5,
}
printXML(v)

XML: <data><n>5</n></data>

Fields marked with xml:"-" are not serialized.

type ShowOmit struct {
    Name          string `xml:"name"`
    NotSerialized string `xml:"-"`
}

v := &ShowOmit{
    Name:          "John",
    NotSerialized: "Connor",
}
printXML(v)

XML: <ShowOmit><name>John</name></ShowOmit>

To skip serializing fields with empty values, use xml:",omitempty".

type ShowOmitEmpty struct {
    NonEmpty string `xml:",omitempty"`
    Empty    string `xml:",omitempty"`
}

v := &ShowOmitEmpty{
    NonEmpty: "non empty",
    Empty:    "",
}
printXML(v)

XML: <ShowOmitEmpty><NonEmpty>non empty</NonEmpty></ShowOmitEmpty>

To change serialization from XML element to XML attribute use xml:",attr".

type ShowAttr struct {
    B bool `xml:"n,attr"`
}

v := &ShowAttr{
    B: true,
}
printXML(v)

XML: <ShowAttr n="true"></ShowAttr>

To change serialization from XML element to character data use xml:",chardata".

type ShowCharData struct {
    S string `xml:",chardata"`
}

v := &ShowCharData{
    S: "str",
}
printXML(v)

XML: <ShowCharData>str</ShowCharData>

To change serialization from XML element to CDATA use xml:",cdata".

type ShowCData struct {
    S string `xml:",cdata"`
}

v := &ShowCData{
    S: "cdata",
}
printXML(v)

XML: <ShowCData><![CDATA[cdata]]></ShowCData>

To serialize the value verbatim, use xml:",innerxml".

type ShowInnerXML struct {
    Str string `xml:"s"`
    Raw string `xml:",innerxml"`
}

v := &ShowInnerXML{
    Str: "<foo></foo>",
    Raw: "<foo></foo>",
}
printXML(v)

XML: <ShowInnerXML><s><foo></foo></s><foo></foo></ShowInnerXML>

To serialize field as XML comment, use xml:",comment". Value can’t contain --.

type ShowComment struct {
    Str string `xml:",comment"`
}

v := &ShowComment{
    Str: "comment",
}
printXML(v)

XML: <ShowComment></ShowComment>

xml:"a>b>str" nests XML element. This also influences parsing.

type ShowNesting struct {
    Str string `xml:"a>b>str"`
}

v := &ShowNesting{
    Str: "str",
}
printXML(v)

XML: <ShowNesting><a><b><str>str</str></b></a></ShowNesting>

Custom XML marshaling

Writing custom XML marshalling
Sometimes a type doesn’t have an obvious mapping to JSON.

How would you serialize time.Time? There are so many possibilities.

Go provides a default XML mapping for time.Time. We can implement custom marshaller for user-defined types like structs.

For existing types we can define a new (but compatible) type.

Here’s a custom marshalling and unmarshalling for time.Time that only serializes year/month/date part:

type Event struct {
What string
When time.Time
}
e := Event{
What: "earthquake",
When: time.Now(),
}
d, err := xml.Marshal(&e)
if err != nil {
log.Fatalf("json.MarshalIndent failed with '%s'\n", err)
}
fmt.Printf("Standard time JSON: %s\n", string(d))

type customTime time.Time

const customTimeFormat = 2006-02-01

func (ct customTime) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
t := time.Time(ct)
v := t.Format(customTimeFormat)
return e.EncodeElement(v, start)
}

func (ct *customTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
err := d.DecodeElement(&s, &start)
if err != nil {
return err
}
t, err := time.Parse(customTimeFormat, s)
if err != nil {
return err
}
*ct = customTime(t)
return nil
}

type Event2 struct {
What string
When customTime
}

e := Event2{
What: "earthquake",
When: customTime(time.Now()),
}
d, err := xml.Marshal(&e)
if err != nil {
log.Fatalf("xml.Marshal failed with '%s'\n", err)
}
fmt.Printf("\nCustom time XML: %s\n", string(d))
var decoded Event2
err = xml.Unmarshal(d, &decoded)
if err != nil {
log.Fatalf("xml.Unmarshal failed with '%s'\n", err)
}
t := time.Time(decoded.When)
fmt.Printf("Decoded custom time: %s\n", t.Format(customTimeFormat))

notCustom()
custom()

Standard time JSON: <Event><What>earthquake</What><When>2019-11-06T04:19:54.27741912Z</When></Event>

Custom time XML: <Event2><What>earthquake</What><When>2019-06-11</When></Event2>
Decoded custom time: 2019-06-11
Notice that receiver type of UnmashalXML is a pointer to the type.

This is necessary for changes to persist outside the function itself.

Custom marshaling behind the scenes
How does custom marshaling works?

Package XML defines 2 interfaces: Marshaler and Unmarshaler.

type Marshaler interface {
    MarshalXML(e *Encoder, start StartElement) error
}

type Unmarshaler interface {
    UnmarshalXML(d *Decoder, start StartElement) error
}
By implementing those functions we make our type conform to Marshaler or Unmarshaler interface.

XML encoder / decoder checks if the value being encoded conforms to those interfaces and will call those functions instead of executing default logic.

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容