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