#include <fstream>
#include <functional>
#include <numeric>
#include <list>
#include <vector>
#include <string>
#include <boost/spirit.hpp>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace boost::spirit;
typedef pair<string, string> Entry; // запись
typedef list<Entry> Entries; // список записей
typedef pair<string, Entries> Section; // секция
typedef list<Section> IniData; // данные ini-файла
/*
Функторы
*/
// Добавляет пустую секцию
struct add_section
{
add_section( IniData & data ) : data_(data) {}
void operator()( char const * p, char const * q ) const
{
string s(p, q);
boost::algorithm::trim(s);
data_.push_back( Section( s, Entries() ) );
}
IniData & data_;
};
// Добавляет пустую запись
struct add_key
{
add_key( IniData & data ) : data_(data) {}
void operator()( char const * p, char const * q ) const
{
string s(p, q);
boost::algorithm::trim(s);
data_.back().second.push_back( Entry( s, string() ) );
}
IniData & data_;
};
// Заполняет запись
struct add_value
{
add_value( IniData & data ) : data_(data) {}
void operator()( char const * p, char const * q ) const
{
data_.back().second.back().second.assign(p, q);
}
IniData & data_;
};
struct inidata_parser : public grammar<inidata_parser>
{
// Получаем и сохраняем пустую IniData
inidata_parser( IniData & data ) : data_(data) {}
template <typename ScannerT>
struct definition
{
// rule — это парсеры для каждого из нетерминалов
// нашей грамматики в форме Бэкуса-Наура
rule<ScannerT> inidata, section, entry, ident, value, stringSpaces, spaces;
// start возвращает ссылку на главный нетерминал - inidata
rule<ScannerT> const& start() const { return inidata; }
definition( inidata_parser const & self )
{
// inidata состоит из нескольких секций -
// это выражается звездочкой
inidata = *section;
section = ch_p('[') // ch_p - парсит один символ
>> ident[add_section(self.data_)]
>> ch_p(']')
>> stringSpaces
>> ch_p('\n')
>> spaces
>> *(entry);
entry = ident[add_key(self.data_)]
>> stringSpaces
>> ch_p('=')
>> stringSpaces
>> value[add_value(self.data_)]
>> spaces;
// "+" слева означает хотя бы один
// alnum_p — встроенный парсер для букв и цифр
// chset<> соответствует любому символу из строки (важно,
// что минус идёт первым, иначе он воспринимается
// как знак интервала, вроде «a-z»)
ident = +( alnum_p | chset<>("-_.,){}#@&*|") );
// "~" означает отрицание
value = *(~ch_p('\n'));
// blank_p - пробел или табуляция
stringSpaces = *blank_p;
// space_p - любой пробельный символ
// в т.ч. и переводу строки и возврату каретки
spaces = *space_p;
}
};
IniData & data_;
};
// Функтор для определения комментариев
struct is_comment
{
bool operator()( string const & s ) const
{
return (s[0] == '\n') || (s[0] == ';');
}
};
struct first_is
{
first_is( std::string const & s ) : s_(s) {}
template< class Pair >
bool operator()( Pair const & p ) const
{
return p.first == s_;
}
string const & s_;
};
//************************************
// Method: find_value
// Returns: bool
// Qualifier: поиск значения параметра в IniData
// Parameter: IniData const & ini
// Parameter: string const & s - имя секции
// Parameter: string const & p - имя параметра
// Parameter: string & res - найденое значение параметра
//************************************
bool find_value( IniData const & ini, string const & s, string const & p, string & res )
{
// ищем в списке секцию по имени
IniData::const_iterator sit = find_if(ini.begin(), ini.end(), first_is(s));
if ( sit == ini.end() )
return false;
// ищем в списке записей секции параметр по имени
Entries::const_iterator it = find_if(sit->second.begin(), sit->second.end(), first_is(p));
if ( it == sit->second.end() )
return false;
// все нашлось, возвращаем значение параметра через параметр-ссылку
res = it->second;
return true;
}
int main(int argc, char **argv)
{
// Проверяем количество входных параметров
if ( argc != 4 )
{
cout << "Usage: " << argv[0] << " <file.ini> <section> <parameter>" << endl;
return 0;
}
// Открываем файл
ifstream in(argv[1]);
if ( !in )
{
cout << "Can't open file \"" << argv[1] << '\"' << endl;
return 0;
}
// Массив строк. В него будет считываться файл
vector< string > lns;
std::string s;
// Считываем файл в массив lns
while ( !in.eof() )
{
std::getline(in, s);
boost::algorithm::trim(s);
lns.push_back( s += '\n' );
}
// Удаляем все комментарии из массива lns
lns.erase( remove_if(lns.begin(), lns.end(), is_comment()), lns.end() );
// Склеиваем все строчки в одну
string text = accumulate( lns.begin(), lns.end(), string() );
// Создаем и инициализируем парсер
IniData data;
inidata_parser parser(data);
BOOST_SPIRIT_DEBUG_NODE(parser);
// Запускаем парсер
// 1-й аргумент - это текст
// 2-й агрумент - парсер
// 3-й аргумент - парсер для пропускаемых символов (скажем, мы хотели бы
// пропускать все пробелы). В нашем случае парсер для
// пропускаемых символов будет пустым — nothing_p (
// т.е. ничего не парсящий)
// Результат - структура parse_info<>
parse_info<> info = parse(text.c_str(), parser, nothing_p);
// Проверяем, не произошло ли ошибок во время парсинга
if ( !info.hit )
{
cout << "Parse error!" << endl;
return 1;
}
// Ищем параметр
string res;
if ( find_value(data, argv[2], argv[3], res ))
{
cout << res;
} else {
cout << "Can't find requested parameter";
}
cout << endl;
return 0;
}