Stringlist and CSV

社会主义新天地 提交于 2019-12-11 02:10:19

问题


How can i access the individual records based on Index in Stringlist after loading this CSV file in to it.

CSV Example:

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

回答1:


SplitString will split your string with delimiters you define. In this example space and ; character.

Update

Added an example of an indexed split function. (SplitByIndex).

Update 2

Added an example (SplitByIndexAlt) not using SplitString, but TStringList.DelimitedText. This will treat spaces and ; as delimiter (not the ones enclosed by 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.

The output here will look like:

#0

Record0
Record1
Record2

etc

Now consider that a CSV file format is not standardised, see CSV Wiki. So for a more general solution the solution might look more complex.




回答2:


You can't use a TStringList for CSV Files described in RFC4180

Example of a valid RFC4180 CSV File (2 Rows, 5 Fields)

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

and the single Field Values

  1. Data1
  2. Data2;Data2
  3. Data3#13#10Data3
  4. Data4"Data4"
  5. Data5

but maybe you find this usable (a quick sample i wrote in 2011).

Do not get confused, that you can modify the Cell Value but no SaveToFile Method.

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: I know i have another approach using the State Pattern, but i can't find it ... maybe later



来源:https://stackoverflow.com/questions/13513678/stringlist-and-csv

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!