Suppression de chaînes dans un fichier

Signaler
-
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
-
Bonjour,

J'ai déjà posté précédemment mais j'ai préféré ouvrir un autre sujet, même si cela touche le même projet.

Du coup, je rappel que je créé un de mes premiers programmes sur Visual Studio pour un projet. Je dois coder celui-ci via le langage C et l'API Windows uniquement.

J'ai réussi avec beaucoup d'aide de @Dal à lister tous mes fichiers présents dans des sous-dossiers.

A présent, mon objectif est de lire tous ces fichiers, et de les filtrer ...
Dans ces fichiers, il peut y avoir (ou non) des balises <A> et </A> puis <B> et </B>. Je veux pouvoir sélectionner uniquement le code compris dans ces balises.

Pour que ce soit plus simple, j'ai créé un bouton permettant de copier-coller le dossier concerné, ainsi, si je veux le code <B> </B>, il faut que je supprime la chaînes de caractères comprise entre les balises <A> et </A>.

Pour l'instant, j'essaie de m'entraîner avec les fopen, par exemple pour lire les caractères d'un fichier ... Car je sais que pour supprimer, c'est assez compliqué, d'après mes recherches.
Bon, voici ce que j'ai essayé de faire :


//fonction permettant de lister tous les fichiers dans leurs répertoires

void recursively_list_files(FILE* fichier, char * dir) {

	HANDLE hFind3;
	WIN32_FIND_DATA fd;
	char path[MAX_PATH + 1];
	FILE* fichier2 = NULL;
	FILE* fichier3 = NULL;
	int caractereActuel=0;


	sprintf(path, "%s\\*", dir);

	fprintf(fichier, "---- dans le repertoire %s ----\n\n", dir);

 	if ((hFind3 = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE) {

		fprintf(fichier, "Erreur FindFirstFIle sur %s\n", path);
		exit(1);
	}

	while(1) {

		if ((strcmp(".", fd.cFileName) !=0) && (strcmp("..", fd.cFileName) != 0)) {

			if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {

                /* c'est un dossier */

				sprintf(path, "%s\\%s", dir, fd.cFileName);

				recursively_list_files(fichier, path);


			} else {

				/* c'est un fichier */

				fprintf(fichier, "fichier : %s\n", fd.cFileName);


				//Ouverture du fichier trouvé

				
				fichier2 =fopen(fd.cFileName,"r");
				fichier3 =fopen("contenufichier.txt","w+");


							if (fichier2 != NULL)

							{
								do 
								
								{
									caractereActuel = fgetc(fichier2); //On lit le caractère
									fprintf(fichier3, "Contenu : %c\n", caractereActuel); //on l'affiche

								} while (caractereActuel != EOF);
							}

				fclose(fichier3);
				fclose(fichier2);
	    	}
		}
		if (!FindNextFile(hFind3, &fd))
            break;

	}



	if (GetLastError() != ERROR_NO_MORE_FILES) {

		printf("Erreur FindNextFile - dans %s\n", path);
		fprintf(fichier, "Erreur FindNextFile - dans %s\n", path);
		exit(1);
	}
	if (FindClose(hFind3) == FALSE) {
		
		fprintf(fichier, "Erreur FindClose\n");
		exit(1);
	}

}


Suite à l’exécution, j'ai l'erreur :

Debug Assertion Failed !

Programm : ...ments\Visual Studio 2010\Projects\Projet-c\Debug\Projet-c.exe
File: f:\dd\vctools\crt_bld\self_x86\crt\src\fclose.c

7 réponses

Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934
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
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
> Telnaz
Si, cela a changé quelque chose, j'ai du contenu qui s'affiche dans le fichier contenufichier.txt, des lettres une par une ... Par contre, j'ai encore le message " Le fichier n'a pas pu être ouvert" qui s'affiche, c'est parce que la boucle n'a pas le temps de se faire non ?
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 > Telnaz
Le fichier n'a pas pu être ouvert" qui s'affiche, c'est parce que la boucle n'a pas le temps de se faire non ?

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 :

void recursively_list_files(FILE* fichier, char * dir) {
    HANDLE hFind3;
    WIN32_FIND_DATA fd;
    char path[MAX_PATH + 1];
    FILE * fichier2 = NULL;
    int caractereActuel = 0;

    sprintf(path, "%s\\*", dir);

    fprintf(fichier, "---- dans le repertoire %s ----\n\n", dir);

    if ((hFind3 = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE) {
        fprintf(fichier, "Erreur FindFirstFIle sur %s\n", path);
        exit(1);
    }

    while(1) {
        if ((strcmp(".", fd.cFileName) !=0) && (strcmp("..", fd.cFileName) != 0)) {
            sprintf(path, "%s\\%s", dir, fd.cFileName);
            if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
                /* c'est un dossier */
                recursively_list_files(fichier, path);
            } else {
                /* c'est un fichier */
                fprintf(fichier, "fichier : %s\n", fd.cFileName);
                fichier2 =fopen(path,"r");
                if (fichier2 == NULL) {
                    MessageBox(hwnd, "Le fichier n'a pas pu être ouvert", "Erreur", MB_OK);
                    fprintf(fichier, "Erreur impossible d'ouvrir %s\n", path);
                    exit(1);
                } else {
                    /* fichier ouvert en lecture - inspection du contenu */
                    do {
                        caractereActuel = fgetc(fichier2); //On lit le caractère
                        fprintf(fichier, "Contenu : %c\n", caractereActuel); //on l'affiche
                    } while (caractereActuel != EOF);
                    fclose(fichier2);
                }
            }
        }
        if (!FindNextFile(hFind3, &fd))
            break;
    }

    if (GetLastError() != ERROR_NO_MORE_FILES) {
        printf("Erreur FindNextFile - dans %s\n", path);
        fprintf(fichier, "Erreur FindNextFile - dans %s\n", path);
        exit(1);
    }
    if (FindClose(hFind3) == FALSE) {
        fprintf(fichier, "Erreur FindClose\n");
        exit(1);
    }
}

(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.
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

Salut Dal, merci pour ta réponse.

Suite à la conversation avec Phil, j'ai créé une autre fonction, afin que mon code soit plus propre.
La voici :

int AnalyseFichier() 

{
 FILE* fichier2 = NULL;
 FILE* fichier3 = NULL;
 int caractereActuel=0;
  
//on ouvre le fichier contenant les chemins absolu de tous les fichiers

 fichier2 =fopen("C:\\Users\\cTelnaz\\Documents\\Visual Studio 2010\\Projects\\Projet-c\\Projet-c\\donnees.txt","r");
 fichier3 =fopen("contenufichier.txt","w+");

  if (fichier2 == NULL)
  {
   MessageBox(NULL, "Le fichier n'a pas pu être ouvert", "Erreur", MB_ICONWARNING | MB_OK);
    exit(1);

  }
  else     
  {
    do 
        
    {
     caractereActuel = fgetc(fichier2);

     if (fichier3 == NULL)
     {
       MessageBox(NULL, "Le fichier n'a pas pu être ouvert en écriture", "Erreur", MB_ICONWARNING | MB_OK);
       exit(1);
     }

     else 

     {
      //Recherche de la chaîne comprise dans les balises 
      //- > pour l'instant test avec le caractère "y"
 
      if (caractereActuel == 'y')
       
      {
       fprintf(fichier3, "Contenu : %c\n", caractereActuel);
      }
      
      else { MessageBox(NULL, "Pas de code dans les balises", "Erreur", MB_ICONWARNING | MB_OK); }   
     }
    } while (caractereActuel != EOF);
  }
 fclose(fichier3);
 fclose(fichier2);
}


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)
> Telnaz
Remarque, c'est normal vu que fgetc analyse caractère par caractère ...
> Telnaz
J'ai posté mon dernier code dans la conversation de Phil ...
Messages postés
1049
Date d'inscription
lundi 23 mars 2020
Statut
Membre
Dernière intervention
17 mai 2021
112
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 ...
Ah oui :)

Je vais donc modifier l'écriture dans le fichier, je vais faire en sorte que tous les fichiers soient écrits avec leur chemin absolu. Ainsi, je pourrais avancer, merci Phil

ps : oui la fonction marche très bien
Messages postés
1049
Date d'inscription
lundi 23 mars 2020
Statut
Membre
Dernière intervention
17 mai 2021
112
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
Oui c'est ce que j'avais compris, j'ai commencé à le faire :)
Messages postés
1049
Date d'inscription
lundi 23 mars 2020
Statut
Membre
Dernière intervention
17 mai 2021
112
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;
}
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 >
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

J'ai fait une erreur là :

6. si cette ligne est égale à "<A\\>\n" alors je note que je ne suis plus à l'intérieur de la balise, je n'écris pas cette ligne dans "fichier.ext.new" et je boucle sur 3.


c'est une comparaison avec "</A>\n" qu'il faut faire pour la balise fermante
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 >
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

L'algorithme que j'ai donné en français est, je pense, assez facile à implémenter.

Quelques conseils additionnels.

Il faut retirer ajouter un point "0. retirer le retour à la ligne à la fin du chemin absolu vers "fichier.ext" s'il y en a un"

car fgets() devrait capturer le '\n' de la fin de ligne dans ta lecture de la liste des fichiers.

Tu devras définir une variable comme
int dans_balise = 0;
avant ta boucle de lecture, que tu pourras changer selon l'état dans lequel tu seras.

Pour t'aider, tu peux aussi définir dans ta boucle, après avoir récupéré une ligne dans un tableau de char
st
:

        int is_balise_debut = (strcmp(st, balise_debut) == 0) ? 1 : 0;
        int is_balise_fin   = (strcmp(st, balise_fin) == 0)   ? 1 : 0;


et tu pourras faire des tests qui se lisent bien du type :

        if (!is_balise_debut && !dans_balise)
            fprintf(fichnew, "%s", st);
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

Très bien, je me lance dans le code !

PS : En ce qui concerne l'extension des fichiers, j'avais oublié un détail : il y a deux types d'extensions de fichiers, .blk et .tol

Si cela pose problème, je fais la procédure dans les cas/types d'extensions non ?
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 > Telnaz
Si la fonction de recherche, telle qu'elle fonctionne actuellement, retourne tous les fichiers contenus dans l'arborescence et que tu dois en fait en traiter seulement une partie, tu devras faire quelque chose.

Par exemple, avant de lancer la fonction qui s'occupe du traitement avec le path d'un fichier, tu lances une autre fonction qui s'occupe de vérifier que le fichier en question comporte une extension concernée par le traitement.

La fonction PathFindExtension() de l'API Windows pourra t'aider à localiser l'extension.

https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-pathfindextensiona
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

D'accord, j'ai essayé de reprendre toutes vos étapes, voici le code que je propose pour l'instant :



//Analayse du fichier 


int AnalyseFichier() 

{
FILE* fichier2 = NULL;
FILE* fichier3 = NULL;
FILE* file = NULL;

char buf[255];
char st[255];
int dans_balise =0;
  
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 
        
 {
 

  //on cherche les lignes du fichier2

  if (fgets(buf, sizeof buf, fichier2) != NULL)
       
  {
   //int is_balise_debut =(strcmp(st, balise_debut) ==0) ? 1 : 0;
   //int is_balise_fin =(strcmp(st, balise_fin) ==0) ? 1 : 0;

      
   //Si le fichier a pour extension .blk ou .tol

   if (PathFindExtension(buf) == ".blk" || PathFindExtension(buf) == ".tol")
   {


    //On ouvre le fichier correspondant au chemin absolu (de la ligne trouvée)

    fichier3 = fopen(buf,"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.ext.new", "w");

    //condition : si <A> n'existe pas dans st

     do
     {
      if (strcmp("<A>\n", st) ==0)

      {
       //je ne suis pas à l'intérieur de la balise
       //on écrit la ligne dans le nouveau fichier
       fputs(st, file);
         
      }

      else if (strcmp("<A>\n", st) !=0)

      {
       //Je suis à l'intérieur de la balise
       //on n'écris pas cette ligne dans le fichier
       continue;
          
      }

      else if (strcmp("<//A>\n", st) !=0)

      {
       //je ne suis plus à l'intérieur de la balise
       //je n'écris pas cette ligne
       continue;
      }

     } while (fgetc(fichier3) != EOF);

     //on ferme les fichiers

     fclose(fichier3);
     fclose(file);

     //on efface le fichier

     remove("fichier3");

     //on renomme le fichier créé

     rename("fichier.ext.new", "fichier.ext");
    }
    }

   else 
    {
    MessageBox(NULL, "Plus de lignes à lire", "Stop", MB_ICONWARNING | MB_OK);
    }
     
  } while (buf != 0);

  fclose(fichier2);
}
 
}


J'ai mis en commentaire le code pour les balises (debut/fin) car je n'ai pour l'instant pas trop compris ce que vous proposiez de faire dans votre avant-dernier post. J'y travaille dès maintenant
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934
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 :

#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).
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 >
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

oh, et sous Windows, probablement, il est préférable que strtock() rajoute \r à \n en raison de la façon particulière dont les retours à la lignes sont faits. Donc tu devrais probablement passer
"\r\n"
en 2ème argument des deux appels à strtock()
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

Ok, pour l'instant, j'ai testé cela :



char* traiter_retrait_balises(char * contenu, char * balise_debut, char * balise_fin)

{ 
  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");

  while (ligne != NULL) {

   int is_balise_debut = (strcmp(contenu, balise_debut) ==0)? 1 :0;
         int is_balise_fin = (strcmp(contenu, balise_fin) ==0)? 1 :0;

   if (!is_balise_debut && !dans_balise)

   {
    return contenu;
   }

   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");

  }
}

//////////////////////////////////////////////////////////////////////////////////////////////////

int main(void) 

{ 

 char * contenu = "testestestestestest\n <A> \n testtesttest </A> testestestest";
 char * contenunew;

 FILE* fichiertest;
 fichiertest = fopen("fichier.txt", "w+");

 contenunew = traiter_retrait_balises(contenu, BALISE_A_DEBUT, BALISE_A_FIN);
 fprintf(fichiertest, "%s", contenunew);

 fclose(fichiertest);
 free(contenu);
 free(contenunew);
 
}



Cela m'affiche l'erreur :

Unhandled exception at 0x5a883b60 (msvcr100d.dll) in test-balise.exe

Access violation writing location ....
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 > Telnaz
Tu appelles traiter_retrait_balises() avec une chaîne de test, je suppose, cela serait bien que tu me dises laquelle et avec quels paramètres de balises de début et fin.

Sinon (sans avoir testé moi même ton code), je vois les différents problèmes suivants :
  • ta fonction doit renvoyer un char * alloué par la fonction, avec une taille suffisante (au moins égale à longueur de la chaîne contenu + 1), où sont présentes les lignes résultant du retraitement (ou NULL s'il n'y a pas de changements) - or : à aucun moment tu alloues de la mémoire, ta fonction ne retourne rien explicitement, et elle ne compose pas la nouvelle chaîne ... du coup l'exécution de la fonction appelante qui repose sur le pointeur sur char retourné par cette fonction ne peux pas faire quoi que ce soit de bon
  • en lignes 14 et 15, tu fais les tests ternaires sur "contenu", alors que c'est sur la "ligne" que tu dois les faire, car tu cherches à déterminer ce que contient la ligne que tu viens d'identifier au sein de "contenu"
  • en ligne 20, si tu n'est pas dans la balise et que "ligne" n'est pas une balise de début, tu dois ajouter cette ligne à la chaîne à retourner (utilise
    strcat()
    )
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

Voici le code modifié d'après ce que tu as ajouté :

char* traiter_retrait_balises(char * contenu, char * balise_debut, char * balise_fin)

{ 
  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");
  char* contenunew;
  contenunew =(char*)malloc(sizeof(char)+ 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;

   if (!is_balise_debut && !dans_balise)

   {

    strcat(contenunew, ligne);
    return contenunew;

   }

   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");

  }
   free(contenunew);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int main(void) 

{ 

 char contenu[] = "testestestestestest\r\n<AA>\r\n testtesttest\r\n</AA>\r\ntestestestest";
 char * contenunew;

 FILE* fichiertest;
 fichiertest = fopen("fichier.txt", "w+");

 contenunew = traiter_retrait_balises(contenu, BALISE_AA_DEBUT, BALISE_AA_FIN);
 fprintf(fichiertest, "%s", contenunew);

 fclose(fichiertest);
 free(contenu);
 free(contenunew);
 
}


En ajoutant que les balises sont définies comme ceci :

#define A "A"
#define BALISE_A_DEBUT "<" A ">"
#define BALISE_A_DEBUT "</" A ">"


J'ai toujours la même erreur, ce qui m'embête, c'est que je ne comprends pas l'erreur ... Car j'aimerais essayer de résoudre le problème moi-même mais là ... même après recherche sur internet je ne comprends pas.

Tu appelles traiter_retrait_balises() avec une chaîne de test, je suppose, cela serait bien que tu me dises laquelle

La chaîne de test est nommée "contenu" dans la fonction main.

EDIT : je viens de voir ton message suivant, j'ai donc modifié ma chaîne de test ... L'erreur se situe maintenant sur l'allocation de mémoire, je vais chercher pourquoi.
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 >
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

le fait de déclarer ton contenu de test comme ceci :

char * contenu = "testestestestestest\n <A> \n testtesttest </A> testestestest";


va aussi provoquer un plantage, car tu déclares une chaîne littérale, que ton programme n'est pas sensé modifier, or strtok() va découper cette chaîne en sous-chaînes en remplaçant tous les caractères '\r' ou '\n' qu'elle trouve pas des '\0' terminateurs des sous-chaînes.

Il faut donc que la chaîne de test (comme dans ton cas réel), soit modifiable.

par ailleurs :
  • tes retours à la ligne sont mal faits car Windows retourne à la ligne en faisant \r\n dans un fichier texte Windows
  • tu as mis des caractères d'espacement avant et après les balises, qui ne sont pas sensés être dans les lignes contenant les balises selon ta spécification


ta chaîne test de contenu devrait donc être :

char contenu[] = "testestestestestest\r\n<A>\r\ntesttesttest\r\n</A>\r\ntestestestest";


Je ne sais pas ce que tu passes en guise de BALISE_A_DEBUT et BALISE_A_FIN car tu ne le montres pas.

Mes autres observations ci-dessus restent d'actualité : https://forums.commentcamarche.net/forum/affich-37193175-suppression-de-chaines-dans-un-fichier#47
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934
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 :

#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
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

Oui effectivement c'est parce que je lance le programme en tant qu'utilisateur dans le répertoire C:
De toute façon, le chemin prévu n'est pas encore configuré.

J'ai une question par rapport aux balises. Je suis en train de configurer le code main.c lors de l'appuie des boutons A et B par exemple.

J'avais définis les balises comme ceci :

#define  A    "A"
#define  B    "B"
#define BALISE_A_DEBUT  "<"  A ">\n"
#define BALISE_A_FIN    "</" A ">\n"
#define BALISE_B_DEBUT  "<"  B ">\n"
#define BALISE_B_FIN    "</" B ">\n"


Et donc après je dois appeler supprimer_balise avec par exemple BALISE_A_DEBUT et BALISE_A_FIN ? Car cela ne fonctionne pas, j'ai dû définir : balise_debut[] = "<A>";
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 > Telnaz
j'avais proposé cette forme lorsque tu recherchais dans le fichier chaque ligne avec fgets() et que la ligne contenant la balise était capturée avec le caractère de fin de ligne.

Là ce n'est plus le cas, tu peux donc retirer les \n en fin de macro
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

Très bien. Maintenant que tout est installé sur le projet, je vais retourner sur le module pour étudier les cas où il y a des balises dans d'autres balises (par exemple on veut supprimer le code entre <A> </A> et garder <B> </B> et que le code est du type : <A> ...... <B> .... </B> .... <A>.

Je vais également regarder pour le cas où il y a une balise ouvrante et non fermante comme tu en avait parlé.

D'ailleurs, pour ceci, je pense qu'il faut ajouter la condition que si, après avoir trouvé une ligne contenant is_balise_debut, on atteint la fin du fichier, alors ... erreur

Par exemple dans la fonction retrait_balise :

/*...*/

      ligne = strtok(NULL, "\r\n");
}
if (dans_balise == 1 && strcmp(ligne, "EOF")==0)
{
      printf("Une erreur de balisage a été détecté");
}

/*...*/
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 > Telnaz
je ne comprends pas
strcmp(ligne, "EOF")==0
(qui vérifie si une ligne comporte la chaîne de caractères "EOF")

on cherche à déterminer si on se trouve dans la situation suivante :
  • on a trouvé une balise ouvrante
  • on a épuisé les lignes dans le contenu à traiter et il n'y a pas de balise fermante


on se trouve dans cette situation si on sort du while et que
dans_balise == 1
, c'est tout... il me semble :-)

après, s'il y a une erreur d'absence de baliser fermante, tu devra décider comment traiter l'erreur

si tu produis toujours un fichier avec le contenu de l'arborescence, tu pourrais t'en servir pour stocker le résultat du traitement, indiquer si le fichier a été modifié ou pas, s'il y a eu des erreurs ou pas et lesquelles, etc.

tu peux en plus émettre des messages d'avertissements, stopper le programme ou pas, etc.
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

Ok, je te remercie énormément pour ton aide. Tu m'a permis de comprendre, évoluer et avancer correctement dans le projet. Aujourd’hui, on peut dire que l'objectif est atteint, il faut seulement que j'optimise certaines choses ...

Je te souhaite un bon wee-kend (si tu fais le pont).
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 :

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.
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934
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
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.
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 >
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

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
>
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021

D'accord, par exemple je rajoute dans
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...
Messages postés
5553
Date d'inscription
mercredi 15 septembre 2004
Statut
Contributeur
Dernière intervention
18 mai 2021
934 > Telnaz
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
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 :-)