2011-01-03 9 views
7

Estoy tratando de analizar un archivo YAML como esto:convertir un hash de rubí en la lista html

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

me sale un hash de esta manera:

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

y quiero convertirlo a una lista:

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

estoy tratando de buscar la manera más eficiente, no importa qué tan profundo es el hash

Finalmente lo hice de esta manera:

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 

Ahora está preparado para analizar las opciones también.

+1

¿podría corregir el formato de su pregunta? – cam

+0

Este es un problema muy bueno para un código kata. – kikito

Respuesta

4

Para dar salida a HTML plano que sólo puede realizar una llamada recursiva de la misma función dentro de un bloque each (o utilizar la función como el bloque each como he hecho aquí):

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

Para salida a cualquier lenguaje de plantillas que estés usando (HAML, creo), necesitamos hacer un seguimiento de la sangría, por lo que las cosas son un poco más complicadas: vamos a usar una función que tome la profundidad de sangrado como un parámetro, y devuelve otra función para ser invocada en cada pareja clave/valor que imprime ese par clave/valor (recursivamente) con la sangría apropiada. (En la programación funcional, llamar a una función de esta forma se llama una "función parcialmente aplicada", y son por lo general un poco más fácil de definir que en 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

bastante bien, pero para que funcione tengo que modificar las 3 primeras líneas a: def hash_to_html (mapa) clave = mapa [0] = valor mapa [1] – JAlberto

+1

me gustaría recomendar la devolución de valores en lugar de ejecutar pone.De esta forma, la función se puede usar en otros lugares (y no solo en la consola). – kikito

+0

@JAlberto: Estoy confundido: 'map [0]' y 'map [1]' devolverían nulo en los datos de ejemplo que proporcionó. –

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

Ver los comentarios sobre la respuesta de Nakilon sobre nombres de variables de 1 letra. – kikito

+0

editado gracias. – gduq

3

que he hecho éste :

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 

Uso:

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

salida:

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

Aclaraciones: EDIT-

  • string * n repite stringn veces.
  • Usar array.join es más eficiente que crear un conjunto de cadenas y unirlas con +.
+0

bastante limpio, me gusta. – JAlberto

+0

Agradable, pero el problema con esta solución es que si procesa la salida generada a través de HAML :: Engine, se quejará de la anidación ilegal: 'el contenido no se puede dar tanto en la misma línea como% li y anidado dentro de él' . –

1

Aquí hay una solución que hace tanto HTML como Haml. Ligeramente prolijo, pero legible.

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> 
Cuestiones relacionadas