Search code examples
phpxmldomsimplexml

Using SimpleXML/DOM to copy node from one XML to another via php


I am currently trying to copy nodes from one XML to another. Say we have 1.xml as follows:

///THIS IS 1.XML
<SuccessResponse>
    <Body>
        <Orders>
            <Order>
            <OrderId>123456789</OrderId>
            ...
            </Order>
            <Order>
            <OrderId>987654321</OrderId>
            ...
            </Order>
        </Orders/>
    </Body>
</SuccessResponse>

And 2.xml as follows:

<SuccessResponse>
    <Body>
        <Orders>
            <Order>
            <OrderId>123456789</OrderId>
            <OrderItems>
                <OrderItem>
                ...
                </OrderItem>
                <OrderItem>
                ...
                </OrderItem>
            </OrderItems>
            </Order>
            <Order>
            <OrderId>987654321</OrderId>
            <OrderItems>
                <OrderItem>
                ...
                </OrderItem>
                <OrderItem>
                ...
                </OrderItem>
            </OrderItems>
            </Order>
        </Orders/>
    </Body>
</SuccessResponse>

I would like to take EACH <OrderItems> node and all of its children from 2.xml and append to the correct node in 1.xml. Both XMLs will be in a specific order, so that the first tag will both have the same , and I have created an array that will contain all of these. So far, I have written this:

$idsarray = range(0, count($ids)-1); //idsarray is array of order Ids. this transforms into a list: 0, 1... etc. counting how many orders i have requested
$itemsxml = new SimpleXMLElement($orderitemresponse); //$orderitemresponse is XML string with 2.xml
$ordersxml = new SimpleXMLElement($getordersRESPONSE); //$getordersRESPONSE is XML string with 1.xml

foreach ($idsarray as $orderno) {
    $item = $itemsxml->Body->Orders->Order[$orderno]->OrderItems;
    $ordersxml->Body->Orders->Order[$orderno]->addChild('Items');
    $ordersxml->Body->Orders->Order[$orderno]->Items = $item;
}
echo $ordersxml->asXML();

This correctly appends an 'Items' where I need it, but the information sent from the last line of the foreach function doesn't appear to be functional, as the 'Items' tag is empty after running the script.

I may need to revert to DOM XML manipulation to achieve what I am trying, but I am more familiar with the SimpleXML notation so I thought I would try this way. The difficulty arises with the fact that the items for OrderId 123456789 need to be appended under the correct order. Any pointers is greatly apprecaited. I will update the post with the correct solution when it is reached. I understand that with php DOM, I could possibly use xpath to navigate to the correct correct node, however I am unsure about how to proceed with this, as the OrderId is not stored as an attribute. I have written this excerpt using the DOM manual, however I am unsure how to proceed with appending the node to the correct location. At the moment it just adds a child to the end of the document.

My question for DOM XML handling would be how to navigate to SuccessResponse->Body->Orders->OrderId with a specific text assigned to that node, such as 123456789, and then append to the node BEFORE OrderId, so append as a CHILD to Orders

$itemxml = new DOMDocument;
$itemxml->formatOutput = true;
$itemxml = DOMDocument::loadXML($orderitemresponse);
$orderxml = new DOMDocument;
$orderxml->formatOutput = true;
$orderxml = DOMDocument::loadXML($getordersRESPONSE);
$idsarray = range(0, count($ids)-1); //creates counting total orders

foreach ($idsarray as $orderno) {
    $i = $itemxml->getElementsByTagName("OrderItems")[$orderno];
    $node = $orderxml->importNode($i, true);
    $orderxml->documentElement->appendChild($node);
}
echo $orderxml->saveXML();

UPDATE: I believe I have solved this. For those who are interested:

$itemxml = new DOMDocument;
$itemxml->formatOutput = true;
$itemxml = DOMDocument::loadXML($orderitemresponse);
$orderxml = new DOMDocument;
$orderxml->formatOutput = true;
$orderxml = DOMDocument::loadXML($getordersRESPONSE);
$idsarray = range(0, count($ids)-1); //creates counting total orders

foreach ($idsarray as $orderno) {
    $i = $itemxml->getElementsByTagName("OrderItems")[$orderno];
    $node = $orderxml->importNode($i, true);
    $parent = $orderxml->getElementsByTagName("Order")[$orderno];
    $parent->appendChild($node);
}
echo $orderxml->saveXML();

Solution

  • You can make that a little more stable by using Xpath expressions to match the OrderId values.

    // bootstrap the DOMs
    $sourceDocument = new DOMDocument();
    $sourceDocument->loadXML($itemsXML);
    $sourceXpath = new DOMXpath($sourceDocument);
    
    $targetDocument = new DOMDocument();
    $targetDocument->loadXML($targetXML);
    $targetXpath = new DOMXpath($targetDocument);
    
    // iterate the target orders
    foreach ($targetXpath->evaluate('//Order') as $order) {
        // get the order id
        $orderId = $targetXpath->evaluate('string(OrderId)', $order);
        // iterate the OrderItems elements for a specific order id
        foreach($sourceXpath->evaluate('//Order[OrderId="'.$orderId.'"]/OrderItems') as $items) {
            // import and append
            $order->appendChild($targetDocument->importNode($items, TRUE));
        }
    }
    
    echo $targetDocument->saveXML();