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