//$Id: INIFile.cpp,v 1.35 2007/02/09 11:27:05 markus Rel $ //PROJECT : libYGP //SUBSYSTEM : INIFile //REFERENCES : //TODO : //BUGS : //REVISION : $Revision: 1.35 $ //AUTHOR : Markus Schwab //CREATED : 7.5.2000 //COPYRIGHT : Copyright (C) 2000 - 2005 // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifdef _MSC_VER #pragma warning(disable:4786) // disable warning about truncating debug info #endif #include #include #include "YGP/Entity.h" #include "YGP/INIFile.h" #include "YGP/Internal.h" #ifdef _MSC_VER #pragma warning(disable:4355) // disable warning about this in init-list #endif // Define constant values; don't skip white-spaces after parsing static unsigned int LEN_SECTIONNAME = 32; static unsigned int LEN_KEY = 32; static unsigned int LEN_VALUE = 256; namespace YGP { //----------------------------------------------------------------------------- /// Constructor; creates an object to parse the header of a section. //----------------------------------------------------------------------------- INISection::ISectionParser::ISectionParser () : SectionHeader (_SectionHeader, _("Section-header"), 1, 0) , SectionBegin ("[", _("Start of section ([)"), false) , SectionName ("\\X\\9_.", _("Name of section"), *this, &ISectionParser::foundSection, LEN_SECTIONNAME, 1) , SectionEnd ("]", _("End of section (])"), false) { _SectionHeader[0] = &SectionBegin; _SectionHeader[1] = &SectionName; _SectionHeader[2] = &SectionEnd; _SectionHeader[3] = NULL; } //----------------------------------------------------------------------------- /// Destructor //----------------------------------------------------------------------------- INISection::ISectionParser::~ISectionParser () { } //----------------------------------------------------------------------------- /// Parses the section header /// \param stream: Stream to parse from /// \returns int: Status of parse /// \throw YGP::ParseError: Error while parsing //----------------------------------------------------------------------------- int INISection::ISectionParser::parse (Xistream& stream) throw (YGP::ParseError) { INISection::skipComments (stream); return SectionHeader.parse (stream); } //----------------------------------------------------------------------------- /// Constructor; name is the name of the section. /// \param name: Name of section /// \remarks name must be a valid ASCIIZ-string (not NULL) //----------------------------------------------------------------------------- INISection::INISection (const char* name) : pFoundAttr (NULL), pName (name) , Attributes (_Attributes, _("Attribute"), 1, 0) , Identifier ("\\X\\9_.", _("Identifier (key)"), *this, &INISection::foundKey, LEN_KEY, 1, false) , equals ("=", _("Equal-sign (=)"), false) , Value ("\n", _("Value"), *this, &INISection::foundValue, LEN_VALUE, 0) { TRACE9 ("INISection::INISection (const char*) - Create: " << pName); Check1 (pName); _Attributes[0] = &Identifier; _Attributes[1] = = _Attributes[2] = &Value; _Attributes[3] = NULL; } //----------------------------------------------------------------------------- /// Destructor //----------------------------------------------------------------------------- INISection::~INISection () { } //----------------------------------------------------------------------------- /// Adds an attribute to parse to the section /// \param attribute: %Attribute to add //----------------------------------------------------------------------------- void INISection::addAttribute (const IAttribute& attribute) { Check3 (!findAttribute (attribute.getName ())); TRACE9 ("INISection::addAttribute (const IAttribute&) - " << attribute.getName ()); attributes.push_back (&attribute); } //----------------------------------------------------------------------------- /// Searches for an attribute matching the passed name. If such an attribute /// is not found, NULL is returned. /// \param name: Name of attribute to find /// \returns \c IAttribute*: Pointer to attribute or NULL (if not found) /// \remarks name must not be a NULL pointer //----------------------------------------------------------------------------- const IAttribute* INISection::findAttribute (const char* name) const { TRACE9 ("INISection::findAttribute (const char*) - " << name); Check1 (name); std::vector::const_iterator i; for (i = attributes.begin (); i != attributes.end (); ++i) if ((*i)->matches (name)) return *i; return NULL; } //----------------------------------------------------------------------------- /// TriesSearches for an attribute matching the passed name. If such an /// attribute is not found, NULL is returned. /// \param name: Name of attribute to find /// \returns \c IAttribute*: Pointer to attribute or NULL (if not found) //----------------------------------------------------------------------------- const IAttribute* INISection::findAttribute (const std::string& name) const { TRACE9 ("INISection::findAttribute (const std::string&) - " << name); std::vector::const_iterator i; for (i = attributes.begin (); i != attributes.end (); ++i) if ((*i)->matches (name)) return *i; return NULL; } //----------------------------------------------------------------------------- /// Tries to reads the (whole) section from the INI-file. First the section /// header is parsed. Note that the name of the section in the INI-file must /// match those of the class. If the header can be parsed successfully, every /// following key=value pair is inspected and - if key matches the name of an /// attribute in the section - assigned to the connected variable. The /// function returns ParseObject::PARSE_OK, if EOF or a new section-header (to /// be exact: Anything which is not a key) is reached. Errors are returned (or /// execeptions are thrown) if the attribute is either not found inside the /// class or the value can not be assigned. /// \param stream: Extended stream to read from /// \returns \c int: ParseObject::OK if a know key is found and it's value can /// be assigned Remarks The exact behaviour depends on the type of the /// attribute! /// \throw YGP::ParseError: With text describing error if an unrecoverable /// error occurs //----------------------------------------------------------------------------- int INISection::readFromStream (Xistream& stream) throw (YGP::ParseError) { TRACE9 ("INISection::readFromStream (Xistream&)"); TSectionParser hdrParser (*this, &INISection::foundSection); int rc (hdrParser.parse (stream)); return rc ? rc : readAttributes (stream); } //----------------------------------------------------------------------------- /// Tries to read the attributes of a section from the INI-file. Every /// key=value pair is inspected and - if key matches the name of an attribute /// in the section - assigned to the connected variable. The function returns /// ParseObject::PARSE_OK, if EOF or a new section-header (to be exact: /// Anything which is not a key) is reached. Errors are returned (or /// execeptions are thrown) if the attribute is either not found inside the /// class or the value can not be assigned. /// \param stream: Extended stream to read from /// \returns \c int: ParseObject::OK if a know key is found and it's value can /// be assigned Remarks The exact behaviour depends on the type of the /// attribute! /// \throw YGP::ParseError: With text describing error if an unrecoverable /// error occurs //----------------------------------------------------------------------------- int INISection::readAttributes (Xistream& stream) throw (YGP::ParseError) { TRACE9 ("INISection::readAttributes (Xistream&)"); int rc (ParseObject::PARSE_OK); do { pFoundAttr = NULL; INISection::skipComments (stream); rc = Attributes.parse (stream); } while (pFoundAttr != NULL); return rc; } //----------------------------------------------------------------------------- /// Skips over comments (the text following a semi-colon (;) til the end of the /// line. /// \param stream: Stream to read from //----------------------------------------------------------------------------- void INISection::skipComments (Xistream& stream) { ParseExact semi (";", "Semicolon", true, false); ParseText line ("\n", "EOL", -1U, 0, true, false); ParseObject::skipWS (stream); while (semi.parse (stream) == ParseObject::PARSE_OK) line.parse (stream); } //----------------------------------------------------------------------------- /// Callback when the name of a section was found /// \param section: Name of found section /// \param int: Unused length of the section name /// \returns \c int: PARSE_OK, if name of section is OK //----------------------------------------------------------------------------- int INISection::foundSection (const char* section, unsigned int) { TRACE5 ("INISection::foundSection (const char*, unsigned int): '" << section << '\''); Check1 (section); Check3 (pName); return strcmp (pName, section) ? ParseObject::PARSE_CB_ABORT : ParseObject::PARSE_OK; } //----------------------------------------------------------------------------- /// Callback if a key of an attribute has been found. Every attribute of the /// section is compared with the passed key. If they match the value of the /// key is assigned to the attribute /// \param key: Name of the found key /// \param int: Unused length of the section name /// \returns \c int: PARSE_OK, if key is found; else /// ParseObject::PARSE_CB_ABORT. //----------------------------------------------------------------------------- int INISection::foundKey (const char* key, unsigned int) { TRACE5 ("INISection::foundKey (const char*, unsigned int): '" << key << '\''); Check1 (key); // Search for attribute std::vector::iterator i; for (i = attributes.begin (); i != attributes.end (); ++i) { if ((*i)->matches (key)) { // If attribute matches: Store for value pFoundAttr = *i; return ParseObject::PARSE_OK; } // endif } // end-for all attributes return ParseObject::PARSE_CB_ABORT; } //----------------------------------------------------------------------------- /// Callback if a value of an attribute has been found. This value is assigned /// using the assign-member of IAttribute. /// \param value: Value to assign to the (previously) found attribute /// \param len: Length of text hold by \c value /// \returns \c int: PARSE_OK, if value could be assigned successfully; else /// ParseObject::PARSE_CB_ABORT //----------------------------------------------------------------------------- int INISection::foundValue (const char* value, unsigned int len) { Check3 (value); Check3 (pFoundAttr); TRACE5 ("INISection::foundValue (const char*, unsigned int): '" << value << '\''); return pFoundAttr->assign (value, len) ? ParseObject::PARSE_OK : ParseObject::PARSE_CB_ABORT; } //----------------------------------------------------------------------------- /// Constructor; The parameter filename specifies the file to parse for /// initialization-information. If this file does not exist, an exception is /// thrown. /// \param filename: Name of the INI file /// \throw YGP::FileError: If file couldn't be open a text describing the error /// \remarks filename must be an ASCIIZ-string //----------------------------------------------------------------------------- INIFile::INIFile (const char* filename) throw (YGP::FileError) : pSection (NULL) { Check3 (filename); TRACE9 ("INIFile::INIFile (const char*): Read from " << filename); file.open (filename, std::ios::in); if (!file) { std::string error (_("Could not open INI-file '%1': Reason: %2")); error.replace (error.find ("%1"), 2, filename); error.replace (error.find ("%2"), 2, strerror (errno)); throw (YGP::FileError (error)); } file.init (); } //----------------------------------------------------------------------------- /// Destructor //----------------------------------------------------------------------------- INIFile::~INIFile () { std::vector::iterator i; for (i = sectionsToFree.begin (); i != sectionsToFree.end (); ++i) delete (*i); } //----------------------------------------------------------------------------- /// Adds the passed section to the list of sections to parse. /// \param section: Specification of the section //----------------------------------------------------------------------------- void INIFile::addSection (const INISection& section) { TRACE9 ("INIFile::addSection (const INISection&) - " << section.getName ()); Check3 (!findSection (section.getName ())); sections.push_back (§ion); } //----------------------------------------------------------------------------- /// Adds a section to parse to the INI-file; does nothing if section already /// exists /// \param section: Name of the section /// \returns \c INISection*: Pointer to new (or existing) section //----------------------------------------------------------------------------- INISection* INIFile::addSection (const char* section) { TRACE9 ("INIFile::addSection (const char*) - " << section); Check3 (section); INISection* pSec = const_cast (findSection (section)); if (!pSec) { pSec = new INISection (section); sections.push_back (pSec); sectionsToFree.push_back (pSec); } return pSec; } //----------------------------------------------------------------------------- /// Tries to read the INI-file from the file. First the section header is /// parsed. If the name of the section from the file is found in the list of /// sections, those sections are parsed. This step is repeated, until the end /// of the file is reached or a line is found which is neither a /// section-header, nor an attribute. The function returns /// ParseObject::PARSE_OK, if EOF is reached; else a non-zero value is /// returned or - depending on the error - an exception is thrown. /// \returns \c int: Status of reading: <0 hard error; 0 OK, >0 soft error /// \throw YGP::ParseError: With a message describing error in case of an invalid value //----------------------------------------------------------------------------- int INIFile::read () throw (YGP::ParseError) { TRACE9 ("INIFile::read ()"); // Parse the section-header; terminate on error int rc = 0; do { pSection = NULL; INISection::TSectionParser hdrParser (*this, &INIFile::foundSection); rc = hdrParser.parse ((Xistream&)file); if (rc || file.eof ()) break; if (pSection) rc = pSection->readAttributes ((Xistream&)file); } while (!rc); // end-do return rc; } //----------------------------------------------------------------------------- /// Searches the entries of the INI-file-object for a section with the passed /// name. If a matching entry is found, a pointer to it is returned; else /// NULL. /// \param name: Name of section to find /// \returns \c Section*: Pointer to section or NULL (if not found) //----------------------------------------------------------------------------- const INISection* INIFile::findSection (const char* name) const { std::vector::const_iterator i; for (i = sections.begin (); i != sections.end (); ++i) if ((*i)->matches (name)) return *i; return NULL; } //----------------------------------------------------------------------------- /// Callback if a section-header (to be exact: an identifier after the /// start-of-section character open bracket ([)) is found. The default action /// is to check, if the section matches (case-sensitive!) the passed /// parameter. If yes, its attributes are parsed. /// \param section: Name of found section /// \param int: Unused length of the section name /// \returns \c int: PARSE_OK, if name of section is OK, else /// ParseObject::PARSE_CB_ABORT. //----------------------------------------------------------------------------- int INIFile::foundSection (const char* section, unsigned int) { Check3 (section); TRACE5 ("INIFile::foundSection (const char* , unsigned int): '" << section << '\''); pSection = const_cast (findSection (section)); return pSection ? ParseObject::PARSE_OK : ParseObject::PARSE_CB_ABORT; } //----------------------------------------------------------------------------- /// Adds all the attributes of the Entity to the passed section /// \param obj: Object whose attributes should be added /// \param section: Section where to add the attributes //----------------------------------------------------------------------------- void INIFile::addEntity (const Entity& obj, INISection& section) { TRACE9 ("INIFile::addEntity (const Entity&, INISection&) - adding " << obj.attributes.size () << " attributes"); std::vector::const_iterator i; for (i = obj.attributes.begin (); i != obj.attributes.end (); ++i) { Check3 (*i); section.addAttribute (**i); } } //----------------------------------------------------------------------------- /// Writes an entity to the INI-file. Every attribute of the object is written /// as "name=value". /// \param stream: File to write to /// \param section: Name of the section for all attributes in the object /// \param obj: Object to write /// \remarks There is no error-handling; failures are silently ignored! //----------------------------------------------------------------------------- void INIFile::write (std::ostream& stream, const char* section, const Entity& obj) { TRACE9 ("INIFile::write (std::ostream&, const char*, const Entity&) - Section:" << section); Check1 (section); Check1 (stream); writeSectionHeader (stream, section); std::vector::const_iterator i; for (i = obj.attributes.begin (); i != obj.attributes.end (); ++i) { Check3 (*i); stream << (*i)->getName () << '=' << (*i)->getValue () << '\n'; } } }