Parse XML to a nested list with PHP - php

I have an XML file which contains family tree data in a nested structure, and I'm wanting to parse it into a nested list.
I have the following code
<?php
$doc = new DOMDocument();
$doc->load('armstrong.xml');
echo $doc->saveXML();
?>
Which loads in the following XML file and prints it as-is
<?xml version="1.0" encoding="UTF-8"?>
<indi>
<id>id1</id>
<fn>Matt</fn>
<bday>1919</bday>
<dday>2000</dday>
<spouse>Evelyn Ross</spouse>
<family>
<indi>
<id>id2</id>
<fn>Jane</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
<indi>
<id>id3</id>
<fn>Jason</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
<indi>
<id>id4</id>
<fn>Samuel</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
<indi>
<id>id5</id>
<fn>John</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
<indi>
<id>id6</id>
<fn>John</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
</family>
</indi>
</family>
However I want to parse it into the following format:
<ul>
<li>
<span class="vcard person" id="id1">
<span class="edit fn">Matt</span>
<span class="edit bday">1956</span>
<span class="edit dday"></span>
<span class="edit spouse">Eunace Fulton</span>
</span>
<ul> ... List of Family ... </ul>
</li>
</ul>
I'm pretty new to php, so if this is an incredibly simple problem I apologise! Would really appreciate any ideas.
EDIT
I'm now using the following recursive loop but still having problems
$doc = new DOMDocument();
$doc->load('armstrong.xml');
function outputIndi($indi) {
$i = new DOMDocument();
$i = $indi;
echo '<li>';
echo '<span class="edit fn">' . $indi->getElementsByTagName("fn") . '</span>'; // name not a real attribute, must access through DOM
echo '<span class="edit bday">' . $indi->getElementsByTagName("bday") . '</span>'; // ditto
// ...
echo '<ul>';
foreach ($indi->getElementsByTagName("family") as $subIndi) { // again, family not a real attribute
outputIndi($subIndi);
}
echo '</ul>';
echo '</li>';
}
outputIndi($doc->documentRoot);
?>

Here's your code. You'll need to add the rest of the attributes (dday, spouse)
RECURSION!
function outputIndi($indi) {
echo '<li>';
$id = $indi->getElementsByTagName('id')->item(0)->nodeValue;
echo '<span class="vcard person" id="' . $id . '">';
$fn = $indi->getElementsByTagName('fn')->item(0)->nodeValue;
$bday = $indi->getElementsByTagName('bday')->item(0)->nodeValue;
echo '<span class="edit fn">' . $fn . '</span>';
echo '<span class="edit bday">' . $bday . '</span>';
// ...
echo '<ul>';
$family = $indi->getElementsByTagName('family')->item(0)->childNodes;
foreach ($family as $subIndi) {
outputIndi($subIndi);
}
echo '</ul>';
echo '</span>';
echo '</li>';
}
$doc = new DOMDocument();
$doc->load('armstrong.xml');
outputIndi($doc->documentElement);
You see, it outputs all information about an "indi", loops through each child of <family>, and calls itself on that. Does that make sense?

Related

Output items from XML feed when matching to category

I've exported all product items from my shop as an XML-File and parsed these items with PHP in my single.php of my theme. The parse works fine so far.
Now i want to only display products which category name matches with the ones that have been set in the article editor of WP. This is my code:
<?php
// Parse XML Feed
$html = "";
$url = "http://example.com/feed.xml";
$xml = simplexml_load_file($url);
$item = $xml->channel->item;
$categories = get_the_category();
for($i = 0; $i < count($item); $i++){
$title = $item[$i]->title;
$link = $item[$i]->link;
$category = $item[$i]->category;
$img = $item[$i]->description->a->img["src"];
// This approach breaks my site. A loop in another loop seems not to be that good
// foreach ($categories as $i => $cat) {
// $catName = $cat->name;
// if (preg_match("/\b$catName\b/", $category)) {
// $html .= "
// <a href='$link'><h3>$title</h3>
// <img src='$img'>
// <p>$category</p></a>
// <hr>";
// }
// }
if (preg_match("/\bCATEGORY_NAME\b/", $category)) {
$html .= "
<a href='$link'><h3>$title</h3>
<img src='$img'>
<p>$category</p></a>
<hr>";
}
}
echo $html;
?>
If i change CATEGORY_NAME to an category name which exits, then the output works like i want. The other approach with that loop breaks my site with an "Fatal error: Allowed memory size of *** bytes exhausted…".
Would love to hear some tips
EDIT: Here is some example code
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<atom:link href="www.example.com/export.xml" rel="self" type="application/rss+xml" />
<title>www.example.com</title>
<description>desc</description>
<link>www.example.com</link>
<language>1-1</language>
<image>
<url>www.example.com/logo.gif</url>
<title>test page</title>
<link>http://example.com</link>
</image>
<item>
<title>Example item</title>
<guid>www.example.com/item</guid>
<link>www.example.com/item</link>
<description>
<a href="www.example.com/item" style="border:0 none;">
<img src="www.example.com/image.png" align="right" style="padding: 0pt 0pt 12px 12px; float: right;" />
</a>
</description>
<category>Categoryname</category>
<pubDate>Thu, 24 Aug 2017 17:19:17 +0200</pubDate>rn</item>
</channel>
</rss>

XPath: how to iterate all text nodes?

Please tell me how to iterate through all text nodes inside a paragraph? After all, they can be 2-3 level.
For example, take the following paragraph:
<p>Lorem <i>ipsum dolor</i> sit <span>amet, <b><i>consectetur</i> adipisicing</b> elit</span>. Odit, sunt?</p>
In which you want to process all text nodes and return them to their places.
$content = '<p>Lorem <i>ipsum dolor</i> sit <span>amet, <b><i>consectetur</i> adipisicing</b> elit</span>. Odit, sunt?</p>';
$html = new DOMDocument();
$html->loadHTML($content);
$xpath = new DOMXpath($html);
$elements = $xpath->query('//descendant-or-self::p//node()');
// My processor (not working...)
foreach ($elements as $element) {
// Processed, only text nodes (not working...)
if ( $element->nodeType == 3 ) {
function() {
return $element->nodeValue = '<span style="background-color: yellow;">' . $element->nodeValue . '</span>';
}
}
// return to the place
echo $element->C14N();
}
You need to get such result:
<p>
<span style="background-color: yellow;">Lorem </span>
<i>
<span style="background-color: yellow;">ipsum dolor</span>
</i>
<span style="background-color: yellow;">sit </span>
<span>
<span style="background-color: yellow;">amet, </span>
<b><i>
<span style="background-color: yellow;">consectetur</span>
</i>
<span style="background-color: yellow;">adipisicing</span>
</b>
<span style="background-color: yellow;">elit</span>
</span>
<span style="background-color: yellow;">. Odit, sunt?</span>
</p>
This will wrap all the text nodes into span elements:
$content = '<p>Lorem <i>ipsum dolor</i> sit <span>amet, <b><i>consectetur</i> adipisicing</b> elit</span>. Odit, sunt?</p>';
$html = new DOMDocument();
$html->loadHTML($content);
$xpath = new DOMXpath($html);
$elements = $xpath->query('//descendant-or-self::p//text()');
/* #var DomNode $element*/
foreach ($elements as $element) {
$span = $html->createElement("span", $element->nodeValue);
$span->setAttribute("style", "background-color: yellow;");
$element->parentNode->replaceChild($span, $element);
}
echo $html->saveHTML();

PHP xpath Error in foreach() Statement [duplicate]

This question already has answers here:
SimpleXML: Selecting Elements Which Have A Certain Attribute Value
(2 answers)
Closed 8 years ago.
I am trying to build a basic shopping cart using only PHP and XML. I have chosen to use simplexml and xpath here. Items are added fine, but when it comes to the "getProduct($id)" method, which will return the product with a matching code, I am faced with the following error:
Warning: Invalid argument supplied for foreach() in
/srv/disk6/1664324/www/mattc.biz.ht/cart.php on line 203
Here is a snippet from my XML (For the Structure)
<?xml version="1.0" encoding="utf-8"?>
<GreenTrade>
<brand id="Alara">
<product code="1">
<name>Fair Trade Muesli x 500g</name>
<desc>Piced with cinnamon and honey</desc>
<image>1.jpg</image>
</product>
<product code="2">
<name>Luxury Gluten Free Muesli x 500g</name>
<desc>Gluten Free - Wheat Free - High Proteins (Over 10 Percent) - Balanced Nutrition - Vegan </desc>
<image>luxuryglutenfree.jpg</image>
</product>
<product code="3">
<name>Of the Earth - Goji berries x 60g</name>
<desc>Bursting with Beta-Carotene and all 8 Essential Amino Acids </desc>
<image>gojiberries.jpg</image>
</product>
</brand>
<brand id = "BigOz">
<product code="5">
<name>Corn Flakes x 350g</name>
<desc> Gluten Free – Dairy free – Low in Sodium – No added artificial flavours, colours or preservatives – Naturally cholesterol and sodium free </desc>
<image>5.jpg</image>
</product>
</brand>
<brand id="Windmill">
<product code="8">
<name>Amisa Organic Corn and Rice Rigatoni</name>
<desc>Organic, Gluten Free, Dairy Free, Wholegrain</desc>
<image>corn_rice_rigatoni.jpg</image>
</product>
</brand>
</GreenTrade>
And here is my PHP / HTML. For some reason I was not allowed to add PHP tags so i put where they would be in comments. The page would be accessed from a URL Such as:
http://www.mattc.biz.ht/cart.php?action=add&item=4 (Actual Site)
<body>
<div class="container">
<div class="header"><img src="Mattc.fw.png" alt="Insert Logo Here" name="Insert_logo" width="180" height="90" id="Insert_logo" style="background-color: #C6D580; display:block;" />
<!-- end .header --></div>
<div class="sidebar1">
<ul class="nav">
<li>Home</li>
<li>Store</li>
<li>Cart</li>
<li>Email</li>
</ul>
<p>Left Side Bar</p>
<!-- end .sidebar1 --></div>
<div class="content">
<h1>Your Shopping Cart</h1>
//OPEN PHP TAG
$xml = simplexml_load_file('GreenTrade.xml');
$brands = $xml->xpath('//brand');
$prodz = $xml->xpath('//product');
$product_id = $_GET [item];
$action = $_GET [action];
//$contains = $_SESSION['my_cart'];
$brandz = 3;
//Adds 1 to QTY
if (!empty($_GET['item']) && $action == "add") {
$_SESSION['cart'][$product_id]++;
}
//Delete 1 From QTY
if (!empty($_GET['item']) && $action == "del") {
$_SESSION['cart'][$product_id]--;
if($_SESSION['cart'][$product_id] <= 0){
unset($_SESSION['cart'][$product_id]);
}
}
if ($action == "empty") {
unset($_SESSION['cart']);
}
echo '</br><hr align="CENTER">';
if($_SESSION['cart']){
echo "<div align='center'><a href='cart.php?action=empty'>Empty Cart</a></div>";
foreach($_SESSION['cart'] as $prd => $quantity){
$product=getProduct($prd); //<--- Call to getProduct
echo "Name: " . $product->name . '</br>';
echo "Description: " . $product->desc . '</br>';
echo '<img src=' . $product->image . ' >' . '</br>';
echo "Ordered: " . $quantity . '</br>';
echo 'Add Another' . '</br>';
echo 'Remove One' . '</br>';
echo '</br><hr align="CENTER">';
}
} else {
echo '<h2>'."Your Cart Is Empty".'</h2>';
}
//VVV Here Is the Troublesome Function VVV
function getProduct($id){
foreach($prodz as $prod){
if($prod['code'] == $id){
return $prod;
}
}
}
// CLOSE PHP TAG
<!-- end .content --></div>
<div class="footer">
<p>MattC.biz.ht (C) Matthew Cassar 2014</p>
<p>A Chris Porter Web Project <!-- end .footer --></p>
</div>
<!-- end .container --></div>
</body>
The array must be passed as a paramter because $prodz is out of scope (compare: PHP variable scope.
function getProduct(array $products, $id)
{
foreach ($products as $product) {
if ($product['code'] == $id) {
return $product;
}
}
}

Modify value of an XML element in foreach loop using simpleXML in php

I want to modify XML values (of the file) using simpleXML in a foreach loop.
My XML look like that :
<a>
<b id="first">
<c></c>
<d></d>
</b>
<b id="second">
<c></c>
<d></d>
</b>
<b id="third">
<c></c>
<d></d>
</b>
And my php look like that :
$xml = simplexml_load_file('myfile.xml');
$idOfB = 'first'; // the id of b and I want to modify the children nodes of that
foreach($xml->a->b as $node) {
foreach($node->attributes() as $id => $value) {
if((string)$value == $idOfB) {
// Something there I guess...
// I want to change the value of c and d of this node
}
}
}
$xml->asXML();
I have tried and searched quite a lot without any luck.
Your testing file suppose to be like ---
<?xml version="1.0" encoding="UTF-8"?>
<a>
<b id="first">
<c>Abhishek</c>
<d/>
</b>
<b id="second">
<c/>
<d/>
</b>
<b id="third">
<c/>
<d/>
</b>
</a>
here abhishek will be replaced by Nishant
<?php
$xml = simplexml_load_file("testingxml.xml");
$idOfB = 'first'; // the id of b and I want to modify the children nodes of that
foreach($xml->b as $node) {
foreach($node->attributes() as $id => $value) {
if((string)$value == $idOfB) {
$node->c = 'Nishant';
$node->d = 'Tyagi';
// Something there I guess...
// I want to change the value of c and d of this node
}
}
//$finobjArr[] = $node;
}
//$xmlobj = $finobjArr;
file_put_contents("testingxml.xml", $xml->saveXML());
////$xml->asXML("testingxml.xml");
//print($xml->asXML("testingxml.xml"));
?>

Why is my recursive loop creating too many children?

I'm using a PHP recursive loop to parse through an XML document to create a nested list, however for some reason the loop is broken and creating duplicates of elements within the list, as well as blank elements.
The XML (a list of family tree data) is structured as follows:
<?xml version="1.0" encoding="UTF-8"?>
<family>
<indi>
<id>id1</id>
<fn>Thomas</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
<indi>
<id>id1</id>
<fn>Alexander</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
<indi>
<id>id1</id>
<fn>John</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
<indi>
<id>id1</id>
<fn>George</fn>
<bday></bday>
<dday></dday>
<spouse></spouse>
<family>
</family>
</indi>
</family>
</indi>
</family>
</indi>
</family>
And here's my PHP loop, which loads the XML file then loops through it to create a nested ul:
<?php
function outputIndi($indi) {
echo '<li>';
$id = $indi->getElementsByTagName('id')->item(0)->nodeValue;
echo '<span class="vcard person" id="' . $id . '">';
$fn = $indi->getElementsByTagName('fn')->item(0)->nodeValue;
$bday = $indi->getElementsByTagName('bday')->item(0)->nodeValue;
echo '<span class="edit fn">' . $fn . '</span>';
echo '<span class="edit bday">' . $bday . '</span>';
// ...
echo '</span>';
echo '<ul>';
$family = $indi->getElementsByTagName('family');
foreach ($family as $subIndi) {
outputIndi($subIndi);
}
echo '</ul></li>';
}
$doc = new DOMDocument();
$doc->load('armstrong.xml');
outputIndi($doc);
?>
EDIT here's the desired outcome (nested lists, with ul's signifying families and li's signifying individuals)
<ul>
<li>
<span class="vcard">
<span class="fn">Thomas</span>
<span class="bday"></span>
<span class="dday"></span>
<ul>
... repeat for all ancestors ...
</ul>
<li>
<ul>
You can see the output at http://chris-armstrong.com/gortin . Any ideas where I'm going wrong? I think it's something to do with the $subIndi value, but anytime I try and change it I get an error. Would really appreciate any help!
Sounds perfect! Could you give me an
example? Does this mean I can save the
data as XML, then load it in as nested
ul's?
Yes, you can do exactly that. Here's an XSL which renders nested UL's:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Family tree</h2>
<ul>
<li><xsl:value-of select="indi/fn" /></li>
<!-- apply-templates will select all the indi/family nodes -->
<xsl:apply-templates select="indi/family" />
</ul>
</body>
</html>
</xsl:template>
<xsl:template match="family">
<ul>
<li>
<div>
<xsl:value-of select="id" />: <xsl:value-of select="fn" />
(<xsl:variable name="bday" select="bday" />
to
<xsl:variable name="dday" select="dday" />)
</div>
</li>
<!-- This node matches the 'family' nodes, and we're going to apply-templates on the inner 'family' node,
so this is the same thing as recursion. -->
<xsl:apply-templates select="family" />
</ul>
</xsl:template>
</xsl:stylesheet>
I don't know php, but this article will show you how to transform XML using the style sheet above.
You can also link your style sheet by adding a stylesheet directive at the top of your XML file (see for an example).
getElementsByTagName will give you all nodes, not just immediate children:
$family = $indi->getElementsByTagName('family');
foreach ($family as $subIndi) {
outputIndi($subIndi);
}
You will call outputIndi() for grand children, etc repeatedly.
Here is an example (from another stackoverflow question):
for ($n = $indi->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof DOMElement && $n->tagName == "family") {
outputIndi($n);
}
}
Replace this
$family = $indi->getElementsByTagName('family');
foreach ($family as $subIndi) {
outputIndi($subIndi);
}
by this
if(!empty($indi))
foreach($indi as $subIndi){
outputIndi($subIndi);
}
I realize
if($indi->hasChildNodes())
is better than
if(!empty($indi))

Categories