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