2016-09-29 131 views
2

我试图创建一个实现MarshalXML输出的XML。 但目前我正面临几个问题。Golang XML自定义输出

我使用的用于存储数据的结构是:

type Edition struct { 
    Launch   string    `xml:"launch" json:"launch"` 
    Code   string    `xml:"code" json:"code"` 
    Names   []NameNode   `xml:"names>name"` 
    Cards   CardsComposition `xml:"cards" json:"cards,omitempty"` 
    Preconstructed PreconstructedInfo `xml:"preconstructed" json:"preconstructed,omitempty"` 
    Vault   *struct{}   `xml:"vault" json:"vault"` 
    Online   *struct{}   `xml:"online" json:"online"` 
} 

我要的是: 如果未设置预先构建的领域,不要把<preconstructed>标签(使用标准的封送它即使它是空的)。

所以我所做的是:

func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 
    if (PreconstructedInfo{} == preconstructed) { 
     return nil 
    } 
    return e.EncodeElement(preconstructed, start) 
} 

而且它显然作品,如果我用它的编码单版实体。 但是,如果我尝试编码版实体的数组,我得到以下错误:

runtime: goroutine stack exceeds 1000000000-byte limit 
fatal error: stack overflow 

(数组是〜200个)

所以我不明白的是:

  • 为什么堆栈溢出问题只发生在我尝试自定义xml时出现,在这种情况下,它也试图删除空标签,所以“节省空间”
  • 这样做的最佳方式是什么?有人可以向我解释如何实现一个定制的XML Marshaller去?我发现很多JSON元帅,但几乎没有任何XML)
+0

根据你的堆栈溢出,你导致了一个无限循环,所以EncodeElement调用了正在调用EncodeElement的MarshalXML。 – GarMan

+0

好的,但是为什么只有尝试编组一个版本元素才会发生这种情况,而不是如果我编组单个元素? – Ivan

回答

3

好吧,我会回答自己,因为我终于解决了这个问题。

所以显然,问题之一是EncodeElement正在使用MarshalXML,并且对于一个巨大的文件,它会导致爆炸函数调用。

无论如何,解决方案是手动编码元素的所有组件。

因此,在这种情况下,我这样做:

// MarshalXML generate XML output for PrecsontructedInfo 
func (preconstructed PreconstructedInfo) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) { 
    if (PreconstructedInfo{} == preconstructed) { 
     return nil 
    } 
    if preconstructed.Decks > 0 { 
     start.Attr = []xml.Attr{xml.Attr{Name: xml.Name{Local: "decks"}, Value: strconv.Itoa(preconstructed.Size)}} 
    } 
    if strings.Compare(preconstructed.Type, "") != 0 { 
     start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: preconstructed.Type}) 
    } 

    err = e.EncodeToken(start) 
    e.EncodeElement(preconstructed.Size, xml.StartElement{Name: xml.Name{Local: "size"}}) 
    return e.EncodeToken(xml.EndElement{Name: start.Name}) 
} 

所以我所做的是:

  1. 检查该字段为空或不是,如果是返回NULL(等同于我问题)
  2. 如果不为空,请检查PreconstructedInfo中包含的值,并将它们添加到它们的相关位置,将第一个属性添加到start元素。 start.Attr将包含被Marshalled标签的xml属性,它的语法非常简单,您可以指定名称和值。这必须在调用e.EncodeToken(start)之前完成。
  3. 之后,将标记的其他元素编码到当前的开始元素中。正如你所看到的,你必须使用xml.StartElement对标签进行编码,方式与该属性类似。
  4. 最后您可以关闭开始标签。

以这种方式,将生成的XML标签唯一的数据是否可用,并添加属性/孩子的,只有当他们有一个值,如果它们是空或0它不会被添加。