Парсим csv и tsv файлы в delphi

Парсинг csv на delphi
Читая иностранные блоги, наткнулся на пост, в котором поднимается проблема парсинга и отображения в TStringGrid tsv файлов (tab-separated values — значения, разделенные символом табуляции, англ.). В таких файлах элементы в строках разделены знаком табуляции (девятым символом). Также не хочется забывать про не менее популярный формат csv — comma-separated values — в нём элементы разделены запятой. Стоит сказать, что программы для работы с таблицами просто обязаны читать эти два формата.

[Матчасть]

Как оказалось, delphi нам уже всё подготовил. Мы будем использовать два объекта TStringList и, очевидно, TStringGrid.
У объекта TStringList есть свойство Delimiter (разделитель — англ.). А сам TStringList по сути своей — массив строк.
DelimitedText — string. Строка, присвоенная DelimitedText, разобьётся, согласно Delimiter, и у нас становится TStringList с элементами строки. Впрочем, тот же explode на php.
QuoteChar — это свойство нам сегодня не пригодится, но упомянуть стоит. Например, у нас есть строка «delimiter»;»delimitedtext»;»quotechar»;»tstringlist». Элементы в ней не только разделены точкой с запятой, но еще и заключены в кавычки. В этом случае Delimiter ставим как ;, а QuoteChar как «. И легким движением руки мы получаем список из ключевых слов данного поста :)
Хотел сначала написать, что функция explode на php так не может (ха-ха), а потом прочитал, что там есть готовая функция fgetcsv, Нагайченко Максим написал об этом в посте на своём блоге (на 10.06.2015 – ссылка не работает).

Готовим TStringGrid

Для будущей загрузки таблицы, в Object Inspector я изначально поставил ColCount=1, а FixedCols и FixedRows обратил в ноль.

Парсим csv

Допустим, у нас есть таблица c:\table.csv, которую нам предстоит загрузить в TStringGrid.
Наши действия: каждую строку построчно разбиваем на элементы с помощью вышеописанных свойств и вставляем в TStringGrid.
В примере я использовал второй TStringList для открытия файла. Для больших таблиц также неплохо будет использование стандартного построчного получения файла, перешедшего из паскаля (AssignFile, Reset, ReadLn), но сегодня акцент не на этом.

Код:

procedure TForm1.Button1Click(Sender: TObject);
var
  sdata, srow: TStrings;
  i:integer;
begin
  sdata:=TStringList.Create;
  srow:=TStringList.Create;
  srow.Delimiter:=',';
  sdata.LoadFromFile('c:\table.csv');
  StringGrid1.RowCount:=sdata.Count;
  for i:=0 to sdata.Count-1 do begin
    srow.DelimitedText:=sdata[i];
    if srow.Count>StringGrid1.ColCount then
      StringGrid1.ColCount:=srow.Count;
    StringGrid1.Rows[i].Assign(srow);
  end;
  srow.Free;
  sdata.Free;
end;

Небольшие пояснения: в sdata загружаем файл, строки из sdata по очереди парсим в srow, который каждый раз присваиваем следующей строке TStringGrid.

Про tsv и прочее

Для парсинга tsv свойству Delimiter нужно присвоить значение табуляции. Это девятый символ, #9, chr(9) — так можно записать. Согласно википедии, csv и tsv объединяет формат dsv — delimiter-separated values, собственно его мы сегодня и отпарсили.

(c) crystalbit, http://parsers.info
RSS поток — будь в курсе!

А вы знаете два основных типа проблем с ноутбуком? В одном случае ноутбук не включается, в другом – не загружается. Причин может быть – аккумулятор, проблемы с материнкой и прочими комплектующими. Ноутбук, в отличие от стационарного компьютера, требует больше инструментов и более аккуратный подход. Лучше обращаться к профильному специалисту.

12 ответов к «Парсим csv и tsv файлы в delphi»

  1. Ну во первых, TStringList не самый быстрый способ распарсить строку Во вторых readln тоже не самый быстрый способ считывания csv файлов.
    По вашему примеру алгоритм примерно таким будет:
    1. readln считывает из файла по байтно ища разделитель строк
    2. найденная строка передаётся в stringlist, который снова проверяет всю строку посимвольно и разделяет строку.
    3. Каждый элемент stringlist копируется в ячейку grid

    Тут однозначно можно провести небольшой апгрейд алгоритма, тобишь оптимизацию:
    1. Считываем например 1000 байт из файла в память.
    2. Посимвольно проверяем каждый байт ища указанный разделитель или разделитель строк.
    3. Если найден csv-разделитель, то копируем найденную часть строки в ячейку grid
    4. Если разделитель строк, то создаем новую строку в grid
    5. Таким образом считываем весь файл.

    Алгоритм конечно сложнее по реализации, но скорость парсинга dsv файлов вырастает на порядок (проверенно опытным путём).

    1. Соглашусь, TStringList — не самый оптимальный вариант, зато всё уже сделано за нас. А с небольшими таблицами скорость существенной роли не играет.
      Ваш алгоритм не столь сложен в реализации простых таблиц, немного придётся потрудиться в случае, когда каждый элемент заключен в кавычки (QuoteChar). В принципе, повод, чтобы написать модуль для работы с dsv на winapi :)

  2. Сам вначале долго программировал на 1Cv77. Как же я потом матерился в дельфи, когда искал аналог функции «ИзСтрокиСразделителем», а потом пришлось ручками писать свой аналог. Зато теперь любой разделитель в качестве параметра задается и ву-аля :)

  3. у тебя в коде примера ошибка.
    когда назначаешь разделитель «sdata.Delimiter:=’,’;»-это не для того StringList, правильно будет вот так «srow.Delimiter:=’,’;». Исправь пожалуста, не надо вводить людей в заблуждение)))))

  4. проблема такого разделения tsv данных в том, что delimiter разбивает как по символу которому ты ему указываешь так и по пробелу. т. е указываешь разделить по табуляции, а он, …, делит по табуляции и пробелу:(((((((((((((((((((((((((((((((((((((((((


  5. procedure TForm1.Button1Click(Sender: TObject);
    var
    sdata, srow: TStrings;
    i:integer;
    begin
    sdata:=TStringList.Create;
    srow:=TStringList.Create;
    srow.Delimiter:=',';
    srow.StrictDelimiter:='True;
    sdata.LoadFromFile('c:\table.csv');
    StringGrid1.RowCount:=sdata.Count;
    for i:=0 to sdata.Count-1 do begin
    srow.DelimitedText:=sdata[i];
    if srow.Count>StringGrid1.ColCount then
    StringGrid1.ColCount:=srow.Count;
    StringGrid1.Rows[i].Assign(srow);
    end;
    srow.Free;
    sdata.Free;
    end;

  6. 1 как модифицировать код если загружаемый файл csv в другой кодировке
    2 и какой командой можно загружать файл csv из Интернета

  7. Все это, конечно выглядит симпатично… Но только на первый взгляд.
    В целом этот подход не решает проблему. Да, большинство csv файлов распарсит.
    Но как только окажется, что в одном из полей есть chr(10) или chr(13)… туши свет.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *