最近,我被一位声誉卓着的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拆分错误?”
答案 0 :(得分:13)
并不多......其中一个就是,这些错误很少出现在测试数据中,但在现实世界中却很少见。
只需一例。测试数据不是随机数据,一个用户有一个失败案例应该提交数据并且我们有一个测试用例。如果没有人能提供测试数据,也许没有错误/失败?
CSV没有标准规范。
那个确定有助于混乱。没有标准规范,你如何证明出错?如果这是出于自己的直觉,你可能会遇到各种各样的麻烦。这里有一些来自我自己与政府发行的软件的快乐互动;我的应用程序应该以CSV格式导出数据,政府应用程序应该导入它。这就是让我们连续几年陷入麻烦的很多的原因:
Field,"",Field
无效,应为Field,,Field
。有很多乐趣向我的客户解释gov应用程序将验证规则从一周更改为下一周...... 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的解析器差不多。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;