TStringList拆分错误

时间:2011-06-23 22:05:10

标签: delphi csv delphi-2007 tstringlist

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

注意,我对基于意见的答案没有兴趣。


我所知道的:

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

另一方面,如上所述,它们阻止正确解析CSV。认为很难用测试数据重现错误,我(可能)寻求帮助,他们尝试在生产代码中使用字符串列表作为CSV解析器。

不相关的问题:

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

此外,由于字符串列表只能包含字符串,因此它只负责拆分字段。涉及由区域差异等引起的字段值(f.i. date,浮点数..)的任何转换难度都不在范围内。

基本规则:

CSV没有标准规格。但是从various specifications推断出基本规则。

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


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

Test string: [1997, Ford , E350]
Items: [1997] [ Ford ] [ E350]


包含逗号的字段必须包含在双引号字符中。

Test string: [1997,Ford,E350,"Super, luxurious truck"]
Items: [1997] [Ford] [E350] [Super, luxurious truck]


带有嵌入式双引号字符的字段必须用双引号字符括起来,每个嵌入的双引号字符必须用一对双引号字符表示。

Test string: [1997,Ford,E350,"Super, ""luxurious"" truck"]
Items: [1997] [Ford] [E350] [Super, "luxurious" truck]


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

Test string: [1997,Ford,E350,"Go get one now
they are going fast"]
Items: [1997] [Ford] [E350] [Go get one now
they are going fast]


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

Test string: [1997,Ford,E350," Super luxurious truck "]
Items: [1997] [Ford] [E350] [ Super luxurious truck ]


字段可以随时包含在双引号字符中,无论是否必要。

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;


如果你已经读完了,问题是:),什么是“TStringList拆分错误?”

4 个答案:

答案 0 :(得分:13)

  

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

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

  

CSV没有标准规范。

那个确定有助于混乱。没有标准规范,你如何证明出错?如果这是出于自己的直觉,你可能会遇到各种各样的麻烦。这里有一些来自我自己与政府发行的软件的快乐互动;我的应用程序应该以CSV格式导出数据,政府应用程序应该导入它。这就是让我们连续几年陷入麻烦的很多的原因:

  • 您如何表示空数据?由于没有CSV标准,一年我友好的政府决定采取任何行动,包括一切(连续两个逗号)。接下来他们决定只使用 连续逗号,即Field,"",Field无效,应为Field,,Field。有很多乐趣向我的客户解释gov应用程序将验证规则从一周更改为下一周......
  • 您是否导出ZERO整数数据?这可能是一个更大的滥用,但我的“政府应用程序”决定也验证。同时必须包含0,然后强制不要包含0。也就是说,有一次Field,0,Field有效,下一个Field,,Field是唯一有效的方式......

这是另一个测试案例,其中(我的)直觉失败了:

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

请注意,"Super之间的空格,以及"Super后面的非常幸运的逗号。 TStrings使用的解析器只有立即跟在分隔符后才能看到引号char。该字符串被解析为:

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

我希望直觉:

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

但是猜猜看,Excel就像Delphi那样做了......

结论

  • TStrings.CommaText相当不错且实现得很好,至少我看过的Delphi 2010版本非常有效(避免了多个字符串分配,使用PChar“遍历”解析后的字符串)并且正常工作和Excel的解析器差不多。
  • 在现实世界中,您需要与其他软件交换数据,使用其他库(或根本没有库)编写,人们可能会错过解释CSV的某些(缺失?)规则。你必须适应,它可能不是一个对错的情况,而是一个“我的客户需要导入这个废话”的情况。如果发生这种情况,您将必须编写自己的解析器,该解析器适应您要处理的第三方应用程序的要求。在此之前,您可以安全地使用TStrings。当它确实发生时,它可能不是TString的错!

答案 1 :(得分: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

答案 2 :(得分:0)

另一个例子...... 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项的字符串的内存(其空终止符被"覆盖)。

答案 3 :(得分: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;