I'm having some difficulties in changing XML Node values with PHP.
My XML is the following
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ProcessTransaction
xmlns="http://example.com">
<TransactionRequest
xmlns="http://example.com">
<Header>
<RequestType>SALE</RequestType>
<RequestMethod>SYNCHRONOUS</RequestMethod>
<MerchantInfo>
<PosName>kwstasna</PosName>
<PosID>1234</PosID>
</MerchantInfo>
</Header>
</TransactionRequest>
</ProcessTransaction>
</soap:Body>
</soap:Envelope>
And i want to change PosName and PosID.
The XML is received from a POST Request.
If i print_r($REQUEST['xml']
I get the values in text.
And what i've tried is the following
$posid = '321';
$posname = 'nakwsta';
$result = $xml->xpath("/soap:Envelope/soap:Body/ProcessTransaction/TransactionRequest/Header/MerchantInfo");
$result[0]->PosID = $posid;
$result[0]->PosName = $posname;
echo $result;
But i get an empty array Array[]
I think my mistake is in the values of <soap:Envelope for example.
Anyone that had the same issue and find out the way to solve it?
Thanks a lot for your time.
The ProcessTransaction element (and all of its child nodes) are in the "http://example.com" namespace. If you want to access them using xpath(), you'll need to register a namespace prefix:
$xml->registerXPathNamespace('ex', 'http://example.com');
You can then use the ex prefix on all relevant parts of your query
$result = $xml->xpath("/soap:Envelope/soap:Body/ex:ProcessTransaction/ex:TransactionRequest/ex:Header/ex:MerchantInfo");
The rest of your code should function correctly, see https://eval.in/916856
Consider a parameterized XSLT (not unlike parameterized SQL) where PHP passes value to the underlying script with setParameter().
As information, XSLT (sibling to XPath) is a special-purpose language designed to transform XML files. PHP can run XSLT 1.0 scripts with the XSL class. Specifically, below runs the Identity Transform to copy XML as is and then rewrites the PosName and PosID nodes. Default namespace is handled accordingly in top root tag aligned to doc prefix.
XSLT (save as .xsl file, a special well-formed .xml file)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:doc="http://example.com">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:param name="PosNameParam"/>
<xsl:param name="PosIDParam"/>
<!-- IDENTITY TRANSFORM -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- RE-WRITE PosName NODE -->
<xsl:template match="doc:PosName">
<xsl:copy>
<xsl:value-of select="$PosNameParam"/>
</xsl:copy>
</xsl:template>
<!-- RE-WRITE PosID NODE -->
<xsl:template match="doc:PosID">
<xsl:copy>
<xsl:value-of select="$PosIDParam"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
PHP
$posid = '321';
$posname = 'nakwsta';
// Load XML and XSL
$xml = new DOMDocument;
$xml->load('Input.xml');
$xsl = new DOMDocument;
$xsl->load('XSLTScript.xsl');
// Configure transformer
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// Assign values to XSLT parameters
$proc->setParameter('', 'PosNameParam', $posid);
$proc->setParameter('', 'PosIDParam', $posname);
// Transform XML source
$newXML = new DOMDocument;
$newXML = $proc->transformToXML($xml);
// Output to console
echo $newXML;
// Output to file
file_put_contents('Output.xml', $newXML);
Output
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ProcessTransaction xmlns="http://example.com">
<TransactionRequest>
<Header>
<RequestType>SALE</RequestType>
<RequestMethod>SYNCHRONOUS</RequestMethod>
<MerchantInfo>
<PosName>nakwsta</PosName>
<PosID>321</PosID>
</MerchantInfo>
</Header>
</TransactionRequest>
</ProcessTransaction>
</soap:Body>
</soap:Envelope>
Related
I need to delete elements of an XML file using PHP.
This is my XML file
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetVehiculesLocationResponse xmlns="http://google.fr/">
<GetVehiculesLocationResult>
<Location>
<idVH>001</idVH>
<date>2020-06-30T09:06:39</date>
<latitude>111111</latitude>
<longitude>11111</longitude>
</Location>
<Location>
<idVH>002</idVH>
<date>2020-04-02T13:45:51</date>
<latitude>1111111</latitude>
<longitude>111111</longitude>
</Location>
<Location>
<idVH>11111111</idVH>
<date>2020-03-24T21:49:46</date>
<latitude>1111111</latitude>
<longitude>11111111</longitude>
</Location>
</GetVehiculesLocationResult>
</GetVehiculesLocationResponse>
</soap:Body>
</soap:Envelope>
I want to delete elements (in this case Location) where idVH is a certain value (in this case 002)
I have tried this but it doesn't work
$xml1 = simplexml_load_string($result);
$items = $xml1->xpath("/soap:Envelope/soap:Body/GetVehiculesLocationResponse/GetVehiculesLocationResult/Location[idVH = 002]");
foreach ($items as $i) unset($i[0]);
echo $xml1->asXML();
The issue is that the element GetVehiculesLocationResponse defines a new default namespace, so that and the child elements are all in that new namespace...
<GetVehiculesLocationResponse xmlns="http://google.fr/">
So first register the new namespace and then use it as a prefix in the lower level elements...
$xml1->registerXPathNamespace("d", "http://google.fr/");
$items = $xml1->xpath("/soap:Envelope/soap:Body/d:GetVehiculesLocationResponse/d:GetVehiculesLocationResult/d:Location[d:idVH = '002']");
Consider XSLT (sibling to XPath), the special-purpose language designed to transform XML files and especially best suited for processing many elements. In fact, you can even pass parameters like 001 from PHP to XSLT. PHP can run XSLT 1.0 scripts with its xsl class using DOMDocument library.
XSLT
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:googl="http://google.fr/">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- DEFINE PARAM WITH DEFAULT -->
<xsl:param name="id_param">001</xsl:param>
<!-- IDENTITY TRANSFORM -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- KEEP NODES BY PARAM VALUE -->
<xsl:template match="googl:GetVehiculesLocationResult">
<xsl:copy>
<xsl:copy-of select="googl:Location[googl:idVH != $id_param]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
PHP (pass parameters to XSLT in loop)
// LOAD XML
$xml = new DOMDocument('1.0', 'UTF-8');
$xml->load($data->xmlFile);
// LOAD XSLT
$xsl = new DOMDocument('1.0', 'UTF-8');
$xsl->load('XSLT_Script.xsl');
// INITIALIZE TRANSFORMER
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
foreach($param as array('001', '002')) {
// SET PARAMETER VALUE
$proc->setParameter('', 'id_param', $param);
// TRANSFORM SOURCE
$xml = $proc->transformToDoc($xml);
}
// ECHO TO SCREEN
echo $xml->saveXML();
// SAVE TO FILE
file_put_contents($data->xmlFile, $xml);
Output
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Body>
<GetVehiculesLocationResponse xmlns="http://google.fr/">
<GetVehiculesLocationResult>
<Location>
<idVH>11111111</idVH>
<date>2020-03-24T21:49:46</date>
<latitude>1111111</latitude>
<longitude>11111111</longitude>
</Location>
</GetVehiculesLocationResult>
</GetVehiculesLocationResponse>
</soap:Body>
</soap:Envelope>
Online Demo
I am seeking some advice on the best method to modify an xml file (Garmin TCX file) uploaded to the server then return the modified version to the user without the browser timing out. I need to parse the uploaded file and add some extra elements. For example I need each of these Trackpoint tags:
<?xml version="1.0"?>
<TrainingCenterDatabase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">
<Activities>
<Activity Sport="Biking">
<Id>2018-01-08T18:15:32Z</Id>
<Lap StartTime="2017-12-16T12:43:09Z">
<TotalTimeSeconds>5023.91015625</TotalTimeSeconds>
<DistanceMeters>39999.578125</DistanceMeters>
<MaximumSpeed>15</MaximumSpeed>
<Calories>0</Calories>
<Intensity>Active</Intensity>
<Cadence>75</Cadence>
<TriggerMethod>Manual</TriggerMethod>
<Track>
<Trackpoint>
<Time>2017-12-16T12:43:10Z</Time>
<DistanceMeters>0</DistanceMeters>
<Cadence>1</Cadence>
<Extensions>
<TPX xmlns="http://www.garmin.com/xmlschemas/ActivityExtension/v2">
<Speed>0</Speed>
<Watts>1</Watts>
<Slope>-1.49</Slope>
</TPX>
</Extensions>
</Trackpoint>
.....
to become:
<Trackpoint>
<Time>2017-12-16T12:43:11Z</Time>
<DistanceMeters>0</DistanceMeters>
<Cadence>1</Cadence>
<Extensions>
<TPX xmlns="http://www.garmin.com/xmlschemas/ActivityExtension/v2">
<Speed>0</Speed>
<Watts>1</Watts>
<Slope>-1.49</Slope>
</TPX>
</Extensions>
<AltitudeMeters>106.6</AltitudeMeters>
<Position>
<LatitudeDegrees>55.02935</LatitudeDegrees>
<LongitudeDegrees>-8.140617</LongitudeDegrees>
</Position>
</Trackpoint>
I have the position data and the distance already loaded into an array - already converted from a GPX file. For each trackpoint I search the array for the distance and then append the relevant position data to the trackpoint tag.
The files I'm dealing with could be 5MB-20MB so am not sure what the best way to do this is.
I understand the DOM parser would be the easiest but also the most memory intensive and slowest.
I was thinking off using XMLREADER to parse the file and then use XMLWRITER to write all of the trackpoint element + position data to another file.
I do not have access to the server so can only test on local machine.
Any advice is greatly appreciated.
James.
Update1
I have a multi-dimensional array loaded from a csv file containing distance, lat, lon, and elevation data. I read the distance from the trackpoint tag and use it as the key for array(key=distance) then return the associated lat and lon data. I then create position, latitude, and longitude elements + text nodes and append to trackpoint tag.
Update2
Used Parfait's suggestion - use xsl to add the extra elements. Ran into issues with the default namespace. I could not select any Trackpoint elements. Fixed this using:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:e="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Identity Transform -->
<xsl:template match="#* | node()" name="identity-copy">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="e:TrainingCenterDatabase/e:Activities/e:Activity/e:Lap/e:Track/e:Trackpoint">
<xsl:copy>
<xsl:copy-of select="*"/>
<AltitudeMeters xmlns = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">xxxx</AltitudeMeters>
<Position xmlns = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">
<LatitudeDegrees xmlns = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">laaa</LatitudeDegrees>
<LongitudeDegrees xmlns = "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2">looo</LongitudeDegrees>
</Position>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Consider XSLT, the special-purpose language designed to transform XML files and at those specified file sizes should be fast rendering. PHP can run XSLT 1.0 with its php-xsl class.
And since you are iterating through a CSV files to conditional set XSLT values, consider passing values from PHP as parameters into designated placeholders on matching Distance.
XSLT (save as .xsl file)
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:doc="http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2"
exclude-result-prefixes="doc">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Initializing Parameters -->
<xsl:param name="DistanceParam"/>
<xsl:param name="AltParam"/>
<xsl:param name="LatParam"/>
<xsl:param name="LngParam"/>
<!-- Copy elements -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<!-- Walk Down Tree Levels -->
<xsl:template match="doc:TrainingCenterDatabase|doc:Activities|doc:Activity">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="doc:Id"/>
<xsl:template match="doc:Lap">
<xsl:apply-templates select="doc:Track"/>
</xsl:template>
<!-- Extract Specific TrackPoint -->
<xsl:template match="doc:Track">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="doc:Trackpoint[doc:DistanceMeters = $DistanceParam]"/>
</xsl:element>
</xsl:template>
<xsl:template match="doc:Trackpoint">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="*"/>
<AltitudeMeters><xsl:value-of select="$AltParam"/></AltitudeMeters>
<Position>
<LatitudeDegrees><xsl:value-of select="$LatParam"/></LatitudeDegrees>
<LongitudeDegrees><xsl:value-of select="$LngParam"/></LongitudeDegrees>
</Position>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
PHP
// LOAD XML
$xml = new DOMDocument('1.0', 'UTF-8');
$xml->load('Input.xml');
// LOAD XSLT
$xsl = new DOMDocument('1.0', 'UTF-8');
$xsl->load('XSLT_Script.xml');
// INITIALIZE NEW DOM TREE
$dom = new DOMDocument();
$dom->appendChild($dom->createElement('Data'));
// INITIALIZE TRANSFORMER
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// ITERATE THROUGH CSV ARRAY
foreach($csvdata as $row){
// BIND LOOP VALUES TO XSLT PARAMETERS
$proc->setParameter('', 'DistanceParam', $row[0]);
$proc->setParameter('', 'AltParam', $row[1]);
$proc->setParameter('', 'LatParam', $row[2]);
$proc->setParameter('', 'LngParam', $row[3]);
// TRANSFORM SOURCE
$newXML = $proc->transformToDoc($xml);
// IMPORT OUTPUT
$dom->importNode($newXML->Track->Trackpoint, TRUE);
}
// SAVE NEW DOM TREE TO FILE
file_put_contents('Output.xml', $dom->saveXML());
I'm trying to separate an XML file into two files, longrentals.xml and shortrentals.xml but have hit a last hurdle I'm stuck on. The following is what I would like to happen:
rentals.xml is parsed and for each instance of term = "short" the top parent "property" node of that entry is saved to shortrentals.xml.
Each instance is removed from the rentals.xml file (after extracting).
The shortrentals.xml file is saved.
The remaining entries in the original file is saved to longrentals.xml.
The XML structure is as follows:
<property>
...
<rent>
<term>short</term>
<freq>week</freq>
<price_peak>5845</price_peak>
<price_high>5845</price_high>
<price_medium>4270</price_medium>
<price_low>3150</price_low>
</rent>
...
</property>
The code I'm using is as follows:
$destination = new DOMDocument;
$destination->preserveWhiteSpace = true;
$destination->loadXML('<?xml version="1.0" encoding="utf-8"?><root></root>');
$source = new DOMDocument;
$source->load('file/rentals.xml');
$xp = new DOMXPath($source);
$destRoot = $destination->getElementsByTagName("root")->item(0);
foreach ($xp->query('/root/property/rent[term = "short"]') as $item) {
$newItem = $destination->importNode($item, true);
$destRoot->appendChild($newItem);
$item->parentNode->removeChild($item);
}
$source->save("file/longrentals.xml");
$destination->formatOutput = true;
$destination->save("file/shortrentals.xml");
This works except the output in shortrentals.xml only contains the rent node not the top level parent Property node. Also the removed entry from longrentals.xml only removes the Rent child node. So, how do I go up a level using my code please?
You can use the parentNode attribute of a DOMNode to go up a level in the structure (similar to how you do it in the removeChild code)...
foreach ($xp->query('/root/property/rent[term = "short"]') as $item) {
$property = $item->parentNode;
$newItem = $destination->importNode($property, true);
$destRoot->appendChild($newItem);
$property->parentNode->removeChild($property);
}
Alternatively, consider XSLT, the special-purpose XML transformation language, to create both such XML files without foreach loops. Here, XSLT is embedded as string but can be parsed from file like any other XML file. Assumed XML structure: <root><property><rent>...
shortrentals.xml output
// Load XML and XSL sources
$xml = new DOMDocument;
$xml->load('file/rentals.xml');
$xslstr = '<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="property[rent/term=\'short\']"/>
</xsl:copy>
</xsl:template>
<xsl:template match="property">
<xsl:copy>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>';
$xsl = new DOMDocument;
$xsl->loadXML($xslstr);
// Configure transformer
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// Transform XML source
$newXML = new DOMDocument;
$newXML = $proc->transformToXML($xml);
// Output file
file_put_contents('file/shortrentals.xml', $newXML);
longrentals.xml (Using Identity Transform and empty template to remove nodes)
// Load XML and XSL sources
$xml = new DOMDocument;
$xml->load('file/rentals.xml');
$xslstr = '<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Identity Transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Remove Non-Short Terms -->
<xsl:template match="property[rent/term=\'short\']"/>
</xsl:stylesheet>';
$xsl = new DOMDocument;
$xsl->loadXML($xslstr);
// Configure transformer
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// Transform XML source
$newXML = new DOMDocument;
$newXML = $proc->transformToXML($xml);
// Output file
file_put_contents('file/longrentals.xml', $newXML);
I have an XML file and has 300 elements. I just want to pull 10 latest records from it and create another XML file.
I will really appreciate if you can just give me some ideas about it?
PHP
$file = '/directory/xmlfile.xml';
if(!$xml = simplexml_load_file($file)){
exit('Failed to open '.$file);
} else{
print_r($xml);
// I want to do some logic here to retrieve top 10 records from file and then create another xml file with 10 records
}
XML Sample Data
<data>
<total>212</total>
<start>0</start>
<count>212</count>
<data>
<item0>
<id>123</id>
<title>abc-test1</title>
<clientContact>
<id>111</id>
<firstName>abc</firstName>
<lastName>xyz</lastName>
<email>abc#xyz.ca</email>
</clientContact>
<isOpen>1</isOpen>
<isPublic>1</isPublic>
<isJobcastPublished>1</isJobcastPublished>
<owner>
<id>222</id>
<firstName>testname</firstName>
<lastName>testlastname</lastName>
<address>
<address1>test address,</address1>
<address2>test</address2>
<city>City</city>
<state>state</state>
<zip>2222</zip>
<countryID>22</countryID>
<countryName>Country</countryName>
<countryCode>ABC</countryCode>
</address>
<email>test#test.com</email>
<customText1>test123</customText1>
<customText2>testxyz</customText2>
</owner>
<publicDescription>
<p>test info</p>
</publicDescription>
<status>test</status>
<dateLastModified>22222</dateLastModified>
<customText4>test1</customText4>
<customText10>test123</customText10>
<customText11>test</customText11>
<customText16>rtest</customText16>
<_score>123</_score>
</item0>
<item1>
...
</item1>
...
</data>
</data>
Consider XSLT, the special-purpose language designed to transform/manipulate XML to various end uses like extracting top ten <item*> tags. No need of foreach or if logic. PHP maintains an XSLT processor that can be enabled in .ini file (php-xsl).
Specifically, XSLT runs the Identity Transform to copy document as is then writes a blank template for item nodes with position over 10. XML was a bit difficult due to same parent/child <data> tags.
XSLT (save as .xsl file which is a well-formed xml)
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[substring(name(),1,4)='item' and position() > 10]"/>
</xsl:stylesheet>
PHP
$file = '/directory/xmlfile.xml';
if(!$xml = simplexml_load_file($file)) {
exit('Failed to open '.$file);
} else {
// Load XSLT
$xsl = new DOMDocument;
$xsl->load('/path/to/xsl_script.xsl');
// Configure transformer
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// Transform XML source
$newXML = new DOMDocument;
$newXML = $proc->transformToXML($xml);
// Echo new XML tree
echo $newXML;
// Save output to file
$xmlfile = '/path/to/output.xml';
file_put_contents($xmlfile, $newXML);
}
I have to clone an XML node and its childs and append it to a new XML in a specifics tag.
Ie:
Source XML:
<root>
<element>
<back>
<item1>ABC</item1>
<item2>DEF</item2>
<more>
<moreitem>GHI</moreitem>
</more
</back>
</element>
</root>
Destination XML:
<root>
<base1>
<item1>FOO</item1>
<item2>BAR</item2>
<base2>
**<back>From source XML and all its childs here</back>**
</base2>
</base1>
<root>
DOMXpath::evaluate() allows you to fetch nodes using Xpath expressions. DOMDocument::importNode() duplicates a node and imports a node into a target document. DOMNode::cloneNode() create a duplicate of node to add in the same document. DOMNode::appendChild() allows you to append the imported/cloned node.
$source = <<<'XML'
<root>
<element>
<back>
<item1>ABC</item1>
<item2>DEF</item2>
<more>
<moreitem>GHI</moreitem>
</more>
</back>
</element>
</root>
XML;
$target = <<<'XML'
<root>
<base1>
<item1>FOO</item1>
<item2>BAR</item2>
<base2>
</base2>
</base1>
</root>
XML;
$sourceDocument = new DOMDocument();
$sourceDocument->loadXml($source);
$sourceXpath = new DOMXpath($sourceDocument);
$targetDocument = new DOMDocument();
$targetDocument->loadXml($target);
$targetXpath = new DOMXpath($targetDocument);
foreach ($targetXpath->evaluate('/root/base1/base2[1]') as $targetNode) {
foreach ($sourceXpath->evaluate('/root/element/back') as $backNode) {
$targetNode->appendChild($targetDocument->importNode($backNode, TRUE));
}
}
echo $targetDocument->saveXml();
Output:
<?xml version="1.0"?>
<root>
<base1>
<item1>FOO</item1>
<item2>BAR</item2>
<base2>
<back>
<item1>ABC</item1>
<item2>DEF</item2>
<more>
<moreitem>GHI</moreitem>
</more>
</back>
</base2>
</base1>
</root>
Of course you can use XSLT, the native programming language to restructure XML documents to any nuanced needs. Specifically here, you require pulling XML content from an external source XML file. And PHP like other general purpose languages (Java, C#, Python, VB) maintain libraries for XSLT processing.
XSLT (save as .xsl or .xslt file to be used in PHP below and be sure Source and Destination XML files are in same directory)
<?xml version="1.0" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<!-- Identity Transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="back">
<back>
<xsl:copy-of select="document('Source.xml')"/>
</back>
</xsl:template>
</xsl:transform>
PHP (loading XML and XSL files externally but can be embedded as string)
$destinationdoc = new DOMDocument();
$doc1->load('Destination.xml');
$xsl = new DOMDocument;
$xsl->load('XSLTScript.xsl');
// Configure the transformer
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// Transform XML source
$newXml = $proc->transformToXML($doc1);
// Save output to file
$xmlfile = 'FinalOutput.xml';
file_put_contents($xmlfile, $newXml);
OUTPUT (using your above posted Source and Destination xml)
<?xml version="1.0" encoding="UTF-8"?>
<root>
<base1>
<item1>FOO</item1>
<item2>BAR</item2>
<base2>
<back>
<root>
<element>
<back>
<item1>ABC</item1>
<item2>DEF</item2>
<more>
<moreitem>GHI</moreitem>
</more>
</back>
</element>
</root>
</back>
</base2>
</base1>
</root>
This is an easy way to do this:
$src = new DOMDocument();
$dst = new DOMDocument();
$src->loadXML($src_xml);
$dst->loadXML($dst_xml);
$back = $src->getElementsByTagName('back')->item(0);
$base = $dst->getElementsByTagName('base2')->item(0);
$base->appendChild( $dst->importNode( $back, true ) );
echo $dst->saveXML();