mirror of
https://github.com/pocoproject/poco.git
synced 2025-01-30 22:31:29 +01:00
Merge pull request #934 from aaron0x/PreserveComment
implement GH #700: PropertyFileConfiguration preserve comments.
This commit is contained in:
commit
50c84921fd
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user