How can I remove certain elements from XML using SimpleXML - php

I load the following XML data into SimpleXML like this:
<?php
$xmlString = <<<'XML'
<?xml version="1.0"?>
<response>
<item key="0">
<title>AH 2308</title>
<field_a>3.00</field_a>
<field_b>7.00</field_b>
<field_d1>35.00</field_d1>
<field_d2>40.00</field_d2>
<field_e></field_e>
<field_g2></field_g2>
<field_g>M 45x1,5</field_g>
<field_gewicht>0.13</field_gewicht>
<field_gtin>4055953012781</field_gtin>
<field_l>40.00</field_l>
<field_t></field_t>
<field_abdrueckmutter>KM 9</field_abdrueckmutter>
<field_sicherung>MB 7</field_sicherung>
<field_wellenmutter>KM 7</field_wellenmutter>
</item>
<item key="1">
<title></title>
<field_a></field_a>
<field_b></field_b>
<field_d1></field_d1>
<field_d2></field_d2>
<field_e></field_e>
<field_g2></field_g2>
<field_g></field_g>
<field_gewicht></field_gewicht>
<field_gtin></field_gtin>
<field_l></field_l>
<field_t></field_t>
<field_abdrueckmutter></field_abdrueckmutter>
<field_sicherung></field_sicherung>
<field_wellenmutter></field_wellenmutter>
</item>
</response>
XML;
$xml = simplexml_load_string($xml);
How can I achieve the following result:
<?xml version="1.0"?>
<response>
<item key="0">
<title>AH 2308</title>
<field_a>3.00</field_a>
<field_b>7.00</field_b>
<field_d1>35.00</field_d1>
<field_d2>40.00</field_d2>
<field_e></field_e>
<field_g2></field_g2>
<field_g>M 45x1,5</field_g>
<field_gewicht>0.13</field_gewicht>
<field_gtin>4055953012781</field_gtin>
<field_l>40.00</field_l>
<field_t></field_t>
<field_abdrueckmutter>KM 9</field_abdrueckmutter>
<field_sicherung>MB 7</field_sicherung>
<field_wellenmutter>KM 7</field_wellenmutter>
</item>
<item key="1"></item>
</response>
To delete all empty elements, I could use the following working code:
foreach ($xml->xpath('/child::*//*[not(*) and not(text()[normalize-space()])]') as $emptyElement) {
unset($emptyElement[0]);
}
But that's not exactly what I want.
Basically, when the <title> element is empty, I want to remove it with all its siblings and keep the parent <item> element.
What's important: I also want to keep empty element, if the <title> is not empty. See <item key="0"> for example. The elements <field_e>, <field_g2> and <field_t>will be left untouched.
Is there an easy xpath query which can achieve that? Hope anyone can help. Thanks in advance!

This xpath query is working:
foreach ($xml->xpath('//title[not(text()[normalize-space()])]/following-sibling::*') as $emptyElement) {
unset($emptyElement[0]);
}
It keeps the <title> element but I can live with that.

DOM is more flexible manipulating nodes:
$document = new DOMDocument();
$document->loadXML($xmlString);
$xpath = new DOMXpath($document);
$expression = '/response/item[not(title[normalize-space()])]';
foreach ($xpath->evaluate($expression) as $emptyItem) {
// replace children with an empty text node
$emptyItem->textContent = '';
}
echo $document->saveXML();

Related

Remove white spaces between tag values in xml with php

I been searching information how to remove white spaces between tag values leaved by a PHP code when I export it to XML, I will explain detailed, first I load and XML then I do a search on the file with xPath, then I remove some elements that do not match some brands and finally I reexport it as a new XML, the problem is that this new XML is full of white spaces leaved by the code. I tried trim it but it doesn't seems to work correctly.
Here is my code:
<?php
$sXML = simplexml_load_file('file.xml'); //First load the XML
$brands = $sXML->xPath('//brand'); //I do a search for the <brand> tag
function filter(string $input) { //Then I give it a list of variables
switch ($input) {
case 'BRAND 3':
case 'BRAND 4':
return false;
default:
return true;
}
}
array_walk($brands, function($brand) { //I remove all elements do not match my list
$content = (string) $brand;
if (filter($content)) {
$item = $brand->xPath('..')[0];
unset($item[0]);
}
});
$sXML->asXML('filtred.xml'); // And finally export a new xml
?>
This one is the original XML:
<?xml version="1.0" encoding="utf-8"?>
<products>
<item>
<reference>00001</reference>
<other_string>PRODUCT 1</other_string>
<brand>BRAND 1</brand>
</item>
<item>
<reference>00002</reference>
<other_string>PRODUCT 2</other_string>
<brand>BRAND 2</brand>
</item>
<item>
<reference>00003</reference>
<other_string>PRODUCT 3</other_string>
<brand>BRAND 3</brand>
</item>
<item>
<reference>00004</reference>
<other_string>PRODUCT 4</other_string>
<brand>BRAND 4</brand>
</item>
<item>
<reference>00005</reference>
<other_string>PRODUCT 5</other_string>
<brand>BRAND 5</brand>
</item>
</products>
And the output of the script sends this:
<?xml version="1.0" encoding="utf-8"?>
<products>
<item>
<reference>00001</reference>
<other_string>PRODUCT 1</other_string>
<brand>BRAND 1</brand>
</item>
<item>
<reference>00002</reference>
<other_string>PRODUCT 2</other_string>
<brand>BRAND 2</brand>
</item>
<item>
<reference>00005</reference>
<other_string>PRODUCT 5</other_string>
<brand>BRAND 5</brand>
</item>
</products>
As you can see on the output, there is a white space between product 2 and product 5 and that I need to remove it. Any help will be appreciate.
You can force SimpleXML to trim all whitespace when it reads the file, by passing the LIBXML_NOBLANKS option to simplexml_load_file:
$sXML = simplexml_load_file('file.xml', null, LIBXML_NOBLANKS);
Then when you call ->asXML(), all the whitespace will be removed, and you'll get XML all on one line, like this:
<?xml version="1.0" encoding="utf-8"?>
<products><item><reference>00003</reference><other_string>PRODUCT 3</other_string><brand>BRAND 3</brand></item><item><reference>00004</reference><other_string>PRODUCT 4</other_string><brand>BRAND 4</brand></item></products>
To re-generate whitespace based on the remaining structure, you'll need to use DOM rather than SimpleXML - but that's easy to do without changing any of your existing code, because dom_import_simplexml simply "rewraps" the XML without reparsing it.
Then you can use the DOMDocument formatOutput property and save() method to "pretty-print" the document:
$sXML = simplexml_load_file('file.xml', null, LIBXML_NOBLANKS);
// ...
// process $sXML as before
// ...
$domDocument = dom_import_simplexml($sXML)->ownerDocument;
$domDocument->formatOutput = true;
echo $domDocument->save('filtered.xml');
Another possibility is to use preg_replace:
// Get simpleXml as string
$xmlAsString = $yourSimpleXmlObject->asXML();
// Remove newlines
$xmlAsString = preg_replace("/\n/", "", $xmlAsString);
// Remove spaces between tags
$xmlAsString = preg_replace("/>\s*</", "><", $xmlAsString);
var_dump($xmlAsString);
Now you get your XML as string in one line (including the XML declaration).

PHP XML: Getting text of a node and its children

I know that this questions has been asked before, but I cannot make it work. I'm using simplexml and xpath in a PHP file. I need to get text from a node including the text in its child nodes. So, the results should be:
Mr.Smith bought a white convertible car.
Here is the xml:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test9.xsl"?>
<items>
<item>
<description>
<name>Mr.Smith bought a <car>white</car> <car>convertible</car> car.</name>
</description>
</item>
</items>
The php that's not working is:
$text = $xml->xpath('//items/item/description/name');
foreach($text as &$value) {
echo $value;
}
Please help!
To get the node value with all its child elements, you can use DOMDocument, with C14n():
<?php
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="test9.xsl"?>
<items>
<item>
<description>
<name>Mr.Smith bought a <car>white</car> <car>convertible</car> car.</name>
</description>
</item>
</items>
XML;
$doc = new DOMDocument;
$doc->loadXML($xml);
$x = new DOMXpath($doc);
$text = $x->query('//items/item/description/name');
echo $text[0]->C14n(); // Mr.Smith bought a white convertible car.
Demo

Merge two variable SimpleXML Objects in PHP

I've been trying to merge two XML files I use to build my menubar in my web application for hours, but I can't get it to work.
I have my main XML file which looks like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<root>
<version>1.0.0</version>
<menu>
<Category1>
<item>
<id>Cake</id>
<nr>1</nr>
<hint>I like these</hint>
<userlevel>5</userlevel>
</item>
<item>
<id>Cake 2</id>
<nr>2</nr>
<hint>I like these too, but only for me</hint>
<userlevel>10</userlevel>
</item>
<Category1>
<Category2WithApples>
<item>
<id>Apple Cake</id>
<nr>1</nr>
<hint>Sweet</hint>
<userlevel>5</userlevel>
</item>
<item>
<id>Rainbow Cake</id>
<nr>2</nr>
<hint>Mine!!</hint>
<userlevel>10</userlevel>
</item>
<Category2WithApples>
</menu>
</root>
Now, I want each user to be able to load in his custom XML which is in the same folder as the main.xml which looks like this:
<CategoryMyOwn>
<item>
<id>Item in my Category</id>
<nr>0</nr>
<hint>Some text</hint>
<userlevel>0</userlevel>
</item>
</CategoryMyOwn>
<Category1>
<item>
<id>Item in existing category</id>
<nr>0</nr>
<hint>Some text</hint>
<userlevel>0</userlevel>
</item>
</Category1>
I've tried solutions from
http://php.net/manual/de/ref.simplexml.php
php recursion, function doesn't return any value
http://durgagupta.com.np/php-how-to-merge-two-simplexml-objects/
but they all do not work at all for me or just append the second file to the end of my main.xml. So, my question is, how do I properly merge the user.xml into my main.xml so it looks like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<root>
<version>1.0.0</version>
<menu>
<Category1>
<item>
<id>Cake</id>
<nr>1</nr>
<hint>I like these</hint>
<userlevel>5</userlevel>
</item>
<item>
<id>Cake 2</id>
<nr>2</nr>
<hint>I like these too, but only for me</hint>
<userlevel>10</userlevel>
</item>
<item>
<id>Item in existing category</id>
<nr>0</nr>
<hint>Some text</hint>
<userlevel>0</userlevel>
</item>
<Category1>
<Category2WithApples>
<item>
<id>Apple Cake</id>
<nr>1</nr>
<hint>Sweet</hint>
<userlevel>5</userlevel>
</item>
<item>
<id>Rainbow Cake</id>
<nr>2</nr>
<hint>Mine!!</hint>
<userlevel>10</userlevel>
</item>
<Category2WithApples>
<CategoryMyOwn>
<item>
<id>Item in my Category</id>
<nr>0</nr>
<hint>Some text</hint>
<userlevel>0</userlevel>
</item>
</CategoryMyOwn>
</menu>
</root>
Your second XML is not a document, XML documents need to have a document element node. In other words here at the top level only a single element node is allowed. All other element nodes have to be descendants of that node.
You can treat this as an XML fragment however. A fragment is the inner XML of an element node.
In both cases it easier to use DOM for that.
Append a fragment to a parent element node
Let's keep it simple for the first step and append the fragment to the menu node.
$document = new DOMDocument();
$document->loadXml($targetXml);
$xpath = new DOMXpath($document);
$fragment = $document->createDocumentFragment();
$fragment->appendXml($fragmentXml);
foreach ($xpath->evaluate('/root/menu[1]') as $menu) {
$menu->appendChild($fragment);
}
echo $document->saveXml();
The Xpath expression can /root/menu[1] selects the first menu element node inside the root. This can be only one node or none.
A document fragment in DOM is a node object and can be appended like any other node (element, text, ...).
Merging nodes
Merging the category nodes is a little more difficult. But Xpath will help.
$document = new DOMDocument();
$document->loadXml($targetXml);
$xpath = new DOMXpath($document);
$fragment = $document->createDocumentFragment();
$fragment->appendXml($fragmentXml);
$menu = $xpath->evaluate("/root/menu[1]")->item(0);
foreach ($xpath->evaluate('*', $fragment) as $category) {
$targets = $xpath->evaluate("{$category->nodeName}[1]", $menu);
if ($targets->length > 0) {
$targetCategory = $targets->item(0);
foreach ($category->childNodes as $item) {
$targetCategory->appendChild($item);
}
} else {
$menu->appendChild($category);
}
}
echo $document->saveXml();
Fetching the menu node
$menu = $xpath->evaluate("/root/menu[1]")->item(0);
This is about the same like in the first simple example. It fetch the menu nodes in root and returns the first found node. You should check if the list contained a node. But for this example just take it for guaranteed.
Iterating the fragment
foreach ($xpath->evaluate('*', $fragment) as $category) {
...
}
* is a simple Xpath expression that returns any element child node. The fragment can contain other nodes (whitespace, text, comment, ...). The second argument for DOMXpath::evaluate() is the context for the Xpath expression.
Fetching the target category
Next you need to fetch the category node with the same name from the target document. This will return a list with one node or an empty list.
$targets = $xpath->evaluate("{$category->nodeName}[1]", $menu);
if ($targets->length > 0) {
...
} else {
...
}
Append to the found target category
If the category exists append all child nodes from the category in the fragment to the target.
$targetCategory = $targets->item(0);
foreach ($category->childNodes as $item) {
$targetCategory->appendChild($item);
}
Append a category
$menu->appendChild($category);
If the category doesn't exists, just append it to the menu.

How to make child xml node with while loop using php

$xml = new DOMDocument();
$root=$xml->createElement("ROOT");
$xml->appendChild($root);
$data=$xml->createElement("DATA");
while($row=db_fetch_object($result))
{
$data=$xml->createElement("ITEM");
$item->setAttribute("COMPANY",$row->field_windmill_fabrikant_value);
$item->setAttribute("HEIGHT",$row->field_windmill_ashoogte_value);
$item->setAttribute("POWER",$row->field_windmill_vermogen_value);
$item->setAttribute("LOCATION",$row->field_windmill_provincie_value);
$item->setAttribute("START_YEAR",$row->field_windmill_startjaar_value);
$data->appendChild($item);
}
$root->appendChild($data);
echo $xml->saveXML();
Here I want to append ITEM as a child node to data but ITEM is getting appended to item and not to data. I'm using PHP.
Can anyone help in it.
Thanks.
Just replace
$data=$xml->createElement("ITEM");
with
$item=$xml->createElement("ITEM");
result of this will be
<?xml version="1.0"?>
<ROOT>
<DATA>
<ITEM COMPANY="COMPANY0" HEIGHT="HEIGHT0" POWER="POWER0" LOCATION="LOCATION0" START_YEAR="START_YEAR0"/>
<ITEM COMPANY="COMPANY1" HEIGHT="HEIGHT1" POWER="POWER1" LOCATION="LOCATION1" START_YEAR="START_YEAR1"/>
<ITEM COMPANY="COMPANY2" HEIGHT="HEIGHT2" POWER="POWER2" LOCATION="LOCATION2" START_YEAR="START_YEAR2"/>
</DATA>
</ROOT>

PHP: Find XML node and insert child

I have an xml document with the following structure:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<id>1</id>
<url>www.test.com</url>
</item>
<item>
<id>2</id>
<url>www.test2.com</url>
</item>
</items>
I would like to be able to search for a node value, such as the value of 1 for the id field. Then, once that node is found, select the parent node, which would be < item > and insert a new child within.
I know the concept of using dom document, but not sure how to do it in this instance.
This should be a start:
$dom = new DOMDocument;
$dom->loadXML($input);
$ids = $dom->getElementsByTagName('id');
foreach ($ids as $id) {
if ($id->nodeValue == '1') {
$child = $dom->createElement('tagname');
$child->appendChild($dom->createTextNode('some text'));
$id->parentNode->appendChild($child);
}
}
$xml = $dom->saveXML();
or something close to it.
You can do the same thing in a simpler way. Instead of looking for an <id/> node whose value is 1 then selecting its parent, you can reverse the relation and look for any node which has an <id/> child whose value is 1.
You can do that very easily in XPath, and here's how to do it in SimpleXML:
$items = simplexml_load_string(
'<?xml version="1.0" encoding="UTF-8"?>
<items>
<item>
<id>1</id>
<url>www.test.com</url>
</item>
<item>
<id>2</id>
<url>www.test2.com</url>
</item>
</items>'
);
$nodes = $items->xpath('*[id = "1"]');
$nodes[0]->addChild('new', 'value');
echo $items->asXML();

Categories