Removing an xml node using php - php

I have an xml file like this:
<uploads>
<upload>
<name>asd</name>
<type>123</name>
</upload>
<upload>
<name>qwe</name>
<type>456</name>
</upload>
</uploads>
Now I want to delete a node upload that has a name child as (say qwe). How can I delete it using php , so that the resulting xml is
<uploads>
<upload>
<name>asd</name>
<type>123</name>
</upload>
</uploads>
I do not have too much knowledge about xml and related techniques. Is it possible to do it using xpath, like this $xml->xpath('//upload//name')? Assuming $xml was loaded using simplexml_load_file() function.
For reference, I was using this question to do what I want. But, it selects the element based on an attribute and I want to select an element based on the value of it child node.

Yes, you can use XPath to find the node that is to be removed. You can use predicates to specifiy the exact elements you're looking for. In your case the predicate can be has a name element and its (string/node) value is 'qwe', like e.g.:
<?php
$doc = new DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->loadxml( getData() );
$xpath = new DOMXPath($doc);
foreach( $xpath->query("/uploads/upload[name='qwe']") as $node) {
$node->parentNode->removeChild($node);
}
$doc->formatOutput = true;
echo $doc->savexml();
function getData() {
return <<< eox
<uploads>
<upload>
<name>asd</name>
<type>123</type>
</upload>
<upload>
<name>qwe</name>
<type>456</type>
</upload>
</uploads>
eox;
}
prints
<?xml version="1.0"?>
<uploads>
<upload>
<name>asd</name>
<type>123</type>
</upload>
</uploads>

Related

how could i import xml list with same name using php domdocument

using php domdocument, to import xml file, i can't have the list of "tags"
I have tried multiple way but i can't
xml document :
<resource>
<title>hello world</title>
<tags>
<resource>great</resource>
<resource>fun</resource>
<resource>omg</resource>
</resource>
php :
<?php
$url='test.xml';
$doc = new DOMDocument();
$doc->load($url);
$feed = $doc->getElementsByTagName("resource");
foreach($feed as $entry) {
echo $entry->getElementsByTagName("username")->item(0)->nodeValue;
echo '<br>';
echo $entry->getElementsByTagName("tags")->item(0)->nodeValue;
echo '<br>';
}
i expect the outpout to be a list like that :
hello world
great
fun
omg
but the actual output is NOT a list the result is a sentence without space :
hello world greatfunomg
DOMDocument::getElementsByTagName() returns all descendant element nodes with the specified name. DOMElement::$nodeValue will return the text content of an element node including all its descendants.
In your case echo $entry->getElementsByTagName("tags")->item(0)->nodeValue fetches all tags, access the first node of that list and outputs its text content. That is greatfunomg.
Using the DOM methods to access nodes is verbose and requires a lot of code and if you want to do it right a lot of conditions. It is a lot easier if you use Xpath expressions. The allow you to scalar values and lists of nodes from an DOM.
$xml = <<<'XML'
<_>
<resource>
<title>hello world</title>
<tags>
<resource>great</resource>
<resource>fun</resource>
<resource>omg</resource>
</tags>
</resource>
</_>
XML;
$document = new DOMDocument();
$document->loadXML($xml);
// create an Xpath instance for the document
$xpath = new DOMXpath($document);
// fetch resource nodes that are a direct children of the document element
$entries = $xpath->evaluate('/*/resource');
foreach($entries as $entry) {
// fetch the title node of the current entry as a string
echo $xpath->evaluate('string(title)', $entry), "\n";
// fetch resource nodes that are children of the tags node
// and map them into an array of strings
$tags = array_map(
function(\DOMElement $node) {
return $node->textContent;
},
iterator_to_array($xpath->evaluate('tags/resource', $entry))
);
echo implode(', ', $tags), "\n";
}
Output:
hello world
great, fun, omg
If you just need to output the first piece of text for each <resource> element - wherever it is, then using XPath and (making sure you ignore whitespace on load) pick the first child element of this and output the node value.
Ignoring the whitespace on load is important as the whitespace will create nodes for all the padding around each element and so the first child of each <resource> element may just be a new line or tab.
$xml = '<root>
<resource>
<title>hello world</title>
<tags>
<resource>great</resource>
<resource>fun</resource>
<resource>omg</resource>
</tags>
</resource>
</root>';
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->loadXML($xml);
// $doc->load($filename); // If loading from a file
$xpath = new DOMXpath($doc);
$resources = $xpath->query("//resource");
foreach ( $resources as $resource ){
echo $resource->firstChild->nodeValue.PHP_EOL;
}
The output of which is
hello world
great
fun
omg
Or without using XPath...
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->loadXML($xml);
//$doc->load($filename);
$resources = $doc->getElementsByTagName("resource");
foreach ( $resources as $resource ){
echo $resource->firstChild->nodeValue.PHP_EOL;
}

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

why I can't get first child value for a XML file in php

I used an XMLHttpRequest object to retrieve data from a PHP response.
Then, I created an XML file:
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person>
<name>Ce</name>
<gender>male</gender>
<age>24</age>
</person>
<person>
<name>Lin</name>
<gender>female</gender>
<age>25</age>
</person>
</persons>
In the PHP file, I load the XML file and try to echo tag values of "name."
$dom = new DOMDocument("1.0");
$dom -> load("test.xml");
$persons = $dom -> getElementsByTagName("person");
foreach($persons as $person){
echo $person -> childNodes -> item(0) -> nodeValue;
}
But the nodeValue returned is null. However, when I change to item(1), the name tag values can be displayed. Why?
Change code to
$dom = new DOMDocument("1.0");
$dom -> load("test.xml");
$persons = $dom -> getElementsByTagName("persons");
foreach($persons as $person){
echo $person->childNodes[1]->nodeValue;
}
Anything in a DOM is a node, include texts and text with only whitespaces. So the first child of the person element node is a text node that contains the linebreak and indent before the name element node.
Here is a property that removes any whitespace node at parse time:
$document = new DOMDocument("1.0");
// do not preserve whitespace only text nodes
$document->preserveWhiteSpace = FALSE;
$document->load("test.xml");
$persons = $document->getElementsByTagName("person");
foreach ($persons as $person) {
echo $person->firstChild->textContent;
}
However typically a better way is to use Xpath expressions.
$document = new DOMDocument("1.0");
$document->load("test.xml");
$xpath = new DOMXpath($document)
$persons = $xpath->evaluate("/persons/person");
foreach ($persons as $person) {
echo $xpath->evaluate("string(name)", $person);
}
string(name) fetches the child element node name (position is not relevant) and casts it into a string. If here is no name element it will return an empty string.
Using DOM you need to get the right element to pick up the name, child nodes include all sorts of things including whitespace. The node 0 your trying to use is null because of this. So for DOM...
$dom = new DOMDocument("1.0");
$dom -> load("test.xml");
$persons = $dom -> getElementsByTagName("person");
foreach($persons as $person){
$name = $person->getElementsByTagName("name");
echo $name->item(0)->nodeValue.PHP_EOL;
}
If your requirements are as simple as this, you could alternatively use SimpleXML...
$sxml = simplexml_load_file("test.xml");
foreach ( $sxml->person as $person ) {
echo $person->name.PHP_EOL;
}
This allows you to access elements as though they are object properties and as you can see ->person equates to accessing <person>.

With DOMDocument, is it possible to filter the output of getElementsByTagName based on an additional parameter?

Take the following example of this xml:
<xml>
<siblings>
<brother>Derek</brother>
<sister>Elaine</sister>
<sister>Flora</sister>
</siblings>
<siblings>
<brother>Gary</brother>
<sister>Hannah</sister>
</siblings>
</xml>
If I were to use the following code:
$xmlDoc=new DOMDocument();
$xmlDoc->load("Family.xml");
$siblings = $xmlDoc->getElementsByTagName('Siblings');
$sister = $xmlDoc->getElementsByTagName('Sister');
This would normally return all instances of the tag "Sister", in this case "Elaine", "Flora" and "Hannah". Would it be possible to change it so that you could filter the tagnames by the name of one of the other nodes? For instance, using the name "Derek" to change the output to "Elaine" and "Flora" only.
Xpath expressions allow you to use conditions to fetch nodes from a DOM.
$xml = <<<'XML'
<xml>
<siblings>
<brother>Derek</brother>
<sister>Elaine</sister>
<sister>Flora</sister>
</siblings>
<siblings>
<brother>Gary</brother>
<sister>Hannah</sister>
</siblings>
</xml>
XML;
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$expression = '/xml/siblings[brother = "Derek"]/*[not(self::brother = "Derek")]';
foreach ($xpath->evaluate($expression) as $sibling) {
echo $sibling->textContent, "\n";
}
Output:
Elaine
Flora
The Xpath Expression
Fetch the siblings elements ...
/xml/siblings
... if they have a child element brother with the value Derek ...
/xml/siblings[brother = "Derek"]
... and fetch their child elements...
/xml/siblings[brother = "Derek"]/*
... if they are not a brother element node with the value Derek.
/xml/siblings[brother = "Derek"]/*[not(self::brother = "Derek")]

PHP Query XML with xPath

I have an XML Structure like the following:
<Tickets>
<EventsPoints>
<Event ID="23">
<PerformanceName>U2</PerformanceName>
<EventDate>25/05/2012</EventDate>
<EventPrice>75.00</EventPrice>
</Event>
<Event ID="27">
<PerformanceName>Jedward</PerformanceName>
<EventDate>28/05/2012</EventDate>
<EventPrice>20.00</EventPrice>
</Event>
<Event ID="27">
<PerformanceName>Rolling Stones</PerformanceName>
<EventDate>03/12/2012</EventDate>
<EventPrice>80.00</EventPrice>
</Event>
</EventsPoints>
</Tickets>
Basically I want to search this XML for a certain performance name, say "U2", and then return that entire XML block (i.e. that performance name, event date and price - all in formatted XML and saved in a separate xml file)
This is my php code but it doesn't seem to be extracting the data correctly:
$srcDom = new DOMDocument;
$srcDom->load('/var/www/html/xml/searchfile.xml');
$xPath = new DOMXPath($srcDom);
foreach ($srcDom->getElementsByTagName('Event') as $event) {
$dstDom = new DOMDocument('1.0', 'utf-8');
$dstDom->appendChild($dstDom->createElement('EventsPricePoints'));
$dstDom->documentElement->appendChild($dstDom->importNode($event, true));
$allEventsForVenue = $xPath->query(
sprintf(
'/Tickets/EventsPoints/Event/PerformanceName[.="U2"]'
)
);
foreach ($allEventsForVenue as $event) {
$dstDom->documentElement->appendChild($dstDom->importNode($event, true));
}
$dstDom->formatOutput = true;
$dstDom->save(sprintf('/var/www/html/xml/searchresults1.xml'));
}
Your XPath gets the PerformanceName element when you want the parent element. Change it to
/Tickets/EventsPoints/Event/PerformanceName[.="U2"]/..
or
/Tickets/EventsPoints/Event[PerformanceName[.="U2"]]
or import
$event->parentNode
Also, you dont need the first foreach. Remove it and move the code writing the $dstDom into the code iterating the XPath result. See http://codepad.org/zfOZXycZ
Also see:
DOMDocument in php and
http://schlitt.info/opensource/blog/0704_xpath.html

Categories