Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

turn a ruby hash into html list

I'm trying to parse a yaml file like this:

a:
 a1:
 a2:
b:
 b1:
  b11:
 b2:

i get a hash like this:

{"a"=>{"a1"=>nil, "a2"=>nil}, "b"=>{"b1"=>{"b11"=>nil}, "b2"=>nil}}

and i want to turn it to a list:

%ul
 %li a
  %ul 
   %li a1
   %li a2
 %li b
  %ul
   %li b1
    %ul
     %li b11
   %li b2

I'm trying to search the most efficent way doesn't matter how deep is the hash

Finally i did in this way:

KeyWords = %w(url)

# Convert a multilevel hash into haml multilevel tree
# Special KeyWords
# url : item url
def hash_to_haml(hash, url = nil)
  haml_tag(:ul) do
    hash.each do |key, value|

      unless KeyWords.include?(key)
        url = get_url(key, value)

        haml_tag(:li) do
          haml_tag(:a, :href => url ) do
            haml_concat(key)
          end
          hash_to_haml(value) if value.is_a?(Hash) && !value.empty?
        end

      end

    end
  end
end

private

def get_url(key, hash)
  # TODO: get full url from hash
  if hash.nil?
    "/#{key}"
  else
    hash.include?("url") ? hash.delete("url") : "/#{key}"
  end
end

Now is prepared to parse options too.

like image 826
JAlberto Avatar asked Jan 03 '11 20:01

JAlberto


4 Answers

I've done this one:

INDENT = '  ' # use 2 spaces for indentation

def hash_to_haml(hash, level=0)
  result = [ "#{INDENT * level}%ul" ]
  hash.each do |key,value|
    result << "#{INDENT * (level + 1)}%li #{key}"
    result << hash_to_haml(value, level + 2) if value.is_a?(Hash)
  end
  result.join("\n")
end

Usage:

hash = {"a"=>{"a1"=>nil, "a2"=>nil}, "b"=>{"b1"=>{"b11"=>nil}, "b2"=>nil}
string = hash_to_haml(hash)
puts string

Output:

%ul
  %li a
    %ul
      %li a1
      %li a2
  %li b
    %ul
      %li b1
        %ul
          %li b11
      %li b2

EDIT- Clarifications:

  • string * n repeats string n times.
  • Using array.join is more efficient than creating a bunch of strings and joining them with +.
like image 24
kikito Avatar answered Sep 29 '22 20:09

kikito


require 'yaml'
yaml = <<EOS
a:
 a1:
 a2:
b:
 b1:
  b11:
 b2:
EOS

# unless using ruby 1.9 make sure you use an ordered hash such as ActiveSupport::OrderedHash 
hash = YAML::load yaml

def hash_to_haml(hash, indent)
  puts " " * indent + "%ul"
  indent += 1
  hash.each do |key, value|
    puts " " * indent + "%li " + key 
    hash_to_haml(value, indent + 1) if value.is_a? Hash
  end
end

#start with 0 indent
hash_to_haml(hash, 0)
like image 38
gduq Avatar answered Sep 29 '22 20:09

gduq


Here's a solution that does both HTML and Haml. Slightly wordy, but readable.

class ListMaker
  def initialize(hash)
    @hash = hash
    @indent = "  "
    @level = 0
    @out = []
  end

  def append(tag,value=nil)
    str = @indent * @level + "#{tag}"
    str += @tag_space + value unless value.nil?
    str += "\n"
    @out << str
  end

  def ul(hash)
    open_tag('ul') { li(hash) }
  end

  def li(hash)
    @level += 1
    hash.each do |key,value|
      open_tag('li',key) { ul(value) if value.is_a?(Hash) }
    end
    @level -= 1
  end

  def list
    ul(@hash)
    @out.join
  end
end

class HtmlListMaker < ListMaker
  def initialize(hash)
    super
    @tag_space = ""
  end

  def open_tag(tag,value=nil,&block)
    append("<#{tag}>",value)
    yield if block_given?
    append("</#{tag}>")
  end
end

class HamlListMaker < ListMaker
  def initialize(hash)
    super
    @tag_space = " "
  end

  def open_tag(tag,value=nil,&block)
    append("%#{tag}",value)
    yield if block_given?
  end

end

require 'yaml'

yaml = <<EOS
a:
 a1:
 a2:
b:
 b1:
  b11:
 b2:
EOS

hash = YAML.load(yaml) # {"a"=>{"a1"=>nil, "a2"=>nil}, "b"=>{"b1"=>{"b11"=>nil}, "b2"=>nil}}

puts HamlListMaker.new(hash).list

# %ul
#   %li a
#   %ul
#     %li a1
#     %li a2
#   %li b
#   %ul
#     %li b1
#     %ul
#       %li b11
#     %li b2

puts HtmlListMaker.new(hash).list

# <ul>
#   <li>a
#   <ul>
#     <li>a1
#     </li>
#     <li>a2
#     </li>
#   </ul>
#   </li>
#   <li>b
#   <ul>
#     <li>b1
#     <ul>
#       <li>b11
#       </li>
#     </ul>
#     </li>
#     <li>b2
#     </li>
#   </ul>
#   </li>
# </ul>
like image 27
zetetic Avatar answered Sep 29 '22 19:09

zetetic


To output to plain HTML you can just perform a recursive call of the same function within a each block (or use the function as the each block as I have done here):

def hash_to_html key,value
   if value.nil?
     puts "<li>#{key}</li>"
   elsif value.is_a?(Hash)
     puts "<li>#{key}"
     puts "<ul>"
     value.each(&method(:hash_to_html))
     puts "</ul></li>"
   else
     fail "I don't know what to do with a #{value.class}"
   end
end

puts "<ul>"
yourhash.each(&method(:hash_to_html))
puts "</ul>"

To output to whatever templating language you're using (HAML, I think), we need to keep track of indentation, so things are a little more complicated -- we're going to use a function that takes the indenting depth as a parameter, and returns another function to be called on each key/value pair that prints that key/value pair (recursively) with appropriate indentation. (In functional programming, calling a function this way is called a "partially applied function", and they're usually a little easier to define than in Ruy.)

def hash_to_haml depth
   lambda do |key,value|
     puts " "*depth + "%li #{key}"
      if value.nil?
        # do nothing
        # (single this case out, so as not to raise an error here)
      elsif value.is_a?(Hash)
        puts " "*(depth+1) + "%ul"
        value.each(&hash_to_haml(depth+2))
      else
        fail "I don't know what to do with a #{value.class}"
      end
   end
end

puts "%ul"
yourhash.each(&hash_to_haml(1))
like image 119
Ken Bloom Avatar answered Sep 29 '22 21:09

Ken Bloom