1 // ---------------------------------------------------------------------------- 2 // Copyright (C) 2002-2006 Marcin Kalicinski 3 // 4 // Distributed under the Boost Software License, Version 1.0. 5 // (See accompanying file LICENSE_1_0.txt or copy at 6 // http://www.boost.org/LICENSE_1_0.txt) 7 // 8 // For more information, see www.boost.org 9 // ---------------------------------------------------------------------------- 10 #ifndef BOOST_PROPERTY_TREE_DETAIL_INFO_PARSER_READ_HPP_INCLUDED 11 #define BOOST_PROPERTY_TREE_DETAIL_INFO_PARSER_READ_HPP_INCLUDED 12 13 #include "boost/property_tree/ptree.hpp" 14 #include "boost/property_tree/detail/info_parser_error.hpp" 15 #include "boost/property_tree/detail/info_parser_utils.hpp" 16 #include <iterator> 17 #include <string> 18 #include <stack> 19 #include <fstream> 20 #include <cctype> 21 22 namespace boost { namespace property_tree { namespace info_parser 23 { 24 25 // Expand known escape sequences 26 template<class It> 27 std::basic_string<typename std::iterator_traits<It>::value_type> expand_escapes(It b,It e)28 expand_escapes(It b, It e) 29 { 30 typedef typename std::iterator_traits<It>::value_type Ch; 31 std::basic_string<Ch> result; 32 while (b != e) 33 { 34 if (*b == Ch('\\')) 35 { 36 ++b; 37 if (b == e) 38 { 39 BOOST_PROPERTY_TREE_THROW(info_parser_error( 40 "character expected after backslash", "", 0)); 41 } 42 else if (*b == Ch('0')) result += Ch('\0'); 43 else if (*b == Ch('a')) result += Ch('\a'); 44 else if (*b == Ch('b')) result += Ch('\b'); 45 else if (*b == Ch('f')) result += Ch('\f'); 46 else if (*b == Ch('n')) result += Ch('\n'); 47 else if (*b == Ch('r')) result += Ch('\r'); 48 else if (*b == Ch('t')) result += Ch('\t'); 49 else if (*b == Ch('v')) result += Ch('\v'); 50 else if (*b == Ch('"')) result += Ch('"'); 51 else if (*b == Ch('\'')) result += Ch('\''); 52 else if (*b == Ch('\\')) result += Ch('\\'); 53 else 54 BOOST_PROPERTY_TREE_THROW(info_parser_error( 55 "unknown escape sequence", "", 0)); 56 } 57 else 58 result += *b; 59 ++b; 60 } 61 return result; 62 } 63 64 // Detect whitespace in a not very smart way. 65 template <class Ch> is_ascii_space(Ch c)66 bool is_ascii_space(Ch c) 67 { 68 // Everything outside ASCII is not space. 69 unsigned n = c; 70 if (n > 127) 71 return false; 72 return isspace(c) != 0; 73 } 74 75 // Advance pointer past whitespace 76 template<class Ch> skip_whitespace(const Ch * & text)77 void skip_whitespace(const Ch *&text) 78 { 79 using namespace std; 80 while (is_ascii_space(*text)) 81 ++text; 82 } 83 84 // Extract word (whitespace delimited) and advance pointer accordingly 85 template<class Ch> read_word(const Ch * & text)86 std::basic_string<Ch> read_word(const Ch *&text) 87 { 88 using namespace std; 89 skip_whitespace(text); 90 const Ch *start = text; 91 while (!is_ascii_space(*text) && *text != Ch(';') && *text != Ch('\0')) 92 ++text; 93 return expand_escapes(start, text); 94 } 95 96 // Extract line (eol delimited) and advance pointer accordingly 97 template<class Ch> read_line(const Ch * & text)98 std::basic_string<Ch> read_line(const Ch *&text) 99 { 100 using namespace std; 101 skip_whitespace(text); 102 const Ch *start = text; 103 while (*text != Ch('\0') && *text != Ch(';')) 104 ++text; 105 while (text > start && is_ascii_space(*(text - 1))) 106 --text; 107 return expand_escapes(start, text); 108 } 109 110 // Extract string (inside ""), and advance pointer accordingly 111 // Set need_more_lines to true if \ continuator found 112 template<class Ch> read_string(const Ch * & text,bool * need_more_lines)113 std::basic_string<Ch> read_string(const Ch *&text, bool *need_more_lines) 114 { 115 skip_whitespace(text); 116 if (*text == Ch('\"')) 117 { 118 119 // Skip " 120 ++text; 121 122 // Find end of string, but skip escaped " 123 bool escaped = false; 124 const Ch *start = text; 125 while ((escaped || *text != Ch('\"')) && *text != Ch('\0')) 126 { 127 escaped = (!escaped && *text == Ch('\\')); 128 ++text; 129 } 130 131 // If end of string found 132 if (*text == Ch('\"')) 133 { 134 std::basic_string<Ch> result = expand_escapes(start, text++); 135 skip_whitespace(text); 136 if (*text == Ch('\\')) 137 { 138 if (!need_more_lines) 139 BOOST_PROPERTY_TREE_THROW(info_parser_error( 140 "unexpected \\", "", 0)); 141 ++text; 142 skip_whitespace(text); 143 if (*text == Ch('\0') || *text == Ch(';')) 144 *need_more_lines = true; 145 else 146 BOOST_PROPERTY_TREE_THROW(info_parser_error( 147 "expected end of line after \\", "", 0)); 148 } 149 else 150 if (need_more_lines) 151 *need_more_lines = false; 152 return result; 153 } 154 else 155 BOOST_PROPERTY_TREE_THROW(info_parser_error( 156 "unexpected end of line", "", 0)); 157 158 } 159 else 160 BOOST_PROPERTY_TREE_THROW(info_parser_error("expected \"", "", 0)); 161 } 162 163 // Extract key 164 template<class Ch> read_key(const Ch * & text)165 std::basic_string<Ch> read_key(const Ch *&text) 166 { 167 skip_whitespace(text); 168 if (*text == Ch('\"')) 169 return read_string(text, NULL); 170 else 171 return read_word(text); 172 } 173 174 // Extract data 175 template<class Ch> read_data(const Ch * & text,bool * need_more_lines)176 std::basic_string<Ch> read_data(const Ch *&text, bool *need_more_lines) 177 { 178 skip_whitespace(text); 179 if (*text == Ch('\"')) 180 return read_string(text, need_more_lines); 181 else 182 { 183 *need_more_lines = false; 184 return read_word(text); 185 } 186 } 187 188 // Build ptree from info stream 189 template<class Ptree, class Ch> read_info_internal(std::basic_istream<Ch> & stream,Ptree & pt,const std::string & filename,int include_depth)190 void read_info_internal(std::basic_istream<Ch> &stream, 191 Ptree &pt, 192 const std::string &filename, 193 int include_depth) 194 { 195 typedef std::basic_string<Ch> str_t; 196 // Possible parser states 197 enum state_t { 198 s_key, // Parser expects key 199 s_data, // Parser expects data 200 s_data_cont // Parser expects data continuation 201 }; 202 203 unsigned long line_no = 0; 204 state_t state = s_key; // Parser state 205 Ptree *last = NULL; // Pointer to last created ptree 206 // Define line here to minimize reallocations 207 str_t line; 208 209 // Initialize ptree stack (used to handle nesting) 210 std::stack<Ptree *> stack; 211 stack.push(&pt); // Push root ptree on stack initially 212 213 try { 214 // While there are characters in the stream 215 while (stream.good()) { 216 // Read one line from stream 217 ++line_no; 218 std::getline(stream, line); 219 if (!stream.good() && !stream.eof()) 220 BOOST_PROPERTY_TREE_THROW(info_parser_error( 221 "read error", filename, line_no)); 222 const Ch *text = line.c_str(); 223 224 // If directive found 225 skip_whitespace(text); 226 if (*text == Ch('#')) { 227 // Determine directive type 228 ++text; // skip # 229 std::basic_string<Ch> directive = read_word(text); 230 if (directive == convert_chtype<Ch, char>("include")) { 231 // #include 232 if (include_depth > 100) { 233 BOOST_PROPERTY_TREE_THROW(info_parser_error( 234 "include depth too large, " 235 "probably recursive include", 236 filename, line_no)); 237 } 238 str_t s = read_string(text, NULL); 239 std::string inc_name = 240 convert_chtype<char, Ch>(s.c_str()); 241 std::basic_ifstream<Ch> inc_stream(inc_name.c_str()); 242 if (!inc_stream.good()) 243 BOOST_PROPERTY_TREE_THROW(info_parser_error( 244 "cannot open include file " + inc_name, 245 filename, line_no)); 246 read_info_internal(inc_stream, *stack.top(), 247 inc_name, include_depth + 1); 248 } else { // Unknown directive 249 BOOST_PROPERTY_TREE_THROW(info_parser_error( 250 "unknown directive", filename, line_no)); 251 } 252 253 // Directive must be followed by end of line 254 skip_whitespace(text); 255 if (*text != Ch('\0')) { 256 BOOST_PROPERTY_TREE_THROW(info_parser_error( 257 "expected end of line", filename, line_no)); 258 } 259 260 // Go to next line 261 continue; 262 } 263 264 // While there are characters left in line 265 while (1) { 266 267 // Stop parsing on end of line or comment 268 skip_whitespace(text); 269 if (*text == Ch('\0') || *text == Ch(';')) { 270 if (state == s_data) // If there was no data set state to s_key 271 state = s_key; 272 break; 273 } 274 275 // Process according to current parser state 276 switch (state) 277 { 278 279 // Parser expects key 280 case s_key: 281 { 282 283 if (*text == Ch('{')) // Brace opening found 284 { 285 if (!last) 286 BOOST_PROPERTY_TREE_THROW(info_parser_error("unexpected {", "", 0)); 287 stack.push(last); 288 last = NULL; 289 ++text; 290 } 291 else if (*text == Ch('}')) // Brace closing found 292 { 293 if (stack.size() <= 1) 294 BOOST_PROPERTY_TREE_THROW(info_parser_error("unmatched }", "", 0)); 295 stack.pop(); 296 last = NULL; 297 ++text; 298 } 299 else // Key text found 300 { 301 std::basic_string<Ch> key = read_key(text); 302 last = &stack.top()->push_back( 303 std::make_pair(key, Ptree()))->second; 304 state = s_data; 305 } 306 307 }; break; 308 309 // Parser expects data 310 case s_data: 311 { 312 313 // Last ptree must be defined because we are going to add data to it 314 BOOST_ASSERT(last); 315 316 if (*text == Ch('{')) // Brace opening found 317 { 318 stack.push(last); 319 last = NULL; 320 ++text; 321 state = s_key; 322 } 323 else if (*text == Ch('}')) // Brace closing found 324 { 325 if (stack.size() <= 1) 326 BOOST_PROPERTY_TREE_THROW(info_parser_error("unmatched }", "", 0)); 327 stack.pop(); 328 last = NULL; 329 ++text; 330 state = s_key; 331 } 332 else // Data text found 333 { 334 bool need_more_lines; 335 std::basic_string<Ch> data = read_data(text, &need_more_lines); 336 last->data() = data; 337 state = need_more_lines ? s_data_cont : s_key; 338 } 339 340 341 }; break; 342 343 // Parser expects continuation of data after \ on previous line 344 case s_data_cont: 345 { 346 347 // Last ptree must be defined because we are going to update its data 348 BOOST_ASSERT(last); 349 350 if (*text == Ch('\"')) // Continuation must start with " 351 { 352 bool need_more_lines; 353 std::basic_string<Ch> data = read_string(text, &need_more_lines); 354 last->put_value(last->template get_value<std::basic_string<Ch> >() + data); 355 state = need_more_lines ? s_data_cont : s_key; 356 } 357 else 358 BOOST_PROPERTY_TREE_THROW(info_parser_error("expected \" after \\ in previous line", "", 0)); 359 360 }; break; 361 362 // Should never happen 363 default: 364 BOOST_ASSERT(0); 365 366 } 367 } 368 } 369 370 // Check if stack has initial size, otherwise some {'s have not been closed 371 if (stack.size() != 1) 372 BOOST_PROPERTY_TREE_THROW(info_parser_error("unmatched {", "", 0)); 373 374 } 375 catch (info_parser_error &e) 376 { 377 // If line undefined rethrow error with correct filename and line 378 if (e.line() == 0) 379 { 380 BOOST_PROPERTY_TREE_THROW(info_parser_error(e.message(), filename, line_no)); 381 } 382 else 383 BOOST_PROPERTY_TREE_THROW(e); 384 385 } 386 387 } 388 389 } } } 390 391 #endif 392