From bc3e680d849d45f6254d9e97a17c954c3a01b873 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Tue, 12 Jan 2021 16:41:58 +0100 Subject: [PATCH] Un-resolve xml attribute namespaces This updates the XML decoder to un-resolve xml namspaces in attributes. The Go decoder resolves them fully by default, but we need the short names to match attributes to members while decoding. --- encoding/xml/xml_decoder.go | 32 +++++++++++++++++++++++++++++++- encoding/xml/xml_decoder_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/encoding/xml/xml_decoder.go b/encoding/xml/xml_decoder.go index 2d3c17cac..dc4eebdff 100644 --- a/encoding/xml/xml_decoder.go +++ b/encoding/xml/xml_decoder.go @@ -38,7 +38,7 @@ func (d NodeDecoder) Token() (t xml.StartElement, done bool, err error) { } if t, ok := token.(xml.StartElement); ok { - return t, false, err + return restoreAttrNamespaces(t), false, err } // skip token if it is a comment or preamble or empty space value due to indentation @@ -46,6 +46,36 @@ func (d NodeDecoder) Token() (t xml.StartElement, done bool, err error) { } } +// restoreAttrNamespaces update XML attributes to restore the short namespaces found within +// the raw XML document. +func restoreAttrNamespaces(node xml.StartElement) xml.StartElement { + if len(node.Attr) == 0 { + return node + } + + // Generate a mapping of XML namespace values to their short names. + ns := map[string]string{} + for _, a := range node.Attr { + if a.Name.Space == "xmlns" { + ns[a.Value] = a.Name.Local + break + } + } + + for i, a := range node.Attr { + if a.Name.Space == "xmlns" { + continue + } + // By default, xml.Decoder will fully resolve these namespaces. So if you had + // then by default the second attribute would have the `Name.Space` resolved to `baz`. But we need it to + // continue to resolve as `bar` so we can easily identify it later on. + if v, ok := ns[node.Attr[i].Name.Space]; ok { + node.Attr[i].Name.Space = v + } + } + return node +} + // GetElement looks for the given tag name at the current level, and returns the element if found, and // skipping over non-matching elements. Returns an error if the node is not found, or if an error occurs while walking // the document. diff --git a/encoding/xml/xml_decoder_test.go b/encoding/xml/xml_decoder_test.go index c2679da3c..802fd4a58 100644 --- a/encoding/xml/xml_decoder_test.go +++ b/encoding/xml/xml_decoder_test.go @@ -52,6 +52,30 @@ func TestXMLNodeDecoder_Token(t *testing.T) { Attr: []xml.Attr{}, }, }, + "attr with namespace": { + responseBody: bytes.NewReader([]byte(``)), + expectedStartElement: xml.StartElement{ + Name: xml.Name{ + Local: "Grantee", + }, + Attr: []xml.Attr{ + { + Name: xml.Name{ + Space: "xmlns", + Local: "xsi", + }, + Value: "http://www.w3.org/2001/XMLSchema-instance", + }, + { + Name: xml.Name{ + Space: "xsi", + Local: "type", + }, + Value: "CanonicalUser", + }, + }, + }, + }, } for name, c := range cases {