How to print list using hierarchical data structure? - php

When I run this code:
foreach ($tree as $node) {
echo str_repeat(' ', $node->tree_depth * 4) . $node->id . PHP_EOL;
}
I get well formatted text like:
Food
Fruit
Red
Cherry
Strawberry
Cool
Not cool
Yellow
Banana
Meat
Beef
Pork
But I want to create a list with <ul><li>...:
I tried with:
echo '<ul>';
$prev_depth = 0;
foreach($table->fetchTree() as $row) {
if ($row->tree_depth > $prev_depth) {
echo '<li><ul>';
} else if ($row->tree_depth < $prev_depth) {
echo '</li></ul>';
}
echo '<li>' . $row->name . '</li>';
$prev_depth = $row->tree_depth;
}
echo '</ul>';
But I have some extra ul tags and so on. I lost 2 days on this so if you can help me please post here...

Try this algorithm:
$tree = array(
array('Food', 0),
array('Fruit', 1),
array('Red', 2),
array('Cherry', 3),
array('Strawberry', 3),
array('Cool', 4),
array('Not cool', 4),
array('Yellow', 2),
array('Banana', 3),
array('Meat', 0),
array('Beef', 1),
array('Pork', 1),
);
$depth = -1;
$flag = false;
foreach ($tree as $row) {
while ($row[1] > $depth) {
echo "<ul>\n", "<li>";
$flag = false;
$depth++;
}
while ($row[1] < $depth) {
echo "</li>\n", "</ul>\n";
$depth--;
}
if ($flag) {
echo "</li>\n", "<li>";
$flag = false;
}
echo $row[0];
$flag = true;
}
while ($depth-- > -1) {
echo "</li>\n", "</ul>\n";
}
Here you just need to replace $tree by $table->fetchTree(), $row[0] by $row->name and $row[1] by $row->tree_depth.

Try this code instead:
<?php
echo "<ul>\n";
$tree = array(
array('Food', 0),
array('Fruit', 1),
array('Red', 5),
array('Cherry', 3),
array('Strawberry', 3),
array('Cool', 4),
array('Not cool', 4),
array('Yellow', 2),
array('Banana', 3),
array('Meat', 0),
array('Beef', 4),
array('Pork', 2),
);
$depth = 0;
foreach ($tree as $node) {
if ($node[1] > $depth)
echo str_repeat("<ul>\n", $node[1] - $depth);
if ($node[1] < $depth)
echo str_repeat("</ul>\n", $depth - $node[1]);
$depth = $node[1];
echo "<li>" . $node[0] . "\n";
}
echo str_repeat("</ul>\n", $depth+1);
?>
I've updated it to output fewer <li> tags, thereby reducing the number of bullets. But on the other hand, this will generate HTML that wont validate since a jump of more than one level will result in a <ul><ul> being generated.

Related

Create a multi-dimensional array with PHP from lineal array

TL;DR I'm looking for a function to create a nested <ol> list from a one-dimensional array in PHP.
1) Currently I have this simplified markup in my test page:
<h2>Spiders</h2>
<h2>Beetles</h2>
<h3>External morphology</h3>
<h4>Head</h4>
<h4>Thorax</h4>
<h4>Legs</h4>
<h3>Anatomy and physiology</h3>
<h2>Ants</h2>
2) Which then is captured by a pretty simple function into an one-dimensional array like this:
array
(
0 => "H2 Spiders",
1 => "H2 Beetles",
2 => "H3 External morphology",
3 => "H4 Head",
4 => "H4 Thorax",
5 => "H4 Legs",
6 => "H3 Anatomy and physiology"
7 => "H2 Ants"
);
3) This is the tricky part, because I use the next loop with these overly complicated if statements to populate a multi-dimensional array.
$toc = array ();
//
foreach ($array as $value) {
$value_arr = explode(' ', $value, 2);
$depth = str_replace("H", "", $value_arr[0]);
$content = $value_arr[1];
//
if ($depth == 1) $toc[$title] = null;
elseif ($depth == 2) {
if (empty (end ($toc))) $toc[array_key_last ($toc)] = array ($title => null);
else $toc[array_key_last ($toc)][$title] = null;
} elseif ($depth == 3) {
if (empty (end ($toc[array_key_last ($toc)]))) $toc[array_key_last ($toc)][array_key_last ($toc[array_key_last ($toc)])] = array ($title => null);
else $toc[array_key_last ($toc)][array_key_last ($toc[array_key_last ($toc)])][$title] = '';
}
}
Output:
Array (
[Spiders] =>
[Beetles] => Array
(
[External morphology] => Array
(
[Head] =>
[Thorax] =>
[Legs] =>
)
[Anatomy and physiology] =>
)
[Ants] =>
)
4) And finally be parsed with this function into an perfectly indented html list.
function table_of_contents ($toc, $output = '') {
foreach ($toc as $key => $value) {
$output = "$output <li><a href='##" . sanitize_title ($key) . "'>$key</a>" . (is_array ($value) ? table_of_contents ($value) : null) . '</li>';
}
//
return "<ol>$output</ol>";
}
//
table_of_contents ($toc);
-
Spiders Beetles External morphology Head Thorax Legs Anatomy and physiology Ants
Everything works fine in the 1st, 2nd and 4th steps, but my current approach has the drawback that only allows me up to three levels of depth from the first array in the 3rd step.
My question is whether there is a more efficient and cleaner way to create the multi-dimensional array with (maybe) a recursive function or something like that?
It might be easier to parse your input using preg_match_all; this can give you arrays of depths and associated values. You can then iterate through those arrays, opening an <ol> when the depth increases, and closing it when the depth decreases:
$html = '<h2>Spiders</h2>
<h2>Beetles</h2>
<h3>External morphology</h3>
<h4>Head</h4>
<h4>Thorax</h4>
<h4>Legs</h4>
<h3>Anatomy and physiology</h3>
<h2>Ants</h2>';
preg_match_all('/<h(\d)>([^<]+)/', $html, $matches);
$cdepth = $matches[1][0] - 1;
foreach ($matches[1] as $key => $depth) {
if ($depth > $cdepth) {
echo "\n" . str_repeat(' ', $cdepth * 4) . "<ol>\n";
}
elseif ($depth < $cdepth) {
echo "</li>\n" . str_repeat(' ', $depth * 4) . "</ol>\n" . str_repeat(' ', $depth * 4) . "</li>\n";
}
else {
echo "</li>\n";
}
$cdepth = $depth;
echo str_repeat(' ', $cdepth * 4) . "<li>{$matches[2][$key]}";
}
while ($cdepth-- >= $matches[1][0]) {
echo "</li>\n" . str_repeat(' ', $cdepth * 4) . "</ol>\n";
}
Output:
<ol>
<li>Spiders</li>
<li>Beetles
<ol>
<li>External morphology
<ol>
<li>Head</li>
<li>Thorax</li>
<li>Legs</li>
</ol>
</li>
<li>Anatomy and physiology</li>
</ol>
</li>
<li>Ants</li>
</ol>
Demo on 3v4l.org
Update
If you want the third stage array for other reasons than output, you can generate it with this recursive function (which still works from the output of preg_match_all):
$html = '<h2>Spiders</h2>
<h2>Beetles</h2>
<h3>External morphology</h3>
<h4>Head</h4>
<h4>Thorax</h4>
<h4>Legs</h4>
<h5>Feet</h5>
<h3>Anatomy and physiology</h3>
<h2>Ants</h2>';
function push_values(&$k, $depth, $content) {
$output = array();
$cdepth = $depth[$k];
while ($k < count($depth)) {
if ($depth[$k] == $cdepth) {
$output[$content[$k]] = '';
$k++;
}
elseif ($depth[$k] > $cdepth) {
$output[$content[$k-1]] = push_values($k, $depth, $content);
}
else {
return $output;
}
}
return $output;
}
preg_match_all('/<h(\d)>([^<]+)/', $html, $matches);
$key = 0;
print_r(push_values($key, $matches[1], $matches[2]));
Output:
Array
(
[Spiders] =>
[Beetles] => Array
(
[External morphology] => Array
(
[Head] =>
[Thorax] =>
[Legs] => Array
(
[Feet] =>
)
)
[Anatomy and physiology] =>
)
[Ants] =>
)
Demo on 3v4l.org
Lastly, I would suggest a minor fix for the first code posted by #Nick causing an undesirable HTML output in the next scenario, where a depth difference greater than two will change the most superficial siblings; for example, the <h3> after <h6> become <h5> and the next <h2> was parsed as <h4>:
$html = '<h2>Spiders</h2>
<h2>Beetles</h2>
<h3>External morphology</h3>
<h4>Head</h4>
<h4>Thorax</h4>
<h4>Legs</h4>
<h5>Feet</h5>
<h6>Toes</h6>
<h3>Anatomy and physiology</h3>
<h2>Ants</h2>';
foreach ($matches[1] as $key => $depth) {
if ($depth > $cdepth) {
echo "\n" . str_repeat(' ', $cdepth * 4) . "<ol>\n";
}
elseif ($depth < $cdepth) {
echo "</li>\n" . str_repeat(' ', $depth * 4) . "</ol>\n" . str_repeat(' ', $depth * 4) . "</li>\n";
}
else {
echo "</li>\n";
}
$cdepth = $depth;
echo str_repeat(' ', $cdepth * 4) . "<li>{$matches[2][$key]}";
}
while ($cdepth-- >= $matches[1][0]) {
echo "</li>\n" . str_repeat(' ', $cdepth * 4) . "</ol>\n";
}
Output:
<ol>
<li>Spiders</li>
<li>Beetles
<ol>
<li>External morphology
<ol>
<li>Head</li>
<li>Thorax</li>
<li>Legs
<ol>
<li>Feet
<ol>
<li>Toes</li>
</ol>
</li>
<li>Anatomy and physiology</li>
</ol>
</li>
<li>Ants</li>
</ol>
</li></ol></li></ol>
-
To address that I just added another while statement to put the correct amount of </li></ol> before adding the next <li> element, which now can correctly validate the W3C inspector.
foreach ($matches[1] as $key => $depth) {
if ($depth > $cdepth) {
echo "\n" . str_repeat(' ', $cdepth * 4) . "<ol>\n";
}
elseif ($depth < $cdepth) {
while ($cdepth -- > $depth) {
echo "</li>\n" . str_repeat(' ', $depth * 4) . "</ol>\n" . str_repeat(' ', $depth * 4) . "\n";
}
}
else {
echo "</li>\n";
}
$cdepth = $depth;
echo str_repeat(' ', $cdepth * 4) . "<li>{$matches[2][$key]}";
}
while ($cdepth-- >= $matches[1][0]) {
echo "</li>\n" . str_repeat(' ', $cdepth * 4) . "</ol>\n";
}
Output:
<ol>
<li>Spiders</li>
<li>Beetles
<ol>
<li>External morphology
<ol>
<li>Head</li>
<li>Thorax</li>
<li>Legs
<ol>
<li>Feet
<ol>
<li>Toes</li>
</ol>
</li>
</ol>
</li>
</ol>
<li>Anatomy and physiology</li>
</ol>
<li>Ants</li>
</ol>

PHP rss feed sort alphabetically and display First letter above title

PHP to display feed items and list them alphabetically and display first letter of each title above. It is only show one letter above the whole list and it is the letter of the first title on the feed. I changed feedurl here for privacy purposes. Any Ideas?
<?php
$rss = new DOMDocument();
$feed = array();
$urlArray = array(array('url' => 'https://feeds.megaphone.fm/SH')
);
foreach ($urlArray as $url) {
$rss->load($url['url']);
foreach ($rss->getElementsByTagName('item') as $node) {
$item = array (
'title' => $node->getElementsByTagName('title')->item(0)->nodeValue
);
array_push($feed, $item);
}
}
usort( $feed, function ( $a, $b ) {
return strcmp($a['title'], $b['title']);
});
$previous = null;
foreach($item as $value) {
$firstLetter = substr($value, 0, 1);
if($previous !== $firstLetter)
$previous = $firstLetter;
}
$limit = 3000;
echo '<p>'.$firstLetter.'</p>';
echo '<ul style="list-style-type: none;>"';
for ($x = 0; $x < $limit; $x++) {
$title = str_replace(' & ', ' & ', $feed[$x]['title']);
echo '<li>';
echo ''.$title.'';
echo '</li>';
}
echo '</ul>';
?>
Your foreach loop that determines the first letter of the title is outside the for loop that actually prints it. Therefore, you are always going to output the last letter of the foreach loop.
Move that part inside the for loop, something like
$limit = 3000;
$previous = null;
$count_firstletters = 0;
for ($x = 0; $x < $limit; $x++) {
$firstLetter = substr($feed[$x]['title'], 0, 1); // Getting the first letter from the Title you're going to print
if($previous !== $firstLetter) { // If the first letter is different from the previous one then output the letter and start the UL
if($count_firstletters != 0) {
echo '</ul>'; // Closing the previously open UL only if it's not the first time
}
echo '<p>'.$firstLetter.'</p>';
echo '<ul style="list-style-type: none;>"';
$previous = $firstLetter;
$count_firstletters ++;
}
$title = str_replace(' & ', ' & ', $feed[$x]['title']);
echo '<li>';
echo ''.$title.'';
echo '</li>';
}
echo '</ul>'; // Close the last UL

How to build "ul li" list from this array?

I have data in an array with the following structure:
$persons = array(
array($id, $name, $parent),
array(2, 'John Smith', 0),
array(3, 'Steve Martin', 2),
array(4, 'Peter Griffin', 3),
array(5, 'Cinder Ella', 0)
);
and I would like to get a tree like this:
<ul>
<li>John Smith</li>
<ul>
<li>Steve Martin</li>
<ul>
<li>Peter Griffin</li>
</ul>
</ul>
<li>Cinder Ella</li>
</ul>
Probably this is just as easy as 1-2-3 but I have tried everything already without success...
heres a solution:
<?php
$persons = array(
array(2, 'John Smith', 0),
array(3, 'Steve Martin', 2),
array(4, 'Peter Griffin', 3),
array(5, 'Cinder Ella', 0)
);
echo "<ul>";
printChildren($persons,0);
echo '</ul>';
function printChildren($arr,$id){
foreach($arr as $subvalue)
if($subvalue[2] == $id){
echo '<li>'.$subvalue[1].'</li>';
if(countChildren($arr,$subvalue[0])>0){
echo '<ul>';
printChildren($arr,$subvalue[0]);
echo '</ul>';
}
}
}
function countChildren($arr,$id){
$i=0;
foreach($arr as $value){
if($value[2] == $id) $i++;
}
return $i;
}
?>
function getChild($searchkey,$arr,&$doneArr){
$doneArr[] = $searchkey;
foreach($arr as $valArr){
if($key = array_search($searchkey,$valArr)){
$id = $valArr[0];
$name = $valArr[1];
$parent = $valArr[2];
if(!in_array($id,$doneArr)){
$html = '<ul>';
$html .= '<li>'.$name.'</li>';
$html .= getChild($id,$arr,$doneArr); // used to get recursively all childs
$html .= '</ul>';
}
}
}
return $html;
}
$html = '<ul>';
$cnt = sizeof($persons);
$doneArr = Array(); // used to maintain processed items.
for($i=0;$i<$cnt;$i++){
$id = $persons[$i][0];
$name = $persons[$i][1];
$parent = $persons[$i][2];
if(empty($parent)){
$html .= '<li>'.$name.'</li>';
}
$html .= getChild($id,$persons,$doneArr);
}
$html .= '</ul>';
echo $html;
unset($doneArr);

how can I add items from an array as a class

I am new at php, so please be kind.
I am building a script that gets the number of facebook likes from facebook pages.
Then it sorts them, I have found a way to add the page's profile picture using css, however the only class I am able to add is a url. how can I give each thumbnail it's own class, which I can then apply the css to?
here is my code:
function array_sort($array, $on, $order=SORT_ASC)
{
$new_array = array();
$sortable_array = array();
if (count($array) > 0) {
foreach ($array as $k => $v) {
if (is_array($v)) {
foreach ($v as $k2 => $v2) {
if ($k2 == $on) {
$sortable_array[$k] = $v2;
}
}
} else {
$sortable_array[$k] = $v;
}
}
switch ($order) {
case SORT_ASC:
asort($sortable_array);
break;
case SORT_DESC:
arsort($sortable_array);
break;
}
foreach ($sortable_array as $k => $v) {
$new_array[$k] = $array[$k];
}
}
return $new_array;
}
function getLikes($arr){
$urls = "";
// Add urls to check for likes
for($i = 0;$i < count($arr);$i++) {
if($urls != "") $urls .= ",https://www.facebook.com/";
$urls .= $arr[$i];
}
// Retreive info from Facebook
$xml = simplexml_load_file("http://api.facebook.com/restserver.php?method=links.getStats&urls=https://www.facebook.com/" . $urls);
$likes = array();
// Loop through the result and populate an array with the likes
for ($i = 0;$i < count($arr);$i++) {
$url = $xml->link_stat[$i]->url;
$counts = (int)$xml->link_stat[$i]->like_count;
$likes[] = array('likes' => $counts,'url' => $url);number_format(1000, 0, '.', ',');
}
return $likes;
}
$array = array("kylieminogue","SiaMusic","iggyazalea");
$likes = getLikes($array);
$likes = array_sort($likes, 'likes', SORT_DESC);
foreach ($likes as $key => $val) {
$final = number_format($val['likes'], 0, '.', ',');
echo "<li class='facebook'><div class='fb-page'><div class='rank'>" . $key . "</div>" . "<div class='thumb " . $val['url'] . "'><div class='link'>" . $val['url'] . "</div></div>" . "<div class='likes'>" . $final . "</div></div></li><br />";
}
If you do this in getLikes(), inside the second loop:
$likes[] = array(
'likes' => $counts,
'url' => $url,
// create a hopefully unique class name
'class' => strtolower($arr[$i]) . '-' . $i
);
// After this you call number_format without receiving its value, why?
Then in the HTML you change
"<div class='thumb " . $val['url'] . "
for
"<div class='thumb " . $val['class'] . "
Is this what you mean?

Foreach and columns

<?php
$arr = range(1,rand(40,120)); ?>
<table>
<?php
foreach ($arr as &$value) {
echo '<tr><td>' . $value . '</td></tr>';
} ?>
</table>
This generate for me for example:
1
2
3
...
111
all in one columns. How can i make - when in first column are 25 rows then create new column, etc. For example:
1 26 51
2 27 ...
3
..
25 50
How can i make it?
I can use DIV instead of table.
<?php
$arr = range(1,rand(40,120));
?>
<div style="width:40px; float:left;">
<?php
foreach ($arr as $value) {
echo $value . '<br />';
if ($value % 25 == 0) {
echo '</div><div style="width:40px; float:left;">';
}
}
?>
Vertically sorted columns of that sort (no pun intended) are a serious pain in html, since this arragement is a "top->bottom, left->right", while HTML tables by their nature are "left->right, top->bottom" instead.
To get around it, you have to do some offset math on the array indexes, so you can output by rows:
$arr = range(1,rand(40,120));
$rows = ceil(count($arr) / 3); // assuming 3 columns
for ($i = 0; $i < $rows; $i++) {
echo <<<EOL
<tr>
<td>{$arr[$i]}</td> 1, 2, 3, etc...
<td>{$arr[$i+rows]}</td> 11, 12, 13, etc...
<td>{$arr[$i+(2*$rows)]}</td> 21, 22, 23, etc...
</tr>
EOL;
}
This code probably won't work as is, but should give you the basic idea.
EDITED
<?php
$rows = 25;
$arr = range(1, rand(40, 120));
$arr = array_merge($arr, array_fill(0, $rows - (count($arr) % $rows), null));
$cols = ceil(count($arr) / $rows);
$render = array();
echo '<table>' . "\n";
foreach ($arr as $i => $value) {
$render[$i % $rows][] = $value;
if (count($render[$i % $rows]) == $cols) {
echo ' <tr>' . "\n" . ' <td>' . implode('</td>' . "\n" . ' <td>', $render[$i % $rows]) . '</td>' . "\n" . ' </tr>' . "\n";
}
}
echo '</table>' . "\n";
?>

Categories