如何在节点集中进行搜索并从同一节点集中删除节点

时间:2016-09-30 07:50:48

标签: ruby xml nokogiri

我有以下xml:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <w:document mc:Ignorable="w14 w15 wp14" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">
        <w:body>
            <w:p w14:paraId="56037BEC" w14:textId="1188FA30" w:rsidR="001665B3" w:rsidRDefault="008B4AC6">
                <w:r>
                    <w:t xml:space="preserve">This is the story of a man who </w:t>
                </w:r>
                <w:ins w:author="Mitchell Gould" w:date="2016-09-28T09:15:00Z" w:id="0">
                    <w:r w:rsidR="003566BF">
                        <w:t>went</w:t>
                    </w:r>
                </w:ins>
                <w:del w:author="Mitchell Gould" w:date="2016-09-28T09:15:00Z" w:id="1">
                    <w:r w:rsidDel="003566BF">
                        <w:delText>goes</w:delText>
                    </w:r>
                </w:del>
...

我使用Nokogiri来解析xml,如下所示:

zip = Zip::File.open("test.docx")
doc = zip.find_entry("word/document.xml")
file = Nokogiri::XML.parse(doc.get_input_stream)

我有一个&#39;删除&#39;包含所有w:del元素的nodeset:

@deletions = file.xpath("//w:del")

我在此节点集内部搜索以查看元素是否存在,如下所示:

 my_node_set = @deletions.search("//w:del[@w:id='1']" && "//w:del/w:r[@w:rsidDel='003566BF']")

如果存在,我想从删除节点集中删除它。我这样做有以下几点:

deletions.delete(my_node_set.first)

这似乎有效,因为没有返回任何错误,它会在终端中显示已删除的节点集。

然而,当我检查我的@deletions节点集时,似乎项目仍在那里:

@deletions.search("//w:del[@w:id='1']" && "//w:del/w:r[@w:rsidDel='003566BF']")

我只是把我的头围绕着Nokogiri,所以我显然没有在我的@deletions节点集中正确搜索元素,而是搜索整个文档。

如何在@deletions节点集内搜索元素,然后从节点集中删除它?

1 个答案:

答案 0 :(得分:0)

考虑一下:

require 'nokogiri'

doc = Nokogiri::HTML(<<EOT)
<html>
  <body>
    <div id="foo"><p>foo</p></div>
    <div id="bar"><p>bar</p></div>
  </body>
</html>
EOT

divs包含div标记,它们是NodeSet:

divs = doc.css('div')
divs.class  # => Nokogiri::XML::NodeSet

并包含:

divs.to_html # => "<div id=\"foo\"><p>foo</p></div><div id=\"bar\"><p>bar</p></div>"

您可以使用at搜索NodeSet以查找第一个匹配项:

divs.at('#foo').to_html # => "<div id=\"foo\"><p>foo</p></div>"

您可以轻松删除它:

divs.at('#foo').remove

将其从文档中删除:

puts doc.to_html

# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html>
# >>   <body>
# >>     
# >>     <div id="bar"><p>bar</p></div>
# >>   </body>
# >> </html>

没有从NodeSet中删除它,但我们并不关心它,NodeSet只是一个指向文档本身节点的指针,用于给出要删除的内容列表。

如果您在删除某些节点后想要更新的NodeSet,请重新扫描文档并重建NodeSet:

divs = doc.css('div')
divs.to_html # => "<div id=\"bar\"><p>bar</p></div>"

如果您的目标是删除NodeSet中的所有节点,而不是搜索该列表,您只需使用:

divs.remove
puts doc.to_html

# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html>
# >>   <body>
# >>     
# >>     
# >>   </body>
# >> </html>

当我删除节点时,我不会收集一个中间NodeSet,而是使用以下内容进行动态操作:

doc = Nokogiri::HTML(<<EOT)
<html>
  <body>
    <div id="foo"><p>foo</p></div>
    <div id="bar"><p>bar</p></div>
  </body>
</html>
EOT

doc.at('div#bar p').remove

puts doc.to_html

# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html>
# >>   <body>
# >>     <div id="foo"><p>foo</p></div>
# >>     <div id="bar"></div>
# >>   </body>
# >> </html>

删除<p>中嵌入的#bar标记。放松选择器并从at更改为search我可以将它们整体删除:

doc.search('div p').remove

puts doc.to_html

# >> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
# >> <html>
# >>   <body>
# >>     <div id="foo"></div>
# >>     <div id="bar"></div>
# >>   </body>
# >> </html>

如果你坚持走过NodeSet,请记住它们就像数组一样,你可以这样对待它们。以下是使用reject跳过特定节点的示例:

doc = Nokogiri::HTML(<<EOT)
<html>
  <body>
    <div id="foo"><p>foo</p></div>
    <div id="bar"><p>bar</p></div>
  </body>
</html>
EOT

divs = doc.search('div').reject{ |d| d['id'] == 'foo' }
divs.map(&:to_html) # => ["<div id=\"bar\"><p>bar</p></div>"]

虽然您不会收到NodeSet,但您将获得一个数组:

divs.class # => Array

虽然你可以做到这一点,但你最好使用特定的选择器来减少集合,而不是依赖Ruby来selectreject元素。