Как найти некоторые данные XML и заменить их новым значением, используя Nokogiri Ruby gem - PullRequest
0 голосов
/ 17 марта 2020

База ниже XML пример файла сотрудников. xml и использование Ruby драгоценного камня Nokogiri Я хочу открыть этот файл, изменить номер здания на 320 и номер комнаты на 99 для Сандры Дефо и сохранить изменения. Каков рекомендуемый способ сделать это.

<?xml version="1.0" encoding="utf-16"?>
<employees>
    <employee id="be129">
        <firstname>Jane</firstname>
        <lastname>Doe</lastname>
        <building>327</building>
        <room>19</room>
    </employee>
    <employee id="be130">
        <firstname>William</firstname>
        <lastname>Defoe</lastname>
        <building>326</building>
        <room>14a</room>
    </employee>
    <employee id="be132">
        <firstname>Sandra</firstname>
        <lastname>Defoe</lastname>
        <building>327</building>
        <room>22</room>
    </employee>
    <employee id="be133">
        <firstname>Steve</firstname>
        <lastname>Casey</lastname>
        <building>327</building>
        <room>24</room>
    </employee>
</employees>

Ответы [ 2 ]

2 голосов
/ 18 марта 2020

Я бы использовал это:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="utf-16"?>
<employees>
    <employee id="be130">
        <firstname>William</firstname>
        <lastname>Defoe</lastname>
        <building>326</building>
        <room>14a</room>
    </employee>
    <employee id="be132">
        <firstname>Sandra</firstname>
        <lastname>Defoe</lastname>
        <building>327</building>
        <room>22</room>
    </employee>
</employees>
EOT

first_name = 'Sandra'
last_name = 'Defoe'
node = doc.at("//employee[firstname/text()='%s' and lastname/text()='%s']" % [first_name, last_name])
node.at('building').content = '320'
node.at('room').content = '99'

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

doc.to_xml
# => "\uFEFF<?xml version=\"1.0\" encoding=\"utf-16\"?>\n" +
#    "<employees>\n" +
#    "    <employee id=\"be130\">\n" +
#    "        <firstname>William</firstname>\n" +
#    "        <lastname>Defoe</lastname>\n" +
#    "        <building>326</building>\n" +
#    "        <room>14a</room>\n" +
#    "    </employee>\n" +
#    "    <employee id=\"be132\">\n" +
#    "        <firstname>Sandra</firstname>\n" +
#    "        <lastname>Defoe</lastname>\n" +
#    "        <building>320</building>\n" +
#    "        <room>99</room>\n" +
#    "    </employee>\n" +
#    "</employees>\n"

Обычно я рекомендую использовать CSS селекторы, потому что они имеют тенденцию приводить к меньшему визуальному шуму, однако CSS не позволяет нам заглядывать в текст узлов, и работа с ним, хотя и возможно, приводит к еще большему шуму. XPath, с другой стороны, может быть очень шумным, но для такого рода задач он более удобен.

XPath очень хорошо задокументирован, и выяснить, что он делает, должно быть довольно легко.

Сторона Ruby использует строку формата :

"//employee[firstname/text()='%s' and lastname/text()='%s']" % [first_name, last_name])

, аналогичную

"%s %s" % [first_name, last_name] # => "Sandra Defoe"
"//employee[firstname/text()='%s' and lastname/text()='%s']" % [first_name, last_name] 
# => "//employee[firstname/text()='Sandra' and lastname/text()='Defoe']"

Просто для тщательности, вот что я бы сделал, если бы я хотел использовать CSS исключительно:

node = doc.search('employee').find { |node| 
  node.at('firstname').text == first_name && node.at('lastname').text == last_name
}

Хотя это уродливо, потому что search говорит Nokogiri извлечь все employee узлы из lib XML, тогда Ruby должен пройти через все, чтобы сказать Нокогири, чтобы он велел lib XML посмотреть на дочерние firstname и lastname узлы и вернуть их текст. Это медленно, особенно если есть много employee узлов, а тот, который вам нужен, находится внизу файла.

Селектор XPath говорит Nokogiri передать поиск в lib XML, которая его анализирует, находит узел employee с дочерними узлами, содержащими имя и фамилию, и возвращает только этот узел. Это намного быстрее.

Обратите внимание, что at('employee') эквивалентно search('employee').first.

   # File 'lib/nokogiri/xml/searchable.rb', line 70

   def at(*args)
     search(*args).first
   end

Наконец, опосредуйте разницу между NodeSet # text и Node # text , так как первое приведет к безумию.

1 голос
/ 17 марта 2020

Предположим, что ваш контент представляет собой строку:

xml=%q(
<?xml version="1.0" encoding="utf-16"?>
<employees>
    <employee id="be129">
        <firstname>Jane</firstname>
        <lastname>Doe</lastname>
        <building>327</building>
        <room>19</room>
    </employee>
    <employee id="be130">
        <firstname>William</firstname>
        <lastname>Defoe</lastname>
        <building>326</building>
        <room>14a</room>
    </employee>
    <employee id="be132">
        <firstname>Sandra</firstname>
        <lastname>Defoe</lastname>
        <building>327</building>
        <room>22</room>
    </employee>
    <employee id="be133">
        <firstname>Steve</firstname>
        <lastname>Casey</lastname>
        <building>327</building>
        <room>24</room>
    </employee>
</employees>)

doc = Nokogiri.parse(xml)

Это будет работать, но предполагается, что имя и фамилия являются уникальными, в противном случае будет изменено первое совпадение имени и фамилии.

target = doc.css('employee').find do |node|
  node.search('firstname').text == 'Sandra' &&
  node.search('lastname').text == 'Defoe'
end

target.at_css('building').content = '320'
target.at_css('room').content = '99'

doc # outputs the updated xml
=> <?xml version="1.0"?>
<?xml version="1.0" encoding="utf-16"?>
<employees>
    <employee id="be129">
        <firstname>Jane</firstname>
        <lastname>Doe</lastname>
        <building>327</building>
        <room>19</room>
    </employee>
    <employee id="be130">
        <firstname>William</firstname>
        <lastname>Defoe</lastname>
        <building>326</building>
        <room>14a</room>
    </employee>
    <employee id="be132">
        <firstname>Sandra</firstname>
        <lastname>Defoe</lastname>
        <building>320</building>
        <room>99</room>
    </employee>
    <employee id="be133">
        <firstname>Steve</firstname>
        <lastname>Casey</lastname>
        <building>327</building>
        <room>24</room>
    </employee>
</employees>
...