2011-01-03 14 views
7

Sto cercando di analizzare un file YAML come questo:trasformare un hash rubino nella lista html

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

ottengo un hash come questo:

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

e voglio girarlo al un elenco:

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

sto cercando di cercare il modo più efficiente non importa quanto in profondità è l'hash

Infine ho fatto in questo modo:

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 

Ora è pronto per analizzare le opzioni troppo.

+1

è possibile correggere la formattazione della domanda? – cam

+0

Questo è un bel problema per un codice kata. – kikito

risposta

4

Per l'uscita di semplice HTML si può solo effettuare una chiamata ricorsiva della stessa funzione all'interno di un blocco each (oppure utilizzare la funzione come il blocco each come ho fatto io qui):

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>" 

Per uscita in qualsiasi linguaggio di template tu stia usando (HAML, penso), abbiamo bisogno di tenere traccia del rientro, quindi le cose sono un po 'più complicate - useremo una funzione che prende la profondità dell'indentazione come parametro, e restituisce un'altra funzione da chiamare su ogni coppia chiave/valore che stampa quella coppia chiave/valore (in modo ricorsivo) con indentazione appropriata. (In programmazione funzionale, chiamando una funzione in questo modo viene chiamata una "funzione parzialmente applicata", e sono di solito un po 'più facile da definire rispetto a 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)) 
+0

molto bello, ma per farlo funzionare ho bisogno di modificare le prime 3 righe a: def hash_to_html (mappa) key = mappa [0] value = mappa [1] – JAlberto

+1

mi consiglia la restituzione di valori invece di eseguire mette.In questo modo la funzione può essere utilizzata in altri posti (e non solo sulla console). – kikito

+0

@JAlberto: Sono confuso: 'map [0]' e 'map [1]' restituirebbero entrambi nil nei dati di esempio che hai dato. –

2
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) 
+0

Vedere i commenti sulla risposta di Nakilon sui nomi delle variabili a 1 lettera. – kikito

+0

modificato grazie. – gduq

3

ho fatto questo uno :

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 

uscita:

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

Chiarimenti Modifica-:

  • string * n ripete stringn volte.
  • L'utilizzo di array.join è più efficiente rispetto alla creazione di un gruppo di stringhe e alla loro unione con +.
+0

abbastanza pulito, mi piace. – JAlberto

+0

Bello, ma il problema con questa soluzione è che se si elabora l'output generato tramite HAML :: Engine, si lamenterà dell'annidamento illegale: "il contenuto non può essere fornito sulla stessa riga di% li e annidato al suo interno". . –

1

Ecco una soluzione che esegue sia HTML che Haml. Leggermente verboso, ma leggibile.

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>