Added INIParser support for quoted values and names, for unquoted use of # and ; and for files not ending with a newline

This commit is contained in:
Ingo Ruhnke 2011-02-24 04:36:14 +01:00
parent da371f05e6
commit 3d1f13e2ba
3 changed files with 150 additions and 29 deletions

View file

@ -59,36 +59,43 @@ INIParser::run()
}
else // assume name=value pair
{
std::string n;
std::string v;
std::string name;
std::string value;
n = get_ident();
whitespace();
expect('=');
name = get_ident_or_string();
whitespace();
if (accept(';') || accept('#'))
{ // "foobar = ; comment here, value empty"
{ // "name"
eat_rest_of_line();
newline();
}
else
else if (accept('='))
{
if (accept('"'))
{
v = get_string();
expect('"');
whitespace();
whitespace();
if (accept(';') || accept('#'))
{ // "name = # comment"
eat_rest_of_line();
newline();
}
else
{
v = get_value();
{ // "name = value"
value = get_value_or_string();
whitespace();
if (accept(';') || accept('#'))
{ // "name = value # comment"
eat_rest_of_line();
newline();
}
else
{
newline();
}
}
if (accept(';') || accept('#'))
eat_rest_of_line();
newline();
}
m_builder.send_pair(n, v);
m_builder.send_pair(name, value);
}
}
}
@ -158,23 +165,56 @@ INIParser::expect(char c)
}
}
std::string
INIParser::get_value_or_string()
{
if (accept('"'))
{
std::string str = get_string();
expect('"');
return str;
}
else
{
return get_value();
}
}
std::string
INIParser::get_ident_or_string()
{
if (accept('"'))
{
std::string str = get_string();
expect('"');
return str;
}
else
{
return get_ident();
}
}
std::string
INIParser::get_value()
{
// a value is terminated either by a newline or a comment character,
// whitespace at the end of the value will be trimmed
// an unquoted value is terminated either by a newline or a comment
// character, whitespace at the end of the value will be trimmed
std::ostringstream str;
std::string::size_type last_char = std::string::npos;
std::string::size_type cur = 0;
while(peek() != '\n' &&
peek() != ';' && peek() != '#')
char last_c = -1;
while(peek() != '\n' &&
peek() != -1 &&
!((last_c == ' ' || last_c == '\t') && (peek() == ';' || peek() == '#')))
{
if (peek() != ' ' && peek() != '\t')
{
last_char = cur;
}
str << static_cast<char>(peek());
last_c = static_cast<char>(peek());
str << last_c;
next();
cur += 1;
@ -193,18 +233,43 @@ INIParser::get_value()
std::string
INIParser::get_ident()
{
// an unquoted value is terminated either by a newline or a comment
// character, whitespace at the end of the value will be trimmed
std::ostringstream str;
while(peek() != '=' && peek() != ' ' && peek() != '\t' && peek() != '\n')
std::string::size_type last_char = std::string::npos;
std::string::size_type cur = 0;
char last_c = -1;
while(peek() != '\n' &&
peek() != -1 &&
peek() != '=' &&
!((last_c == ' ' || last_c == '\t') && (peek() == ';' || peek() == '#')))
{
str << static_cast<char>(peek());
if (peek() != ' ' && peek() != '\t')
{
last_char = cur;
}
last_c = static_cast<char>(peek());
str << last_c;
next();
cur += 1;
}
if (last_char == std::string::npos)
{
return str.str();
}
else
{
return str.str().substr(0, last_char + 1);
}
return str.str();
}
std::string
INIParser::get_string()
{
// reads a string, handles escaping, does not eat begin and end quotes
std::ostringstream str;
while(peek() != '"')
{
@ -235,7 +300,11 @@ INIParser::get_string()
void
INIParser::newline()
{
if (peek() != '\n')
if (peek() == -1)
{
return;
}
else if (peek() != '\n')
{
error("expected newline");
}
@ -248,7 +317,7 @@ INIParser::newline()
void
INIParser::eat_rest_of_line()
{
while(peek() != '\n')
while(peek() != '\n' && peek() != -1)
{
next();
}

View file

@ -47,6 +47,8 @@ private:
std::string get_string();
std::string get_value();
std::string get_ident();
std::string get_value_or_string();
std::string get_ident_or_string();
void newline();
void eat_rest_of_line();
std::string get_section();

50
test/ini_parser_test.cpp Normal file
View file

@ -0,0 +1,50 @@
#include <iostream>
#include <sstream>
#include "ini_parser.hpp"
#include "ini_builder.hpp"
class INIParserTestBuilder : public INIBuilder
{
public:
void send_section(const std::string& section)
{
std::cout << "[" << section << "]" << std::endl;
}
void send_pair(const std::string& name, const std::string& value)
{
std::cout << "\"" << name << "\" = \"" << value << "\"" << std::endl;
}
};
int main()
try
{
std::istringstream in(
"[test]\n"
"name = value1 # comment\n"
"name = value2\n"
" name = value3 \n"
"name#foo = value#foo # comment\n"
"\n"
" name#foo = value#foo # comment\n"
" name#foo = value#foo # comment\n"
" name#empty \n"
" name#empty =\n"
"\"name#not-empty\" = \"value#foo\" # comment\n"
" foo bar = oing # foo"
);
INIParserTestBuilder builder;
INIParser parser(in, builder, "test");
parser.run();
return 0;
}
catch(const std::exception& err)
{
std::cout << "exception: " << err.what() << std::endl;
}
/* EOF */