2013-04-29 150 views
0

我正在爲TinyMCE生成的HTML體創建類似wiki的差異功能。 diff-lcs是接受數組或對象的差異gem。大多數不同的任務在代碼上,只是比較行。 HTML文本體的差異更爲複雜。如果我插入文本的主體,我會逐字比較一個字符。雖然輸出是正確的,但它看起來像垃圾。將HTML字符串解析爲數組

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

Nokogiri被設計用來解析XML/HTML,所以'seq2'是一個字符串數組的起點,不適合用於Nokogiri。 TinyMCE的全部輸出是什麼?有沒有根元素? – 2013-04-29 16:48:10

+0

TinyMCE的輸出與seq1類似。 seq2並不重要,我只想用seq3這樣的格式進行操作。看起來我必須爲孩子解析Nokogiri對象,然後執行諸如seq2之類的操作。 – Archonic 2013-04-29 16:55:02

+0

您可以使用SAX界面並將每個標籤附加到數組,並將文本節點附加到單詞上。 – 2013-04-29 16:58:08

回答

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」方法元素屬性。

+0

任何想法,我可以添加到'ArraySplitParser'來保存標籤屬性? – Archonic 2013-05-01 19:52:55

+0

@Archonic提示:查看'start_element'方法的參數。解析器傳遞一個'attrs'變量。 – 2013-05-02 01:39:58

+0

我已經更新了答案。它正在解析attrs,但不會將它們寫入「@ array」。 – Archonic 2013-05-02 14:25:46

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 }\" " 

在你的代碼是不是紅寶石,因爲在所有的字符串沒有一個|運營商。您是否將|運算符添加到您的代碼中,而不顯示該定義?

我看到的一個問題是:

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 
+0

謝謝!這非常棒。我是否正確地思考@maerics回答sax解析器是否繞過了遞歸轉儲節點的需要? – Archonic 2013-05-01 15:43:13

+0

SAX就其本質而言,可以連續地看到每個標籤,這有助於以相同的方式輸出節點。因此,爲此,它避免了遞歸轉儲節點的需要。請參閱我對原始問題有關SAX與DOM的優缺點的評論。 – 2013-05-01 15:51:09