Merge two xml files based on common attribute

2019-03-02 11:56发布

问题:

I have two xml files with a similar structure, and I need to merge them based on a common attribute. To be more explicit, here are two samples:

file1.xml

 <Products>
 <record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
 </Products>

file2.xml

 <Productprices>
 <record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
 </Productprices>

The common attribute in both files is ProductId. I need to merge all the attributes so that the combined file would look like this:

 <Products>
 <record ProductId="366" ProductName="Test" ProductCategory="Categ1" ProductPrice="10" ProductVAT="24"></record>
 </Products>

Unfortunately, all I have managed to do so far is simply merge the two files, the merged file looks like this:

 <Products>
 <record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
 <record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
 </Products>

This is the PHP code I used:

 $doc1 = new DOMDocument();
 $doc1->load('file1.xml');
 $doc2 = new DOMDocument();
 $doc2->load('file2.xml');
 $res1 = $doc1->getElementsByTagName('Products')->item(0);
 $items2 = $doc2->getElementsByTagName('record');
 for ($i = 0; $i < $items2->length; $i ++) {
 $item2 = $items2->item($i);
 $item1 = $doc1->importNode($item2, true);
 $res1->appendChild($item1);
 }
 $doc1->save('file1.xml');

Is there any way I can merge all the attributes into one record based on the common ProductId by using DomDocument? I would rather not go into XSLT.

Any help will be greatly appreciated.

Thanks in advance.

回答1:

I use Xpath to fetch nodes and values from DOM. In your case I see two tasks.

One task to iterate all record elements in on document, fetch the attributes of the matching element from the second document and copy the attributes.

The other task to iterate all record elements in the second document and add them to the first if here is no element with that ProductId.

$xmlOne = <<<'XML'
<Products>
 <record ProductId="366" ProductName="Test" ProductCategory="Categ1"></record>
</Products>
XML;

$xmlTwo = <<<'XML'
<Productprices>
 <record ProductId="366" ProductPrice="10" ProductVAT="24"></record>
 <record ProductId="444" ProductPrice="23" ProductVAT="32"></record>
</Productprices>
XML;

$targetDom = new DOMDocument();
$targetDom->loadXml($xmlOne);
$targetXpath = new DOMXpath($targetDom);

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

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

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

echo $targetDom->saveXml();


回答2:

You can use the attribute() function of SimpleXML

$xml = simplexml_load_file($filename);
foreach($xml->Products->record->attributes() as $attribute => $value) {
    //do something
}