Search code examples
domdocumentphp-7clonenode

PHP XML DOM Document move sub-sub-nodes within sub-node


I have an xml like this:

<?xml version="1.0" encoding="UTF-8"?>
<OrderListResponse>
  <OrderListResponseContainer>
    <DateFrom>2018-07-01T00:00:00+00:00</DateFrom>
    <DateTo>2018-07-19T00:00:00+00:00</DateTo>
    <Page>1</Page>
    <TotalNumberOfPages>4</TotalNumberOfPages>
    <Orders>
      <Order>
        <OrderID>158772</OrderID>
        <Customer>
          <Name><![CDATA[John Smith]]></Name>
          <StreetAddress><![CDATA[33, Sunset Boulevrd]]></StreetAddress>
        </Customer>
        <Delivery>
          <Name><![CDATA[John Smith]]></Name>
          <StreetAddress><![CDATA[47, Rodeo Drive]]></StreetAddress>
        </Delivery>
        <Billing>
          <Name><![CDATA[John Smith]]></Name>
          <StreetAddress><![CDATA[33, Sunset Boulevrd]]></StreetAddress>
        </Billing>
        <Payment>
          <Module>paypal</Module>
          <TransactionID/>
        </Payment>
        <DatePurchased>2018-07-01 16:30:42</DatePurchased>
        <DateLastModified>2018-07-02 21:08:28</DateLastModified>
        <CheckoutMessage><![CDATA[]]></CheckoutMessage>
        <Status>cancelled</Status>
        <Currency>EUR</Currency>
        <Products>
          <Product>
            <MxpID>44237</MxpID>
            <SKU>IRF 8707TR</SKU>
            <Quantity>3</Quantity>
            <Price>2.46</Price>
          </Product>
        </Products>
        <Total>
          <SubTotal>7.38</SubTotal>
          <Shipping>2.7</Shipping>
          <Cod>0</Cod>
          <Insurance>0</Insurance>
          <Tax>1.62</Tax>
          <Total>11.7</Total>
        </Total>
      </Order>
      <Order>...</Order>
     </Orders>
  </OrderListResponseContainer>
</OrderListResponse>

and although surely there a better way to do it, to parse all orders I build a routine like this:

$xmlDoc = new DOMDocument();
$xmlDoc->preserveWhiteSpace = false;
$xmlDoc->loadXML($response);
$xpath = new DOMXPath($xmlDoc);
$rootNode = $xpath->query('//OrderListResponseContainer/Orders')->item(0);
foreach($rootNode->childNodes as $node)
{
    foreach($node->childNodes as $subnode)
    {
        Process User
        foreach($subnode->childNodes as $subsubnode)
        {
            foreach($subsubnode->childNodes as $subsubsubnode)
            {
                Process Products and Sales
            }
        }
    }
}

**** ADDED ****
I use the nested loops to create one xml for each product (each xml contains details about the buyer, the item and the sale) and then this xml is passed to a Stored Procedure to generate the user/item/sale records: For several reason I cannot bulky import Users first, then Items and then Sales but while building the sale xml I need some details from the Total Node and one way to get them is to move Total Node on top of the XML, but clearly within the Order Node
**** ADDED ****

I need to access some Total subnodes before processing Products The only solution I found is to move Total node at the beginning, but although many attempts, I've not been able to succeed: The idea was to clone the totalNode and to appendbefore the OrderID Node

The problem is that I need to work on subdocuments and select the node to clone from a node itself, while all example I found do clone the full DocumentElement

perhaps an easier solution can be achieved using XSLT?

Can suggest a solution?


Solution

  • I don't completely understand what you are trying to say about the cloning part. Perhaps you can edit your question and clarify what you mean.

    However, about accessing the Total nodes... you could simply use XPath for this as well.

    $xmlDoc = new DOMDocument();
    $xmlDoc->preserveWhiteSpace = false;
    $xmlDoc->loadXML($response);
    $xpath = new DOMXPath($xmlDoc);
    
    // first, let's fetch all <Order> elements
    $orders = $xpath->query('//OrderListResponseContainer/Orders/Order');
    
    // loop through all <Order> elements
    foreach( $orders as $order ) {
      /*
        There's all sorts of ways you could convert <Total> to something useful
      */
    
      // Example 1.
      // fetch <Total> that is a direct child (./) of our context node (second argument) $order
      $total = $xpath->query( './Total', $order )->item( 0 );
    
      // then do something like
      $subTotal = $total->getElementsByTagName( 'SubTotal' )->item( 0 );
      $shipping = $total->getElementsByTagName( 'Shipping' )->item( 0 );
      // ... etc. for each child node of <Total>
    
      // or perhaps simply convert it to a SimpleXMLElement
      $total = simplexml_import_dom( $total );
      var_dump( $total );
      // and then access the values like this:
      $total->SubTotal;
      $total->Shipping;
      // ... etc.
    
    
      // Example 2.1
      // fetch all children of <Total> into an array
      $total = [];
      foreach( $xpath->query( './Total/*', $order ) as $totalNode ) {
        $total[ $totalNode->nodeName ] = $totalNode->textContent;
      }
      var_dump( $total );
    
      // Example 2.2
      // fetch all children of <Total> into a stdClass object
      $total = new \stdClass;
      foreach( $xpath->query( './Total/*', $order ) as $totalNode ) {
        $total->{ $totalNode->nodeName } = $totalNode->textContent;
      }
      var_dump( $total );
    
      /*
        Now, after this you can create and process the Customer and Products data
        in a similar fashion as I've shown how to process the Total data above
      */
    }