Skip to content

Dom\XMLDocument::C14N() seems broken compared to DOMDocument::C14N() #20444

Closed

Description

@aprat84

Description

The following code:

$xml = <<<EOF
<?xml version="1.0" encoding="UTF-8"?>
<test:root xmlns:test="http://example.com/dummy/ns">
    <test:a/>
    <test:b test:kind="123">abc</test:b>
</test:root>
EOF;

$d = \Dom\XMLDocument::createFromString($xml);
var_dump($d->C14N(true));

Resulted in this output:

<test:root xmlns:test="http://example.com/dummy/ns" xmlns:xmlns="http://www.w3.org/2000/xmlns/" xmlns:test="http://example.com/dummy/ns">
    <test:a></test:a>
    <test:b test:kind="123">abc</test:b>
</test:root>

But I expected this output instead:

<test:root xmlns:test="http://example.com/dummy/ns">
    <test:a></test:a>
    <test:b test:kind="123">abc</test:b>
</test:root>

However, this code works:

$xml = <<<EOF
<?xml version="1.0" encoding="UTF-8"?>
<test:root xmlns:test="http://example.com/dummy/ns">
    <test:a/>
    <test:b test:kind="123">abc</test:b>
</test:root>
EOF;

$d = new DOMDocument();
$d->loadXML($xml);
var_dump($d->C14N(true));

Shouldn't the new \Dom\XMLDocument class give the same result as the "old" \DOMDocument class?

PHP Version

PHP 8.4.14 (cli) (built: Oct 24 2025 19:07:34) (NTS)
Copyright (c) The PHP Group
Built by https://github.com/docker-library/php
Zend Engine v4.4.14, Copyright (c) Zend Technologies
    with Zend OPcache v8.4.14, Copyright (c), by Zend Technologies

Operating System

Alpine Linux v3.22.2 (php:8.4-fpm-alpine docker image)

Activity

  1. changed the title [-]`Dom\XMLDocument::C14N()` seems broken[/-] [+]`Dom\XMLDocument::C14N()` seems broken compared to `DOMDocument::C14N()`[/+] on Nov 10, 2025
  2. ndossche commented on Nov 11, 2025

    @ndossche
    Member
  3. aprat84 commented on Nov 11, 2025

    @aprat84
    Author

    The non-exclusive variant also produces weird results, repeating the namespace declaration in every first level node:

    $xml = <<<EOF
    <?xml version="1.0" encoding="UTF-8"?>
    <ns1:root xmlns:ns1="http://example.com/dummy/ns">
        <ns1:a/>
        <ns1:b>
            <ns1:c>123</ns1:c>
        </ns1:b>
    </ns1:root>
    EOF;
    
    $d = \Dom\XMLDocument::createFromString($xml);
    var_dump($d->C14N());
    
    $d = new DOMDocument();
    $d->loadXML($xml);
    var_dump($d->C14N());

    The first one outputs this:

    <ns1:root xmlns:ns1="http://example.com/dummy/ns">
        <ns1:a xmlns:ns1="http://example.com/dummy/ns"></ns1:a>
        <ns1:b xmlns:ns1="http://example.com/dummy/ns">
            <ns1:c>123</ns1:c>
        </ns1:b>
    </ns1:root>
    

    While the second one outputs this:

    <ns1:root xmlns:ns1="http://example.com/dummy/ns">
        <ns1:a></ns1:a>
        <ns1:b>
            <ns1:c>123</ns1:c>
        </ns1:b>
    </ns1:root>
    

    Also note that the DOMDocument::C14N() method produces the same results as the xmllint [--c14n|--exc-c14n] <file> command (libxml2-2.13.9-r0).

  4. ndossche commented on Nov 11, 2025

    @ndossche
    Member

    Yes I figured that out too when I experimented with the code yesterday evening.
    The root cause is that the new DOM code stores namespaces in a different way, and C14N has very strict rules about how namespaces should get stored. So I need to manually insert fake temporary declarations to satisfy the constraints of the C14N engine.
    I tried other tricks but the problem is that in the modern DOM specification there are no real namespace declarations nodes (they're proper attributes), so we don't have a fixed insertion point.

  5. added 3 commits that reference this issue on Dec 26, 2025
    9015f41
    40c291c
    31ae40d
  6. added a commit that references this issue on Dec 26, 2025
    004934c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions