解析HTML字符串到数组



我正在为TinyMCE生成的HTML主体开发一个类似wiki的差异功能。diff-lcs是一个接受数组或对象的不同gem。大多数不同的任务都在代码上,只是比较行。HTML文本主体上的差异更为复杂。如果我只插入文本主体,就会得到一个字符一个字符的比较。虽然输出将是正确的,但它看起来像垃圾。

seq1 = "<p>Here is a paragraph. A sentence with <strong>bold text</strong>.</p><p>The second paragraph.</p>"
seq2 = seq1.gsub(/[.!?]/, '|').split('|')
=> ["<p>Here is a paragraph.", " A sentence with <strong>bold text</strong>.", "</p><p>The second paragraph.", "</p>"]

如果有人更改了第二段,则输出的差异涉及前一段的结束标记。我不能只使用strip_tags,因为我想在比较视图上保持格式化。理想的比较是基于完整的句子,将HTML分开。

seq2.NokogiriMagic
=> ["<p>", "Here is a paragraph.", " A sentence with ", "<strong>", "bold text", "</strong>", ".", "</p>", "<p>", "The second paragraph.", "</p>"]

我找到了很多整洁的Nokogiri方法,但是没有一个能做到以上这些。

下面是使用SAX解析器的方法:

require 'nokogiri'
html = "<p>Here is a paragraph. A sentence with <strong>bold text</strong>.</p><p>The second paragraph.</p>"
class ArraySplitParser < Nokogiri::XML::SAX::Document
  attr_reader :array
  def initialize; @array = []; end
  def start_element(name, attrs=[])
    tag = "<" + name
    attrs.each { |k,v| tag += " #{k}="#{v}"" }
    @array << tag + ">"
  end
  def end_element(name); @array << "</#{name}>"; end
  def characters(str); @array += str.gsub(/s/, '|').split('|'); end
end
parser = ArraySplitParser.new
Nokogiri::XML::SAX::Parser.new(parser).parse(html)
puts parser.array.inspect
# ["<p>", "Here ", "is ", "a ", "paragraph. ", "A ", "sentence ", "with ", "<strong>", "bold ", "text", "</strong>", ".", "</p>"]

注意,您必须将HTML包装在根元素中,以便XML解析器不会错过示例中的第二段。像这样的代码应该可以工作:

# ...
Nokogiri::XML::SAX::Parser.new(parser).parse('<x>' + html + '</x>')
# ...
puts parser.array[1..-2]
# ["<p>", "Here ", "is ", "a ", "paragraph. ", "A ", "sentence ", "with ", "<strong>", "bold ", "text", "</strong>", ".", "</p>", "<p>", "The ", "second ", "paragraph.", "</p>"]

[Edit]更新以演示如何在"start_element"方法中保留元素属性

您不是在用惯用的Ruby编写代码。我们不会在变量名中混合使用大小写,而且,在编程中,为了清晰起见,使用助记变量名是个好主意。重构你的代码,让它更像我写的那样:

tags = %w[p ol ul li h6 h5 h4 h3 h2 h1 em strong i b table thead tbody th tr td]
# Deconstruct HTML body 1
doc = Nokogiri::HTML.fragment(@versionOne.body)
nodes = doc.css(tags.join(', '))
# Reconstruct HTML body 1 into comparable array
output = []
nodes.each do |node|
  output << [
    "<#{ node.name }",
    node.attributes.map { |param| '%s="%s"' % [param.name, param.value] }.join(' '),
    '>'
  ].join
  output << node.children.to_s.gsub(/[s.!?]/, '||').split('|').flatten
  output << "</#{ node.name }>"
end
# Same deal for nokoOutput2
sdiff = Diff::LCS.sdiff(nokoOutput2.flatten, output.flatten)

:

tag | " #{ param.name }="#{ param.value }" "
代码中的

根本不是Ruby,因为String没有|操作符。您是否在代码中添加了|操作符而没有显示该定义?

我看到的一个问题是:

output << node.children.to_s.gsub(/[s.!?]/, '||').split('|').flatten

您正在查找的许多标签可能包含列表中的其他标签:

<html>
  <body>
    <table><tr><td>
      <table><tr><td>
        foo
      </td></tr></table>
    </td></tr></table>
  </body>
</html>

创建一个递归方法来处理:

node.attributes.map { |param| '%s="%s"' % [param.name, param.value] }.join(' '),

可能会提高你的输出。这是未经测试的,但大致的思路是:

def dump_node(node)
  output = [
    "<#{ node.name }",
    node.attributes.map { |param| '%s="%s"' % [param.name, param.value] }.join(' '),
    '>'
  ].join
  output += node.children.map{ |n| dump_node(n) }
  output << "</#{ node.name }>"
end

相关内容

  • 没有找到相关文章

最新更新