| // ---------------------------------------------------------------------------- |
| // 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 |