我正在为TinyMCE生成的HTML主体开发类似wiki的差异功能。 diff-lcs
是一个接受数组或对象的差异gem。大多数差异任务都在代码上,只是比较行。 HTML ridden文本的主体差异更加复杂。如果我只是插入文本正文,我会逐字符比较。虽然输出是正确的,但它看起来像垃圾。
seq1 = "<p>Here is a paragraph. A sentence with <strong>bold text</strong>.</p><p>The second paragraph.</p>"
seq2 = seq1.gsub(/[.!?]/, '\0|').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方法,但我发现上述内容并没有。
答案 0 :(得分:3)
以下是使用SAX parser:
执行此操作的方法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/, '\0|').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>"]
[编辑] 更新以演示如何在“start_element”方法中保留元素属性。
答案 1 :(得分:2)
你不是用惯用的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.!?]/, '|\0|').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.!?]/, '|\0|').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