Showing Two elements from XML file - php

The file can be seen on the following URL: #
The above XML file consists of two elements which I want to be shown in order which are the "product" and "offer".
I use SimpleXML to load the XML feed.
$text = simplexml_load_file('feed.xml');
I also use foreach to show the data from the file
foreach ($text->categories->category->items->product as $product) {}
How is it possible to show the "product" and "offer" from the XML file using a for each statement or any other method?

Does <items> only ever contain <product> and <offer> elements? If so:
foreach ($text->categories->category->items->children() as $product_or_offer) {
// Do something
}
See http://php.net/simplexmlelement.children
If you want to be explicit about only ever getting the product/offer elements, a simple XPath expression could be employed.
$items = $text->categories->category->items;
$items->registerXPathNamespace('so', 'urn:types.partner.api.url.com');
foreach ($items->xpath('so:offer|so:product') as $product_or_offer) {
// Do something
}

Although I'm still don't really get what you would like to obtain I will post some examples of how to deal with this XML via xPath.
First of all select all product and offer nodes:
$xml = simplexml_load_file('feed.xml');
// Make sure to register custom namespace
$xml->registerXPathNamespace('ns', 'urn:types.partner.api.url.com');
$products = $xml->xpath('//ns:product');
$offers = $xml->xpath('//ns:offer');
echo count($products); // Number of all product nodes
echo count($offers); // Number of offer nodes
Basic iteration:
foreach ($products as $product) {
//echo '<pre>'; print_r($product); echo '</pre>';
echo '<pre>'; echo $product->name . ', ' . $product->minPrice; echo '</pre>';
}

Related

PHP get nodes value with nested nodes XML

I have a xml file:
<Epo>
<Doc upd="add">
<Fld name="IC"><Prg><Sen>A01B1/00 <Cmt>(1585, 779)</Cmt></Sen></Prg></Fld>
<Fld name="CC"><Prg><Sen>A01B1/00 <Cmt>(420, 54%)</Cmt>;</Sen><Sen>B25G1/102 <Cmt>(60, 8%)</Cmt>;</Sen><Sen>A01B1/02 <Cmt>(47, 6%)</Cmt></Sen></Prg></Fld></Doc>
<Doc upd="add">
<Fld name="IC"><Prg><Sen>A01B1/02 <Cmt>(3847, 1718)</Cmt></Sen></Prg></Fld>
<Fld name="CC"><Prg><Sen>A01B1/02 <Cmt>(708, 41%)</Cmt>;</Sen><Sen>A01B1/022 <Cmt>(347, 20%)</Cmt>;</Sen><Sen>A01B1/028 <Cmt>(224, 13%)</Cmt></Sen></Prg></Fld></Doc>
</Epo>
I want to get node value, for example : A01B1/00 (1585, 779) - A01B1/00 (420, 54%); B25G1/102 (60, 8%); A01B1/02 (47, 6%)
Then formating them into table's column. how can I do that?
My code:
<?php
$doc = new DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->load('test.xml'); //IPCCPC-epoxif-201905
$xpath = new DOMXPath($doc);
$titles = $xpath->query('//Doc/Fld');
foreach ($titles as $title){
echo $title->nodeValue ."<hr>";
}
?>
I cannot separate evrey node. Please help me.
I've tried to split it down to fetch all the various levels of content, but I think the main problem was just getting the current node text without the child elements text content. Using DOMDocument, the nodeValue is the same as textContent which (from the manual)...
textContent The text content of this node and its descendants.
Using DOMDocument isn't the easiest to use when just accessing a relatively simple hierarchy and requires you to continually make calls (in this case) to getElementsByTagName() to fetch the enclosed elements, the following source shows how you can get at each part of the document using this method...
foreach ( $doc->getElementsByTagName("Doc") as $item ) {
echo "upd=".$item->getAttribute("upd").PHP_EOL;
foreach ( $item->getElementsByTagName("Fld") as $fld ) {
echo "name=".$fld->getAttribute("name").PHP_EOL;
foreach ( $fld->getElementsByTagName("Sen") as $sen ) {
echo trim($sen->firstChild->nodeValue) ." cmt = ".
$sen->getElementsByTagName("Cmt")[0]->firstChild->nodeValue.PHP_EOL;
}
}
}
Using the SimpleXML API can however give a simpler solution. Each level of the hierarchy is accessed using object notation, and so ->Doc is used to access the Doc elements off the root node, and the foreach() loops just work off that. You can also see that using just the element name ($sen->Cmt) will give you just the text content of that node and not the descendants (although you have to cast it to a string to get it's value from the object) ...
$doc = simplexml_load_file("test.xml");
foreach ( $doc->Doc as $docElemnt ) {
echo "upd=".(string)$docElemnt['upd'].PHP_EOL;
foreach ( $docElemnt->Fld as $fld ) {
echo "name=".(string)$fld['name'].PHP_EOL;
foreach ( $fld->Prg->Sen as $sen ) {
echo trim((string)$sen)."=".trim((string)$sen->Cmt).PHP_EOL;
}
}
}

Xpath looping query

I have the following xml doc:
<shop id="123" name="xxx">
<product id="123456">
<name>Book</name>
<price>9.99</price
</product>
<product id="789012">
<name>Perfume</name>
<price>12.99</price
</product>
<product id="345678">
<name>T-Shirt</name>
<price>9.99</price
</product>
</shop>
<shop id="456" name="yyy">
<product id="123456">
<name>Book</name>
<price>9.99</price
</product>
</shop>
I have the following loop to gather the information for each product:
$data_feed = 'www.mydomain.com/xml/compression/gzip/';
$xml = simplexml_load_file("compress.zlib://$data_feed");
foreach ($xml->xpath('//product') as $row) {
$id = $row["id"]; // product id eg. "123456"
$name = $row->name;
$price = $row->price;
// update database etc.
}
HOWEVER, I also want to gather the information for each product's parent shop ("id" and "name").
I can easily change my xpath to start from shop as opposed to product, but I'm unsure of the most efficient way to then construct an additional loop within my foreach to loop each indented product
Make sense?
I'd go without xpath and just use two nested foreach-loops:
$xml = simplexml_load_string($x); // assume XML in $x
foreach ($xml->shop as $shop) {
echo "shop $shop[name], id $shop[id] <br />";
foreach ($shop->product as $product) {
echo "- $product->name (id $product[id]), $product->price <br />";
}
}
see it working: http://codepad.viper-7.com/vFmGvY
BTW: your XML is broken, probably a typo. Each closing </price> is missing its last >.
Sure, makes sense, you want one iteration, not a nested product of iterations (albeit that won't cut you much, #michi showed already), which is possible as well:
foreach ($xml->xpath('//product') as $row)
{
$id = $row["id"]; // product id eg. "123456"
$name = $row->name;
$price = $row->price;
$shopId = $row->xpath('../#id')[0];
$shopName = $row->xpath('../#name')[0];
// update database etc.
}
As this example shows, you can run xpath() on each element-node and the context-node is automatically set to the node itself, therefore the realtive path .. in xpath works to access the parent element (see as well: Access an element's parent with PHP's SimpleXML?). Of that then both attributes are read and then via PHP 5.4 array de-referencing the first (and only) attribute is accessed.
I hope this helps and shed some light how it works. Your question reminds me a bit of an earlier one where I suggested some kind of generic solution to these kind of problems:
Answer to Combining two Xpaths into one loop?

Simplexml Object Node Iteration

I have an XML file that I'm parsing with PHP's Simplexml, but I'm having an issue with an iteration through nodes.
The XML:
<channel>
<item>
<title>Title1</title>
<category>Cat1</category>
</item>
<item>
<title>Title2</title>
<category>Cat1</category>
</item>
<item>
<title>Title3</title>
<category>Cat2</category>
</item>
</channel>
My counting function:
public function cat_count($cat) {
$count = 0;
$items = $this->xml->channel->item;
$size = count($items);
for ($i=0; $i<$size; $i++) {
if ($items[$i]->category == $cat) {
$count++;
}
}
return $count;
}
Am I overlooking an error in my code, or is there another preferred method for iterating through the nodes? I've also used a foreach and while statement with no luck, so I'm at a loss. Any suggestions?
EDIT: while using the xpath method below, I noticed that using
foreach ($this->xml->channel->item as $item) {
echo $item->category;
}
will print all the category name, but, using
foreach ($this->xml->channel->item as $item) {
if ($item->category == $cat) {
echo $item->category;
}
}
will only print one instance of the doubled categories. Even when I have copy and pasted the lines, only one shows. Does this mean the XML structure could be invalid somehow?
An easy way to count elements with a given name in an XML file is to use xpath. Try this:
private function categoryCount($categoryName) {
$categoryName = $this->sanitize($categoryName); // easy xpath injection protection
return count($this->xml->xpath("//item[category='$categoryName']"));
}
The sanitize() function should remove single and double quotes in your $categoryName to prevent xpath injection. To also get queries for a category name containing quotes to work, you need to build your xpath query string depending on wheather it contains single or double quotes:
// xpath in case of single quotes in category name
$xpath = '//item[category="' . $categoryName . '"]';
// xpath in case of double quotes in category name
$xpath = "//item[category='" . $categoryName . "']";
If you don't have full control over the xml data (for example if is created out of user generated content), you should take this into account. Unfortunately there is no simple way to this in php like parametrized queries.
see here for the php xpath function docs: http://php.net/manual/en/simplexmlelement.xpath.php
see here for an xpath reference: http://www.w3schools.com/xpath/xpath_syntax.asp

How to iterate through an XML element node with dynamic children

I currently have the following XML structure:
<root>
<maininfo>
<node>
<tournament_id>3100423</tournament_id>
<games>
<a_0>
<id>23523636</id>
<type>
<choice_4>
<choice_id>345</choice_id>
<choice_4>
<choice_9>
<choice_id>345</choice_id>
<choice_9>
... etc
</type>
</a_0>
<a_1></a_1>
<a_2></a_2>
...etc
</games>
</info>
</node>
</root>
I can easily get the id of the first node element "a_0" by just doing:
maininfo[0]->a_3130432[0]->games[0]->a_1[0]->id;
My issue is:
How do I automatically iterate (with a foreach) through all a_0, a_1, a_2 and get the values of each of these node elements and all of their children like "345" in <choice_id>345</choice_id>?
The ending numbers of a_0, a_1 + the children of choice_4, choice_9, are dynamically created and there are no logic in the _[number] counting up with +1 for each next element.
As it has been outlined previously on Stackoverflow (for example in Read XML dynamic PHP) and as well generally in the PHP manual (for example in Basic SimpleXML usage), you can iterate over all child elements by using foreach.
For example to go over all a_* elements, it's just
foreach ($xml->maininfo->node->games[0] as $name => $a) {
echo $name, "\n";
}
Output:
a_0
a_1
a_2
You then want to iterate over these their ->type children again. This is possible in pure PHP by putting one foreach into a another:
foreach ($xml->maininfo->node->games[0] as $name => $a) {
echo $name, "\n";
if (!$a->type[0]) {
continue;
}
foreach ($a->type[0] as $name => $choice) {
echo ' +- ', $name, "\n";
}
}
This now outputs:
a_0
+- choice_4
+- choice_9
a_1
a_2
This starts to get a bit complicated. As you can imagine since XML is famous for it's tree structures, you're not the first one running into this problem. Therefore a query-language to get elements from an XML document has been invented: Xpath.
With Xpath you can access XML data as if it was a file-system. As I know that each a_* element is a child of games and each choice_* element a child of type, it's pretty straight forward:
/*/maininfo/node/games/*/type/*
^ ^ ^
| | choice_*
root |
a_*
In PHP Simplexml this looks like:
$choices = $xml->xpath('/*/maininfo/node/games/*/type/*');
foreach ($choices as $choice) {
echo $choice->getName(), ': ', $choice->choice_id, "\n";
}
Output:
choice_4: 345
choice_9: 345
As this example shows, the data is now retrieved with a single foreach.
If you as well need access to the <a_*> elements, you need to have multiple foreach's or your own iteration but that is even a more advanced topic which I'd say would extend over the limits of your question.
I hope this is helpful so far. See as well SimpleXMLElement::children() which also gives all children (like ->games[0] in the first example). All example codes are as well available as a working, interactive online-demo.
If I understand it well, you can do something like:
for($i = 0; $i < $max; ++$i){
$a = $parentNode->{'a_'.$i};
}
You can do this very easily using SimpleXML :
<?php
$xmlStr = "<?xml version='1.0' standalone='yes'?>
<root>
<maininfo>
<node>
<tournament_id>3100423</tournament_id>
<games>
<a_0>
<id>23523636</id>
<type>
<choice_4>
<choice_id>345</choice_id>
</choice_4>
<choice_9>
<choice_id>345</choice_id>
</choice_9>
</type>
</a_0>
<a_1></a_1>
<a_2></a_2>
</games>
</node>
</maininfo>
</root>";
$xmlRoot = new SimpleXMLElement($xmlStr);
$i = 0;
foreach($xmlRoot->maininfo[0]->node[0]->games[0] as $a_x)
{
echo $i++ . " - " . htmlentities($a_x->asXML()) . "<br/>";
}
?>
I have modified some parts of your XML string to make it syntactically correct. You can view the results at http://phpfiddle.org/main/code/56q-san

rss feed xml: cannot access images after converting rss feed to xml object

In the http://feeds.feedburner.com/rb286, there are many images. However, when i convert it into and xml object with simplXmlElement, i'm not able to see the images.My code:
if (function_exists("curl_init")){
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,"http://feeds.feedburner.com/rb286");
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$data=curl_exec($ch);
curl_close($ch);
//print_r($data); //here i'm able to see the images
$doc=new SimpleXmlElement($data);
print_r($doc); //here i'm not able to see the images
}
Can someone tell me on how can I access the images after converting to xml object? thank you.
You will have to iterate trough the <content:encoded> tags of the individual <items> in the <channel> main tag. I would use the xpath method to select the tags. Once you get the element you want you can grep the <img> out of them with string manipulation tools like preg_match_all:
Edit: added more refined image tag matching, that excludes ads from feedburner and other cdns.
$xml = simplexml_load_string(file_get_contents("http://feeds.feedburner.com/rb286"));
foreach ($xml->xpath('//item/content:encoded') as $desc) {
preg_match_all('!(?<imgs><img.+?src=[\'"].*?http://feeds.feedburner.com.+?[\'"].+?>)!m', $desc, $>
foreach ($m['imgs'] as $img) {
print $img;
}
}
The <content:encoded> tag is namespaced, so if you want to use simplexml's built in property mapping, you have to deal with it like this:
// obtain simplexml object of the feed as before
foreach ($xml->channel->item as $item) {
$namespaces = $item->getNameSpaces(true);
$content = $item->children($namespaces['content']);
print $content->encoded; // use it howevery you want
}
You can read more from the xpath query language here.

Categories