1 // ----------------------------------------------------------------------------
2 // Copyright (C) 2002-2006 Marcin Kalicinski
3 // Copyright (C) 2009 Sebastian Redl
4 //
5 // Distributed under the Boost Software License, Version 1.0.
6 // (See accompanying file LICENSE_1_0.txt or copy at
7 // http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // For more information, see www.boost.org
10 // ----------------------------------------------------------------------------
11 #ifndef BOOST_PROPERTY_TREE_INI_PARSER_HPP_INCLUDED
12 #define BOOST_PROPERTY_TREE_INI_PARSER_HPP_INCLUDED
13 
14 #include <boost/property_tree/ptree.hpp>
15 #include <boost/property_tree/detail/ptree_utils.hpp>
16 #include <boost/property_tree/detail/file_parser_error.hpp>
17 #include <fstream>
18 #include <string>
19 #include <sstream>
20 #include <stdexcept>
21 #include <locale>
22 
23 namespace boost { namespace property_tree { namespace ini_parser
24 {
25 
26     /**
27      * Determines whether the @c flags are valid for use with the ini_parser.
28      * @param flags value to check for validity as flags to ini_parser.
29      * @return true if the flags are valid, false otherwise.
30      */
validate_flags(int flags)31     inline bool validate_flags(int flags)
32     {
33         return flags == 0;
34     }
35 
36     /** Indicates an error parsing INI formatted data. */
37     class ini_parser_error: public file_parser_error
38     {
39     public:
40         /**
41          * Construct an @c ini_parser_error
42          * @param message Message describing the parser error.
43          * @param filename The name of the file being parsed containing the
44          *                 error.
45          * @param line The line in the given file where an error was
46          *             encountered.
47          */
ini_parser_error(const std::string & message,const std::string & filename,unsigned long line)48         ini_parser_error(const std::string &message,
49                          const std::string &filename,
50                          unsigned long line)
51             : file_parser_error(message, filename, line)
52         {
53         }
54     };
55 
56     /**
57      * Read INI from a the given stream and translate it to a property tree.
58      * @note Clears existing contents of property tree. In case of error
59      *       the property tree is not modified.
60      * @throw ini_parser_error If a format violation is found.
61      * @param stream Stream from which to read in the property tree.
62      * @param[out] pt The property tree to populate.
63      */
64     template<class Ptree>
read_ini(std::basic_istream<typename Ptree::key_type::value_type> & stream,Ptree & pt)65     void read_ini(std::basic_istream<
66                     typename Ptree::key_type::value_type> &stream,
67                   Ptree &pt)
68     {
69         typedef typename Ptree::key_type::value_type Ch;
70         typedef std::basic_string<Ch> Str;
71         const Ch semicolon = stream.widen(';');
72         const Ch hash = stream.widen('#');
73         const Ch lbracket = stream.widen('[');
74         const Ch rbracket = stream.widen(']');
75 
76         Ptree local;
77         unsigned long line_no = 0;
78         Ptree *section = 0;
79         Str line;
80 
81         // For all lines
82         while (stream.good())
83         {
84 
85             // Get line from stream
86             ++line_no;
87             std::getline(stream, line);
88             if (!stream.good() && !stream.eof())
89                 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
90                     "read error", "", line_no));
91 
92             // If line is non-empty
93             line = property_tree::detail::trim(line, stream.getloc());
94             if (!line.empty())
95             {
96                 // Comment, section or key?
97                 if (line[0] == semicolon || line[0] == hash)
98                 {
99                     // Ignore comments
100                 }
101                 else if (line[0] == lbracket)
102                 {
103                     // If the previous section was empty, drop it again.
104                     if (section && section->empty())
105                         local.pop_back();
106                     typename Str::size_type end = line.find(rbracket);
107                     if (end == Str::npos)
108                         BOOST_PROPERTY_TREE_THROW(ini_parser_error(
109                             "unmatched '['", "", line_no));
110                     Str key = property_tree::detail::trim(
111                         line.substr(1, end - 1), stream.getloc());
112                     if (local.find(key) != local.not_found())
113                         BOOST_PROPERTY_TREE_THROW(ini_parser_error(
114                             "duplicate section name", "", line_no));
115                     section = &local.push_back(
116                         std::make_pair(key, Ptree()))->second;
117                 }
118                 else
119                 {
120                     Ptree &container = section ? *section : local;
121                     typename Str::size_type eqpos = line.find(Ch('='));
122                     if (eqpos == Str::npos)
123                         BOOST_PROPERTY_TREE_THROW(ini_parser_error(
124                             "'=' character not found in line", "", line_no));
125                     if (eqpos == 0)
126                         BOOST_PROPERTY_TREE_THROW(ini_parser_error(
127                             "key expected", "", line_no));
128                     Str key = property_tree::detail::trim(
129                         line.substr(0, eqpos), stream.getloc());
130                     Str data = property_tree::detail::trim(
131                         line.substr(eqpos + 1, Str::npos), stream.getloc());
132                     if (container.find(key) != container.not_found())
133                         BOOST_PROPERTY_TREE_THROW(ini_parser_error(
134                             "duplicate key name", "", line_no));
135                     container.push_back(std::make_pair(key, Ptree(data)));
136                 }
137             }
138         }
139         // If the last section was empty, drop it again.
140         if (section && section->empty())
141             local.pop_back();
142 
143         // Swap local ptree with result ptree
144         pt.swap(local);
145 
146     }
147 
148     /**
149      * Read INI from a the given file and translate it to a property tree.
150      * @note Clears existing contents of property tree.  In case of error the
151      *       property tree unmodified.
152      * @throw ini_parser_error In case of error deserializing the property tree.
153      * @param filename Name of file from which to read in the property tree.
154      * @param[out] pt The property tree to populate.
155      * @param loc The locale to use when reading in the file contents.
156      */
157     template<class Ptree>
read_ini(const std::string & filename,Ptree & pt,const std::locale & loc=std::locale ())158     void read_ini(const std::string &filename,
159                   Ptree &pt,
160                   const std::locale &loc = std::locale())
161     {
162         std::basic_ifstream<typename Ptree::key_type::value_type>
163             stream(filename.c_str());
164         if (!stream)
165             BOOST_PROPERTY_TREE_THROW(ini_parser_error(
166                 "cannot open file", filename, 0));
167         stream.imbue(loc);
168         try {
169             read_ini(stream, pt);
170         }
171         catch (ini_parser_error &e) {
172             BOOST_PROPERTY_TREE_THROW(ini_parser_error(
173                 e.message(), filename, e.line()));
174         }
175     }
176 
177     namespace detail
178     {
179         template<class Ptree>
check_dupes(const Ptree & pt)180         void check_dupes(const Ptree &pt)
181         {
182             if(pt.size() <= 1)
183                 return;
184             const typename Ptree::key_type *lastkey = 0;
185             typename Ptree::const_assoc_iterator it = pt.ordered_begin(),
186                                                  end = pt.not_found();
187             lastkey = &it->first;
188             for(++it; it != end; ++it) {
189                 if(*lastkey == it->first)
190                     BOOST_PROPERTY_TREE_THROW(ini_parser_error(
191                         "duplicate key", "", 0));
192                 lastkey = &it->first;
193             }
194         }
195 
196         template <typename Ptree>
write_keys(std::basic_ostream<typename Ptree::key_type::value_type> & stream,const Ptree & pt,bool throw_on_children)197         void write_keys(std::basic_ostream<
198                                       typename Ptree::key_type::value_type
199                                   > &stream,
200                                   const Ptree& pt,
201                                   bool throw_on_children)
202         {
203             typedef typename Ptree::key_type::value_type Ch;
204             for (typename Ptree::const_iterator it = pt.begin(), end = pt.end();
205                  it != end; ++it)
206             {
207                 if (!it->second.empty()) {
208                     if (throw_on_children) {
209                         BOOST_PROPERTY_TREE_THROW(ini_parser_error(
210                             "ptree is too deep", "", 0));
211                     }
212                     continue;
213                 }
214                 stream << it->first << Ch('=')
215                     << it->second.template get_value<
216                         std::basic_string<Ch> >()
217                     << Ch('\n');
218             }
219         }
220 
221         template <typename Ptree>
write_top_level_keys(std::basic_ostream<typename Ptree::key_type::value_type> & stream,const Ptree & pt)222         void write_top_level_keys(std::basic_ostream<
223                                       typename Ptree::key_type::value_type
224                                   > &stream,
225                                   const Ptree& pt)
226         {
227             write_keys(stream, pt, false);
228         }
229 
230         template <typename Ptree>
write_sections(std::basic_ostream<typename Ptree::key_type::value_type> & stream,const Ptree & pt)231         void write_sections(std::basic_ostream<
232                                 typename Ptree::key_type::value_type
233                             > &stream,
234                             const Ptree& pt)
235         {
236             typedef typename Ptree::key_type::value_type Ch;
237             for (typename Ptree::const_iterator it = pt.begin(), end = pt.end();
238                  it != end; ++it)
239             {
240                 if (!it->second.empty()) {
241                     check_dupes(it->second);
242                     if (!it->second.data().empty())
243                         BOOST_PROPERTY_TREE_THROW(ini_parser_error(
244                             "mixed data and children", "", 0));
245                     stream << Ch('[') << it->first << Ch(']') << Ch('\n');
246                     write_keys(stream, it->second, true);
247                 }
248             }
249         }
250     }
251 
252     /**
253      * Translates the property tree to INI and writes it the given output
254      * stream.
255      * @pre @e pt cannot have data in its root.
256      * @pre @e pt cannot have keys both data and children.
257      * @pre @e pt cannot be deeper than two levels.
258      * @pre There cannot be duplicate keys on any given level of @e pt.
259      * @throw ini_parser_error In case of error translating the property tree to
260      *                         INI or writing to the output stream.
261      * @param stream The stream to which to write the INI representation of the
262      *               property tree.
263      * @param pt The property tree to tranlsate to INI and output.
264      * @param flags The flags to use when writing the INI file.
265      *              No flags are currently supported.
266      */
267     template<class Ptree>
write_ini(std::basic_ostream<typename Ptree::key_type::value_type> & stream,const Ptree & pt,int flags=0)268     void write_ini(std::basic_ostream<
269                        typename Ptree::key_type::value_type
270                    > &stream,
271                    const Ptree &pt,
272                    int flags = 0)
273     {
274         BOOST_ASSERT(validate_flags(flags));
275         (void)flags;
276 
277         if (!pt.data().empty())
278             BOOST_PROPERTY_TREE_THROW(ini_parser_error(
279                 "ptree has data on root", "", 0));
280         detail::check_dupes(pt);
281 
282         detail::write_top_level_keys(stream, pt);
283         detail::write_sections(stream, pt);
284     }
285 
286     /**
287      * Translates the property tree to INI and writes it the given file.
288      * @pre @e pt cannot have data in its root.
289      * @pre @e pt cannot have keys both data and children.
290      * @pre @e pt cannot be deeper than two levels.
291      * @pre There cannot be duplicate keys on any given level of @e pt.
292      * @throw info_parser_error In case of error translating the property tree
293      *                          to INI or writing to the file.
294      * @param filename The name of the file to which to write the INI
295      *                 representation of the property tree.
296      * @param pt The property tree to tranlsate to INI and output.
297      * @param flags The flags to use when writing the INI file.
298      *              The following flags are supported:
299      * @li @c skip_ini_validity_check -- Skip check if ptree is a valid ini. The
300      *     validity check covers the preconditions but takes <tt>O(n log n)</tt>
301      *     time.
302      * @param loc The locale to use when writing the file.
303      */
304     template<class Ptree>
write_ini(const std::string & filename,const Ptree & pt,int flags=0,const std::locale & loc=std::locale ())305     void write_ini(const std::string &filename,
306                    const Ptree &pt,
307                    int flags = 0,
308                    const std::locale &loc = std::locale())
309     {
310         std::basic_ofstream<typename Ptree::key_type::value_type>
311             stream(filename.c_str());
312         if (!stream)
313             BOOST_PROPERTY_TREE_THROW(ini_parser_error(
314                 "cannot open file", filename, 0));
315         stream.imbue(loc);
316         try {
317             write_ini(stream, pt, flags);
318         }
319         catch (ini_parser_error &e) {
320             BOOST_PROPERTY_TREE_THROW(ini_parser_error(
321                 e.message(), filename, e.line()));
322         }
323     }
324 
325 } } }
326 
327 namespace boost { namespace property_tree
328 {
329     using ini_parser::ini_parser_error;
330     using ini_parser::read_ini;
331     using ini_parser::write_ini;
332 } }
333 
334 #endif
335