我正在使用nokogiri解析XML文件。文件中的某些节点具有特定于名称空间的属性:
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:identifier id="iden" opf:scheme="ISBN">xxxx</dc:identifier>
<dc:creator opf:role="aut" opf:file-as="Name">xxxx</dc:creator>
<dc:date opf:event="publication">xxxx</dc:date>
<dc:publisher>xxxx</dc:publisher>
<meta name="cover" content="x"/>
</metadata>
我正在尝试删除所有带有“ opf”前缀的属性。我在基于部分匹配找到属性 value 时遇到了xpath解决方案,但是当它是属性名称本身的部分匹配时呢?我尝试了很多没有用的东西。我做了一件简单的事情,只是试图至少提取属性名称,但是如果这样做:
elements = @doc.at_xpath('//xmlns:metadata').children
elements.each { |el|
el.attributes.each { |attribute|
if attribute[1].namespace_scopes[1].prefix == "opf"
puts attribute[0]
end
}
}
我最终得到:
id
scheme
role
file-as
event
name
content
但是我只想要带有“ opf”前缀的文件(“ opf:scheme”,“ opf:role,“ opf:file-as”,“ opf:event”),这样就可以删除它们而无需碰触其他任何属性。我什至尝试通过对我知道的属性进行硬编码来强制使用它:
opf_attributes = ["opf:file-as","opf:scheme","opf:role","opf:event"]
elements.each { |el|
opf_attributes.each { |x|
el.remove_attribute(x) if el[x] != nil
}
}
这不是解决此问题的最明智的方法,但这仍然行不通。节点没有任何反应,并且属性保持原样。 (我不知道是否值得注意,但是如果我改用remove_attr(x)
方法,则会出现此错误:undefined method 'remove_attr' for #<Nokogiri::XML::Element:0x...>
所以我的问题是:
有更清晰的方法
答案 0 :(得分:1)
节点对象具有remove
方法,可将其从树中删除,因此您可以编写如下内容:
require 'nokogiri'
doc = Nokogiri::XML(DATA)
puts '--- Before'
puts doc.to_s
doc.traverse do |node|
next unless node.respond_to? :attributes
node.attributes.each do |key, val|
val.remove if val&.namespace&.prefix == 'opf'
end
end
puts
puts '--- After'
puts doc.to_s
__END__
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:identifier id="iden" opf:scheme="ISBN">xxxx</dc:identifier>
<dc:creator opf:role="aut" opf:file-as="Name">xxxx</dc:creator>
<dc:date opf:event="publication">xxxx</dc:date>
<dc:publisher>xxxx</dc:publisher>
<meta name="cover" content="x"/>
</metadata>
并查看以下输出:
➜ ~ ruby test.rb
--- Before
<?xml version="1.0"?>
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:identifier id="iden" opf:scheme="ISBN">xxxx</dc:identifier>
<dc:creator opf:role="aut" opf:file-as="Name">xxxx</dc:creator>
<dc:date opf:event="publication">xxxx</dc:date>
<dc:publisher>xxxx</dc:publisher>
<meta name="cover" content="x"/>
</metadata>
--- After
<?xml version="1.0"?>
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:identifier id="iden">xxxx</dc:identifier>
<dc:creator>xxxx</dc:creator>
<dc:date>xxxx</dc:date>
<dc:publisher>xxxx</dc:publisher>
<meta name="cover" content="x"/>
</metadata>
注意:如果您使用的Ruby版本不支持&.
,则需要处理可能为nil
的命名空间。
答案 1 :(得分:1)
我相信这要简单得多
doc.xpath('//@opf:*', { opf: "http://www.idpf.org/2007/opf" }).each(&:remove)
//
搜索任何后代节点,@
指示它必须是一个属性节点,opf:
与名称空间定义({ opf: "http://www.idpf.org/2007/opf" }
)一起说明其具有的名称空间属于,并且*
与任何名称匹配。
请注意,opf:
本身并不意味着什么。 "http://www.idpf.org/2007/opf"
可以,而opf
只是其范围的简写。 .xpath('//@foobar:*', { foobar: "http://www.idpf.org/2007/opf" })
对于您的情况同样适用。
由于您在根目录上有名称空间定义,并且在文档中没有更改,因此可以简化为
doc.xpath('//@opf:*', doc.namespaces).each(&:remove)
,但请注意,这通常并不安全(例如,可以在子节点上定义名称空间)。 doc.collect_namespaces
会更安全一些,但是即使那样您也不是完全安全的(例如,如果文档的不同部分中的两个不同的URI使用相同的前缀)。除非我亲眼看到XML,并且知道在哪里以及如何定义和使用前缀,否则我会选择第一个(显式URI)。
tl; dr:前缀无意义,请参考相关的URI。