9
votes

Comment puis-je lire et écrire des CSV d'une manière similaire à net FichiersHelPers?

Quelqu'un sache que je peux importer / exporter des fichiers CSV, TXT, d'une manière similaire à net FichiersHelPers, mais à l'aide de Delphi, de prenant des espaces et des citations en tenant compte et gérer les règles traditionnelles de l'échappement de CSV de manière similaire à la manière dont CSV s'échappe Excel?

Réf. link http://www.filehelpers.com/

Si votre réponse a tendance à être: "Pourquoi ce gars paresseux n'écrit pas un simple analyseur CSV", considérez cette lecture de 5 minutes et vous saurez alors pourquoi l'analyse de la CSV n'est pas triviale:

http://secretgeek.net/csv_trouble.asp


11 commentaires

En réalité, la tâche est divisée en deux: d'abord écrire un jeton qui comptera pour les guillemets et les espaces (jetez des expansures, la plupart du temps que vous pouvez vivre plus facilement), puis utilisez le tokenizer pour analyser la ligne CSV par ligne. Écrire un jeton correct et complet m'a pris une heure environ.


et l'exportation est encore plus facile. J'ai récemment écrit mon exportateur avec un objectif de haute performance.


Pourquoi avez-vous relié la page qui ronflait de ces choses évidentes? En fait, regarder dans le problème prend moins de temps que de rechercher des conseils aléatoires dans les internets de Teh.


L'exportation est certainement plus facile que d'analyser. À quiconque pense que l'analyse du CSV est triviale; Envoyez-moi votre parseur CSV et je vous montrerai dix fichiers d'entrée CSV réels qui vont briser votre analyseur.


@Eugene: Je manque probablement quelque chose ici, mais pourquoi la nécessité de compter des espaces?


@Simon erreur en libellé. Disons "Prendre des espaces et des citations en compte".


J'ai changé le texte de cette question, donc il convient à la nouvelle règle que vous ne pouvez pas poser de questions sur la bibliothèque à utiliser, seulement comment faire une tâche.


Wow, voyez, la police proche est ici en quelques secondes.


@Warrenp Nous aimons garder la pile trop-fleuve propre et exempte de spam. Les questions de recommandation attirent le spam.


Comme je l'ai dit, je la répare, alors pouvez-vous partir en janvier?


Vous voudrez peut-être voir celui-ci: CodeProject. com / conseils / 783493 / ... . Ce n'est qu'un seul fichier PAS. Facile à utiliser.


7 Réponses :


9
votes

C'est assez basique, mais Tstringlist a Délimiter , Delimitedtext et quecechar Propriétés, qui adresse une partie de ces problèmes.

mis à jour pour ajouter, par commentaires: Ne soyez pas tenté par la propriété commatxt , qui présente certaines limitations surprenantes pour la compatibilité à l'envers avec les versions archaïques de Delphi.


11 commentaires

@David, il adresse tous les points spécifiques de la publication du blog. Comme je l'ai dit, ce n'est pas exhaustif, mais si vous avez des idées sur les détails des besoins de José, n'hésitez pas à les partager.


Craig, Tstringlist résout-t-il toutes les nombreuses mini courtiers CSV? (Cordes à l'intérieur des citations, nouvelles lignes dans des citations, des valeurs manquantes, etc.)


@CRAIG Je suis désolé, mais vous ne serez pas loin avec Tstringlist. Un espace est toujours considéré comme un délimiteur !! De plus, vous auriez besoin d'une liste de chaînes par ligne, même si cela pourrait l'analyser.


@David, c'est juste faux. Le problème "espace" est vrai pour commatxt , mais pas pour délimitétext . C'est pourquoi je ne l'ai pas suggéré, même pour CSV. @Leandro, certains, mais pas tous. Que ce soit approprié dépend de vos besoins, mais il a l'avantage particulier d'être intégré.


@CRAIG, je lis le fichier d'aide qui indique: "Lors de l'attribution de délimitétext, la valeur est analysée sous forme de texte formaté SDF. Pour le format SDF, les chaînes sont séparées par des caractères délimités ou espaces ", mon emphase. Mais plus tard, indique que StrictDelimiter est défini sur False, le caractère spatial est également interprété comme un délimiteur, quelle que soit la valeur du délimiteur. Ce n'est pas vrai lorsque le caractère spatial se produit entre les guillemets. " Cette propriété stricte n'a pas toujours été présente, ce qui peut expliquer mon malentendu et mes commentaires erronés qui en résultent.


@CRAIG I a corrigé une faute de frappe dans votre réponse et m'a donc également donné l'occasion de supprimer ma voix basse.


@CRAIG en fait, je ne suis pas sûr que le strictelimiter est pertinent du tout. Je pense que je viens d'être séduit par Commatext dans le passé et ma déception avec elle a coloré mes opinions de la capacité de Tstringlist à faire cela. Veuillez accepter mes excuses. Downvote converti en uppote!


@DavID Il est certainement vrai que commatxt et délimitétext a (surprenant) des règles différentes et je devrais probablement noter que dans ma réponse.


@CRAIG a fait mon upvote vous pencher sur la barrière magique de 50k? !! Bon travail! Actuellement à une ronde cool 50000!


Essayer d'utiliser TStringList pour analyser les fichiers CSV du monde réel non trivial finira par un exercice de frustration. Utilisez une classe dédiée comme les poteaux d'un misha sur ci-dessous ou celui que j'ai écrit (JVCl JVCSVDataset). TStringList ne peut pas gérer la CR-LF incorporé ESAPED dans un fichier CSV, et il n'est donc pas prudent de recommander pour une utilisation réelle du monde.


Je regarde une version «archaïque» de Delphi, mais le problème avec StressList est que LoadFromFile / LoadFromstream parse »dans les chaînes séparées par le retour de chariot ou les caractères lignes à la ligne». À partir de là, peu importe si vous utilisez COMMATEXT qui utilise Delimitedtext: ils ne remarquent pas les caractères LF ou CR ou CR, à moins que vous spécifiez l'un de ces caractères comme délimiteur. (Mais j'utilise une unité CSV complète ou personnalisée si je veux une compatibilité avec d'autres CSV: Si vous voulez une compatibilité, il y a toujours quelque chose que vous devez jouer)



3
votes

Mon cadre contient du code pour cela dans le fichier CSITEXTSTREAMSUNT.PAS (voir http: //www.csinnovations. com / framework_delphi.htm )


6 commentaires

Est-ce que cela gère-t-il des lignes de ligne de retour de chariot incorporées à l'intérieur d'une rangée de CSV?


J'ai intentionnellement supprimé cette fonctionnalité il y a environ un an, car il n'y a pas de moyen standard de gérer des enregistrements sur plusieurs lignes pour la lecture et l'écriture de fichiers. Au lieu de cela, j'ai introduit une méthode ultérieure:


En essayant de nouveau: j'ai intentionnellement supprimé cette fonctionnalité il y a environ un an, car il n'y a pas de moyen standard de gérer les enregistrements s'étendant sur plusieurs lignes pour la lecture et l'écriture (et il n'y a vraiment pas de point essentielle). Au lieu de cela, j'ai introduit deux méthodes simples, une pour la lecture, une pour la rédaction, qui permettent à un développeur d'ajouter tout type de traitement des enregistrements multi-lignes dont ils ont besoin.


D'accord, Misha, il n'y a aucune solution standard, mais il y a une façon la plus courante qu'elle soit vue. C'est ce que j'appelle la règle "Defacto". Tout ce que fait Excel. Donc, peut-être que vous pourriez activer le «drapeau Excel».


Bon point, bien que je n'ai jamais eu à utiliser cela dans des scénarios du monde réel, je l'ai mis sous le principe de Yagni, et si j'en ai besoin, je vais remplacer mes méthodes. J'ai au moins 20 000 LOI dans la mise en œuvre de routines d'utilité et de classes que j'ai réellement utilisées dans des applications en direct au cours des 10 dernières années - ce serait beaucoup trop d'effort pour ajouter des choses que je n'aurais peut-être pas besoin ;-)


@ Misha-je voudrais l'essayer mais le lien ne fonctionne pas



17
votes

J'ai écrit un jeu de données (objet de type TTAble) pour le projet Jedi appelé TJVCSVDataset qui suit toutes les règles d'analyse de la CSV similaires aux règles d'analyse de la CSV utilisées par Excel et divers outils de base de données et de rapport qui importent et exportent des CSV.

Vous pouvez installer JVCL, déposez un TJVCSVDaTaset sur votre formulaire.

Il contient également une classe de flux qui chargera très rapidement un fichier sur disque et analysez la ligne de ligne par ligne, à l'aide des règles d'évacuation correctes requises pour les fichiers CSV, même des fichiers comprenant des codes de retour-retour / ligne de ligne codés dans un champ.

Vous venez de le laisser tomber sur votre formulaire et définissez la propriété Fielddefs comme celle-ci:

CSVFIELDDEF = ABC:%, DEF: #, GHI: $, ....

Il existe des codes spéciaux pour entier, point flottant, ISO Date-Heure et d'autres domaines. Il vous permet même de mapper un champ à chaîne large sur un champ UTF8 dans un fichier CSV.

Il y a un éditeur de propriétés de designTIM pour vous éviter de supprimer les défenseurs de champ CSV à l'aide de la syntaxe ci-dessus, vous pouvez simplement choisir visuellement les types de colonne.

Si vous ne configurez pas de champ CSV def, il ne fait que cartographier tout ce qui existe dans le fichier dans les champs de type à cordes.

Jedi jvcl: http://jvcl.delphi-jedi.org/

JVCSVDaTaset Docs:

http://help.delphi-jedi.org/unit.php ? Id = 3107

http://help.delphi-jedi.org/item.php ? Id = 174896

Entrez la description de l'image ici


11 commentaires

P, est-il capable ou facile d'implémenter une présentation "N lignes". Je veux dire première ligne a une mise en page, le second autre, comme un détail de maîtrise?


Le composant est tolérant des champs et des valeurs manquants. C'est aussi loin que ça va. Vous pouvez avoir une base de données CSV et vous pouvez continuer à ajouter des champs, et il chargera un fichier créé avant que ce champ ait été ajouté. Cela ne se soucie même pas si vous réorganisez les champs. C'est assez intelligent. Mais ce n'est pas la magie, et cela n'aura pas complètement différents ensembles de données sur chaque ligne, no.


Si vous souhaitez implémenter une mise en page différente de chaque ligne, vous ne voudriez pas de composant TTable. Mais peut-être que vous pourriez trouver facile de commencer par saisir ma classe de streams et ma classe de séparateur CSV, puis enveloppez-les dans ce que vous vouliez faire, ce n'est pas un CSV exactement, mais le texte délimité.


Beau composant Warren. Certainement très rapide et fiable. Juste curieux - Comment optimiser l'utilisation de la mémoire? Juste en définissant les champs? Ou existe-t-il une valeur optimale (moyen de calculer) pour le textustreambuffer? (D7)


@WarrenP J'ai essayé d'utiliser ce composant mais il a été étouffé sur des champs contenant des guillemets doubles (rapportés avec un boîtier de test). En tout cas, ça a l'air assez agréable.


Leonardo: Vous devriez publier des données d'échantillonnage et enregistrer un bogue sur le suivi de bugs Jedi. Je voudrais certainement réparer un tel bogue aussi rapide que je peux. Besoin de plus de données.


Cela me donne des erreurs de mémoire, même avec un fichier assez petit de seulement quelques milliers de MB.


Ainsi avez-vous publié un bogue sur le tracker de Bug Jedi? Quel bon fait un commentaire ici? Avez-vous des lignes plus longues que 4K pour une seule ligne? Si tel est le cas, il s'agit d'une limitation connue et la solution de contournement est également bien connue.


Ironiquement, les dieux de Stackoverflow ont décidé que ce genre de chose n'est pas un bon type de question ici. (Suggère une bibliothèque? Fermer instantané.) Et pourtant, cette question a une vue de 6 000 et a aidé beaucoup de gens!)


Ce composant a fière allure. J'aimerais l'utiliser. Mais il a une limite interne (codée en dur?) De 120 colonnes / champs. J'ai une table avec 210 champs, et ainsi la bibliothèque Gripe à l'exécution lorsque j'active l'ensemble de données. Des suggestions sur la déplacement de cela?


FYI: Pour la limite de champ codée de 120 codée, je suis simplement heurté JVCSV_MAXColumns dans jvcl / exécuté / jvcsvdata.pas . Et puis avant d'ouvrir la table au moment de l'exécution, j'ai lu mes défensements de champ et ajustez jvcsvdataseet.textbuffersize à une largeur odieuse pour rendre compte de mes longueurs de ligne. Maintenant, le contrôle fonctionne bien, merci pour ce composant @warrenp



1
votes

Après la logique VCL TXMLTRANSFORM, j'ai écrit une aide de la classe TCSvtransform qui traduit une structure de format .csv sur / depuis un TClientDataset.
Pour plus de détails sur TCSVTransform, cf http://didiacer.cabale.free.fr/delphi. htm # ucsvtransform .
NB: J'ai défini les mêmes symboles de type de champ que TJVCSVDataset de Warren


0 commentaires

0
votes

Mes fonctions

function ParseCSVString(s: string; const delimiter: Char = ','; const enclosure: Char = '"'): TStrings;
var
    i,len: Integer;
    f: string;
    inQuoted: Boolean;
begin
    Result := TStringList.Create;
    len := Length(s);
    if len = 0 then Exit;
    //Test,Test;"Test;Test";"Test""Test";;
    f := '';
    inQuoted := False;
    i:=0;
    while i < len do
    begin
        Inc(i);
        if s[i] = enclosure then
        begin
            if inQuoted and (i<len) and (s[i+1] = enclosure) then
            begin
                f := f + '"';
                i:=i+1;
            end
            else
                inQuoted := not inQuoted;
        end
        else if s[i] = delimiter then
        begin
            if inQuoted then
                f := f+s[i]
            else
            begin
                Result.Add(f);
                inQuoted := false;
                f := '';
            end;
        end
        else
            f := f + s[i];
    end;
    Result.Add(f);
end;

function EscapeCSVString(s: string; const delimiter: Char = ','; const enclosure: Char = '"'): string;
var
    i: Integer;
begin
    Result := StringReplace(s,enclosure,enclosure+enclosure,[rfReplaceAll]);
    if (Pos(delimiter,s) > 0) OR (Pos(enclosure,s) > 0) then
        Result := enclosure+Result+enclosure;
end;


0 commentaires

3
votes

Ici, le code que j'ai écrit que lit les fichiers CSV, il gère également les retours de chariot à l'intérieur de citations.

unit CSV;

interface
uses
  SysUtils, Generics.Collections, IOUtils;

type
  TParseState = (psRowStart, psFieldStart, psUnquotedFieldData,
    psQuotedFieldData, psQFBranch, psEndOfQuotedField, psQFEndSearch,
    psEndOfLine, psEndOfFile);

  TCSVField = class
  strict private
    FText: String;
  public
    constructor Create;
    destructor Destroy; override;
    property Text: string read FText write FText;
    procedure Clear;
  end;

  TCSVFieldList = class(TObjectList<TCSVField>)
  public
    function AddField(const AText: string): TCSVField;
    procedure ClearFields;
  end;

  TCSVRow = class
  strict private
    FFields: TCSVFieldList;
  public
    constructor Create;
    destructor Destroy; override;
    property Fields: TCSVFieldList read FFields;
  end;

  TCSVParser = class
  strict private
    FRow: TCSVRow;
    FContent: String;
    FCIdx: Integer;
    FParseState: TParseState;
    FEOF: Boolean;
    procedure ParseRow;
  public
    function First: Boolean;
    function EOF: Boolean;
    function Next: Boolean;
    procedure OpenFile(AFileName: String);
    procedure OpenText(const AText: string);
    property Row: TCSVRow read FRow;
    constructor Create;
    destructor Destroy; override;
  end;

implementation



{implementation of TCSVField}

procedure TCSVField.Clear;
begin
  FText:= '';
end;

constructor TCSVField.Create;
begin
  inherited Create;
end;

destructor TCSVField.Destroy;
begin
  inherited Destroy;
end;

{implementation of TCSVRow}

constructor TCSVRow.Create;
begin
  inherited Create;
  FFields:= TCSVFieldList.Create;
end;

destructor TCSVRow.Destroy;
begin
  FreeAndNil(FFields);
  inherited Destroy;
end;

{implementation of TCSVParser}

constructor TCSVParser.Create;
begin
  inherited Create;
  FRow:= TCSVRow.Create;
  FCIdx:= 1;
  FParseState:= psEndOfFile;
end;

destructor TCSVParser.Destroy;
begin
  FreeAndNil(FRow);
  inherited Destroy;
end;



function TCSVParser.EOF: Boolean;
begin
  Result:= FEOF;
end;

function TCSVParser.First: Boolean;
begin
  FEOF:= False;
  FCIdx:= 1;
  FParseState:= psRowStart;
  Result:= Next;
end;

function TCSVParser.Next: Boolean;
begin
  if not EOF then
    ParseRow;
  Result:= not EOF;
end;

procedure TCSVParser.OpenFile(AFileName: String);
begin
  OpenText(TFile.ReadAllText(AFileName));
end;

procedure TCSVParser.OpenText(const AText: string);
begin
  FContent:= AText;
  FRow.Fields.Clear;
  First;
end;

procedure TCSVParser.ParseRow;
var
  FieldIdx: Integer;

  procedure AddField(const AText: string);
  begin
    if FieldIdx > FRow.Fields.Count-1 then
      FRow.Fields.AddField(AText)
    else
      FRow.Fields[FieldIdx].Text:= AText;

    Inc(FieldIdx);
  end;

var
  FieldText: string;
  Curr: Char;
  LastIdx: Integer;
begin
  if FParseState = psEndOfFile then
  begin
    FEOF:= True;
    FRow.Fields.ClearFields;
    Exit;
  end;

  if not (FParseState in [psRowStart]) then
    raise Exception.Create('ParseRow requires ParseState = psRowState');

  FieldIdx:= 0;
  FRow.Fields.ClearFields;
  LastIdx:= Length(FContent);
  while True do
  begin
    case FParseState of
      psRowStart:
        begin
          if FCIdx > LastIdx then
          begin
            FEOF:= True;
            FParseState:= psEndOfFile;
          end
          else
          begin
            FParseState:= psFieldStart;
          end;
          Dec(FCIdx); // do not consume
        end;
      psFieldStart:
        begin
          FieldText:= '';
          if FContent[FCIdx] = '"' then
            FParseState:= psQuotedFieldData
          else
          begin
            FParseState:= psUnquotedFieldData;
            Dec(FCIdx); // do not consume
          end;
        end;
      psUnquotedFieldData:
        begin
          if FCIdx > LastIdx then
          begin
            AddField(FieldText);
            FParseState:= psEndOfFile;
          end
          else
          begin
            Curr:= FContent[FCIdx];
            case Curr of
              #13, #10:
                begin
                  AddField(FieldText);
                  FParseState:= psEndOfLine;
                end;
              ',':
                begin
                  AddField(FieldText);
                  FParseState:= psFieldStart;
                end;
            else
              FieldText:= FieldText + Curr;
            end;
          end;
        end;
      psQuotedFieldData:
        begin
          if FCIdx > LastIdx then
            raise Exception.Create('EOF in quoted Field.');

          Curr:= FContent[FCIdx];
          if Curr = '"' then
            FParseState:= psQFBranch
          else
            FieldText:= FieldText + Curr;
        end;
      psQFBranch:
        begin
          Curr:= FContent[FCIdx];
          if Curr = '"' then
          begin
            FieldText:= FieldText + Curr;
            FParseState:= psQuotedFieldData;
          end
          else
          begin
            AddField(FieldText);
            FParseState:= psEndOfQuotedField;
            Dec(FCIdx); // do not consume
          end;
        end;
      psEndOfQuotedField:
        begin
          if FCIdx > LastIdx then
            FParseState:= psEndOfFile
          else
          begin
            Curr:= FContent[FCIdx];
            if CharInSet(Curr, [#13, #10]) then
              FParseState:= psEndOfLine
            else
            begin
              FParseState:= psQFEndSearch;
              Dec(FCIdx); // do not consume
            end;
          end;
        end;
      psQFEndSearch:
        begin
          if FCIdx > LastIdx then
            FParseState:= psEndOfFile
          else
          begin
            Curr:= FContent[FCIdx];
            if CharInSet(Curr, [#13, #10]) then
              FParseState:= psEndOfLine
            else if Curr = ',' then
              FParseState:= psFieldStart;

            // skips white space or other until end
          end;
        end;
      psEndOfLine:
        begin
          if FCIdx > LastIdx then
            FParseState:= psEndOfFile
          else
          begin
            Curr:= FContent[FCIdx];
            if not CharInSet(Curr, [#13, #10]) then
            begin
              FParseState:= psRowStart;
              Break; // exit loop, we are done with this row
            end;
          end;
        end;
      psEndOfFile:
        begin
          Break;
        end;
    end;
    Inc(FCIdx);
  end;
end;


{ TCSVFieldList }

function TCSVFieldList.AddField(const AText: string): TCSVField;
begin
  Result:= TCSVField.Create;
  Add(Result);
  Result.Text:= AText;
end;

procedure TCSVFieldList.ClearFields;
var
  F: TCSVField;
begin
  for F in Self do
    F.Clear;
end;

end.


0 commentaires

0
votes

est tombé sur ce fichier delphi csv et chaîne Classes de lecteur pour Delphi 2009 et plus tard aujourd'hui sur CodeProject, je n'ai pas essayé, mais à partir du code exemple, c'est un peu cool. Écrit par Vladimir Nikitenko, la classe principale est TNVVCSVReader.


0 commentaires