Merge pull request #934 from aaron0x/PreserveComment

implement GH #700: PropertyFileConfiguration preserve comments.
This commit is contained in:
Aleksandar Fabijanic 2015-09-11 12:38:37 -05:00
commit 50c84921fd
4 changed files with 283 additions and 64 deletions

View File

@ -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);
};

View File

@ -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

View File

@ -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;
}

View File

@ -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);
};