Brian Silverman | 836e90c | 2018-08-04 16:19:46 -0700 | [diff] [blame^] | 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 |