Parsing config file in C++

This is a discussion on Parsing config file in C++ within the C++ Programming forums, part of the General Programming Boards category; Hi, I have a config file which has entries like Code: DYNAMICBASEDIR : /var/maildir DYNAMICTEMPDIR : {DYNAMICBASEDIR}/tmp/ USERDATAMAILFILEDIR : {DYNAMICBASEDIR}/domains/ ...

  1. #1
    Registered User
    Join Date
    Jan 2005
    Posts
    10

    Question Parsing config file in C++

    Hi,
    I have a config file which has entries like
    Code:
     DYNAMICBASEDIR : /var/maildir
     DYNAMICTEMPDIR : {DYNAMICBASEDIR}/tmp/
     USERDATAMAILFILEDIR : {DYNAMICBASEDIR}/domains/
    This is a common config file which is also being parsed by php script and perl scripts.
    I have to parse this file in C++, I can get the key value pairs like
    Code:
     key = DYNAMICTEMPDIR and value = {DYNAMICBASEDIR}/tmp/
    Now I have to further parse this key value pair and replace the variable in the curly braces by it's value i.e. /var/maildir,
    and create a hash, so that I can reference any of the values by their key.
    Appreciate if anybody can help me out with this.

    Thanks,
    Dynamo

  2. #2
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,544
    So what's the problem?

    How to find the ':' ?

    Or what to do with "DYNAMICBASEDIR" and "/var/maildir" when you've extracted them from the input string?
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  3. #3
    Registered User
    Join Date
    Jan 2005
    Posts
    10
    QUOTE=Salem]So what's the problem?

    How to find the ':' ?

    Or what to do with "DYNAMICBASEDIR" and "/var/maildir" when you've extracted them from the input string?[/QUOTE]
    I have to replace the value of DYNAMICBASEDIR inside the curly braces with /var/maildir and this has to be done recursively for all such enteries in the config file, there maybe other enteries like
    Code:
    BASEALLOWDIR = /var/maildir/allow
    USERALLOWDIR = {BASEALLOWDIR}/username
    in this have to replace the BASEALLOWDIR INSIDE THE CURLY BRACES WITH IT'S VALUE /var/maildir/allow so that the value of USERALLOWDIR = /var/maildir/allow/username.
    I am not sure how to do recursive parsing so that I get the clean values for all the variables (removing the variables inside the curly braces with their respective actual values).
    Thanks,
    Dynamo

  4. #4
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,544
    Well I might start with
    Code:
    std::map< std::string, std::string > config;
    So you can do things like
    Code:
    config["DYNAMICBASEDIR"] = "/var/maildir";
    Then something to iterate over the map looking for values containing braces, and perform the appropriate substitution.

    Of course, you need to watch out for cycles such as
    Code:
    FOO = {FOO}/bar
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  5. #5
    Registered User
    Join Date
    Jan 2005
    Posts
    10
    that's exactly what I am doing, however I am unable to iterate over a map and look for the values within the braces.
    I am trying to do like this, but it's not working
    Code:
    resultstring = "DYNAMICBASEDIR"; // after removing the curly braces and /tmp/ from {DYNAMICBASEDIR}/tmp/
    //using your map example above
    config[resultstring]; // will this work instead of config[ "DYNAMICBASEDIR"]
    or
    can you suggest some other way of doing this.
    Thanks.

  6. #6
    and the hat of wrongness Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    32,544
    Well you could post what you tried I suppose....
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.
    I support http://www.ukip.org/ as the first necessary step to a free Europe.

  7. #7
    ZuK
    ZuK is offline
    Registered User
    Join Date
    Aug 2005
    Location
    Austria
    Posts
    1,990
    Quote Originally Posted by Dynamo
    Code:
    resultstring = "DYNAMICBASEDIR"; // after removing the curly braces and /tmp/ from {DYNAMICBASEDIR}/tmp/
    //using your map example above
    config[resultstring]; // will this work instead of config[ "DYNAMICBASEDIR"]
    Shure works
    K

  8. #8
    Magically delicious LuckY's Avatar
    Join Date
    Oct 2001
    Posts
    856
    Try this:
    Code:
    //#define CONFIG_FILE "your_file_name.cfg"
    
    #include <iostream>
    #include <string>
    #include <map>
    
    #ifdef CONFIG_FILE
    #include <fstream>
    #endif
    
    using std::cout;
    using std::endl;
    using std::string;
    using std::map;
    
    ////////////////////////////////////////////////////////////////////////////////
    
    string& trim_string(string &str);
    string& trim_string(string &str,
                        const string &pre_chars,
                        const string &post_chars);
    string split_string(string &str,
                        const string &delim_chars,
                        bool trim_space = true);
    string split_string(string &str,
                        char delim_char,
                        bool trim_space = true);
    
    ////////////////////////////////////////////////////////////////////////////////
    
    
    
    int main() {
      typedef map<string, string> StringMap;
      StringMap var_map;
      
    #ifndef CONFIG_FILE
      static const int TEST_LINES = 2;
      string test_data[TEST_LINES] = {
        "BASEALLOWDIR = /var/maildir/allow",
        "USERALLOWDIR = {BASEALLOWDIR}/username"
      };
      
      for (int i = 0; i < TEST_LINES; ++i) {
        string &line = test_data[i];
    #else
      std::ifstream fin(CONFIG_FILE);
      string file_data;
      
      while (getline(fin, file_data).good()) {
        string &line = file_data;
    #endif
        string var_name = split_string(line, "=");    
        string::size_type brack = line.find("}");
        
        // no variable in the path, so just map it
        if (brack == string::npos)
          var_map[var_name] = line;
        // parse the variable name and replace it from the map data
        else {
          // take the var without the last bracket
          string replace = line.substr(0, brack);
          split_string(replace, "{");
          
          // insure the variable name already exists in the map
          StringMap::iterator itr = var_map.find(replace);
          if (itr == var_map.end())
            cout << "ERROR: \"" << replace << "\" is undefined!" << endl;
          // now map the new variable replacing the {variable}
          else {
            string path = itr->second + line.substr(brack + 1);
            var_map[var_name] = path;
          }
        }
      }
      
      // let's display everything we've mapped
      StringMap::iterator itr = var_map.begin();
      for (; itr != var_map.end(); ++itr)
        cout << "[" << itr->first << "] = " << itr->second << endl;
      
      return 0;
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    
    //
    // some of my strmanip library functions - very simple, but very
    // useful string manipulation routines
    //   http://www.rommelsantor.com
    //
    
    string& trim_string(string &str) {
      return trim_string(str, " \t\r\n", " \t\r\n");
    }
    
    string& trim_string(string &str,
                        const string &pre_chars,
                        const string &post_chars)
    {
      if (str.length() > 0) {
        string::size_type first = str.find_first_not_of(pre_chars);
        string::size_type last = str.find_last_not_of(post_chars);
        if (first == string::npos)
          str.erase();
        else
          str = str.substr(first,
            first != string::npos && last != string::npos
            ?
              last - first + 1
            :
              string::npos);
      }
      return str;  
    }
    
    string split_string(string &str,
                        const string &delim_chars,
                        bool trim_space)
    {
      string token;
    
      string::size_type n = str.find_first_of(delim_chars);
      if (n == string::npos) {
        token = str;
        str.erase();
      }
      else {
        token = str.substr(0, n);
        str = str.substr(n + 1);
      }
    
      if (trim_space) {
        trim_string(token);
        trim_string(str);
      }
    
      return token;
    }
    
    string split_string(string &str,
                        char delim_char,
                        bool trim_space)
    {
      char delim_str[2] = { delim_char, 0 };
      return split_string(str, delim_str, trim_space);
    }
    Quote Originally Posted by output
    [BASEALLOWDIR] = /var/maildir/allow
    [USERALLOWDIR] = /var/maildir/allow/username
    Last edited by LuckY; 08-31-2005 at 02:51 PM.

  9. #9
    Registered User
    Join Date
    Jan 2005
    Posts
    10
    Here is my code
    Code:
    void PMail::parsePmailConfig()   {
    
    // Local variable declaration
       string pmailNameStr;
       string pmailValueStr;
        string remainderstr;
    
    
    // The file is read line by line and we are storing
    // the buffer into strLine variable
       string strLine1;
    
    
    // open the file for reading 
       ifstream pmail(m_pmailConfigFile.c_str());
       if(!pmail) {
          utilityObj.error("Cannot open file", "PMail::parsePmailConfig() method");
       }
    
    
       while(getline(pmail, strLine1))  {
    
    // Trim the line to get rid of all space
    // chars for more efficient comparison
       utilityObj.trim(strLine1);
    
    
        string::size_type start = 0;
        string::size_type end = strLine1.find(':');
        if (end == string::npos)
           continue;
        pmailNameStr =  strLine1.substr(start,end);
    
    
    
    
        do {
        end++;  // skip the tab
        start = end;
        end = strLine1.find('\n',start);
        pmailValueStr = strLine1.substr(start,end-start);
        } while (end != string::npos);
        
        map<string, string>pvaluestr;
        pair<string, string>p1(pmailNameStr, pmailValueStr);
        pvaluestr.insert(p1);
    
        
        string::size_type start1 = pmailValueStr.find('{');
       
        if(start1 == string::npos)
        {
        continue;
        }
        else {
        
        string::size_type end1 = pmailValueStr.find('}');
        
        remainderstr = pmailValueStr.substr(start1+1, end1-1);
        string rstr;
        rstr = pmailValueStr.substr(end1+1);
         cout << "Remainder  String =" << remainderstr << endl;/ value within curly braces
         cout << "Second Part = " << rstr << endl; //remaining value after removing the cury braces
         cout << "String **=** " << pvaluestr[remainderstr] << endl; // this doesn't work, stuck here
         parsedvalue = pvaluestr[remainderstr] + rstr; 
    //have to create a map again with the new parsed values 
        }
       
          
        }
       
       
    
       } // End While Loop
    
    // Close the config file
       pmail.close();
    // Debugging 1
       utilityObj.debug(1, "Exiting PMail::parsePmailConfig() ", "");
    
    }

  10. #10
    Registered User
    Join Date
    Jan 2005
    Posts
    10
    Hi Lucky,
    Appreciate your code, however I already have a simple config file which is also being parsed by php and perl scripts, defining the file again in C++ will be double the work and if any changes have to be made in the config file, that has to be done at both the places, so to avoid this, I am trying to read and parse the config file which already exists.
    Can you look at the code that I have posted and suggest some changes.

    Thanks.

  11. #11
    ZuK
    ZuK is offline
    Registered User
    Join Date
    Aug 2005
    Location
    Austria
    Posts
    1,990
    Hi. Got a Version that reads your example configuration ok. Did some tests with multiple symbols in the values. Worked as well. But there is no code for handling loops and the code is far from beeing optimized. maybe it is useful.
    BTW there is a feature to skip lines starting with '#'. Don't know if that applies to this kind of configuration files ( put it in for testing ).
    Code:
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <map>
    
    using namespace std;
    
    map<string, string> cfg;
    
    string strip_space( const string & s ) {
       string ret(s);
       while ( ret.length() && ( ret[0]== ' ' || ret[0]== '\t' )) 
         ret = ret.substr(1);
       while ( ret.length() && ( ret[ret.length()-1]== ' ' || ret[ret.length()-1]== '\t' )) 
         ret = ret.substr(0,ret.length()-1);
       return ret;
    }
    
    int main() {
       string ifname("test.cfg");
       ifstream in(ifname.c_str());
       string line;
       cout << "\nreading configuration " << ifname << "\n";
       while ( !in.eof() ) {
          getline(in, line);
          line = strip_space(line);
          if ( line.length() && line[0] != '#' ) {
             // cout << line << endl;
    	 int pos = line.find(":");
    	 if ( pos != string::npos ) {
    	    string key = strip_space(line.substr(0,pos));
    	    string value = strip_space(line.substr(pos+1));
                //cout << "key='" << key << "' value='" << value << "'\n";
    	    cfg.insert(make_pair(key,value));
    	 }
          }
       }
       cout << "\nprinting configuration\n";
       for ( map<string, string>::const_iterator i = cfg.begin(); i != cfg.end(); ++i )
          cout << "key='" << (*i).first << "' value='" << (*i).second << "'\n";
       // resolve symbols
       cout << "\nresolving...\n";
    
       int done = false;
       while ( !done ) { // loop until no more symbols found in values
          done = true;
          for ( map<string, string>::iterator i = cfg.begin(); i != cfg.end(); ++i ) {
              string value = (*i).second;  
              string key = (*i).first;  
              unsigned p1, p2;
              if ( (p1 = value.find( "{" )) != string::npos ) {
                 done = false;
                 p2 = value.find("}");
                 if ( p2 != string::npos ) {
    		string var = value.substr(p1+1, p2-p1-1 );
    		string rep = cfg[var];
    		//cout << "resolving "<< var << " with '" << rep << "'\n";
    		value.replace(p1,p2+1,rep);
    		//cout << "key='" << (*i).first << "' value='" << value << "'\n";
    		(*i).second=value;
                 }
    	  }
          }
       }
       
       cout << "\nprinting configuration (symbols resolved)\n";
       for ( map<string, string>::const_iterator i = cfg.begin(); i != cfg.end(); ++i )
         cout << "key='" << (*i).first << "' value='" << (*i).second << "'\n";   
    }
    Kurt

  12. #12
    Magically delicious LuckY's Avatar
    Join Date
    Oct 2001
    Posts
    856
    Quote Originally Posted by Dynamo
    Hi Lucky,
    Appreciate your code, however I already have a simple config file which is also being parsed by php and perl scripts, defining the file again in C++ will be double the work
    You lost me, Dynamo. Did I somehow imply that you have to change the format of your config file? I wrote that program based on some of the sample config data you posted previously. In any case, it can be very easily modified to suit your specific needs if you're interested, but it looks like you've got a good working solution either way. So, cool.

    As far as using a pound (#) as a comment, it is quite a trivial thing. Essentially you could just replace the '#' character in a string (if found) with a null byte (for a char array) or substr() everything up to the pound (for an STL string).

  13. #13
    Registered User
    Join Date
    Jan 2005
    Posts
    10
    Hi kurt,
    Thanks !!!!!!!! Your code worked perfectly.

    Thanks again.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Formatting the contents of a text file
    By dagorsul in forum C++ Programming
    Replies: 2
    Last Post: 04-29-2008, 12:36 PM
  2. System
    By drdroid in forum C++ Programming
    Replies: 3
    Last Post: 06-28-2002, 10:12 PM
  3. Hmm....help me take a look at this: File Encryptor
    By heljy in forum C Programming
    Replies: 3
    Last Post: 03-23-2002, 09:57 AM
  4. Need a suggestion on a school project..
    By Screwz Luse in forum C Programming
    Replies: 5
    Last Post: 11-27-2001, 01:58 AM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21