Удалить атрибут nokogiri на основе префикса пространства имен - PullRequest
0 голосов
/ 07 ноября 2018

Я использую 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". Я сталкивался с решениями 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: схема", "opf: роль," opf: файл-как "," opf: событие "), чтобы их можно было удалить, не касаясь любой из других атрибутов. Я даже пытался форсировать его, жестко кодируя атрибуты, которые, как я знал, существовали:

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 ]

0 голосов
/ 07 ноября 2018

Я считаю, что это намного проще:

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 в разных частях документа). Я бы пошел с первым (явный URI), если только я не видел XML своими глазами и не знал, где и как префикс определяется и используется.

tl; dr: префиксы ничего не значат, вместо этого обратитесь к связанному URI.

0 голосов
/ 07 ноября 2018

Узловые объекты имеют метод 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.

...