Как использовать Nokogiri для внесения множества изменений в файл XML - PullRequest
0 голосов
/ 20 марта 2020

Я использую Nokogiri, чтобы преобразовать довольно большой файл XML, более 80 тыс. Строк, в формат CSV.

Мне нужно массово отредактировать узел <ImageFile />, например,

www.mybaseurl.com + text of <ImageFile /> 

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

Я хочу использовать Ruby, чтобы проверить, является ли <AltImageFile1> пустым, а если нет, мне нужно создать новую строку прямо с тем же значением дескриптора, но со значением

<AltImageFile1> for <ImageFile />

вот так:

введите описание изображения здесь

Вот пример файла XML, с которым я работаю:


<Products>
  <Product>
    <Name>36-In. Homeowner Bent Single-Bit Axe Handle</Name>
    <Description>This single bit curved grip axe handle is made for 3 to 5 pound axes. A good quality replacement handle made of American hickory with a natural wax finish. Hardwood handles do not conduct electricity and American Hickory is known for its strength, elasticity and ability to absorb shock. These handles provide exceptional value and economy for homeowners and other occasional use applications. Each Link handle comes with the required wedges, rivets, or epoxy needed for proper application of the tool head.</Description>
    <ImageFile>100024.jpg</ImageFile>
    <AltImageFile1>103387-1.jpg</AltImageFile1>
    <ItemNumber>100024</ItemNumber>
    <ModelNumber>64707</ModelNumber>
  </Product>

  <Product>
    <Name>1-1/4-Inch Lavatory Pop Up Assembly</Name>
    <Description>Classic chrome finish with ABS plastic top &amp; body includes push rod, no overflow.</Description>
    <ImageFile>100024.jpg</ImageFile>
    <AltImageFile1>103429-1.jpg</AltImageFile1>
    <ItemNumber>100024</ItemNumber>
    <ModelNumber>64707</ModelNumber>
  </Product>

  <Product>
    <Name>30-Inch Belt-Drive Whole-House Attic Fan With Shutter</Name>
    <Description>The 30" belt drive whole house fan (5700 CFM) with automatic shutter helps cool living spaces up to 1900 square feet. It runs on high &amp; low and a 2 speed wall switch is included. The automatic shutter is white. It needs 1095 square inches of open exhaust vents in attic space, with a rough opening of 34-1/4" x 29". You do have to cut joist when installing fan, with the motor mounted on struts above housing. The fan will be quieter than direct drive models. There is a 10 year limited parts warranty, 5 year limited labor warranty.</Description>

    <ImageFile>100073.jpg</ImageFile>
    <AltImageFile1>
    <ItemNumber>100024</ItemNumber>
    <ModelNumber>64707</ModelNumber>
  </Product>
</Products>

Вот мой код. Как я могу улучшить это?

require 'csv'
require 'nokogiri'

xml = File.read('Desktop/roduct_catalog.xml')
doc = Nokogiri::XML(xml)

all_the_things = []

doc.xpath('//Products/Product').each do |file|
  handle = file.xpath("./ItemNumber").first.text 
  title          = file.xpath("./Name").first.text
  description       = file.xpath("./Description").first.text
  collection = file.xpath("./FLDeptName").first.text 
  image1 = file.xpath("./ImageFile").first.text 
  all_the_things << [ handle, title, description, collection, image1]
end


CSV.open('product_file_1.csv', 'wb' ) do |row|
  row << [ 'handle', 'title', 'description', 'collection', 'image1']
  all_the_things.each do |data|
    row << data
  end
end

Ответы [ 2 ]

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

Я бы начал с чего-то вроде этого:

require 'csv'
require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<Products>
  <Product>
    <Name>36-In. Homeowner Bent Single-Bit Axe Handle</Name>
    <Description>This single bit curved grip axe handle is made for 3 to 5 pound axes. A good quality replacement handle made of American hickory with a natural wax finish. Hardwood handles do not conduct electricity and American Hickory is known for its strength, elasticity and ability to absorb shock. These handles provide exceptional value and economy for homeowners and other occasional use applications. Each Link handle comes with the required wedges, rivets, or epoxy needed for proper application of the tool head.</Description>
    <ImageFile>100024.jpg</ImageFile>
    <AltImageFile1>103387-1.jpg</AltImageFile1>
    <ItemNumber>100024</ItemNumber>
    <ModelNumber>64707</ModelNumber>
  </Product>

  <Product>
    <Name>1-1/4-Inch Lavatory Pop Up Assembly</Name>
    <Description>Classic chrome finish with ABS plastic top &amp; body includes push rod, no overflow.</Description>
    <ImageFile>100024.jpg</ImageFile>
    <AltImageFile1>103429-1.jpg</AltImageFile1>
    <ItemNumber>100024</ItemNumber>
    <ModelNumber>64707</ModelNumber>
  </Product>

  <Product>
    <Name>30-Inch Belt-Drive Whole-House Attic Fan With Shutter</Name>
    <Description>The 30" belt drive whole house fan (5700 CFM) with automatic shutter helps cool living spaces up to 1900 square feet. It runs on high &amp; low and a 2 speed wall switch is included. The automatic shutter is white. It needs 1095 square inches of open exhaust vents in attic space, with a rough opening of 34-1/4" x 29". You do have to cut joist when installing fan, with the motor mounted on struts above housing. The fan will be quieter than direct drive models. There is a 10 year limited parts warranty, 5 year limited labor warranty.</Description>

    <ImageFile>100073.jpg</ImageFile>
    <AltImageFile1>
    <ItemNumber>100024</ItemNumber>
    <ModelNumber>64707</ModelNumber>
  </Product>
</Products>
EOT

Это логика c:

NODES_TO_COLUMNS = {
  'ItemNumber'  => 'handle',
  'Name'        => 'title',
  'Description' => 'description',
  # 'FLDeptName'  => 'collection',
  'ImageFile'   => 'image1'
}

all_things = doc.search('Product').map do |product|
  NODES_TO_COLUMNS.keys.map { |node|
    product.at(node).text
  }
end

CSV.open('/dev/stdout', 'wb') do |csv|
  csv << NODES_TO_COLUMNS.values
  all_things.each do |r|
    csv << r
  end
end

, которая при запуске приводит к:

handle,title,description,image1
100024,36-In. Homeowner Bent Single-Bit Axe Handle,"This single bit curved grip axe handle is made for 3 to 5 pound axes. A good quality replacement handle made of American hickory with a natural wax finish. Hardwood handles do not conduct electricity and American Hickory is known for its strength, elasticity and ability to absorb shock. These handles provide exceptional value and economy for homeowners and other occasional use applications. Each Link handle comes with the required wedges, rivets, or epoxy needed for proper application of the tool head.",100024.jpg
100024,1-1/4-Inch Lavatory Pop Up Assembly,"Classic chrome finish with ABS plastic top & body includes push rod, no overflow.",100024.jpg
100024,30-Inch Belt-Drive Whole-House Attic Fan With Shutter,"The 30"" belt drive whole house fan (5700 CFM) with automatic shutter helps cool living spaces up to 1900 square feet. It runs on high & low and a 2 speed wall switch is included. The automatic shutter is white. It needs 1095 square inches of open exhaust vents in attic space, with a rough opening of 34-1/4"" x 29"". You do have to cut joist when installing fan, with the motor mounted on struts above housing. The fan will be quieter than direct drive models. There is a 10 year limited parts warranty, 5 year limited labor warranty.",100073.jpg

Поскольку в XML отсутствует FLDeptName, который не должен быть , чтобы быть правильным вопросом для SO, я прокомментировал это. Как использовать это оставлено для вас.

Вы хотите изменить:

CSV.open('/dev/stdout', 'wb') do |csv|

на то, что вы хотите использовать для имени файла. '/dev/stdout' - это просто способ сохранить кодирование и направить вывод в STDOUT для его отображения.

В вашем коде вы используете такие вещи, как:

xpath("./ItemNumber").first.text

Не сделай это. Nokogiri предоставляет ярлык at, который эквивалентен xpath....first, но является более кратким. Кроме того, нет необходимости использовать xpath, поскольку методы Nokogiri search и at достаточно умны, чтобы почти каждый раз выяснять, что такое селектор XPath или CSS.

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

Ваше требование создать вторичный, в основном- Пустая строка, если AltImageFile1 не пуста, я бы не рекомендовал. Строка CSV считается отдельной, отдельной записью, и будет интерпретироваться как таковая каждым приложением, которое поддерживает CSV, который я видел, поэтому вы просите создать вторичную запись без полей нестандартного формата. Вместо этого это поле должно быть добавлено к той же строке, что и дополнительное поле. Этот лог c не сложен и оставлен на ваше усмотрение.

Документ IETF CSV указывает:

Каждая запись расположена на отдельная строка, разделенная разрывом строки (CRLF). Например:

   aaa,bbb,ccc CRLF
   zzz,yyy,xxx CRLF

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

Если вы перемещаете данные в DBM, создайте временную таблицу для импорта непосредственно из XML, выполните операторы базы данных, чтобы соответствующим образом управлять записями, а затем добавьте их в основную таблицу. Если вы импортируете данные в Excel, используйте отдельную таблицу, измените поля, затем скопируйте или объедините данные в обычную таблицу. Создание нестандартного представления данных кажется мне тупиком.

Альтернативой может быть использование файлов YAML, которые являются более гибкими и гораздо более надежными.

0 голосов
/ 20 марта 2020

Вот код, который вы можете попробовать. Я не вижу FLDeptName узла в XML, поэтому я прокомментировал строки, связанные с этим узлом.

require 'csv'
require 'nokogiri'

xml = File.read('roduct_catalog.xml')
doc = Nokogiri::XML(xml)

all_the_things = []

doc.xpath('//Products/Product').each do |file|
  handle = file.xpath("./ItemNumber").first.text
  title = file.xpath("./Name").first.text
  description = file.xpath("./Description").first.text
  # collection = file.xpath("./FLDeptName").first.text #<== commented because as ./FLDeptName node not present
  image1 = "www.mybaseurl.com/" + file.xpath("./ImageFile").first.text
  # all_the_things << [ handle, title, description, collection,  image1]#<== commented because as ./FLDeptName node not present
  all_the_things << [handle, title, description, image1]
end


CSV.open('product_file_1.csv', 'wb') do |row|
  # row << [ 'handle', 'title', 'description','collection' 'image1'] #<== commented because as ./FLDeptName node not present
  row << ['handle', 'title', 'description', 'image1']
  all_the_things.each do |data|
    row << data
  end
end

Вот вывод. enter image description here


Пример XML с двумя изображениями:

<?xml version="1.0"?>
<ProductCatalogImport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Products>
    <Product>
      <Name>36-In. Homeowner Bent Single-Bit Axe Handle</Name>
      <Description>This single bit curved grip axe handle is made for 3 to 5 pound axes. A good quality replacement
        handle made of American hickory with a natural wax finish. Hardwood handles do not conduct electricity and
        American Hickory is known for its strength, elasticity and ability to absorb shock. These handles provide
        exceptional value and economy for homeowners and other occasional use applications. Each Link handle comes with
        the required wedges, rivets, or epoxy needed for proper application of the tool head.
      </Description>
      <ImageFile>100024.jpg</ImageFile>
      <ImageFile2>100024-2.jpg</ImageFile2>
      <ItemNumber>100024</ItemNumber>
      <ModelNumber>64707</ModelNumber>
    </Product>

    <Product>
      <Name>1-1/4-Inch Lavatory Pop Up Assembly</Name>
      <Description>Classic chrome finish with ABS plastic top &amp; body includes push rod, no overflow.</Description>
      <ImageFile>100024.jpg</ImageFile>
      <ItemNumber>100024</ItemNumber>
      <ModelNumber>64707</ModelNumber>
    </Product>

    <Product>
      <Name>30-Inch Belt-Drive Whole-House Attic Fan With Shutter</Name>
      <Description>The 30" belt drive whole house fan (5700 CFM) with automatic shutter helps cool living spaces up to
        1900 square feet. It runs on high &amp; low and a 2 speed wall switch is included. The automatic shutter is
        white. It needs 1095 square inches of open exhaust vents in attic space, with a rough opening of 34-1/4" x 29".
        You do have to cut joist when installing fan, with the motor mounted on struts above housing. The fan will be
        quieter than direct drive models. There is a 10 year limited parts warranty, 5 year limited labor warranty.
      </Description>

      <ImageFile>100073.jpg</ImageFile>
      <ItemNumber>100024</ItemNumber>
      <ModelNumber>64707</ModelNumber>
    </Product>
  </Products>
  <ProductCatalogImport/>

Это код для записи содержимого в другую строку:

require 'csv'
require 'nokogiri'

xml = File.read('roduct_catalog.xml')
doc = Nokogiri::XML(xml)

all_the_things = []

doc.xpath('//Products/Product').each do |file|
  handle = file.xpath("./ItemNumber").first.text
  title = file.xpath("./Name").first.text
  description = file.xpath("./Description").first.text
  # collection = file.xpath("./FLDeptName").first.text #<== commented because as ./FLDeptName node not present
  image1 = "www.mybaseurl.com/" + file.xpath("./ImageFile").first.text
  if file.xpath("./ImageFile2").size() > 0
    image2 = "www.mybaseurl.com/" + file.xpath("./ImageFile2").first.text
  else
    image2 = ''
  end

  # all_the_things << [ handle, title, description, collection,  image1]#<== commented because as ./FLDeptName node not present
  all_the_things << [handle, title, description, image1, image2]
end


CSV.open('product_file_1.csv', 'wb') do |row|
  # row << [ 'handle', 'title', 'description','collection' 'image1'] #<== commented because as ./FLDeptName node not present
  row << ['handle', 'title', 'description', 'image1', 'image2']
  all_the_things.each do |data|
    if data[-1] != ''
      row << data[0...-1]
      row << [data[0], '', '', '', data[-1]]
    else
      row << data
    end

  end
end

Вот вывод:

enter image description here

...