代码之家  ›  专栏  ›  技术社区  ›  lumos

基于命名空间前缀删除nokogiri属性

  •  2
  • lumos  · 技术社区  · 7 年前

    <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”前缀的属性。我在查找属性时遇到过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...>

    所以,我的问题是:
    有没有更清楚的方法

    1. 根据部分匹配和/或命名空间前缀查找属性,然后
    2. 从包含这些属性的节点中移除这些属性?
    2 回复  |  直到 7 年前
        1
  •  1
  •   Nick Veys    7 年前

    节点对象具有 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 .

        2
  •  1
  •   Amadan    7 年前

    doc.xpath('//@opf:*', { opf: "http://www.idpf.org/2007/opf" }).each(&:remove)
    

    // @ opf: 结合命名空间定义( { opf: "http://www.idpf.org/2007/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)。我会选择第一个(显式URI),除非我真的亲眼看到了XML,并且知道前缀在哪里以及如何定义和使用。