diff --git a/Util/include/Poco/Util/PropertyFileConfiguration.h b/Util/include/Poco/Util/PropertyFileConfiguration.h index a6b8c4442..aef89b064 100644 --- a/Util/include/Poco/Util/PropertyFileConfiguration.h +++ b/Util/include/Poco/Util/PropertyFileConfiguration.h @@ -24,6 +24,7 @@ #include "Poco/Util/MapConfiguration.h" #include <istream> #include <ostream> +#include <list> namespace Poco { @@ -60,21 +61,25 @@ public: PropertyFileConfiguration(); /// Creates an empty PropertyFileConfiguration. - PropertyFileConfiguration(std::istream& istr); + PropertyFileConfiguration(std::istream& istr, bool preserveComment = false); /// Creates an PropertyFileConfiguration and loads the configuration data /// from the given stream, which must be in properties file format. + /// Set the preserveComment to preserve the comments in the given stream. - PropertyFileConfiguration(const std::string& path); + PropertyFileConfiguration(const std::string& path, bool preserveComment = false); /// Creates an PropertyFileConfiguration and loads the configuration data /// from the given file, which must be in properties file format. + /// Set the preserveComment to preserve the comments in the given stream. - void load(std::istream& istr); + void load(std::istream& istr, bool preserveComment = false); /// Loads the configuration data from the given stream, which /// must be in properties file format. + /// Set the preserveComment to preserve the comments in the given stream. - void load(const std::string& path); + void load(const std::string& path, bool preserveComment = false); /// Loads the configuration data from the given file, which /// must be in properties file format. + /// Set the preserveComment to preserve the comments in the given stream. void save(std::ostream& ostr) const; /// Writes the configuration data to the given stream. @@ -87,10 +92,28 @@ public: /// Writes the configuration data to the given file. protected: + void setRaw(const std::string& key, const std::string& value); + void removeRaw(const std::string& key); ~PropertyFileConfiguration(); private: + typedef std::list<std::string> FileContent; + typedef std::map<std::string, FileContent::iterator> KeyFileContentItMap; + void parseLine(std::istream& istr); + void skipSpace(std::istream& istr) const; + bool isComment(int c) const ; + void saveComment(std::istream& istr); + void skipLine(std::istream& istr) const; + void saveKeyValue(std::istream& istr); + bool isNewLine(int c) const; + bool isKeyValueSeparator(int c) const; + void outputKeyValue(std::ostream& ostr, const std::string& key, const std::string& value) const; + + bool _preserveComment; + FileContent _fileContent; + KeyFileContentItMap _keyFileContentItMap; + static int readChar(std::istream& istr); }; diff --git a/Util/src/PropertyFileConfiguration.cpp b/Util/src/PropertyFileConfiguration.cpp index 6bc414db5..7e56f0d1a 100644 --- a/Util/src/PropertyFileConfiguration.cpp +++ b/Util/src/PropertyFileConfiguration.cpp @@ -31,20 +31,23 @@ namespace Poco { namespace Util { -PropertyFileConfiguration::PropertyFileConfiguration() +PropertyFileConfiguration::PropertyFileConfiguration() : + _preserveComment(false) { } -PropertyFileConfiguration::PropertyFileConfiguration(std::istream& istr) +PropertyFileConfiguration::PropertyFileConfiguration(std::istream& istr, bool preserveComment) : + _preserveComment(preserveComment) { - load(istr); + load(istr, preserveComment); } -PropertyFileConfiguration::PropertyFileConfiguration(const std::string& path) +PropertyFileConfiguration::PropertyFileConfiguration(const std::string& path, bool preserveComment) : + _preserveComment(preserveComment) { - load(path); + load(path, preserveComment); } @@ -53,21 +56,22 @@ PropertyFileConfiguration::~PropertyFileConfiguration() } -void PropertyFileConfiguration::load(std::istream& istr) +void PropertyFileConfiguration::load(std::istream& istr, bool preserveComment) { + _preserveComment = preserveComment; clear(); - while (!istr.eof()) - { - parseLine(istr); - } + _fileContent.clear(); + _keyFileContentItMap.clear(); + + while (!istr.eof()) parseLine(istr); } -void PropertyFileConfiguration::load(const std::string& path) +void PropertyFileConfiguration::load(const std::string& path, bool preserveComment) { Poco::FileInputStream istr(path); if (istr.good()) - load(istr); + load(istr, preserveComment); else throw Poco::OpenFileException(path); } @@ -75,37 +79,25 @@ void PropertyFileConfiguration::load(const std::string& path) void PropertyFileConfiguration::save(std::ostream& ostr) const { - MapConfiguration::iterator it = begin(); - MapConfiguration::iterator ed = end(); - while (it != ed) + if (_preserveComment) { - ostr << it->first << ": "; - for (std::string::const_iterator its = it->second.begin(); its != it->second.end(); ++its) + // Check the starting char of each line in _fileContent. + // If the char is a comment sign, write the line out directly. + // Otherwise, use this line as key to get the value from parent's map and write out. + for (FileContent::const_iterator it = _fileContent.begin(); it != _fileContent.end(); ++it) { - switch (*its) - { - case '\t': - ostr << "\\t"; - break; - case '\r': - ostr << "\\r"; - break; - case '\n': - ostr << "\\n"; - break; - case '\f': - ostr << "\\f"; - break; - case '\\': - ostr << "\\\\"; - break; - default: - ostr << *its; - break; - } + if (isComment((*it)[0])) ostr << *it; + else outputKeyValue(ostr, *it, getString(*it)); + } + } else + { + MapConfiguration::iterator it = begin(); + MapConfiguration::iterator ed = end(); + while (it != ed) + { + outputKeyValue(ostr, it->first, it->second); + ++it; } - ostr << "\n"; - ++it; } } @@ -127,32 +119,27 @@ void PropertyFileConfiguration::save(const std::string& path) const void PropertyFileConfiguration::parseLine(std::istream& istr) { - static const int eof = std::char_traits<char>::eof(); + skipSpace(istr); - int c = istr.get(); - while (c != eof && Poco::Ascii::isSpace(c)) c = istr.get(); - if (c != eof) + if (!istr.eof()) { - if (c == '#' || c == '!') + if (isComment(istr.peek())) { - while (c != eof && c != '\n' && c != '\r') c = istr.get(); - } - else - { - std::string key; - while (c != eof && c != '=' && c != ':' && c != '\r' && c != '\n') { key += (char) c; c = istr.get(); } - std::string value; - if (c == '=' || c == ':') - { - c = readChar(istr); - while (c != eof && c) { value += (char) c; c = readChar(istr); } - } - setRaw(trim(key), trim(value)); + // Save + if (_preserveComment) saveComment(istr); + else skipLine(istr); } + else saveKeyValue(istr); } } +void PropertyFileConfiguration::skipSpace(std::istream& istr) const +{ + while (!istr.eof() && Poco::Ascii::isSpace(istr.peek())) istr.get(); +} + + int PropertyFileConfiguration::readChar(std::istream& istr) { for (;;) @@ -189,4 +176,127 @@ int PropertyFileConfiguration::readChar(std::istream& istr) } +void PropertyFileConfiguration::setRaw(const std::string& key, const std::string& value) +{ + MapConfiguration::setRaw(key, value); + // Insert the key to the end of _fileContent and update _keyFileContentItMap. + if (_preserveComment) + { + FileContent::iterator fit = _fileContent.insert(_fileContent.end(), key); + _keyFileContentItMap[key] = fit; + } +} + + +void PropertyFileConfiguration::removeRaw(const std::string& key) +{ + MapConfiguration::removeRaw(key); + // remove the key from _fileContent and _keyFileContentItMap. + if (_preserveComment) + { + _fileContent.erase(_keyFileContentItMap[key]); + _keyFileContentItMap.erase(key); + } +} + +bool PropertyFileConfiguration::isComment(int c) const +{ + return c == '#' || c == '!'; +} + + +void PropertyFileConfiguration::saveComment(std::istream& istr) +{ + std::string comment; + + int c = istr.get(); + while (!isNewLine(c)) + { + comment += (char) c; + c = istr.get(); + } + comment += (char) c; + + _fileContent.push_back(comment); +} + + +void PropertyFileConfiguration::skipLine(std::istream& istr) const +{ + int c = istr.get(); + while (!isNewLine(c)) c = istr.get(); +} + + +void PropertyFileConfiguration::saveKeyValue(std::istream& istr) +{ + int c = istr.get(); + + std::string key; + while (!isNewLine(c) && !isKeyValueSeparator(c)) + { + key += (char) c; + c = istr.get(); + } + + std::string value; + if (isKeyValueSeparator(c)) + { + c = readChar(istr); + while (!istr.eof() && c) + { + value += (char) c; + c = readChar(istr); + } + } + + setRaw(trim(key), trim(value)); +} + + +bool PropertyFileConfiguration::isNewLine(int c) const +{ + return c == std::char_traits<char>::eof() || c == '\n' || c == '\r'; +} + + +void PropertyFileConfiguration::outputKeyValue(std::ostream& ostr, const std::string& key, const std::string& value) const +{ + ostr << key << ": "; + + for (std::string::const_iterator its = value.begin(); its != value.end(); ++its) + { + switch (*its) + { + case '\t': + ostr << "\\t"; + break; + case '\r': + ostr << "\\r"; + break; + case '\n': + ostr << "\\n"; + break; + case '\f': + ostr << "\\f"; + break; + case '\\': + ostr << "\\\\"; + break; + default: + ostr << *its; + break; + } + } + + ostr << "\n"; +} + + +bool PropertyFileConfiguration::isKeyValueSeparator(int c) const +{ + return c == '=' || c == ':'; +} + + } } // namespace Poco::Util diff --git a/Util/testsuite/src/PropertyFileConfigurationTest.cpp b/Util/testsuite/src/PropertyFileConfigurationTest.cpp index c5cbb483c..1279c6fb8 100644 --- a/Util/testsuite/src/PropertyFileConfigurationTest.cpp +++ b/Util/testsuite/src/PropertyFileConfigurationTest.cpp @@ -37,6 +37,31 @@ PropertyFileConfigurationTest::~PropertyFileConfigurationTest() void PropertyFileConfigurationTest::testLoad() +{ + testLoad(false); +} + + +void PropertyFileConfigurationTest::testLoadEmpty() +{ + static const std::string propFile = " "; + + std::istringstream istr(propFile); + AutoPtr<PropertyFileConfiguration> pConf = new PropertyFileConfiguration(istr); + + AbstractConfiguration::Keys keys; + pConf->keys(keys); + assert (keys.size() == 0); +} + + +void PropertyFileConfigurationTest::testLoadWithPreserveComment() +{ + testLoad(true); +} + + +void PropertyFileConfigurationTest::testLoad(bool preserveComment) { static const std::string propFile = "! comment\n" @@ -54,7 +79,7 @@ void PropertyFileConfigurationTest::testLoad() "prop5:foo"; std::istringstream istr(propFile); - AutoPtr<PropertyFileConfiguration> pConf = new PropertyFileConfiguration(istr); + AutoPtr<PropertyFileConfiguration> pConf = new PropertyFileConfiguration(istr, preserveComment); assert (pConf->getString("prop1") == "value1"); assert (pConf->getString("prop2") == "value2"); @@ -106,6 +131,59 @@ void PropertyFileConfigurationTest::testSave() "prop3: value\\\\1\\txxx\n"); } +void PropertyFileConfigurationTest::testLoadSaveWithPreserveComment() +{ + std::string propFile = + "! comment #\n" + "prop1=value1\n" + "# comment #\n" + "# comment !\n" + "prop2 = value2 \n" + "! comment !\n" + "prop3:foo"; + + std::istringstream istr(propFile); + AutoPtr<PropertyFileConfiguration> pConf = new PropertyFileConfiguration(istr, true); + + std::ostringstream ostr; + pConf->save(ostr); + assertEqual ("! comment #\n" + "prop1: value1\n" + "# comment #\n" + "# comment !\n" + "prop2: value2\n" + "! comment !\n" + "prop3: foo\n", + ostr.str()); + + pConf->setString("prop4", "value4"); + ostr.clear(); + ostr.str(""); + pConf->save(ostr); + assertEqual ("! comment #\n" + "prop1: value1\n" + "# comment #\n" + "# comment !\n" + "prop2: value2\n" + "! comment !\n" + "prop3: foo\n" + "prop4: value4\n", + ostr.str()); + + pConf->remove("prop2"); + ostr.clear(); + ostr.str(""); + pConf->save(ostr); + assertEqual ("! comment #\n" + "prop1: value1\n" + "# comment #\n" + "# comment !\n" + "! comment !\n" + "prop3: foo\n" + "prop4: value4\n", + ostr.str()); +} + AbstractConfiguration* PropertyFileConfigurationTest::allocConfiguration() const { @@ -129,7 +207,10 @@ CppUnit::Test* PropertyFileConfigurationTest::suite() AbstractConfigurationTest_addTests(pSuite, PropertyFileConfigurationTest); CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoad); + CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoadEmpty); CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testSave); - + CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoadWithPreserveComment); + CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoadSaveWithPreserveComment); + return pSuite; } diff --git a/Util/testsuite/src/PropertyFileConfigurationTest.h b/Util/testsuite/src/PropertyFileConfigurationTest.h index 203d77a35..7bb80776e 100644 --- a/Util/testsuite/src/PropertyFileConfigurationTest.h +++ b/Util/testsuite/src/PropertyFileConfigurationTest.h @@ -27,7 +27,10 @@ public: virtual ~PropertyFileConfigurationTest(); void testLoad(); + void testLoadEmpty(); + void testLoadWithPreserveComment(); void testSave(); + void testLoadSaveWithPreserveComment(); void setUp(); void tearDown(); @@ -36,6 +39,8 @@ public: private: virtual Poco::Util::AbstractConfiguration* allocConfiguration() const; + + void testLoad(bool preserveComment); };