英文:
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:
<!-- 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>
And we have the following structure:
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
}
Will it be possible for ReportDate
to obtain type="?"
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 (
"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)
}
}
答案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 (
"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)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论