如何以编程方式猜测 CSV 文件是逗号还是分号分隔

Posted

技术标签:

【中文标题】如何以编程方式猜测 CSV 文件是逗号还是分号分隔【英文标题】:How to programmatically guess whether a CSV file is comma or semicolon delimited 【发布时间】:2011-02-16 21:30:52 【问题描述】:

在大多数情况下,CSV 文件是记录由逗号分隔的文本文件。但是,有时这些文件会以分号分隔。 (如果区域设置将小数分隔符设置为逗号,Excel 将在保存 CSV 时使用分号分隔符——这在欧洲很常见。参考:http://en.wikipedia.org/wiki/Comma-separated_values#Application_support)

我的问题是,让程序猜测是逗号还是分号分隔的最佳方法是什么?

例如像 1,1;1,1 这样的行可能不明确。它可以解释为逗号分隔: 1 1;1(一个字符串) 1

或分号分隔为 1,1 1,1

到目前为止,我最好的猜测是尝试使用 , 和 ; 来解析文件。定界符,然后选择与第一行(通常是标题行)具有相同长度的行数最多的解析。如果两者的行数相同,请选择具有更多列的行。这样做的主要缺点是额外的开销。

想法?

【问题讨论】:

计算文件中分隔符的数量。可以肯定的是,使用最多的分隔符就是实际的分隔符。 一项可能的额外检查是查看在分隔符上拆分是否会在每行产生相等数量的段。 您有关于文件内容的任何信息吗?如果是这样,您可能可以利用它来发挥自己的优势。如果不是,那真的没法说,最好的办法是询问用户。 我认为您的方法很好,但是您可以通过将比较限制在前 50 行左右来节省时间,如果可能的话,您应该包括一些“无法猜测”的阈值,例如两个定界符似乎都是合理的(例如,两个定界符都解析为一个多于 1 列的矩形表) 既然你猜不到总是正确的,你应该给用户一个覆盖的机会。 【参考方案1】:

假设您的 csv 中有以下内容:

title,url,date,copyright,hdurl,explanation,media_type,service_version

那么你可以使用python的内置CSV模块如下:

import csv
data = "title,url,date,copyright,hdurl,explanation,media_type,service_version"
sn = csv.Sniffer()
delimiter = sn.sniff(data).delimiter

打印名为delimiter 的变量将返回',',这是这里的分隔符。您可以使用一些不同的分隔符进行测试。

【讨论】:

【参考方案2】:

这是我的代码(文本没有验证)...也许它可以帮助或建立一个基础:-)!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using MoreLinq; // http://***.com/questions/15265588/how-to-find-item-with-max-value-using-linq

namespace HQ.Util.General.CSV

    public class CsvHelper
    
        public static Dictionary<LineSeparator, Func<string, string[]>>  DictionaryOfLineSeparatorAndItsFunc = new Dictionary<LineSeparator, Func<string, string[]>>();

        static CsvHelper()
        
            DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Unknown] = ParseLineNotSeparated;
            DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Tab] = ParseLineTabSeparated;
            DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Semicolon] = ParseLineSemicolonSeparated;
            DictionaryOfLineSeparatorAndItsFunc[LineSeparator.Comma] = ParseLineCommaSeparated;
        

        // ******************************************************************
        public enum LineSeparator
        
            Unknown = 0,
            Tab,
            Semicolon,
            Comma
        

        // ******************************************************************
        public static LineSeparator GuessCsvSeparator(string oneLine)
        
            List<Tuple<LineSeparator, int>> listOfLineSeparatorAndThereFirstLineSeparatedValueCount = new List<Tuple<LineSeparator, int>>();

            listOfLineSeparatorAndThereFirstLineSeparatedValueCount.Add(new Tuple<LineSeparator, int>(LineSeparator.Tab, CsvHelper.ParseLineTabSeparated(oneLine).Count()));
            listOfLineSeparatorAndThereFirstLineSeparatedValueCount.Add(new Tuple<LineSeparator, int>(LineSeparator.Semicolon, CsvHelper.ParseLineSemicolonSeparated(oneLine).Count()));
            listOfLineSeparatorAndThereFirstLineSeparatedValueCount.Add(new Tuple<LineSeparator, int>(LineSeparator.Comma, CsvHelper.ParseLineCommaSeparated(oneLine).Count()));

            Tuple<LineSeparator, int> bestBet = listOfLineSeparatorAndThereFirstLineSeparatedValueCount.MaxBy((n)=>n.Item2);

            if (bestBet != null && bestBet.Item2 > 1)
            
                return bestBet.Item1;
            

            return LineSeparator.Unknown;
        

        // ******************************************************************
        public static string[] ParseLineCommaSeparated(string line)
        
            // CSV line parsing : From "jgr4" in http://www.kimgentes.com/worshiptech-web-tools-page/2008/10/14/regex-pattern-for-parsing-csv-files-with-embedded-commas-dou.html
            var matches = Regex.Matches(line, @"\s?((?<x>(?=[,]+))|""(?<x>([^""]|"""")+)""|""(?<x>)""|(?<x>[^,]+)),?",
                                        RegexOptions.ExplicitCapture);

            string[] values = (from Match m in matches
                               select m.Groups["x"].Value.Trim().Replace("\"\"", "\"")).ToArray();

            return values;
        

        // ******************************************************************
        public static string[] ParseLineTabSeparated(string line)
        
            var matchesTab = Regex.Matches(line, @"\s?((?<x>(?=[\t]+))|""(?<x>([^""]|"""")+)""|""(?<x>)""|(?<x>[^\t]+))\t?",
                            RegexOptions.ExplicitCapture);

            string[] values = (from Match m in matchesTab
                                select m.Groups["x"].Value.Trim().Replace("\"\"", "\"")).ToArray();

            return values;
        

        // ******************************************************************
        public static string[] ParseLineSemicolonSeparated(string line)
        
            // CSV line parsing : From "jgr4" in http://www.kimgentes.com/worshiptech-web-tools-page/2008/10/14/regex-pattern-for-parsing-csv-files-with-embedded-commas-dou.html
            var matches = Regex.Matches(line, @"\s?((?<x>(?=[;]+))|""(?<x>([^""]|"""")+)""|""(?<x>)""|(?<x>[^;]+));?",
                                        RegexOptions.ExplicitCapture);

            string[] values = (from Match m in matches
                               select m.Groups["x"].Value.Trim().Replace("\"\"", "\"")).ToArray();

            return values;
        

        // ******************************************************************
        public static string[] ParseLineNotSeparated(string line)
        
            string [] lineValues = new string[1];
            lineValues[0] = line;
            return lineValues;
        

        // ******************************************************************
        public static List<string[]> ParseText(string text)
        
            string[] lines = text.Split(new string[]  "\r\n" , StringSplitOptions.None);
            return ParseString(lines);
        

        // ******************************************************************
        public static List<string[]> ParseString(string[] lines)
        
            List<string[]> result = new List<string[]>();

            LineSeparator lineSeparator = LineSeparator.Unknown;
            if (lines.Any())
            
                lineSeparator = GuessCsvSeparator(lines[0]);
            

            Func<string, string[]> funcParse = DictionaryOfLineSeparatorAndItsFunc[lineSeparator];

            foreach (string line in lines)
            
                if (string.IsNullOrWhiteSpace(line))
                
                    continue;
                

                result.Add(funcParse(line));
            

            return result;
        

        // ******************************************************************
    

【讨论】:

【参考方案3】:

你可以看第一行

FileReader fileReader = new FileReader(filePath);
    BufferedReader bufferedReader = new BufferedReader(fileReader);
    String s = bufferedReader.readLine();
    String substring = s.substring(s.indexOf(firstColumnName) + 3, s.indexOf(firstColumnName) + 4);
    bufferedReader.close();
    fileReader.close();
    substring.charAt(0);

然后你捕获这个值

substring.charAt(0)

取决于CSV是逗号还是分号可以使用最后一个值

【讨论】:

【参考方案4】:

如果每一行都应该有相同的列数,我相信 Excel 就是这种情况,那么,使用逗号和分号,计算第 N 行和第 N+1 行的列数。无论哪种方法(逗号或分号)产生不同的答案都是错误的(不是文件的格式)。您可以从头开始,直到其中一个被证明是不正确的。你不需要标题行或任何东西。您不必阅读比必要更多的文件,而且它永远不会为您提供文件格式的错误答案,它可能会到最后但尚未得出结论。您所需要的只是让每一行都有相同数量的列属性来保存。

【讨论】:

嗯,谢谢!我认为每行具有相同列数的假设在某些情况下可能无效(我痛苦地发现当最后一列为空白时 Excel 有时不会遵循这一点),但是您对不处理标题行的见解因为特殊是有帮助的(避免那里的假设)。也许最好的办法是尝试使用这两种方法并选择具有最多行数的列数一致的方法。【参考方案5】:

根据您正在使用的内容,如果您要保证有一个标题行,那么您尝试两者的方法可能是最佳的整体做法。然后,一旦您确定发生了什么,如果您到达更下方没有所需列数的行,那么您就知道格式不正确。

通常我会将此视为用户在上传时指定的选项,而不是编程测试。

【讨论】:

以上是关于如何以编程方式猜测 CSV 文件是逗号还是分号分隔的主要内容,如果未能解决你的问题,请参考以下文章

pyparsing用分号而不是逗号解析csv文件

用 .csv 文件中的 VBA 仅在 3 列中用分号替换逗号

如何修改CSV文件的分隔符

如何修改excel转换csv的分隔符

使用 soffice 命令行将 xls 转换为分号分隔的 csv

iOS-解析读取CSV文件,解析excel文件