この CSV ファイルをロードした後、Stringlist の Index に基づいて個々のレコードにアクセスするにはどうすればよいですか。
CSV の例:
Record0;Record1;Record2
Record0;Record1;Record2
Record0;Record1;Record2
Record0;Record1;Record2
この CSV ファイルをロードした後、Stringlist の Index に基づいて個々のレコードにアクセスするにはどうすればよいですか。
CSV の例:
Record0;Record1;Record2
Record0;Record1;Record2
Record0;Record1;Record2
Record0;Record1;Record2
RFC4180に記載されている CSV ファイルに TStringList を使用することはできません
有効な RFC4180 CSV ファイルの例 (2 行、5 フィールド)
Data1;"Data2;Data2";"Data3
Data3";"Data4""Data4""";Data5
Data1;"Data2;Data2";"Data3
Data3";"Data4""Data4""";Data5
および単一のフィールド値
しかし、あなたはこれが使えると思うかもしれません (私が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: 状態パターンを使用した別のアプローチがあることは知っていますが、それが見つからない...多分後で
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を参照してください。したがって、より一般的なソリューションの場合、ソリューションはより複雑に見えるかもしれません。