Delphi TStringList分裂错误

Delphi TStringList分裂错误,delphi,csv,delphi-2007,tstringlist,Delphi,Csv,Delphi 2007,Tstringlist,最近,一位著名的SO用户通知我,TStringList存在分裂错误,这将导致它无法解析CSV数据。我还没有被告知这些错误的性质,在互联网上的搜索没有产生任何结果,所以我在问。什么是TStringList拆分错误 请注意,我对基于观点的毫无根据的答案不感兴趣 我所知道的: 不多。。。一个是,这些bug很少出现在测试数据中,但在现实世界中并不少见 另一个是,如上所述,它们阻止正确解析CSV。考虑到很难用测试数据重现bug,我(可能)正在寻求帮助,因为我曾经尝试在生产代码中使用字符串列表作为CSV解析

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

请注意,我对基于观点的毫无根据的答案不感兴趣


我所知道的:

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

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

无关问题:

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

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

基本规则:

CSV没有标准规范。但从中可以推断出一些基本规则

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


空格被视为字段的一部分,不应忽略



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

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

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

CSV没有标准规范

这肯定有助于消除混乱。如果没有标准规范,您如何证明某件事是错误的?如果这是一个人的直觉,你可能会陷入各种各样的麻烦。以下是我与政府发行的软件愉快互动的一些经验;我的应用程序应该以CSV格式导出数据,而政府应用程序应该导入数据。以下是让我们连续几年陷入麻烦的原因:

  • 如何表示空数据?由于没有CSV标准,有一年我的友好的政府决定什么都不做,包括什么都不做(连续两个逗号)。接下来,他们决定只有连续的逗号可以,也就是说,
    Field,“,Field
    无效,应该是
    Field,Field
    。向我的客户解释gov应用程序从一周到下一周改变了验证规则,这很有趣
  • 是否导出零整数数据?这可能是一个更大的滥用,但我的“政府应用程序”决定也验证这一点。有一段时间必须包含
    0
    ,然后必须不包含
    0
    。也就是说,在某个时间
    Field,0,Field
    是有效的,接下来
    Field,Field
    是唯一有效的方法
下面是另一个(我的)直觉失败的测试用例:

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

请注意
“Super
之间的空格,以及
“Super
后面的非常幸运的逗号。
TStrings
使用的解析器仅在引号字符紧跟分隔符之后时才能看到它。该字符串被解析为:

[1997]
[ Ford]
[ E350]
[ "Super]
[ luxurious truck"]
直觉上我希望:

[1997]
[ Ford]
[ E350]
[Super luxurious truck]
但是你猜怎么着,Excel和Delphi做的一样

结论
  • TStrings.CommaText
    相当好,实现得很好,至少我看到的Delphi 2010版本非常有效(避免了多个字符串分配,使用
    PChar
    来“遍历”解析的字符串),其工作原理与Excel的解析器大致相同
  • 在现实世界中,您需要与使用其他库(或根本没有库)编写的其他软件交换数据,在这些软件中,人们可能没有解释CSV的一些(缺失?)规则。你必须适应,这可能不是一个正确或错误的案例,而是一个“我的客户需要输入这些垃圾”的案例。如果发生这种情况,您必须编写自己的解析器,该解析器可以适应您将要处理的第三方应用程序的要求。在此之前,您可以安全地使用
    t字符串
    。当它真的发生时,可能不是TString的错
我要说的是,最常见的故障情况是嵌入式断线。我知道我做的大多数CSV解析都忽略了这一点。我将使用两个TStringLists,一个用于我正在解析的文件,另一个用于当前行。因此,我将以类似以下的代码结束:

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

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

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 setter和相关方法损坏了保存
a
项的字符串的内存(其空终止符字符被
覆盖)。

是否已尝试使用
TArray
拆分

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

+1,我以前听过,但从未验证过。为什么不问问告诉你这件事的著名SO用户? Test string: ["1997","Ford","E350"] Items: [1997] [Ford] [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;
[1997]
[ Ford]
[ E350]
[ "Super]
[ luxurious truck"]
[1997]
[ Ford]
[ E350]
[Super luxurious truck]
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;
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;
var
text: String;
arr: TArray<String>;
begin
text := '1997,Ford,E350';
arr := text.split([',']);
arr[0] = 1997;
arr[1] = Ford;
arr[2] = E350;