Ruby - 散列数组 - 从散列值嵌套的HTML菜单,无需重复写入 [英] Ruby - Array of hashes - Nested HTML menu from hash values without writing duplicates

查看:134
本文介绍了Ruby - 散列数组 - 从散列值嵌套的HTML菜单,无需重复写入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个相对较大的散列值,其中所有的键值都是散列数组(参见下面的样本布局)。我花了4.5个小时试图为我的Rails应用程序写出HTML,我真的很想哭,因为我已经绕着圈子走了,没有真正的展示它!



任何帮助都很大赞赏,并提前感谢您的时间!



遇到的具体问题


  1. 书籍的章节/经文不对齐。

  2. 我也是例如,第4章出现多次(而不是一次出现,而多个章节/诗节引用嵌套在下面)

期望的解决方案标准


  1. HTML需要写成3层/嵌套(第一层/ div包含书名(例如创世纪,出埃及记,利未记),第二层/ div包含章节和第三层/ div包含诗句)


  2. 章节和诗节必须对齐(例如E xodus 2:7不应该写在创世记第2章菜单中)。

  3. 书名应该按照 的键(例如,数据格式:数据格式:


  4.   hsh = {:genesis => 
    [
    {:id => 1,:verse => '创世纪4:12'},
    {:id => 1,:verse => '创4:23-25'},
    {:id => 2,:verse => '创世记6:17'}
    ],
    :exodus =>
    [
    {:id => 5,:verse => '出埃及记2:7'},
    {:id => 3,:verse => '出埃及记2:14-15'},
    {:id => 4,:verse => 'Exodus 12:16'}
    ],
    :leviticus =>
    [
    {:id => 2,:verse => '利未记11:19-21'},
    {:id => 7,:verse => '利未记15:14-31'},
    {:id => 7,:verse => '利未记19:11-12'}
    ]
    }

    所需输出HTML [缩写简明]

     < div class =submenu> 
    < a href =#> Genesis< / a>
    < div class =lvl-2>
    < div>
    < div class =submenu>
    < a>第4章< / a>
    < div class =lvl-3>
    < div>
    < a onclick =load('1')>< span> ID 1< / span>第12节< / a>
    < a onclick =load('1')>< span> ID 1< / span>第23-25节< / a>
    < / div>
    < / div>
    < / div>
    < div class =submenu>
    < a>第6章< / a>
    < div class =lvl-3>
    < div> < a onclick =load('2')>< span> ID 2< / span>第17节< / a> < / DIV>
    < / div>
    < / div>
    < / div>
    < / div>
    < div class =submenu>
    < a href =#> Exodus< / a>
    < div class =lvl-2>
    < div>
    < div class =submenu>
    < a>第2章< / a>
    < div class =lvl-3>
    < div>
    < a onclick =load('5')>< span> ID 5< / span>第7节< / a>
    < a onclick =load('3')>< span> ID 3< / span> 14-15页< / a>
    < / div>
    < / div>
    < / div>
    < div class =submenu>
    < a>第12章< / a>
    < div class =lvl-3>
    < div>
    < a onclick =load('4')>< span> ID 4< / span>第16节< / a>
    < / div>
    < / div>
    < / div>
    < / div>
    < / div>
    ##为简洁起见缩短(不包括Leviticus参考)


< / div>

代码

  final_html = String.new 

hsh.each do | book,verse_array |

verse_array.each do | reference |

book = reference [:verse] .split('').first#=> Genesis
full_verse = reference [:verse] .split('').last#=> 4:12
chapter = full_verse.split(':')。first#=> 4
verse = full_verse.split(':')。first#=> 12

#失败在这里,将正确的HTML添加到final_html字符串中非常不幸...
#final_html<< < div class = \子菜单\\>
#final_html<< ...
#final_html<< ...

end
end

最后,请注意没有重复的章节/诗节组合(例如,创世纪4:12永远不会再出现)。同样值得注意的是,章节和诗句都需要按数字和升序排序。

解决方案

一旦将数据放入与所需输出非常相似的形状中,它就变得更加容易解决。你的输出是一个嵌套的树结构,所以你的数据也应该是这样。我们先做。以下是您的数据:

  data = {
genesis:[
{id:1,verse:创世纪4:12},
{id:1,诗:创世记4:23-25},
{id:2,verse:创世记6:17}
],
exodus:[
{id:5,verse:Exodus 2:7},
#...
],
#...
}

然后,暂时搁置HTML,这是我们想要的结构: / p>

 创世纪
第4章
ID 1 - 第12节
ID 1 - 第23-25节
第6章
ID 2 - 第17节
出埃及记
第2章
ID 5 - 第7节
...
...

我注意到您的数据的第一件事是它具有嵌套你不需要。由于:verse 值包含书名,因此我们不会从外部散列(:genesis et我们可以将所有内部哈希平铺到一个数组中:

  data.values.flatten 
#=> {创世记4:12},
#{id:1,诗:创世记4:23-25},
#{id:2,verse: 创6:17}
#{id:5,verse:Exodus 2:7},
##...]

现在我们需要一种方法从:verse 字符串中提取书籍,章节和诗歌。如果你愿意,你可以使用 String#split ,但是正则表达式也是不错的选择:

  VERSE_EXPR = /(.+)\s+(\\+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ VERSE_EXPR)
raise无效的字符串!如果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,书:创世记,章节:4,诗节:12},
#{id:1,书:创世记,章节:4,诗: 23-25},
#{id:2,book:Genesis,章节:6,诗句:17},
#{id:5,book:Exodus ,章节:2,诗句:7},
##...]



现在按书分组数据很容易:

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

...在每本书中,按章节:

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

你会注意到,因为我们在 books 上调用 map ,我们的最终结果是一个数组,而不是哈希。你可以调用 to_h 来使它成为一个哈希,但是对于我们的目的来说,这并不是必须的(迭代一个键值对的数组与遍历哈希)。



它看起来有点乱,但你可以看到结构在那里:Verses嵌套在书中嵌套的章节内。现在我们只需要将它变成HTML。


另外,为了我们的残疾朋友:正确的
用于嵌套树结构的HTML元素是< ul> < ol> 。如果
有一些要求可以使用< div> s,但是
,否则使用正确的元素作为使用者
辅助设备会感谢你。 (很多文章都写了
的样式树,所以我不会介绍它,但是一开始你可以用$ list-style-type隐藏项目符号: )

我没有Rails应用程序,所以要生成HTML我将只使用Ruby标准库中的ERB。除了如何将变量传递给视图之外,它在Rails中看起来或多或少都是相同的。

  requireerb 

VIEW =<< END
< ul>
<%books.each do | book_name,章节| %GT;
< li>
< a href =#><%= book_name%>< / a>
< ul>
<%chapters.each do | chapter,verses | %GT;
< li>
< a href =#>章节<%= chapter>< / a>
< ul>
<%verses.each do | id :, verse :, ** | %GT;
< li>
< a onclick =load(<%= id%>)> ID<%= id%> ;:诗歌<%=诗歌%>< / 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)

以下是结果作为一个HTML代码片段:



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

它正在对repl.it进行操作: https://repl.it/Duhm


I have a relatively large hash where the values for all keys within are array of hashes (see below for sample layout). I have spent the last 4.5 hours attempting to write out HTML for my Rails application and I am seriously about to cry as I've gone round in circles with nothing really to show for it!

Any help is greatly appreciated and thank you in advance for your time!

Specific Problems Encountered

  1. Chapters/verses appear for books that they do not align to.
  2. I'm also not able to de-duplicate entries, so 'Chapter 4', for example, is appearing multiple times (instead of it appearing once, with mulitple chapter/verse references nested beneath)

Desired Solution Criteria

  1. The HTML needs to be written in 3 layers/nested (1st layer/div containing Book Name (e.g. Genesis, Exodus, Leviticus), 2nd layer/div containing chapter and 3rd layer/div containing verse)

  2. Chapters and verses must align (e.g. Exodus 2:7 should not be written to the Genesis chapter 2 menu).

  3. Book names should be written in the order of hsh's keys (e.g. Genesis followed by Exodus followed by Leviticus as opposed to alphabetical order)

Data Format:

hsh = { :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'},
                 {:id => 3, :verse => 'Exodus 2:14-15'},
                 {:id => 4, :verse => 'Exodus 12:16'}
             ],
        :leviticus =>
             [
                 {:id => 2, :verse => 'Leviticus 11:19-21'},
                 {:id => 7, :verse => 'Leviticus 15:14-31'},
                 {:id => 7, :verse => 'Leviticus 19:11-12'}
             ]
     }

Desired Output HTML [Shortened for Brevity]

<div class="submenu">
    <a href="#">Genesis</a>
    <div class="lvl-2">
        <div>
            <div class="submenu">
                <a>Chapter 4</a>
                <div class="lvl-3">
                    <div>
                        <a onclick="load('1')"><span>ID 1</span> Verse 12</a>
                        <a onclick="load('1')"><span>ID 1</span> Verse 23-25</a>
                    </div>
                </div>
            </div>
            <div class="submenu">
                <a>Chapter 6</a>
                <div class="lvl-3">
                    <div> <a onclick="load('2')"><span>ID 2</span> Verse 17</a> </div>
                </div>
            </div>
        </div>
    </div>
    <div class="submenu">
        <a href="#">Exodus</a>
        <div class="lvl-2">
            <div>
                <div class="submenu">
                    <a>Chapter 2</a>
                    <div class="lvl-3">
                        <div>
                            <a onclick="load('5')"><span>ID 5</span> Verse 7</a>
                            <a onclick="load('3')"><span>ID 3</span>Verse 14-15</a>
                        </div>
                    </div>
                </div>
                <div class="submenu">
                    <a>Chapter 12</a>
                    <div class="lvl-3">
                        <div>
                            <a onclick="load('4')"><span>ID 4</span> Verse 16</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        ## Shortened for brevity (Leviticus references excluded)
    </div>
</div>

Code

final_html = String.new

hsh.each do |book, verse_array|

    verse_array.each do |reference|

      book = reference[:verse].split(' ').first           # => "Genesis"
      full_verse = reference[:verse].split(' ').last      # => "4:12"
      chapter = full_verse.split(':').first               # => "4"
      verse = full_verse.split(':').first                 # => "12"

      # Failing Miserably at appending the right HTML to the final_html string here...
      # final_html << "<div class=\"submenu\">"
      # final_html << ...
      # final_html << ...

    end
end

Finally, note that there are no duplicate chapter/verse combinations (e.g. Genesis 4:12 will never appear a second time). It's also worth noting that both chapters and verses need to be sorted numerically and ascending.

解决方案

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>

Finally, here it is in action on repl.it: https://repl.it/Duhm

这篇关于Ruby - 散列数组 - 从散列值嵌套的HTML菜单,无需重复写入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆