Вставка и удаление узлов и элементов XML с помощью Nokogiri

Я хочу извлечь части XML-файла и отметить, что я извлек какую-то часть из этого файла, например, «здесь что-то было извлечено».

Я пытаюсь сделать это с Nokogiri, но, похоже, на самом деле не задокументировано, как:

  1. удалить все дочерние элементы <Nokogiri::XML::Element>
  2. изменить inner_text этого полного элемента

Любые подсказки?


person Community    schedule 13.08.2009    source источник
comment
Учебники Нокогири по изменению документа HTML/XML охватывают это. Также node.unlink удалит его из DOM.   -  person the Tin Man    schedule 12.03.2011
comment
См. Как спросить. В нем отсутствует важная информация, такая как минимальный пример XML для ввода и ожидаемый результат, а также код, который был написан для решения проблемы.   -  person the Tin Man    schedule 08.10.2015


Ответы (4)


Nokogiri делает это довольно легко. Используя этот документ в качестве примера, следующий код найдет все теги vitamins, удалит их дети (и дети детей и т. д.), и изменить их внутренний текст, чтобы сказать «Дети удалены.»:

require 'nokogiri'

io = File.open('sample.xml', 'r')
doc = Nokogiri::XML(io)
io.close

doc.search('//vitamins').each do |node|
  node.children.remove
  node.content = 'Children removed.'
end

Данный узел food будет выглядеть не так:

<food>
    <name>Avocado Dip</name>
    <mfr>Sunnydale</mfr>
    <serving units="g">29</serving>
    <calories total="110" fat="100"/>
    <total-fat>11</total-fat>
    <saturated-fat>3</saturated-fat>
    <cholesterol>5</cholesterol>
    <sodium>210</sodium>
    <carb>2</carb>
    <fiber>0</fiber>
    <protein>1</protein>
    <vitamins>
        <a>0</a>
        <c>0</c>
    </vitamins>
    <minerals>
        <ca>0</ca>
        <fe>0</fe>
    </minerals>
</food>

к этому:

<food>
    <name>Avocado Dip</name>
    <mfr>Sunnydale</mfr>
    <serving units="g">29</serving>
    <calories total="110" fat="100"/>
    <total-fat>11</total-fat>
    <saturated-fat>3</saturated-fat>
    <cholesterol>5</cholesterol>
    <sodium>210</sodium>
    <carb>2</carb>
    <fiber>0</fiber>
    <protein>1</protein>
    <vitamins>Children removed.</vitamins>
    <minerals>
        <ca>0</ca>
        <fe>0</fe>
    </minerals>
</food>
person Pesto    schedule 14.08.2009

Предыдущий пример Nokogiri направил меня в правильном направлении, но использование doc.search оставило искаженное //vitamins, поэтому я использовал CSS:

require "rubygems"
require "nokogiri"

f = File.open("food.xml")
doc = Nokogiri::XML(f)

doc.css("food vitamins").each do |node|
  puts "\r\n[debug] Before: vitamins= \r\n#{node}"
  node.children.remove
  node.content = "Children removed"
  puts "\r\n[debug] After: vitamins=\r\n#{node}"
end
f.close

Что приводит к:

debug] Before: vitamins= 
<vitamins>
        <a>0</a>
        <c>0</c>
    </vitamins>

[debug] After: vitamins=
<vitamins>Children removed</vitamins>
person eking    schedule 20.01.2010

Вы можете сделать это следующим образом:

doc=Nokogiri::XML(your_document)
note=doc.search("note") # find all tags with the node_name "note"
note.remove

Хотя это удалит все дочерние элементы в теге <note>, я не уверен, как «изменить внутренний_текст» всех элементов заметки. Я думаю, что inner_text неприменим для Nokogiri::XML::Element.

person Community    schedule 14.08.2009

Вот что я бы сделал:

Сначала разберите XML:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<?xml version="1.0"?>
<?xml-stylesheet type="text/css" href="nutrition.css"?>
<nutrition>

  <daily-values>
    <total-fat units="g">65</total-fat>
    <saturated-fat units="g">20</saturated-fat>
    <cholesterol units="mg">300</cholesterol>
    <sodium units="mg">2400</sodium>
    <carb units="g">300</carb>
    <fiber units="g">25</fiber>
    <protein units="g">50</protein>
  </daily-values>

  <food>
    <name>Avocado Dip</name>
    <mfr>Sunnydale</mfr>
    <serving units="g">29</serving>
    <calories total="110" fat="100"/>
    <total-fat>11</total-fat>
    <saturated-fat>3</saturated-fat>
    <cholesterol>5</cholesterol>
    <sodium>210</sodium>
    <carb>2</carb>
    <fiber>0</fiber>
    <protein>1</protein>
    <vitamins>
      <a>0</a>
      <c>0</c>
    </vitamins>
    <minerals>
      <ca>0</ca>
      <fe>0</fe>
    </minerals>
  </food>

</nutrition>
EOT

Если я хочу удалить содержимое узла, я могу удалить его children или присвоить nil его содержимому:

doc.at('total-fat').to_xml # => "<total-fat units=\"g\">65</total-fat>"
doc.at('total-fat').children.remove
doc.at('total-fat').to_xml # => "<total-fat units=\"g\"/>"

or:

doc.at('saturated-fat').to_xml # => "<saturated-fat units=\"g\">20</saturated-fat>"
doc.at('saturated-fat').content = nil
doc.at('saturated-fat').to_xml # => "<saturated-fat units=\"g\"/>"

Если я хочу извлечь текст из узла для использования другим способом:

food = doc.at('food').text
# => "\n    Avocado Dip\n    Sunnydale\n    29\n    \n    11\n    3\n    5\n    210\n    2\n    0\n    1\n    \n      0\n      0\n    \n    \n      0\n      0\n    \n  "

or:

food = doc.at('food').children.map(&:text)
# => ["\n    ",
#     "Avocado Dip",
#     "\n    ",
#     "Sunnydale",
#     "\n    ",
#     "29",
#     "\n    ",
#     "",
#     "\n    ",
#     "11",
#     "\n    ",
#     "3",
#     "\n    ",
#     "5",
#     "\n    ",
#     "210",
#     "\n    ",
#     "2",
#     "\n    ",
#     "0",
#     "\n    ",
#     "1",
#     "\n    ",
#     "\n      0\n      0\n    ",
#     "\n    ",
#     "\n      0\n      0\n    ",
#     "\n  "]

или как-то еще вы хотите исказить текст.

И, если вы хотите отметить, что вы удалили текст:

doc.at('food').content = 'REMOVED'
doc.at('food').to_xml # => "<food>REMOVED</food>"

Вместо этого вы также можете использовать комментарий XML:

doc.at('food').children = '<!-- REMOVED -->'
doc.at('food').to_xml # => "<food>\n  <!-- REMOVED -->\n</food>"
person the Tin Man    schedule 08.10.2015