mirror of
https://github.com/pocoproject/poco.git
synced 2025-10-29 12:18:01 +01:00
implement GH #700: PropertyFileConfiguration preserve comments.
This commit is contained in:
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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,37 +79,25 @@ void PropertyFileConfiguration::load(const std::string& path)
|
|||||||
|
|
||||||
void PropertyFileConfiguration::save(std::ostream& ostr) const
|
void PropertyFileConfiguration::save(std::ostream& ostr) const
|
||||||
{
|
{
|
||||||
MapConfiguration::iterator it = begin();
|
if (_preserveComment)
|
||||||
MapConfiguration::iterator ed = end();
|
|
||||||
while (it != ed)
|
|
||||||
{
|
{
|
||||||
ostr << it->first << ": ";
|
// Check the starting char of each line in _fileContent.
|
||||||
for (std::string::const_iterator its = it->second.begin(); its != it->second.end(); ++its)
|
// 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)
|
if (isComment((*it)[0])) ostr << *it;
|
||||||
{
|
else outputKeyValue(ostr, *it, getString(*it));
|
||||||
case '\t':
|
}
|
||||||
ostr << "\\t";
|
} else
|
||||||
break;
|
{
|
||||||
case '\r':
|
MapConfiguration::iterator it = begin();
|
||||||
ostr << "\\r";
|
MapConfiguration::iterator ed = end();
|
||||||
break;
|
while (it != ed)
|
||||||
case '\n':
|
{
|
||||||
ostr << "\\n";
|
outputKeyValue(ostr, it->first, it->second);
|
||||||
break;
|
++it;
|
||||||
case '\f':
|
|
||||||
ostr << "\\f";
|
|
||||||
break;
|
|
||||||
case '\\':
|
|
||||||
ostr << "\\\\";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
ostr << *its;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ostr << "\n";
|
|
||||||
++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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user