PHP XML DOM Uncaught exception 'DOMException' with message 'Wrong Document Error' - php

I am trying to learn XML and I know this is a problem with not properly importing the Nodes. But I can't quite figure it out. I've been looking around and most people don't have multiple child elements like I do with the departments.
Here is my XML structure:
<SOT>
<DEPARTMENT name="Aviation Technology" id="AT">
<EMPLOYEE type="Faculty">
<LOGIN>jdoe1</LOGIN>
<NAME>John Doe</NAME>
</EMPLOYEE>
<EMPLOYEE type="Faculty">
<LOGIN>jdoe2</LOGIN>
<NAME>Jane Doe</NAME>
</EMPLOYEE>
<EMPLOYEE type="Faculty">
<LOGIN>jdoe3</LOGIN>
<NAME>Joe Doe</NAME>
</EMPLOYEE>
</DEPARTMENT>
<DEPARTMENT name="Building and Construction Management" id="BCM">
</DEPARTMENT>
<DEPARTMENT name="Computer Graphics Technology" id="CGT">
</DEPARTMENT>
</SOT>
I understand that SOT is my root element and that departments are "children" of SOT and each department has multiple employee "children". The problem I run into is when I am trying to add a new employee to a certain department. When I try $departmentArray->item($i)->appendChild($employee); I get the Wrong Document Error.
I'm using this PHP code to try and append the child to the departmentNode
<?php
//grab form data
$username = $_POST['username'];
$employeeName = $_POST['employeeName'];
$department = $_POST['department'];
//create new DOMDocument to hold current XML data
$doc = new DOMDocument();
$doc->load("test.xml");
$xpath = new DOMXpath($doc);
//create our new DOMDocument for combining the XML data
$newDoc = new DOMDocument();
$newDoc->preserveWhiteSpace = false;
//create School of Tech Node and append to new doc
$sotElement = $newDoc->createElement("SOT");
$newDoc->appendChild($sotElement);
$root = $newDoc->documentElement;
//grab the department Nodes
$departmentArray = $doc->getElementsByTagName("DEPARTMENT");
//create a new employee and set attribute to faculty
$employee = $newDoc->createElement("EMPLOYEE");
$employee->setAttribute("type", "Faculty");
//counters (might use them later for ->item(counter) function
$indexCounter = 0;
$i = 0;
foreach($departmentArray as $departmentNode){
if(strcmp($departmentNode->getAttribute('name'),$department) == 0){//check if departments match
//create login element
$loginNode = $newDoc->createElement("LOGIN");
$loginNode->appendChild($newDoc->createTextNode($username));
$employee->appendChild($loginNode);
//create name node
$nameNode = $newDoc->createElement("NAME");
$nameNode->appendChild($newDoc->createTextNode($employeeName));
$employee->appendChild($nameNode);
//append employee onto department node
//$departmentArray->item($i) = $doc->importNode($departmentArray->item($i), true);
$departmentArray->item($i)->appendChild($employee);
//set index of department array (possibly used for appending later)
$indexCounter = $i;
}
$i++;
}
#######################################
/*Write out data to XML file */
#######################################
//$departmentArray = $doc->getElementsByTagName("DEPARTMENT");
foreach($departmentArray as $departmentNode){
$tempNode = $newDoc->importNode($departmentNode, true);
/*if(strcmp($departmentNode->getAttribute('name'),$department) == 0){
$sotElement->appendChild($employee);
}*/
$sotElement->appendChild($tempNode);
}
$newDoc->formatOutput = true;
$newDoc->save("test2.xml");
?>
Any help explaining how to properly import all the department nodes to be able to append onto them would be greatly appreciated. I've tried using arrays.

You need to import any node to append it to another document:
$departmentArray->item($i)->appendChild( $doc->importNode( $employee, true ) );

I'm pretty sure that this is happening because you are trying to append an element from a different document into your output document.
I found this code in a comment on php's site for DOMNode::cloneNode which might be what you want.
<?php
$dom1->documentElement->appendChild(
$dom1->importNode( $dom2->documentElement, true )
);
?>
Alternatively, you could look at exporting the node's XML and reimporting it into a DOMDocumentFragment, but I'd have to experiment to know for sure.

Related

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();
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();

Modify XML in PHP

I have the xml below
<?xml version="1.0" encoding="UTF-8"?>
<!--Sample XML file generated by XMLSpy v2013 (http://www.altova.com)-->
<ftc:FATCA_OECD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ftc="urn:oecd:ties:fatca:v2" xmlns:sfa="urn:oecd:ties:stffatcatypes:v2" version="2.0" xsi:schemaLocation="urn:oecd:ties:fatca:v2 FatcaXML_v2.0.xsd">
<ftc:MessageSpec>
<sfa:SendingCompanyIN>S519K4.99999.SL.392</sfa:SendingCompanyIN>
<sfa:TransmittingCountry>JP</sfa:TransmittingCountry>
<sfa:ReceivingCountry>US</sfa:ReceivingCountry>
<sfa:MessageType>FATCA</sfa:MessageType>
<sfa:MessageRefId>DBA6455E-8454-47D9-914B-FEE48E4EF3AA</sfa:MessageRefId>
<sfa:ReportingPeriod>2016-12-31</sfa:ReportingPeriod>
<sfa:Timestamp>2017-01-17T09:30:47Z</sfa:Timestamp>
<ftc:SendingCompanyIN>testing</ftc:SendingCompanyIN></ftc:MessageSpec>
<ftc:FATCA>
<ftc:ReportingFI>
<sfa:ResCountryCode>JP</sfa:ResCountryCode>
<sfa:TIN>S519K4.99999.SL.392</sfa:TIN>
<sfa:Name>Bank of NN</sfa:Name>
<sfa:Address>
<sfa:CountryCode>JP</sfa:CountryCode>
<sfa:AddressFree>123 Main Street</sfa:AddressFree>
</sfa:Address>
<ftc:DocSpec>
<ftc:DocTypeIndic>FATCA1</ftc:DocTypeIndic>
<ftc:DocRefId>S519K4.99999.SL.392.50B80D2D-79DA-4AFD-8148-F06480FFDEB5</ftc:DocRefId>
</ftc:DocSpec>
</ftc:ReportingFI>
<ftc:ReportingGroup>
<ftc:NilReport>
<ftc:DocSpec>
<ftc:DocTypeIndic>FATCA1</ftc:DocTypeIndic>
<ftc:DocRefId>S519K4.99999.SL.392.CE54CA78-7C31-4EC2-B73C-E387C314F426</ftc:DocRefId>
</ftc:DocSpec>
<ftc:NoAccountToReport>yes</ftc:NoAccountToReport>
</ftc:NilReport>
</ftc:ReportingGroup>
</ftc:FATCA>
</ftc:FATCA_OECD>
I want to change node value, sfa:TIN and save the xml in a new file. How can this be accomplished in PHP? I got examples but none used namespaces.
One way you could do this is using DOMDocument and DOMXPath and find your elements using for example an xpath expression which will find the 'TIN' elements in the sfa namespace.
/ftc:FATCA_OECD/ftc:FATCA/ftc:ReportingFI/sfa:TIN
To update the value of the first found elemement you could take the first item from the DOMNodeList which is returned by query.
$doc = new DOMDocument();
$doc->loadXML($data);
$xpath = new DOMXPath($doc);
$res = $xpath->query("/ftc:FATCA_OECD/ftc:FATCA/ftc:ReportingFI/sfa:TIN");
if ($res->length > 0) {
$res[0]->nodeValue = "test";
}
$doc->save("yourfilename.xml");
Demo
You can use the following solution, using DOMDocument::getElementsByTagNameNS:
<?php
$dom = new DOMDocument();
$dom->load('old-file.xml');
//get all TIN nodes.
$nodesTIN = $dom->getElementsByTagNameNS('urn:oecd:ties:stffatcatypes:v2', 'TIN');
//check for existing TIN node.
if (count($nodesTIN) === 1) {
//update the first TIN node.
$nodesTIN->item(0)->nodeValue = 'NEWVALUE_OF_TIN';
}
//save the file to a new one.
$dom->save('new-file.xml');

PHP To Edit Existing XML Entry

Ok, please bear with me as I explain this app. The first view shows a tableview, where each row is a 'request' that someone has made, also using the app. The requests are actually a parsed xml file that looks something like this:
<item>
<title>Request</title>
<name>Name</name>
<Responses>0</Responses>
</item>
Using the script listed underneath this paragraph, I am able to run some code in iOS that will take text from text-boxes in the app, and append the XML with a new item set.
<?php
$firstName = $_POST['firstName'];
$lastName = $_POST['lastName'];
$request = $_POST['request'];
$anon = $_POST['anon'];
$pubDate = $_POST['pubDate'];
$loc = $_POST['loc'];
//This line will load the XML file
$xml = simplexml_load_file("URL.xml") or die("Not loaded!\n");
//print_r($xml);
//This line gets the channel element (from an array returned by the xpath method)
$channel = $xml->xpath('//channel');
$channel = $channel[0];
//print_r($channel);
$person = $channel->addChild("item");
$person->addChild("first_name", $firstName);
$person->addChild("last_name", $lastName);
$person->addChild("title", $request);
$person->addChild("date", $pubDate);
$person->addChild("anonymous", $anon);
$person->addChild("responses", "0");
$person->addChild("location", $loc);
//This next line will overwrite the original XML file with new data added
$xml->asXML("Test.xml");
?>
The actual XML has much more listed than what I put at the beginning, I just edited it down to save space, so that's why it and the PHP don't quite match up.
All of this works great, the app parses the XML to show what has been requested, and a user can make a new request that will then show in the app.
My problem is that when someone responds to an existing request, I would like the <Responses> to increase by 1. Is there something I can do with a different script that would find that entry, and +1 to whatever number currently exists in that item's Response tag?
I have seen this script, but it appears to go through and search for all of a certain tag, and change all tags of that name in the XML. I am looking to just find one specific one and change it. Suggestions?
$str = <<<XML
<ScrapBook>
<Event>
<Name> Name of Event </Name>
<Blah>glop glop</Blah>
</Event>
</ScrapBook>
XML;
$xmlDoc = new DOMDocument();
$xmlDoc->loadXml($str);
$events = $xmlDoc->getElementsByTagName("Event");
foreach($events as $event){
$eventNames = $event->getElementsByTagName("Name");
$eventN = $eventNames->item(0)->nodeValue;
if(' Name of Event ' == $eventN){
$eventNames->item(0)->nodeValue = 'New name';
}
}
var_dump($xmlDoc->saveXML());
You can go with simplexml to do that; first, select the <item> to be changed, then store the new <Responses>:
$xml = simplexml_load_string($x); // assume XML in $x
// select <Responses> with xpath by title and name
$responses = $xml->xpath("/items/item[title = 'Foo' and name = 'Bar']/Responses");
// store number + 1
$responses[0][0] = $responses[0] + 1;
see it working: https://eval.in/127882

Change the value of a text node using SimpleXML

I am trying to write a code where it will find a specific element in my XML file and then change the value of the text node. The XML file has different namespaces. Till now, I have managed to register the namespaces and also echo the text node of the element, which I want to change.
<?php
$xml = simplexml_load_file('getobs.xml');
$xml->registerXPathNamespace('g','http://www.opengis.net/gml');
$result = $xml->xpath('//g:beginPosition');
foreach ($result as $title) {
echo $title . "\n";
}
?>
My question is: How can I change the value of this element using SimpleXML? I tried to use the nodeValue command but I am not able to make it work.
This is a part of the XML:
<sos:GetObservation xmlns:sos="http://www.opengis.net/sos/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" service="SOS" version="1.0.0" srsName="urn:ogc:def:crs:EPSG:4326">
<sos:offering>urn:gfz:cawa:def:offering:meteorology</sos:offering>
<sos:eventTime>
<ogc:TM_During xmlns:ogc="http://www.opengis.net/ogc" xsi:type="ogc:BinaryTemporalOpType">
<ogc:PropertyName>urn:ogc:data:time:iso8601</ogc:PropertyName>
<gml:TimePeriod xmlns:gml="http://www.opengis.net/gml">
<gml:beginPosition>2011-02-10T01:10:00.000</gml:beginPosition>
Thanks
Dimitris
In the end I managed to do it by using the PHP XML DOM.
Here is the code that I used in order to change the text node of a specific element:
<?php
// create new DOM document and load the data
$dom = new DOMDocument;
$dom->load('getobs.xml');
//var_dump($dom);
// Create new xpath and register the namespace
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('g','http://www.opengis.net/gml');
// query the result amd change the value to the new date
$result = $xpath->query("//g:beginPosition");
$result->item(0)->nodeValue = 'sds';
// save the values in a new xml
file_put_contents('test.xml',$dom->saveXML());
?>
Not wanting to switch from the code I've already made for SimpleXML, I found this solution:
http://www.dotdragnet.com/forum/index.php?topic=3979.0
Specificially:
$numvotes = $xml->xpath('/gallery/image[path="'.$_GET["image"].'"]/numvotes');
...
$numvotes[0][0] = $votes;
Hope this helps!

PHP - Retrieving Only Certain XML Nodes and Saving to a file

I am using PHP curl to retrive an xml file from a remote url and save it to a local file on my server. The structure is the following:
<Store Country="Ireland">
<EventsPoints>
<Event ID="1800" >
<ArtistIDs>
<ArtistID ID="109" Type="Primary" />
</ArtistIDs>
<CategoryID>1</CategoryID>
<Country>IRL</Country>
<PerformanceName>Music and Arts</PerformanceName>
<VenueID ID="197" />
</Event>
<Venues>
<Venue ID="197">
<City>Dublin</City>
<Country>IRL</Country>
<VenueName>ABC</VenueName>
<VenueNumber>22</VenueNumber>
</Venue>
</Venues>
The above xml blocks are stored in the same XML file. There are several Event blocks and several Venue blocks.
The problem i'm having is using PHP to access this large XML file and iterate through the Venue blocks retrieving only a Venue block with a certain ID specified via a parameter.
I then want to iterate through the event blocks - only retrieving the events matching this specified venue ID. I then want to save this to a file on the server.
I want to do this for each venue.
How do I go about doing the above?
EDIT:
For each Venue and events related to that venue, I just want to literally copy them to their own file - basically splitting down the larger file into individual files
$docSource = new DOMDocument();
$docSource->loadXML($xml);
$docDest = new DOMDocument();
$docDest->loadXML(file_get_contents('/var/www/html/xml/testfile.xml'));
$xpath = new DOMXPath($docSource);
$id = "197";
$result = $xpath->query('//Event/VenueID[#ID=$id]')->item(0); //Get directly the node you want
$result = $docDest->importNode($result, true); //Copy the node to the other document
$items = $docDest->getElementsByTagName('items')->item(0);
$items->appendChild($result); //Add the copied node to the destination document
echo $docDest->saveXML();
You are not showing the desired output format, so I will assume this generates what you want. If not, feel free to modify the code so it meets the desired output format. This along with the comments above should have all you need to get this working on your own.
// load Source document
$srcDom = new DOMDocument;
$srcDom->load('/var/www/html/xml/testfile.xml');
$xPath = new DOMXPath($srcDom);
// iterate over all the venues in the source document
foreach ($srcDom->getElementsByTagName('Venue') as $venue) {
// create a new destination document for the current venue
$dstDom = new DOMDocument('1.0', 'utf-8');
// add an EventsPoint element as the root node
$dstDom->appendChild($dstDom->createElement('EventsPoint'));
// import the Venue element tree to the new destination document
$dstDom->documentElement->appendChild($dstDom->importNode($venue, true));
// fetch all the events for the current venue from the source document
$allEventsForVenue = $xPath->query(
sprintf(
'/Store/EventsPoints/Event[VenueID/#ID=%d]',
$venue->getAttribute('ID')
)
);
// iterate all the events found in Xpath query
foreach ($allEventsForVenue as $event) {
// add event element tree to current destination document
$dstDom->documentElement->appendChild($dstDom->importNode($event, true));
}
// make output prettier
$dstDom->formatOutput = true;
// save XML to file named after venue ID
$dstDom->save(sprintf('/path/to/%d.xml', $venue->getAttribute('ID')));
}
This will create an XML file like this
<?xml version="1.0" encoding="utf-8"?>
<EventsPoint>
<Venue ID="197">
<City>Dublin</City>
<Country>IRL</Country>
<VenueName>ABC</VenueName>
<VenueNumber>22</VenueNumber>
</Venue>
<Event ID="1800">
<ArtistIDs>
<ArtistID ID="109" Type="Primary"/>
</ArtistIDs>
<CategoryID>1</CategoryID>
<Country>IRL</Country>
<PerformanceName>Music and Arts</PerformanceName>
<VenueID ID="197"/>
</Event>
</EventsPoint>
in the file 197.xml

Categories