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