0

I have 2 XML files.

I am trying to get data from XML File B and add it to XML File A. I want to first add an empty child to XML File A, and then in that child, I want to add data from XML File B (in this case, the animal child element). I want to achieve this through either SimpleXML or DOMDocument. I have been stuck on this. Any help would be great!

personinfo.xml < XML File A

<personinfo>
    <personN number="1">
        <personL letter="A">
            <fullname>
                <firstname>Summer</firstname>
                <lastname>Smith</lastname>
            </fullname>
            <favourites>
                <color>pink</color>
            </favourites>
        </personL>  
    </personN>

    <personN number="2">
        <personL letter="B">
            <fullname>
                <firstname>Autumn</firstname>
                <lastname>Smith</lastname>
            </fullname>
            <favourites>
                <color>blue</color>
            </favourites>
        </personL>  
    </personN>
</personinfo>

favouritesinfo.xml < XML File B

<favouritesinfo>
    <personN number="1">
            <animal>cat</animal>
    </personN>
    <personN number="2">
            <animal>dog</animal>
    </personN>
</favouritesinfo>

Is this possible? If so, am I able to do this via XML DOM Document or SimpleXML?

I attempted doing it this way via a separate PHP file but it does not work/do anything with SimpleXML

$personXML = simplexml_load_file("personinfo.xml");
$faveXML = simplexml_load_file("favouritesinfo.xml");

$animal=$faveXML->personN[0]->animal;

$newFave=$personXML->personN->personL[0]->addChild("favourites");
$favourites->addChild($animal);

$personXML->asXML();

Desired output

<personinfo>
    <personN number="1">
        <personL letter="A">
            <fullname>
                <firstname>Summer</firstname>
                <lastname>Smith</lastname>
            </fullname>
            <favourites>
                <color>pink</color>
            </favourites>
            <favourites>
                <color>cat</color>
            </favourites>
        </personL>  
    </personN>

    <personN number="2">
        <personL letter="B">
            <fullname>
                <firstname>Autumn</firstname>
                <lastname>Smith</lastname>
            </fullname>
            <favourites>
                <color>blue</color>
            </favourites>
            <favourites>
                <color>dog</color>
            </favourites>
        </personL>  
    </personN>
</personinfo>
Kesha XX
  • 27
  • 3

2 Answers2

0

This is easier in DOM because you have access to the nodes and can copy them from one document to another. Use Xpath to fetch parts of a document.

// load the XML into DOM document and create Xpath instances
$personsDocument = new DOMDocument;
$personsDocument->preserveWhiteSpace = FALSE;
$personsDocument->loadXML($xmlPersons);
$personsXpath = new DOMXpath($personsDocument);

$favoritesDocument = new DOMDocument;
$favoritesDocument->preserveWhiteSpace = FALSE;
$favoritesDocument->loadXML($xmlFavorites);
$favoritesXpath = new DOMXpath($favoritesDocument);

// iterate the personN nodes in the favorties XML
foreach ($favoritesXpath->evaluate('.//personN') as $personFavorites) {
    $personId = (int)$personFavorites->getAttribute('number');
    // find the matching personL node in the persons XML
    foreach ($personsXpath->evaluate('.//personN[@number='.$personId.']/personL') as $personNode) {
        // check if it has a 'favourites' child already
        $favoritesNode = $personsXpath->evaluate('favourites', $personNode)->item(0);
        if (!$favoritesNode) {
            // otherwise create one
            $favoritesNode = $personNode->appendChild(
                $personsDocument->createElement('favourites')
            );
        }
        // now iterate the different favorites of a person
        foreach ($personFavorites->childNodes as $favorite) {
            // import and add
            $favoritesNode->appendChild($personsDocument->importNode($favorite, TRUE));
        }
    }
}

$personsDocument->formatOutput = TRUE;
echo $personsDocument->saveXML();

Output:

<?xml version="1.0"?>
<personinfo>
  <personN number="1">
    <personL letter="A">
      <fullname>
        <firstname>Summer</firstname>
        <lastname>Smith</lastname>
      </fullname>
      <favourites>
        <color>pink</color>
        <animal>cat</animal>
      </favourites>
    </personL>
  </personN>
  <personN number="2">
    <personL letter="B">
      <fullname>
        <firstname>Autumn</firstname>
        <lastname>Smith</lastname>
      </fullname>
      <favourites>
        <color>blue</color>
        <animal>dog</animal>
      </favourites>
    </personL>
  </personN>
</personinfo>

Adding a favourites parent for each favorite does not make much sense. So I added a check to my example. If you really want the parent nodes, you can remove it:

foreach ($personsXpath->evaluate('.//personN[@number='.$personId.']/personL') as $personNode) {
    //create 'favourites' child
    $favoritesNode = $personNode->appendChild(
        $personsDocument->createElement('favourites')
    );
    foreach ($personFavorites->childNodes as $favorite) {
    //...
ThW
  • 19,120
  • 3
  • 22
  • 44
0

From what I know pure SimpleXMLElement does not allow importing nodes from another document out of the box, but DOMDocument does.

Luckily both work well together, an SimpleXMLElement can be "turned" into a DOMNode and vice-versa.

This is what the dom_import_simplexml() function is for.

Here an example which relates to the example you have in your question, but instead of using SimpleXML::addChild() importing into DOM and then adding the (again imported) child to the DOMElement node:

$parent = dom_import_simplexml($newFave); # [1]
$parent->appendChild( # [2]
    $parent->ownerDocument->importNode(dom_import_simplexml($animal), true) # [3]
);

echo $personXML->asXML(); # [4]
  1. create a DOM node for the <favourites> parent element
  2. append the child on the DOM parent node
  3. create and import the animal favourite DOM node. The node needs to be imported to the parent document first because it is part of a different DOMDocument and adding child nodes is only possible if the nodes are part of the same document.
  4. output with the SimpleXMLElement

The result (in ugly print) then is like:

...
        <personL letter="A">
            <fullname>
                <firstname>Summer</firstname>
                <lastname>Smith</lastname>
            </fullname>
            <favourites>
                <color>pink</color>
            </favourites>
        <favourites><animal>cat</animal></favourites></personL>
               ^       ^
               |       |
               |   $animal = $faveXML->personN[0]->animal;
               |   $parent->ownerDocument
               |       ->importNode(dom_import_simplexml($animal), true)
               |
               |

   $newFave = $personXML->personN->personL[0]->addChild("favourites");
...

I hope this is easy to follow. This is a little bit what @ThW suggests in his answer but using DOMDocument only for the part where SimpleXMLElement can't do the job.

Also I like his suggestion to no add the favourites element again and again. So for a more dynamic approach to add the the persons' favorites by the persons number across those two XML documents, let's add some Xpath to the mix:

foreach ($faveXML->xpath('./personN[*]') as $person) {
    foreach ($personXML->xpath(
        sprintf('./personN[@number = "%d"]/*/favourites[1]', $person['number'])
    ) as $favourites) {
        $parent = dom_import_simplexml($favourites);
        $parent->appendChild(
            $parent->ownerDocument->importNode(dom_import_simplexml($person->children()[0]), true)
        );
    }
};
hakre
  • 193,403
  • 52
  • 435
  • 836