Golang XML Creation with same tags

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

Golang XML Creation with same tags

问题

所以我有一个特定的XML格式(行业标准),我正在尝试创建一个简单的程序,以便我们可以创建这个XML的样本来测试我们的服务。我正在使用标准的Go XML库。

问题是XML的格式非常繁琐。
这是它的简化版本:

<Document>
  <SubDocument>
    {其他字段}
    <component>
         <section>
             <id value="0" valueType="num"/> //这是区分section类型的部分
             <title>Foo-Type Section</title>
             {其他你们不需要关心的字段}
         </section>
    </component>
    <component>
         <section>
             <id value="1" valueType="num"/>
             <title>Bar-Type Section</title>
             {更多你们不需要关心的字段,但大部分与上面不同}
         </section>
    </component>
    {更多section}
  </SubDocument>
</Document>

我遇到的问题是,在Go中,如果不同的结构类型,每个section上的标签需要是唯一的。

我有以下Go代码:

type HasID struct{
    ID   string `xml:"value,attr,omitempty"`
    IDType    string `xml:"valueType,attr,omitempty"`
}
type FooSection struct{
     ID   HasID `xml:"id,omitempty"`
     Title string `xml:"title,omitempty"`
     //Foo字段
}
type BarSection struct{
     ID   HasID `xml:"id,omitempty"`
     Title string `xml:"title,omitempty"`
     //Bar字段
}
type Document struct{
    XMLName  struct{} `xml:"Document,omitempty"`
    //其他字段
    Sections  []interface{} `xml:"SubDocument>component>section,omitempty"`
}

我还尝试过让Sections字段没有标签,并且让FooSection和BarSection都有以下标签:

XMLName  struct{} `xml:"component>section,omitempty"`

但是没有成功。此外,我还尝试过让Sections成为一个字符串数组,然后对每个section类型进行编组,将它们放入数组中,并使用",innerxml"标签,但是这样会转义innerxml中的"<"等字符。

有人知道如何在Go中实现这个吗?这些结构体是我编写的,如果需要的话,可以完全更改。

可能是因为我太过于执着于面向对象,所以在使用Go时遇到了困难。

谢谢!

英文:

So I've got this specific format for xml (industry standard) and I'm trying to create a simple program to allow us to make samples of this xml to test our services. I'm using the standard Go XML library.

Problem is the XML is annoyingly formatted.
Here's the simplified version of it:

&lt;Document&gt;
  &lt;SubDocument&gt;
    {other fields}
    &lt;component&gt;
         &lt;section&gt;
             &lt;id value=&quot;0&quot; valueType=&quot;num&quot;/&gt; //This is the part that differentiates the type of the section
             &lt;title&gt;Foo-Type Section&lt;/title&gt;
             {other fields you lot don&#39;t need to care about}
         &lt;/section&gt;
    &lt;/component&gt;
    &lt;component&gt;
         &lt;section&gt;
             &lt;id value=&quot;1&quot; valueType=&quot;num&quot;/&gt;
             &lt;title&gt;Bar-Type Section&lt;/title&gt;
             {more fields you don&#39;t need to care about, but most are different than above}
         &lt;/section&gt;
    &lt;/component&gt;
    {more sections}
  &lt;/SubDocument&gt;
&lt;/Document&gt;

What I'm struggling with is that in Go, the tags on each section need to be unique if they are different struct types.

I've the following Go code:

type HasID struct{
    ID   string `xml:&quot;value,attr,omitempty&quot;`
    IDType    string `xml:&quot;valueType,attr,omitempty&quot;`
}
type FooSection struct{
     ID   HasID `xml:&quot;id,omitempty&quot;`
     Title string `xml:&quot;title,omitempty&quot;`
     //Foo fields
}
type BarSection struct{
     ID   HasID `xml:&quot;id,omitempty&quot;`
     Title string `xml:&quot;title,omitempty&quot;`
     //Bar fields
}
type Document struct{
    XMLName  struct{} `xml:&quot;Document,omitempty&quot;`
    //Other fields
    Sections  []interface{} `xml:&quot;SubDocument&gt;component&gt;section,omitempty&quot;`
}

I've also tried to have the Sections field have no tag and have both FooSection and BarSection have the

XMLName  struct{} `xml:&quot;component&gt;section,omitempty&quot;`

tag, to no avail. Furthermore, I've tried having Sections be an array of strings and then marshaled each section type, dumped those in and used the ",innerxml" tag, but then it escapes the "<", etc of the innerxml.

Does anyone know a way to do this in Go? The structs are written by me and are completely open to change if need be.

It might just be that I'm too entrenched in OO and am having trouble being Go-like.

Thanks!

答案1

得分: 1

我不知道这是否是一个完美的答案,但它是可行的。要实现这个功能,你需要在Component类型上实现encoding/xml.Unmarshaler,然后在UnmarshalXML方法中对该部分的原始数据进行解析,并在决定是否将其解析为FooSectionBarSection之前检查其ID。

以下是我正在使用的类型:

type ID struct {
    Value int    `xml:"value,attr,omitempty"`
    Type  string `xml:"valueType,attr,omitempty"`
}

type Document struct {
    Components []Component `xml:"SubDocument>component"`
}

type Component struct {
    Section interface{} `xml:"section"`
}

type FooSection struct {
    ID    ID     `xml:"id"`
    Title string `xml:"title"`
    Foo   string `xml:"foo"`
}

type BarSection struct {
    ID    ID     `xml:"id"`
    Title string `xml:"title"`
    Bar   string `xml:"bar"`
}

请注意,Component将其Section存储为interface{}类型。这有点麻烦,因为每当你想使用它时,你都必须进行类型切换,所以你可能可以对此进行改进。

然后,UnmarshalXML方法如下所示:

func (c *Component) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {

    // tmp保存此Component的数据。我们只能调用一次d.DecodeElement,所以我们必须将其放在某个地方以便重用。
    tmp := struct {
        Data []byte `xml:",innerxml"`
    }{}
    if err := d.DecodeElement(&tmp, &start); err != nil {
        return err
    }

    // which只包含足够的信息来告诉我们要创建哪种类型的section。我们将解析tmp.Data以检查它。
    which := struct {
        ID ID `xml:"id"`
    }{}
    if err := xml.Unmarshal(tmp.Data, &which); err != nil {
        return err
    }

    switch which.ID.Value {
    case 0:
        var f FooSection
        if err := xml.Unmarshal(tmp.Data, &f); err != nil {
            return err
        }
        c.Section = f

    case 1:
        var b BarSection
        if err := xml.Unmarshal(tmp.Data, &b); err != nil {
            return err
        }
        c.Section = b
    }

    return nil
}

完整的可工作代码可以在playground上找到

编辑:这些类型也适用于生成XML字符串,正如你实际上所问的那样。当你构造每个Component时,你应该选择要创建哪种类型的section,并将其放入其中。由于它是一个interface{},它可以保存任何类型的值。我已经更新了playground链接,其中包含一个示例,展示了将这些类型转换回字符串的工作方式。

英文:

I don't know if this is a perfect answer but it's workable. The gist is to implement encoding/xml.Unmarshaler on the Component type then inside that UnmarshalXML method you unmarshal the raw data of the section into a temporary value and inspect its ID before deciding if you want to unmarshal it into a FooSection or BarSection

These are the types I'm working with

type ID struct {
	Value int    `xml:&quot;value,attr,omitempty&quot;`
	Type  string `xml:&quot;valueType,attr,omitempty&quot;`
}

type Document struct {
	Components []Component `xml:&quot;SubDocument&gt;component&quot;`
}

type Component struct {
	Section interface{} `xml:&quot;section&quot;`
}

type FooSection struct {
	ID    ID     `xml:&quot;id&quot;`
	Title string `xml:&quot;title&quot;`
	Foo   string `xml:&quot;foo&quot;`
}

type BarSection struct {
	ID    ID     `xml:&quot;id&quot;`
	Title string `xml:&quot;title&quot;`
	Bar   string `xml:&quot;bar&quot;`
}

Note that Component is storing its Section as just an interface{}. That is kind of annoying because you'll have to type switch it whenever you want to use it so you can probably do something better with that.

Then the UnmarshalXML method is here

func (c *Component) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {

	// tmp holds the data for this Component. We can only call d.DecodeElement
	// once so we have to put it somewhere so it can be reused.
	tmp := struct {
		Data []byte `xml:&quot;,innerxml&quot;`
	}{}
	if err := d.DecodeElement(&amp;tmp, &amp;start); err != nil {
		return err
	}

	// which holds just enough information to tell us what kind of section to
	// make. We&#39;ll unmarshal tmp.Data into this to inspect it
	which := struct {
		ID ID `xml:&quot;id&quot;`
	}{}
	if err := xml.Unmarshal(tmp.Data, &amp;which); err != nil {
		return err
	}

	switch which.ID.Value {
	case 0:
		var f FooSection
		if err := xml.Unmarshal(tmp.Data, &amp;f); err != nil {
			return err
		}
		c.Section = f

	case 1:
		var b BarSection
		if err := xml.Unmarshal(tmp.Data, &amp;b); err != nil {
			return err
		}
		c.Section = b
	}

	return nil
}

Full working code on the playground.


Edit: These types should also work for generating the XML string as you actually were asking. When you're constructing each Component you should choose what kind of section to make and just stick it in there. Since it's a interface{} it can hold anything. I've updated my playground link to an example that shows turning those types back into a string works as expected.

huangapple
  • 本文由 发表于 2016年9月13日 22:03:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/39472041.html
匿名

发表评论

匿名网友

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

确定