Page principale | Pages associées

Un programme pour lire les fichiers .INI (à l'aide d'un map)

Je ne pouvais pas finir ce tutorial sans utiliser une classe de la STL très pratique pouvant être utilisée facilement pour associer une clef avec une valeur, j'ai nommé : std::map L'idée de ce conteneur associatif est d'avoir accès à une valeur grâce à une clef. Les seuls prérequis pour utiliser ce map sont d'avoir une clef sur laquelle est définie une notion d'ordre (disons l'opérateur <), et des valeurs ayant un constructeur par défaut disponible.

Le but de ce tutorial est de lire un fichier de paramètres formatté dans le style des fichiers .ini (enfin de ce que je m'en souviens). Le format est le suivant (sûrement simplifié) [ZONE] VALEUR1=X VALEUR2=Y

Soit des lignes contenant des [] pour définir une zone de valeur. Des lignes contenant un '=' pour affecter une valeur à une clef. J'ai fourni un petit fichier contenant des valeurs à lire (param.ini) pour tester le programme. Pour nous aider dans cette tache, on va utiliser deux fonctions pratiques:

#include <iostream> #include <fstream> #include <string> #include <map>
Le nouveau fichier contenant les maps

using namespace std; // ----------------------------------------------------------- // Suppression de caractères au début et à la fin d'une chaine string trim( const string& str, const string& tr ) { // Recherche d'un début valide string::size_type debut = str.find_first_not_of( tr );
find_first_not_of recherche à partir du début de la chaîne un caractère n'appartement pas à la chaîne passée en paramètre

if ( debut == string::npos ) return ""; // uniquement des espaces ou des tabulations // Recherche d'une fin valide string::size_type fin = str.find_last_not_of( tr );
find_last_not_of recherche à partir de la fin de la chaîne un caractère n'appartement pas à la chaîne passée en paramètre

// On revoie la chaîne trimée à gauche et à droite return str.substr( debut, fin - debut + 1 ); } // ------------------------------------------------------------ // Extraction d'une colonne d'une chaîne selon // un séparateur et un index // ex: extract( "A;B;C;D", 2, ";" ) = "C" string extract( const string& chaine, int index, char separator ) { // Recherche du début du champ int debut = -1; while( index ) { debut = chaine.find( separator, debut + 1 ); // Si on a pas trouvé de <separator>, on sort if ( debut == string::npos ) return ""; --index; // On change l'index } // Recherche de la fin du champ size_t fin = chaine.find( separator, debut + 1 ); // On n'a pas trouvé de fin de champ avant la fin if ( fin == string::npos ) fin = chaine.size(); // On prend toute la chaîne // Extraction du champ à partir de la chaîne string champ = string( chaine, debut + 1, fin - ( debut + 1 ) ); return trim( champ, " \t" ); } // ----- définitions des types utilisé ----- struct lstring : public string { lstring( const char* str ) : string( str ) {} lstring( const string& str ) : string( str ) {} lstring() : string() {} }; typedef map< lstring, lstring > tMapNameValue; typedef tMapNameValue::const_iterator tMapNameValueConstIter;
typedef définit un type tMapNameValue, qui est un map permettant d'associé une chaine avec une autre chaîne

typedef map< lstring, tMapNameValue > tMapZoneValue; typedef tMapZoneValue::const_iterator tMapZoneValueConstIter; tMapZoneValue mZoneValue;
déclaration d'un map associant une zone avec un tMapNameValue ce qui permet d'avoir plusieurs couples (nom, valeur) par zone

// ----------------------- programme principal --------------------------- int main() { // Ouverture du fichier contenant les paramètres à lire ifstream file( "param.ini" ); if ( !file ) return 1; // mais qu'avez-vous fait du fichier ? // La zone courante de lecture string zone; // par défaut, on est hors zone // Lecture du fichier string line; while( getline( file, line ) ) { // On commence par supprimer les espaces et tabulations line = trim( line, " \t" ); if ( line.empty() ) continue; // la ligne est vide, on passe à la suivante // Recherche du type de ligne if ( line[0] == '[' ) { // On a trouvé une ligne de type zone // On modifie donc la zone courante zone = trim( trim( line, "[]" ), " \t" );
On passe par deux trim pour supprimer les crochets, puis les espaces/tabulations qui pourrait y avoir dans les crochets Toutes les valeurs qui seront lues par la suite seront associées à cette nouvelle zone
} else { // On va essayer d'identifier une ligne contenant une valeur string::size_type found = line.find( '=' ); if ( found != string::npos ) {
Ok, une ligne valeur a été identifiée Le plus beau arrive.
string name = extract( line, 0, '=' ); string valeur = extract( line, 1, '=' ); // Ajout du couple (clef, valeur) dans la 'zone' mZoneValue[ zone ][ name ] = valeur;
Il y a plusieurs manière d'ajouter une clef pour une zone selon l'effet désiré. Ici, on écrasera une valeur si elle est déjà présente.
} } } // Les paramètres sont maintenant chargés, on va les afficher cout << "[ZONE_A][VALEUR_3]= " << '[' << mZoneValue[ "ZONE_A" ][ "VALEUR_3" ] << ']' << endl;
L'utilisation du map est donc simple. Si on demande une clef sur une zone qui n'existe pas le map en créera une avec une valeur vide par défaut

Affichage de toutes les clef disponibles sous forme d'arbre. Cette partie est plus délicate car elle utilise des iterateurs sur map qui ne sont pas les plus car ce sont des pairs Le but est juste d'afficher l'arbre.

for ( tMapZoneValueConstIter i = mZoneValue.begin(); i != mZoneValue.end(); ++i ) { // i est iterateur qui pointe sur une pair de l'élément courant du map // une pair contient deux élements accessible par first et second // Affichage de la zone cout << '[' << i->first << ']' << endl; // Affichage de l'ensemble des valeurs pour cette zone for ( tMapNameValueConstIter j = i->second.begin(); j != i->second.end(); ++j ) { // On peut afficher les noms/valeurs cout << "\t[" << j->first << "]=[" << j->second << ']' << endl; } } }

Ce tutorial fonctionne bien, il repose sur deux fonctions trim et extract pratiques (et j'espère non bugguées) qui permettent d'extraire assez simplement des informations d'un fichier formatté en ligne.

Voilà, je pense avoir présenté assez de choses pour que vous puissez n'être plus limité que par votre imagination. Il reste encore pas mal de conteneurs dont je n'ai pas parlé qui peuvent être utils ( std::list et std::set notamment ). Vous pouvez trouver sur le net de bonnes documentations sur la STL.

Une prochaine étape pour un tutorial serait de jetter un oeil du côté des itérateurs puis des algorithmes.

Qui sait peut être à une prochaine.

Hylvenir

Si vous détectez des erreurs, ou si vous avez des commentaires, n'hésitez pas : mail: hylvenir@libertysurf.fr


Le source complet :
#include <iostream> #include <fstream> #include <string> #include <map> using namespace std; // ----------------------------------------------------------- // Suppression de caractères au début et à la fin d'une chaine string trim( const string& str, const string& tr ) { // Recherche d'un début valide string::size_type debut = str.find_first_not_of( tr ); if ( debut == string::npos ) return ""; // uniquement des espaces ou des tabulations // Recherche d'une fin valide string::size_type fin = str.find_last_not_of( tr ); // On revoie la chaîne trimée à gauche et à droite return str.substr( debut, fin - debut + 1 ); } // ------------------------------------------------------------ // Extraction d'une colonne d'une chaîne selon // un séparateur et un index // ex: extract( "A;B;C;D", 2, ";" ) = "C" string extract( const string& chaine, int index, char separator ) { // Recherche du début du champ int debut = -1; while( index ) { debut = chaine.find( separator, debut + 1 ); // Si on a pas trouvé de <separator>, on sort if ( debut == string::npos ) return ""; --index; // On change l'index } // Recherche de la fin du champ size_t fin = chaine.find( separator, debut + 1 ); // On n'a pas trouvé de fin de champ avant la fin if ( fin == string::npos ) fin = chaine.size(); // On prend toute la chaîne // Extraction du champ à partir de la chaîne string champ = string( chaine, debut + 1, fin - ( debut + 1 ) ); return trim( champ, " \t" ); } // ----- définitions des types utilisé ----- struct lstring : public string { lstring( const char* str ) : string( str ) {} lstring( const string& str ) : string( str ) {} lstring() : string() {} }; typedef map< lstring, lstring > tMapNameValue; typedef tMapNameValue::const_iterator tMapNameValueConstIter; typedef map< lstring, tMapNameValue > tMapZoneValue; typedef tMapZoneValue::const_iterator tMapZoneValueConstIter; tMapZoneValue mZoneValue; // ----------------------- programme principal --------------------------- int main() { // Ouverture du fichier contenant les paramètres à lire ifstream file( "param.ini" ); if ( !file ) return 1; // mais qu'avez-vous fait du fichier ? // La zone courante de lecture string zone; // par défaut, on est hors zone // Lecture du fichier string line; while( getline( file, line ) ) { // On commence par supprimer les espaces et tabulations line = trim( line, " \t" ); if ( line.empty() ) continue; // la ligne est vide, on passe à la suivante // Recherche du type de ligne if ( line[0] == '[' ) { // On a trouvé une ligne de type zone // On modifie donc la zone courante zone = trim( trim( line, "[]" ), " \t" ); } else { // On va essayer d'identifier une ligne contenant une valeur string::size_type found = line.find( '=' ); if ( found != string::npos ) { string name = extract( line, 0, '=' ); string valeur = extract( line, 1, '=' ); // Ajout du couple (clef, valeur) dans la 'zone' mZoneValue[ zone ][ name ] = valeur; } } } // Les paramètres sont maintenant chargés, on va les afficher cout << "[ZONE_A][VALEUR_3]= " << '[' << mZoneValue[ "ZONE_A" ][ "VALEUR_3" ] << ']' << endl; for ( tMapZoneValueConstIter i = mZoneValue.begin(); i != mZoneValue.end(); ++i ) { // i est iterateur qui pointe sur une pair de l'élément courant du map // une pair contient deux élements accessible par first et second // Affichage de la zone cout << '[' << i->first << ']' << endl; // Affichage de l'ensemble des valeurs pour cette zone for ( tMapNameValueConstIter j = i->second.begin(); j != i->second.end(); ++j ) { // On peut afficher les noms/valeurs cout << "\t[" << j->first << "]=[" << j->second << ']' << endl; } } }
00001 #include <iostream> 00002 #include <fstream> 00003 #include <string> 00004 #include <map> 00005 using namespace std; 00006 00007 // ----------------------------------------------------------- 00008 // Suppression de caractères au début et à la fin d'une chaine 00009 string trim( const string& str, const string& tr ) 00010 { 00011 // Recherche d'un début valide 00012 string::size_type debut = str.find_first_not_of( tr ); 00013 if ( debut == string::npos ) 00014 return ""; // uniquement des espaces ou des tabulations 00015 00016 // Recherche d'une fin valide 00017 string::size_type fin = str.find_last_not_of( tr ); 00018 // On revoie la chaîne trimée à gauche et à droite 00019 return str.substr( debut, fin - debut + 1 ); 00020 } 00021 00022 // ------------------------------------------------------------ 00023 // Extraction d'une colonne d'une chaîne selon 00024 // un séparateur et un index 00025 // ex: extract( "A;B;C;D", 2, ";" ) = "C" 00026 string extract( const string& chaine, int index, char separator ) 00027 { 00028 // Recherche du début du champ 00029 int debut = -1; 00030 while( index ) { 00031 debut = chaine.find( separator, debut + 1 ); 00032 // Si on a pas trouvé de <separator>, on sort 00033 if ( debut == string::npos ) 00034 return ""; 00035 --index; // On change l'index 00036 } 00037 00038 // Recherche de la fin du champ 00039 size_t fin = chaine.find( separator, debut + 1 ); 00040 // On n'a pas trouvé de fin de champ avant la fin 00041 if ( fin == string::npos ) 00042 fin = chaine.size(); // On prend toute la chaîne 00043 00044 // Extraction du champ à partir de la chaîne 00045 string champ = string( chaine, debut + 1, fin - ( debut + 1 ) ); 00046 return trim( champ, " \t" ); 00047 } 00048 00049 // ----- définitions des types utilisé ----- 00050 struct lstring : public string { 00051 lstring( const char* str ) : string( str ) {} 00052 lstring( const string& str ) : string( str ) {} 00053 lstring() : string() {} 00054 00055 }; 00056 00057 typedef map< lstring, lstring > tMapNameValue; 00058 typedef tMapNameValue::const_iterator tMapNameValueConstIter; 00059 typedef map< lstring, tMapNameValue > tMapZoneValue; 00060 typedef tMapZoneValue::const_iterator tMapZoneValueConstIter; 00061 tMapZoneValue mZoneValue; 00062 // ----------------------- programme principal --------------------------- 00063 int main() 00064 { 00065 // Ouverture du fichier contenant les paramètres à lire 00066 ifstream file( "param.ini" ); 00067 if ( !file ) 00068 return 1; // mais qu'avez-vous fait du fichier ? 00069 00070 // La zone courante de lecture 00071 string zone; // par défaut, on est hors zone 00072 00073 // Lecture du fichier 00074 string line; 00075 while( getline( file, line ) ) 00076 { 00077 // On commence par supprimer les espaces et tabulations 00078 line = trim( line, " \t" ); 00079 if ( line.empty() ) 00080 continue; // la ligne est vide, on passe à la suivante 00081 00082 // Recherche du type de ligne 00083 if ( line[0] == '[' ) 00084 { // On a trouvé une ligne de type zone 00085 // On modifie donc la zone courante 00086 zone = trim( trim( line, "[]" ), " \t" ); 00087 } 00088 else 00089 { // On va essayer d'identifier une ligne contenant une valeur 00090 string::size_type found = line.find( '=' ); 00091 if ( found != string::npos ) 00092 { 00093 string name = extract( line, 0, '=' ); 00094 string valeur = extract( line, 1, '=' ); 00095 00096 // Ajout du couple (clef, valeur) dans la 'zone' 00097 mZoneValue[ zone ][ name ] = valeur; 00098 } 00099 } 00100 } 00101 00102 // Les paramètres sont maintenant chargés, on va les afficher 00103 cout << "[ZONE_A][VALEUR_3]= " 00104 << '[' << mZoneValue[ "ZONE_A" ][ "VALEUR_3" ] << ']' << endl; 00105 for ( tMapZoneValueConstIter i = mZoneValue.begin(); 00106 i != mZoneValue.end(); ++i ) 00107 { // i est iterateur qui pointe sur une pair de l'élément courant du map 00108 // une pair contient deux élements accessible par first et second 00109 00110 // Affichage de la zone 00111 cout << '[' << i->first << ']' << endl; 00112 00113 // Affichage de l'ensemble des valeurs pour cette zone 00114 for ( tMapNameValueConstIter j = i->second.begin(); 00115 j != i->second.end(); ++j ) 00116 { // On peut afficher les noms/valeurs 00117 cout << "\t[" << j->first << "]=[" << j->second << ']' << endl; 00118 } 00119 } 00120 }

Dernière modification : Sun Jul 4 20:19:13 2004