使用来自父级的属性动态解组子XML

时间:2017-01-19 02:33:39

标签: xml go

我们如何使用来自父级的属性动态解组子XML?

我们有以下XML:

<!-- Report I -->
<report type="YYYY-MM-DD">
  <created_at>2016-01-01</created_at>
</report>

<!-- Report II -->
<report type="DD-MM-YYYY">
  <created_at>01-01-2016</created_at>
</report>

我们有以下结构:

type Report struct {
  XMLName   xml.Name    `xml:"report"`
  Type      string      `xml:"type,attr"`
  CreatedAt *ReportDate `xml:"created_at"`
}

type ReportDate struct {
    time.Time
}

func (c *ReportDate) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    const format = "02-01-2006" // or "2016-01-02" depending on parent's "type"
    var v string
    d.DecodeElement(&v, &start)
    parse, err := time.Parse(format, v)
    if err != nil {
        return err
    }
    *c = ReportDate{parse}
    return nil
}

ReportDate是否可以从type="?"的父母那里获取UnmarshalXML?或者Report是否可以将属性值传递给所有子标记?如果有可能,我们如何做到这一点?

2 个答案:

答案 0 :(得分:1)

在解析父母时,您可以设置私人&#39;子元素中的字段,它让它知道要使用的时间格式字符串。

这是一个工作示例https://play.golang.org/p/CEqjWoDQR3

以下是代码:

package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "time"
)

// TypeMap converts the XML date format string to a valid Go date format string
var typeMap = map[string]string{
    "YYYY-MM-DD": "2006-01-02",
    "DD-MM-YYYY": "02-01-2006",
}

// Example XML documents
var reportStrings = []string{
    `<!-- Report I -->
<report type="YYYY-MM-DD">
  <created_at>2016-01-01</created_at>
</report>`,

    `<!-- Report II -->
<report type="DD-MM-YYYY">
  <created_at>01-01-2016</created_at>
</report>`,
}

type Report struct {
    XMLName   xml.Name   `xml:"report"`
    Type      string     `xml:"type,attr"`
    CreatedAt ReportDate `xml:"created_at"`
}

type ReportDate struct {
    dateFormatStr string // lower-case field is ignored by decoder/encoder

    Time time.Time
}

func (r *Report) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    for _, attr := range start.Attr {
        if attr.Name.Local == "type" {
            dateFormatStr, ok := typeMap[attr.Value]
            if !ok {
                return fmt.Errorf("unknown date type '%s'", attr.Value)
            }
            r.CreatedAt.dateFormatStr = dateFormatStr
        }
    }

    for {
        tok, err := d.Token()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        switch tok.(type) {
        case xml.StartElement:
            nextStart := tok.(xml.StartElement)
            if nextStart.Name.Local == "created_at" {
                d.DecodeElement(&r.CreatedAt, &nextStart)
            }
        }
    }

    return nil
}

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

func main() {
    for i, reportStr := range reportStrings {
        var report Report
        if err := xml.Unmarshal([]byte(reportStr), &report); err != nil {
            panic(err)
        }

        fmt.Printf("[%d] %s\n", i, report.CreatedAt.Time)
    }
}

答案 1 :(得分:1)

我不确定Golang是否有更多惯用语,但是......

如果您向Report添加了更多元素(如'name'),代码将如下所示:

https://play.golang.org/p/5VpzXM5F95

package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "time"
)

var typeMap = map[string]string{
    "YYYY-MM-DD": "2006-01-02",
    "DD-MM-YYYY": "02-01-2006",
}

var reportStrings = []string{
    `<!-- Report I -->
<report type="YYYY-MM-DD">
  <created_at>2016-01-01</created_at>
  <name>Awesome Report I</name>
</report>`,

    `<!-- Report II -->
<report type="DD-MM-YYYY">
  <created_at>01-01-2016</created_at>
  <name>Awesome Report II</name>
</report>`,
}

type Report struct {
    XMLName xml.Name `xml:"report"`
    Type    string   `xml:"type,attr"`

    Name      string     `xml:"name"`
    CreatedAt ReportDate `xml:"created_at"`
}

type ReportDate struct {
    dateFormatStr string // lower-case field is ignored by decoder/encoder

    Time time.Time
}

func (r *Report) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
    for _, attr := range start.Attr {
        if attr.Name.Local == "type" {
            r.Type = attr.Value
            dateFormatStr, ok := typeMap[attr.Value]
            if !ok {
                return fmt.Errorf("unknown date type '%s'", attr.Value)
            }
            r.CreatedAt.dateFormatStr = dateFormatStr
        }
    }

    for {
        tok, err := d.Token()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        switch tok.(type) {
        case xml.StartElement:
            nextStart := tok.(xml.StartElement)
            local := nextStart.Name.Local
            if local == "created_at" {
                d.DecodeElement(&r.CreatedAt, &nextStart)
            } else if local == "name" {
                d.DecodeElement(&r.Name, &nextStart)
            }
        }
    }

    return nil
}

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

func main() {
    for i, reportStr := range reportStrings {
        var report Report
        if err := xml.Unmarshal([]byte(reportStr), &report); err != nil {
            panic(err)
        }

        fmt.Printf("[%d] %v\n", i, report)
    }
}