ruby 替换Nokogiri节点中的部分文本,同时保留内容中的标记

qlvxas9a  于 10个月前  发布在  Ruby
关注(0)|答案(2)|浏览(154)

我试图通过使用Nokogiri扫描节点的内容,然后执行gsub来替换一组文件中唯一字符串的示例。我保留了部分字符串,并将其转换为锚标记。然而,大多数节点的内容中有各种形式的标记,而不仅仅是简单的字符串。例如,假设我有一个这样的文件:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<html>
    <head>
        <title>Title</title>
        <link href="style.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
        <div>
            <p class="header">&lt;&lt;2&gt;&gt;Header</p>
            <p class="paragraph">
            <p class="text_style">Lorem ipsum blah blah blah. &lt;&lt;3&gt;&gt; Here is more content. <span class="style">Preserve this.</span> Blah blah extra text.</p>
        </div>
    </body>
</html>

字符串
整个文档中都有数字,被&lt;&lt;&gt;&gt;包围。我想把数字的值转换成这样的标记:<a id='[#]'/>,但我想保留同一节中其他元素的HTML标记,即<span class="style">Preserve this.</span>
以下是我尝试过的所有方法:

file = File.open("file.xhtml") {|f| Nokogiri::XML(f)}

file.xpath("//text()").each { |node|
    if node.text.match(/<<([^_]*)>>/)
        new_content = node.text.gsub(/<<([^_]*)>>/,"<a id=\"\\1\"/>")
        node.parent.inner_html = new_content
    end
}


gsub可以正常工作,但是因为它使用了.text方法,所以任何标记都被忽略并有效地清除。在这种情况下,<span class="style">Preserve this.</span>部分被完全删除。(仅供参考,我使用.parent方法,因为如果我只执行node.inner_html = new_content,我会得到这个错误:add_child_node': cannot reparent Nokogiri::XML::Element there (ArgumentError)。)
如果我这样做:

new_content = node.text.gsub(/<<([^_]*)>>/,"<a id=\"\\1\"/>")
    node.content = new_content


字符没有被正确转义:文件以&lt;a id="3"/&gt;而不是<a id="3"/>结束。
我尝试使用CSS方法,而不是像这样:

file.xpath("*").each { |node|
    if node.inner_html.match(/&lt;&lt;([^_]*)&gt;&gt;/)
        new_content = node.inner_html.gsub(/&lt;&lt;([^_]*)&gt;&gt;/,"<a id=\"\\1\"/>")
        node.inner_html = new_content
    end
}


gsub工作,标记被保留,被替换的标记被正确转义。但是<head><body>标记被删除,这导致无效文件:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <title>Title</title>
        <link href="style.css" rel="stylesheet" type="text/css"/>
        <div>
            <p class="header"><a id="2"/>Header</p>
            <p class="paragraph">
            </p><p class="text_style">Lorem ipsum blah blah blah. <a id="3"/> Here is more content. <span class="style">Preserve this.</span> Blah blah extra text. </p>    
    </div>
</html>


我怀疑这与我迭代所有节点(file.css("*"))有关,这也是冗余的,因为除了子节点之外,还扫描了父节点。
我已经搜索了网络,但找不到任何解决方案。我只是想能够交换出唯一的文本,同时保持标记,并使其正确编码。有什么非常明显的,我错过了这里?

kse8i1jr

kse8i1jr1#

这看起来工作得很好:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<html>
    <head>
        <title>Title</title>
        <link href="style.css" rel="stylesheet" type="text/css" />
    </head>
    <body>
        <div>
            <p class="header">&lt;&lt;2&gt;&gt;Header</p>
            <p class="paragraph">
            <p class="text_style">Lorem ipsum. &lt;&lt;3&gt;&gt; more content. <span class="style">Preserve this.</span> extra text.</p>
        </div>
    </body>
</html>
EOT

doc.search("//text()[contains(.,'<<')]").each do |node|
  node.replace(node.content.gsub(/<<(\d+)>>/, '<a id="[\1]" />'))
end

字符串
其结果是:

puts doc.to_html

# >> <html>
# >>     <head>
# >> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
# >>         <title>Title</title>
# >>         <link href="style.css" rel="stylesheet" type="text/css">
# >>     </head>
# >>     <body>
# >>         <div>
# >>             <p class="header"><a id="[2]"></a>Header</p>
# >>             <p class="paragraph">
# >>             <p class="text_style">Lorem ipsum. <a id="[3]"></a> more content. <span class="style">Preserve this.</span> extra text.</p>
# >>         </p>
# >>     </div>
# >> </body>
# >> </html>


Nokogiri正在添加

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">


行,可能是因为标记被定义为XML。
选择器"//text()[contains(.,'<<')]"只查找包含'<<'的文本节点。如果可能导致误报,您可能需要修改它以使其更具体。有关语法,请参阅“XPath: using regex in contains function“。
replace正在执行技巧;您试图修改Nokogiri::XML::Text节点以包含<a.../>,但它不能,<>必须被编码。将节点更改为Nokogiri::XML::Element,这是Nokogiri默认的<a id="[2]">,让它按照您的需要存储它。

guicsvcw

guicsvcw2#

在我的例子中,我有一个Nokogiri::XML::Element,我通过记录类发现:
puts content.class
为了在不改变HTML/XML结构的情况下更改文本,必须使用递归函数向下钻取到文档的叶子。

def replace_text(content)
    if content.children.any?
      content.children.each do |node|
        replace_text(node)
      end
    elsif content.text
      content.content = content.text.gsub(/REGEX/, "REPLACE_VALUE")
    end
  end

字符串
使用方法:

content = document.css('.myclass')

return replace_text(content)

相关问题