I'm currently working to implement a simple configuration file parser. I intend to add more functional down the road.
ConfigReader.hpp
#include <string>
#include <vector>
class ConfigReader
{
public:
struct record
{
std::string section;
std::string name;
std::string value;
};
ConfigReader();
explicit ConfigReader (const std::string & file);
bool readfile (const std::string & file);
std::string get_string (const std::string & tsection,
const std::string & tname, std::string tdefault = std::string());
private:
std::vector<record> records;
};
ConfigReader.cpp
#include <fstream>
#include "ConfigReader.hpp"
namespace
{
/* Erases leading tabs, leading spaces; trailing tabs, trailing spaces. */
std::string & trim (std::string & str)
{
// leading tabs / spaces
int i = 0;
while (i < (int) str.length() && (str[i] == ' ' || str[i] == '\t'))
++i;
// erase leading tabs / spaces
if (i > 0)
str.erase (0, i);
int j = i = str.length();
while (i > 0 && (str[i - 1] == ' ' || str[i - 1] == '\t'))
--i;
// erase trailing tabs / spaces
if (i < j)
str.erase (i, j);
return str;
}
/* Erases tabs and spaces between the variable's name and its value. */
std::string & normalize (std::string & str)
{
// Erases leading tabs, leading spaces; trailing tabs, trailing spaces.
trim (str);
// i is the start of the section of tabs and spaces.
// j is the end of said section.
std::size_t i, j;
i = j = 0;
while (i < str.length())
{
if (str[i] == ' ' || str[i] == '\t')
{
j = i + 1;
// find the end of section of tabs and spaces.
while (j < str.length() && (str[j] == ' ' || str[j] == '\t'))
++j;
// if the section consists of just one character,
// then erase just one character.
// otherwise, remove j - i characters.
if (j == i)
str.erase (i, 1);
else
str.erase (i, j - i);
}
else
{
str[i] = std::tolower (str[i]);
++i;
}
}
return str;
}
/* Check if a line consists only of spaces and tabs */
bool spaceonly (const std::string & line)
{
for (int i = 0, j = line.length(); i < j; ++i)
{
if (line[i] != ' ' && line[i] != '\t')
return false;
}
return true;
}
/* Check if a line is valid */
bool isvalid (std::string & line)
{
normalize (line);
std::size_t i = 0;
// if the line is a section
if (line[i] == '[')
{
// find where the section's name ends
std::size_t j = line.find_last_of (']');
// if the ']' character wasn't found, then the line is invalid.
if (j == std::string::npos)
return false;
// if the distance between '[' and ']' is equal to one,
// then there are no characters between section brackets -> invalid line.
if (j - i == 1)
return false;
}
/* Check if a line is a comment */
else if (line[i] == ';' || line[i] == '#' || (line[i] == '/' && line[i + 1] == '/'))
return false;
/* Check if a line is ill-formed */
else if (line[i] == '=' || line[i] == ']')
return false;
else
{
std::size_t j = line.find_last_of ('=');
if (j == std::string::npos)
return false;
if (j + 1 >= line.length())
return false;
}
return true;
}
// parse the line and write the content to our vector
void parse (std::vector<ConfigReader::record> & records, std::string & section, std::string & line)
{
std::size_t i = 0;
// if the line is a section
if (line[i] == '[')
{
++i;
std::size_t j = line.find_last_of (']') - 1;
section = line.substr (i, j);
}
// if the line is a variable + value
else
{
ConfigReader::record temp;
temp.section = section;
// construct the name of the variable
std::size_t j = line.find ('=');
std::string name = line.substr (i, j);
temp.name = name;
// construct the variable's value
std::size_t k = line.find ('=') + 1;
std::size_t z = line.find (';');
z = (z == std::string::npos) ? line.length() : z;
std::string value = line.substr (k, z); // bug ? if the line is width = 32; then the semicolon is not erased.
temp.value = value;
records.push_back (temp);
}
}
}
bool ConfigReader::readfile (const std::string & file)
{
records.clear();
std::ifstream config (file);
if (!config.is_open())
return false;
std::string section;
std::string buffer;
std::size_t i = 0;
while (std::getline (config, buffer, '\n'))
{
if (!spaceonly (buffer))
{
if (isvalid (buffer))
parse (records, section, buffer);
else{}
// std::cout << "Failed at line " << i;
}
++i;
}
return true;
}
std::string ConfigReader::get_string (const std::string & tsection,
const std::string & tname, std::string tdefault)
{
for (std::size_t i = 0; i < records.size(); ++i)
{
if (records[i].section == tsection && records[i].name == tname)
{
return records[i].value;
}
}
record temp;
temp.section = tsection;
temp.name = tname;
temp.value = tdefault;
records.push_back (temp);
return tdefault;
}
ConfigReader::ConfigReader (const std::string & file) : records()
{
readfile (file);
}
ConfigReader::ConfigReader() : records()
{
}
The syntax of the config file is simple:
[video]
width = 1920;
etc;
What should I change? Improve? Are there any errors? (Well, there is one in parse.)