Как я могу читать и писать CSV способом, аналогичным NET FileHelpers? - PullRequest
9 голосов
/ 31 января 2011

Любой знает, как я могу импортировать / экспортировать файлы csv, txt аналогично NET FileHelpers, но с использованием Delphi, принимая во внимание пробелы и кавычки и обрабатывать традиционные правила выхода из CSV способом, аналогичным тому, как работает выход из CSV. Excel

исх. ссылка http://www.filehelpers.com/

Если ваш ответ, как правило, звучит так: «почему этот ленивый парень не пишет простой анализатор CSV», прочитайте 5-минутное чтение, и тогда вы поймете, почему анализ CSV не тривиален:

http://secretgeek.net/csv_trouble.asp

Ответы [ 7 ]

17 голосов
/ 01 февраля 2011

Я написал набор данных (объект, похожий на TTable) для проекта Jedi под названием TJvCsvDataSet, который следует всем правилам синтаксического анализа CSV аналогично правилам синтаксического анализа CSV, используемым в Excel, а также различным инструментам баз данных и отчетов, которые импортируют и экспортируют CSV.

Вы можете установить JVCL, перетащив TJvCsvDataSet в форму.

Он также содержит класс потока, который будет очень быстро загружать файл на диск и анализировать его построчно, используя правильные правила перехода, необходимые для файлов CSV, даже файлов, которые содержат коды возврата каретки / перевода строки, закодированные в поле.

Вы просто перетащите его на форму и установите свойство FieldDefs следующим образом:

CsvFieldDef = ABC:%, DEF: #, ГХИ: $, ....

Существуют специальные коды для целых чисел, чисел с плавающей запятой, даты-времени iso и других полей. Он даже позволяет сопоставить широкополосное поле с полем utf8 в файле CSV.

Существует редактор свойств designtime, который избавляет вас от необходимости объявлять определения полей CSV с использованием приведенного выше синтаксиса, вместо этого вы можете просто визуально выбрать типы столбцов.

Если вы не настроили CSV Field Def, он просто отображает все, что существует в файле, в поля строкового типа.

Jedi JVCL: http://jvcl.delphi -jedi.org /

Документы JvCsvDataSet:

http://help.delphi -jedi.org / unit.php? Id = 3107

http://help.delphi -jedi.org / item.php? Id = 174896

enter image description here

9 голосов
/ 31 января 2011

Это довольно просто, но TStringList имеет свойства Delimiter, DelimitedText и QuoteChar, которые решают некоторые из этих проблем.

Обновлено для добавления по комментариям: не поддавайтесь соблазну свойством CommaText, которое имеет некоторые неожиданные ограничения для обратной совместимости с архаичными версиями Delphi.

3 голосов
/ 13 августа 2013

Вот некоторый код, который я написал, который читает файлы CSV, он также обрабатывает возврат каретки внутри кавычек.

unit CSV;

interface
uses
  SysUtils, Generics.Collections, IOUtils;

type
  TParseState = (psRowStart, psFieldStart, psUnquotedFieldData,
    psQuotedFieldData, psQFBranch, psEndOfQuotedField, psQFEndSearch,
    psEndOfLine, psEndOfFile);

  TCSVField = class
  strict private
    FText: String;
  public
    constructor Create;
    destructor Destroy; override;
    property Text: string read FText write FText;
    procedure Clear;
  end;

  TCSVFieldList = class(TObjectList<TCSVField>)
  public
    function AddField(const AText: string): TCSVField;
    procedure ClearFields;
  end;

  TCSVRow = class
  strict private
    FFields: TCSVFieldList;
  public
    constructor Create;
    destructor Destroy; override;
    property Fields: TCSVFieldList read FFields;
  end;

  TCSVParser = class
  strict private
    FRow: TCSVRow;
    FContent: String;
    FCIdx: Integer;
    FParseState: TParseState;
    FEOF: Boolean;
    procedure ParseRow;
  public
    function First: Boolean;
    function EOF: Boolean;
    function Next: Boolean;
    procedure OpenFile(AFileName: String);
    procedure OpenText(const AText: string);
    property Row: TCSVRow read FRow;
    constructor Create;
    destructor Destroy; override;
  end;

implementation



{implementation of TCSVField}

procedure TCSVField.Clear;
begin
  FText:= '';
end;

constructor TCSVField.Create;
begin
  inherited Create;
end;

destructor TCSVField.Destroy;
begin
  inherited Destroy;
end;

{implementation of TCSVRow}

constructor TCSVRow.Create;
begin
  inherited Create;
  FFields:= TCSVFieldList.Create;
end;

destructor TCSVRow.Destroy;
begin
  FreeAndNil(FFields);
  inherited Destroy;
end;

{implementation of TCSVParser}

constructor TCSVParser.Create;
begin
  inherited Create;
  FRow:= TCSVRow.Create;
  FCIdx:= 1;
  FParseState:= psEndOfFile;
end;

destructor TCSVParser.Destroy;
begin
  FreeAndNil(FRow);
  inherited Destroy;
end;



function TCSVParser.EOF: Boolean;
begin
  Result:= FEOF;
end;

function TCSVParser.First: Boolean;
begin
  FEOF:= False;
  FCIdx:= 1;
  FParseState:= psRowStart;
  Result:= Next;
end;

function TCSVParser.Next: Boolean;
begin
  if not EOF then
    ParseRow;
  Result:= not EOF;
end;

procedure TCSVParser.OpenFile(AFileName: String);
begin
  OpenText(TFile.ReadAllText(AFileName));
end;

procedure TCSVParser.OpenText(const AText: string);
begin
  FContent:= AText;
  FRow.Fields.Clear;
  First;
end;

procedure TCSVParser.ParseRow;
var
  FieldIdx: Integer;

  procedure AddField(const AText: string);
  begin
    if FieldIdx > FRow.Fields.Count-1 then
      FRow.Fields.AddField(AText)
    else
      FRow.Fields[FieldIdx].Text:= AText;

    Inc(FieldIdx);
  end;

var
  FieldText: string;
  Curr: Char;
  LastIdx: Integer;
begin
  if FParseState = psEndOfFile then
  begin
    FEOF:= True;
    FRow.Fields.ClearFields;
    Exit;
  end;

  if not (FParseState in [psRowStart]) then
    raise Exception.Create('ParseRow requires ParseState = psRowState');

  FieldIdx:= 0;
  FRow.Fields.ClearFields;
  LastIdx:= Length(FContent);
  while True do
  begin
    case FParseState of
      psRowStart:
        begin
          if FCIdx > LastIdx then
          begin
            FEOF:= True;
            FParseState:= psEndOfFile;
          end
          else
          begin
            FParseState:= psFieldStart;
          end;
          Dec(FCIdx); // do not consume
        end;
      psFieldStart:
        begin
          FieldText:= '';
          if FContent[FCIdx] = '"' then
            FParseState:= psQuotedFieldData
          else
          begin
            FParseState:= psUnquotedFieldData;
            Dec(FCIdx); // do not consume
          end;
        end;
      psUnquotedFieldData:
        begin
          if FCIdx > LastIdx then
          begin
            AddField(FieldText);
            FParseState:= psEndOfFile;
          end
          else
          begin
            Curr:= FContent[FCIdx];
            case Curr of
              #13, #10:
                begin
                  AddField(FieldText);
                  FParseState:= psEndOfLine;
                end;
              ',':
                begin
                  AddField(FieldText);
                  FParseState:= psFieldStart;
                end;
            else
              FieldText:= FieldText + Curr;
            end;
          end;
        end;
      psQuotedFieldData:
        begin
          if FCIdx > LastIdx then
            raise Exception.Create('EOF in quoted Field.');

          Curr:= FContent[FCIdx];
          if Curr = '"' then
            FParseState:= psQFBranch
          else
            FieldText:= FieldText + Curr;
        end;
      psQFBranch:
        begin
          Curr:= FContent[FCIdx];
          if Curr = '"' then
          begin
            FieldText:= FieldText + Curr;
            FParseState:= psQuotedFieldData;
          end
          else
          begin
            AddField(FieldText);
            FParseState:= psEndOfQuotedField;
            Dec(FCIdx); // do not consume
          end;
        end;
      psEndOfQuotedField:
        begin
          if FCIdx > LastIdx then
            FParseState:= psEndOfFile
          else
          begin
            Curr:= FContent[FCIdx];
            if CharInSet(Curr, [#13, #10]) then
              FParseState:= psEndOfLine
            else
            begin
              FParseState:= psQFEndSearch;
              Dec(FCIdx); // do not consume
            end;
          end;
        end;
      psQFEndSearch:
        begin
          if FCIdx > LastIdx then
            FParseState:= psEndOfFile
          else
          begin
            Curr:= FContent[FCIdx];
            if CharInSet(Curr, [#13, #10]) then
              FParseState:= psEndOfLine
            else if Curr = ',' then
              FParseState:= psFieldStart;

            // skips white space or other until end
          end;
        end;
      psEndOfLine:
        begin
          if FCIdx > LastIdx then
            FParseState:= psEndOfFile
          else
          begin
            Curr:= FContent[FCIdx];
            if not CharInSet(Curr, [#13, #10]) then
            begin
              FParseState:= psRowStart;
              Break; // exit loop, we are done with this row
            end;
          end;
        end;
      psEndOfFile:
        begin
          Break;
        end;
    end;
    Inc(FCIdx);
  end;
end;


{ TCSVFieldList }

function TCSVFieldList.AddField(const AText: string): TCSVField;
begin
  Result:= TCSVField.Create;
  Add(Result);
  Result.Text:= AText;
end;

procedure TCSVFieldList.ClearFields;
var
  F: TCSVField;
begin
  for F in Self do
    F.Clear;
end;

end.
3 голосов
/ 01 февраля 2011

Мой фреймворк имеет код для этого в файле CsiTextStreamsUnt.pas (см. http://www.csinnovations.com/framework_delphi.htm)

1 голос
/ 12 января 2013

Следуя логике VCL TXMLTransform, я написал помощник класса TCsvTransform, который переводит структуру формата .csv в / из TClientDataSet.Для получения дополнительной информации о TCsvTransform, cf http://didier.cabale.free.fr/delphi.htm#uCsvTransform.
Примечание: я установил те же символы типа поля, что и у TJvCsvDataSet Уоррена

0 голосов
/ 26 июля 2014

Наткнулся на этот Delphi CSV File и String Reader для классов Delphi 2009 и более поздних версий сегодня на CodeProject, я не пробовал, но из примера кода это круто. Автор Владимир Никитенко, основной класс - TnvvCSVReader.

0 голосов
/ 19 января 2013

Мои функции

function ParseCSVString(s: string; const delimiter: Char = ','; const enclosure: Char = '"'): TStrings;
var
    i,len: Integer;
    f: string;
    inQuoted: Boolean;
begin
    Result := TStringList.Create;
    len := Length(s);
    if len = 0 then Exit;
    //Test,Test;"Test;Test";"Test""Test";;
    f := '';
    inQuoted := False;
    i:=0;
    while i < len do
    begin
        Inc(i);
        if s[i] = enclosure then
        begin
            if inQuoted and (i<len) and (s[i+1] = enclosure) then
            begin
                f := f + '"';
                i:=i+1;
            end
            else
                inQuoted := not inQuoted;
        end
        else if s[i] = delimiter then
        begin
            if inQuoted then
                f := f+s[i]
            else
            begin
                Result.Add(f);
                inQuoted := false;
                f := '';
            end;
        end
        else
            f := f + s[i];
    end;
    Result.Add(f);
end;

function EscapeCSVString(s: string; const delimiter: Char = ','; const enclosure: Char = '"'): string;
var
    i: Integer;
begin
    Result := StringReplace(s,enclosure,enclosure+enclosure,[rfReplaceAll]);
    if (Pos(delimiter,s) > 0) OR (Pos(enclosure,s) > 0) then
        Result := enclosure+Result+enclosure;
end;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...