Недавно я был информирован уважаемым пользователем SO, что TStringList
имеет разбиение ошибок, которые могут привести к сбою в обработке данных CSV. Я не был проинформирован о характере этих ошибок, и поиск в Интернете, включая Quality Central, не дал никаких результатов, поэтому я спрашиваю. Что такое расщепления TStringList?
Пожалуйста, заметьте, меня не интересуют необоснованные ответы на основе мнения.
Что я знаю:
Не так много... Одно из них: эти ошибки появляются редко с тестовыми данными, но не так редко в реальном мире.
Другое, как заявлено, предотвращает правильный анализ CSV. Думая, что трудно воспроизвести ошибки с тестовыми данными, я (возможно) обратился за помощью от тех, кто попытался использовать строковый список в качестве анализатора CSV в производственном коде.
Неприемлемые проблемы:
Я получил информацию по теме "Delphi-XE" с тегами, поэтому неудачный синтаксический анализ из-за "пробельного символа, который рассматривается как разделитель" , не применяется, Потому что введение свойства StrictDelimiter
с Delphi 2006 разрешило это. Я сам использую Delphi 2007.
Кроме того, поскольку список строк может содержать только строки, он отвечает только за разделение полей. Любая сложность преобразования, включающая значения полей (f.i. date, числа с плавающей запятой..), возникающие из-за различий в языковых курсах и т.д., Не имеет значения.
Основные правила:
Нет стандартной спецификации для CSV. Но есть основные правила, вытекающие из различных спецификаций.
Ниже показано, как обрабатывает 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 расщепление ошибок?"
Не так много... Одно из них: эти ошибки появляются редко с тестовыми данными, но не так редко в реальном мире.
Все, что требуется, - это один случай. Тестовые данные не являются случайными данными, один пользователь с одним случаем отказа должен представить данные и voilà, у нас есть тестовый пример. Если никто не может предоставить тестовые данные, может быть, нет ошибок/сбоев?
Нет стандартной спецификации для CSV.
Эта уверенность помогает в замешательстве. Без стандартной спецификации, как вы доказываете, что что-то не так? Если это останется в одной собственной интуиции, вы можете столкнуться со всеми неприятностями. Здесь некоторые из моего собственного счастливого взаимодействия с правительством выпустили программное обеспечение; Мое приложение должно было экспортировать данные в формате CSV, и правительственное приложение должно было его импортировать. Вот что заставило нас много беспокоиться несколько лет подряд:
Field,"",Field
недействительно, должно быть Field,,Field
. Мне было очень весело объяснять моим клиентам, что приложение gov изменило правила проверки с одной недели на другую...0
, тогда было обязательно НЕ включать 0
. То есть, когда-то было Field,0,Field
, следующий Field,,Field
был единственным допустимым способом...И вот другой тестовый сценарий, в котором (моя) интуиция потерпела неудачу:
1997, Ford, 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
!Я собираюсь выйти на конечность и сказать, что наиболее распространенным случаем отказа является встроенная линия. Я знаю, что большинство анализов 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
Другой пример... эта ошибка 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
(его нулевой символ-ограничитель перезаписывается "
).