Ticket #12259: boost_ini_parser_with_comments.hpp

File boost_ini_parser_with_comments.hpp, 18.1 KB (added by jan@…, 6 years ago)

alternative implementation, as described in the ticket

Line 
1// ----------------------------------------------------------------------------
2// Copyright (C) 2002-2006 Marcin Kalicinski
3// Copyright (C) 2009 Sebastian Redl
4//
5// Modified to support comments: (C) 2016 Jan Krieger (Heidelberger Druckmaschinen)
6//
7// Distributed under the Boost Software License, Version 1.0.
8// (See accompanying file LICENSE_1_0.txt or copy at
9// http://www.boost.org/LICENSE_1_0.txt)
10//
11// For more information, see www.boost.org
12// ----------------------------------------------------------------------------
13#ifndef BOOST_PROPERTY_TREE_INI_PARSER_WITH_COMMENTS_HPP_INCLUDED
14#define BOOST_PROPERTY_TREE_INI_PARSER_WITH_COMMENTS_HPP_INCLUDED
15
16#include <boost/property_tree/ptree.hpp>
17#include <boost/property_tree/detail/ptree_utils.hpp>
18#include <boost/property_tree/detail/file_parser_error.hpp>
19#include <fstream>
20#include <string>
21#include <sstream>
22#include <stdexcept>
23#include <locale>
24#include <locale>
25
26namespace boost { namespace property_tree { namespace ini_parser_with_comments
27{
28 template <class Str>
29 inline Str comment_key() {
30 return Str("inicomment");
31 }
32
33 /**
34 * Determines whether the @c flags are valid for use with the ini_parser.
35 * @param flags value to check for validity as flags to ini_parser.
36 * @return true if the flags are valid, false otherwise.
37 */
38 inline bool validate_flags(int flags)
39 {
40 return flags == 0;
41 }
42
43 /** Indicates an error parsing INI formatted data. */
44 class ini_parser_error: public file_parser_error
45 {
46 public:
47 /**
48 * Construct an @c ini_parser_error
49 * @param message Message describing the parser error.
50 * @param filename The name of the file being parsed containing the
51 * error.
52 * @param line The line in the given file where an error was
53 * encountered.
54 */
55 ini_parser_error(const std::string &message,
56 const std::string &filename,
57 unsigned long line)
58 : file_parser_error(message, filename, line)
59 {
60 }
61 };
62
63 /**
64 * Read INI from a the given stream and translate it to a property tree.
65 * @note Clears existing contents of property tree. In case of error
66 * the property tree is not modified.
67 * @throw ini_parser_error If a format violation is found.
68 * @param stream Stream from which to read in the property tree.
69 * @param[out] pt The property tree to populate.
70 *
71 * Comments will be stored in the propertyTree alongside the actual property
72 * in an additional entry \c key.inicomment (wie von comment_key() zurückgegeben).
73 */
74 template<class Ptree>
75 void read_ini(std::basic_istream<
76 typename Ptree::key_type::value_type> &stream,
77 Ptree &pt)
78 {
79 typedef typename Ptree::key_type::value_type Ch;
80 typedef std::basic_string<Ch> Str;
81 const Ch semicolon = stream.widen(';');
82 const Ch hash = stream.widen('#');
83 const Ch lbracket = stream.widen('[');
84 const Ch rbracket = stream.widen(']');
85 const Str commentKey = comment_key<Str>();
86
87 Ptree local;
88 unsigned long line_no = 0;
89 Ptree *section = 0;
90 Str line;
91 Str lastComment;
92
93 // For all lines
94 while (stream.good())
95 {
96
97 // Get line from stream
98 ++line_no;
99 std::getline(stream, line);
100 if (!stream.good() && !stream.eof())
101 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
102 "read error", "", line_no));
103
104 // If line is non-empty
105 line = property_tree::detail::trim(line, stream.getloc());
106 if (!line.empty())
107 {
108 // Comment, section or key?
109 if (line[0] == semicolon || line[0] == hash)
110 {
111 // Save comments to intermediate storage
112 if (lastComment.size()>0) lastComment+=Ch('\n');
113 if (line.size()>=2) {
114 if (std::isspace(line[1]) )lastComment+=line.substr(2);
115 else lastComment+=line.substr(1);
116 }
117 }
118 else if (line[0] == lbracket)
119 {
120 // If the previous section was empty, drop it again.
121 if (section && section->empty())
122 local.pop_back();
123 typename Str::size_type end = line.find(rbracket);
124 if (end == Str::npos)
125 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
126 "unmatched '['", "", line_no));
127 Str key = property_tree::detail::trim(
128 line.substr(1, end - 1), stream.getloc());
129 if (local.find(key) != local.not_found())
130 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
131 "duplicate section name", "", line_no));
132 section = &local.push_back(
133 std::make_pair(key, Ptree()))->second;
134 if (lastComment.size()>0) {
135 if (section) section->put(commentKey, lastComment);
136 lastComment.clear();
137 }
138 }
139 else
140 {
141 Ptree &container = section ? *section : local;
142 typename Str::size_type eqpos = line.find(Ch('='));
143 if (eqpos == Str::npos)
144 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
145 "'=' character not found in line", "", line_no));
146 if (eqpos == 0)
147 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
148 "key expected", "", line_no));
149 Str key = property_tree::detail::trim(
150 line.substr(0, eqpos), stream.getloc());
151 Str data = property_tree::detail::trim(
152 line.substr(eqpos + 1, Str::npos), stream.getloc());
153 if (container.find(key) != container.not_found())
154 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
155 "duplicate key name", "", line_no));
156 Ptree* section= &container.push_back(std::make_pair(key, Ptree(data)))->second;
157 if (lastComment.size()>0) {
158 if (section) section->put(commentKey, lastComment);
159 lastComment.clear();
160 }
161 }
162 } else {
163 lastComment += Ch('\n');
164 }
165 }
166 // If the last section was empty, drop it again.
167 if (section && section->empty())
168 local.pop_back();
169
170 // Swap local ptree with result ptree
171 pt.swap(local);
172
173 }
174
175 /**
176 * Read INI from a the given file and translate it to a property tree.
177 * @note Clears existing contents of property tree. In case of error the
178 * property tree unmodified.
179 * @throw ini_parser_error In case of error deserializing the property tree.
180 * @param filename Name of file from which to read in the property tree.
181 * @param[out] pt The property tree to populate.
182 * @param loc The locale to use when reading in the file contents.
183 */
184 template<class Ptree>
185 void read_ini(const std::string &filename,
186 Ptree &pt,
187 const std::locale &loc = std::locale())
188 {
189 std::basic_ifstream<typename Ptree::key_type::value_type>
190 stream(filename.c_str());
191 if (!stream)
192 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
193 "cannot open file", filename, 0));
194 stream.imbue(loc);
195 try {
196 read_ini(stream, pt);
197 }
198 catch (ini_parser_error &e) {
199 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
200 e.message(), filename, e.line()));
201 }
202 }
203
204 namespace detail
205 {
206 template<class Ptree>
207 void check_dupes(const Ptree &pt)
208 {
209 if(pt.size() <= 1)
210 return;
211 const typename Ptree::key_type *lastkey = 0;
212 typename Ptree::const_assoc_iterator it = pt.ordered_begin(),
213 end = pt.not_found();
214 lastkey = &it->first;
215 for(++it; it != end; ++it) {
216 if(*lastkey == it->first)
217 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
218 "duplicate key", "", 0));
219 lastkey = &it->first;
220 }
221 }
222
223 template<class Ptree>
224 void write_comment(std::basic_ostream<
225 typename Ptree::key_type::value_type
226 > &stream,
227 const typename Ptree::key_type& comment,
228 const typename Ptree::key_type& commentStart)
229 {
230 if (comment.size()<=0) return;
231 typename Ptree::key_type line;
232 bool hadNonEmptyLine=false;
233 for (size_t i=0; i<comment.size(); i++) {
234 if (comment[i]=='\n') {
235 if (line.size()>0 || hadNonEmptyLine) {
236 // dump comment line
237 stream<<commentStart<<line<<'\n';
238 } else {
239 // preceding empty lines are output without the commentStart
240 // dump comment line
241 stream<<'\n';
242 }
243 line.clear();
244 hadNonEmptyLine=hadNonEmptyLine||(line.size()>0);
245 } else if (comment[i]!='\r') {
246 // Ignore '\r' (for Windows!)
247 line+=comment[i];
248 }
249 }
250 if (line.size()>0 || hadNonEmptyLine) {
251 // dump comment line
252 stream<<commentStart<<line<<'\n';
253 } else {
254 // preceding empty lines are output without the commentStart
255 // dump comment line
256 stream<<'\n';
257 }
258 }
259
260 template <typename Ptree>
261 void write_keys(std::basic_ostream<
262 typename Ptree::key_type::value_type
263 > &stream,
264 const Ptree& pt,
265 bool throw_on_children,
266 const typename Ptree::key_type& commentKey,
267 const typename Ptree::key_type& commentStart)
268 {
269 typedef typename Ptree::key_type::value_type Ch;
270 for (typename Ptree::const_iterator it = pt.begin(), end = pt.end();
271 it != end; ++it)
272 {
273 if (it->first!=commentKey) {
274 // We ignore the ".comment"-nodes, as these have special meaning!
275
276 // check for existence of comment node
277 boost::optional<typename Ptree::key_type> comment = it->second.get_optional<typename Ptree::key_type>(commentKey);
278
279 if (!it->second.empty() && !(it->second.size()==1 && comment)) {
280 //only two depth-levels are allowd in INI-files ... but we also have to filter out the additional .comment nodes
281 if (throw_on_children) {
282 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
283 "ptree is too deep (only two depth steps alowed in INI files)", "", 0));
284 }
285 continue;
286 }
287 if (comment) {
288 // ... if it exists: output the comment
289 write_comment<Ptree>(stream, *comment, commentStart);
290 }
291 stream << it->first << Ch('=')
292 << it->second.template get_value<
293 std::basic_string<Ch> >()
294 << Ch('\n');
295 }
296 }
297 }
298
299 template <typename Ptree>
300 void write_top_level_keys(std::basic_ostream<
301 typename Ptree::key_type::value_type
302 > &stream,
303 const Ptree& pt,
304 const typename Ptree::key_type& commentKey,
305 const typename Ptree::key_type& commentStart)
306 {
307 write_keys(stream, pt, false, commentKey, commentStart);
308 }
309
310 template <typename Ptree>
311 void write_sections(std::basic_ostream<
312 typename Ptree::key_type::value_type
313 > &stream,
314 const Ptree& pt,
315 const typename Ptree::key_type& commentKey,
316 const typename Ptree::key_type& commentStart)
317 {
318 typedef typename Ptree::key_type::value_type Ch;
319 for (typename Ptree::const_iterator it = pt.begin(), end = pt.end();
320 it != end; ++it)
321 {
322 if (!it->second.empty()) {
323 check_dupes(it->second);
324 if (!it->second.data().empty())
325 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
326 "mixed data and children", "", 0));
327 // empty lines in front of a new section to better separate it from other sections
328 stream << Ch('\n') << Ch('\n');
329 // check for existence of comment node
330 boost::optional<typename Ptree::key_type> comment=pt.get_optional<typename Ptree::key_type>(it->first+"."+commentKey);
331 if (comment) {
332 std::string c=*comment;
333 // eat linebreak from start of comment to account for the two explicit \n inserted above!
334 while (c.size()>0 && c[0]=='\n') c=c.substr(1);
335 // ... if it exists: output the comment
336 if (c.size()>0) write_comment<Ptree>(stream, c, commentStart);
337 }
338 stream << Ch('[') << it->first << Ch(']') << Ch('\n');
339 write_keys(stream, it->second, true, commentKey, commentStart);
340 }
341 }
342 }
343 }
344
345 /**
346 * Translates the property tree to INI and writes it the given output
347 * stream.
348 * @pre @e pt cannot have data in its root.
349 * @pre @e pt cannot have keys both data and children.
350 * @pre @e pt cannot be deeper than two levels.
351 * @pre There cannot be duplicate keys on any given level of @e pt.
352 * @throw ini_parser_error In case of error translating the property tree to
353 * INI or writing to the output stream.
354 * @param stream The stream to which to write the INI representation of the
355 * property tree.
356 * @param pt The property tree to tranlsate to INI and output.
357 * @param flags The flags to use when writing the INI file.
358 * No flags are currently supported.
359 */
360 template<class Ptree>
361 void write_ini(std::basic_ostream<
362 typename Ptree::key_type::value_type
363 > &stream,
364 const Ptree &pt,
365 int flags = 0)
366 {
367 BOOST_ASSERT(validate_flags(flags));
368 (void)flags;
369
370 const Ptree::key_type commentKey=comment_key<Ptree::key_type>();
371 const Ptree::key_type commentStart="; ";
372
373 if (!pt.data().empty())
374 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
375 "ptree has data on root", "", 0));
376 detail::check_dupes(pt);
377
378 detail::write_top_level_keys(stream, pt, commentKey, commentStart);
379 detail::write_sections(stream, pt, commentKey, commentStart);
380 }
381
382 /**
383 * Translates the property tree to INI and writes it the given file.
384 * @pre @e pt cannot have data in its root.
385 * @pre @e pt cannot have keys both data and children.
386 * @pre @e pt cannot be deeper than two levels.
387 * @pre There cannot be duplicate keys on any given level of @e pt.
388 * @throw info_parser_error In case of error translating the property tree
389 * to INI or writing to the file.
390 * @param filename The name of the file to which to write the INI
391 * representation of the property tree.
392 * @param pt The property tree to tranlsate to INI and output.
393 * @param flags The flags to use when writing the INI file.
394 * The following flags are supported:
395 * @li @c skip_ini_validity_check -- Skip check if ptree is a valid ini. The
396 * validity check covers the preconditions but takes <tt>O(n log n)</tt>
397 * time.
398 * @param loc The locale to use when writing the file.
399 */
400 template<class Ptree>
401 void write_ini(const std::string &filename,
402 const Ptree &pt,
403 int flags = 0,
404 const std::locale &loc = std::locale())
405 {
406 std::basic_ofstream<typename Ptree::key_type::value_type>
407 stream(filename.c_str());
408 if (!stream)
409 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
410 "cannot open file", filename, 0));
411 stream.imbue(loc);
412 try {
413 write_ini(stream, pt, flags);
414 }
415 catch (ini_parser_error &e) {
416 BOOST_PROPERTY_TREE_THROW(ini_parser_error(
417 e.message(), filename, e.line()));
418 }
419 }
420
421} } }
422
423namespace boost { namespace property_tree
424{
425 using ini_parser_with_comments::ini_parser_error;
426 using ini_parser_with_comments::read_ini;
427 using ini_parser_with_comments::write_ini;
428} }
429
430#endif // BOOST_PROPERTY_TREE_INI_PARSER_WITH_COMMENTS_HPP_INCLUDED