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 withlist-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. ANokogiri::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#[]
andNokogiri::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
Finding the Difference Between Strings in Ruby
Ruby Method, Proc, and Block Confusion
Re-Opened Nested Module Anomaly in Ruby
Simulating Race Conditions in Rspec Unit Tests
Ruby - Problems with Expect and Pty
How to Join a Table and Count Records in Rails 3
Which Ruby Rest API Client for Neo4J
Groups in a Gemfile in Rails 3
What Are Some Examples of Using Nokogiri
How to Change Case of Letters in String Using Regex in Ruby
How to Get Rufus-Scheduler Working with a Rails App Deployed to Heroku
How to Import Using Fastercsv a Row with a Name Like "CiaráN"
How to Upload a Local File to a Carrierwave Model
How Do Open a File for Writing Only If It Doesn't Already Exist in Ruby