Stringlist和CSV

时间:2012-11-22 13:24:11

标签: delphi delphi-xe2

如何在将此CSV文件加载到Stringlist后,根据Stringlist中的索引访问各个记录。

CSV示例:

   Record0;Record1;Record2
   Record0;Record1;Record2
   Record0;Record1;Record2
   Record0;Record1;Record2

2 个答案:

答案 0 :(得分:7)

您不能将RFC4180

中描述的CSV文件用于TStringList

有效RFC4180 CSV文件示例(2行,5个字段)

Data1;"Data2;Data2";"Data3
Data3";"Data4""Data4""";Data5
Data1;"Data2;Data2";"Data3
Data3";"Data4""Data4""";Data5

和单个字段值

  1. 数据1
  2. 数据2;数据2
  3. 数据3#13#10Data3
  4. 数据4 “数据4”
  5. DATA5
  6. 但也许你觉得这个可用(a quick sample i wrote in 2011)。

    不要感到困惑,您可以修改Cell Value但不能修改SaveToFile方法。

    unit CSVData;
    
    interface
    
    type
      TCSVData = class
      private
        FData : array of array of string;
        FDelim : Char;
        FQuote : Char;
        function GetRows : Integer;
        function GetCols : Integer;
        function GetCell( Row, Col : Integer ) : string;
        procedure SetCell( Row, Col : Integer; const Value : string );
      public
        destructor Destroy; override;
        procedure LoadFromFile( const FileName : string );
        property Cell[Row, Col : Integer] : string
          read GetCell
          write SetCell;
        property Rows : Integer
          read GetRows;
        property Cols : Integer
          read GetCols;
        property Delim : Char
          read FDelim
          write FDelim;
        property Quote : Char
          read FQuote
          write FQuote;
      end;
    
    implementation
    
    uses
      Classes;
    
    { TCSVData }
    
    destructor TCSVData.Destroy;
    begin
      SetLength( FData, 0, 0 );
      inherited;
    end;
    
    function TCSVData.GetCell( Row, Col : Integer ) : string;
    begin
      Result := FData[Row, Col];
    end;
    
    function TCSVData.GetCols : Integer;
    begin
      if Rows > 0
      then
        Result := Length( FData[0] )
      else
        Result := 0;
    end;
    
    function TCSVData.GetRows : Integer;
    begin
      Result := Length( FData );
    end;
    
    procedure TCSVData.LoadFromFile( const FileName : string );
    var
      Data : TStrings;
      Val : string;
      MyChar : Char;
      LastChar : Char;
      QuotePart : Boolean;
      Col : Integer;
      Row : Integer;
      MaxCol : Integer;
    begin
      Data := TStringList.Create;
      try
        Data.LoadFromFile( FileName );
    
        LastChar := #0;
        QuotePart := False;
        Val := '';
        MaxCol := 0;
        Col := 0;
        Row := 0;
    
        // Jedes Zeichen durchlaufen
        for MyChar in Data.Text do
          begin
            if ( MyChar = Quote )
            then
              begin
                // QuotePart wechselt den Status
                QuotePart := not QuotePart;
    
                // Befinden wir uns im QuotePart und das letzte Zeichen
                // war das Quote-Zeichen, dann handelt es sich um eine
                // Verdoppelung und wir hängen das Quote-Zeichen an
                // den Puffer
                if QuotePart and ( LastChar = Quote )
                then
                  Val := Val + Quote;
              end
            else if not QuotePart and ( MyChar = Delim )
            then
              begin
                // Sind noch nicht genug Zeilen da ...
                if high( FData ) < Row + 1
                then
                  // ... dann auf Verdacht schon mal 10 hinzufügen
                  SetLength( FData, Row + 10 );
                // Sind noch nicht genug Spalten da ...
                if high( FData[Row] ) < Col + 1
                then
                  // ... dann auf Verdacht schon mal 10 hinzufügen
                  SetLength( FData[Row], Col + 10 );
                // Wert eintragen
                FData[Row, Col] := Val;
                // Puffer leeren
                Val := '';
                // Spalte hochzählen
                Inc( Col );
              end
            else if not QuotePart and ( ( MyChar = #13 ) or ( MyChar = #10 ) )
            then
              begin
                // Haben wir CR LF gefunden ...
                if ( MyChar = #10 ) and ( LastChar = #13 )
                then
                  begin
                    // Sind noch nicht genug Zeilen da ...
                    if high( FData ) < Row + 1
                    then
                      // ... dann auf Verdacht schon mal 10 hinzufügen
                      SetLength( FData, Row + 10 );
                    // Die Anzahl der Spalten steht jetzt fest
                    SetLength( FData[Row], Col + 1 );
                    // MaxCol merken
                    if Col > MaxCol
                    then
                      MaxCol := Col;
                    // Wert eintragen
                    FData[Row, Col] := Val;
                    // Puffer leeren
                    Val := '';
                    // Zeile hochzählen
                    Inc( Row );
                    // Neue Zeile => erste Spalte
                    Col := 0;
                  end;
              end
            else
              // Das aktuelle Zeichen an den Puffer hängen
              Val := Val + MyChar;
            // Das letzte Zeichen merken
            LastChar := MyChar;
          end;
    
        SetLength( FData, Row );
    
        // Das ist eigentlich nur notwendig, wenn die CSV-Datei falsch aufgebaut ist
        // und unterschiedliche Anzahl von Spalten in den Zeilen aufweist
        // Dieses ist allerdings nicht RFC-konform, aber wir wollen mal nicht so sein
        for Row := low( FData ) to high( FData ) do
          SetLength( FData[Row], MaxCol + 1 );
    
      finally
        Data.Free;
      end;
    end;
    
    procedure TCSVData.SetCell( Row, Col : Integer; const Value : string );
    begin
      FData[Row, Col] := Value;
    end;
    
    end.
    

    PS:我知道我有另一种方法使用状态模式,但我找不到它......也许以后

答案 1 :(得分:2)

SplitString会使用您定义的分隔符拆分字符串。 在这个例子中空间和;字符。

更新

添加了索引拆分功能的示例。 (SplitByIndex)。

更新2

添加了一个示例(SplitByIndexAlt),但未使用SplitStringTStringList.DelimitedText。 这将处理空间和;作为分隔符(不是QuoteChar所包含的分隔符)。

uses
  SysUtils,Classes,System.Types,System.StrUtils;

procedure Test(aStringList: TStringList);
var
  s,split : String;
  splittedString : TStringDynArray;
begin
  for s in aStringList do begin
    splittedString := SplitString(s,' ;'); // Splits at space and ;
    for split in splittedString do
    begin
      WriteLn(split);
    end;
  end;    
end;


Function SplitByIndex(aList : TStringList; aRow,aCol : Integer) : String;
// Zero based index !
var
  splittedString : TStringDynArray;
begin
  Result := '';
  if (aRow < aList.Count) then
  begin
    splittedString := SplitString(aList[aRow],' ;');
    if (aCol < Length(splittedString))
      then Result := splittedString[aCol];
  end;    
end;


Function SplitByIndexAlt(aList : TStringList; aRow,aCol : Integer) : String;
// Zero based index !
var
  splitlist : TstringList;
begin
  Result := '';
  if (aRow < aList.Count) then
  begin
    splitList := TStringList.Create;
    Try
      splitList.Delimiter := ';';
      // splitList.QuoteChar := '"'; // This may have to be changed
      splitList.StrictDelimiter := false;
      splitList.DelimitedText := aList[aRow];
      if (aCol < splitList.Count)
        then Result := splitList[aCol];
    Finally
      splitList.Free;
    End;
  end;
end;


var
  myList: TStringList;
begin
  myList := TStringList.Create;
  Try
    myList.Add('#0  Record0;Record1;Record2');
    myList.Add('#1  Record0;Record1;Record2');
    myList.Add('#2  Record0;Record1;Record2');
    myList.Add('#3  Record0;Record1;Record2');
    Test(myList);
    WriteLn(SplitByIndex(myList,0,4);
    ReadLn;
    Finally
      myList.Free;
    End;
end.

此处的输出如下所示:

#0

Record0
Record1
Record2

etc

现在考虑CSV文件格式未标准化,请参阅CSV Wiki。 因此,对于更通用的解决方案,解决方案可能看起来更复杂。