31

最近,我收到了一位知名 SO 用户的通知,该用户TStringList存在拆分错误,这将导致它无法解析 CSV 数据。我没有被告知这些错误的性质,包括Quality Central在内的互联网搜索没有产生任何结果,所以我在问。什么是TStringList 拆分错误

注意,我对基于毫无根据的意见的答案不感兴趣。


我知道的:

不多......一个是,这些错误很少出现在测试数据中,但在现实世界中并不罕见。

另一个是,如前所述,它们阻止正确解析 CSV。考虑到很难用测试数据重现错误,我(可能)正在向那些尝试在生产代码中使用字符串列表作为 CSV 解析器的人寻求帮助。

无关问题:

我获得了有关“Delphi-XE”标记问题的信息,因此由于“空格字符被视为分隔符” 功能而导致解析失败不适用。因为 Delphi 2006 引入的StrictDelimiter属性解决了这个问题。我自己正在使用 Delphi 2007。

另外由于字符串列表只能保存字符串,所以它只负责拆分字段。任何涉及由区域设置差异等引起的字段值(fi 日期,浮点数......)的转换困难都不在范围内。

基本规则:

CSV 没有标准规范。但是从各种规范中可以推断出一些基本的规律。

下面是 TStringList 如何处理这些的演示。规则和示例字符串来自Wikipedia。括号 ( [ ]) 叠加在字符串周围,以便能够通过测试代码查看前导或尾随空格(如果相关)。


空格被认为是字段的一部分,不应被忽略。

测试字符串:[1997, Ford , E350]
项目:[1997] [福特] [E350]


带有嵌入逗号的字段必须用双引号括起来。

测试字符串:[1997,Ford,E350,"Super, Luxury truck"]
项目:[1997] [福特] [E350] [超级,豪华卡车]


嵌入双引号字符的字段必须包含在双引号字符内,并且每个嵌入的双引号字符必须由一对双引号字符表示。

测试字符串:[1997,Ford,E350,"Super, ""luxurious""卡车"]
项目:[1997] [福特] [E350] [超级,“豪华”卡车]


带有嵌入换行符的字段必须用双引号括起来。

测试字符串:[1997,Ford,E350,"现在去买一个
他们走得很快”]
项目:[1997] [福特] [E350] [现在去买一个
他们走得很快]


在修剪前导或尾随空格的 CSV 实现中,具有此类空格的字段必须用双引号字符括起来。

测试字符串:[1997,福特,E350,“超豪华卡车”]
项目:[1997] [福特] [E350] [超豪华卡车]


字段可能总是包含在双引号字符中,无论是否必要。

测试字符串:["1997","Ford","E350"]
项目:[1997] [福特] [E350]



测试代码:

var
  SL: TStringList;
  rule: string;

  function GetItemsText: string;
  var
    i: Integer;
  begin
    for i := 0 to SL.Count - 1 do
      Result := Result + '[' + SL[i] + '] ';
  end;

  procedure Test(TestStr: string);
  begin
    SL.DelimitedText := TestStr;
    Writeln(rule + sLineBreak, 'Test string: [', TestStr + ']' + sLineBreak,
            'Items: ' + GetItemsText + sLineBreak);
  end;

begin
  SL := TStringList.Create;
  SL.Delimiter := ',';        // default, but ";" is used with some locales
  SL.QuoteChar := '"';        // default
  SL.StrictDelimiter := True; // required: strings are separated *only* by Delimiter

  rule := 'Spaces are considered part of a field and should not be ignored.';
  Test('1997, Ford , E350');

  rule := 'Fields with embedded commas must be enclosed within double-quote characters.';
  Test('1997,Ford,E350,"Super, luxurious truck"');

  rule := 'Fields with embedded double-quote characters must be enclosed within double-quote characters, and each of the embedded double-quote characters must be represented by a pair of double-quote characters.';
  Test('1997,Ford,E350,"Super, ""luxurious"" truck"');

  rule := 'Fields with embedded line breaks must be enclosed within double-quote characters.';
  Test('1997,Ford,E350,"Go get one now'#10#13'they are going fast"');

  rule := 'In CSV implementations that trim leading or trailing spaces, fields with such spaces must be enclosed within double-quote characters.';
  Test('1997,Ford,E350," Super luxurious truck "');

  rule := 'Fields may always be enclosed within double-quote characters, whether necessary or not.';
  Test('"1997","Ford","E350"');

  SL.Free;
end;



如果您已经阅读了所有内容,那么问题是:),什么是“TStringList 拆分错误”?

4

4 回答 4

13

不多......一个是,这些错误很少出现在测试数据中,但在现实世界中并不罕见。

只需要一个案例。测试数据不是随机数据,一个失败案例的用户应该提交数据,瞧,我们有一个测试案例。如果没有人可以提供测试数据,也许没有错误/失败?

CSV 没有标准规范。

那肯定有助于消除混乱。没有标准规范,你如何证明有问题?如果听凭自己的直觉,你可能会遇到各种各样的麻烦。这是我自己与政府发行的软件的愉快互动中的一些内容;我的应用程序应该以 CSV 格式导出数据,而政府应用程序应该导入它。以下是连续几年给我们带来很多麻烦的原因:

  • 你如何表示空数据?由于没有 CSV 标准,一年我友好的政府决定什么都可以,包括什么都没有(两个连续的逗号)。接下来他们决定只有连续的逗号是可以的,也就是说,Field,"",Field是无效的,应该是Field,,Field。向我的客户解释 gov 应用程序将验证规则从一周更改为下一周,这很有趣……
  • 您是否导出零整数数据?这可能是一个更大的滥用,但我的“政府应用程序”决定也验证这一点。曾经强制包含0,然后强制不包含0。也就是说,一次Field,0,Field是有效的,下一次Field,,Field是唯一有效的方式......

这是另一个(我的)直觉失败的测试用例:

1997,福特,E350,“超级豪华卡车”

请注意 and 之间的空格,"Super以及后面的非常幸运的逗号"Super如果引号字符紧跟在分隔符之后,则使用的解析器TStrings只会看到引号字符。该字符串被解析为:

[1997]
[ Ford]
[ E350]
[ "Super]
[ luxurious truck"]

直觉上我期望:

[1997]
[ Ford]
[ E350]
[Super luxurious truck]

但你猜怎么着,Excel 的做法与 Delphi 的做法相同……

结论

  • TStrings.CommaText相当不错并且实现得很好,至少我查看的 Delphi 2010 版本非常有效(避免多个字符串分配,使用 aPChar来“遍历”已解析的字符串)并且与 Excel 解析器的工作方式大致相同。
  • 在现实世界中,您需要与其他软件交换数据,这些软件使用其他库(或根本没有库)编写,人们可能误解了 CSV 的某些(缺失?)规则。你必须适应,这可能不是对或错的情况,而是“我的客户需要导入这个废话”的情况。如果发生这种情况,您将不得不编写自己的解析器,以适应您要处理的第 3 方应用程序的要求。在此之前,您可以安全地使用TStrings. 当它真的发生时,它可能不是TString错的!
于 2011-06-24T08:11:15.270 回答
4

我要冒昧地说最常见的失败案例是嵌入式换行符。我知道我所做的大多数 CSV 解析都忽略了这一点。我将使用 2 个 TStringLists,1 个用于我正在解析的文件,另一个用于当前行。所以我最终会得到类似于以下的代码:

procedure Foo;
var
    CSVFile, ALine: TStringList;
    s: string;

begin
    CSVFile := TStringList.Create;
    ALine := TStringList.Create;
    ALine.StrictDelimiter := True;
    CSVFile.LoadFromFile('C:\Path\To\File.csv');
    for s in CSVFile do begin
        ALine.CommaText := s;
        DoSomethingInteresting(ALine);
    end;
end;

当然,由于我没有注意确保每一行都是“完整的”,因此我可能会遇到输入在字段中包含带引号的换行符而我错过它的情况。

在我遇到问题的现实世界数据之前,我不会费心去修复它。:-P

于 2011-06-24T12:58:41.693 回答
0

另一个例子......这个 TStringList.CommaText 错误存在于 Delphi 2009 中。

procedure TForm1.Button1Click(Sender: TObject);
var
  list : TStringList;
begin
  list := TStringList.Create();
  try
    list.CommaText := '"a""';
    Assert(list.Count = 1);
    Assert(list[0] = 'a');
    Assert(list.CommaText = 'a'); // FAILS -- actual value is "a""
  finally
    FreeAndNil(list);
  end;
end;

TStringList.CommaText 设置器和相关方法会破坏保存a项目的字符串的内存(其空终止符被 a 覆盖")。

于 2013-08-06T21:12:25.020 回答
0

已经尝试使用TArray<String>拆分?

var
text: String;
arr: TArray<String>;
begin
text := '1997,Ford,E350';
arr := text.split([',']);

所以arr会是:

arr[0] = 1997;
arr[1] = Ford;
arr[2] = E350;
于 2018-08-11T02:00:47.970 回答