如何使用Nokogiri将多个格式相同的XML文件合并为CSV

时间:2016-04-08 18:34:57

标签: ruby xml nokogiri concat

我想将多个格式相同的XML文件解析为CSV文件。

我在谷歌,nokogiri.org上搜索过,但是我没有找到答案。

我在节点/元素结构方面有10个格式相同的XML文件,它们位于当前目录中。

将XML文件合并到一个XML文件后,我需要提取advisory节点的特定元素。我想将linktitlelocationos -> language -> namereference -> name数据输出到CSV文件。

我的代码只能解析单个XML文档,我希望它能够考虑到1:很多:

# Parse the XML file into a Nokogiri::XML::Document object
@doc = Nokogiri::XML(File.open("file.xml"))

# Gather the 5 specific XML elements out of the 'advisory' top-level node
data = @doc.search('advisory').map { |adv|
  [
    adv.at('link').content,
    adv.at('title').content,
    adv.at('location').content,
    adv.at('os > language > name').content,
    adv.at('reference > name').content
  ]
}

# Loop through each array element in the object and write out as CSV row
CSV.open('output_file.csv', 'wb') do |csv|
  # Explicitly set headers until you figure out how to get them programatically
  csv << ['Link', 'Title', 'Location', 'OS Name', 'Reference Name']
  data.each do |row|
    csv << row
  end
end

我尝试更改代码以支持多个XML文件并将它们导入Nokogiri :: XML :: Document对象:

xml_docs = []

Dir.glob("*.xml").each do |file|
  xml = Nokogiri::XML(File.new(file))
  xml_docs << Nokogiri::XML::Document.new(xml)
end

这成功创建了一个数组xml_docs,其中包含正确的对象,但我不知道如何将这六个对象转换为单个对象。

这是示例XML。所有XML文件都使用相同的节点/元素结构:

<advisories>
  <title> Not relevant </title>
  <customer> N/A </customer>
  <advisory id="12345">
    <link> https://www.google.com </link>
    <release_date>2016-04-07</release_date>
    <title> The Short Description Would Go Here </title>
    <location> Location Name Here </location>
    <os>
      <product>
        <id>98765</id>
        <name>Product Name</name>
      </product>
      <language>
        <id>123</id>
        <name>en</name>
      </language>
    </os>
    <reference>
      <id>00029</id>
      <name>Full</name>
      <area>Not Defined</area>
    </reference>
  </advisory>
  <advisory id="98765">
    <link> https://www.msn.com </link>
    <release_date>2016-04-08</release_date>
    <title> The Short Description Would Go Here </title>
    <location> Location Name Here </location>
    <os>
      <product>
        <id>12654</id>
        <name>Product Name</name>
      </product>
      <language>
        <id>126</id>
        <name>fr</name>
      </language>
    </os>
    <reference>
      <id>00052</id>
      <name>Partial</name>
      <area>Defined</area>
    </reference>
  </advisory>
</advisories>

代码利用了Nokogiri :: XML :: Document但是如果Nokogiri :: XML :: Builder能更好地为此工作,我非常愿意相应地调整我的代码。

1 个答案:

答案 0 :(得分:0)

我处理解析一个XML文件的第一部分,如下所示:

require 'nokogiri'

doc = Nokogiri::XML(<<EOT)
<advisories>
  <advisory id="12345">
    <link> https://www.google.com </link>
    <title> The Short Description Would Go Here </title>
    <location> Location Name Here </location>
    <os>
      <language>
        <name>en</name>
      </language>
    </os>
    <reference>
      <name>Full</name>
    </reference>
  </advisory>
  <advisory id="98765">
    <link> https://www.msn.com </link>
    <release_date>2016-04-08</release_date>
    <title> The Short Description Would Go Here </title>
    <location> Location Name Here </location>
    <os>
      <language>
        <name>fr</name>
      </language>
    </os>
    <reference>
      <name>Partial</name>
    </reference>
  </advisory>
</advisories>
EOT

注意:这已删除节点,因为它们对问题并不重要。因为它会分散注意力,请删除绒毛。

这是代码的核心:

doc.search('advisory').map{ |advisory|
  link = advisory.at('link').text
  title = advisory.at('title').text
  location = advisory.at('location').text
  os_language_name = advisory.at('os > language > name').text
  reference_name = advisory.at('reference > name').text

  {
    link: link,
    title: title,
    location: location,
    os_language_name: os_language_name,
    reference_name: reference_name
  }
}

这可能是干的,但写的是作为一个例子。

运行它会产生一个哈希数组,可以通过CSV轻松输出:

# => [
      {:link=>" https://www.google.com ", :title=>" The Short Description Would Go Here ", :location=>" Location Name Here ", :os_language_name=>"en", :reference_name=>"Full"}, 
      {:link=>" https://www.msn.com ", :title=>" The Short Description Would Go Here ", :location=>" Location Name Here ", :os_language_name=>"fr", :reference_name=>"Partial"}
     ]

一旦你完成了这项工作,然后将其放入循环的修改版本中以输出CSV并读取XML文件。这是未经测试的,但看起来是正确的:

CSV.open('output_file.csv', 'w', 
  headers: ['Link', 'Title', 'Location', 'OS Name', 'Reference Name'],
  write_headers: true
) do |csv|
  Dir.glob("*.xml").each do |file|
    xml = Nokogiri::XML(File.read(file))
    # parse a file and get the array of hashes
  end

  # pass the array of hashes to CSV for output
end

请注意,您使用的文件模式为'wb'。您很少需要使用CSV b,因为CSV应该是文本格式。如果您确定,您将遇到二进制数据,然后也使用'b',但这可能导致包含龙的路径。

另请注意,这是使用readread不具备可扩展性,这意味着它并不关心文件的大小,它会尝试将其读入内存,无论它是否真正适合。有很多理由可以避免这种情况,但最好的是它会让你的程序瘫痪。如果您的XML文件可能超过系统的可用内存,那么您将要使用Nokogiri支持的SAX解析器进行重写。怎么做是一个不同的问题。

  

它实际上是一个哈希数组的数组。我不知道我是如何结束的,但我很容易就能使用array.flatten

默想:

foo = []    # => []
foo << [{}] # => [[{}]]
foo.flatten # => [{}]

您可能想要这样做:

foo = []    # => []
foo += [{}] # => [{}]

任何时候我必须使用flatten我看看是否可以创建数组而不是某个数组的数组。并不是因为它们本身就很糟糕,因为有时候它们非常有用,但你真的想要一系列哈希,所以你知道出了什么问题,flatten是一种便宜的出路,但使用它也会花费更多的CPU时间。最好找出问题并修复它,最终得到更快/更高效的代码。 (有些人会说这是浪费精力或过早优化,但编写高效代码是一个非常好的特质和目标。)