【问题标题】:Dynamically unmarshal child XML with attributes from parent使用来自父级的属性动态解组子 XML
【发布时间】:2017-01-19 02:33:39
【问题描述】:

我们如何动态解组具有父属性的子 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 是否有可能从其在UnmarshalXML 中的父级获得type="?"?或者Report 是否可以将属性值传递给所有子标签?如果可能,我们如何做到这一点?

【问题讨论】:

    标签: xml go


    【解决方案1】:

    在解析父元素时,您可以在子元素中设置一个“私有”字段,让它知道要使用的时间格式字符串。

    这是一个工作示例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)
        }
    }
    

    【讨论】:

    • 不错!如果报告中有其他元素,例如&lt;report&gt;&lt;name&gt;Awesome Report&lt;/name&gt;&lt;/report&gt;。我们如何在UnmarshalXML 中传递它们?
    • 查看我的其他答案。
    【解决方案2】:

    我不确定 Golang 是否还有更惯用的语言,但是...

    如果您向报告中添加更多元素(如“名称”),代码将如下所示:

    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)
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-12-18
      • 1970-01-01
      • 1970-01-01
      • 2013-07-28
      • 1970-01-01
      • 2021-11-05
      • 1970-01-01
      • 2021-12-13
      • 1970-01-01
      相关资源
      最近更新 更多