5

What I tried and what doesn't work:

  • Input:

    $d = new DOMDocument();
    $d->formatOutput = true;
    
    // Out of my control:
    $someEl = $d->createElementNS('http://example.com/a', 'a:some');
    
    // Under my control:
    $envelopeEl = $d->createElementNS('http://example.com/default',
                                      'envelope');
    $d->appendChild($envelopeEl);
    $envelopeEl->appendChild($someEl);
    
    echo $d->saveXML();
    
    $someEl->prefix = null;
    echo $d->saveXML();
    
  • Output is invalid XML after substitution:

    <?xml version="1.0"?>
    <envelope xmlns="http://example.com/default">
      <a:some xmlns:a="http://example.com/a"/>
    </envelope>
    <?xml version="1.0"?>
    <envelope xmlns="http://example.com/default">
      <:some xmlns:a="http://example.com/a" xmlns:="http://example.com/a"/>
    </envelope>
    

Note that <a:some> may have children. One solution would be to create a new <some>, and copy all children from <a:some> to <some>. Is that the way to go?

hakre
  • 193,403
  • 52
  • 435
  • 836
feklee
  • 7,555
  • 9
  • 54
  • 72
  • It is easier if you use a converter xml to array and backwards [example][1] [1]: http://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml – AURIGADL Feb 22 '13 at 17:21
  • @AURIGADL Easier for the CPU or for the programmer? :) – hek2mgl Feb 22 '13 at 17:22
  • 1
    I just realized that changing the namespace of a node (here: from `a` to default) is *the same as renaming* the node. So this question can be marked as a duplicate of the question ["Rename an XML node using PHP"](http://stackoverflow.com/questions/12463550/rename-an-xml-node-using-php), which already has a decent answer. – feklee Feb 22 '13 at 23:30
  • I now found a robust solution which may be worthy an answer, despite the fact that moving to default namespace is akin to renaming. – feklee Feb 26 '13 at 15:19
  • The aforementioned solution is simply to create a wrapper for the [rename function from my answer to that other question](http://stackoverflow.com/a/15092434/282729): `function moveToDefaultNamespace($element) { renameElement($element, $element->localName); }` – feklee Feb 26 '13 at 16:18

1 Answers1

3

This is really an interesting question. My first intention was to clone the <a:some> node, remove the xmlns:a attribute, remove the <a:some> and insert the clone - <a>. But this will not work, as PHP does not allow to remove the xmlns:a attribute like any regular attribute.

After some struggling with DOM methods of PHP I started to google the problem. I found this comment in the PHP documentation on this. The user suggest to write a function that clones the node manually without it's namespace:

<?php

/**
 * This function is based on a comment to the PHP documentation.
 * See: http://www.php.net/manual/de/domnode.clonenode.php#90559
 */
function cloneNode($node, $doc){
  $unprefixedName = preg_replace('/.*:/', '', $node->nodeName);
  $nd = $doc->createElement($unprefixedName);

  foreach ($node->attributes as $value)
    $nd->setAttribute($value->nodeName, $value->value);

  if (!$node->childNodes)
    return $nd;

  foreach($node->childNodes as $child) {
    if($child->nodeName == "#text")
      $nd->appendChild($doc->createTextNode($child->nodeValue));
    else
      $nd->appendChild(cloneNode($child, $doc));
  }

  return $nd;
}

Using it would lead to a code like this:

$xml = '<?xml version="1.0"?>
<envelope xmlns="http://example.com/default">
  <a:some xmlns:a="http://example.com/a"/>
</envelope>';

$doc = new DOMDocument();
$doc->loadXML($xml);

$elements = $doc->getElementsByTagNameNS('http://example.com/a', 'some');
$original = $elements->item(0);

$clone = cloneNode($original, $doc);
$doc->documentElement->replaceChild($clone, $original);

$doc->formatOutput = TRUE;
echo $doc->saveXML();
feklee
  • 7,555
  • 9
  • 54
  • 72
hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • Thanks! I edited your code a little bit, mainly fixed syntax, and added the `cloneNode` definition from the comment that you linked to. In the end this is actually what I proposed in my question *(copy all children)*, but of course it's nice to have a ready-made function. – feklee Feb 22 '13 at 18:14
  • Fine. I just didn't wanted to reproduce the code from the comment and also wanted to keep the code as short as possible – hek2mgl Feb 22 '13 at 18:20
  • Had to tweak the function somewhat. I noticed, it doesn't remove the namespace prefix, i.e. in the original form it preserves ``, while I want plain ``. – feklee Feb 22 '13 at 18:28
  • Please don't edit too much. My answer - as is - worked as you expected. I have tested it – hek2mgl Feb 22 '13 at 18:29
  • I've tested it too: There were syntax errors. Please don't understand me wrong: Your answer is great! Still I will wait a little bit before marking it as accepted. – feklee Feb 22 '13 at 18:31
  • Fine. ;) Then there must be a copy and paste error. I've just posted the working example. What syntax errors have you encountered? – hek2mgl Feb 22 '13 at 18:33
  • For example: `Argument 1 passed to DOMNode::replaceChild() must be an instance of DOMNode, null given` I fixed that. – feklee Feb 22 '13 at 18:36
  • That's odd.. But however, if you are happy with the result I'll be too. :) – hek2mgl Feb 22 '13 at 18:39
  • Please revert back to my last edit. That was the code that worked, and it included the necessary edit to `cloneNode` to remove the prefix. As it stands, I cannot mark the answer as accepted. – feklee Feb 22 '13 at 18:41
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/24979/discussion-between-hek2mgl-and-feklee) – hek2mgl Feb 22 '13 at 18:41
  • No need for that. The code that I have (and that I edited in some minutes ago) works fine with PHP 5.3. – feklee Feb 22 '13 at 18:44
  • It was because you originally had: `$doc->getElementsByTagNameNS('http://example.com/a', 'some')->item(0)` – feklee Feb 22 '13 at 18:49
  • I finally got you ;) You should consider to add the edited function to the PHP documentation as a comment as well – hek2mgl Feb 22 '13 at 18:55
  • `cloneNode` does not properly deep-copy elements, and the function is unnecessarily complex: The check `!$node->childNodes` is unnecessary. A robust solution should be to create a wrapper for the `renameElement` function found in [my answer to another question](http://stackoverflow.com/a/15092434/282729): `function moveToDefaultNamespace($element) { renameElement($element, $element->localName); }` – feklee Feb 27 '13 at 10:59