implement GH #700: PropertyFileConfiguration preserve comments.

This commit is contained in:
aaron0x
2015-09-11 14:22:00 +08:00
parent fee347ec6a
commit 334a7267c7
4 changed files with 283 additions and 64 deletions

View File

@@ -24,6 +24,7 @@
#include "Poco/Util/MapConfiguration.h" #include "Poco/Util/MapConfiguration.h"
#include <istream> #include <istream>
#include <ostream> #include <ostream>
#include <list>
namespace Poco { namespace Poco {
@@ -60,21 +61,25 @@ public:
PropertyFileConfiguration(); PropertyFileConfiguration();
/// Creates an empty PropertyFileConfiguration. /// Creates an empty PropertyFileConfiguration.
PropertyFileConfiguration(std::istream& istr); PropertyFileConfiguration(std::istream& istr, bool preserveComment = false);
/// Creates an PropertyFileConfiguration and loads the configuration data /// Creates an PropertyFileConfiguration and loads the configuration data
/// from the given stream, which must be in properties file format. /// 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 /// Creates an PropertyFileConfiguration and loads the configuration data
/// from the given file, which must be in properties file format. /// 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 /// Loads the configuration data from the given stream, which
/// must be in properties file format. /// 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 /// Loads the configuration data from the given file, which
/// must be in properties file format. /// must be in properties file format.
/// Set the preserveComment to preserve the comments in the given stream.
void save(std::ostream& ostr) const; void save(std::ostream& ostr) const;
/// Writes the configuration data to the given stream. /// Writes the configuration data to the given stream.
@@ -87,10 +92,28 @@ public:
/// Writes the configuration data to the given file. /// Writes the configuration data to the given file.
protected: protected:
void setRaw(const std::string& key, const std::string& value);
void removeRaw(const std::string& key);
~PropertyFileConfiguration(); ~PropertyFileConfiguration();
private: private:
typedef std::list<std::string> FileContent;
typedef std::map<std::string, FileContent::iterator> KeyFileContentItMap;
void parseLine(std::istream& istr); 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); static int readChar(std::istream& istr);
}; };

View File

@@ -31,20 +31,23 @@ namespace Poco {
namespace Util { 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(); clear();
while (!istr.eof()) _fileContent.clear();
{ _keyFileContentItMap.clear();
parseLine(istr);
} 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); Poco::FileInputStream istr(path);
if (istr.good()) if (istr.good())
load(istr); load(istr, preserveComment);
else else
throw Poco::OpenFileException(path); throw Poco::OpenFileException(path);
} }
@@ -75,38 +79,26 @@ void PropertyFileConfiguration::load(const std::string& path)
void PropertyFileConfiguration::save(std::ostream& ostr) const void PropertyFileConfiguration::save(std::ostream& ostr) const
{ {
if (_preserveComment)
{
// 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)
{
if (isComment((*it)[0])) ostr << *it;
else outputKeyValue(ostr, *it, getString(*it));
}
} else
{
MapConfiguration::iterator it = begin(); MapConfiguration::iterator it = begin();
MapConfiguration::iterator ed = end(); MapConfiguration::iterator ed = end();
while (it != ed) while (it != ed)
{ {
ostr << it->first << ": "; outputKeyValue(ostr, it->first, it->second);
for (std::string::const_iterator its = it->second.begin(); its != it->second.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";
++it; ++it;
} }
}
} }
@@ -127,32 +119,27 @@ void PropertyFileConfiguration::save(const std::string& path) const
void PropertyFileConfiguration::parseLine(std::istream& istr) void PropertyFileConfiguration::parseLine(std::istream& istr)
{ {
static const int eof = std::char_traits<char>::eof(); skipSpace(istr);
int c = istr.get(); if (!istr.eof())
while (c != eof && Poco::Ascii::isSpace(c)) c = istr.get();
if (c != eof)
{ {
if (c == '#' || c == '!') if (isComment(istr.peek()))
{ {
while (c != eof && c != '\n' && c != '\r') c = istr.get(); // Save
} if (_preserveComment) saveComment(istr);
else else skipLine(istr);
{
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));
} }
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) int PropertyFileConfiguration::readChar(std::istream& istr)
{ {
for (;;) 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 } } // namespace Poco::Util

View File

@@ -37,6 +37,31 @@ PropertyFileConfigurationTest::~PropertyFileConfigurationTest()
void PropertyFileConfigurationTest::testLoad() 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 = static const std::string propFile =
"! comment\n" "! comment\n"
@@ -54,7 +79,7 @@ void PropertyFileConfigurationTest::testLoad()
"prop5:foo"; "prop5:foo";
std::istringstream istr(propFile); 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("prop1") == "value1");
assert (pConf->getString("prop2") == "value2"); assert (pConf->getString("prop2") == "value2");
@@ -106,6 +131,59 @@ void PropertyFileConfigurationTest::testSave()
"prop3: value\\\\1\\txxx\n"); "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 AbstractConfiguration* PropertyFileConfigurationTest::allocConfiguration() const
{ {
@@ -129,7 +207,10 @@ CppUnit::Test* PropertyFileConfigurationTest::suite()
AbstractConfigurationTest_addTests(pSuite, PropertyFileConfigurationTest); AbstractConfigurationTest_addTests(pSuite, PropertyFileConfigurationTest);
CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoad); CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoad);
CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoadEmpty);
CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testSave); CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testSave);
CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoadWithPreserveComment);
CppUnit_addTest(pSuite, PropertyFileConfigurationTest, testLoadSaveWithPreserveComment);
return pSuite; return pSuite;
} }

View File

@@ -27,7 +27,10 @@ public:
virtual ~PropertyFileConfigurationTest(); virtual ~PropertyFileConfigurationTest();
void testLoad(); void testLoad();
void testLoadEmpty();
void testLoadWithPreserveComment();
void testSave(); void testSave();
void testLoadSaveWithPreserveComment();
void setUp(); void setUp();
void tearDown(); void tearDown();
@@ -36,6 +39,8 @@ public:
private: private:
virtual Poco::Util::AbstractConfiguration* allocConfiguration() const; virtual Poco::Util::AbstractConfiguration* allocConfiguration() const;
void testLoad(bool preserveComment);
}; };