Ошибки расщепления 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 элемент (его нулевой символ-терминатор перезаписывается ").

Другие вопросы по тегам