Create nested list with PHP from XML dataset - php

I'm trying to build an unordered list from an existing XML file which contains categories and subcategories + images. I need each subcategory to be an item in an unordered list with each related image in a nested list. The subcategories should be shown only once (must not repeat if it's the same). I believe what I'm looking for is a recursive loop, but that's where I get lost.
Essentially, I'm trying to achieve a look similar to this:
--------------DOGS-------------
[..........small..........]
[picture] [picture]
[..........large..........]
[picture]
--------------CATS-------------
[..........medium..........]
[picture]
[..........large..........]
[picture] [picture]
My XML file structure:
<root>
<animal>
<category>DOGS</category>
<subcategory>small</subcategory>
<name>Terrier</name>
<image>aaa.jpg</image>
</animal>
<animal>
<category>-</category>
<subcategory>small</subcategory>
<name>Havanese</name>
<image>bbb.jpg</image>
</animal>
<animal>
<category>-</category>
<subcategory>large</subcategory>
<name>Dalmatian</name>
<image>ccc.jpg</image>
</animal>
<animal>
<category>CATS</category>
<subcategory>medium</subcategory>
<name>Abyssinian</name>
<image>ddd.jpg</image>
</animal>
<animal>
<category>-</category>
<subcategory>large</subcategory>
<name>Birman</name>
<image>eee.jpg</image>
</animal>
<animal>
<category>-</category>
<subcategory>large</subcategory>
<name>American Shorthair</name>
<image>fff.jpg</image>
</animal>
</root>
Here's what I have attempted so far:
<?php
$xml = simplexml_load_file("file.xml");
$categories = array();
$subcategories = array();
$names = array();
$image = array();
foreach($xml->animal as $animals) {
$category = $animals->category;
$subcategory = $animals->subcategory;
$name = $animals->name;
$image = $animals->image;
$categories[] = $category;
$subcategories[] = $subcategory;
$names[] = $name;
$images[] = $image
}
function getMenu($xml, $categories, $subcategories, $names, $images) {
$output = '<ul>';
foreach(array_keys($images) as $n) {
$output .= '<li class="animals" data-tags="'.$names[$n].'">';
$output .= '<img src="xml/'.$images[$n].'" width="75" height="75" alt="'.$names[$n].'" />';
$output .= '</li>';
}
$output.= '</ul>';
return array($output);
}
$result = getMenu($xml, $categories, $subcategories, $names, $images);
echo json_encode($result);
?>

There are literally a thousand ways how you can do that. Most straight forward is probably with xpath(). It's a powerful XML querying language worth to learn. Example with your $xml simplexmlelement:
foreach ($xml->xpath('//category[not(. = following::category)]') as $category) {
echo "=== $category === \n";
foreach ($xml->xpath("//animal[category = '$category']/subcategory[not(. = following::animal[category = '$category']/subcategory)]") as $subcategory) {
echo " = $subcategory =\n";
foreach ($xml->xpath("//animal[category = '$category' and subcategory = '$subcategory']") as $animal) {
echo " * $animal->name ($animal->image)\n";
}
}
}
Output:
=== DOGS ===
= small =
* Terrier (aaa.jpg)
* Havanese (bbb.jpg)
= large =
* Dalmatian (ccc.jpg)
You further on might want to replace the lengthy xpath strings out there. With the help of a closure and an iterator aggregate, it's even possible to reduce the code quite comfortable to:
foreach ($categories as $category) {
echo "=== $category === \n";
foreach ($subcategories as $subcategory) {
echo " = $subcategory =\n";
foreach ($animals as $animal) {
echo " * $animal->name ($animal->image)\n";
}
}
}
This would allow to change the XML structure later on and have the xpath's configured in a central location:
$categories = $vpath('//category[not(. = following::category)]');
$subcategories = $vpath('//animal[category = "%1$s"]/subcategory[not(. = following::animal[category = "%1$s"]/subcategory)]', [&$category]);
$animals = $vpath("//animal[category = '%s' and subcategory = '%s']", [&$category, &$subcategory]);
I have put that example online as a demo.
I have the output just as text with indents, however thanks to the foreachs it should be really straight forward to turn that into HTML. I leave that as an exercise.

Related

Displaying XML feed content

I am trying to get the contents from an xml feed and display them in a list. My test below is just to get the job_title for a vacancy.
$feed = file_get_contents('https://www.jobs.nhs.uk/search_xml?client_id=120650');
$xml = simplexml_load_string($feed);
$items = $xml->nhs_search->vacancy_details;
foreach($items as $item) {
$job_title = $item->job_title;
echo $job_title;
}
Here is a snippet of the xml feed
<nhs_search>
<vacancy_details>
<id>915854585</id>
<job_title>Band 5 Speech and Language Therapist</job_title>
</vacancy_details>
</nhs_search>
Nothing is displaying and no errors.
It's work fine:
$feed = file_get_contents('https://www.jobs.nhs.uk/search_xml?client_id=120650');
$xml = simplexml_load_string($feed);
$items = $xml->vacancy_details;
foreach ($items as $item) {
$id = $item->id;
$job_title = $item->job_title;
echo $id; echo '<br />';
echo $job_title;
}
Changed $items = $xml->nhs_search->vacancy_details; to $items = $xml->vacancy_details;

Retrieving all google tags xml

Im parsing a xml file but im having some issues regarding a tag (":g"), i cant access the information, his content, the problem is when i try to get the categories, i have more than one category.
xml:
<item>
<g:id>4011700742288</g:id>
<title><![CDATA[4711 Acqua Colonia Blood Orange & Basil Eau de Cologne 170ml]]></title>
<link><![CDATA[https://url/asdasd.html]]></link>
<g:image_link><![CDATA[https://url/media/catalog/product/4/7/4711-acqua-colonia-blood-_2.jpg]]></g:image_link>
<g:price>34.86 EUR</g:price>
<g:product_type><![CDATA[Mulher]]></g:product_type>
<g:product_type><![CDATA[Homem]]></g:product_type>
<g:product_type><![CDATA[Unisexo]]></g:product_type>
</item>
I try getting the categories using for example:
$categories = $item->children('g', TRUE)->product_type;
But it only brings the first category, is not geting the rest of the categories.
Here above is my code example of how i get the data.
ex:
foreach($rss->channel->item as $item) {
$categories = $item->children('g', TRUE)->product_type;
// bringing in to array <content:encoded> items from SimpleXMLElement Object()
$content = xmlObjToArr($item->children('content', true)->encoded);
echo $categories . PHP_EOL;
return;
}
function xmlObjToArr($obj) {
$namespace = $obj->getDocNamespaces(true);
$namespace[NULL] = NULL;
$children = array();
$attributes = array();
$name = strtolower((string)$obj->getName());
$text = trim((string)$obj);
if( strlen($text) <= 0 ) {
$text = NULL;
}
// get info for all namespaces
if(is_object($obj)) {
foreach( $namespace as $ns=>$nsUrl ) {
// atributes
$objAttributes = $obj->attributes($ns, true);
foreach( $objAttributes as $attributeName => $attributeValue ) {
$attribName = strtolower(trim((string)$attributeName));
$attribVal = trim((string)$attributeValue);
if (!empty($ns)) {
$attribName = $ns . ':' . $attribName;
}
$attributes[$attribName] = $attribVal;
}
// children
$objChildren = $obj->children($ns, true);
foreach( $objChildren as $childName=>$child ) {
$childName = strtolower((string)$childName);
if( !empty($ns) ) {
$childName = $ns.':'.$childName;
}
$children[$childName][] = xmlObjToArr($child);
}
}
}
return array(
'name'=>$name,
'text'=>$text,
'attributes'=>$attributes,
'children'=>$children
);
}
Your code is correct.
$categories = $item->children('g', TRUE)->product_type;
This will set $categories to an object which gives you access to all the <g:product_type> elements.
Your problem is when you write:
echo $categories . PHP_EOL;
This displays the text content of a single XML element. Since $categories is a collection of multiple elements, SimpleXML guesses that you want the first one. In other words, it's equivalent to:
echo (string)$categories[0] . PHP_EOL;
Where (string) extracts the text content and is implied by echo, and [0] gets the first item in the collection.
Looping over the collection of elements works exactly how you'd expect a list to work - you use foreach:
foreach ( $categories as $cat ) {
echo $cat . PHP_EOL;
}

parsing nested xml using php

here's the XML which I'm trying to parse for a while but I'm stuck on nested elements.
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:course="https://www.example.org/api/course/elements/1.0/" xmlns:staff="https://www.example.org/api/staff/elements/1.0/" version="2.0">
<channel>
<item>
<title>example.org course feed</title>
<link>https://www.example.org/api/v2/report/course-feed/rss</link>
<description>.org - course catalog feed</description>
<language>en</language>
<course:instructors>
<course:staff>
<staff:name>Mark Moran</staff:name>
</course:staff>
</course:instructors>
</item>
</channel>
how to parse course: instructors, my PHP code is
$rss = simplexml_load_file('https://www.edx.org/api/v2/report/course-feed/rss');
$namespaces = $rss->getNamespaces(true);
foreach ($rss->channel->item as $item) {
$title = $item->title ;
}
EDIT:2
$rss = simplexml_load_file('https://www.example.org/api/v2/report/course-feed/rss');
$namespaces = $rss->getNamespaces(true);//Add this line
foreach ($rss->channel->item as $item) {
$course_title = $item->title ;
$course_description = $item->description;
$course_url = $item->link;
$course = $item->children($namespaces['course']);
$course_thumbnail_url = $course->{'image-thumbnail'};
$course_banner_url = $course->{'image-banner'};
$course_teaser_url = $course->{'video-youtube'};
$course_start_date = $course->start;
$course_duration = $course->length;
$instructors = $item->children('course',true)->instructors;
$staff = $instructors->children('course',true)->staff;
$instructor_name = $staff->children('staff',true)->name;
$instructor_image = $staff->children('staff',true)->image;
echo $instructor_name.' '.$instructor_image,"<br>";
$course_price = 0;
$course_provider_id = 3;
$course_affiliates = $course->school;
$categories = $course->subject;
$categories = explode(',', $categories);
$c = count($categories);
$i = 0;
while($i < $c)
{
$course_rating = mt_rand(3.5,5);
$course_category = $categories[$i];
if(mysqli_query($conn,"INSERT into course_catalog_table (course_title,course_description,course_url,course_thumbnail_url,course_banner_url,course_teaser_url,course_category,course_start_date,course_duration,course_rating,course_affiliates,course_instructor,course_instructor_image,course_price,course_provider_id) VALUES('$course_title','$course_description','$course_url','$course_thumbnail_url','course_banner_url','course_teaser_url','$course_category','$course_start_date','$course_duration','$course_rating','$course_affiliates','$instructor_name',$instructor_image','$course_price')"))
{
echo "successfull\r\n";
}
$i++;
}
}
When i print instructor_name and instructor_image sometimes its prints but sometimes it throws warning that main(): Node no longer exists ,how can i check that is empty or not
You can use the children() function to access the child tree of the xml structure.
Do like this:
$rss->channel->item->children('course',true)->instructors;
Read: http://php.net/manual/en/simplexmlelement.children.php
But since the XML have multiple nest, you need to use multiple children() function to access the deepest nest.
Here is the modified code to parse the XML you give:
foreach ($rss->channel->item as $item)
{
$title = $item->title;
// access course:instructors nest
$instructors = $item->children('course',true)->instructors;
// then access the course:staff nest
$staff = $instructors->children('course',true)->staff;
// finally access the staff:name nest value
$name = $staff->children('staff',true)->name;
// print the value
echo "Staff Name: ". $name . "<br>";
}
Test run: https://eval.in/735810

PHP foreach with two 'as'?

Hi there i am trying to combine two loops of foreach but i have a problem.
The problem is that the <a href='$link'> is same to all results but they must be different.
Here is the code that i am using:
<?php
$feed = file_get_contents('http://grabo.bg/rss/?city=&affid=16090');
$rss = simplexml_load_string($feed);
$doc = new DOMDocument();
#$doc->loadHTML($feed);
$tags = $doc->getElementsByTagName('link');
foreach ($tags as $tag) {
foreach($rss as $r){
$title = $r->title;
$content = $r->content;
$link = $tag->getAttribute('href');
echo "<a href='$link'>$title</a> <br> $content";
}
}
?>
Where i my mistake? Why it's not working and how i make it work properly?
Thanks in advance!
Both loops were going through different resources so you are just simply cross joining all records in them.
This should work to get the data you need:
<?php
$feed = file_get_contents('http://grabo.bg/rss/?city=&affid=16090');
$rss = simplexml_load_string($feed);
foreach ($rss as $key => $entry) {
if ($key == "entry")
{
$title = (string) $entry->title;
$content = (string) $entry->content;
$link = (string) $entry->link["href"];
echo "<a href='$link'>$title</a><br />" . $content;
}
}

PHP + RSS duplicate elements

Im using this PHP to get a list of Title's from an RSS feed:
<?php require_once('magpie/rss_fetch.inc');
$rss = fetch_rss('http://live.visitmix.com/Sessions/RSS');
foreach ($rss->items as $item) {
$cat = $item['category'];
$title = $item['title'];
echo '<li class="'.$cat.'">'.$title.'</li>';
}
?>
I want to use <category> and add it as the class, however the <category> element appears for each <item> 1,2,3,4 or more times depending on the Title. How can I take the category element and seperate each category with a space if there is more than 1?
what about accumulating the categories in an array and then implode the array?
$categoriesArray = array();
foreach ($rss->items as $item) {
array_push($categoriesArray, $item['category']);
}
$categories = implode(" ", $categoriesArray);
EDIT TO ADD
If the categories are cumulated, you can try something like this:
$categoriesByTitle = array();
foreach ($rss->items as $item) {
$currentTitle = $item['title'];
$categories = $categoriesByTitle[$currentTitle];
if ($categories == NULL) {
$categories = array();
$categoriesByTitle[$currentTitle] = $categories;
}
if (!in_array($item['category'], $categories)) {
array_push($categories, $item['category']);
}
}
foreach ($categoriesByTitle as $title=>$category) {
// use implode for each title
$categoryString = implode(" ", $category);
echo '<li class="'.$categoryString.'">'.$title.'</li>';
}
Check this reference for arrays, it could be very useful for dealing with this kind of problems

Categories