36

I'm trying to build a rather complex XML document.

I have a bunch of sections of the XML document that repeats. I thought I'd use multiple string templates as base document for the sections and create instances of XML elements using simplexml_load_string.

So I have one instance of SimpleXMLElement as the base document

$root = simplexml_load_string($template_root);

then I loop through some items in my database, create new SimpleXMLElement, something like this:

for (bla bla bla):

$item = simplexml_load_string($template_item); // do stuff with item // try to add item to the root document..
// Stuck here.. can't do $root->items->addChild($item)

endfor;

I can't call addChild because it just expects a tag name and value.. you can't addChild another SimpleXMLElement.

Am I missing something here? seems really dumb that addChild can't take a SimpleXMLELement as a parameter.

Is there any other way to do this? (apart from using a different xml lib)

Ben
  • 20,737
  • 12
  • 71
  • 115
  • Possible duplicate of [Is there a way to add a PHP SimpleXMLElement to another SimpleXMLElement?](http://stackoverflow.com/questions/1157104/is-there-a-way-to-add-a-php-simplexmlelement-to-another-simplexmlelement) – Vinícius Fagundes Dec 04 '15 at 20:35

3 Answers3

69

As far as I know, you can't do it with SimpleXML because addChild doesn't make a deep copy of the element (being necessary to specify the tag name can easily be overcome by calling SimpleXMLElement::getName()).

One solution would be to use DOM instead:

With this function:

function sxml_append(SimpleXMLElement $to, SimpleXMLElement $from) {
    $toDom = dom_import_simplexml($to);
    $fromDom = dom_import_simplexml($from);
    $toDom->appendChild($toDom->ownerDocument->importNode($fromDom, true));
}

We have for

<?php
header("Content-type: text/plain");
$sxml = simplexml_load_string("<root></root>");

$n1 = simplexml_load_string("<child>one</child>");
$n2 = simplexml_load_string("<child><k>two</k></child>");

sxml_append($sxml, $n1);
sxml_append($sxml, $n2);

echo $sxml->asXML();

the output

<?xml version="1.0"?>
<root><child>one</child><child><k>two</k></child></root>

See also some user comments that use recursive functions and addChild, e.g. this one.

Artefacto
  • 96,375
  • 17
  • 202
  • 225
  • Thanks.. I was slowly coming to that conclusion. Your sxml_append function works well. – Ben Jan 24 '11 at 05:40
  • `sxml_append()` seems to be a great solution, however I cant seem to find any references to it in the documentation. – The Thirsty Ape Feb 28 '14 at 19:11
  • 2
    @Foo_Chow sxml_append() is a user created function. That's why you won't find it in the documentation. – Chad Feb 28 '14 at 19:14
  • @Chad seems I overlooked the top portion of the answer, it is a very intuitive way to use DOM-XML – The Thirsty Ape Mar 01 '14 at 00:35
  • Very nice solution, I created a subclass of `SimpleXMLElement` to add this support: https://gist.github.com/ericmulder/1e3bf4592d7c0463127d – ejazz Feb 26 '16 at 14:16
  • I was searching for it from last few days. Finally got the solution. Excellent! – Nishit Modi Mar 31 '17 at 07:15
20

You could use this function that is based in creating the children with attributes from the source:

function xml_adopt($root, $new) {
    $node = $root->addChild($new->getName(), (string) $new);
    foreach($new->attributes() as $attr => $value) {
        $node->addAttribute($attr, $value);
    }
    foreach($new->children() as $ch) {
        xml_adopt($node, $ch);
    }
}

$xml = new SimpleXMLElement("<root/>");
$child = new SimpleXMLElement("<content><p a=\"aaaaaaa\">a paragraph</p><p>another <br/>p</p></content>");

xml_adopt($xml, $child);
echo $xml->asXML()."\n";

This will produce:

<?xml version="1.0"?>
<root><content><p a="aaaaaaa">a paragraph</p><p>another p<br/></p></content></root>
Carlos C Soto
  • 1,025
  • 9
  • 11
9

The xml_adopt() example doesn't preserve namespace nodes.
My edit was rejected because it changed to much? was spam?.

Here is a version of xml_adopt() that preserves namespaces.

function xml_adopt($root, $new, $namespace = null) {
    // first add the new node
    // NOTE: addChild does NOT escape "&" ampersands in (string)$new !!!
    //  replace them or use htmlspecialchars(). see addchild docs comments.
    $node = $root->addChild($new->getName(), (string) $new, $namespace);
    // add any attributes for the new node
    foreach($new->attributes() as $attr => $value) {
        $node->addAttribute($attr, $value);
    }
    // get all namespaces, include a blank one
    $namespaces = array_merge(array(null), $new->getNameSpaces(true));
    // add any child nodes, including optional namespace
    foreach($namespaces as $space) {
      foreach ($new->children($space) as $child) {
        xml_adopt($node, $child, $space);
      }
    }
}

(edit: example added)

$xml = new SimpleXMLElement(
  '<?xml version="1.0" encoding="utf-8"?>
  <rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
  <channel></channel></rss>');

$item = new SimpleXMLElement(
  '<item xmlns:media="http://search.yahoo.com/mrss/">
    <title>Slide Title</title>
    <description>Some description</description>
    <link>http://example.com/img/image.jpg</link>
    <guid isPermaLink="false">A1234</guid>
    <media:content url="http://example.com/img/image.jpg" medium="image" duration="15">
    </media:content>
  </item>');

$channel = $xml->channel;
xml_adopt($channel, $item);

// output:
// Note that the namespace is (correctly) only preserved on the root element
'<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
  <channel>
    <item>
      <title>Slide Title</title>
      <description>Some description</description>
      <link>http://example.com/img/image.jpg</link>
      <guid isPermaLink="false">A1234</guid>
      <media:content url="http://example.com/img/image.jpg" medium="image" duration="15">
        </media:content>
    </item>
  </channel>
</rss>'
GDmac
  • 880
  • 1
  • 9
  • 28
  • I like your answer with namespaces Why you don't include namespaces for attributes? – Carlos C Soto Aug 20 '15 at 22:54
  • Carlos, that was beyond of the scope of what i needed. Also it looks somewhat difficult to handle namespaces to attributes ( http://books.xmlschemata.org/relaxng/relax-CHP-11-SECT-1.html ). – GDmac Aug 26 '15 at 01:45