Ошибки расщепления TStringList

31

Недавно я был информирован уважаемым пользователем 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 расщепление ошибок?"

  • 1
    +1, я слышал это раньше, но никогда не подтверждал это.
  • 0
    Почему бы не спросить уважаемого пользователя SO, который сказал вам об этом? (Кто это вообще был?)
Показать ещё 11 комментариев
Теги:
csv
tstringlist
delphi-2007

3 ответа

13
Лучший ответ

Не так много... Одно из них: эти ошибки появляются редко с тестовыми данными, но не так редко в реальном мире.

Все, что требуется, - это один случай. Тестовые данные не являются случайными данными, один пользователь с одним случаем отказа должен представить данные и voilà, у нас есть тестовый пример. Если никто не может предоставить тестовые данные, может быть, нет ошибок/сбоев?

Нет стандартной спецификации для CSV.

Эта уверенность помогает в замешательстве. Без стандартной спецификации, как вы доказываете, что что-то не так? Если это останется в одной собственной интуиции, вы можете столкнуться со всеми неприятностями. Здесь некоторые из моего собственного счастливого взаимодействия с правительством выпустили программное обеспечение; Мое приложение должно было экспортировать данные в формате CSV, и правительственное приложение должно было его импортировать. Вот что заставило нас много беспокоиться несколько лет подряд:

  • Как вы представляете пустые данные? Поскольку нет стандарта CSV, один год мой дружеский гость решил, что все идет, в том числе ничего (две последовательные запятые). Затем они решили, что только последовательные запятые в порядке, то есть Field,"",Field недействительно, должно быть Field,,Field. Мне было очень весело объяснять моим клиентам, что приложение gov изменило правила проверки с одной недели на другую...
  • Вы экспортируете целые данные ZERO? Вероятно, это было более жестокое обращение, но мое "приложение 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 делает.
  • В реальном мире вам нужно будет обмениваться данными с другим программным обеспечением, написанным с использованием других библиотек (или вообще никаких библиотек), где люди могут пропускать некоторые из (отсутствующих?) правил CSV. Вам придется адаптироваться, и это, вероятно, не будет правильным или неправильным, но случай "моим клиентам нужно импортировать эту дерьмо". Если это произойдет, вам придется написать собственный синтаксический анализатор, который будет адаптирован к требованиям стороннего приложения, с которым вы имеете дело. Пока это не произойдет, вы можете безопасно использовать TStrings. И когда это произойдет, это может быть не ошибка TString!
  • 1
    Космин, если мы примем основные правила в качестве спецификации, я полагаю, ваш тестовый пример не соответствует спецификации. Поскольку пробелы не следует игнорировать (правило № 1), двойные кавычки встраиваются в поле, следовательно, они нарушают правило № 3 (оно должно быть экранировано двойными кавычками, а поле должно быть заключено в двойные кавычки). Тем не менее, ваша интуиция имеет смысл и демонстрирует потенциальную путаницу. За это и за всю другую полезную информацию, спасибо!
  • 0
    Я думаю, что точка зрения Космина заключается в том, что в реальном мире нет «базовых правил» - потому что нет точной спецификации CSV, на которую можно указать;) Очень хороший пост, Космин.
3

Я собираюсь выйти на конечность и сказать, что наиболее распространенным случаем отказа является встроенная линия. Я знаю, что большинство анализов 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

  • 1
    Парсер Delphi 2010 отлично справляется с линейными тормозами в цитируемом CSV. Вот консольное приложение PasteBin, которое показывает это в действии
  • 0
    @Cosmin: Читайте мой код более внимательно. Если вы читаете файл в список строк, а затем обрабатываете его построчно, вы не поймаете встроенный перевод строки.
Показать ещё 2 комментария
0

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

  • 0
    На мой взгляд, ваш пример не соответствует правилам, которым RTL придерживается. У вас есть встроенная цитата, но она не удваивается (как и должно быть), код может свободно выкладываться по своему усмотрению.
  • 0
    Правда, я подал недействительный CSV в RTL. Но цель RTL (основанная на рассмотрении источника TStringList) - обрабатывать любой ввод и возвращать только верный вывод. Думаю, моя «ошибка» нарушила это.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню