xmlgo

encoding/xml - Retaining XML Namespace Prefixes when Encoding/Decoding


Is there a way to retain xml namespace prefixes when encoding? I am processing XML using the xml.Decoder Token/RawToken function and doing some transformations. There is a large amount of XML we want to remain formatted similar to the original XML. When I write a StartElement token back out unchanged the namespaces get written incorrectly.

Desired output:

<Inq xmlns="http://test/default">
  <Num>TestNum</Num>
  <test xmlns:t="http://test/prefix">
    <t:namespace>test</t:namespace>
  </test>
</Inq>

Actual output:

<Inq xmlns="http://test/default">
  <Num>TestNum</Num>
  <test xmlns:_xmlns="xmlns" _xmlns:t="http://test/prefix">
    <namespace xmlns="t">test</namespace>
  </test>
</Inq>

Code to reproduce the issue:

package main

import (
    "bytes"
    "encoding/xml"
    "fmt"
    "io"
    "strings"
)

func main() {
    expected := `<Inq xmlns="http://test/default"><Num>TestNum</Num><test xmlns:t="http://test/prefix"><t:namespace>test</t:namespace></test></Inq>`
    payloadReader := strings.NewReader(expected)
    decoder := xml.NewDecoder(payloadReader)
    buf := new(bytes.Buffer)
    encoder := xml.NewEncoder(buf)

    for {
        token, err := decoder.RawToken()
        if err != nil {
            if err == io.EOF {
                break
            }

            panic(err)
        }

        if token == nil {
            break
        }

        if err := encoder.EncodeToken(token); err != nil {
            panic(err)
        }
    }

    if err := encoder.Flush(); err != nil {
        panic(err)
    }

    actual := buf.String()
    if actual != expected {
        fmt.Printf("got: %s\nwant: %s\n", actual, expected)
    }
}

https://go.dev/play/p/mrjN5xOAp-y

Is there anyway to get the desired behavior other than tracking the namespace and prefixes and recreating the Start and End elements with the desired prefix naming?


Solution

  • This is not yet possible using an official Go release but this feature request which is still in review makes it possible. It's not yet released which means that you need to build and use your own Go compiler which can be problematic especially if you want to build the code on another machine as a part of the CI pipeline. To build the custom Go compiler:

    $ git clone https://go.googlesource.com/go
    $ cd go/src
    $ git fetch https://go.googlesource.com/go refs/changes/53/355353/30 && git checkout -b change-355353 FETCH_HEAD
    $ git rebase origin
    $ ./make.bash
    $ PATH="$PWD"/../bin:"$PATH"
    $ GOROOT="$(git rev-parse --show-toplevel)"
    

    Now, in the same shell, change to the directory where you have the code. Make sure you use the correct Go compiler (the timestamp will be different):

    $ go version
    go version go1.25-devel_dcc605f108 Sat May 24 23:57:00 2025 +0200 linux/amd64
    

    Make one change - replace:

    token, err := decoder.RawToken()
    

    with:

    token, err := decoder.Token()
    

    Run code:

    $ go run .
    $
    

    Output is now as you expected.

    Your question is specifically about encoding/xml but https://github.com/golang/go/issues/9519 issue mentioned in the comment under the question mentions using an external library that reportedly supports the feature you're looking for out of the box:

    For now, I found a replacement to encoding/xml using nbio/xml which handles namespaces properly.

    So if you don't want to use a custom Go compiler with unreleased features you can consider using nbio/xml.