【问题标题】:XML to CSV rubyXML 到 CSV 红宝石
【发布时间】:2019-01-14 17:39:02
【问题描述】:

我有多个 XML 示例文件,我想将它们转换为 CSV,但是对于不同的 XML 文件会有多个不同的属性/节点,因此我不想硬编码不同的属性。我希望输出将列标题显示为第一行,然后每个节点/记录都像传统的列和行电子表格一样垂直显示。 这是一个示例 XML:

<?xml version="1.0" encoding="UTF-8"?>
<sd:root xmlns:wd="urn:com.sample/bsvc" sd:version="v31.0">
  <sd:Put_Job_Profile_Request sd:Add_Only="0">
    <sd:Job_Profile_Data>
      <sd:Job_Code>30000</sd:Job_Code>
      <sd:Effective_Date>1900-01-01</sd:Effective_Date>
      <sd:Job_Profile_Basic_Data>
        <sd:Job_Title>Chief Executive Officer</sd:Job_Title>
      </sd:Job_Profile_Basic_Data>
    </sd:Job_Profile_Data>
  </sd:Put_Job_Profile_Request>
  <sd:Put_Job_Profile_Request sd:Add_Only="0">
    <sd:Job_Profile_Data>
      <sd:Job_Code>30100</sd:Job_Code>
      <sd:Effective_Date>1900-01-01</sd:Effective_Date>
      <sd:Job_Profile_Basic_Data>
        <sd:Job_Title>Administrator Job Profile</sd:Job_Title>
      </sd:Job_Profile_Basic_Data>
    </sd:Job_Profile_Data>
  </sd:Put_Job_Profile_Request>
  <sd:Put_Job_Profile_Request sd:Add_Only="0">
    <sd:Job_Profile_Data>
      <sd:Job_Code>30200</sd:Job_Code>
      <sd:Effective_Date>1900-01-01</sd:Effective_Date>
      <sd:Job_Profile_Basic_Data>
        <sd:Inactive>0</sd:Inactive>
        <sd:Job_Title>Facilities &amp; Grounds Maintenance Attendant</sd:Job_Title>
        <sd:Include_Job_Code_in_Name>0</sd:Include_Job_Code_in_Name>
        <sd:Job_Profile_Private_Title>Maintenance Job Title</sd:Job_Profile_Private_Title>
        <sd:Job_Profile_Summary>Maintain cleanliness of the campus building throughout the day and fulfill special requests as needed.</sd:Job_Profile_Summary>
        <sd:Job_Description>&lt;p>Job Description&lt;b> rich text!&lt;/b>&lt;/p></sd:Job_Description>
        <sd:Additional_Job_Description>&lt;p>&lt;b>&lt;i>&lt;span class="emphasis-2">&lt;u>Additional&lt;/u>&lt;/span>&lt;/i>&lt;/b> Job Description&lt;b> rich text!&lt;/b>&lt;/p></sd:Additional_Job_Description>
        <sd:Work_Shift_Required>0</sd:Work_Shift_Required>
        <sd:Public_Job>1</sd:Public_Job>    
      </sd:Job_Profile_Basic_Data>
    </sd:Job_Profile_Data>
  </sd:Put_Job_Profile_Request>
  <sd:Put_Job_Profile_Request sd:Add_Only="0">
    <sd:Job_Profile_Data>
      <sd:Job_Code>30300</sd:Job_Code>
      <sd:Effective_Date>1900-01-01</sd:Effective_Date>
      <sd:Job_Profile_Basic_Data>
        <sd:Inactive>0</sd:Inactive>
        <sd:Job_Title>Sample_Job_Title</sd:Job_Title>
        <sd:Include_Job_Code_in_Name>0</sd:Include_Job_Code_in_Name>
        <sd:Job_Profile_Summary>Sample Job Profile Summary</sd:Job_Profile_Summary>
        <sd:Job_Description>Sample Job Description</sd:Job_Description>
        <sd:Additional_Job_Description>Sample Additional Job Description</sd:Additional_Job_Description>
        <sd:Work_Shift_Required>1</sd:Work_Shift_Required>
      </sd:Job_Profile_Basic_Data>
    </sd:Job_Profile_Data>
  </sd:Put_Job_Profile_Request>
</sd:root>

我使用但输出错误的代码:

require 'csv'
require 'nokogiri'

file = File.read('jobProfile.xml')
doc = Nokogiri::XML(file)
a = []

CSV.open('xmloutput.csv', 'wb') do |csv|
  csv << doc.at('.').search('*').map(&:name)
  doc.search('.').each do |x|
    csv << x.search('*').map(&:text)
  end
end

每组记录的列标题和数据水平设置。但我想迭代数据并保留一行列标题。如果不对每个属性进行硬编码,我不确定如何做到这一点:/ 请帮忙,因为我还是编程新手,我已经尝试了一周找到解决方案:(

screenshot showing the csv output

【问题讨论】:

  • 不清楚这里的问题是什么。与往常一样,尝试将这个大问题分解为更小的问题,以便解决方案更简单。
  • 我正在尝试将列标题解析到第一行,并像传统的 Excel 电子表格一样向下迭代每个节点/记录的数据。正如您从屏幕截图中看到的那样,每个记录:列和数据都是从代码中水平提取的,这不是我想要的。请指教。
  • CSV 以csv &lt;&lt; row 形式工作,因此您可能需要在将其填充到csv 之前逐步创建一行。您应该在添加之前完成该行,而不是随手添加。
  • 如果您澄清预期的输出会有所帮助 - 例如,您希望此 XML 示例使用什么 CSV?我猜你希望每个“Put_Job_Profile_Request”都是一行。但是,我不太确定预期的列标题是什么 - 特别是“Job_Profile_Basic_Data”中的那些

标签: ruby xml csv nokogiri export-to-csv


【解决方案1】:

您需要首先构建一个哈希数组并将键提取为标题,然后将值放在右列中,所有节点平展为列,忽略根键和记录键。

类似的东西

require 'nokogiri'
require 'set'

file    = File.read('jobProfile.xml')
doc     = Nokogiri::XML(file)
record  = {}
keys    = Set.new
records = []
csv     = ""

doc.traverse do |node| 
  value = node.text.gsub(/\n +/, '')
  if node.name
    if node.name != "text" # skip these nodes
      if value.length > 0 # skip empty nodes
        key = node.name.gsub(/sd:/,'').to_sym
        # if a new and not empty record, add to our records collection
        if key == :Job_Profile_Data && !record.empty?
          records << record
          record = {}
        elsif key[/Job_Profile|^root$|^document$/]
          # neglect these keys
        else
          key = node.name.gsub(/sd:/,'').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 allready in the set
          keys << key
        end
      end
    end
  end
end

# build our csv
File.open('./xmloutput.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
end

这在我们的 csv 文件中给出了以下内容

"Job_Code","Effective_Date","Job_Title","Inactive","Include_Job_Code_in_Name","Job_Description","Additional_Job_Description","Work_Shift_Required","Public_Job"
"30000","1900-01-01","Chief Executive Officer","","","","","","",
"30100","1900-01-01","Administrator Job Profile","","","","","","",
"30200","1900-01-01","Facilities & Grounds Maintenance Attendant","0","0","Job Description rich text!","Additional Job Description rich text!","0","1",
"30300","1900-01-01","Sample_Job_Title","0","0","Sample Job Description","Sample Additional Job Description","1","",

【讨论】:

  • 哦哇...非常感谢!而已!所以看起来没有必要使用 CSV 的库?我需要了解“设置”的其他功能。你太棒了!
  • 你总是需要决定你是否真的需要额外的依赖,在这种情况下似乎没有必要,尽管如果你需要更多的功能重写部分 CSV gem 也会很糟糕。如果您需要有关 Set 的更多信息,请参阅此处ruby-doc.org/stdlib-2.5.3/libdoc/set/rdoc/Set.html
猜你喜欢
  • 2014-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多