如何读取多个 XML 文件然后输出到具有相同 XML 文件名的多个 CSV 文件

How to read multiple XML files then output to multiple CSV files with the same XML filenames

我正在尝试解析多个 XML 文件,然后将它们输出到 CSV 文件中以列出正确的行和列。

我能够通过定义文件名一次处理一个文件来做到这一点,并将它们专门输出到定义的输出文件名中:

File.open('H:/output/xmloutput.csv','w')

我想写入多个文件并使它们的名称与 XML 文件名相同,而不用硬编码。我尝试了多种方法,但到目前为止都没有成功。

样本XML:

<?xml version="1.0" encoding="UTF-8"?>
<record:root>
<record:Dataload_Request>
    <record:name>Bob Chuck</record:name>
    <record:Address_Data>
        <record:Street_Address>123 Main St</record:Street_Address>
        <record:Postal_Code>12345</record:Postal_Code>
    </record:Address_Data>
    <record:Age>45</record:Age>
</record:Dataload_Request>
</record:root>

这是我试过的:

require 'nokogiri'
require 'set'

files = ''
input_folder = "H:/input"
output_folder = "H:/output"

if input_folder[input_folder.length-1,1] == '/'
   input_folder = input_folder[0,input_folder.length-1]
end

if output_folder[output_folder.length-1,1] != '/'
   output_folder = output_folder + '/'
end


files   = Dir[input_folder + '/*.xml'].sort_by{ |f| File.mtime(f)}
file    = File.read(input_folder + '/' + files)
doc     = Nokogiri::XML(file)
record  = {} # hashes
keys    = Set.new
records = [] # array
csv     = ""

doc.traverse do |node| 
  value = node.text.gsub(/\n +/, '')
    if node.name != "text" # skip these nodes: if class isnt text then skip
      if value.length > 0 # skip empty nodes
        key = node.name.gsub(/wd:/,'').to_sym
        if key == :Dataload_Request && !record.empty?
          records << record
          record = {}
        elsif key[/^root$|^document$/]
          # neglect these keys
        else
          key = node.name.gsub(/wd:/,'').to_sym
          # in case our value is html instead of text
          record[key] = Nokogiri::HTML.parse(value).text
          # add to our key set only if not already in the set
          keys << key
        end
      end
    end
  end

# build our csv
File.open('H:/output/.*csv', 'w') do |file|
  file.puts %Q{"#{keys.to_a.join('","')}"}
  records.each do |record|
    keys.each do |key|
      file.write %Q{"#{record[key]}",}
    end
    file.write "\n"
  end
  print ''
  print 'output files ready!'
  print ''
end

我遇到了 'read memory': no implicit conversion of Array into String (TypeError) 和其他错误。

您应该使用 Ruby 的 CSV class。此外,您不需要进行任何字符串匹配或正则表达式操作。使用 Nokogiri 定位元素。如果你知道 XML 中的节点名称是一致的,那应该很简单。我不确定这是否是您想要的输出,但这应该让您朝着正确的方向前进:

require 'nokogiri'
require 'csv'

def xml_to_csv(filename)
  xml_str = File.read(filename)
  xml_str.gsub!('record:','') # remove the record: namespace
  doc = Nokogiri::XML xml_str
  csv_filename = filename.gsub('.xml', '.csv')

  CSV.open(csv_filename, 'wb' ) do |row|
    row << ['name', 'street_address', 'postal_code', 'age']
    row << [
      doc.xpath('//name').text,
      doc.xpath('//Street_Address').text,
      doc.xpath('//Postal_Code').text,
      doc.xpath('//Age').text,
    ]
  end
end

# iterate over all xml files
Dir.glob('*.xml').each { |filename| xml_to_csv(filename) }

这是您的代码的快速 peer-review,类似于您在公司环境中获得的代码...

而不是写作:

input_folder = "H:/input"

input_folder[input_folder.length-1,1] == '/' # => false

考虑使用距字符串末尾的 -1 偏移量来访问字符:

input_folder[-1] # => "t"

这简化了您的逻辑,使其更具可读性,因为它没有不必要的视觉噪音:

input_folder[-1] == '/' # => false

请参阅字符串文档中的 [] and []=


这对我来说像是一个错误:

files   = Dir[input_folder + '/*.xml'].sort_by{ |f| File.mtime(f)}
file    = File.read(input_folder + '/' + files)

files 是一个文件名数组。 input_folder + '/' + files 正在将数组附加到字符串:

foo = ['1', '2'] # => ["1", "2"]
'/parent/' + foo # => 
# ~> -:9:in `+': no implicit conversion of Array into String (TypeError)
# ~>  from -:9:in `<main>'

你想如何处理它留给程序员作为练习。


doc.traverse do |node|

很恶心,因为它回避了 Nokogiri 使用访问器搜索特定标签的能力。我们很少需要逐个标记地遍历文档标记,通常只有在我们查看其结构和布局时才需要这样做。 traverse 速度较慢,所以将其用作最后的手段。


length 很好,但在检查字符串是否包含内容时不需要:

value = 'foo'
value.length > 0 # => true
value > '' # => true

value = ''
value.length > 0 # => false
value > '' # => false

来自 Java 的程序员喜欢使用访问器,但我喜欢偷懒,可能是因为我的 C 和 Perl 背景。


小心 subgsub,因为它们并不像您想象的那样。两者都需要一个正则表达式,但会在开始扫描之前采用一个字符串 escape

你传递的是一个正则表达式,在这种情况下没问题,但如果你不记得模式匹配的所有规则并且 gsub 扫描到最后,它可能会导致意想不到的问题字符串的:

foo = 'wd:barwd:' # => "wd:barwd:"
key = foo.gsub(/wd:/,'') # => "bar"

一般来说,我建议人们在使用正则表达式之前三思而后行。我见过相当高级的程序员在编写的逻辑中出现了一些漏洞,因为他们不知道引擎要做什么。它们非常强大,但需要通过外科手术使用,而不是作为通用解决方案。

同样的事情发生在字符串上,因为gsub不知道什么时候退出:

key = foo.gsub('wd:','') # => "bar"

因此,如果您只想更改第一个实例,请使用 sub:

key = foo.sub('wd:','') # => "barwd:"

不过我会做一些不同的事情。

foo = 'wd:bar'

我可以查看前三个字符是什么:

foo[0,3] # => "wd:"

或者我可以使用字符串索引将它们替换为其他内容:

foo[0,3] = '' 
foo # => "bar"

还有更多,但我认为现在已经足够了。