2

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

CSV 示例:

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

2 回答 2

8

您不能将 TStringList 用于RFC4180中描述的 CSV 文件

有效 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#10数据3
  4. 数据4“数据4”
  5. 数据5

但也许你觉得这很有用(我在 2011 年写的一个快速示例)。

不要混淆,您可以修改单元格值但不能修改 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:我知道我有另一种使用状态模式的方法,但我找不到它......也许稍后

于 2012-11-22T14:04:18.137 回答
3

SplitString将使用您定义的分隔符拆分您的字符串。在此示例中,空格和 ; 特点。

更新

添加了索引拆分函数的示例。( SplitByIndex)。

更新 2

添加了一个示例 ( SplitByIndexAlt) 不使用SplitString, 但TStringList.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。因此,对于更通用的解决方案,解决方案可能看起来更复杂。

于 2012-11-22T13:39:47.663 回答