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