【问题标题】:How to detect the column separator of a CSV file in Ruby?如何在 Ruby 中检测 CSV 文件的列分隔符?
【发布时间】:2020-05-19 10:27:24
【问题描述】:

我遇到了几个问题,我们的董事会成员上传的 CSV 文件由于格式不一致而无法正确解析:逗号分隔、分号分隔、制表符分隔...通常他们甚至不知道哪个已使用分隔符,因为 Excel / LibreOffice Calc 在导出为 CSV 时未指定它。

【问题讨论】:

    标签: ruby csv


    【解决方案1】:

    此函数假定 CSV 至少有 4 列。它在文件的第一行(= 通常是标题,不太可能有额外的逗号或奇怪的字符)中查找分隔符和非分隔符的交替,然后返回最常见的分隔符。

      def self.detect_separator(file)
        firstline = File.open(file, &:readline)
        if firstline
          separators = ",;\t|#"
          non_sep = "[^" + separators + "]+"
          sep = "([" + separators + "])"
          reg = Regexp.new(non_sep + sep + non_sep + sep + non_sep + sep + non_sep + sep)
          m = firstline.match(reg)
          if m 
            four_separators = m[1..-1].join('') # this line should have four separators but may have less if the data is less conclusive
            detected_separator = separators.split('').map {|x| [four_separators.count(x),x]}.max
            return detected_separator[1] if detected_separator
          end
        end
        nil
      end
    

    【讨论】:

    • 一些注意事项:1) "[^" + separators + "]+" 通常写成 "[^#{separators}]+" (ruby 倾向于插值而不是串联) 2) 鉴于这是一个 Regexp,你可以直接在里面插值non_sep = /[^#{separators}]+/ 3) | 在正则表达式中有意义,所以你可能想逃避它。
    【解决方案2】:

    让我们从构建一个可能的 CSV 文件开始。

    arr = [
      ["abc", "d;ef", "hi;j", "k;l;mnp"],
      ["efg", "i;jk", "mn;p", "q;r;stu"],
      ["tuv", "w;xy", "zg;b", "c;d;e;f"]
    ]
    

    FName = 't.csv'
    

    require 'csv'
    

    我们将使用逗号分隔列1

    CSV.open(FName, mode='w', col_sep: ',') { |csv| arr.each { |s| csv << s } }
    

    让我们看看我们创建的文件。

    puts File.read(FName)
    # abc,d;ef,hi;j,k;l;mnp
    # efg,i;jk,mn;p,q;r;stu
    # tuv,w;xy,zg;b,c;d;e;f
    

    现在让我们读取这个文件,首先用逗号作为列分隔符,然后用分号作为分隔符。

    arr_comma = CSV.read(FName, col_sep: ',')
      #=> [["abc", "d;ef", "hi;j", "k;l;mnp"],
      #    ["efg", "i;jk", "mn;p", "q;r;stu"],
      #    ["tuv", "w;xy", "zg;b", "c;d;e;f"]]
    
    arr_semicolon = CSV.read(FName, col_sep: ';')
      #=> [["abc,d", "ef,hi", "j,k", "l", "mnp"],
      #    ["efg,i", "jk,mn", "p,q", "r", "stu"],
      #    ["tuv,w", "xy,zg", "b,c", "d", "e", "f"]]
    

    正如预期的那样,arr_comma 是一个由三个 4 元素数组组成的数组。相比之下,arr_semicolon 包含两个 5 元素数组和一个 6 元素数组。假设缺少的字段包含空字符串(例如,...ef;;gh;...),这告诉我们文件与用作列分隔符的逗号一致,但不是分号。

    我们可以编写一个小方法来检查每一行对于给定的列分隔符是否具有相同的列数。

    def sep_check(filename, sep)
      CSV.read(FName, col_sep: sep).map(&:size).uniq.size == 1
    end
    
    sep_check(FName, ',')
      #=> true
    sep_check(FName, ';')
      #=> false
    

    请注意,CSV 行可能包含带引号的字符串。如果带引号的字符串包含列分隔符,则它们不会被视为列分隔符。这是一个例子。

    arr = [
      ["abc", "d;ef", "h\"q,r\"i;j", "k;l;mnp"],
      ["efg", "i;jk", "mn;p",        "q;r;stu"],
      ["tuv", "w;xy", "zg;b",        "c;d;e;f"]
    ]
    

    CSV.open(FName, mode='w', col_sep: ',') { |csv| arr.each { |s| csv << s } }
    

    puts File.read(FName)
    # abc,d;ef,"h""q,r""i;j",k;l;mnp
    # efg,i;jk,mn;p,q;r;stu
    # tuv,w;xy,zg;b,c;d;e;f
    

    sep_check(FName, ',')
      #=> true
    sep_check(FName, ';')
      #=> CSV::MalformedCSVError (Illegal quoting in line 1.)
    

    这是另一个将分号排除在列分隔符之外的条件,而不是逗号。

    1.包括, col_sep: ',' 是可选的,因为col_sep 的默认值是逗号。

    【讨论】:

      猜你喜欢
      • 2013-01-19
      • 2021-12-17
      • 2014-07-08
      • 1970-01-01
      • 2015-10-15
      • 2018-02-18
      • 2020-01-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多