How to read large XML node of SimpleXML - php

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;
}
?>

Related

Change previous sibling value with php xPath

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.

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

PHP xpath get elements by attribute in foreach loop

im am trying to loop through all the LINE_ITEMS and it works fine but in the foreach-loop im trying to access the single LINE_ITEM by attribute by using xpath but i don't get any result. Does anyone know the problem ?
foreach($items = $xmlData->xpath('//zw:LINE_ITEM') as $item) {
$item->xpath('//namespace:PID[#type="erp_pid"]'); No result
}
$xmlData->xpath('//zw:LINE_ITEM')
works fine i get all LINE_ITEMS but
when i try to do some xpath on the item i don't get any result.
How can i access the PID value of for example "erp_pid" ?
<?xml version="1.0" encoding="UTF-8"?>
<NM_DOCS>
<NM_DOC>
<DOCUMENT qualifier="default" role="original" test="false" type="orders">
<VERSION>4.0</VERSION>
<HEADER>
<CONTROL_INFO>
<LAST_SAVE_DATE>2015-03-18T13:44:32+01:00</LAST_SAVE_DATE>
<PROCESS_TYPE>silent</PROCESS_TYPE>
<SOURCE>sales</SOURCE>
</CONTROL_INFO>
<SOURCING_INFO>
<REFERENCES>
<REFERENCE type="order_nexmart">
<ID>109546063</ID>
<DATES>
<DATE type="order">2015-03-18T13:44:30+01:00</DATE>
</DATES>
</REFERENCE>
</REFERENCES>
</SOURCING_INFO>
<DOCUMENT_INFO>
<DOCUMENT_ID>Test bestellnummer</DOCUMENT_ID>
<DATES>
<DATE type="delivery_ordered">2015-04-19T12:41:41+02:00</DATE>
</DATES>
<PARTIES>
<PARTY type="buyer">
<BUSINESS_ROLE>commercial</BUSINESS_ROLE>
<PORTAL_ID>BDE600028</PORTAL_ID>
<ADDITIONAL_IDS>
</ADDITIONAL_IDS>
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
<ACCOUNTS>
<ID type="emart">sales.nexmart</ID>
</ACCOUNTS>
</CONTACT_DETAILS>
</PARTY>
<PARTY type="supplier">
<BUSINESS_ROLE>commercial</BUSINESS_ROLE>
<PORTAL_ID>zweygart_app_de</PORTAL_ID>
<ADDITIONAL_IDS>
</ADDITIONAL_IDS>
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
</CONTACT_DETAILS>
</PARTY>
<PARTY type="delivery">
<BUSINESS_ROLE>commercial</BUSINESS_ROLE>
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
</CONTACT_DETAILS>
</PARTY>
<PARTY type="invoice_recipient">
<ADDRESS>
</ADDRESS>
<CONTACT_DETAILS>
</CONTACT_DETAILS>
</PARTY>
</PARTIES>
<REMARKS>
<REMARK type="order">Test bemerkung</REMARK>
</REMARKS>
</DOCUMENT_INFO>
</HEADER>
<LINE_ITEMS>
<LINE_ITEM>
<PRODUCT_ID>
<SUPPLIER_PID>119556</SUPPLIER_PID>
<GTIN>4030646269130</GTIN>
<ADDITIONAL_PIDS>
<PID type="supplier_pid_original">119556</PID>
<PID type="gtin_original">4030646269130</PID>
<PID type="erp_pid">119556</PID>
</ADDITIONAL_PIDS>
<DESCRIPTIONS>
<DESCR type="short">short desc</DESCR>
<DESCR type="short_original">some text</DESCR>
</DESCRIPTIONS>
</PRODUCT_ID>
</LINE_ITEM>
<LINE_ITEM>
<PRODUCT_ID>
<SUPPLIER_PID>123456789</SUPPLIER_PID>
<GTIN>123456789</GTIN>
<ADDITIONAL_PIDS>
<PID type="supplier_pid_original">123456</PID>
<PID type="gtin_original">123456</PID>
<PID type="erp_pid">123456</PID>
</ADDITIONAL_PIDS>
<DESCRIPTIONS>
<DESCR type="short">short desc</DESCR>
<DESCR type="short_original">Some description</DESCR>
</DESCRIPTIONS>
</PRODUCT_ID>
</LINE_ITEM>
</LINE_ITEMS>
</DOCUMENT>
</NM_DOC>
</NM_DOCS>
The problem is not XPath but SimpleXML. SimpleXMLElement::xpath() is limited. It converts the result into an array of SimpleXMLElement objects, but here are other nodes in a DOM. More important you will have to register the namespaces on each new SimpleXMLElement again.
$element = new SimpleXMLElement($xml);
$element->registerXPathNamespace('namespace', 'urn:foo');
foreach($element->xpath('//namespace:LINE_ITEMS/namespace:LINE_ITEM') as $item) {
$item->registerXPathNamespace('namespace', 'urn:foo');
var_dump((string)$item->xpath('.//namespace:PID[#type="erp_pid"]')[0]);
}
Output:
string(6) "119556"
string(6) "123456"
You might notice that I prefixed your detail expression with an .. A slash at the start of the expression always makes it relative to the document itself, not the current node. The . represents the current node.
If you use DOM directly, you create a separate DOMXPath object and register the namespaces on this object. Additionally you can use XPath expressions that return scalar values.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('namespace', 'urn:foo');
foreach($xpath->evaluate('//namespace:LINE_ITEMS/namespace:LINE_ITEM') as $node) {
var_dump($xpath->evaluate('string(.//namespace:PID[#type="erp_pid"])', $node));
}
This works for me:
foreach($xmlData->LINE_ITEM as $item) {
$erp = ( $item->xpath('//PID[#type="erp_pid"]'));
foreach($erp as $v) {
echo $v. " / ";
}
}
Just remove the namespace in your xpath, your xml doesn't use a namespace.
If you want to iterate through a part of the xml be sure to use the correct path.

Parse XML in PHP by specific attribute

I need to get <name> and <URL> tag's value where subtype="mytype".How can do it in PHP?
I want document name and test.pdf path in my result.
<?xml version="1.0" encoding="UTF-8"?>
<test>
<required>
<item type="binary">
<name>The name</name>
<url visibility="restricted">c:/temp/test/widget.exe</url>
</item>
<item type="document" subtype="mytype">
<name>document name</name>
<url visiblity="visible">c:/temp/test.pdf</url>
</item>
</required>
</test>
Use SimpleXML and XPath, eg
$xml = simplexml_load_file('path/to/file.xml');
$items = $xml->xpath('//item[#subtype="mytype"]');
foreach ($items as $item) {
$name = (string) $item->name;
$url = (string) $item->url;
}
PHP 5.1.2+ has an extension called SimpleXML enabled by default. It's very useful for parsing well-formed XML like your example above.
First, create a SimpleXMLElement instance, passing the XML to its constructor. SimpleXML will parse the XML for you. (This is where I feel the elegance of SimpleXML lies - SimpleXMLElement is the entire library's sole class.)
$xml = new SimpleXMLElement($yourXml);
Now, you can easily traverse the XML as if it were any PHP object. Attributes are accessible as array values. Since you're looking for tags with specific attribute values, we can write a simple loop to go through the XML:
<?php
$yourXml = <<<END
<?xml version="1.0" encoding="UTF-8"?>
<test>
<required>
<item type="binary">
<name>The name</name>
<url visibility="restricted">c:/temp/test/widget.exe</url>
</item>
<item type="document" subtype="mytype">
<name>document name</name>
<url visiblity="visible">c:/temp/test.pdf</url>
</item>
</required>
</test>
END;
// Create the SimpleXMLElement
$xml = new SimpleXMLElement($yourXml);
// Store an array of results, matching names to URLs.
$results = array();
// Loop through all of the tests
foreach ($xml->required[0]->item as $item) {
if ( ! isset($item['subtype']) || $item['subtype'] != 'mytype') {
// Skip this one.
continue;
}
// Cast, because all of the stuff in the SimpleXMLElement is a SimpleXMLElement.
$results[(string)$item->name] = (string)$item->url;
}
print_r($results);
Tested to be correct in codepad.
Hope this helps!
You can use the XML Parser or SimpleXML.

php - simpleXML help

I have this XML code :
<?xml version="1.0"?>
<Days>
<day value="1">
<Imsaak>04:59</Imsaak>
<Fajr>05:09</Fajr>
<Sunrise>06:23</Sunrise>
<Dhuhr>12:39</Dhuhr>
<Asr>16:12</Asr>
<Sunset>18:55</Sunset>
<Maghrib>19:10</Maghrib>
<Isha>20:04</Isha>
</day>
<day value="2">
<Imsaak>04:58</Imsaak>
<Fajr>05:08</Fajr>
<Sunrise>06:22</Sunrise>
<Dhuhr>12:39</Dhuhr>
<Asr>16:12</Asr>
<Sunset>18:56</Sunset>
<Maghrib>19:11</Maghrib>
<Isha>20:05</Isha>
</day>
</Days>
and I want to select <day> node depending on the attribute value
I am using SimpleXMLElement class but I don't how to select with arrtibute value.
how I can do that??
EDIT: my code :
include 'days.xml';
$xml = new SimpleXMLElement($xmlstr);
foreach ($xml->day as $day) {
// process data
}
from php manual SimpleXMLElement::attributes (little bit edited)
Considering this data:
<?xml version="1.0" encoding="utf-8"?>
<data>
<item ID="30001">
<Company>Navarro Corp.</Company>
</item>
<item ID="30002">
<Company>Performant Systems</Company>
</item>
<item ID="30003">
<Company>Digital Showcase</Company>
</item>
</data>
Example of listing both the ID Attribute and Company Element values:
<?php
$xmlObject = new SimpleXMLElement($xmlstring);
foreach ($xmlObject->children() as $node) {
$arr = $node->attributes(); // returns an array
if(in_array("30002", $arr)){ // search the value of an attribute
print ("Company=".$node->Company);
}
//depending of your needs, you could use a switch / case instead of use an if
}
?>
$xml = new SimpleXMLElement($xmlStr)
$xml->day[0]->attribute()->value;//will echo out 1
of course you can loop through all of the day like this:
foreach($sml->day as $day){
$day->attribute()->value; //will trace out 1 and then 2
}

Categories