Turn a Ruby Hash into HTML List

turn a ruby hash into html list

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

Generate an HTML table from an array of hashes in Ruby

Use the XMLBuilder for this:

data = [{"col1"=>"v1", "col2"=>"v2"}, {"col1"=>"v3", "col2"=>"v4"}]
xm = Builder::XmlMarkup.new(:indent => 2)
xm.table {
xm.tr { data[0].keys.each { |key| xm.th(key)}}
data.each { |row| xm.tr { row.values.each { |value| xm.td(value)}}}
}
puts "#{xm}"

Output

<table>
<tr>
<th>col1</th>
<th>col2</th>
</tr>
<tr>
<td>v1</td>
<td>v2</td>
</tr>
<tr>
<td>v3</td>
<td>v4</td>
</tr>
</table>

Dynamically add to HTML list from Rails hash

If that's your actual YAML file, the the reason you are only seeing one object is because your keys are all person. If you parse this into a hash, each instance of person is going to be stored on the person key, and since hash keys must be unique, only the last record is persisted.

You probably want to structure this as an array of hashes (or "sequence of mappings" in YAML speak):

---
- name: LMN
email: qrs@yahoo.com
- name: ABC
email: xyz@gmail.com

Then iterate as:

<% data.each do |entry| %>
<li><%= entry['name'] %> - <%= entry['email'] %></li>
<% end %>

Print hash on table html - Ruby on rails

As David already said, with your input, it will be really difficult to achieve what you need. Since it was fun, I fixed it, but I do believe that it should be fixed somewhere higher in your code (I hope you're using Ruby 2.5+, if not, let me know which version you are on).

def fix_my_data(data)
data.group_by { |x| x[:id_control_access] }
.transform_values do |v|
v.map { |h| h.slice(:input, :output) }
.group_by { |h| h.keys.first }.values.inject(:zip).map { |x,y| x.merge(y.to_h) }
end
end

If you pass your array into this function, it will return this:

{1=>[{:input=>"Antena 1", :output=>"Antena 2"}, {:input=>"Antena 5"}],
2=>[{:input=>"Antena 3", :output=>"Antena 4"}]}

Which should be really simple to generate HTML with, like so:

<tr>
<% @data[control_access[:id_control_access]].each do |antenna| %>
<td><%= antenna[:input] %></td>
<td><%= antenna[:output] %></td>
<% end %>
</tr>

I'm pretty sure fix_my_data can be written in a bit simpler way, but as I mentioned, it's a late place to be fixing the data.

Rails helper method: Nested hash to nested HTML list

You can make a recursive method to render to hash to a nested set of lists. Place this in your relevant helper:

def hash_list_tag(hash)
html = content_tag(:ul) {
ul_contents = ""
ul_contents << content_tag(:li, hash[:parent])
hash[:children].each do |child|
ul_contents << hash_list_tag(child)
end

ul_contents.html_safe
}.html_safe
end

Ruby - Array of hashes - Nested HTML menu from hash values without writing duplicates

As is often the case with such problems, it becomes much easier to solve once you've put the data into a "shape" that closely resembles your desired output. Your output is a nested tree structure, so your data should be, too. Let's do that first. Here's your data:

data = {
genesis: [
{ id: 1, verse: "Genesis 4:12" },
{ id: 1, verse: "Genesis 4:23-25" },
{ id: 2, verse: "Genesis 6:17" }
],
exodus: [
{ id: 5, verse: "Exodus 2:7" },
# ...
],
# ...
}

And, setting aside the HTML for now, here's the structure we want:

Genesis
Chapter 4
ID 1 - Verse 12
ID 1 - Verse 23-25
Chapter 6
ID 2 - Verse 17
Exodus
Chapter 2
ID 5 - Verse 7
...
...

The first thing I notice about your data is that it has a level of nesting you don't need. Since the :verse values contain the book name, we don't the keys from the outer hash (:genesis et al) and we can just flatten all of the inner hashes into a single array:

data.values.flatten
# => [ { id: 1, verse: "Genesis 4:12" },
# { id: 1, verse: "Genesis 4:23-25" },
# { id: 2, verse: "Genesis 6:17" }
# { id: 5, verse: "Exodus 2:7" },
# # ... ]

Now we need a method to extract the book, chapter, and verse from the :verse strings. You can use String#split if you want, but a Regexp is a good choice too:

VERSE_EXPR = /(.+)\s+(\d+):(.+)$/

def parse_verse(str)
m = str.match(VERSE_EXPR)
raise "Invalid verse string!" if m.nil?
book, chapter, verse = m.captures
{ book: book, chapter: chapter, verse: verse }
end

flat_data = data.values.flatten.map do |id:, verse:|
{ id: id }.merge(parse_verse(verse))
end
# => [ { id: 1, book: "Genesis", chapter: "4", verse: "12" },
# { id: 1, book: "Genesis", chapter: "4", verse: "23-25" },
# { id: 2, book: "Genesis", chapter: "6", verse: "17" },
# { id: 5, book: "Exodus", chapter: "2", verse: "7" },
# # ... ]

Now it's easy to group the data by book:

books = flat_data.group_by {|book:, **| book }
# => { "Genesis" => [
# { id: 1, book: "Genesis", chapter: "4", verse: "12" },
# { id: 1, book: "Genesis", chapter: "4", verse: "23-25" },
# { id: 2, book: "Genesis", chapter: "6", verse: "17" }
# ],
# "Exodus" => [
# { id: 5, book: "Exodus", chapter: "2", verse: "7" },
# # ...
# ],
# # ...
# }

...and within each book, by chapter:

books_chapters = books.map do |book, verses|
[ book, verses.group_by {|chapter:, **| chapter } ]
end
# => [ [ "Genesis",
# { "4" => [ { id: 1, book: "Genesis", chapter: "4", verse: "12" },
# { id: 1, book: "Genesis", chapter: "4", verse: "23-25" } ],
# "6" => [ { id: 2, book: "Genesis", chapter: "6", verse: "17" } ]
# }
# ],
# [ "Exodus",
# { "2" => [ { id: 5, book: "Exodus", chapter: "2", verse: "7" },
# # ... ],
# # ...
# }
# ],
# # ...
# ]

You'll notice that since we called map on books our final result is an Array, not a Hash. You could call to_h on it to make it a Hash again, but for our purposes it's not necessary (iterating over an Array of key-value pairs works the same as iterating over a Hash).

It looks a little messy, but you can see that the structure is there: Verses nested within chapters nested within books. Now we just need to turn it into HTML.

An aside, for the sake of our friends with disabilities: The correct
HTML element to use for nested tree structures is <ul> or <ol>. If
you have some requirement to use <div>s instead you can, but
otherwise use the right element for the job—users who use
accessibility devices will thank you. (Many articles have been written
on styling such trees, so I won't go into it, but for a start you can
hide the bullets with list-style-type: none;.)

I don't have a Rails app at my disposal, so to generate the HTML I'll just use ERB from the Ruby standard library. It will look more-or-less identical in Rails except for how you pass the variable to the view.

require "erb"

VIEW = <<END
<ul>
<% books.each do |book_name, chapters| %>
<li>
<a href="#"><%= book_name %></a>
<ul>
<% chapters.each do |chapter, verses| %>
<li>
<a href="#">Chapter <%= chapter %></a>
<ul>
<% verses.each do |id:, verse:, **| %>
<li>
<a onclick="load(<%= id %>)">ID <%= id %>: Verse <%= verse %></a>
</li>
<% end %>
</ul>
</li>
<% end %>
</ul>
</li>
<% end %>
</ul>
END

def render(books)
b = binding
ERB.new(VIEW, nil, "<>-").result(b)
end

puts render(books_chapters)

And here's the result as an HTML snippet:

<ul>
<li> <a href="#">Genesis</a> <ul>
<li> <a href="#">Chapter 4</a> <ul>
<li> <a onclick="load(1)">ID 1: Verse 12</a> </li>
<li> <a onclick="load(1)">ID 1: Verse 23-25</a> </li>
</ul> </li>
<li> <a href="#">Chapter 6</a> <ul>
<li> <a onclick="load(2)">ID 2: Verse 17</a> </li>
</ul> </li>
</ul> </li>
<li> <a href="#">Exodus</a> <ul>
<li> <a href="#">Chapter 2</a> <ul>
<li> <a onclick="load(5)">ID 5: Verse 7</a> </li>
<li> <a onclick="load(3)">ID 3: Verse 14-15</a> </li>
</ul> </li>
<li> <a href="#">Chapter 12</a> <ul>
<li> <a onclick="load(4)">ID 4: Verse 16</a> </li>
</ul> </li>
</ul> </li>
<li> <a href="#">Leviticus</a> <ul>
<li> <a href="#">Chapter 11</a> <ul>
<li> <a onclick="load(2)">ID 2: Verse 19-21</a> </li>
</ul> </li>
<li> <a href="#">Chapter 15</a> <ul>
<li> <a onclick="load(7)">ID 7: Verse 14-31</a> </li>
</ul> </li>
<li> <a href="#">Chapter 19</a> <ul>
<li> <a onclick="load(7)">ID 7: Verse 11-12</a> </li>
</ul> </li>
</ul> </li>
</ul>

Ruby HTML attributes to Hash or Array

Using Nokogiri, the attributes are already parsed for you - simply access them using []:

doc = Nokogiri::HTML.parse('<html><body><div type="checkbox" name="prdCdList" value="102001174" class="bnone" newfl="Y" cpnfl="N" catcpnfl="N" eventfl="N" catcd1="102000" catcd2="102001" prdimgl="/upload/product/320_1405497216907.jpg" prdnm="Dear my volume" prdvol="3.4g" prdlndesc="Limited Pink" selprc="10000" spsalprc="0" cpnprc="0" cashptrat="0" cashpt="0" discpt="0" salstatcdnm="Available" salstatcd="PS01" prdwidth="0" prdheight="0" prddepth="0" pricestr="" price="10000" prepromote="" endpromote=""></body></html>')
div = doc.css('div').first
div['prdnm']
# => "Dear my volume"

From the documentation:

Nokogiri::XML::Node is your window to the fun filled world of dealing
with XML and HTML tags. A Nokogiri::XML::Node may be treated similarly
to a hash with regard to attributes. For example (from irb):

01.irb(main):004:0> node
02.=> <a href="#foo" id="link">link</a>
03.irb(main):005:0> node['href']
04.=> "#foo"
05.irb(main):006:0> node.keys
06.=> ["href", "id"]
07.irb(main):007:0> node.values
08.=> ["#foo", "link"]
09.irb(main):008:0> node['class'] = 'green'
10.=> "green"
11.irb(main):009:0> node
12.=> <a href="#foo" id="link" class="green">link</a>
13.irb(main):010:0>

See Nokogiri::XML::Node#[] and Nokogiri::XML#[]= for more information.

How to convert Ruby data structure to a HTML file

First thing first: if you need to exchange data between applications I suggest to stick with standard formats like JSON or YAML. I don't know if you can control the data logging, but if you can, I suggest to change the code there.

That log file is really a mess but it contains enough information for convert it into a ruby data structure like arrays and hashes.

There is always a better way but I ended up with this solution.

REJECT_THIS = ["", "------------------------------ ----------", "----Persons", "---- Persons", "persons demat"]
holder = []
separator = '|||'

# here we store the file into the holder, skipping rows in REJECT_THIS
text = File.open("_states.log").read
text.each_line do |line|
line = line.split.join(' ')
holder << line unless REJECT_THIS.include? line
end

# just to change the separator mark into a shorter one
holder.map! { |e| e == "=========================================" ? separator : e}

# map where to split the array grouping by state
split_idxs = holder.map.with_index { |e, i| e [0..4] == 'state' ? i : 0}.uniq[1..-1]<<holder.size

# split the array in states using the index map using the my_split_at_index method and building the states hash
holder = holder.my_split_at_index(split_idxs).map { |element| {element.shift => element} }

# remove 'state_' and convert the key to symbol
holder.map! { |e| e.transform_keys { |key| key[6..-1].to_sym } }

# splits subarrays by separator then build the nested hash
holder.map! do |array|
array.transform_values do |sub_array|
split_idxs = sub_array.map.with_index { |e, i| e == separator ? i : 0 }.uniq[1..-1]
sub_array = sub_array.my_split_at_index(split_idxs).map! { |e| e[0] == separator ? e[1..-1] : e }
sub_array.map { |element| {city: element.shift, people: element} }
end
end

# splits the people string
holder.map do |state|
state.transform_values do |array|
array.map do |hash|
hash[:people].map! { |string| string.split(' ') }
end
end
end

p holder

In the code I used this Array monkey patch

class Array

def my_split_at_index(indexes = [])
shift = 0
splitted = []
indexes.map do |index|
splitted << self[shift..index-1]
shift = index
end
splitted
end

end

The variable holder now is an array of nested hashes that you can use with ERB in a code like Dan Hilton posted. The data structure of holder is not the same, so you need to tweak the code.

One last thing, to see how the structure of your data as a YAML looks:

require 'yaml'
puts holder.to_yaml


Related Topics



Leave a reply



Submit