如何通过编程猜测CSV文件是逗号分隔还是分号分隔?

14
在大多数情况下,CSV文件是由逗号分隔的记录的文本文件。然而,有时这些文件可能会使用分号作为分隔符。(如果区域设置的小数分隔符设置为逗号,则Excel在保存CSV时会使用分号分隔符-这在欧洲很常见。 Ref: http://en.wikipedia.org/wiki/Comma-separated_values#Application_support
我的问题是,如何让程序猜测是使用逗号还是分号分隔?
例如,像1,1;1,1这样的一行可能是模棱两可的。它可以被解释为逗号分隔的: 1 1;1(一个字符串) 1
或者是分号分隔的 1,1 1,1
到目前为止,我最好的猜测是尝试使用逗号和分号分隔符解析文件,然后选择具有与第一行(通常是标题行)相同长度的最多行的解析。如果两者行数相同,则选择具有更多列的那个。这种方法的主要缺点是需要额外的开销。
您有什么想法?

3
统计文件中分隔符的数量。很有把握地,使用最多的分隔符就是实际的分隔符。 - Robert Harvey
6
一个可能的额外检查是查看在分隔符上进行分割是否会产生每行相等数量的片段。 - Alexander Torstling
你是否有关于文件内容的任何信息?如果有的话,你可以利用这些信息来帮助你。如果没有的话,就无法确定,最好的办法是询问用户。 - tloflin
1
我认为你的方法很好,但是你可以通过将比较限制在前50行左右来节省时间,如果可能的话,你应该包括一些“无法猜测”的阈值,例如如果两个分隔符都似乎是合理的(例如,两个分隔符解析为具有多列的矩形表),则应该考虑这种情况。 - Keith
2
由于您无法始终正确猜测,因此应该给用户一个覆盖的机会。 - Henry
显示剩余2条评论
5个回答

2
如果每行都应该有相同数量的列,我认为这在Excel中是成立的。那么,使用逗号和分号,计算出第N行和第N+1行的列数。无论是逗号还是分号,哪种方法产生了不同的答案就是错误的(而不是文件格式)。你可以从开头开始,只需要一直做到其中一个被证明不正确为止。你不需要标题行或其他任何东西。你不必读取比必要更多的文件,并且它永远不会给出文件格式的错误答案,它只可能到达结尾但尚未得出结论。所有你需要的是每行具有相同列数的属性。

嗯,谢谢!我认为假设每一行具有相同数量的列在某些情况下可能无效(我痛苦地发现当最后一列为空时,Excel有时不遵循此规则),但是您关于不将标题行视为特殊的见解很有帮助(避免了那里的假设)。也许最好的方法是尝试两种方法并选择具有最多行同意#列数的方法。 - Polemarch

1

你可以读取第一行

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文件是逗号还是分号,可以使用最后一个值。

1

根据您所处理的内容,如果您保证有一个标题行,那么尝试两种方法可能是最佳实践。一旦确定了情况,如果您到达下面没有所需列数的行,则知道格式不正确。

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


0
这是我的代码(文本没有验证)...也许它可以帮助或作为基础 :-)!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using MoreLinq; // https://dev59.com/B2Up5IYBdhLWcg3wdXaa

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;
        }

        // ******************************************************************
    }
}

0
假设你的 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的变量将返回',',这是分隔符。您可以使用一些不同的分隔符进行测试。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接