Search code examples
phpxmlsimplexmldomdocument

Merge selected nodes of the two XML files


Let's say I have two XML files which has the same structure. I need to create new XML file with same structure which contain the selected nodes from initial two XML files.

I will try to explain again with bellow example.

input1.xml :

<parent>
    <item id="100">
         ...
    </item>
    <item id="101">
         ...
    </item>
    <item id="102">
         ...
    </item>
    <item id="103">
         ...
    </item>
</parent> 

input2.xml :

<parent>
    <item id="200">
         ...
    </item>
    <item id="201">
         ...
    </item>
    <item id="202">
         ...
    </item>
    <item id="203">
         ...
    </item>
</parent> 

Now I need select the nodes which have id 100,103 from input1.xml and 202,203 from input2.xml. Also it should be in the order of 203,100,103,202 and final result would be look like bellow.

result.xml :

<parent>
    <item id="203">
         ...
    </item>
    <item id="100">
         ...
    </item>
    <item id="103">
         ...
    </item>
    <item id="202">
         ...
    </item>
</parent> 

It is not necessary to create new file, if I can edit the input2.xml in the way it looks like result.xml, that would be the ideal solution.

What I have done so far :

My approach is first delete the nodes from the input2.xml and then add the nodes to that from input1.xml. I have following function to delete nodes from input2.xml file.

eg: call delete_record(200,'input2.xml','result.xml') can delete the node 200 and I can repeat it in the similar manner.

function delete_record($id, $input, $output){
    $xml = new DOMDocument();
    $xml->load($input);

    $deals = $xml->getElementsByTagName('item');
    foreach ($deals as $deal) {
        $deal_id = $deal->getElementsByTagName('id')->item(0)->nodeValue;
        if ($deal_id == $id) {
            $id_matched = true;
            $deal->parentNode->removeChild($deal);
            break;
        }
    }
    if ($id_matched == true) {
        if ($xml->save($output)) {
            return true;
        }
    }
}

But still I am struggling to find a way how to add nodes to the same result.xml file and how to make the order.

Any kind of help would be highly appreciated.


Solution

  • There is no need to mess with deletion, just do what you need to. Pick nodes by id from both files, and put it in order:

    // merge all nodes by Id
    function getNodesById($id, ...$xpaths) {
      $result = [];
      foreach($xpaths as $xpath) {
        foreach($xpath->query("//item[@id='$id']") as $node) {
          $result[] = $node;
        }
      }
      return $result;
    }
    
    
    // load source documents
    $xml1 = new DOMDocument();
    $xml1->load(....);
    $xpath1 = new DomXpath($xml1);
    
    $xml2 = new DOMDocument();
    $xml2->load(....);
    $xpath2 = new DomXpath($xml2);
    
    // create result document
    $result = new DOMDocument();
    $parent = $result->createElement("parent");
    $result->appendChild($parent);
    
    // populate result document with nodes:
    foreach([203, 100, 103, 202] as $id) {
      $nodesToInsert = getNodesById($id, xpath1, xpath2);
      if (count($nodesToInsert) !== 1) {
        // resolve conflicts, if any
        throw new Exception("Id $id is not found or not unique.");
      }
      $parent->appendChild($result->importNode($nodesToInsert[0], true));
    }
    
    // or save it to a file
    echo $result->saveXml();