// ---------------------------------------------------------------------------- | |
// Copyright (C) 2002-2006 Marcin Kalicinski | |
// | |
// Distributed under the Boost Software License, Version 1.0. | |
// (See accompanying file LICENSE_1_0.txt or copy at | |
// http://www.boost.org/LICENSE_1_0.txt) | |
// | |
// For more information, see www.boost.org | |
// ---------------------------------------------------------------------------- | |
#ifndef BOOST_PROPERTY_TREE_DETAIL_INFO_PARSER_READ_HPP_INCLUDED | |
#define BOOST_PROPERTY_TREE_DETAIL_INFO_PARSER_READ_HPP_INCLUDED | |
#include "boost/property_tree/ptree.hpp" | |
#include "boost/property_tree/detail/info_parser_error.hpp" | |
#include "boost/property_tree/detail/info_parser_utils.hpp" | |
#include <iterator> | |
#include <string> | |
#include <stack> | |
#include <fstream> | |
#include <cctype> | |
namespace boost { namespace property_tree { namespace info_parser | |
{ | |
// Expand known escape sequences | |
template<class It> | |
std::basic_string<typename std::iterator_traits<It>::value_type> | |
expand_escapes(It b, It e) | |
{ | |
typedef typename std::iterator_traits<It>::value_type Ch; | |
std::basic_string<Ch> result; | |
while (b != e) | |
{ | |
if (*b == Ch('\\')) | |
{ | |
++b; | |
if (b == e) | |
{ | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"character expected after backslash", "", 0)); | |
} | |
else if (*b == Ch('0')) result += Ch('\0'); | |
else if (*b == Ch('a')) result += Ch('\a'); | |
else if (*b == Ch('b')) result += Ch('\b'); | |
else if (*b == Ch('f')) result += Ch('\f'); | |
else if (*b == Ch('n')) result += Ch('\n'); | |
else if (*b == Ch('r')) result += Ch('\r'); | |
else if (*b == Ch('t')) result += Ch('\t'); | |
else if (*b == Ch('v')) result += Ch('\v'); | |
else if (*b == Ch('"')) result += Ch('"'); | |
else if (*b == Ch('\'')) result += Ch('\''); | |
else if (*b == Ch('\\')) result += Ch('\\'); | |
else | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"unknown escape sequence", "", 0)); | |
} | |
else | |
result += *b; | |
++b; | |
} | |
return result; | |
} | |
// Advance pointer past whitespace | |
template<class Ch> | |
void skip_whitespace(const Ch *&text) | |
{ | |
using namespace std; | |
while (isspace(*text)) | |
++text; | |
} | |
// Extract word (whitespace delimited) and advance pointer accordingly | |
template<class Ch> | |
std::basic_string<Ch> read_word(const Ch *&text) | |
{ | |
using namespace std; | |
skip_whitespace(text); | |
const Ch *start = text; | |
while (!isspace(*text) && *text != Ch(';') && *text != Ch('\0')) | |
++text; | |
return expand_escapes(start, text); | |
} | |
// Extract line (eol delimited) and advance pointer accordingly | |
template<class Ch> | |
std::basic_string<Ch> read_line(const Ch *&text) | |
{ | |
using namespace std; | |
skip_whitespace(text); | |
const Ch *start = text; | |
while (*text != Ch('\0') && *text != Ch(';')) | |
++text; | |
while (text > start && isspace(*(text - 1))) | |
--text; | |
return expand_escapes(start, text); | |
} | |
// Extract string (inside ""), and advance pointer accordingly | |
// Set need_more_lines to true if \ continuator found | |
template<class Ch> | |
std::basic_string<Ch> read_string(const Ch *&text, bool *need_more_lines) | |
{ | |
skip_whitespace(text); | |
if (*text == Ch('\"')) | |
{ | |
// Skip " | |
++text; | |
// Find end of string, but skip escaped " | |
bool escaped = false; | |
const Ch *start = text; | |
while ((escaped || *text != Ch('\"')) && *text != Ch('\0')) | |
{ | |
escaped = (!escaped && *text == Ch('\\')); | |
++text; | |
} | |
// If end of string found | |
if (*text == Ch('\"')) | |
{ | |
std::basic_string<Ch> result = expand_escapes(start, text++); | |
skip_whitespace(text); | |
if (*text == Ch('\\')) | |
{ | |
if (!need_more_lines) | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"unexpected \\", "", 0)); | |
++text; | |
skip_whitespace(text); | |
if (*text == Ch('\0') || *text == Ch(';')) | |
*need_more_lines = true; | |
else | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"expected end of line after \\", "", 0)); | |
} | |
else | |
if (need_more_lines) | |
*need_more_lines = false; | |
return result; | |
} | |
else | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"unexpected end of line", "", 0)); | |
} | |
else | |
BOOST_PROPERTY_TREE_THROW(info_parser_error("expected \"", "", 0)); | |
} | |
// Extract key | |
template<class Ch> | |
std::basic_string<Ch> read_key(const Ch *&text) | |
{ | |
skip_whitespace(text); | |
if (*text == Ch('\"')) | |
return read_string(text, NULL); | |
else | |
return read_word(text); | |
} | |
// Extract data | |
template<class Ch> | |
std::basic_string<Ch> read_data(const Ch *&text, bool *need_more_lines) | |
{ | |
skip_whitespace(text); | |
if (*text == Ch('\"')) | |
return read_string(text, need_more_lines); | |
else | |
{ | |
*need_more_lines = false; | |
return read_word(text); | |
} | |
} | |
// Build ptree from info stream | |
template<class Ptree, class Ch> | |
void read_info_internal(std::basic_istream<Ch> &stream, | |
Ptree &pt, | |
const std::string &filename, | |
int include_depth) | |
{ | |
typedef std::basic_string<Ch> str_t; | |
// Possible parser states | |
enum state_t { | |
s_key, // Parser expects key | |
s_data, // Parser expects data | |
s_data_cont // Parser expects data continuation | |
}; | |
unsigned long line_no = 0; | |
state_t state = s_key; // Parser state | |
Ptree *last = NULL; // Pointer to last created ptree | |
// Define line here to minimize reallocations | |
str_t line; | |
// Initialize ptree stack (used to handle nesting) | |
std::stack<Ptree *> stack; | |
stack.push(&pt); // Push root ptree on stack initially | |
try { | |
// While there are characters in the stream | |
while (stream.good()) { | |
// Read one line from stream | |
++line_no; | |
std::getline(stream, line); | |
if (!stream.good() && !stream.eof()) | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"read error", filename, line_no)); | |
const Ch *text = line.c_str(); | |
// If directive found | |
skip_whitespace(text); | |
if (*text == Ch('#')) { | |
// Determine directive type | |
++text; // skip # | |
std::basic_string<Ch> directive = read_word(text); | |
if (directive == convert_chtype<Ch, char>("include")) { | |
// #include | |
if (include_depth > 100) { | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"include depth too large, " | |
"probably recursive include", | |
filename, line_no)); | |
} | |
str_t s = read_string(text, NULL); | |
std::string inc_name = | |
convert_chtype<char, Ch>(s.c_str()); | |
std::basic_ifstream<Ch> inc_stream(inc_name.c_str()); | |
if (!inc_stream.good()) | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"cannot open include file " + inc_name, | |
filename, line_no)); | |
read_info_internal(inc_stream, *stack.top(), | |
inc_name, include_depth + 1); | |
} else { // Unknown directive | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"unknown directive", filename, line_no)); | |
} | |
// Directive must be followed by end of line | |
skip_whitespace(text); | |
if (*text != Ch('\0')) { | |
BOOST_PROPERTY_TREE_THROW(info_parser_error( | |
"expected end of line", filename, line_no)); | |
} | |
// Go to next line | |
continue; | |
} | |
// While there are characters left in line | |
while (1) { | |
// Stop parsing on end of line or comment | |
skip_whitespace(text); | |
if (*text == Ch('\0') || *text == Ch(';')) { | |
if (state == s_data) // If there was no data set state to s_key | |
state = s_key; | |
break; | |
} | |
// Process according to current parser state | |
switch (state) | |
{ | |
// Parser expects key | |
case s_key: | |
{ | |
if (*text == Ch('{')) // Brace opening found | |
{ | |
if (!last) | |
BOOST_PROPERTY_TREE_THROW(info_parser_error("unexpected {", "", 0)); | |
stack.push(last); | |
last = NULL; | |
++text; | |
} | |
else if (*text == Ch('}')) // Brace closing found | |
{ | |
if (stack.size() <= 1) | |
BOOST_PROPERTY_TREE_THROW(info_parser_error("unmatched }", "", 0)); | |
stack.pop(); | |
last = NULL; | |
++text; | |
} | |
else // Key text found | |
{ | |
std::basic_string<Ch> key = read_key(text); | |
last = &stack.top()->push_back( | |
std::make_pair(key, Ptree()))->second; | |
state = s_data; | |
} | |
}; break; | |
// Parser expects data | |
case s_data: | |
{ | |
// Last ptree must be defined because we are going to add data to it | |
BOOST_ASSERT(last); | |
if (*text == Ch('{')) // Brace opening found | |
{ | |
stack.push(last); | |
last = NULL; | |
++text; | |
state = s_key; | |
} | |
else if (*text == Ch('}')) // Brace closing found | |
{ | |
if (stack.size() <= 1) | |
BOOST_PROPERTY_TREE_THROW(info_parser_error("unmatched }", "", 0)); | |
stack.pop(); | |
last = NULL; | |
++text; | |
state = s_key; | |
} | |
else // Data text found | |
{ | |
bool need_more_lines; | |
std::basic_string<Ch> data = read_data(text, &need_more_lines); | |
last->data() = data; | |
state = need_more_lines ? s_data_cont : s_key; | |
} | |
}; break; | |
// Parser expects continuation of data after \ on previous line | |
case s_data_cont: | |
{ | |
// Last ptree must be defined because we are going to update its data | |
BOOST_ASSERT(last); | |
if (*text == Ch('\"')) // Continuation must start with " | |
{ | |
bool need_more_lines; | |
std::basic_string<Ch> data = read_string(text, &need_more_lines); | |
last->put_value(last->template get_value<std::basic_string<Ch> >() + data); | |
state = need_more_lines ? s_data_cont : s_key; | |
} | |
else | |
BOOST_PROPERTY_TREE_THROW(info_parser_error("expected \" after \\ in previous line", "", 0)); | |
}; break; | |
// Should never happen | |
default: | |
BOOST_ASSERT(0); | |
} | |
} | |
} | |
// Check if stack has initial size, otherwise some {'s have not been closed | |
if (stack.size() != 1) | |
BOOST_PROPERTY_TREE_THROW(info_parser_error("unmatched {", "", 0)); | |
} | |
catch (info_parser_error &e) | |
{ | |
// If line undefined rethrow error with correct filename and line | |
if (e.line() == 0) | |
{ | |
BOOST_PROPERTY_TREE_THROW(info_parser_error(e.message(), filename, line_no)); | |
} | |
else | |
BOOST_PROPERTY_TREE_THROW(e); | |
} | |
} | |
} } } | |
#endif |