cpp-utilities/io/inifile.cpp

167 lines
4.6 KiB
C++

#include "./inifile.h"
#include "./catchiofailure.h"
#include <iostream>
using namespace std;
namespace IoUtilities {
/*!
* \class IniFile
* \brief The IniFile class parses and makes INI files.
*/
/*!
* \brief Parses all data from the specified \a inputStream.
*/
void IniFile::parse(std::istream &inputStream)
{
// define variable for state machine
enum State { Init, Comment, ScopeName, Key, Value } state = Init;
// current character
char c;
// number of postponed whitespaces
unsigned int whitespace = 0;
// current scope, key and value
string scope, key, value;
scope.reserve(16);
key.reserve(16);
value.reserve(256);
// define actions for state machine
// called when key/value pair is complete
const auto finishKeyValue = [&state, &scope, &key, &value, &whitespace, this] {
if (key.empty() && value.empty() && state != Value) {
return;
}
if (m_data.empty() || m_data.back().first != scope) {
m_data.emplace_back(make_pair(scope, decltype(m_data)::value_type::second_type()));
}
m_data.back().second.insert(make_pair(key, value));
key.clear();
value.clear();
whitespace = 0;
};
// called to add current character to current key or value
const auto addChar = [&whitespace, &c](string &to) {
if (c == ' ') {
++whitespace;
} else {
if (!to.empty()) {
while (whitespace) {
to += ' ';
--whitespace;
}
} else {
whitespace = 0;
}
to += c;
}
};
// thorw an exception when an IO error occurs
inputStream.exceptions(ios_base::failbit | ios_base::badbit);
// parse the file char by char
try {
while (inputStream.get(c)) {
switch (state) {
case Init:
switch (c) {
case '\n':
break;
case '#':
state = Comment;
break;
case '=':
whitespace = 0;
state = Value;
break;
case '[':
scope.clear();
state = ScopeName;
break;
default:
addChar(key);
state = Key;
}
break;
case Key:
switch (c) {
case '\n':
finishKeyValue();
state = Init;
break;
case '#':
finishKeyValue();
state = Comment;
break;
case '=':
whitespace = 0;
state = Value;
break;
default:
addChar(key);
}
break;
case Comment:
switch (c) {
case '\n':
state = Init;
break;
default:;
}
break;
case ScopeName:
switch (c) {
case ']':
state = Init;
break;
default:
scope += c;
}
break;
case Value:
switch (c) {
case '\n':
finishKeyValue();
state = Init;
break;
case '#':
finishKeyValue();
state = Comment;
break;
default:
addChar(value);
}
break;
}
}
} catch (...) {
const char *what = catchIoFailure();
if (inputStream.eof()) {
// we just reached the end of the file
// don't forget to save the last key/value pair
finishKeyValue();
} else {
throwIoFailure(what);
}
}
}
/*!
* \brief Write the current data to the specified \a outputStream.
*/
void IniFile::make(ostream &outputStream)
{
// thorw an exception when an IO error occurs
outputStream.exceptions(ios_base::failbit | ios_base::badbit);
for (const auto &scope : m_data) {
outputStream << '[' << scope.first << ']' << '\n';
for (const auto &field : scope.second) {
outputStream << field.first << '=' << field.second << '\n';
}
outputStream << '\n';
}
}
} // namespace IoUtilities