动态地从父节点解析带有属性的子XML。

huangapple go评论86阅读模式
英文:

Dynamically unmarshal child XML with attributes from parent

问题

我们如何动态地从父级解析带有属性的子级XML?

我们有以下XML:

<!-- 报告 I -->
<report type="YYYY-MM-DD">
  <created_at>2016-01-01</created_at>
</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" // 或者根据父级的 "type" 使用 "2016-01-02"
	var v string
	d.DecodeElement(&v, &start)
	parse, err := time.Parse(format, v)
	if err != nil {
		return err
	}
	*c = ReportDate{parse}
	return nil
}

UnmarshalXML 中,ReportDate 是否能够获取其父级的 type="??"?或者 Report 是否能够将属性值传递给所有子标签?如果可能的话,我们该如何实现这一点?

英文:

How do we dynamically unmarshal child XML with attributes from parent?

We have the the following XMLs:

&lt;!-- Report I --&gt;
&lt;report type=&quot;YYYY-MM-DD&quot;&gt;
  &lt;created_at&gt;2016-01-01&lt;/created_at&gt;
&lt;/report&gt;

&lt;!-- Report II --&gt;
&lt;report type=&quot;DD-MM-YYYY&quot;&gt;
  &lt;created_at&gt;01-01-2016&lt;/created_at&gt;
&lt;/report&gt;

And we have the following structure:

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

type ReportDate struct {
	time.Time
}

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

Will it be possible for ReportDate to obtain type=&quot;?&quot; from it's parent in UnmarshalXML? Or will it be possible for Report to pass down attribute values to all child tags? If it is possible, how do we accomplish this?

答案1

得分: 3

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

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

以下是代码:

package main

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

// TypeMap将XML日期格式字符串转换为有效的Go日期格式字符串
var typeMap = map[string]string{
	"YYYY-MM-DD": "2006-01-02",
	"DD-MM-YYYY": "02-01-2006",
}

// 示例XML文档
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 // 小写字段将被解码器/编码器忽略

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

希望对您有所帮助!

英文:

While parsing the parent you can set a 'private' field within the child element that let's it know the time format string to use.

Here's a working example https://play.golang.org/p/CEqjWoDQR3.

And here's the code:

package main
import (
&quot;encoding/xml&quot;
&quot;fmt&quot;
&quot;io&quot;
&quot;time&quot;
)
// TypeMap converts the XML date format string to a valid Go date format string
var typeMap = map[string]string{
&quot;YYYY-MM-DD&quot;: &quot;2006-01-02&quot;,
&quot;DD-MM-YYYY&quot;: &quot;02-01-2006&quot;,
}
// Example XML documents
var reportStrings = []string{
`&lt;!-- Report I --&gt;
&lt;report type=&quot;YYYY-MM-DD&quot;&gt;
&lt;created_at&gt;2016-01-01&lt;/created_at&gt;
&lt;/report&gt;`,
`&lt;!-- Report II --&gt;
&lt;report type=&quot;DD-MM-YYYY&quot;&gt;
&lt;created_at&gt;01-01-2016&lt;/created_at&gt;
&lt;/report&gt;`,
}
type Report struct {
XMLName   xml.Name   `xml:&quot;report&quot;`
Type      string     `xml:&quot;type,attr&quot;`
CreatedAt ReportDate `xml:&quot;created_at&quot;`
}
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 == &quot;type&quot; {
dateFormatStr, ok := typeMap[attr.Value]
if !ok {
return fmt.Errorf(&quot;unknown date type &#39;%s&#39;&quot;, 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 == &quot;created_at&quot; {
d.DecodeElement(&amp;r.CreatedAt, &amp;nextStart)
}
}
}
return nil
}
func (c *ReportDate) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
d.DecodeElement(&amp;s, &amp;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), &amp;report); err != nil {
panic(err)
}
fmt.Printf(&quot;[%d] %s\n&quot;, i, report.CreatedAt.Time)
}
}

答案2

得分: 1

我不确定是否有更符合Golang习惯的写法,但是如果你向Report中添加更多元素(比如'name'),代码会变成这样:

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

请注意,这只是代码的翻译部分,不包括其他内容。

英文:

I'm not sure if there's anything more idiomatic for Golang, but...

If you added more elements to Report (as in 'name') the code would look like this:

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

package main
import (
&quot;encoding/xml&quot;
&quot;fmt&quot;
&quot;io&quot;
&quot;time&quot;
)
var typeMap = map[string]string{
&quot;YYYY-MM-DD&quot;: &quot;2006-01-02&quot;,
&quot;DD-MM-YYYY&quot;: &quot;02-01-2006&quot;,
}
var reportStrings = []string{
`&lt;!-- Report I --&gt;
&lt;report type=&quot;YYYY-MM-DD&quot;&gt;
&lt;created_at&gt;2016-01-01&lt;/created_at&gt;
&lt;name&gt;Awesome Report I&lt;/name&gt;
&lt;/report&gt;`,
`&lt;!-- Report II --&gt;
&lt;report type=&quot;DD-MM-YYYY&quot;&gt;
&lt;created_at&gt;01-01-2016&lt;/created_at&gt;
&lt;name&gt;Awesome Report II&lt;/name&gt;
&lt;/report&gt;`,
}
type Report struct {
XMLName xml.Name `xml:&quot;report&quot;`
Type    string   `xml:&quot;type,attr&quot;`
Name      string     `xml:&quot;name&quot;`
CreatedAt ReportDate `xml:&quot;created_at&quot;`
}
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 == &quot;type&quot; {
r.Type = attr.Value
dateFormatStr, ok := typeMap[attr.Value]
if !ok {
return fmt.Errorf(&quot;unknown date type &#39;%s&#39;&quot;, 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 == &quot;created_at&quot; {
d.DecodeElement(&amp;r.CreatedAt, &amp;nextStart)
} else if local == &quot;name&quot; {
d.DecodeElement(&amp;r.Name, &amp;nextStart)
}
}
}
return nil
}
func (c *ReportDate) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
d.DecodeElement(&amp;s, &amp;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), &amp;report); err != nil {
panic(err)
}
fmt.Printf(&quot;[%d] %v\n&quot;, i, report)
}
}

huangapple
  • 本文由 发表于 2017年1月19日 10:33:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/41732785.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定