MarshalXML用于根元素

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

MarshalXML for root element

问题

我似乎在调用marshaller的时候遇到了问题,它没有调用MarshalXML来处理根节点。我有一个用于存储XML的结构体,如下所示:

type XmlNode struct {
	Name     string            `xml:"-"`
	Attrs    map[string]string `xml:"-"`
	Children []XmlNode         `xml:",any"`
}

我有一个类似下面的MarshalXML函数:

func (n *XmlNode) MarshalXML(encoder *xml.Encoder, start xml.StartElement) error {
	fmt.Println("Inside MarshalXML")

	// Name
	start.Name = xml.Name{Local: n.Name}

	// Attrs
	for k, v := range n.Attrs {
		start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: k}, Value: v})
	}

	type node XmlNode
	return encoder.EncodeElement((*node)(n), start)
}

我使用类似下面的代码调用它:

func main() {
	root := XmlNode{Name: "root", Attrs: map[string]string{}}
	root.Attrs["rootAttr"] = "rootValue"

	child := XmlNode{Name: "child", Attrs: map[string]string{}}
	child.Attrs["childAttr"] = "childValue"

	root.Children = append(root.Children, child)
	out, _ := xml.Marshal(root)
	fmt.Println(string(out))
}

运行时,我看到以下输出:

Inside MarshalXML
<XmlNode><child childAttr="childValue"></child></XmlNode>

我期望的输出是:

Inside MarshalXML
Inside MarshalXML
<root rootAttr="rootValue"><child childAttr="childValue"></child></root>

我知道我可以将名称编码为特定属性,但我更关注如何处理来自映射的属性。我也不应该在那里修改起始元素,但对于这个例子似乎可以工作。我的问题是MarshalXML不会为根元素调用,但对于任何子元素都会调用。所以,即使我在那里做正确的事情,我也不确定是否能拦截根元素并适当地修改它。

我简要尝试直接使用xml.Encode,但它似乎会按照相同的路径构建起始模板和根元素,然后调用MarshalXML来处理Children节点。有没有一种简洁的方法将XmlNode转换为xml.StartElement,以便我可以传递它?我可以在XmlNode上直接编写一个函数,通过创建一个StartElement并调用EncodeElementEncode来开始编组,但我想避免这样做,并坚持使用xml.Marshal调用,以防止其他开发人员产生困惑。

有什么想法吗?

这是上述代码的可运行形式的Go Playground链接。

英文:

I seem to be having a problem getting the marshaller to call MarshalXML for the root node. I have a struct for storing the XML like so:

type XmlNode struct {
	Name     string            `xml:"-"`
	Attrs    map[string]string `xml:"-"`
	Children []XmlNode         `xml:",any"`
}

I have a MarshalXML function like so:

func (n *XmlNode) MarshalXML(encoder *xml.Encoder, start xml.StartElement) error {
	fmt.Println("Inside MarshalXML")

	// Name
	start.Name = xml.Name{Local: n.Name}

	// Attrs
	for k, v := range n.Attrs {
		start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: k}, Value: v})
	}

	type node XmlNode
	return encoder.EncodeElement((*node)(n), start)
}

And I'm calling it with code similar to:

func main() {
	root := XmlNode{Name: "root", Attrs: map[string]string{}}
	root.Attrs["rootAttr"] = "rootValue"

	child := XmlNode{Name: "child", Attrs: map[string]string{}}
	child.Attrs["childAttr"] = "childValue"

	root.Children = append(root.Children, child)
	out, _ := xml.Marshal(root)
	fmt.Println(string(out))
}

When it runs, I see the following output:

Inside MarshalXML
<XmlNode><child childAttr="childValue"></child></XmlNode>

I'm expecting:

Inside MarshalXML
Inside MarshalXML
<root rootAttr="rootValue"><child childAttr="childValue"></child></root>

I know that I can code the name into a specific attribute, but I'm more concerned with handling the attributes from a map. I'm also likely not supposed to be modifying the start element there, but it seems to work for this. My problem is that the MarshalXML doesn't get called for the root element, but it does for any children. So, even if I were to do something correctly there, I'm not sure I can intercept that root element to modify it appropriately.

I briefly tried using xml.Encode directly, but it seems to follow into the same path to build the start template and root, and then calls the MarshalXML for the Children node. Is there a clean way to get XmlNode converted into xml.StartElement so I can pass that in? I can write a function on XmlNode directly that'll start the marshalling by making a StartElement and calling EncodeElement or Encode, but I'm trying to avoid that and stick with the xml.Marshal call, to prevent some confusion for other devs.

Any thoughts?

Here's a Go Playground link to the above code in a runnable form.

答案1

得分: 2

要翻译的内容如下:

要么传递一个指针,例如 xml.Marshal(&root)playground),要么将接收器类型从指针更改为非指针,例如 func (n *XmlNode) MarshalXML => func (n XmlNode) MarshalXMLplayground)。

https://go.dev/ref/spec#Method_sets

> 类型 T 的方法集包含所有以接收器类型 T 声明的方法。相应指针类型 *T 的方法集包含所有以接收器 *TT 声明的方法(也就是说,它还包含 T 的方法集)。
>
> 类型的方法集决定了该类型实现的接口以及可以使用该类型接收器调用的方法。

上述内容意味着,类型为 XmlNode 的变量 root 不实现 xml.Marshaler,因为 MarshalXML 方法不是 XmlNode 的方法集的一部分。然而,类型为 *XmlNode 的变量 &root 实现 xml.Marshaler,因为 MarshalXML 方法是 *XmlNode 的方法集的一部分。

当你将方法的接收器更改为非指针,例如 func (n XmlNode) MarshalXML,那么类型为 XmlNode 的变量 root实现 xml.Marshaler,因为 MarshalXML 方法将成为 XmlNode 的方法集的一部分。而且,引用规范还指出,类型为 *XmlNode 的变量 &root 也将实现 xml.Marshaler,因为 MarshalXML 方法,即使在 XmlNode 上声明,也将成为 *XmlNode 的方法集的一部分。

请注意,在直接调用具体类型上的方法时,指针与非指针的方法集并不是那么重要。例如,如果你有一个类型为 XmlNode 的变量 root,并且你想调用方法 DoXyz,该方法以指针接收器声明为 func (n *XmlNode) DoXyz(),那么你不需要 &root,表达式 root.DoXyz 将编译通过。

https://go.dev/ref/spec#Calls

> 如果方法集(类型为 x 的)包含 m,并且参数列表可以分配给 m 的参数列表,方法调用 x.m() 是有效的。如果 x 是可寻址的,并且 &x 的方法集包含 m,则 x.m()(&x).m() 的简写形式

英文:

Either pass in a pointer, i.e. xml.Marshal(&root) (playground), or change the receiver type from pointer to non-pointer, i.e. func (n *XmlNode) MarshalXML => func (n XmlNode) MarshalXML (playground).


https://go.dev/ref/spec#Method_sets

> The method set of type T consists of all methods declared
> with receiver type T. The method set of the corresponding pointer type
> *T is the set of all methods declared with receiver *T or T (that is, it also contains the method set of T).
>
> The method set of a type determines the interfaces that the type
> implements and the methods that can be called using a receiver of that
> type.

The above means that the variable root, which is of type XmlNode, DOES NOT implement xml.Marshaler because the MarshalXML method IS NOT part of XmlNode's method set. However &root, which is of type *XmlNode, DOES implement xml.Marshaler because the MarshalXML method IS part of *XmlNode's method set.

When you change the method's receiver to non-pointer, i.e. func (n XmlNode) MarshalXML then, root, which is of type XmlNode, will implement xml.Marshaler because the MarshalXML method will be part of XmlNode's method set. And the cited spec also says that, &root, which is of type *XmlNode will also implement xml.Marshaler because the MarshalXML method, even though declared on XmlNode, will also be part of *XmlNode's method set.


Note that this pointer vs non-pointer method set stuff is not so important to think about when you want to invoke a method on a concrete type directly as opposed to through an interface. For example if you have the variable root which if of type XmlNode, and you want to invoke the method DoXyz, which is declared with a pointer receiver as func (n *XmlNode) DoXyz(), then you DO NOT need &root, the expression root.DoXyz will compile just fine.

https://go.dev/ref/spec#Calls

> A method call x.m() is valid if the method set of (the type of) x contains m and the argument list can be assigned to the parameter list of m. If x is addressable and &x's method set contains m, x.m() is shorthand for (&x).m().

huangapple
  • 本文由 发表于 2021年12月9日 11:02:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/70284228.html
匿名

发表评论

匿名网友

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

确定