2

この CSV ファイルをロードした後、Stringlist の Index に基づいて個々のレコードにアクセスするにはどうすればよいですか。

CSV の例:

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

2 に答える 2

8

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#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 に答える