A voir également:
- Suppression de chaînes dans un fichier
- Forcer suppression fichier - Guide
- Fichier rar - Guide
- Comment ouvrir un fichier epub ? - Guide
- Comment réduire la taille d'un fichier - Guide
- Ouvrir un fichier .bin - Guide
7 réponses
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
28 avril 2021 à 17:54
28 avril 2021 à 17:54
Salut Telnaz,
Je ne connais pas bien Visual Studio. L'erreur que tu as est un arrêt du programme en cours d'exécution causé par une assertion fausse dans le code de débogage de la bibliothèque standard pour fclose(). Si tu lances l'exécutable dans l'environnement VS en mode debug, VS ne t'indique-t-il pas l'instruction dans ton code à l'origine de cet arrêt du programme ?
Ton message est un peu lapidaire... Il n'y a pas non plus d'indication d'un numéro de ligne dans le fichier File: f:\dd\vctools\crt_bld\self_x86\crt\src\fclose.c ? Si oui, la ligne permettrait de vérifier l'assertion qui a échoué.
Dans le code que tu postes, tu ne vérifies pas si
Il est possible qu'en mode debug VS arrête l'exécution si une assertion vérifiant que le pointeur passé n'est pas NULL échoue. C'est une hypothèse. Si cette hypothèse est exacte, tu devrais déterminer pourquoi l'ouverture en écriture échoue.
De façon générale, tu ne devrais utiliser fclose() que sur un fichier pour lequel tu as pu vérifier qu'il a été ouvert correctement avec fopen()
Dal
Je ne connais pas bien Visual Studio. L'erreur que tu as est un arrêt du programme en cours d'exécution causé par une assertion fausse dans le code de débogage de la bibliothèque standard pour fclose(). Si tu lances l'exécutable dans l'environnement VS en mode debug, VS ne t'indique-t-il pas l'instruction dans ton code à l'origine de cet arrêt du programme ?
Ton message est un peu lapidaire... Il n'y a pas non plus d'indication d'un numéro de ligne dans le fichier File: f:\dd\vctools\crt_bld\self_x86\crt\src\fclose.c ? Si oui, la ligne permettrait de vérifier l'assertion qui a échoué.
Dans le code que tu postes, tu ne vérifies pas si
fichier3 =fopen("contenufichier.txt","w+");retourne un pointeur ou NULL.
Il est possible qu'en mode debug VS arrête l'exécution si une assertion vérifiant que le pointeur passé n'est pas NULL échoue. C'est une hypothèse. Si cette hypothèse est exacte, tu devrais déterminer pourquoi l'ouverture en écriture échoue.
De façon générale, tu ne devrais utiliser fclose() que sur un fichier pour lequel tu as pu vérifier qu'il a été ouvert correctement avec fopen()
Dal
Phil_1857
Messages postés
1872
Date d'inscription
lundi 23 mars 2020
Statut
Membre
Dernière intervention
28 février 2024
168
Modifié le 29 avril 2021 à 11:02
Modifié le 29 avril 2021 à 11:02
Bonjour,
ta fonction est récursive, donc tu ouvres et ferme plusieurs fichiers fichier1 et fichier2
pas terrible !
Il faut juste l'utiliser pour lister les fichiers et répertoires et ensuite, dans le programme principal,
exploiter le résultat obtenu dans fichier
(mais ça, on te l'a déjà dit, non ?) :-)
Perso, j'ai testé la fonction sans les ajouts que tu as faits et ca marche très bien ...
ta fonction est récursive, donc tu ouvres et ferme plusieurs fichiers fichier1 et fichier2
pas terrible !
Il faut juste l'utiliser pour lister les fichiers et répertoires et ensuite, dans le programme principal,
exploiter le résultat obtenu dans fichier
(mais ça, on te l'a déjà dit, non ?) :-)
Perso, j'ai testé la fonction sans les ajouts que tu as faits et ca marche très bien ...
Phil_1857
Messages postés
1872
Date d'inscription
lundi 23 mars 2020
Statut
Membre
Dernière intervention
28 février 2024
168
Modifié le 29 avril 2021 à 11:20
Modifié le 29 avril 2021 à 11:20
En fait, ce que je veux dire, c'est que tu doit enlever de la fonction l'ouverture des fichiers fichier1 et fichier2, et que tu la laisse juste écrire dans fichier dont tu as passé le pointeur comme paramètre de la fonction
Ensuite, après l'éxecution de la fonction, une fois revenu dans le main(), tu ouvres fichier et tu analyses son contenu pour faire ce que tu voulais faire avec fichier1 et fichier2, c'est à dire ouvrir les fichiers trouvés
Ensuite, après l'éxecution de la fonction, une fois revenu dans le main(), tu ouvres fichier et tu analyses son contenu pour faire ce que tu voulais faire avec fichier1 et fichier2, c'est à dire ouvrir les fichiers trouvés
Phil_1857
Messages postés
1872
Date d'inscription
lundi 23 mars 2020
Statut
Membre
Dernière intervention
28 février 2024
168
29 avril 2021 à 13:12
29 avril 2021 à 13:12
OK, en gros, ça se présente comme ça:
void recursively_list_files(FILE * fichier, char * dir) { ....... ..... fprintf(fichier, "fichier : %s\n", fd.cFileName); ...... } int main(void) { FILE * fichier; fichier = fopen("donnees.txt", "r+"); recursively_list_files(fichier, "C:\\Drivers"); /* fichier est ouvert, on remet le pointeur au début et on y lit les nom de fichiers trouvés pour les ouvrir */ rewind(fichier); while(fgets(ligne, 252, fichier != NULL) { ...... .... } fclose(fichier); return 0; }
Voici ce que je propose :
main.c :
fonction.c :
Je n'ai aucune idée de comment supprimer le code pour l'instant, je n'ai écrit que la condition.
Je ne sais pas si c'est juste, cette condition doit être la suivante : Si la ligne comprends <A>, alors supprimer le code, tant que la prochaine ligne ne contient pas </A>
main.c :
if (hEditJ !=0 && hEditI !=0) { FILE* fichier; fichier=fopen("donnees.txt", "w+"); recursively_list_files(fichier, bufferCheminSousDossier); AnalyseFichier() ; fclose(fichier); }
fonction.c :
//Analayse du fichier int AnalyseFichier() { FILE* fichier2 = NULL; char buf[255]; fichier2 =fopen("C:\\Users\\cTelnaz\\Documents\\Visual Studio 2010\\Projects\\Projet-c\\Projet-c\\donnees.txt","r"); if (fichier2 == NULL) { MessageBox(NULL, "Le fichier n'a pas pu être ouvert", "Erreur", MB_ICONWARNING | MB_OK); exit(1); } else { do { //Recherche de la chaîne comprise dans les balises if (fgets(buf, sizeof buf, fichier2) != NULL) { fichier2 = fopen(buf,"r"); if (strstr(buf, "<A>")) { do { //suppresion du code } while (strstr(buf, "</A>")); } } else { MessageBox(NULL, "Plus de lignes à lire", "Stop", MB_ICONWARNING | MB_OK); } } while (buf != 0); fclose(fichier2); } }
Je n'ai aucune idée de comment supprimer le code pour l'instant, je n'ai écrit que la condition.
Je ne sais pas si c'est juste, cette condition doit être la suivante : Si la ligne comprends <A>, alors supprimer le code, tant que la prochaine ligne ne contient pas </A>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
Telnaz
Modifié le 29 avril 2021 à 15:10
Modifié le 29 avril 2021 à 15:10
il y a quoi dans donnees.txt ?
s'il y a la liste des path vers les fichiers :
Tu peux faire une fonction de traitement, mais à ce moment, autant l'appeler directement lorsque tu trouves le fichier, non ?
Ensuite, pour vérifier la présence de "<A>" ou "</A>" qui sont des successions de caractères.
C'est possible de le faire avec fgetc mais ce n'est pas le plus efficace, car les appels système seront nombreux (et sont lents).
Si tu utilises fgets() tu auras à chaque appel une ligne du fichier texte, mais rien ne te garantit que le contenu de la balise n'est pas à cheval sur plusieurs lignes, ni même que la balise elle-même ne soit pas à cheval sur plusieurs lignes. Dans un autre ordre d'idées : ta balise peut aussi être "< A>" ou "<a >" ou "< A >" etc., et, peut-être que ton programme doit les considérer comme valides. En tout cas, tu devrais te poser la question de savoir si un strstr() sur une balise "<A>" suffit.
Tu peux alternativement lire tout le fichier et le stocker en mémoire sous la forme d'une chaîne C dans laquelle se trouvent toutes les lignes du fichier et ensuite traiter le contenu de la chaîne.
J'imagine que tu fais ce travail dans le cadre d'un devoir scolaire.
Si c'est le cas, tu dois réfléchir à ton algorithme.
Si ce n'est pas le cas, il y a des moyens plus habituels de faire ce genre de travail avec des regex POSIX (voir la page de manuel
s'il y a la liste des path vers les fichiers :
- tu ne recherches pas dans le contenu des fichiers, mais juste dans le contenu de donnees.txt
- tu te compliques encore la vie, car tu avais déjà ces informations (les path vers les fichiers) dans la fonction récursive
Tu peux faire une fonction de traitement, mais à ce moment, autant l'appeler directement lorsque tu trouves le fichier, non ?
Ensuite, pour vérifier la présence de "<A>" ou "</A>" qui sont des successions de caractères.
C'est possible de le faire avec fgetc mais ce n'est pas le plus efficace, car les appels système seront nombreux (et sont lents).
Si tu utilises fgets() tu auras à chaque appel une ligne du fichier texte, mais rien ne te garantit que le contenu de la balise n'est pas à cheval sur plusieurs lignes, ni même que la balise elle-même ne soit pas à cheval sur plusieurs lignes. Dans un autre ordre d'idées : ta balise peut aussi être "< A>" ou "<a >" ou "< A >" etc., et, peut-être que ton programme doit les considérer comme valides. En tout cas, tu devrais te poser la question de savoir si un strstr() sur une balise "<A>" suffit.
Tu peux alternativement lire tout le fichier et le stocker en mémoire sous la forme d'une chaîne C dans laquelle se trouvent toutes les lignes du fichier et ensuite traiter le contenu de la chaîne.
J'imagine que tu fais ce travail dans le cadre d'un devoir scolaire.
Si c'est le cas, tu dois réfléchir à ton algorithme.
Si ce n'est pas le cas, il y a des moyens plus habituels de faire ce genre de travail avec des regex POSIX (voir la page de manuel
man 3 regex) ou avec la bibliothèque C PRCE http://www.pcre.org/. Peut-être que Microsoft a un moteur de regex aussi dans son API. Tu pourrais chercher aussi de ce côté.
Telnaz
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
29 avril 2021 à 15:18
29 avril 2021 à 15:18
Dans donnees.txt, il y a effectivement les path de tous les fichiers, c'est pour cela que je me sers de lui pour lire ligne par ligne, et ainsi ouvrir les fichiers via ces lignes.
Au niveau des balises, chaque balise doit être clairement écrite, <A>, de plus, une balise est situé sur une ligne où il n'y a aucun autre caractère. C'est pour cela que j'ai pensais que strstr suffirait.
Au niveau du travail, je suis apprenti dans une entreprise et travail sur un projet assez lourd. Cette partie du projet n'est pas l'objectif, mais permet une amélioration au niveau dialogue informatique. C'est pour cela que cette activité ne rentre pas dans mes compétences de programmation, je n'avais jamais codé en C.
Je te remercie donc encore pour ton aide
Au niveau des balises, chaque balise doit être clairement écrite, <A>, de plus, une balise est situé sur une ligne où il n'y a aucun autre caractère. C'est pour cela que j'ai pensais que strstr suffirait.
Au niveau du travail, je suis apprenti dans une entreprise et travail sur un projet assez lourd. Cette partie du projet n'est pas l'objectif, mais permet une amélioration au niveau dialogue informatique. C'est pour cela que cette activité ne rentre pas dans mes compétences de programmation, je n'avais jamais codé en C.
Je te remercie donc encore pour ton aide
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
Telnaz
29 avril 2021 à 15:25
29 avril 2021 à 15:25
OK, peux-tu donner un exemple de fichier texte avec des balises et le résultat que tu veux obtenir une fois le traitement effectué ?
Telnaz
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
29 avril 2021 à 15:33
29 avril 2021 à 15:33
Vu la confidentialité, je vais continuer d'appeler ces balises <A>, </A> et <B>, </B> par exemple.
Tout simplement, le fichier se présente comme ceci :
Plusieurs fichiers se présente comme ceci, dont certains n'ont même pas de balises ...
L'objectif est que, par exemple, si j'appuie sur le bouton A (cette condition est le "if" dans le main.c), je veux supprimer le code contenu dans les balises <B> </B> (ainsi que les caractères <B> et </B>, si impossible, ce ne sera pas grave). Ainsi, j'aurais donc les fichiers contenant uniquement le code de base (sans les balises) et le code compris dans les balises <A> et </A>.
Tout simplement, le fichier se présente comme ceci :
Début du code : code code code code code code code code code code code code code code code code code <A> code code code code code code code code code code code code code code code code </A> code code code code code code code code code code code code code code code code code code code code <B> code code code code code code code code </B>
Plusieurs fichiers se présente comme ceci, dont certains n'ont même pas de balises ...
L'objectif est que, par exemple, si j'appuie sur le bouton A (cette condition est le "if" dans le main.c), je veux supprimer le code contenu dans les balises <B> </B> (ainsi que les caractères <B> et </B>, si impossible, ce ne sera pas grave). Ainsi, j'aurais donc les fichiers contenant uniquement le code de base (sans les balises) et le code compris dans les balises <A> et </A>.
Vous n’avez pas trouvé la réponse que vous recherchez ?
Posez votre question
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
Modifié le 30 avril 2021 à 12:33
Modifié le 30 avril 2021 à 12:33
Salut Telnaz,
Ton code est difficile à lire sur le forum avec tous les espaces que tu mets verticalement (sauts de lignes) alors que tu en mets très peu horizontalement, voire pas du tout (indentation très approximative).
Une présentation et indentation rigoureuse va t'aider à visualiser ce que fait ton code.
Tu n'as visiblement pas testé ton code qui comporte de nombreuses erreurs.
Tu devrais mettre au point une fonction à laquelle tu passes le chemin absolu du fichier à traiter, avec la balise à supprimer. Ainsi, cette fonction te servira que tu souhaites supprimer la balise A ou la balise B et elle va simplifier la fonction AnalyseFichier() que tu pourrais d'ailleurs appeler traitement_resultats_recherche() car cette fonction ne se contente pas d'analyser.
La fonction qui traite individuellement un fichier pourrait s'appeler supprimer_balise() et susceptible d'être appelée comme ceci :
Teste cette fonction avec UN fichier de test.
N'implémente les étapes :
10. j'efface le fichier "fichier.ext"
11. je renomme le fichier "fichier.ext.new" en "fichier.ext"
que lorsque tu auras testé extensivement la fonction dans tous ses cas possibles d'exécution
Bien sûr, les noms "fichier.ext" et "fichier.ext.new" sont des exemples à adapter.
Lorsque tu seras satisfait du fonctionnement de ta fonction, intègre la dans ton code (tu pourrais même en faire un module).
Ton code est difficile à lire sur le forum avec tous les espaces que tu mets verticalement (sauts de lignes) alors que tu en mets très peu horizontalement, voire pas du tout (indentation très approximative).
Une présentation et indentation rigoureuse va t'aider à visualiser ce que fait ton code.
Tu n'as visiblement pas testé ton code qui comporte de nombreuses erreurs.
Tu devrais mettre au point une fonction à laquelle tu passes le chemin absolu du fichier à traiter, avec la balise à supprimer. Ainsi, cette fonction te servira que tu souhaites supprimer la balise A ou la balise B et elle va simplifier la fonction AnalyseFichier() que tu pourrais d'ailleurs appeler traitement_resultats_recherche() car cette fonction ne se contente pas d'analyser.
La fonction qui traite individuellement un fichier pourrait s'appeler supprimer_balise() et susceptible d'être appelée comme ceci :
#include <stdio.h> #include <stdlib.h> #include <string.h> /* modifier la valeur de NOM_BALISE_A et NOM_BALISE_B avec les vraies balises */ #define NOM_BALISE_A "A" #define NOM_BALISE_B "B" #define BALISE_A_DEBUT "<" NOM_BALISE_A ">\n" #define BALISE_A_FIN "</" NOM_BALISE_A ">\n" #define BALISE_B_DEBUT "<" NOM_BALISE_B ">\n" #define BALISE_B_FIN "</" NOM_BALISE_B ">\n" #define EXT_NEW ".new" void supprimer_balise(char * pathfich, char * balise_debut, char * balise_fin) { } int main(void) { /* simulation d'une ligne récupérée par fgets() contenant un chemin absolu vers un fichier à traiter avec un \n en fin de chaîne récupéré par fgets() */ char pathfich[] = "fichier-test.blk\n"; supprimer_balise(pathfich, BALISE_A_DEBUT, BALISE_A_FIN); return 0; }
Teste cette fonction avec UN fichier de test.
N'implémente les étapes :
10. j'efface le fichier "fichier.ext"
11. je renomme le fichier "fichier.ext.new" en "fichier.ext"
que lorsque tu auras testé extensivement la fonction dans tous ses cas possibles d'exécution
Bien sûr, les noms "fichier.ext" et "fichier.ext.new" sont des exemples à adapter.
Lorsque tu seras satisfait du fonctionnement de ta fonction, intègre la dans ton code (tu pourrais même en faire un module).
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
Modifié le 30 avril 2021 à 13:06
Modifié le 30 avril 2021 à 13:06
Quant à ta question sur ceci :
ces lignes utilisent l'opérateur ternaire
l'expression
en utilisant le type bool et l'entête stdbool.h on peut aussi écrire :
L'une ou l'autre forme sont une façon d'initialiser de façon concise des variables booléennes (ici : is_balise_debut et is_balise_fin) utiles dans l'algorithme.
Ensuite, le coeur de ton algorithme peut s'exprimer très simplement :
ou remplace 1 par true et 0 par false ci-dessus si tu utilises le type bool
int is_balise_debut = (strcmp(st, balise_debut) == 0) ? 1 : 0; int is_balise_fin = (strcmp(st, balise_fin) == 0) ? 1 : 0;
ces lignes utilisent l'opérateur ternaire
l'expression
(strcmp(st, balise_debut) == 0) ? 1 : 0évalue la condition
(strcmp(st, balise_debut) == 0)et si cette condition est vraie, l'expression retourne 1 (toute valeur autre que 0 signifie "vrai" dans l'évaluation d'une condition en C), si elle est fausse, l'expression retourne 0.
en utilisant le type bool et l'entête stdbool.h on peut aussi écrire :
bool is_balise_debut = (strcmp(st, balise_debut) == 0) ? true : false; bool is_balise_fin = (strcmp(st, balise_fin) == 0) ? true : false;
L'une ou l'autre forme sont une façon d'initialiser de façon concise des variables booléennes (ici : is_balise_debut et is_balise_fin) utiles dans l'algorithme.
Ensuite, le coeur de ton algorithme peut s'exprimer très simplement :
if (!is_balise_debut && !dans_balise) fprintf(fichnew, "%s", st); else if (is_balise_debut) dans_balise = 1; else if (is_balise_fin) dans_balise = 0;
ou remplace 1 par true et 0 par false ci-dessus si tu utilises le type bool
Telnaz
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
30 avril 2021 à 15:29
30 avril 2021 à 15:29
D'accord, je comprends mieux ...
Je m'excuse pour le code précédemment posté, l'indentation s'est mal copiée ...
J'ai donc créé un autre projet sur VS pour tester la fonction, après compilation, aucune erreur. Par contre, lors de l’exécution, j'ai l'erreur : Debug Assertion Failed !
La ligne concernée par l'erreur est la 39 sur le code posté ci-dessous.
Je m'excuse pour le code précédemment posté, l'indentation s'est mal copiée ...
J'ai donc créé un autre projet sur VS pour tester la fonction, après compilation, aucune erreur. Par contre, lors de l’exécution, j'ai l'erreur : Debug Assertion Failed !
La ligne concernée par l'erreur est la 39 sur le code posté ci-dessous.
void supprimer_balise(char * pathfich, char * balise_debut, char * balise_fin) { char st[255]; int dans_balise =0; FILE* fichier3 = NULL; FILE* file = NULL; FILE* fichnew = fopen("fichnew.txt", "w"); int is_balise_debut = (strcmp(st, balise_debut) ==0)? 1 :0; int is_balise_fin = (strcmp(st, balise_fin) ==0)? 1 :0; //On ouvre le fichier correspondant au chemin absolu (de la ligne trouvée) fichier3 = fopen(pathfich,"r"); //on cherche les lignes en boucle, du fichier conerné // on mets le résultat dans la variable st fgets(st, sizeof st, fichier3); //Ouverture d'un fichier en écriture file = fopen("fichier.blk.new", "w"); do { if (!is_balise_debut && !dans_balise) fprintf(fichnew, "%s", st); else if (is_balise_debut) dans_balise = 1; else if (is_balise_fin) dans_balise =0; } while (fgetc(fichier3) != EOF); //on ferme les fichiers fclose(fichier3); fclose(file); fclose(fichnew); } int main (void) { char pathfich[] = "C:\\Users\\cTelnaz\Documents\\test.blk\n"; supprimer_balise(pathfich, BALISE_A_DEBUT, BALISE_A_FIN); }
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
Telnaz
Modifié le 30 avril 2021 à 16:43
Modifié le 30 avril 2021 à 16:43
après compilation, aucune erreur. Par contre, lors de l’exécution
Oui, ce n'est parce que le compilateur ne va rien te signaler que tout va bien
tu as donc oublié l'étape suivante que je t'indiquais :
note que je préfère appeler le pointeur sur FILE "fichold" plutôt que "fichier3" - c'est plus parlant
Il y a peut-être d'autres problèmes, car je n'ai pas tenté d'exécuter et déboguer ta fonction.
Déboguer fait partie de la programmation. Pour limiter le nombre de problèmes, tu devrais développer en itérations courtes. A mesure que tu programmes, dès que tu as quelque chose de testable, tu le testes, et tu n'attends pas d'avoir écris une 50aine de lignes pour le faire.
A toi de jouer :-)
Oui, ce n'est parce que le compilateur ne va rien te signaler que tout va bien
- ligne 19, il y a toutes les chances que ton fopen en lecture échoue sur pathfich parce que celui-i se termine par \n (comme le ferait une ligne récupérée par fgets de ton fichier de liste de fichiers). Comme tu ne testes pas le retour de fopen pour vérifier l'ouverture, ... (tu utilises un pointeur NULL, etc.)
tu as donc oublié l'étape suivante que je t'indiquais :
/* 0. retirer le retour à la ligne de pathfich, s'il y en a un */ char * nl = strchr(pathfich, '\n'); if (nl != NULL) *nl = '\0';
- ligne 24 tu lis une ligne que tu mets dans ton tableau de char st, mais tu ne le fais pas dans la boucle, donc tu lis seulement une ligne. Au lieu de faite une boucle do / while, fait une boucle while qui exécutera le corps de la boucle que si quelque chose a été récupéré
while (fgets(st, sizeof st, fichold)) {
note que je préfère appeler le pointeur sur FILE "fichold" plutôt que "fichier3" - c'est plus parlant
- lignes 13 et 14, tu testes la valeur de st avant d'avoir lu dans le fichier. Evidemment, il n'y a rien de bon dans les variables initialisées, et tu dois mettre ces lignes dans la boucle après avoir fait cette lecture
- ligne 10 tu définis
FILE* file = NULL;
, puis tu ouvres un fichier en écriture ligne 29file = fopen("fichier.blk.new", "w");
, alors qu'en ligne 11 tu en as déjà ouvert un autreFILE* fichnew = fopen("fichnew.txt", "w");
qui est celui sur lequel tu travailles. Il y en a un de trop, supprime file, puisque tu ne mets rien dedans. - lignes 57 et 60 tu fais des fclose() sur "fichier3" et "fichnew" (note que si tu l'avais appelé "fichold" plutôt que "fichier3" tu saurais mieux identifier que "file" n'a rien à faire là). C'est mal de faire cela sans avoir au préalable vérifié que dans la séquence d'exécution de ton programme les fichiers ont bien été ouverts. Ce que tu ne fais pas, de nouveau. Fais le pour les deux. Une façon de faire est, si l'ouverture en lecture ou écriture est impossible, d'envoyer un message d'erreur et de fermer le programme avec exit(). Si tu fais cela, tu es sûr que si le programme arrive aux lignes 57 et 60, c'est que l'ouverture des deux flux sur ces fichiers s'est bien passée.
- ton indentation est mieux, mais il y a encore des ratés (lignes 55 à 58) et encore des lignes ou doubles lignes sautées un peu partout. Tu devrais adopter un style et t'y tenir. Le plus classique en C est celui de K&R, les créateurs du langage : https://fr.wikipedia.org/wiki/Style_d%27indentation.
Il y a peut-être d'autres problèmes, car je n'ai pas tenté d'exécuter et déboguer ta fonction.
Déboguer fait partie de la programmation. Pour limiter le nombre de problèmes, tu devrais développer en itérations courtes. A mesure que tu programmes, dès que tu as quelque chose de testable, tu le testes, et tu n'attends pas d'avoir écris une 50aine de lignes pour le faire.
A toi de jouer :-)
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
30 avril 2021 à 21:05
30 avril 2021 à 21:05
En parlant de tests...
En fait, je t'ai détaillé un algorithme que tu peux mettre en oeuvre avec la façon dont tu voulais traiter le problème, mais ce design ne me plaît pas trop, pour plusieurs raisons.
L'une d'elle est qu'il dépend trop, au coeur de son fonctionnement d'éléments externes au programme (les fichiers), ce qui le rend difficile à tester facilement au moyen de tests unitaires.
1.
Alors, au risque de te faire "pêter un câble", si j'étais toi, je jetterais tout le contenu actuel de la fonction, pour que cette fonction
2.
De cette façon, on isole les parties qui font des entrées / sorties, et on dispose d'une fonction traiter_retrait_balises() que l'on peut tester facilement, avec une batterie de tests unitaires que tu peux créer sur les différents types de cas de figure que doit gérer le programme, pour en vérifier le comportement, et que tu pourras rejouer automatiquement. Pour ces tests, il suffira de disposer de jeux de tests avec des chaînes fournies en entrée et des chaînes attendues en sortie, et tu pourras vérifier le comportement attendu en comparant ces chaînes. Si tes tests sont représentatifs des comportements attendus selon les différents cas, ils seront des alliés précieux :
Tu pourras alors faire ceci :
https://fr.wikipedia.org/wiki/Test_driven_development
avec les bénéfices expliqués dans cette page succincte
3.
Ce découpage présente aussi l'avantage que l'on ne va pas s'amuser à écrire un fichier nouveau, effacer, renommer, remplacer des fichiers si, en fait, il n'y a pas de balise dedans qui corresponde à ce que l'on veux supprimer...
4.
Le fonctionnement sera plus rapide, car il est plus rapide de lire un fichier d'un bloc que de le lire ligne par ligne (au prix d'une utilisation de mémoire vive supérieure).
Les avantages expliqués en 2., 3. et 4. sont non négligeables :-) mais c'est surtout le 2. qui est important.
En fait, je t'ai détaillé un algorithme que tu peux mettre en oeuvre avec la façon dont tu voulais traiter le problème, mais ce design ne me plaît pas trop, pour plusieurs raisons.
L'une d'elle est qu'il dépend trop, au coeur de son fonctionnement d'éléments externes au programme (les fichiers), ce qui le rend difficile à tester facilement au moyen de tests unitaires.
1.
Alors, au risque de te faire "pêter un câble", si j'étais toi, je jetterais tout le contenu actuel de la fonction, pour que cette fonction
void supprimer_balise(char * pathfich, char * balise_debut, char * balise_fin);, à la place, appelle les fonctions suivantes :
- une fonction
char * charger_fichier(char * pathfich);
qui charge le fichier à traiter dans sa totalité en mémoire et le met dans une chaîne de caractères C dont elle retourne l'adresse, ou NULL si le fichier ne peut être chargé - une fonction
char * traiter_retrait_balises(char * contenu, char * balise_debut, char * balise_fin);
qui contient le coeur de l'algorithme travaillant, cette fois, en mémoire et non pas avec des fgets() et fprintf(), qui renvoie l'adresse d'une nouvelle chaîne de caractères C comprenant la totalité du contenu du fichier retraité s'il y a des balises correspondantes dedans, ou NULL s'il n'y en a pas et que le fichier ne doit pas changer - une fonction
int remplacer_fichier(char * pathfich, char * contenunew);
appelée si traiter_retrait_balises() renvoie autre chose que NULL et qui s'occupe d'écrire le contenu nouveau dans un fichier temporaire, d'effacer l'ancien fichier, et de renommer le fichier temporaire, ou le renommer et déplacer (si tu ne l'as pas créé au bon emplacement déjà).
2.
De cette façon, on isole les parties qui font des entrées / sorties, et on dispose d'une fonction traiter_retrait_balises() que l'on peut tester facilement, avec une batterie de tests unitaires que tu peux créer sur les différents types de cas de figure que doit gérer le programme, pour en vérifier le comportement, et que tu pourras rejouer automatiquement. Pour ces tests, il suffira de disposer de jeux de tests avec des chaînes fournies en entrée et des chaînes attendues en sortie, et tu pourras vérifier le comportement attendu en comparant ces chaînes. Si tes tests sont représentatifs des comportements attendus selon les différents cas, ils seront des alliés précieux :
- pour prouver objectivement que ton programme fonctionne selon un comportement attendu
- pour te permettre de faire évoluer ton programme en rajoutant des comportements additionnels (avec de nouveaux tests associés), ou en améliorant le code existant, tout en ayant l'assurance que les comportements précédents continuent de fonctionner (absence de régression)
Tu pourras alors faire ceci :
https://fr.wikipedia.org/wiki/Test_driven_development
avec les bénéfices expliqués dans cette page succincte
3.
Ce découpage présente aussi l'avantage que l'on ne va pas s'amuser à écrire un fichier nouveau, effacer, renommer, remplacer des fichiers si, en fait, il n'y a pas de balise dedans qui corresponde à ce que l'on veux supprimer...
4.
Le fonctionnement sera plus rapide, car il est plus rapide de lire un fichier d'un bloc que de le lire ligne par ligne (au prix d'une utilisation de mémoire vive supérieure).
Les avantages expliqués en 2., 3. et 4. sont non négligeables :-) mais c'est surtout le 2. qui est important.
Telnaz
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
30 avril 2021 à 21:14
30 avril 2021 à 21:14
Je te remercie beaucoup pour le temps que tu donnes pour m'aider.
J'ai compris où tu voulais en venir mais je dois attendre mon retour au travail lundi pour m'y remettre. (Matériel spécifique)
Cependant, je vais relire ce que tu as envoyé pour être sûr de bien comprendre, afin d'être efficace la semaine prochaine.
Bonne soirée Dal :)
J'ai compris où tu voulais en venir mais je dois attendre mon retour au travail lundi pour m'y remettre. (Matériel spécifique)
Cependant, je vais relire ce que tu as envoyé pour être sûr de bien comprendre, afin d'être efficace la semaine prochaine.
Bonne soirée Dal :)
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
Modifié le 4 mai 2021 à 14:30
Modifié le 4 mai 2021 à 14:30
J'ajouterais à mon commentaire ci-dessus de 13:52 que tu devrais commencer par des cas plus simple de tests, déterminant le comportement de ta fonction.
Par exemple, commence par un premier cas :
1. on passe à traiter_retrait_balises() une chaîne vide. Cette fonction doit alors retourner NULL.
en termes de test, cela donne quelque chose comme :
Ce premier cas est facile à traiter. La fonction vérifie juste si contenu[0] == '\0' et si c'est le cas elle sort en renvoyant NULL.
Un test suivant pourrait être :
2. on passe à traiter_retrait_balises() une chaîne qui ne contient aucune balise recherchée. Cette fonction doit alors aussi retourner NULL.
Une fois que ton deuxième test passe, sans que le premier soit affecté, tu passes au 3ème, qui pourrait tester un cas où la balise est présente :
3. on passe à traiter_retrait_balises() une chaîne qui contient une balise recherchée. Cette fonction ne doit alors pas retourner NULL et elle doit retourner une chaîne identique à la chaîne retraitée attendue.
Cela donnerait :
Quand cela est au point, tu rajoutes un test à ta suite de tests, etc. A chaque itération de développement, la suite de tests est intégralement exécutée pour vérifier l'absence de régressions.
Dal
Par exemple, commence par un premier cas :
1. on passe à traiter_retrait_balises() une chaîne vide. Cette fonction doit alors retourner NULL.
en termes de test, cela donne quelque chose comme :
#include <assert.h> void test_trb_chaine_vide(void) { char balise_debut[] = "<A>"; char balise_fin[] = "</A>"; char contenu[] = ""; char * contenunew = traiter_retrait_balises(contenu, balise_debut, balise_fin); assert(contenunew == NULL && "test_trb_chaine_vide renvoie NULL"); } void test_suite_trb_traiter_retrait_balises(void) { test_trb_chaine_vide(); }
- lance ta suite de tests test_suite_trb_traiter_retrait_balises()
- constate que le premier test échoue (ta fonction ne prend pas encore en compte ce cas)
- développe juste ce qu'il faut dans ta fonction pour que ce test passe
- une fois que le test passe, améliore ton code en vérifiant que le test passe toujours
- passe au cas suivant en ajoutant un test suivant
Ce premier cas est facile à traiter. La fonction vérifie juste si contenu[0] == '\0' et si c'est le cas elle sort en renvoyant NULL.
Un test suivant pourrait être :
2. on passe à traiter_retrait_balises() une chaîne qui ne contient aucune balise recherchée. Cette fonction doit alors aussi retourner NULL.
Une fois que ton deuxième test passe, sans que le premier soit affecté, tu passes au 3ème, qui pourrait tester un cas où la balise est présente :
3. on passe à traiter_retrait_balises() une chaîne qui contient une balise recherchée. Cette fonction ne doit alors pas retourner NULL et elle doit retourner une chaîne identique à la chaîne retraitée attendue.
Cela donnerait :
#include <assert.h> void test_trb_chaine_vide(void) { char balise_debut[] = "<A>"; char balise_fin[] = "</A>"; char contenu[] = ""; char * contenunew = traiter_retrait_balises(contenu, balise_debut, balise_fin); assert(contenunew == NULL && "test_trb_chaine_vide doit renvoyer NULL"); } void test_trb_chaine_sans_balise_recherchee(void) { char balise_debut[] = "<A>"; char balise_fin[] = "</A>"; char contenu[] = "Ceci est un texte.\r\nIl ne comprend aucune balise recherchée\r\n\r\n<B>\r\nTexte dans B\r\n</B>\r\nC'est terminé"; char * contenunew = traiter_retrait_balises(contenu, balise_debut, balise_fin); assert(contenunew == NULL && "test_trb_chaine_sans_balise_recherchee doit renvoyer NULL"); } void test_trb_chaine_avec_une_balise_recherchee(void) { char balise_debut[] = "<B>"; char balise_fin[] = "</B>"; char contenu[] = "Ceci est un texte.\r\nIl comprend une balise recherchée\r\n\r\n<B>\r\nTexte dans B\r\n</B>\r\nC'est terminé"; char expected[] = "Ceci est un texte.\r\nIl comprend une balise recherchée\r\n\r\nC'est terminé"; char * contenunew = traiter_retrait_balises(contenu, balise_debut, balise_fin); assert(contenunew != NULL && "test_trb_chaine_avec_une_balise_recherchee ne doit pas renvoyer NULL"); assert(strcmp(expected, contenunew) == 0 && "test_trb_chaine_avec_une_balise_recherchee doit renvoyer la chaîne attendue"); free(contenunew); } void test_suite_trb_traiter_retrait_balises(void) { test_trb_chaine_vide(); test_trb_chaine_sans_balise_recherchee(); test_trb_chaine_avec_une_balise_recherchee(); } int main(void) { test_suite_trb_traiter_retrait_balises(); return 0; }
Quand cela est au point, tu rajoutes un test à ta suite de tests, etc. A chaque itération de développement, la suite de tests est intégralement exécutée pour vérifier l'absence de régressions.
Dal
Cette façon de faire est très intéressante ...
J'ai pu voir qu'au deuxième test, l'assert ne renvoie pas la valeur NULL.
Pour mieux convenir à ce que tu as dit, j'ai modifié ces lignes de codes pour allouer la bonne taille :
J'ai pu voir qu'au deuxième test, l'assert ne renvoie pas la valeur NULL.
Pour mieux convenir à ce que tu as dit, j'ai modifié ces lignes de codes pour allouer la bonne taille :
char * ligne = strtok(contenu, "\r\n"); char * contenunew; int nbrcontenunew = strlen(ligne); contenunew = (char*)malloc(sizeof(char)* (nbrcontenunew + 1));
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
Telnaz
Modifié le 4 mai 2021 à 16:06
Modifié le 4 mai 2021 à 16:06
Oui vois le lien Wikipedia que j'ai posté plus haut sur cette méthodologie.
contenunew = (char*)malloc(sizeof(char)* nbrcontenunew);
non, c'est
Note qu'il n'est pas faux de multiplier cela par
En ce qui concerne le reste de ton code, je ne sais pas où en est ta fonction, mais dans ce que tu as posté là : https://forums.commentcamarche.net/forum/affich-37193175-suppression-de-chaines-dans-un-fichier#48 je vois que :
fais un peu attention stp
contenunew = (char*)malloc(sizeof(char)* nbrcontenunew);
non, c'est
contenunew = (char*)malloc(nbrcontenunew + 1);car tu as besoin d'un byte additionnel pour le '\0' terminateur.
Note qu'il n'est pas faux de multiplier cela par
sizeof(char), mais cela ne sert à rien pour le type
charcar la norme du C garantit que ce type a une taille de un byte. Tu multiplies par 1 donc... autant ne pas écrire cela (ce ne sera pas le cas avec d'autres types de
char, bien sûr, où le sizeof a un sens, par exemple un type
int, qui peut occuper 4 à 8 bytes, mais là c'est des
char).
En ce qui concerne le reste de ton code, je ne sais pas où en est ta fonction, mais dans ce que tu as posté là : https://forums.commentcamarche.net/forum/affich-37193175-suppression-de-chaines-dans-un-fichier#48 je vois que :
- tu ne testes pas si la chaîne "contenu" passée est vide : si elle est vide, tu peux faire un
return NULL;
tout de suite en début de fonction et ton test 1. passera parce que tu auras implémenté ce comportement - ligne 22 tu fais
return contenunew;
après avoir ajouté une ligne ... pourquoi cela ? Ce n'est du tout l'algo que j'ai montré plus haut lorsqu'on s'occupait d'ajouter une ligne à un fichier - ligne 50 tu fais
free(contenunew);
alors que ... ta fonction est sensée renvoyer cette chaîne ... !
fais un peu attention stp
Telnaz
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
Modifié le 4 mai 2021 à 18:17
Modifié le 4 mai 2021 à 18:17
Effectivement pour le "free" cela était un peu bête, j'ai donc essayé de corriger mes erreurs.
Voici mon code plus propre :
code :
Ce code est juste pour montrer mon état d'avancement, j'ai ajouté un fprintf juste après le strtok, cela m'affiche des centaines de lignes avec : "Ceci est un texte". Je reprends demain pour essayer d'y remédier.
Voici mon code plus propre :
code :
char* traiter_retrait_balises(char * contenu, char * balise_debut, char * balise_fin) { char* contenunew; //Si le contenu est vide, on retourne NULL while (contenu != NULL) { int dans_balise =0; //On découpe la chaîne avec les caractères de séparation \r\n char * ligne = strtok(contenu, "\r\n"); //On compte le nombre de caractère de la chaîne int nbrcontenunew = strlen(ligne); //On alloue la mémoire contenunew =(char *)malloc(nbrcontenunew + 1); while (ligne != NULL) { int is_balise_debut = (strcmp(ligne, balise_debut) ==0)? 1 :0; int is_balise_fin = (strcmp(ligne, balise_fin) ==0)? 1 :0; //Si on est pas dans la balise et que "ligne" n'est pas une balise de début, if (!is_balise_debut && !dans_balise) { //On ajoute la ligne à la chaîne à retourner strcat(contenunew, ligne); } else if (is_balise_debut) { dans_balise = 1; return NULL; } else if (is_balise_fin) { dans_balise =0; return NULL; } /*on demande à strtok() de nous trouver la prochaine ligne et on boucle */ ligne = strtok(NULL, "\r\n"); } //Dès qu'il n'y a plus de lignes, on retourne le nouveau contenu return contenunew; } return NULL; }
Ce code est juste pour montrer mon état d'avancement, j'ai ajouté un fprintf juste après le strtok, cela m'affiche des centaines de lignes avec : "Ceci est un texte". Je reprends demain pour essayer d'y remédier.
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
Telnaz
Modifié le 4 mai 2021 à 19:26
Modifié le 4 mai 2021 à 19:26
Tu as mis tout le code de la fonction dans une boucle
si c'est une tentative de faire ceci :
"tu ne testes pas si la chaîne "contenu" passée est vide : si elle est vide, tu peux faire un return NULL; tout de suite en début de fonction et ton test 1. passera parce que tu auras implémenté ce comportement"
Pour tester si la chaîne "contenu" passée est vide, tu dois accéder au premier élément pointé par l'adresse mémoire contenue à ce pointeur, et si ce premier élément est égal au caractère terminateur de chaîne, c'est que ta chaîne est vide.
Donc, la conditions suivante, en début de ta fonction suffit à satisfaire le test 1 :
Retire ta boucle while, y compris son accolade fermante (bien sûr) et supprime le
Ensuite, dans ton code, tu arrêtes la fonction en faisant
La boucle pilotée par le while sur strtock() doit se poursuivre jusqu'à la fin de la chaîne traitée, pour que tu espères traiter toutes les lignes, non ?
Donc, retire ces deux
Edit :
Maintenant que tu postes la fonction complète je vois aussi une autre erreur qui m'avait échappée : tu fais ton malloc sur la longueur de la première ligne récupérée par strtock(), alors que le but est de disposer d'une zone mémoire au moins aussi grande que le contenu passé à la fonction pour dimensionner contenunew...
Donc, bien évidemment, tu dois faire :
et pas ce que tu fais...
while (contenu != NULL)qui se répète indéfiniment.
si c'est une tentative de faire ceci :
"tu ne testes pas si la chaîne "contenu" passée est vide : si elle est vide, tu peux faire un return NULL; tout de suite en début de fonction et ton test 1. passera parce que tu auras implémenté ce comportement"
- tu ne testes pas si le pointeur sur char pointe vers une chaîne qui est vide, mais tu testes si le pointeur sur char est initialisé avec une adresse mémoire ou pas.
- de façon inexplicable, tu le fais dans une boucle while au lieu d'un simple if
Pour tester si la chaîne "contenu" passée est vide, tu dois accéder au premier élément pointé par l'adresse mémoire contenue à ce pointeur, et si ce premier élément est égal au caractère terminateur de chaîne, c'est que ta chaîne est vide.
Donc, la conditions suivante, en début de ta fonction suffit à satisfaire le test 1 :
if (contenu[0] == '\0') return NULL;
Retire ta boucle while, y compris son accolade fermante (bien sûr) et supprime le
return NULL;que tu as mis en dernière ligne de la fonction. La dernière ligne de ta fonction doit retourner contenunew lorsqu'elle arrive à la fin de la fonction et que toutes les lignes ont été traitées.
Ensuite, dans ton code, tu arrêtes la fonction en faisant
return NULL;lorsque la balise de début est trouvée (et tu fais pareil quand la balise de fin est trouvée). De nouveau, ... pourquoi cela ? Ce n'est du tout l'algo que j'ai montré plus haut lorsqu'on s'occupait d'ajouter une ligne à un fichier.
La boucle pilotée par le while sur strtock() doit se poursuivre jusqu'à la fin de la chaîne traitée, pour que tu espères traiter toutes les lignes, non ?
Donc, retire ces deux
return NULL;.
Edit :
Maintenant que tu postes la fonction complète je vois aussi une autre erreur qui m'avait échappée : tu fais ton malloc sur la longueur de la première ligne récupérée par strtock(), alors que le but est de disposer d'une zone mémoire au moins aussi grande que le contenu passé à la fonction pour dimensionner contenunew...
Donc, bien évidemment, tu dois faire :
char * contenunew = malloc(strlen(contenu) + 1); char * ligne = strtok(contenu, "\r\n");
et pas ce que tu fais...
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
Modifié le 5 mai 2021 à 01:37
Modifié le 5 mai 2021 à 01:37
Tu ne peux pas juste te contenter d'un
Pour le 2ème test, il a une adaptation à faire de l'algorithme, pour qu'il garde trace du fait que qu'une balise attendue est présente ou pas et renvoyer NULL si elle ne l'est pas. Je te laisse la trouver :-)
Il y a aussi un autre détail à prendre en compte, c'est que strtok() va écraser les retours à la ligne consécutifs. C'est lié au fonctionnement de cette fonction, puisqu'ils sont considérés comme séparant des chaînes.
Comme tu disais dans ta spécification que les lignes vides n'avaient pas d'importance, je comprends que cela n'est pas grave pour toi.
Par contre, c'est à prendre en compte dans les tests.
Donc le 3ème pourrait être :
strcat(contenunew, ligne);. Car il te faut ajouter les retours à la ligne. D'ailleurs, à la réflexion, je pense aussi qu'un \n suffirait à la fin de chaque ligne pour le contenunew généré, car Windows s'occupera tout seul de rajouter les \r à l'écriture du fichier texte à la fin de chaque ligne de texte. Le jeu de test devrait être modifié en conséquence (pour le 3ème).
Pour le 2ème test, il a une adaptation à faire de l'algorithme, pour qu'il garde trace du fait que qu'une balise attendue est présente ou pas et renvoyer NULL si elle ne l'est pas. Je te laisse la trouver :-)
Il y a aussi un autre détail à prendre en compte, c'est que strtok() va écraser les retours à la ligne consécutifs. C'est lié au fonctionnement de cette fonction, puisqu'ils sont considérés comme séparant des chaînes.
Comme tu disais dans ta spécification que les lignes vides n'avaient pas d'importance, je comprends que cela n'est pas grave pour toi.
Par contre, c'est à prendre en compte dans les tests.
Donc le 3ème pourrait être :
void test_trb_chaine_avec_une_balise_recherchee(void) { char balise_debut[] = "<B>"; char balise_fin[] = "</B>"; char contenu[] = "Ceci est un texte.\r\nIl comprend une balise recherchée\r\n\r\n<B>\r\nTexte dans B\r\n</B>\r\nC'est terminé"; char expected[] = "Ceci est un texte.\nIl comprend une balise recherchée\nC'est terminé\n"; char * contenunew = traiter_retrait_balises(contenu, balise_debut, balise_fin); assert(contenunew != NULL && "test_trb_chaine_avec_une_balise_recherchee ne doit pas renvoyer NULL"); assert(strcmp(expected, contenunew) == 0 && "test_trb_chaine_avec_une_balise_recherchee doit renvoyer la chaîne attendue"); free(contenunew); }
Salut,
Je suis en train d'essayer de colorer mes boutons.
J'ai utilisé WM_CTLCOLORBTN, j'ai aussi vu que WM_DRAWITEM permet de dessiner un bouton mais il me paraît plus compliqué de l'utiliser.
J'ai par exemple ce bouton :
Le bouton concerné a donc pour identifiant : 4.
Au niveau du case WM_COMMAND, j'ai ceci :
En notant que bgColor est définit comme ceci en haut de page : [CODE]COLORREF bgColor = RGB(0,0,0);[/CODE]
Je ne mets pas tous le code, il y a une multitude de boutons ...
Pour finir, j'ai le case :
Cela créé bien des boutons entourés de noir, et quand j'appuie sur le bouton test, id 4, j'ai du rouge qui apparaît. Jusqu'ici tout va bien, mais lorsque que je survole les autres boutons, tout se colorient !
Je ne comprends pas pourquoi cela fait ça, la condition est pourtant faite uniquement lorsque le bouton test (id 4) est appuyé ...
Je pense que le problème est au niveau du case, il faut que celui-ci ne prennent pas tous les boutons ... J'ai donc essayé ceci :
Mais cela ne marche pas.
Je suis en train d'essayer de colorer mes boutons.
J'ai utilisé WM_CTLCOLORBTN, j'ai aussi vu que WM_DRAWITEM permet de dessiner un bouton mais il me paraît plus compliqué de l'utiliser.
J'ai par exemple ce bouton :
hBouton2 = CreateWindow(TEXT("button"), TEXT("test"), WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON, 420, 185, 150, 30, hwnd, (HMENU)4, NULL, NULL);
Le bouton concerné a donc pour identifiant : 4.
Au niveau du case WM_COMMAND, j'ai ceci :
case WM_COMMAND : switch (LOWORD((PARAM)) { case 4 : bgColor = RGB(255,0,0); SendMessage(NULL, WM_CTLCOLORBTN, (WPARAM)GetDC(hBouton2), (LPARAM)hBouton2); /*.................*/
En notant que bgColor est définit comme ceci en haut de page : [CODE]COLORREF bgColor = RGB(0,0,0);[/CODE]
Je ne mets pas tous le code, il y a une multitude de boutons ...
Pour finir, j'ai le case :
case WM_CTLCOLORBTN : return (LRESULT)CreateSolidBrush(bgColor); break;
Cela créé bien des boutons entourés de noir, et quand j'appuie sur le bouton test, id 4, j'ai du rouge qui apparaît. Jusqu'ici tout va bien, mais lorsque que je survole les autres boutons, tout se colorient !
Je ne comprends pas pourquoi cela fait ça, la condition est pourtant faite uniquement lorsque le bouton test (id 4) est appuyé ...
Je pense que le problème est au niveau du case, il faut que celui-ci ne prennent pas tous les boutons ... J'ai donc essayé ceci :
if (LOWORD(wParam) == 4) { return (LRESULT)CreateSolidBrush(bgColor); }
Mais cela ne marche pas.
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
18 mai 2021 à 11:16
18 mai 2021 à 11:16
Salut,
Je ne suis pas sûr, car je ne fais ce genre de trucs funky :-)
Cependant, il est possible que cela soit ton code dans ton switch/case où tu as
Lors qu'un événement WM_CTLCOLORBTN est reçu, tu devrais vérifier que le message concerne bien le bouton dont tu veux changer la couleur :
https://docs.microsoft.com/en-us/windows/win32/controls/wm-ctlcolorbtn
wParam
An HDC that specifies the handle to the display context for the button.
lParam
An HWND that specifies the handle to the button.
Tu dois, je pense, te servir de ces handles pour déterminer quoi faire.
Je ne suis pas sûr, car je ne fais ce genre de trucs funky :-)
Cependant, il est possible que cela soit ton code dans ton switch/case où tu as
case WM_CTLCOLORBTN :. En l'état, je pense qu'il renvoie
(LRESULT)CreateSolidBrush(bgColor)systématiquement lorsque cet événement est reçu, quel que soit le bouton concerné par l'événement.
Lors qu'un événement WM_CTLCOLORBTN est reçu, tu devrais vérifier que le message concerne bien le bouton dont tu veux changer la couleur :
https://docs.microsoft.com/en-us/windows/win32/controls/wm-ctlcolorbtn
wParam
An HDC that specifies the handle to the display context for the button.
lParam
An HWND that specifies the handle to the button.
Tu dois, je pense, te servir de ces handles pour déterminer quoi faire.
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
18 mai 2021 à 12:17
18 mai 2021 à 12:17
Un handle est de type HWND et ce n'est pas la même chose qu'un id de l'élément de la boite de dialogue, qui est de type int.
Si tu ne connais pas déjà le handle HWND du bouton qui t'intéresse, tu peux le récupérer avec GetDlgItem() en lui passant le handle vers la boite de dialogue qui contient le bouton et l'ID du bouton :
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdlgitem
Si tu ne connais pas déjà le handle HWND du bouton qui t'intéresse, tu peux le récupérer avec GetDlgItem() en lui passant le handle vers la boite de dialogue qui contient le bouton et l'ID du bouton :
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdlgitem
Telnaz
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
18 mai 2021 à 15:08
18 mai 2021 à 15:08
D'accord, par exemple je rajoute dans
Avec hBouton2 la boîte de dialogue du bouton et 4 son ID.
Mais après, j'ai du mal à comprendre comment utiliser cet handle pour créer la condition...
case WM_CTLCOLORBTN:
HWND hBtn; hBtn = GetDlgItem(hBouton2, 4); /*....*/
Avec hBouton2 la boîte de dialogue du bouton et 4 son ID.
Mais après, j'ai du mal à comprendre comment utiliser cet handle pour créer la condition...
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
1 092
>
Telnaz
Modifié le 18 mai 2021 à 19:05
Modifié le 18 mai 2021 à 19:05
Tu le compares au lParam passé à ton callback dans lequel tu dois avoir ce switch/case et qui traite les messages... selon la doc lorsque le message est de type WM_CTLCOLORBTN, lParam contient le "HWND that specifies the handle to the button".
C'est bien une une fonction du type
Je ne pense pas que tu aies besoin d'exécuter GetDlgItem là dedans. Tu vas inutilement ralentir le programme et le traitement des événements, alors que, probablement, le handle ne va pas changer et restera le même une fois l'application initialisée. Renseigne toi, et fais des tests :-)
C'est bien une une fonction du type
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)dans laquelle tu es, non ?
Je ne pense pas que tu aies besoin d'exécuter GetDlgItem là dedans. Tu vas inutilement ralentir le programme et le traitement des événements, alors que, probablement, le handle ne va pas changer et restera le même une fois l'application initialisée. Renseigne toi, et fais des tests :-)
Telnaz
>
[Dal]
Messages postés
6194
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
11 octobre 2024
25 mai 2021 à 09:56
25 mai 2021 à 09:56
J'ai tenté d'écrire uniquement :
Ici j'ai bien uniquement le bouton 2 qui est concerné, mais pour ajouter la condition aux autres boutons, je pense que ce n'est pas possible.
Il faudrait potentiellement utiliser WM_DRAWITEM ...
case WM_CTLCOLORBTN : if (lParam ==(LPARAM)hBouton2) { return (LRESULT)CreateSolidBrush(bgColor); } break;
Ici j'ai bien uniquement le bouton 2 qui est concerné, mais pour ajouter la condition aux autres boutons, je pense que ce n'est pas possible.
Il faudrait potentiellement utiliser WM_DRAWITEM ...
Modifié le 29 avril 2021 à 08:42
J'ai ajouté ce code :
Cela m'affiche le messageBox comme quoi la lecture n'est pas possible, pour chaque fichier (message en boucle) ... Je vais chercher pourquoi
ps : j'ai également ajouté la condition pour fichier3 (non écrite sur le code posté), le fichier est bien créé
EDIT : l'erreur est potentiellement du au fait que le "fd.cFileName" contient uniquement le nom du fichier, pour l'ouvrir, il faut avoir le chemin absolu non ?
J'ai donc essayé d'ajouter ceci avant l'ouverture des deux fichiers :
Mais cela n'a rien changé.
29 avril 2021 à 09:00
Modifié le 29 avril 2021 à 11:34
Non
l'erreur est potentiellement du au fait que le "fd.cFileName" contient uniquement le nom du fichier, pour l'ouvrir, il faut avoir le chemin absolu non ?
Oui
De plus, dans ton code, tu ne fermes toujours pas avec fclose() après t'être assuré que le fichier est bien ouvert que ce soit pour la lecture ou l'écriture.
La question se pose aussi de ce que tu veux vraiment faire et pourquoi, et où tu veux créer un fichier "contenufichier.txt". Là tu en crées un seul, qui sera écrasé à chaque fois.
Si ce code est temporaire (vu ton objectif) et que tu veux juste vérifier que tu accèdes bien au contenu du fichier, tu pourrais utiliser le handle du fichier en écriture où tu écris déjà tes messages de débogage au lieu d'utiliser printf
Cela donnerait quelque chose comme cela :
(code non testé, je ne suis pas sous Windows)
Avec la fonction appelante gérant l'ouverture en écriture, le passage du pointeur sur FILE fichier, et la fermeture, comme précédemment.
Je n'ai pas mis de fclose() avant les exit(), car la fermeture du programme opère normalement un flush et fermeture, mais on pourrait le faire.
Je crois aussi que tu te casses la tête en t'ajoutant des problèmes. Je ne connais pas bien VS, mais dans des posts comme celui-ci, tu trouves différentes solutions pour afficher des messages de débogage le temps de la mise au point de celui-ci :
https://stackoverflow.com/questions/3009042/how-to-view-printf-output-in-a-win32-application-on-visual-studio-2010
avec la solution proposée par Hans Passant tu peux disposer d'une console et y envoyer tes printf()
You'll need a console window. By far the easiest way to get one is to change a linker option: Project + Properties, Linker, System, SubSystem = Console. Add a main() method:
int main(){
return _tWinMain(GetModuleHandle(NULL), NULL, GetCommandLine(), SW_SHOW);
}
(non testé)
Il y a d'autres solutions proposées sur SO, dont l'envoi des messages dans le "output window" de VS.
En utilisant l'une de ces solutions, tu peux afficher des messages utiles au débogage de ton programme en cours de développement autrement qu'en affichant des boites de dialogue ou en écrivant dans un fichier.
Modifié le 29 avril 2021 à 12:30
Suite à la conversation avec Phil, j'ai créé une autre fonction, afin que mon code soit plus propre.
La voici :
J'obtiens bien mon fichier avec contenu : y.
Par contre, je suis toujours dans une boucle et donc le message "Pas de code ..." s'affiche à chaque fois.
Sinon, pour la console, j'avais bien pensé à faire des recherches, d'ailleurs je connaissais la conversation de ton lien. Malheureusement rien n'a marché, peut-être à cause de compatibilité de version je ne sais pas, je n'ai pas voulu trop perdre de temps là-dessus ... Même si effectivement, c'est un peu agaçant de ne pas pouvoir utiliser de printf.
Après avoir réussi cela, je pourrais m'attaquer à la suppression de caractères ...(j'ai peur)
29 avril 2021 à 13:28