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);
-
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><a href=\"{$matches[2][$key]}\">{$matches[2][$key]}</a>";
}
while ($cdepth-- >= $matches[1][0]) {
echo "</li>\n" . str_repeat(' ', $cdepth * 4) . "</ol>\n";
}
Output:
<ol>
<li><a href="Spiders">Spiders</a></li>
<li><a href="Beetles">Beetles</a>
<ol>
<li><a href="External morphology">External morphology</a>
<ol>
<li><a href="Head">Head</a></li>
<li><a href="Thorax">Thorax</a></li>
<li><a href="Legs">Legs</a></li>
</ol>
</li>
<li><a href="Anatomy and physiology">Anatomy and physiology</a></li>
</ol>
</li>
<li><a href="Ants">Ants</a></li>
</ol>
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] =>
)