【问题标题】:How to put a group of <p> inside a <div>如何将一组 <p> 放入 <div>
【发布时间】:2009-03-11 18:54:19
【问题描述】:

我想找到一种方法来使用以下 Ruby 代码和Nokogiri 获取 HTML 结果(下面将进一步提到):

require 'rubygems'
require 'nokogiri'

value = Nokogiri::HTML.parse(<<-HTML_END)
  "<html>
    <body>
      <p id='1'>A</p>
      <p id='2'>B</p>
      <h1>Bla</h1>
      <p id='3'>C</p>
      <p id='4'>D</p>
      <p id='5'>E</p>
    </body>
  </html>"
HTML_END

# The selected-array is given by the application.
# It consists of a sorted array with all ids of 
# <p> that need to be enclosed by the <div>
selected = ["2","3","4"]
first_p = selected.first
last_p = selected.last

#
# WHAT RUBY CODE DO I NEED TO INSERT HERE TO GET
# THE RESULTING HTML AS SEEN BELOW?
#

生成的 HTML 应如下所示(请注意插入的 &lt;div id='XYZ'&gt;):

<html>
  <body>
    <p id='1'>A</p>
    <div id='XYZ'>
      <p id='2'>B</p>
      <h1>Bla</h1>
      <p id='3'>C</p>
      <p id='4'>D</p>
    </div>
    <p id='5'>E</p>
  </body>
</html>

【问题讨论】:

    标签: ruby-on-rails ruby dom rubygems nokogiri


    【解决方案1】:

    在这些情况下,您通常希望使用底层库为您提供的任何 SAX interface,以有状态和连续地遍历和重写输入 XML(或 XHTML):

    require 'nokogiri'
    require 'CGI'
    
    Nokogiri::XML::SAX::Parser.new(
      Class.new(Nokogiri::XML::SAX::Document) {
        def initialize first_p, last_p
          @first_p, @last_p = first_p, last_p
        end
    
        def start_document
          puts '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">'
        end
    
        def start_element name, attrs = []
          attrs = Hash[*attrs]
          @depth += 1 unless @depth.nil?
          print '<div>' if name=='p' && attrs['id'] == @first_p
          @depth = 1    if name=='p' && attrs['id'] == @last_p && @depth.nil?
          print "<#{ [ name, attrs.collect { |k,v| "#{k}=\"#{CGI::escapeHTML(v)}\"" } ].flatten.join(' ') }>"
        end
    
        def end_element name
          @depth -= 1 unless @depth.nil?
          print "</#{name}>"
          if @depth == 0
            print '</div>'
            @depth = nil
          end
        end
    
        def cdata_block string
          print "<![CDATA[#{CGI::escapeHTML(string)}]]>"
        end
    
        def characters string
          print CGI::escapeHTML(string)
        end
    
        def comment string
          print "<!--#{string}-->"
        end
      }.new('2', '4')
    ).parse(<<-HTML_END)
      <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
      <html>
        <body>
          <!-- comment -->
          <![CDATA[
            cdata goes here
          ]]>
          &quot;special&quot; entities 
          <p id="1">A</p>
          <p id="2">B</p>
          <p id="3">C</p>
          <p id="4">D</p>
          <p id="5">E</p>
          <emptytag/>
        </body>
      </html>
    HTML_END
    

    或者,您也可以使用DOM model interface(而不是 SAX 接口)将整个文档加载到内存中(与您在原始问题中开始执行的方式相同),然后执行节点操作(插入和删除)如下:

    require 'rubygems'
    require 'nokogiri'
    
    doc = Nokogiri::HTML.parse(<<-HTML_END)
      <html>
        <body>
          <p id='1'>A</p>
          <p id='2'>B</p>
          <p id='3'>C</p>
          <p id='4'>D</p>
          <p id='5'>E</p>
        </body>
      </html>
    HTML_END
    
    first_p = "2"
    last_p = "4"
    
    doc.css("p[id=\"#{first_p}\"] ~ p[id=\"#{last_p}\"]").each { |node|
      div_node = nil
      node.parent.children.each { |sibling_node|
        if sibling_node.name == 'p' && sibling_node['id'] == first_p
          div_node = Nokogiri::XML::Node.new('div', doc)
          sibling_node.add_previous_sibling(div_node)
        end
        unless div_node.nil?
          sibling_node.remove
          div_node << sibling_node
        end
        if sibling_node.name == 'p' && sibling_node['id'] == last_p
          div_node = nil
        end
      }
    }
    
    puts doc
    

    【讨论】:

    • 这是不正确的。
      可能包含其他块级元素。
    • @zacm 我的错,我想的是 span 而不是 div
    • 嗯,这看起来很复杂。 Hpricot 提供了更改 HTML 代码 (wiki.github.com/why/hpricot/hpricot-altering) 的简单方法,所以我无法想象 Nokogiri 不会提供类似的东西……糟糕 Nokogiri 的文档不如 Hpricot 的好。 :(
    • @Javier,看看我对 DOM 做事方式的更新(比如 hpricot 的)......考虑到您要解决的具体问题,这并不简单(如果它支持更高级的 CSS3 选择器。 ..),但仍然
    • @Vlad:您希望支持哪些更高级的 CSS3 选择器?由于 Nokogiri 支持 CSS3,它的开发人员可能会对这样的功能请求非常感兴趣。
    【解决方案2】:

    这是我在项目中实施的有效解决方案(Vlad@SO & Whitelist@irc#rubyonrails:感谢您的帮助和启发。):

    require 'rubygems'
    require 'nokogiri'
    
    value = Nokogiri::HTML.parse(<<-HTML_END)
      "<html>
        <body>
          <p id='1'>A</p>
          <p id='2'>B</p>
          <h1>Bla</h1>
          <p id='3'>C</p>
          <p id='4'>D</p>
          <p id='5'>E</p>
        </body>
      </html>"
    HTML_END
    
    # The selected-array is given by the application.
    # It consists of a sorted array with all ids of 
    # <p> that need to be enclosed by the <div>
    selected = ["2","3","4"]
    
    # We want an elements, not nodesets!
    # .first returns Nokogiri::XML::Element instead of Nokogiri::XML::nodeset
    first_p = value.css("p##{selected.first}").first
    last_p = value.css("p##{selected.last}").first
    parent = value.css('body').first
    
    # build and set new div_node
    div_node = Nokogiri::XML::Node.new('div', value)
    div_node['class'] = 'XYZ'
    
    # add div_node before first_p
    first_p.add_previous_sibling(div_node)
    
    selected_node = false
    
    parent.children.each do |tag|
      # if it's the first_p
      selected_node = true if selected.include? tag['id']
      # if it's anything between the first_p and the last_p
      div_node.add_child(tag) if selected_node
      # if it's the last_p
      selected_node = false if selected.last == tag['id']
    end
    
    puts value.to_html
    

    【讨论】:

      猜你喜欢
      相关资源
      最近更新 更多
      热门标签