Remove a child with a specific attribute, in SimpleXMLElement [PHP] - php

I load an XML file $xml=simplexml_load_file("productsXML.xml", 'SimpleXMLElement', LIBXML_NOCDATA);
Then i want to delete the items that have a price lower than 16
foreach($xml as $item) {
$item->suggested_price = $item->suggested_price / 1.24;
$timh = (int) $item->suggested_price;
if ($timh < 16) {
//deleting the specific $item
}
}
After that, i recreate the XML with new DOMDocument
I ve tried lots of solutions in the internet without success. I can manipulate the under-16 prices or print them, but i can't somehow delete the whole $item.
XML SAMPLE
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product id="25">
<name><![CDATA[test]]></name>
<name_en><![CDATA[test_en]]></name_en>
<modified>2020-06-29T08:45:09+03:00</modified>
<suggested_price>84.5</suggested_price>

$doc = new DOMDocument;
$doc->load('YourFile.xml');
$thedocument = $doc->documentElement;
$thedocument->removeChild(YourItem);
Can you show the code which u try?
You can Edit the Price right? then you propably need to Remove Parent!?

You can use unset, and for each time you unset continue the loop without incrementing the counter for the index.
$xml = simplexml_load_file("productsXML.xml", 'SimpleXMLElement', LIBXML_NOCDATA);
$cnt = 0;
foreach ($xml as $item) {
$item->suggested_price = $item->suggested_price / 1.24;
if ((int) $item->suggested_price < 16) {
unset($xml->product[$cnt]);
continue;
}
$cnt++;
}
echo $xml->asXml();

Related

PHP find the node by value and remove it XML

I have all properties listed in XML file with this structure
<property>
<details>
<object>25.5 m2 Flat in New York</object>
</details>
</property>
<property>
<details>
<object>95.6 m2 House in New Jersey</object>
</details>
</property>
Now I want to use PHP to find the node with a specific <object> value and to remove the parent node (<property>). How can I do it?
I tried by doing the code below but I cannot manage to work.
$doc = new DOMDocument;
$doc->load('../openimmo/xml-import1.xml');
$thedocument = $doc->documentElement;
$list = $thedocument->getElementsByTagName('property');
$nodeToRemove = null;
foreach ($list as $domElement) {
$attrValue = $domElement->getElementsByTagName('object');
foreach ($attrValue as $item) {
if ($item->nodeValue == $_GET['delete']) {
$nodeToRemove = $domElement;
}
}
}
if ($nodeToRemove != null)
$thedocument->removeChild($nodeToRemove);
echo $doc->saveXML();
You can use Xpath expressions to fetch nodes. This allows you to use conditions.
$document = new DOMDocument;
//$document->load('../openimmo/xml-import1.xml');
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$objectText = '25.5 m2 Flat in New York';
$properties = $xpath->evaluate('//property[details/object = "'.$objectText.'"]');
foreach ($properties as $property) {
// remove the node (PHP 8)
$property->remove();
}
echo $document->saveXML();
The other difference is that the result of DOMXpath::evaluate() is not live. Unlike the result from DOMNode::getElementsByTagName() it does not change if the DOM changes.
PHP 8 adds DOM Living Standard methods. In PHP 7 you would have to use $property->parentNode->removeChild($property).

How to remove XML parent node based on child conditions PHP

Hi I am trying to clean up xml file out of positions I dont need. Here is my code so far:
<?php
$doc = new DOMDocument;
$doc->load('merg.xml');
$xpath = new DOMXPath($doc);
$products = $xpath->query('//offer/products/*');
printf('There is %d products<br /><br />', $products->length);
function findStopPointByName($xml, $query) {
$upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZĄŻŚĆŹĆÓŁ";
$lower = "abcdefghijklmnopqrstuvwxyzążśćźńół";
$arg_query = "translate('$query', '$upper', '$lower')";
return $xml->query("//offer/products/product/description/name[contains(text(),$arg_query)]");
}
foreach(findStopPointByName($xpath,'Skór') as $node)
{
$node->parentNode->removeChild($node);
}
$doc->save('merg_fixed.xml');
?>
Structure of XML:
<offer>
<products>
<product>
<description>
<name>Name of the product</name>
...
</name>
...
</description>
</product>
</products>
</offer>
I am trying to remove all PRODUCT where its NAME contains 'Skór' in any case (Skór, skór, SKÓR - is enough). Funcion findStopPointByName returns DOMNodeList of correct length, but nothing is removed from actual XML file, please help.
First, you can directly find node product with the condition
Second, to make search case insensitive, you can translate node text in any case but should use pattern in the same case. As the result, your code may be so
function findStopPointByName($xml, $query) {
$upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZĄŻŚĆŹĆÓŁ";
$lower = "abcdefghijklmnopqrstuvwxyzążśćźńół";
$arg_query = "translate(text(), '$upper', '$lower')";
$q = "//product[description/name[contains($arg_query, '$query')]]" ."\n";
return $xml->query($q);
}
$doc = new DOMDocument;
$doc->load('merg.xml');
$xpath = new DOMXPath($doc);
foreach(findStopPointByName($xpath,'skór') as $node)
$node->parentNode->removeChild($node);
echo $doc->saveXML();
Demo on eval.in

how to use loop for array

I have a array which it reads its cells from a xml file,i wrote it by "for" but now because i don't know how many node i have i wanna to write this loop in a way that it start and finish up to end of xml file.my code with for is:
$description=array();
for($i=0;$i<2;$i++)
{
$description[$i]=read_xml_node("dscription",$i);
}
and my xml file:
<eth0>
<description>WAN</description>
</eth0>
<eth1>
<description>LAN</description>
</eth1>
in this code i must know "2",but i wanna to know a way that doesn't need to know "2".
i am not sure what kind of parser you are using, but it is very easy with simplexml, so i put together some sample code using simplexml.
something like this should do the trick:
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<node>
<eth0>
<description>WAN</description>
</eth0>
<eth1>
<description>LAN</description>
</eth1>
</node>
XML;
$xml = new SimpleXMLElement($xmlstr);
foreach ($xml as $xmlnode) {
foreach ($xmlnode as $description) {
echo $description . " ";
}
}
output:
WAN LAN
$length = count($description);
for ($i = 0; $i < $length; $i++) {
print $description[$i];
}
The parser you use might allow you to use a while loop which will return false when it has reached the end of the XML document. For example:
while ($node = $xml->read_next_node($mydoc)) {
//Do whatever...
}
If none exists, you can try using count() as the second parameter of your for loop. It returns the length of an array you specify. For example:
for ($i = 0; $i < count($myarray); $i++) {
//Do whatever...
}

PHP XML file filter on match

I am having a heck of a time getting this working...
What I want to do is filter a xml file by a city (or market in this case).
This is the xml data.
<itemset>
<item>
<id>2171</id>
<market>Vancouver</market>
<url>http://</url></item>
<item>
<id>2172</id>
<market>Toronto</market>
<url>http://</url></item>
<item>
<id>2171</id>
<market>Vancouver</market>
<url>http://</url></item>
This is my code...
<?php
$source = 'get-xml-feed.php.xml';
$xml = new SimpleXMLElement($source);
$result = $xml->xpath('//item/[contains(market, \'Toronto\')]');
while(list( , $node) = each($result)) {
echo '//Item/[contains(Market, \'Toronto\')]',$node,"\n";
}
?>
If I can get this working I would like to access each element, item[0], item[1] base on filtered results.
Thanks
I think this implements what you are looking for using XPath:
<?php
$source = file_get_contents('get-xml-feed.php.xml');
$xml = new SimpleXMLElement($source);
foreach ($xml as $node)
{
$row = simplexml_load_string($node->asXML());
$result = $row->xpath("//item/market[.='Toronto']");
if ($result[0])
{
var_dump($row);
}
}
?>
As another answer mentioned, unless you are wed to the use of XPath it's probably more trouble than it's worth for this application: just load the XML and treat the result as an array.
I propose using simplexml_load_file. The learning curve is less step than using the specific XML objects + XPath. It returns an object in the format you descibe.
Try this and you'll see what I mean:
<?php
$source = 'get-xml-feed.php.xml';
$xml = simplexml_load_file($source);
var_dump($xml);
?>
There is also simplexml_load_string if you just have an XML snippet.
<?php
$source = 'get-xml-feed.php.xml';
//$xml = new SimpleXMLElement($source);
$dom = new DOMDocument();
#$dom->loadHTMLFile($source);
$xml = simplexml_import_dom($dom);
$result = $xml->xpath("//item/market[.='Toronto']/..");
while(list( , $node) = each($result)) {
print_r($node);
}
?>
This will get you the parent nodeset when it contains a node with "Toronto" in it. It returns $node as a simplexml element so you will have to deal with it accordingly (I just printed it as an array).

PHP parsing XML file with and without namespaces

I need to get a XML File into a Database. Thats not the problem. Cant read it, parse it and create some Objects to map to the DB. Problem is, that sometimes the XML File can contain namespaces and sometimes not. Furtermore sometimes there is no namespace defined at all.
So what i first got was something like this:
<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/">
<objects>
<object>
<node_1>value1</node_1>
<node_2>value2</node_2>
<node_3 iso_land="AFG"/>
<coords lat="12.00" long="13.00"/>
</object>
</objects>
</struct>
And the parsing:
$obj = new stdClass();
$nodes = array('node_1', 'node_2');
$t = $xml->xpath('/objects/object');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
Thats fine as long as there are no namespaces. Here comes the XML File with namespaces:
<?xml version="1.0" encoding="UTF-8"?>
<b:struct xmlns:b="http://www.w3schools.com/test/">
<b:objects>
<b:object>
<b:node_1>value1</b:node_1>
<b:node_2>value2</b:node_2>
<b:node_3 iso_land="AFG"/>
<b:coords lat="12.00" long="13.00"/>
</b:object>
</b:objects>
</b:struct>
I now came up with something like this:
$xml = simplexml_load_file("test.xml");
$namespaces = $xml->getNamespaces(TRUE);
$ns = count($namespaces) ? 'a:' : '';
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");
$nodes = array('node_1', 'node_2');
$obj = new stdClass();
foreach($nodes AS $node) {
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.$node);
if($t[0]) {
$obj->$node = (string) $t[0];
}
}
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'node_3');
if($t[0]) {
$obj->iso_land = (string) $t[0]->attributes()->iso_land;
}
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'coords');
if($t[0]) {
$obj->lat = (string) $t[0]->attributes()->lat;
$obj->long = (string) $t[0]->attributes()->long;
}
That works with namespaces and without. But i feel that there must be a better way. Before that i could do something like this:
$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
But that just wont work with namespaces.
You could make 'http://www.w3schools.com/test/' the default namespace. This way a:objectswould match regardless of whether the document says <a:objects> or <objects>.
If memory usage is not a issue you can even do it with a textual replacement, e.g.
$data = '<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/">
<objects>
<object>
<node_1>value1</node_1>
<node_2>value2</node_2>
<node_3 iso_land="AFG"/>
<coords lat="12.00" long="13.00"/>
</object>
</objects>
</struct>';
$data = str_replace( // or preg_replace(,,,1) if you want to limit it to only one replacement
'xmlns:b="http://www.w3schools.com/test/"',
'xmlns="http://www.w3schools.com/test/" xmlns:b="http://www.w3schools.com/test/"',
$data
);
$xml = new SimpleXMLElement($data);
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");
foreach($xml->xpath('//a:objects/a:object') as $n) {
echo $n->node_1;
}
You can make your XPATH statements more generic by matching on any element * and using a predicate filter to match on the local-name(), which will match on the element name with/without namespaces.
An XPATH like this:
/*[local-name()='struct']/*[local-name()='objects']/*[local-name()='object']/*[local-name()='coords']
Applied to the code sample you were using:
$obj = new stdClass();
$nodes = array('node_1', 'node_2');
$t = $xml->xpath('/*[local-name()="objects"]/*[local-name()="object"]');
foreach($nodes AS $node) {
if($t[0]->$node) {
$obj->$node = (string) $t[0]->$node;
}
}
Take a look at This
http://blog.sherifmansour.com/?p=302
It helped me a lot.

Categories