php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #67440 append_node of a DOMDocumentFragment does not reconcile namespaces
Submitted: 2014-06-13 21:36 UTC Modified: -
Votes:2
Avg. Score:3.0 ± 0.0
Reproduced:0 of 0 (0.0%)
From: php at deep-freeze dot ca Assigned:
Status: Open Package: DOM XML related
PHP Version: 5.5.13 OS:
Private report: No CVE-ID: None
View Add Comment Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
You can add a comment by following this link or if you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: php at deep-freeze dot ca
New email:
PHP Version: OS:

 

 [2014-06-13 21:36 UTC] php at deep-freeze dot ca
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>


Patches

Add a Patch

Pull Requests

Add a Pull Request

 
PHP Copyright © 2001-2020 The PHP Group
All rights reserved.
Last updated: Sun Jan 26 00:01:25 2020 UTC