Bug #67440 [NEW]: append_node of a DOMDocumentFragment does not reconcile namespaces
| From: | php at deep-freeze dot ca | Date: | Fri, 13 Jun 2014 21:36:40 +0000 |
| Subject: | Bug #67440 [NEW]: append_node of a DOMDocumentFragment does not reconcile namespaces | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-186188@lists.php.net to get a copy of this message | ||
From: php at deep-freeze dot ca
Operating system:
PHP version: 5.5.13
Package: DOM XML related
Bug Type: Bug
Bug description:append_node of a DOMDocumentFragment does not reconcile namespaces
Description:
------------
Namespaces don't get resolved when working with DOMDocumentFragments.
If you create more than one child element in a document fragment, and
then append it into a document, only the first node will resolve its
namespaces, and even then, only if fragment->children->item(0) is a
DOMElement.
This means that
$element->appendChild($fragment);
will give different results than
$childNodes = iterator_to_array($fragment->childNodes);
foreach ($childNodes as $childNode) $element->appendChild($childNode)
When they in effect do the same thing;
it's documented that that is fundamentally what
appendChild(DOMDocumentFragment) does in bug #53890 when dismissing that
bug as invalid.
And is representative of how it's effectively implemented in the ext
source.
The reason for this bug is the combination of:
in file php-src/ext/dom/node.c:
_php_dom_insert_before( ... )
_php_dom_append_node( ... )
// Element: new_child = xmlAddChild(nodePointer, childToInsert)
// Fragment: new_child = _php_dom_insert_fragment(nodePointer, ...,
childToInsert)
dom_reconcile_ns(nodeDocument, new_child)
The Function "dom_reconcile_ns" consits of only
if (nodePointer->type === XML_ELEMENT_NODE) { ... }
Thus it only works on the one node being pointed at, and it's tree.
(understandable)
The Function "_php_dom_insert_fragment" on the other hand causes the
problem.
It simply adds the fragment into the linked list, and returns the first
child.
newchild = fragment->children
prevSib->next = newChild
newchild->prev = prevSib
newchild->next = nextSib
nextSib->prev = newChild
return newchild; // The first child of the fragment
THUS THE BUG:
Only the first child of an DocumentFragment will reconcile it's
namespace in respect to it's current insertion location, the rest will
be ignored. This could potentially create invalid XML output, if a
namespace is resolved in a fragment, and is appended outside that valid
scope without being resolved.
Test script:
---------------
<?php
function createDocument() {
$document = new DOMDocument();
$document->loadXML('<?xml version="1.0"?><rootElement
xmlns:myns="http://example/ns"></rootElement>');
$fragment = $document->createDocumentFragment();
$fragment->appendChild($document->createTextNode("\n"));
$fragment->appendChild($document->createElementNS('http://example/ns',
'myns:childNode', '1'));
$fragment->appendChild($document->createTextNode("\n"));
$fragment->appendChild($document->createElementNS('http://example/ns',
'myns:childNode', '2'));
$fragment->appendChild($document->createTextNode("\n"));
return array($document, $fragment);
}
function case1() {
list($document, $fragment) = createDocument();
echo PHP_EOL, "CASE 1", PHP_EOL;
$document->documentElement->appendChild($fragment);
echo $document->saveXML();
}
function case2() {
list($document, $fragment) = createDocument();
$childNodes = iterator_to_array($fragment->childNodes);
foreach ($childNodes as $childNode) {
$document->documentElement->appendChild($childNode);
}
echo $document->saveXML();
}
function case3() {
list($document, $fragment) = createDocument();
$fragment->removeChild($fragment->firstChild);
$document->documentElement->appendChild($fragment);
echo $document->saveXML();
}
case1(); echo PHP_EOL, PHP_EOL;
case2(); echo PHP_EOL, PHP_EOL;
case3(); echo PHP_EOL, PHP_EOL;
Expected result:
----------------
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
<?xml version="1.0"?>
<rootElement
xmlns:myns="http://example/ns"><myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
Actual result:
--------------
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns">
<myns:childNode xmlns:myns="http://example/ns">1</myns:childNode>
<myns:childNode xmlns:myns="http://example/ns">2</myns:childNode>
</rootElement>
<?xml version="1.0"?>
<rootElement xmlns:myns="http://example/ns">
<myns:childNode>1</myns:childNode>
<myns:childNode>2</myns:childNode>
</rootElement>
<?xml version="1.0"?>
<rootElement
xmlns:myns="http://example/ns"><myns:childNode>1</myns:childNode>
<myns:childNode xmlns:myns="http://example/ns">2</myns:childNode>
</rootElement>
--
Edit bug report at https://bugs.php.net/bug.php?id=67440&edit=1
--