我该如何检测文本文件中使用的分隔符?

时间:2009-04-17 19:52:08

标签: c# asp.net csv text-parsing

我需要能够解析CSV和TSV文件。我不能依赖用户知道差异,所以我想避免要求用户选择类型。有没有一种简单的方法来检测正在使用哪个分隔符?

一种方法是读取每一行并计算制表符和逗号,并找出哪一行最常用。当然,数据可能包括逗号或标签,因此说起来容易做起来难。

编辑:这个项目的另一个有趣的方面是我在阅读时还需要检测文件的架构,因为它可能是众多文件中的一个。这意味着在我解析它之前我不会知道有多少个字段。

13 个答案:

答案 0 :(得分:15)

在Python中,csv模块中有一个Sniffer类,可用于猜测给定文件的分隔符和引号字符。它的策略是(引自csv.py的文档字符串):


[首先,查看]两个相同引号之间的文字 (可能的quotechar)之前和之后 由相同的字符(可能的分隔符)。 例如:

         ,'some text',

获胜最多的引用,与分隔符相同。 如果没有quotechar,则无法确定分隔符 这样。

在这种情况下,请尝试以下操作:

分隔符出现的次数相同 每一行。但是,由于数据格式错误,可能不会。我们不想要 一个全有或全无的方法,所以我们允许在这方面的小变化 号。

  1. 建立一个频率表 每一行都有一个字符。
  2. 建立一个频率表 频率(元频率?),例如 'x在10行中出现5次,6 1000行的次数,2的7次 行
  3. 使用元频率的模式 确定预期 该角色的频率
  4. 找出角色的频率 实际上达到了目标
  5. 最符合它的角色 目标是分隔符
  6. 出于性能原因,数据以块的形式进行评估,因此可以 尝试评估可能的最小部分数据,进行评估 必要时添加额外的块。


    我不打算在这里引用源代码 - 它位于每个Python安装的Lib目录中。

    请记住,CSV也可以使用分号而不是逗号作为分隔符(例如,在德语版本的Excel中,CSV是分号分隔的,因为逗号在德国用作小数分隔符...)

答案 1 :(得分:14)

您可以在预览窗口中显示结果 - 类似于Excel的方式。在这种情况下使用错误的分隔符时非常清楚。然后,您可以允许他们选择一系列分隔符并实时预览更新。

然后你可以简单地猜测一下开头的分隔符(例如,首先是逗号或制表符。)

答案 2 :(得分:4)

你知道应该每行有多少个字段吗?如果是这样,我会阅读文件的前几行并根据它进行检查。

根据我的经验,“普通”数据通常包含逗号,但很少包含制表符。这表明您应该检查前几行中的一致数量的选项卡,并将该​​选项作为首选猜测。当然,这取决于你所拥有的确切数据。

最终,很可能有一个对两种格式都完全有效的文件 - 所以你不能让它绝对万无一失。它必须是“尽力而为”的工作。

答案 3 :(得分:4)

我遇到了类似的需求,并认为我会分享我想出的东西。我还没有通过它运行大量数据,因此可能存在边缘情况。另外,请记住,此功能的目标不是100%确定分隔符,而是最好向用户显示。

/// <summary>
/// Analyze the given lines of text and try to determine the correct delimiter used. If multiple
/// candidate delimiters are found, the highest frequency delimiter will be returned.
/// </summary>
/// <example>
/// string discoveredDelimiter = DetectDelimiter(dataLines, new char[] { '\t', '|', ',', ':', ';' });
/// </example>
/// <param name="lines">Lines to inspect</param>
/// <param name="delimiters">Delimiters to search for</param>
/// <returns>The most probable delimiter by usage, or null if none found.</returns>
public string DetectDelimiter(IEnumerable<string> lines, IEnumerable<char> delimiters) {
  Dictionary<char, int> delimFrequency = new Dictionary<char, int>();

  // Setup our frequency tracker for given delimiters
  delimiters.ToList().ForEach(curDelim => 
    delimFrequency.Add(curDelim, 0)
  );

  // Get a total sum of all occurrences of each delimiter in the given lines
  delimFrequency.ToList().ForEach(curDelim => 
    delimFrequency[curDelim.Key] = lines.Sum(line => line.Count(p => p == curDelim.Key))
  );

  // Find delimiters that have a frequency evenly divisible by the number of lines
  // (correct & consistent usage) and order them by largest frequency
  var possibleDelimiters = delimFrequency
                    .Where(f => f.Value > 0 && f.Value % lines.Count() == 0)
                    .OrderByDescending(f => f.Value)
                    .ToList();

  // If more than one possible delimiter found, return the most used one
  if (possibleDelimiters.Any()) {
    return possibleDelimiters.First().Key.ToString();
  }
  else {
    return null;
  }   

}

答案 4 :(得分:3)

它在PHP中,但这看起来非常可靠:

$csv = 'something;something;something
someotherthing;someotherthing;someotherthing
';
$candidates = array(',', ';', "\t");
$csvlines = explode("\n", $csv);
foreach ($candidates as $candidatekey => $candidate) {
 $lastcnt = 0;
 foreach ($csvlines as $csvline) {
  if (strlen($csvline) <= 2) continue;
  $thiscnt = substr_count($csvline, $candidate);
  if (($thiscnt == 0) || ($thiscnt != $lastcnt) && ($lastcnt != 0)) {
   unset($candidates[$candidatekey]);
   break;
  }
  $lastcnt = $thiscnt;
 }
}
$delim = array_shift($candidates);
echo $delim;

它的作用如下: 对于每个指定的可能分隔符,它读取CSV中的每一行并检查每个分隔符出现的次数是否恒定。如果没有,候选分隔符将被删除,最终你应该最终得到一个分隔符。

答案 5 :(得分:2)

我想你建议的解决方案是最好的方法。在格式良好的CSV或TSV文件中,每行的逗号或制表符数应保持不变(完全没有变化)。对文件的每一行进行计数,并检查哪一行对于所有行都是常量。每行的两个分界符的计数似乎不太可能相同,但在这种不可思议的罕见情况下,您当然可以提示用户。

如果选项卡和逗号的数量都不是常量,则向用户显示一条消息,告诉他们文件格式错误,但程序认为它是(无论格式具有每行的分界线标准偏差最小)文件。

答案 6 :(得分:2)

只需阅读几行,计算逗号数量和标签数量并进行比较。如果有20个逗号且没有标签,则为CSV格式。如果有20个标签和2个逗号(可能在数据中),则它在TSV中。

答案 7 :(得分:1)

没有“有效”的方式。

答案 8 :(得分:1)

假设每行有固定数量的字段,并且值中的任何逗号或制表符都用引号(“)括起来,您应该能够按每行中每个字符的频率计算出来。如果字段不固定,这更难,如果引号不用于包含其他分隔字符,我怀疑它几乎是不可能的(并且取决于数据,特定于语言环境)。

答案 9 :(得分:1)

根据我的经验,数据很少包含制表符,因此一行制表符分隔字段(通常)相当明显。

但是,逗号更难 - 特别是如果你在非美国语言环境中阅读数据。如果您正在读取由国家/地区生成的文件,则数字数据可能包含大量逗号,因为浮点数通常会包含它们。

最后,唯一安全的方法通常是尝试,然后将其呈现给用户并允许他们进行调整,特别是如果您的数据将包含逗号和/或标签。

答案 10 :(得分:1)

我认为在普通文本中,标签很少见,除非是一行中的第一个字符 - 想想缩进的段落或源代码。我想如果你发现嵌入式标签(即那些不遵循逗号的标签),你可以假设标签被用作分隔符并且大部分时间都是正确的。这只是一种预感,未经任何研究验证。我当然会给用户提供覆盖自动计算模式的选项。

答案 11 :(得分:1)

假设您有一组标准的列,那么......

我会使用FileHelper(SourceForge上的开源项目)。 http://filehelpers.sourceforge.net/

定义两个读者模板,一个用于昏迷,一个用于标签。

如果第一个失败,请尝试第二个。

答案 12 :(得分:0)

您可以检查一行是使用一个分隔符还是其他分隔符:

while ((line = readFile.ReadLine()) != null)
{
    if (line.Split('\t').Length > line.Split(',').Length) // tab delimited or comma delimited?
        row = line.Split('\t');
    else
        row = line.Split(',');

    parsedData.Add(row);
}