Convert array of paths into UL list
Edit:
Changed to cater for updated question.
I'm using an array index of __title
to hold the page title. As long as you never have a directory in your tree called __title
this should be fine. You're free to change this sentinel value to anything you wish however.
I have also changed it so the list building function returns a string, so that you can store the value for use later in your page. (You can of course just do echo build_list(build_tree($paths))
to output the list directly.
<?php
$paths = array(
'about/contact/' => 'Contact Us',
'about/contact/form/' => 'Contact Form',
'about/history/' => 'Our History',
'about/staff/' => 'Our Staff',
'about/staff/bobjones/' => 'Bob',
'about/staff/sallymae/' => 'Sally',
'products/' => 'All Products',
'products/gifts/' => 'Gift Ideas!',
'products/widgets/' => 'Widgets'
);
function build_tree($path_list) {
$path_tree = array();
foreach ($path_list as $path => $title) {
$list = explode('/', trim($path, '/'));
$last_dir = &$path_tree;
foreach ($list as $dir) {
$last_dir =& $last_dir[$dir];
}
$last_dir['__title'] = $title;
}
return $path_tree;
}
function build_list($tree, $prefix = '') {
$ul = '';
foreach ($tree as $key => $value) {
$li = '';
if (is_array($value)) {
if (array_key_exists('__title', $value)) {
$li .= "$prefix$key/ <a href=\"/$prefix$key/\">${value['__title']}</a>";
} else {
$li .= "$prefix$key/";
}
$li .= build_list($value, "$prefix$key/");
$ul .= strlen($li) ? "<li>$li</li>" : '';
}
}
return strlen($ul) ? "<ul>$ul</ul>" : '';
}
$tree = build_tree($paths);
$list = build_list($tree);
echo $list;
?>
Convert paths into a nested unordered list using JavaScript:
Here's some vanilla JavaScript to do exactly what you specified. I added a space between 'shop' and the list entry names to stop them from generating right up against each other. Hope this helps!
let list = document.querySelector('#list'); // get list
let items = list.querySelectorAll('[class*="item"]'); // get all items in list
let newList = document.createElement('ul');
items.forEach( item => {
const paths = item.innerText.split('/');
paths.splice(0,1); // remove first element since it's empty
let dir = newList;
let globalPath = "";
for( path of paths ){
globalPath = globalPath + '/' + path;
if( dir[path] == undefined ){
let subList = document.createElement('li'); // begin sublist
subList.innerText = path + ' '; // add space between the list entry and href
let href = document.createElement('a'); // begin href
href.href = globalPath;
subList.appendChild( href ); // end href
if( path != paths[paths.length-1] ){ // prep another ul to go deeper in the list
href.innerText = "shop";
let ul = document.createElement('ul');
subList.appendChild( ul );
dir[path] = ul;
}
dir.appendChild(subList); // end sublist
}
dir = dir[path]; // dive deeper
}
} );
list.innerHTML = newList.innerHTML; // replace old list with new list
<ul class="list" id="list">
<li class="item">/animal/dog/treats/biscuits</li>
<li class="item">/animal/dog/treats/cookies</li>
<li class="item">/clothes/shirts</li>
</ul>
Covert Array into ul li list
I have refactored the god-awful code in @hakre's answer to be slightly less god-aweful. It's using the same aglorithm:
$file = './CatDV/S1/SFX/steam/6004_90_04 LargeHeadlightSm.wav
./CatDV/S1/SFX/steam/AirHissPressureRe HIT032001.wav
./CatDV/S1/SFX/steam/Impact_Metal_Bullet_Hit(1).wav
./CatDV/S1/SFX/steam/Impact_Metal_Bullet_Hit.wav
./CatDV/S1/SFX/steam/6004_94_02 LargeHeavyGlassS.wav
./CatDV/S1/SFX/steam/Impact_Glass_Smash.wav
./CatDV/S1/SFX/steam/AirReleaseHeavyAi HIT030701.wav
./CatDV/S1/SFX/steam/SDR02_15SCIF RhythmicRever.wav
./CatDV/S1/SFX/steam/VAL02_08CRSH GlassBreaking.wav
./CatDV/S1/SFX/steam/AirReleaseLargeAi HIT030601.wav
./CatDV/S1/SFX/steam/SDR02_14SCIF Rhythmic3Beat.wav
./CatDV/S1/SFX/steam/6004_94_01 LargeGlassSmash.wav';
These are internal functions, which you don't need to directly call.
function splitIntoPaths($string) {
$paths = array();
foreach (explode("\n", $string) as $line) {
$node = &$paths;
foreach (explode("/", $line) as $segment) {
if (!isset($node[$segment])) {
$node[$segment] = array();
}
$node = &$node[$segment];
}
$node = $line;
}
return $paths;
}
function createListFromArray(array $array) {
$return = "<ul>";
foreach ($array as $key => $value) {
$return .= "<li>" . $key;
if (is_array($value)) {
$return .= createListFromArray($value);
}
$return .= "</li>";
}
return $return . "</ul>";
}
This is the function that does the work for you, returning you a string.
function createTree($string) {
$paths = splitIntoPaths($string);
return createListFromArray($paths);
}
Usage:
$tree = createTree($file);
Generating a nodes path from an unordered list
You could use parents(), to get the list of parents. You could take this further and get rid of the parent data attribute all together, which would simplify the following code.
$('#mybutton').on('click', function () {
var output = "";
$("ul#tree li").each(function () {
var id = $(this).data("id");
var parent = $(this).data("parent");
output = output + "id=" + id + "&parent=" + parent + "&node_path=";
var node_path = parent + ".";
$(this).parents('li').each(function() {
node_path += $(this).data("parent") + ".";
});
output += node_path.split('').reverse().join('')
output += "<br/>";
});
$("#mysection").html(output);
});
http://jsfiddle.net/4t6jgzhu/
PHP: Convert an array with a path attribute to a tree structure
Ok, I think I just found a solution:
function pathToTree($array){
$tree = array();
foreach($array AS $item) {
$pathIds = explode("/", ltrim($item["path"], "/") . $item["id"]);
$current = &$tree;
foreach($pathIds AS $id) {
if(!isset($current["childs"][$id])) $current["childs"][$id] = array();
$current = &$current["childs"][$id];
if($id == $item["id"]) {
$current = $item;
}
}
}
return $tree["childs"];
}
This is a dynamice solution for 1-n depth. Look at my example at http://ideone.com/gn0XLp . Here I tested it with some level:
- Continent
- Country
- City
- City-District
- City-Subdistrict
- City Sub-Sub-District
How to convert an array of paths into JSON structure?
const data = [
"/org/openbmc/examples/path0/PythonObj",
"/org/openbmc/UserManager/Group",
"/org/openbmc/HostIpmi/1",
"/org/openbmc/HostServices",
"/org/openbmc/UserManager/Users",
"/org/openbmc/records/events",
"/org/openbmc/examples/path1/SDBusObj",
"/org/openbmc/UserManager/User",
"/org/openbmc/examples/path0/SDBusObj",
"/org/openbmc/examples/path1/PythonObj",
"/org/openbmc/UserManager/Groups",
"/org/openbmc/NetworkManager/Interface"
];
const output = {};
let current;
for (const path of data) {
current = output;
for (const segment of path.split('/')) {
if (segment !== '') {
if (!(segment in current)) {
current[segment] = {};
}
current = current[segment];
}
}
}
console.log(output);
Your solution was close, you just didn't reset the current
variable properly. Use this:
current = output;
instead of this:
current = output[path[0]];
Related Topics
Returning First X Items from Array
Measure String Size in Bytes in PHP
How Do Check If a PHP Session Is Empty
Check If Laravel Model Got Saved or Query Got Executed
Difference Between File, File_Get_Contents, and Fopen in PHP
Curl Requires Curlopt_Ssl_Verifypeer=False
How to Search Through a JSON Array in PHP
PHP Remove Elements from Associative Array
Removing Black Borders 4:3 on Youtube Thumbnails
How to Prevent PHP Files from Being Downloaded? and What Are Some Ways Someone Can Download Them
PHP Get the Last 3 Elements of an Associative Array While Preserving the Keys
Prevent PHP Script from Being Flooded
Uncompress a Gzip File from Curl, on PHP
How to Access a Different Controller from Inside a Controller Symfony2