如何使用 Nokogiri 将多个类似格式的 XML 文件组合成 CSV
How to use Nokogiri to combine multiple like-formatted XML files into CSV
我想将多个类似格式的 XML 文件解析为一个 CSV 文件。
我在 Google、nokogiri.org 和 SO 上进行了搜索,但未能找到答案。
我有十个 XML 文件,它们在 node/element 结构方面具有相同的格式,位于当前目录中。
将 XML 文件组合成一个 XML 文件后,我需要提取 advisory
节点的特定元素。我想将 link
、title
、location
、os -> language -> name
和 reference -> name
数据输出到 CSV 文件。
我的代码只能解析单个 XML 文档,我希望它能考虑 1:many:
# 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 文件使用相同的 node/element 结构:
<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 对此效果更好,我非常愿意相应地调整我的代码。
我将处理解析一个 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
}
}
这可以是 DRY'd,但它是作为操作示例编写的。
运行 生成哈希数组,可以通过 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'
。你很少需要 b
和 CSV,因为 CSV 应该是一种文本格式。如果你确定你会遇到二进制数据,那么也使用'b'
,但这可能会导致一条包含龙的路径。
另请注意,这是使用 read
。 read
不可扩展,这意味着它不关心文件有多大,它会尝试将其读入内存,无论它是否适合。有很多理由可以避免这种情况,但最好的是它会让您的程序瘫痪。如果您的 XML 文件可能超过系统的可用空闲内存,那么您将需要使用 Nokogiri 支持的 SAX 解析器重写。如何做到这一点是另一个问题。
it was actually an Array of array of hashes. I'm not sure how I ended up there but I was easily able to use array.flatten
对此进行冥想:
foo = [] # => []
foo << [{}] # => [[{}]]
foo.flatten # => [{}]
您可能想这样做:
foo = [] # => []
foo += [{}] # => [{}]
每当我必须使用 flatten
时,我都会查看是否可以创建数组,而不是它是某物数组的数组。这并不是说它们天生就不好,因为有时它们非常有用,但你真的想要一个哈希数组,这样你就知道出了什么问题,flatten
是一个便宜的出路,但使用它也会花费更多 CPU次。最好找出问题并解决它并最终得到 faster/more 高效的代码。 (有些人会说这是浪费精力或过早优化,但编写高效代码是一个非常好的特征和目标。)
我想将多个类似格式的 XML 文件解析为一个 CSV 文件。
我在 Google、nokogiri.org 和 SO 上进行了搜索,但未能找到答案。
我有十个 XML 文件,它们在 node/element 结构方面具有相同的格式,位于当前目录中。
将 XML 文件组合成一个 XML 文件后,我需要提取 advisory
节点的特定元素。我想将 link
、title
、location
、os -> language -> name
和 reference -> name
数据输出到 CSV 文件。
我的代码只能解析单个 XML 文档,我希望它能考虑 1:many:
# 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 文件使用相同的 node/element 结构:
<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 对此效果更好,我非常愿意相应地调整我的代码。
我将处理解析一个 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
}
}
这可以是 DRY'd,但它是作为操作示例编写的。
运行 生成哈希数组,可以通过 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'
。你很少需要 b
和 CSV,因为 CSV 应该是一种文本格式。如果你确定你会遇到二进制数据,那么也使用'b'
,但这可能会导致一条包含龙的路径。
另请注意,这是使用 read
。 read
不可扩展,这意味着它不关心文件有多大,它会尝试将其读入内存,无论它是否适合。有很多理由可以避免这种情况,但最好的是它会让您的程序瘫痪。如果您的 XML 文件可能超过系统的可用空闲内存,那么您将需要使用 Nokogiri 支持的 SAX 解析器重写。如何做到这一点是另一个问题。
it was actually an Array of array of hashes. I'm not sure how I ended up there but I was easily able to use array.flatten
对此进行冥想:
foo = [] # => []
foo << [{}] # => [[{}]]
foo.flatten # => [{}]
您可能想这样做:
foo = [] # => []
foo += [{}] # => [{}]
每当我必须使用 flatten
时,我都会查看是否可以创建数组,而不是它是某物数组的数组。这并不是说它们天生就不好,因为有时它们非常有用,但你真的想要一个哈希数组,这样你就知道出了什么问题,flatten
是一个便宜的出路,但使用它也会花费更多 CPU次。最好找出问题并解决它并最终得到 faster/more 高效的代码。 (有些人会说这是浪费精力或过早优化,但编写高效代码是一个非常好的特征和目标。)