Traitement de fichier .csv [perl] - Perl - Programmation
Marsh Posté le 12-06-2010 à 22:01:01
Finalement j'ai résolu mon problème: je lis mon fichier et je le modifie à la volée pour supprimer les ; à l'aide de:
Code :
|
J'enregistre le résultat dans un fichier temp puis j'ouvre ce fichier temp avec Tie::Handle::CSV.
Maintenant mon soucis et au niveau de l'encodage je pense: lorsque j'affiche le fichier temp avec Tie::Handle::CSV il s'arrête à la première ligne et affiche un � à la place des accents. Le problème est que je ne peux pas me permettre de remplacer les caractères accentués
Le csv est généré sous windows et je code sous linux. Vous avez une idée?
Marsh Posté le 12-06-2010 à 22:23:09
Bonsoir,
Quelle est la raison pour laquelle vous utilisez Tie::Handle::CSV plutôt que le module assez standard Text::CSV?
Je vous suggère la lecture de http://perlmeme.org/tutorials/parsing_csv.html
Vos problèmes d'accents sont réglables en précisant l'encoding a l'ouverture du fichier.
A+,
Marsh Posté le 12-06-2010 à 22:48:59
Une solution simple: coder sous windows
Marsh Posté le 12-06-2010 à 23:27:08
gilou a écrit : Bonsoir, |
Je trouve l'accès aux champs plus sympa avec Tie::Handle::CSV.
Marsh Posté le 12-06-2010 à 23:35:30
grao a écrit : Une solution simple: coder sous windows |
Euh, les modules CPAN sont en général compatibles windows et linux. Donc ce n'est clairement pas une raison valable.
Je ne vois pas pourquoi utiliser un module de type Tie si on n'en a pas les besoins spécifiques (mise a jour incrémentale du fichier, ce qui est couteux en temps d'IO).
grao a écrit : Je trouve l'accès aux champs plus sympa avec Tie::Handle::CSV. |
Sauf qu'a partir du moment ou vous faites usages d'un fichier temporaire, l'usage d'un Tie devient complètement inutile (et est couteux)
Pour votre problème d'encodage, il faut probablement faire un
Tie::Handle::CSV->new( 'csv_test.csv', open_mode => '+<:encoding(blablabla)', header => 1);
avec le bon encoding à la place de blablabla (et le bon encoding, il va dépendre de celui de vos données).
A+,
Marsh Posté le 12-06-2010 à 23:46:09
L'optimisation n'est clairement pas un de mes objectifs (enfin pour le moment).
Le problème d'encodage vient du fichier .csv qui est généré sous windows, effectivement je pourrais fixer l'encodage mais comme à terme le script est censé tourner sous windows je vais faire au plus simple.
(oui c'est un peu urgent...).
Je veux bien éviter d'utiliser Tie::Handle::CSV mais comment accéder à un champ en particulier à partir de mon fichier temp?
Merci du coup de main
ps: vu mon ancienneté sur le forum tu peux me tutoyer
Marsh Posté le 13-06-2010 à 09:44:29
Il suffit de vous inspirer de ce que j'avais donné en lien:
Code :
|
le next if ($. == 1); sert a sauter la première ligne, qui contient le nom des champs dans leur exemple.
Vous n'en avez pas besoin peut être, mais a ce niveau, vous pouvez rejeter les lignes vides, puis virer ;; de la fin des lignes:
En modifiant cela pour le rendre plus clean:
Code :
|
Pour le open, un <:encoding(xxx) peut s'avérer utile vu ce que vous avez dit plus haut.
Bon, quand vous avez vos champs dans @columns, il ne vous reste plus que bosser avec.
A+,
Marsh Posté le 13-06-2010 à 11:35:01
Merci beaucoup
Bon apparemment à première vue aucune ligne ne passe correctement, il me sort "Failed to parse line" pour toute les lignes.
Faut dire le csv est pas super propre toutes les lignes ressemblent à ""blabla"",""toto"", etc... et même parfois j'ai du texte avec des virgules qui foutent la merde: ""blabla, blabla"",""toto"", etc...
Je vais regarder ça de plus près.
EDIT: en regardant plus attentivement le lien au dessus, il semblerait qu'il y ait de quoi résoudre tout mes problèmes...
Marsh Posté le 13-06-2010 à 12:30:28
En fait tout vient du soft qui sort le csv, il respecte pas le standard.
Chaque ligne ressemble à ça:
"toto,""titi, tata"",""plop"",""tutu""";;;
Notez le " en début de ligne et le """ sur le dernier champs.
L'option allow_loose_quotes pourrait régler le problème: "there is a way to get that parsed, and leave the quotes inside the quoted field as-is. This can be achieved by setting allow_loose_quotes AND making sure that the escape_char is not equal to quote_char."
Seulement dans ce cas comment faire en sorte qu'une virgule du style ""titi,toto"" ne soit pas interprétée comme le séparateur?
Marsh Posté le 13-06-2010 à 14:02:53
Pour l'encodage j'ai à moitié solutionné le problème: quand je mets un encodage windows style windows-1258 à peu près la moitié du fichier passe (y compris certaines lignes avec des accents) mais pas l'autre qui comprend pourtant les mêmes lettres accentuées
J'ai essayé avec différends encodages ça donne la même chose.
EDIT: ce n'était pas un problème d'encodage: dans une des lignes il y avait un ; qui se baladait. Du coup je supprime tous les ; du fichier et il me parse tout le fichier tranquille. J'ai plus qu'à faire mes traitements.
Merci du coup de main. (enfin bon j'ai pas encore terminé...)
Gilou si tu passes sur Paris prochainement je te paye une bière
Marsh Posté le 13-06-2010 à 14:48:27
Une Chimay!!
J'ai pas plus répondre plus tôt, je cueillais les cerises sur l'arbre ce matin
Notes que si tu veux tout controller au niveau du parsing, tu peux faire
Code : |
et n'utiliser aucun module (bref, virer le use Text::CSV
Ça sera peut être un poil plus lent, mais la tu contrôles entièrement ce qui se passe.
Typiquement, tu peux vérifier si le nb de champs trouvés est subitement différent de celui attendu.
Un split /,;/, $_; aurait permis de considérer , ou bien ; comme séparateur de champ (si le ; inattendu était une erreur pour un , et que d'autres peuvent survenir)
A+,
Marsh Posté le 13-06-2010 à 15:03:01
gilou a écrit : Une Chimay!! J'ai pas plus répondre plus tôt, je cueillais les cerises sur l'arbre ce matin Notes que si tu veux tout controller au niveau du parsing, tu peux faire
|
Monsieur est connaisseur, pour mois ce sera une bush
Merci beaucoup pour le code je vais tester tout ça
Marsh Posté le 13-06-2010 à 15:34:47
En revanche il y a un truc que je pige pas avec Text::CSV après avoir parsé le fichier je souhaite afficher un champs en particulier seulement il met l'intégralité de la ligne dans $columns[0]. $columns[1] et suivant sont vides.
Marsh Posté le 13-06-2010 à 15:57:45
Tu peux copier ici
1) deux ou trois lignes de ton fichier exemple pour test
2) le bout de code perl que tu as
Que je voie ce qui cloche
A+,
Marsh Posté le 13-06-2010 à 16:00:50
J'ai trouvé la solution ultime: Virer tous les " de mon fichier à l'aide d'un s/"//g;
Et hop j'accède à tous mes champs comme je veux
Code :
|
L'affichage sur la console m'indique que des erreurs de parse dues à l'encodage mais mon fichier tempmodele (qui me sert d'affichage de test) est nickel.
je vais enfin pouvoir avancer.
Je t'en dois 2
Marsh Posté le 13-06-2010 à 16:28:12
Bon c'est pas la solution miracle parce que dans certains champs j'ai du texte avec une ou des , du coup ça décale mes $columns[x] et fait foirer mes tests.
Comment gérer l'échappement des , intra-champs?
Il faudrait garder les " pour le faire mais ça coince au parsing
EDIT: s/""/'/g; simplement.
Au passage comment tester si un champ est vide? J'ai essayer if(! defined $columns[11]) et if(! exists $columns[11]) mais j'ai plein de faux positifs, if( $columns[11] eq '') génère une erreur
Marsh Posté le 13-06-2010 à 17:41:22
A priori, je ferais:
if ((not defined $columns[11]) or ($columns[11] eq "" ))
Soit c'est pas défini (on a trouvé moins de champs), soit le champ est vide.
Tu peux copier ici quelques lignes de ton fichier de données, ou c'est trop confidentiel?
A+,
Marsh Posté le 13-06-2010 à 17:48:29
Voici des lignes (dont j'ai changé les valeurs biensur):
"Champs1,""Champs2"",""Champs3"",""Champs4"",""Champs5""";;
"BLABLA,""qsdqd2454"",""dqfqd@qdq.fr"",""05/32/2010"",""qzerqzef,qzffd""";;
"BLOBLO,""zdf54"",""dqfqf@qdq.fr"",""05/32/2010"",""sdfsdf"",""""";;
Je pense que le problème vient du parsing: le premier champs fait chier parce qu'il commence par " mais il en manque un à la fin, en fait il est à la fin de la ligne
Sans compter le problème de la , en milieu du champs 5 qui fait que dans les lignes ou je teste un champs après un comportant une , il décale tout.
Mon code:
Code :
|
Voila ce que j'ai dans mon fichier de test:
| BLABLA,"qsdqd2454","dqfqd@qdq.fr","05/32/2010","qzerqzef,qzffd" |
| BLOBLO,"zdf54","dqfqf@qdq.fr","05/32/2010","sdfsdf","" |
La première ligne est considérée comme un header par Text::CSV.
Idéalement j'aimerais quelque chose du style:
| "BLABLA","qsdqd2454","dqfqd@qdq.fr","05/32/2010","qzerqzef,qzffd" |
Par ailleurs si je fais un print $columns[0] il me renvoie toute la ligne au lieu du premier champs. C'est pour ça que je pense que tout le problème vient de ce premier champs.
Si je suis pas clair faut le dire
Marsh Posté le 13-06-2010 à 20:00:17
grao a écrit : Voici des lignes (dont j'ai changé les valeurs biensur): |
Il y a que 5 champs dans le header et 6 dans les lignes, est-ce bien normal, ou une erreur a la recopie?
A+,
Marsh Posté le 13-06-2010 à 20:27:33
Sinon, en rajoutant un champ 6 dans le header, ce script passe assez bien sur votre exemple:
Code :
|
A+,
Marsh Posté le 13-06-2010 à 20:43:32
J'avais donné les champs pour exemple, j'avais pas fais attention au nombre
Merci pour le code.
J'ai réussi finalement en nettoyant le csv dans l'ordre de cette manière:
Code :
|
Tout fonctionne comme je veux. Tous mes champs sont présents et dans l'ordre.
Je peux faire mes traitements.
Après cela j'enverrais chaque ligne par mail.
Quelque chose me dit que la génération et l'envoi des mails risquent d'être sympathiques aussi...
Gilou si tu tiens vraiment à tes binouzes je n'ai qu'une parole
Marsh Posté le 13-06-2010 à 21:14:10
Ça manque de close après les open, ce code...
A+,
Marsh Posté le 13-06-2010 à 21:17:29
Ils y sont, j'ai juste pas copié jusqu'en bas
Marsh Posté le 16-06-2010 à 09:53:07
Je voudrais savoir si il existe un moyen de n'exécuter une instruction dans une boucle qu'une seule fois. ex:
while (my $ligne = <CSV_INPUT> ) {
print "Liste des groupes"; #à n'executer qu'une fois
print $columns[indice du groupe] # je simplifie...
}
Je pourrais stocker un boolean et faire un test dessus à chaque itération mais je me demande si il existe un mot clé du langage.
Merci.
Marsh Posté le 16-06-2010 à 11:29:42
C'était écrit plus haut:
if ($. == 1) {...}
$. c'est le numéro de ligne du fichier en cours de lecture.
A+,
Marsh Posté le 16-06-2010 à 11:59:18
gilou a écrit : C'était écrit plus haut: |
Oui mais ce n'est pas ce que je demande.
Je souhaite executer une seule fois: print "Liste des groupes";
Puis pour chaque ligne: print $columns[indice du groupe] # je simplifie...
"Liste des groupes" n'est pas contenu dans mon fichier en lecture, c'est moi qui l'ajoute dans ma sortie pour faire la mise en page.
Sauf que je ne veux pas afficher:
"Liste des groupes"
"groupe1"
"Liste des groupes"
"groupe2"
Mais:
"Liste des groupes"
"groupe1"
"groupe2"
Le tout sans mettre print "Liste des groupes"; avant la boucle parce que je veux mettre un titre pour chaque champ.
Marsh Posté le 16-06-2010 à 12:11:33
grao a écrit : |
Il faudra m'expliquer pourquoi
if ($. == 1) {print "Liste des groupes";}
ne colle pas pour vous, car c'est exactement ce que ça fait.
A+,
Marsh Posté le 16-06-2010 à 12:35:05
Effectivement, OTAN pour moi
Marsh Posté le 17-06-2010 à 11:33:59
J'ai quasiment fini: seulement j'ai un soucis avec l'envoi de mail.
Voici mon code:
Code :
|
Le mail est bien envoyé (et reçu ) seulement le code HTML n'est pas interprété, il affiche le code "brut".
Ma variable $mail_final contient le contenu du mail de cette façon: <html> le code html du mail </html>.
J'imagine que j'ai oublié un petit truc de rien du tout mais je vois pas quoi...
Marsh Posté le 17-06-2010 à 11:46:27
grao a écrit : J'ai quasiment fini: seulement j'ai un soucis avec l'envoi de mail.
|
Trouvé: il faut ajouter la ligne: $smtp->datasend("Content-Type: text/html\n\n" ); avant $smtp->datasend($mail_final);
Marsh Posté le 18-06-2010 à 16:04:37
Si je peux me permettre, je viens me greffer sur la discussion.
J'utilise le module Text::CSV_XS pour parser mon CSV.
Le hic c'est que je souhaite "filtrer" par un regex les champs avant de les mettre dans mon tableau. Et là je coince
Code :
|
Y'a t'il une solution avec ce module ou faut-il faire le "parse" manuellement ?
Marsh Posté le 18-06-2010 à 17:34:04
Gilou j'ai un soucis avec la vaiable $.
Je l'utilise dans plusieurs boucles seulement j'ai l'impression qu'elle n'est pas réinitialisée entre chacune d'elles.
Du coup la deuxième fois (ou même après) quand je fais next if ($. == 1 ); il ne saute pas la première ligne.
Si je fais dans ma deuxième boucle un print $. il commence à 345
C'est une variable propre à chaque boucle ou globale pour le fichier?
Marsh Posté le 18-06-2010 à 20:04:07
C'est une variable propre au filehandle, pas a une boucle donnée, qui donne le numéro de ligne lue.
Si tu as besoin d'une variable comptant les tours de boucle, dans une boucle pas liée à la lecture d'un fichier, tu te la crée, c'est simple.
Bon, selon ce sur quoi on boucle et le type de boucle, il y a certains hacks qui marchent, mais il n'y a rien de général.
A+,
Marsh Posté le 18-06-2010 à 20:10:40
nono 63 a écrit : Si je peux me permettre, je viens me greffer sur la discussion.
|
Une fois que vous avez @data, rien ne vous empêche d'en modifier le contenu, ou bien de modifier un $data[$i] avant le push.
A+,
Marsh Posté le 18-06-2010 à 21:34:28
gilou a écrit : C'est une variable propre au filehandle, pas a une boucle donnée, qui donne le numéro de ligne lue. |
Ok merci. Je vais faire ça à la main
Marsh Posté le 21-06-2010 à 18:46:59
gilou a écrit : |
ok j'essaye comme ça mais il doit me manquer une astuce car mon regex ne s'applique pas :
Code :
|
le contenu de $data[0] par exemple étant <Titi>valeur</Titi>
Marsh Posté le 21-06-2010 à 19:14:51
A l'oeil nu, le regexp est faux.
Vous voulez matcher quoi dans votre expression?
Le fait qu'il y a du texte entre deux balises? Ca devrait être
m/\>[^<]+\</;
A+,
Marsh Posté le 22-06-2010 à 15:01:20
C'est exact
Je souhaitais extraire le texte entre les 2 balises mais je ne savais pas qu'il fallait échapper les <>. Merci
Marsh Posté le 23-06-2010 à 22:39:53
Sinon j'ai testé les tables de hashage en Perl, c'est plutôt performant...
Marsh Posté le 12-06-2010 à 21:15:48
Bonjour, j'ai besoin d'aide pour réaliser un script permettant de traiter des fichiers CSV. Je précise que je suis un noob en perl
En gros mon script récupère un fichier .csv dont la première ligne sert de header.
Il vérifie que pour chaque ligne:
certains champs sont remplis sinon il les (chaque ligne) envoie vers un fichier erreur.txt,
remplace le contenu d'un champs si il rencontre une certaine valeur dans ce champs.
Plus d'autres traitements que je détaillerais si besoin.
Je rencontre plusieurs problèmes:
Le premier: je souhaite récupérer le fichier .csv à l'aide de Tie::Handle::CSV. Est ce un bon choix où vous me conseillez autre chose?
En voulant récupérer le fichier csv je rencontre une erreur: chacune des lignes se termine par ;; apparemment Tie::Handle::CSV kiffe pas trop et du coup s'arrête à la fin de la première ligne.
J'aimerais supprimer les ;; à la fin de chaque ligne mais je vois pas comment faire vu que le problème se situe au moment ou je récupère le fichier.
Si je fais:
Évidemment ça ne marche pas.
Toute aide est la bienvenue
Message édité par grao le 12-06-2010 à 21:38:57
---------------
Recherche affiche de GITS Arise 3 et 4, faire offre.