0

I have 3 xml files with same structure. What I need to do is merge them together via php. Take a look at my example file below 1.xml

<data>
<product productsku="ABC1" price="550000" pricesale="" hn="1"></product>
<product productsku="ABC2" price="" pricesale="" hn="0"></product>
</data>

2.xml

<data>
<product productsku="ABC2" price="550000" pricesale="" dn="2"></product>
<product productsku="ABC3" price="" pricesale="" dn="0"></product>
</data>

3.xml

<data>
<product productsku="ABC3" price="550000" pricesale="" gn="3"></product>
<product productsku="ABC4" price="" pricesale="" gn="0"></product>
</data>

I would like to get the following result:

<data><product productsku="ABC1" price="550000" pricesale="" hn="1"></product>
<product productsku="ABC2" price="550000" pricesale="" dn="2" hn="0"></product>
<product productsku="ABC3" price="550000" pricesale="" dn="0" gn="3"></product>
<product productsku="ABC4" price="" pricesale="" gn="0"></product>
</data>

The code that I am trying

    <?php 
$xml1 = file_get_contents('1.xml');
$xml2 = file_get_contents('2.xml');
$targetDom = new DOMDocument();
$targetDom->loadXml($xml1);
$targetXpath = new DOMXpath($targetDom);

$addDom = new DOMDocument();
$addDom->loadXml($xml2);
$addXpath = new DOMXpath($addDom);

// merge attributes of product elements depending on productsku
foreach ($targetXpath->evaluate('//product[@productsku]') as $product) {
  $productsku = $product->getAttribute('productsku');
  foreach ($addXpath->evaluate('//product[@productsku='.$productsku.']/@*') as $attribute) {
    if (!$product->hasAttribute($attribute->name)) {
      $product->setAttribute($attribute->name, $attribute->value);
    }
  }
}

// copy products elements that are not in target dom
foreach ($addXpath->evaluate('//product[@productsku]') as $product) {
  $productsku = $product->getAttribute('productsku');
  if ($targetXpath->evaluate('count(//product[@productsku='.$productsku.'])') == 0) {
    $targetDom->documentElement->appendChild(
      $targetDom->importNode($product)
    );
  }
}
echo $targetDom->saveXml();

I tried the above, it works fine if the SKU is numeric. But my SKU or ID is not a number. And I have 3 xml files

I tried to find the solution on stackoverflow. But all is not as I expected. I'm really not good at this. Please help me through a php snippet.

Phan Vũ
  • 27
  • 5
  • 1
    Please [edit your question](https://stackoverflow.com/posts/74625741/edit) to show us your PHP code that you have tried. – kmoser Nov 30 '22 at 10:25
  • 2
    Unlike numbers, string literals have to be quoted in Xpath expressions. So `'//product[@productsku='.$productsku.']/@*'` has to be changed to `'//product[@productsku="'.$productsku.'"]/@*'` – ThW Nov 30 '22 at 10:57
  • this solved the problem I was having. Many thanks for your contribution – Phan Vũ Nov 30 '22 at 12:24

1 Answers1

0

I found the xml_adopt function here: PHP - SimpleXML - AddChild with another SimpleXMLElement

Then it's a matter of loading each file, keeping track of products and overwriting them if the product has only been seen with no price, then adopting them into a single SimpleXMLElement.

<?php

/*

Question Author: Phan Vũ
Question Answerer: Jacob Mulquin
Question: Merge SimpleXML elements by SKU attribute
URL: https://stackoverflow.com/questions/74625741/merge-simplexml-elements-by-sku-attribute
Tags: php, xml

*/

// https://stackoverflow.com/a/11727581/1427345
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('<data></data>');

$files = ['1.xml', '2.xml', '3.xml'];
$to_adopt = [];

foreach ($files as $file) {
    $load = simplexml_load_file($file);

    foreach ($load->product as $product) {
        $sku = (string) $product->attributes()['productsku'];

        if (!isset($to_adopt[$sku])) {
            $to_adopt[$sku] = $product;
        } else {

            $price = (string) $to_adopt[$sku]->attributes()['price'];
            if (empty($price)) {
                $to_adopt[$sku]['price'] = $price;
            }

            $existing_attributes = ((array) $to_adopt[$sku]->attributes())['@attributes'];
            foreach ($product->attributes() as $name => $attribute) {
                if (!in_array($name, $existing_attributes)) {
                    $to_adopt[$sku][$name] = $attribute;
                }
            }
        }
    }
}

foreach ($to_adopt as $adopt) {
    xml_adopt($xml, $adopt);
}

file_put_contents('output.xml', $xml->asXML());

Yields:

<?xml version="1.0"?>
<data>
<product productsku="ABC1" price="550000" pricesale="" hn="1"/>
<product productsku="ABC2" price="550000" pricesale="" hn="0" dn="2"/>
<product productsku="ABC3" price="550000" pricesale="" dn="0" gn="3"/>
<product productsku="ABC4" price="" pricesale="" gn="0"/>
</data>
Jacob Mulquin
  • 3,458
  • 1
  • 19
  • 22