/** @file $Id$ $Date$ $Author$ @copy © Marc Wäckerlin @license LGPL, see file COPYING $Log$ Revision 1.3 2005/11/29 12:39:42 marc make it compilable with gcc 4.0.2 and newer doxygen Revision 1.2 2005/01/28 07:49:32 marc Save configuration using file.hpp Revision 1.1 2005/01/07 00:31:38 marc initial version */ #ifndef __MRW_CONFIGFILE_HPP__ #define __MRW_CONFIGFILE_HPP__ #include #include namespace mrw { /** @defgroup config Configuration File Handler @pre \#include Read configuration parameters from a file. @section configSyntax Configuration File Syntax @subsection configSyntaxSection Sections: [Section] Syntax (regular expression): \\[([^[=#]*)\\] A configuration file consists of none or more sections. The sections start with tag [Section Name], where Section Name is the name of the section. The default section has an empty name. @subsection configSyntaxVariables Variables: name = value Syntax (regular expression): ^[^[\\n\\t ]*([^[=#\\n]]+)[\\n\\t ]*=[\\n\\t ]*([^[=#]]*)[\\n\\t ]* or ^[^[\\n\\t ]*([^[=#\\n]]+)[\\n\\t ]*=[\\n\\t ]*("[^"]]*")[\\n\\t ]* or ^[^[\\n\\t ]*([^[=#\\n]]+)[\\n\\t ]*=[\\n\\t ]*('[^']]*')[\\n\\t ]* In the section, there are variable definitions in the form of name=value, where name is the name of the variable and value is it's contents. White spaces at the begin or end of the name and the value are stripped. Whitespaces inside a name or value remain unchanged. Please note that a variable name starts at the begoin of line and ends before the equal sign, while the contents starts after the equal sign and ends before the next token starts. If you need a token inside a value, or a variable must start or end with white spaces, you can enclose it in either " or ' quotes. If a value is quoted and the quoting is not terminated, then the contents is taken up to the end of file. Please note that the same quote must not occur inside the value. All possible tokens are: [, # and =. All other characters are not treated in a special way. @subsection configSyntaxComments Comments: # Comments start with # and end at the end of the line. @subsection configSyntaxSpecial Special Characters \\n, [, ], #, =, ", ' I have chosen a syntax that is downwards compatible with the common Unix configuration file syntax, but with a grammar as loose as possible and with as few restrictions as possible. Special characters have only special meanings in certain contexts. In other contexts, they can be used with no restrictions. The following characters are treated in a special way: - [ and ] enclose a section name. A section name may contain any arbitrary character, including new line and white spaces, except a ]. - \\n (begin of line or file) and = enclose a variable name. In other circumstances (except in comments), a new line has no special meaning. A = inside a value is allowed only if the value is quoted. A variable name may contain any arbitrary character, including white spaces, except a new line or =. - = and one out of [, # and the begin of a line that contains = enclose a variable contents if the contents is not quoted. A variable's contents may contain any arbitrary character, including new line and white spaces, except a ], # or the begin of a line that contains =. - " or ' may enclose a variables contents. These characters have only a special meaning, if a variable's contents starts with one of them. Then the variable's contents may contain any arbitrary character, except the same quote itself, which marks the end of the contents. - # and \\n (end of line or file) enclose a comment. A variable name may contain any arbitrary character, including white spaces, except a new line. @subsection configSyntaxRestrictions Restrictions @attention Due to the actual syntax, it is impossible that a variable's contents contains the following combination: " @b and ' @b and one out of [, = and # @attention If a variable's contents contains heading or trailing white spaces or one out of [, = and #, then it must be quoted either with " or with '. @section configFile Example File @subsection configFileSamp1 Nice Example @verbatim [Section 1] name1 = value1 name2 = value2 [Section 2] name1 = value1 name2 = value2 @endverbatim @subsection configFileSamp2 Enhanced Example @verbatim # this is in the global section, named "" this is a variable name = this is a variable contents [Global] # start of section "Global" multi line text = this is a text that is longer than one line! 1234 = "var=17" # if you need an equal inside a text, enclose it # either in " or in ' @endverbatim @section configFileCode Code Sample @code try { const char* home(getenv("HOME")); mrw::ConfigFileReader config(home?home+"/.myprogram":".myprogram"); std::string userName(config("User Info", "Name")); } catch (...) { // file read or evaluation error } @endcode */ //@{ //============================================================================ /** @brief Parse configuration file and access the contents. Parse a configuration file that consists of sections containing variables with values. This class does not store anything in the file, even if variables have changed. @see If you want %to save changes %to the file, use @ref ConfigFileWriter. @see For details, see @ref configSyntax. */ class ConfigFileReader { //............................................................... typedefs public: //------------------------------------------------------------------ Value /// A configuration file value. class Value { //............................................................ methods public: /** @brief Get the value string. Get the value string. Trailing and leading white spaces have been truncated. If the variable was quoted, quotes have been removed. @return the variable's value as string */ operator const std::string&() const throw() { return value; } /** @brief Get the value string. Alternative access to the variable's contents. Trailing and leading white spaces have been truncated. If the variable was quoted, quotes have been removed. @return the variable's value as string */ const std::string& operator()() const throw() { return value; } /** @brief Assign a new value. Assign a new value. The new value is lost if you use the ConfigFileReader, but it is stored in the destructor, if you use the ConfigFileWriter instead. */ Value& operator=(const std::string& newValue) throw() { changed = true; value = newValue; return *this; } /// Compare the value to a string. bool operator==(const std::string& o) const throw() { return o==value; } /// Compare a string to the value. friend bool operator==(const std::string& o, const Value& v) throw() { return v==o; } //............................................................ methods protected: friend class ConfigFileReader; friend class ConfigFileWriter; Value(const std::string& v, std::string::size_type s, std::string::size_type e) throw(): value(v), changed(false), start(s), end(e) {} Value(const std::string& v) throw(): value(v), changed(true), start(0), end(0) { } //.......................................................... variables protected: std::string value; bool changed; std::string::size_type start; std::string::size_type end; //............................................................ methods private: Value(); // not implemented, must be initialized Value& operator=(const Value&); // not implemented, no assignment }; //------------------------------------------------------------------------ //................................................................ methods public: /** @brief Uninitialized construction. Uninitialized construction. Use this constructor, if you want to call @ref load() later. */ ConfigFileReader() throw() {} /** @brief Parse a file at construction. This should be the normal case: load and parse the file at construction. @param filename the name of the file to read @throw mrw::invalid_argument - if the file does not exist - if reading the file fails - if there is an unterminated section name in the file - if there is an empty variable name in the file */ ConfigFileReader(const std::string& filename) throw(std::exception): _filename(filename) { reload(); } /// Copy construct from other ConfigFileReader. ConfigFileReader(const ConfigFileReader& o) throw(): _filename(o._filename), _values(o._values) { } virtual ~ConfigFileReader() throw() {} /// Copy from other ConfigFileReader. ConfigFileReader& operator=(const ConfigFileReader& o) { _filename = o._filename; _values = o._values; return *this; } /** @brief Access a value read from the file. Access a value read from the file. @param section the section in the configuration file @param name the variable name @param default_ the default value that is set, if the variable is not configured @return the Value you are looking for */ Value& operator()(const std::string& section, const std::string& name, const std::string& default_) throw(std::bad_exception); /** @brief Load and parse a new file. Load and parse a new file. @param filename the name of the file to load @throw mrw::invalid_argument - if the file does not exist - if reading the file fails - if there is an unterminated section name in the file - if there is an empty variable name in the file */ ConfigFileReader& load(const std::string& filename) throw(std::exception); /** @brief Reload the last file again. Reload the last file again. @throw mrw::invalid_argument - if the file does not exist - if reading the file fails - if there is an unterminated section name in the file - if there is an empty variable name in the file */ virtual ConfigFileReader& reload() throw(std::exception); //............................................................... typedefs protected: typedef std::map< std::string, std::map > Values; typedef std::map Sections; //.............................................................. variables protected: std::string _filename; Values _values; Sections _sections; //................................................................ methods protected: ConfigFileReader& parse(const std::string& file) throw(std::exception); std::string parseSection(const std::string& file, std::string::size_type& pos) const throw(std::exception); Values::mapped_type::value_type parseValue(const std::string& file, std::string::size_type& pos) const throw(std::exception); std::string parseComment(const std::string& file, std::string::size_type& pos) const throw(std::bad_exception); }; //============================================================================ /** @brief Parse configuration file and offer read / write access to the contents. Parse a configuration file that consists of sections containing variables with values. This class stores the file in the destructor if variables have changed. @see If you want %to leave the file untouched, use @ref ConfigFileReader. @see For details, see @ref configSyntax. */ class ConfigFileWriter: virtual public ConfigFileReader { //................................................................ methods public: /** @brief Uninitialized construction. @copydoc ConfigFileReader() */ ConfigFileWriter() throw() {} /** @brief Parse a file at construction. @copydoc ConfigFileReader(const std::string&) */ ConfigFileWriter(const std::string& filename) throw(std::exception) { load(filename); } /// Copy construct from other ConfigFileWriter. ConfigFileWriter(const ConfigFileWriter& o) throw(): ConfigFileReader(o) { } /// The file is stored at destruction. virtual ~ConfigFileWriter() throw() { save(); } /// Copy from other ConfigFileWriter. ConfigFileWriter& operator=(const ConfigFileWriter& o) { ConfigFileReader::operator=(o); return *this; } // GNU g++ prior to 3.4 does not implement covariant returns # if __GNUC__ == 3 && __GNUC_MINOR__ < 4 /** @brief Reload the last file again. @copydoc mrw::ConfigFileReader::reload() @bug Bug in GNU g++ compiler: return type should be ConfigFileWriter&, but the compiler sais:
../mrw/configfile.hpp:295: sorry, unimplemented: adjusting pointers for covariant returns
The problem is fixed since GNU g++ 3.4, so the signature will be changed as soon as you upgrade to the new compiler version. */ virtual ConfigFileReader& reload() throw(std::exception); # else /** @brief Reload the last file again. @copydoc mrw::ConfigFileReader::reload() */ virtual ConfigFileWriter& reload() throw(std::exception); # endif /** @brief Saves changes back to the file. All changed parameters are stored in the configuration file. The rest of the file is left untouched. */ ConfigFileWriter& save() throw(std::exception); //.............................................................. variables protected: std::string _file; }; //@} } #endif