嵌套的content_tags转义内部html ..为什么?

所以,如果我循环并创建一个li / a标签的集合,我会按预期得到..这些标签的数组:

(1..5).to_a.map do content_tag(:li) do link_to("boo", "www.boohoo.com") end end => ["
  • boo
  • ", "
  • boo
  • ", "
  • boo
  • ", "
  • boo
  • ", "
  • boo
  • "]

    我打电话给他们加入我得到一个预期的字符串……

     (1..5).to_a.map do content_tag(:li) do link_to("boo", "www.boohoo.com") end end.join => "
  • boo
  • boo
  • boo
  • boo
  • boo
  • "

    但是,如果我将这一级别更深地嵌入ol标签中……

     content_tag(:ol) do (1..5).to_a.map do content_tag(:li) { link_to("boo", "www.boohoo.com") } end.join end => "
      <li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li>
    "

    我逃脱了内心的疯狂!

    在查看rails源代码时:

      def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) content_tag_string(name, capture(&block), options, escape) else content_tag_string(name, content_or_options_with_block, options, escape) end end private def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options "#{escape ? ERB::Util.h(content) : content}".html_safe end 

    欺骗性地看起来我可以这样做:content_tag(:li,nil,nil,false)并没有让它逃避内容..但是:

     content_tag(:ol, nil, nil, false) do (1..5).to_a.map do content_tag(:li, nil, nil, false) do link_to("boo", "www.boohoo.com") end end.join end => "
      <li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li><li><a href="www.boohoo.com">boo</a></li>
    "

    我仍然患有不必要的html_escape综合症…

    因此,我知道避免这种情况的唯一方法是:

     content_tag(:ol) do (1..5).to_a.map do content_tag(:li) do link_to("boo", "www.boohoo.com") end end.join.html_safe end => "
    1. boo
    2. boo
    3. boo
    4. boo
    5. boo
    "

    但是..为什么会这样?

    之所以发生这种情况,是因为在Rails 3中引入了SafeBuffer类,它包装了String类并覆盖了在调用concat时可能发生的默认转义。

    在您的情况下,content_tag(:li)正在输出正确的SafeBuffer,但是Array#join不能理解SafeBuffers并且只是输出一个String。 然后使用String作为值而不是SafeBuffer调用content_tag(:ol)并将其转义。 所以它与嵌套没有多大关系,因为它与连接返回String而不是SafeBuffer有关。

    在String上调用html_safe,将String传递给raw,或者将数组传递给safe_join都将返回正确的SafeBuffer并阻止外部content_tag转义它。

    现在在将false传递给escape参数的情况下,当您将块传递给内容标记时,这不起作用,因为它调用了capture(&block) ActionView :: Helpers :: CaptureHelper,用于拉入模板,或者你的情况是join的输出值,然后它导致它在进入content_tag_string方法之前调用html_escape上的html_escape

      # action_view/helpers/tag_helper.rb def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) if block_given? options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) # capture(&block) escapes the string from join before being passed content_tag_string(name, capture(&block), options, escape) else content_tag_string(name, content_or_options_with_block, options, escape) end end # action_view/helpers/capture_helper.rb def capture(*args) value = nil buffer = with_output_buffer { value = yield(*args) } if string = buffer.presence || value and string.is_a?(String) ERB::Util.html_escape string end end 

    由于此处的值是join的返回值,而join返回一个String,因此在content_tag代码进入它之前调用html_escape并使用它自己的转义。

    一些感兴趣的参考链接

    https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/action_view/helpers/capture_helper.rb

    https://github.com/rails/rails/blob/v3.1.0/actionpack/lib/action_view/helpers/tag_helper.rb

    http://yehudakatz.com/2010/02/01/safebuffers-and-rails-3-0/

    http://railsdispatch.com/posts/security

    编辑

    处理此问题的另一种方法是执行map / reduce而不是map / join,因为如果reduce没有传递参数,它将使用第一个元素并使用该对象运行给定的操作,在map的情况下,content_tag将调用SafeBuffer上的操作。

     content_tag(:ol) do (1..5).to_a.map do content_tag(:li) do link_to(...) end end.reduce(:<<) # Will concat using the SafeBuffer instead of String with join end 

    作为单线

     content_tag(:ul) { collection.map {|item| content_tag(:li) { link_to(...) }}.reduce(:<<) } 

    添加一点meta-spice来清理东西

     ul_tag { collection.map_reduce(:<<) {|item| li_link_to(...) } } 

    谁需要html_safe ......这是Ruby!

    如果你使用safe_join会发生什么?

     content_tag(:ol) do safe_join (1..5).to_a.map { content_tag(:li) { link_to("boo", "www.boohoo.com") } }, '' end 

    或者只是使用原始?

     content_tag(ol) do 1.upto(5) { raw content_tag(:li) { link_to 'boo', 'www.boohoo.com' } # or maybe # raw content_tag(:li) { raw link_to('boo', 'www.boohoo.com') } } end 

    不是积极的,但我认为html转义发生在每个“层”(缺少更好的术语;每次迭代) – 我的意思是在内部块级别(1..5)…. 然后在外部块级别(content_tag(:ol)做…