Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

Creating the Configuration-File Class

251

The capability to configure applications is the hallmark of a professional program. If you build in the configuration options from the start of the design (rather than hacking on some configurations at the end of the process), the result is a much more robust and extensible application. Even if you add new options later on, the basis for the code will already be there.

Creating the Configuration-File

Class

The configuration-file class encapsulates the reading, parsing, and storing of the data in the text-based configuration file. The following steps show you how to build a stand-alone class that can simply be moved from application to application, allowing you to save time and have a consistent interface.

1. In the code editor of your choice, create a new file to hold the definition for your configuration-file class.

In this example, the file is named

ConfigurationFile.h, although you can use whatever you choose.

2. Type the code from Listing 45-1 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 45-1: THE CONFIGURATION FILES HEADER FILE

#ifndef _CONFIGURATIONFILE_H_ #define _CONFIGURATIONFILE_H_

#include <string> #include <vector> #include <fstream> #include <map> #include <list>

using namespace std;

class ConfigurationFile

{

public:

ConfigurationFile(const char *strFileName);

virtual ~ConfigurationFile(void); bool read(void);

bool

hasValue( const char

*key

);

string

getValue( const

char

*key

);

void

setValue( const

char

*key, const

char *value );

protected:

virtual void get_token_and_value(); virtual char eat_white_and_comments(bool traverse_ newlines=true);

virtual bool advance_to_equal_sign_on_line(); virtual void makeLower (string &instring);

protected:

 

fstream

m_in;

string

m_token;

string

m_value;

string

m_sConfigFile;

typedef pair <string, string> String_Pair;

map<string, string> m_ConfigEntries;

};

#endif

This file contains the definition of the class; it contains no code for manipulating the data. The header file acts as the interface for other applications to use the class, as we will see. It is best to separate your actual implementation code from your definition, as this helps emphasize the encapsulation concept of C++.

3. Save the source-code file.

4. In the code editor of your choice, create a new file to hold the definition for the configurationfile class.

252 Technique 45: Creating a Configuration File

In this example, the file is named

ConfigurationFile.cpp, although you can use whatever you choose.

5. Type the code from Listing 45-2 into your file.

LISTING 45-2: THE CONFIGURATION FILE SOURCE CODE.

#include “ConfigurationFile.h” #include <errno.h>

#include <algorithm> #include <sstream> #include <iostream> #include <string>

template <class T> bool from_string(T &t,

const std::string &s, std::ios_base &

(*f)(std::ios_base&))

{

std::istringstream iss(s); return !(iss>>f>>t).fail();

}

 

 

class StringUtil

 

1

{

 

public: StringUtil() {}

~StringUtil() {}

//Find the given string in the source string and replace it with the

//“replace” string, everywhere instances of that string exist.

static void findandreplace( string& source, const string& find, const string&

replace )

{

size_t j;

for (;(j = source.find( find )) != string::npos;)

{

source.replace( j, find.length(), replace );

}

}

//The following function returns a string with all-uppercase characters.

static string makeUpper( const string& instring)

{

string temp=instring;

transform( temp.begin(), temp.end(), temp.begin(), ::toupper );

return temp;

}

//The following function returns a string with all-lowercase characters.

static string makeLower( const string& instring)

{

string temp;

transform( temp.begin(), temp.end(), temp.begin(), ::tolower );

return temp;

}

static bool contains( const string& source, const char *find )

{

return ( 0!=strstr(source. c_str(),find) );

}

static string pad( const string& instring, char padchar, int length )

{

string outstring = instring;

for ( int i=(int)outstring.length(); i<length; ++i )

outstring += padchar;

return outstring;

}

//Trim the given characters from the beginning and end of a string.

//the default is to trim whitespace. If the string is empty or contains

//only the trim characters, an empty string is returned.

static string trim( const string &instring,

const string &trimstring=string(“ \t\n”))

Creating the Configuration-File Class

253

{

if (trimstring.size()==0) return instring;

string temp=””;

string::size_type begpos=instring.find_first_not_of (trimstring); if (begpos==string::npos)

{

return temp;

}

else

{

string::size_type endpos=instring.find_last_not_of (trimstring); temp=instring.substr(begpos,

endpos-begpos+1);

}

return temp;

}

//Convert the string to an int. Note that a string exception is thrown if

//it is invalid.

static int toInt(const string & myInString)

{

int i=0;

string inString = trim(myInString);

if( !from_string<int>(i, inString, std::dec) )

{

string exceptionText = “StringUtils::toInt() - Not an integer: “ + inString; throw exceptionText;

}

// Time to run some more checks.

for (unsigned int j=0; j < inString.length(); j++)

{

if ( !isNumeric(inString[j]) )

{

if (j==0 && inString[j] ==’-’)

{

continue;

}

else

{

string exceptionText = “StringUtils::toInt() - Not an integer: “ + inString;

throw exceptionText;

}

}

}

(continued)

254 Technique 45: Creating a Configuration File

LISTING 45-2 (continued)

return (i);

}

//Convert the string to an int. Note: A string exception is thrown if

//it is invalid.

static float toFloat(const string & myInString)

{

float f=0;

string inString = trim(myInString);

if( !from_string<float>(f, inString, std::dec) )

{

string exceptionText = “StringUtils::toFloat() - Not a float: “ + inString; throw exceptionText;

}

// Now it runs some more checks. int dec_count=0;

for (unsigned int j=0; j < inString.length(); j++)

{

if ( !isNumeric(inString[j]) )

{

if (j==0 && inString[j] ==’-’)

{

continue;

}

else if (inString[j]==’.’)

{

dec_count++;

if (dec_count > 1)

{

string exceptionText = “StringUtils::toFloat() - Not a float: “ + inString;

throw exceptionText;

}

continue;

}

else

{

string exceptionText = “StringUtils::toFloat() - Not a float: “ + inString; throw exceptionText;

}

}

}

return (f);

}

Creating the Configuration-File Class

255

//Returns true if the character is numeric. static bool isNumeric(char c)

{

return (‘0’ <= c && c <= ‘9’);

}

//Replace environment variables in the string with their values.

//Note: environment variables must be of the form ${ENVVAR}. static string substituteEnvVar( const string &myInString )

{

string outString=””; char variable[512];

const char *s = myInString.c_str(); while(*s!=0)

{

if (*s==’$’ && *(s+1)==’{‘)

{

// When you’ve found beginning of variable, find the end. strcpy(variable,s+2);

char *end = strchr (variable,’}’); if (end)

{

*end=’\0’;

char *cp = (char *)getenv(variable); if (cp)

outString += (char *) getenv(variable); s = strchr(s,’}’);

}

else

{

outString += *s;

}

}

else

{

outString += *s;

}

s++;

}

return outString;

}

};

(continued)

256 Technique 45: Creating a Configuration File

LISTING 45-2 (continued)

ConfigurationFile::ConfigurationFile( const char *strConfigFile )

{

if ( strConfigFile )

m_sConfigFile = strConfigFile;

}

ConfigurationFile::~ConfigurationFile()

{

}

bool ConfigurationFile::read()

{

m_in.open(m_sConfigFile. c_str(),ios::in);

if (m_in.fail())

{

return false;

}

while (!m_in.eof())

{

//--------------------------------------------------------

//Get a token and value.

//This gives values to member vars: m_token and m_value.

//----------------------------------------------------------

get_token_and_value();

if ( m_token.length() ) m_ConfigEntries.insert( String_

Pair(m_token, m_value) );

}

m_in.close();

return true;

}

void ConfigurationFile::get_token_and_ value(void)

{

char token[1024]; char ch;

bool found_equal=false;

int i=0; eat_white_and_comments(); while(!(m_in.get(ch)).fail())

{

if ((ch != ‘\t’))

{

2

3

Creating the Configuration-File Class

257

if ( (ch == ‘=’) || (ch == ‘ ‘) || (ch == ‘\n’) || (ch == ‘\r’) || (ch == ‘\t’))

{

if (ch == ‘=’)found_equal=true; break;

}

token[i++]=ch;

}

}

if (i==0)

{

// It didn’t find a token, in this case. m_token=””;

m_value=””; return;

}

// Null-terminate the token that was found. token[i++]=’\0’;

m_token = token; makeLower(m_token);

// Advance to the equal sign, if need be. if (!found_equal)

{

if (!advance_to_equal_sign_on_line())

{

// The token had no value. m_token=””;

m_value=””; return;

}

}

// Get the token’s value. i=0;

char c = eat_white_and_comments(false);

if ( c != ‘\n’ )

{

i=0; while(!(m_in.get(ch)).fail())

{

if ((ch == ‘\t’) || (ch == ‘\r’) || (ch == ‘\n’) || (ch == ‘#’) )

{

while (ch!=’\n’)

{

if (m_in.get(ch).fail()) break;

}

(continued)

258 Technique 45: Creating a Configuration File

LISTING 45-2 (continued)

break;

}

else

{

token[i++]=ch;

}

}

}

if (i==0)

{

// This token had no value. m_value=””;

}

else

{

token[i++]=’\0’; m_value=token;

//Remove leading/trailing spaces. m_value = StringUtil::trim(m_value);

//Strip leading and trailing quotes, if there are any. if ( m_value[0] == ‘“‘ )

m_value = m_value.substr( 1 );

if ( m_value[ m_value.length() -1 ] == ‘“‘ )

m_value = m_value.substr( 0, m_value.length()-1 );

}

}

bool ConfigurationFile::advance_to_equal_sign_on_line()

{

char ch;

bool found_equal=false;

while ( !(m_in.get(ch)).fail() )

{

if ((ch==’\r’)||(ch==’\n’)) break; if (ch == ‘=’)

{

found_equal=true; break;

}

}

return found_equal;

}

char ConfigurationFile::eat_white_and_comments

Creating the Configuration-File Class

259

(bool traverse_newlines)

{

char ch;

bool in_comment;

in_comment = false;

while (!(m_in.get(ch)).fail()) if (ch == ‘#’)

in_comment = true; else if (ch == ‘\n’)

{

in_comment = false;

if (!traverse_newlines)

{

return(ch); // Stop eating.

}

}

else if ((!in_comment) && (ch != ‘ ‘) && (ch != ‘\t’) && (ch != ‘\r’))

{

m_in.putback(ch); return 0;

}

return 0;

}

void ConfigurationFile::makeLower (string &instring)

{

for(unsigned i=0; i < instring.size(); i++)

{

instring[i] = tolower(instring[i]);

}

}

bool ConfigurationFile::hasValue( const char { *key ) 4

bool bRet = false; std::string sKey = key; makeLower( sKey );

if ( m_ConfigEntries.find( sKey.c_str() ) != m_ConfigEntries.end() )

{

bRet = true;

}

return bRet;

}

string ConfigurationFile::getValue( const char *key )

{

std::string sKey = key; makeLower( sKey );

if ( m_ConfigEntries.find( sKey. c_str() ) != m_ConfigEntries.end() )

{

std::map<string, string>::iterator iter;

iter = m_ConfigEntries.find(sKey.c_str());

return (*iter).second;

}

return “”;

}

void ConfigurationFile::setValue( const char *key, const char *value )

{

std::string sKey = key; makeLower( sKey );

m_ConfigEntries[sKey] = value;

}

Our source code above breaks down into three general pieces. First, we separate out all of the utility routines that work with strings and characters and place them in the StringUtil utility

class (shown at the line marked with

1). Next,

we have the actual configuration-file class,

shown at the line marked with 2. This class

manages the storage and processing

of the file.

The processing is done in the read function,

shown at

3, and the storage functions begin

with the line marked

4. As you can see, the

routine simply reads

in a line from the input file

and separates it into two pieces, divided by an equal sign. Comments, which are lines that are either blank or marked with a pound sign (‘#’) are ignored. Everything to the left of the equal sign is considered to be the “tag,” while everything to the right of the equal sign is considered to be the “value.” Tag and value pairs make up the configuration data. The retrieval routines work by allowing the user to see if a given tag is defined, and if so to retrieve its value.

6. Save the source file in the source code editor.