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? P>
Réf. link http://www.filehelpers.com/ p>
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: P>
7 Réponses :
C'est assez basique, mais mis à jour pour ajouter, par commentaires: Ne soyez pas tenté par la propriété Tstringlist code> a
Délimiter code>,
Delimitedtext code> et
quecechar code> Propriétés, qui adresse une partie de ces problèmes. P>
commatxt code>, qui présente certaines limitations surprenantes pour la compatibilité à l'envers avec les versions archaïques de Delphi. P>
@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 code>, mais pas pour
délimitétext code>. 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 b>", 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 code> et
délimitétext code> 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)
Mon cadre contient du code pour cela dans le fichier CSITEXTSTREAMSUNT.PAS (voir http: //www.csinnovations. com / framework_delphi.htm ) p>
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
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. p>
Vous pouvez installer JVCL, déposez un TJVCSVDaTaset sur votre formulaire. P>
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. p>
Vous venez de le laisser tomber sur votre formulaire et définissez la propriété Fielddefs comme celle-ci: P>
CSVFIELDDEF = ABC:%, DEF: #, GHI: $, .... P>
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. p>
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. p>
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. P>
Jedi jvcl: http://jvcl.delphi-jedi.org/ p>
JVCSVDaTaset Docs: P>
http://help.delphi-jedi.org/unit.php ? Id = 3107 p>
http://help.delphi-jedi.org/item.php ? Id = 174896 p>
p>
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 code> dans
jvcl / exécuté / jvcsvdata.pas code>. 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 code> à une largeur odieuse pour rendre compte de mes longueurs de ligne. Maintenant, le contrôle fonctionne bien, merci pour ce composant @warrenp
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 P>
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;
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.
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. P>
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.