]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 | */ | |
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 | */ | |
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> | |
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> | |
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> | |
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> | |
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> | |
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> | |
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> | |
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> | |
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 |