Generate a file list based on an array

2019-04-02 00:30发布

问题:

I tried a few things but this week i feel like my brain's having holidays and i need to complete this thing.. so i hope someone can help me.

I need to create a filelist based on a hash which is saved into a database. The has looks like this:

['file1', 'dir1/file2', 'dir1/subdir1/file3']

Output should be like this:

  • file1
  • dir1
    • file2
    • subdir1
      • file3

in html, preferrably like this (to extend it with js to fold and multiselect)

<ul>
  <li>file1
  <li>dir1</li>
  <ul>
    <li>file2</li>
    <li>subdir1</li>
    <ul>
      <li>file3</li>
    </ul>
  </ul>
</ul>

I'm using Ruby on Rails and try to achieve this in an RJS template. But this don't really matters. You can also help me with some detailed pseudo-code.

Someone know how to solve this?


Edit

Thanks to everyone for these solutions. Listing works, i extended it to a foldable solution to show/hide directory contents. I still have one problem: The code aims to have complete file paths in checkboxes behind the entries for a synchronisation. Based on sris' solution, i can only read the current file and it's subs, but not the whole path from the root. For a better understanding:

Currently:

[x] dir1
    [x] dir2
        [x] file1

gives me

a checkbox with the same value a sthe text displays, e.g "file1" for [x] file1. But what i need is a full path, e.g "dir1/dir2/file1" for [x] file1.

Does someone have another hint how to add this?

回答1:

Here's a quick implementation you can use for inspiration. This implementation disregards the order of files in the input Array.

I've updated the solution to save the entire path as you required.

dirs = ['file1', 'dir1/file2', 'dir1/subdir1/file3',  'dir1/subdir1/file5']
tree = {}

dirs.each do |path|
  current  = tree
  path.split("/").inject("") do |sub_path,dir|
    sub_path = File.join(sub_path, dir)
    current[sub_path] ||= {}
    current  = current[sub_path]
    sub_path
  end
end

def print_tree(prefix, node)
  puts "#{prefix}<ul>"
  node.each_pair do |path, subtree| 
    puts "#{prefix}  <li>[#{path[1..-1]}] #{File.basename(path)}</li>"    
    print_tree(prefix + "  ", subtree) unless subtree.empty?
  end
  puts "#{prefix}</ul>"
end

print_tree "", tree

This code will produce properly indented HTML like your example. But since Hashes in Ruby (1.8.6) aren't ordered the order of the files can't be guaranteed.

The output produced will look like this:

<ul>
  <li>[dir1] dir1</li>
  <ul>
    <li>[dir1/subdir1] subdir1</li>
    <ul>
      <li>[dir1/subdir1/file3] file3</li>
      <li>[dir1/subdir1/file5] file5</li>
    </ul>
    <li>[dir1/file2] file2</li>
  </ul>
  <li>[file1] file1</li>
</ul>

I hope this serves as an example of how you can get both the path and the filename.



回答2:

Think tree.

  # setup phase
  for each pathname p in list
  do
     add_path_to_tree(p)
  od
  walk tree depth first, emitting HTML

add_path_to_tree is recursive

 given pathname p
 parse p into first_element, rest
 # that is, "foo/bar/baz" becomes "foo", "bar/baz"
 add first_element to tree
 add_path_to_tree(rest)

I'll leave the optimal data struct (list of lists) for the tree (list of lists) as an exercise.



回答3:

Expanding on sris's answer, if you really want everything sorted and the files listed before the directories, you can use something like this:

def files_first_traverse(prefix, node = {})
  puts "#{prefix}<ul>" 
  node_list = node.sort
  node_list.each do |base, subtree|
    puts "#{prefix}  <li>#{base}</li>" if subtree.empty?
  end
  node_list.each do |base, subtree|
    next if subtree.empty?
    puts "#{prefix}  <li>#{base}</li>"
    files_first_traverse(prefix + '  ', subtree)
  end
  puts '#{prefix}</ul>'
end