Change previous sibling value with php xPath - php

I have a situation that's driving me crazy. I want to search my feed for an element with a certain value (deriving from an array) and if that value is found, change the value of it's previous sibling element.
My feed looks like this
<products>
<product>
<properties>
<property name="category">
<value>Fruits</value>
</property>
<property name="item">
<value>Banana</value>
</property>
</properties>
</product>
<product>
<properties>
<property name="category">
<value>Fruits</value>
</property>
<property name="item">
<value>Apple</value>
</property>
</properties>
</product>
<product>
<properties>
<property name="category">
<value>Fruits</value>
</property>
<property name="item">
<value>Carrot</value>
</property>
</properties>
</product>
</products>
As you can see, there can be some errors in the feed. For these instances i made an array with the appropriate value, like so:
$replacements = Array(
"Carrot" => "Vegetable"
);
Now i thought to select every property with the attribute item that has the value as in $replacements's key, then select the previous sibling element with the attribute category and change this value with the matching $replacements's value.
I came up with this, but that only gives me a white screen with no output at all
$xml_src = 'feed.xml';
$document = new DOMDocument();
$document->load($xml_src);
$xpath = new DOMXpath($document);
$query = '//property[#name = "item"]';
$entries = $xpath->query($query);
foreach ($entries as $entry) {
if(array_key_exists($entry->nodeValue,$replacements)){
$entry->previousSibling->previousSibling->nodeValue = $replacements[$entries->nodeValue];
}
}
But i don't understand why it doesn't output anything

There's a couple things a bit off here:
array_key_exists($entry->nodeValue,$replacements)
The nodeValue is going to contain all the text content within the property element and its descendants - including newlines and spaces.
... = $replacements[$entries->nodeValue];
You're likely looking for $replacements[$entry->nodeValue] but again you've got the same problem as above with the white space. Not only that but you'd be replacing the entirety of the prior property's nodeValue with text so:
<property name="category">
<value>Fruits</value>
</property>
would become just:
<property name="category">Vegetable</property>
To fix it all up I'd recommend adjusting the query, as well as how you're addressing the target value to replace in order to get rid of the ->previousSibling->previousSibling chaining and be a bit more explicit.
Example:
foreach ($xpath->query('//property[#name="item"]/value') as $node) {
if (array_key_exists(trim($node->textContent), $replacements)) {
$target = $xpath->query(
'preceding-sibling::property/value/text()',
$node->parentNode
)->item(0);
$target->parentNode->replaceChild(
$dom->createTextNode($replacements[trim($node->textContent)]),
$target
);
}
}
echo $dom->saveXML();
Output:
...
<product>
<properties>
<property name="category">
<value>Vegetable</value>
</property>
<property name="item">
<value>Carrot</value>
</property>
</properties>
</product>
</products>

You need to return Value and in order to do that you need to correct your query to this
$query = '//property[#name = "item"]/value';

You can try to use the complete path, so:
$query = '//products/product/properties/property[#name = "item"]/value';
$entries = $xpath->query($query);
foreach ($entries as $entry) {
if(array_key_exists($entry->nodeValue,$replacements)){
$entry->previousSibling->previousSibling->nodeValue = $replacements[$entries->nodeValue];
}
}
Also, you can try to use a context (Manual):
$context = $document->getElementsByTagName('products')->item(0);
$query = './/property[#name = "item"]/value';//add a '.' before the query to make it relative to the context
$entries = $xpath->query($query, $context);
foreach ($entries as $entry) {
if(array_key_exists($entry->nodeValue,$replacements)){
$entry->previousSibling->previousSibling->nodeValue = $replacements[$entries->nodeValue];
}
}
Both solutions work for me in very similar situations.

Related

How to get the xml sub-parameters

How to get the xml sub-parameters?
how to get all the results of category and return a result $category .. category = Test1, Test2
my xml
<xml>
<title>Test 123</title>
<categories>
<category>Test1</category>
<category>Test2</category>
</categories>
</xml>
my get code
$category = htmlspecialchars($item->categories->category, ENT_XML1 | ENT_QUOTES, 'UTF-8');
echo $category; //I want to return Test1, Test2
You could loop $item->categories->category using a foreach:
$source = <<<DATA
<xml>
<title>Test 123</title>
<categories>
<category>Test1</category>
<category>Test2</category>
</categories>
</xml>
DATA;
$item = simplexml_load_string($source);
foreach ($item->categories->category as $elm) {
echo $elm . PHP_EOL;
}
That will give you:
Test1
Test2
See a php demo
This is what are you searching for?
$xml = new SimpleXMLElement('<item id="1234">
<property name="country_id">
<value>4402</value>
</property>
<property name="rc_maintenance_other">
</property>
<property name="claim_right_shareholder">
</property>
<property name="charges_other">
</property>
<property name="other_expenses_heating">
</property>
<property name="unpaid_bills_amount">
</property>
<property name="iv_person_phone">
<value>03-6756711</value>
</property>
</item>
');
foreach ($xml->xpath('//item[#id="1234"]') as $item)
{
foreach ($item->children() as $child) {
echo $child['name'] ."\n";
}
}
duplicate question?
Access all children of a certain node with simplexml and php

How to read large XML node of SimpleXML

Can anyone help me with read large file?
I reading and item like that:
$xmlReader = new XMLReader();
$xmlReader->open($path);
while ($xmlReader->read() && $xmlReader->name !== 'item') ;
while ($xmlReader->name == 'item')
{
$node = new SimpleXMLElement($xmlReader->readOuterXML());
foreach($node->properties as $property)
{
var_dump($property->price); //empty class SimpleXMLElement
var_dump($property->attributes()); //empty class SimpleXMLElement
}
$xmlReader->next('item');
}
$xmlReader->close();
So i cant to read $property->price because i receive empty class SimpleXMLElement
The same situation with attribute sku i also receive empty class SimpleXMLElement
And i can't convert in (string) because i receive empty string.
How to read the children node?
Source XML:
<items>
<item>
<code>be274178-9039-11e6-86d0-001e6727034e</code>
<delete>0</delete>
<title>Полотенцесушитель М-обр. 500*500 нар. р. 1" арт.00004-5050</title>
<category>13760cb9-8f7b-11e6-86d0-001e6727034e</category>
<producer>5a457cfd-b088-11e2-9c54-001e6727034e</producer>
<properties>
<property sku="40 451">
<price>2831.00</price>
<characteristics>
<characteristic>
<title>Свойство</title>
<value>накопление</value>
<filter>0</filter>
<visible>0</visible>
</characteristic>
<characteristic>
<title>Форма</title>
<value>М-образный</value>
<filter>1</filter>
<visible>1</visible>
</characteristic>
</characteristics>
</property>
<property sku="40 464">
<price>3442.00</price>
<characteristics>
<characteristic>
<title>Свойство</title>
<value>накопление</value>
<filter>0</filter>
<visible>0</visible>
</characteristic>
</characteristics>
</property>
</properties>
</item>
</items>
It should be
foreach($node->properties->property as $property)
because it's only one <properties> elements but many <property> elements.
See Example #4 Accessing non-unique elements in SimpleXML in the PHP Manual:
When multiple instances of an element exist as children of a single parent element, normal iteration techniques apply.
<?php
include 'example.php';
$movies = new SimpleXMLElement($xmlstr);
/* For each <character> node, we echo a separate <name>. */
foreach ($movies->movie->characters->character as $character) {
echo $character->name, ' played by ', $character->actor, PHP_EOL;
}
?>

Replace value in XML feed with php xpath

I (conceptually) understand which steps i have to take, but i can't translate it to a working code.
I have a XML feed with a structure like this:
<item id="1">
<properties>
<property name="region">
<value>Cote d'azur</value>
</property>
</properties>
</item>
<item id="2">
<properties>
<property name="region">
<value>Côte d'Azur</value>
</property>
</properties>
</item>
What i need is the feed to use consequent names, so i have to loop through each property with the name attribute and replace the value, but how?
So far i'm here, but this doesn't work
$xml_src = 'feed.xml';
$document = new DOMDocument();
$document->load($xml_src);
$xpath = new DOMXpath($document);
$regions = $xpath->evaluate('//property[#name = "region"]');
foreach($regions as $region){
$newregion = $document->createElement('value', str_replace("Cote d'azur","Côte d'Azur",$region->nodeValue));
$region->parentNode->replaceChild($newregion, $region);
}
echo $document->saveXml();
I get this error:
Warning: DOMDocument::createElement(): unterminated entity reference Ylläs in .. on line 17
Line 17:
$newregion = $document->createElement('value', str_replace("Cote d'azur","Côte d'Azur",$region->nodeValue));
To make it even more complicated, i sometimes have three value elements in each property with the name city. In that case i need to select the third element.
I hope anybody can help me out
The error message is from a bug in PHP. Do not use the second argument for DOMDocument::createElement(). Create and append a text node to make sure that special characters are escaped into entities.
https://stackoverflow.com/a/27225157/2265374
Anything in a DOM is a node. Not only the element, but attribute and texts, too. You can work on the text nodes inside the value elements directly:
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
foreach ($xpath->evaluate('//property[#name = "region"]/value/text()') as $text) {
$text->data = str_replace("Cote d'azur","Côte d'Azur", $text->data);
}
echo $document->saveXml();
Simple:
$regions = $xpath->query('//property[#name = "region"]/value');
foreach($regions as $region){
$region->nodeValue = str_replace("Cote d'azur","Côte d'Azur",$region->nodeValue);
}
echo $document->saveXml();

Parsing XML data using php to put into mysql database

I have been asked to parse a simple file which is stored as an XML file, the data is to be then put into a mysql database.
However I have absolutely no clue what to do and after looking online all the examples given seem either too complicated for my problem or not the right solution. The XML file looks like this:
<shop>
<products>
<product id="1" name="Cornetto" price="1.20" description="Traditional Cornetto" />
<product id="2" name="Smarties" price="1.00" description="Smarties Icecream" />
</products>
<stocks>
<stock id="1" amount="242" price="pounds" />
<stock id="2" amount="11" price="pounds" />
</stocks>
I've tried looking at SimpleXML and I think that's the direction I have to go but I just have no idea.
Any help or pointers would be great.
I personally like the normal XMl formatting so I changed it since its a bit more readable but this is how you can use it:
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<shop>
<products>
<product>
<id>1</id>
<name>Cornetto</name>
<price>1.20</price>
<description>Traditional Cornetto</description>
</product>
<product>
<id>2</id>
<name>Smarties</name>
<price>1.00</price>
<description>Smarties Icecream</description>
</product>
</products>
<stocks>
<stock>
<id>1</id>
<amount>242</amount>
<price>pounds</price>
</stock>
<stock>
<id>2</id>
<amount>11</amount>
<price>pounds</price>
</stock>
</stocks>
</shop>
XML;
Handling part:
$xml = new SimpleXMLElement($xmlstr);
echo 'single value: <br />';
echo $xml->products->product[0]->id; // get single value
echo '<br /><br />';
//Loop trough multiple products
echo 'multiple values: <br />';
foreach($xml->products->product as $product)
{
echo $product->id.' - ';
echo $product->name.' - ';
echo $product->price.' - ';
echo $product->description;
echo '<br/>';
}
Assuming the file is called data.xml
$string = file_get_contents('data.xml') reads the entire file into $string.
$xml = new SimpleXMLElement($string); parses that string, and converts it into an object tree similar to the actual document. So if that's the document -
<root>
<b>
<c>first</c>
<c>second</c>
</b>
</root>
The SimpleXMLElement object would be used like:
$xml->b // gets all children of b (c[0] and c[1])
print $xml->b->c[0] // gets the first c, will print "first"
You can use for example SimpleXMLElement and xpath
<?php
$xmlStr = <<<EOF
<?xml version="1.0"?>
<shop>
<products>
<product id="1" name="Cornetto" price="1.20" description="Traditional Cornetto" />
<product id="2" name="Smarties" price="1.00" description="Smarties Icecream" />
</products>
<stocks>
<stock id="1" amount="242" price="pounds" />
<stock id="2" amount="11" price="pounds" />
</stocks>
</shop>
EOF;
$xml=new SimpleXMLElement($xmlStr);
// get product line with xpath for example
$products=$xml->xpath("/shop/products/product");
if ($products) {
// loop over each product node
foreach ($products as $product) {
// do whatever you want with the data
echo("id=>".$product["id"].", name=>".$product["name"]."<br/>");
}
}
// same for stock
// get product line with xpath for example
$stocks=$xml->xpath("/shop/stocks/stock");
if ($stocks) {
// loop over each product node
foreach ($stocks as $stock) {
// do whatever you want with the data
echo("id=>".$stock["id"].", amount=>".$stock["amount"]."<br/>");
}
}
?>
$xml = simplexml_load_file($filename);
foreach($xml->product as $product) {
foreach($product->attributes() as $name => $attribute) {
echo "$name = $attribute";
}
}
$xml = simplexml_load_file($filename);
foreach($xml->products->product as $not)
{
foreach($not->attributes() as $a => $b)
{
echo $a,'="',$b,"\"<br />";
}
}

Have XML results in plaintext, want to loop through them in PHP

So I am working with an API that returns results in XML. Let's just say for argument sake I am returned the following:
<?xml version="1.0" encoding="UTF-8"?>
<Properties>
<Property>
<Name>Joes Crab Shack</Name>
<Address>111 Shack Street</Address>
</Property>
<Property>
<Name>Johns Shoe Store</Name>
<Address>123 Shoe Avenue</Address>
</Property>
</Properties>
Now I am using PHP and I get the results into a variable. So essentially this happens:
$xml_results = '<?xml version="1.0" encoding="UTF-8"?><Properties><Property><Name>Joes Crab Shack</Name><Address>111 Shack Street</Address></Property><Property><Name>Johns Shoe Store</Name><Address>123 Shoe Avenue</Address></Property></Properties>';
Now how can I treat this as an XML document and for example loop through it and print out all property names?
Something like this should get the job done.
$request_xml = '<?xml version="1.0" encoding="UTF-8"?>
<Properties>
<Property>
<Name>Joes Crab Shack</Name>
<Address>111 Shack Street</Address>
</Property>
<Property>
<Name>Johns Shoe Store</Name>
<Address>123 Shoe Avenue</Address>
</Property>
</Properties>';
$xml = simplexml_load_string($request_xml);
$i = 0;
while ($xml->Property[$i])
{
echo $xml->Property[$i]->Name;
echo $xml->Property[$i]->Address;
$i++;
}
Deserialize into an xml tree, try SimpleXML. That way you can access that data in a more convenient fashion and grab specific xml elements..

Categories