Skip to content

fix(xml): namespace prefixes are dropped when reading - #559

Open
VXNCXNX wants to merge 1 commit into
TomWright:masterfrom
VXNCXNX:fix/xml-namespace-prefixes
Open

fix(xml): namespace prefixes are dropped when reading#559
VXNCXNX wants to merge 1 commit into
TomWright:masterfrom
VXNCXNX:fix/xml-namespace-prefixes

Conversation

@VXNCXNX

@VXNCXNX VXNCXNX commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Reading namespaced XML drops every prefix and turns the xmlns: declarations
into ordinary attributes, so the output is not the document that went in.

$ dasel -i xml -o xml < soap.xml
before:
<Envelope soap="http://schemas.xmlsoap.org/soap/envelope/">
  <Body>
    <GetPrice m="https://example.org/prices">
      <Item>Apples</Item>
    </GetPrice>
  </Body>

after:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <m:GetPrice xmlns:m="https://example.org/prices">
      <m:Item>Apples</m:Item>
    </m:GetPrice>
  </soap:Body>

soap="..." is not a valid replacement for xmlns:soap="...", so a SOAP
envelope round-tripped through dasel comes out broken. The same happens to
attributes: <root xml:lang="en" xmlns:xsi="..."><a xsi:nil="true"/></root>
became <root lang="en" xsi="..."><a nil="true">.

Cause

encoding/xml resolves a prefix to a URI, giving
Name{Space: "http://...", Local: "Envelope"}. The reader used only .Local, so
the prefix was gone, and the xmlns:soap declaration was carried through as an
attribute whose name had also lost its xmlns prefix.

The fix

Build a scoped prefix map from the xmlns declarations on each element, chained
to the enclosing scopes, and use it to re-qualify element and attribute names on
read. Covered: the default namespace stays unprefixed, the implicit xml: prefix
is known without declaration, and an undeclared prefix is preserved verbatim
rather than guessed at.

The writer already round-trips whatever name it is given, so this is a
reader-only change.

Verification

TestXMLNamespaceRoundTrip, five sub-cases.

Reverting scope.qualify(element.Name) to element.Name.Local fails it:

--- FAIL: TestXMLNamespaceRoundTrip/prefixed_elements_and_namespace_declarations
    namespace_test.go:95: expected:
        <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
        got:
        <Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
--- FAIL: TestXMLNamespaceRoundTrip/undeclared_prefix_is_preserved_verbatim

go test ./... passes across every package, go vet is clean, and
golangci-lint run ./parsing/xml/... reports 0 issues.

gofumpt -l . flags around 50 pre-existing files, including ones I did not
touch, so I left formatting alone.

The part that needs your decision

This changes the JSON representation of namespaced XML, because the names are now
correct:

$ dasel -i xml -o json <<< '<r xmlns:n="u"><n:item>v</n:item></r>'
before:                  after:
{                        {
    "r": {                   "r": {
        "-n": "u",               "-xmlns:n": "u",
        "item": "v"              "n:item": "v"
    }                        }
}                        }

Anyone selecting a namespaced element by its unprefixed name today would need to
update their query. I think the new keys are the right data, since the old ones
described a document that did not exist, but it is a visible break and your call
rather than mine. Documents without namespaces are completely unaffected.

Disclosure: written with AI assistance (Claude Code). I produced the before and after by running binaries built from each tree against the documents shown, and ran the mutation check myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant