Chris@16: // ---------------------------------------------------------------------------- Chris@16: // Copyright (C) 2002-2006 Marcin Kalicinski Chris@16: // Copyright (C) 2009 Sebastian Redl Chris@16: // Chris@16: // Distributed under the Boost Software License, Version 1.0. Chris@16: // (See accompanying file LICENSE_1_0.txt or copy at Chris@16: // http://www.boost.org/LICENSE_1_0.txt) Chris@16: // Chris@16: // For more information, see www.boost.org Chris@16: // ---------------------------------------------------------------------------- Chris@16: #ifndef BOOST_PROPERTY_TREE_INI_PARSER_HPP_INCLUDED Chris@16: #define BOOST_PROPERTY_TREE_INI_PARSER_HPP_INCLUDED Chris@16: Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: #include Chris@16: Chris@16: namespace boost { namespace property_tree { namespace ini_parser Chris@16: { Chris@16: Chris@16: /** Chris@16: * Determines whether the @c flags are valid for use with the ini_parser. Chris@16: * @param flags value to check for validity as flags to ini_parser. Chris@16: * @return true if the flags are valid, false otherwise. Chris@16: */ Chris@16: inline bool validate_flags(int flags) Chris@16: { Chris@16: return flags == 0; Chris@16: } Chris@16: Chris@16: /** Indicates an error parsing INI formatted data. */ Chris@16: class ini_parser_error: public file_parser_error Chris@16: { Chris@16: public: Chris@16: /** Chris@16: * Construct an @c ini_parser_error Chris@16: * @param message Message describing the parser error. Chris@16: * @param filename The name of the file being parsed containing the Chris@16: * error. Chris@16: * @param line The line in the given file where an error was Chris@16: * encountered. Chris@16: */ Chris@16: ini_parser_error(const std::string &message, Chris@16: const std::string &filename, Chris@16: unsigned long line) Chris@16: : file_parser_error(message, filename, line) Chris@16: { Chris@16: } Chris@16: }; Chris@16: Chris@16: /** Chris@16: * Read INI from a the given stream and translate it to a property tree. Chris@16: * @note Clears existing contents of property tree. In case of error Chris@16: * the property tree is not modified. Chris@16: * @throw ini_parser_error If a format violation is found. Chris@16: * @param stream Stream from which to read in the property tree. Chris@16: * @param[out] pt The property tree to populate. Chris@16: */ Chris@16: template Chris@16: void read_ini(std::basic_istream< Chris@16: typename Ptree::key_type::value_type> &stream, Chris@16: Ptree &pt) Chris@16: { Chris@16: typedef typename Ptree::key_type::value_type Ch; Chris@16: typedef std::basic_string Str; Chris@16: const Ch semicolon = stream.widen(';'); Chris@16: const Ch hash = stream.widen('#'); Chris@16: const Ch lbracket = stream.widen('['); Chris@16: const Ch rbracket = stream.widen(']'); Chris@16: Chris@16: Ptree local; Chris@16: unsigned long line_no = 0; Chris@16: Ptree *section = 0; Chris@16: Str line; Chris@16: Chris@16: // For all lines Chris@16: while (stream.good()) Chris@16: { Chris@16: Chris@16: // Get line from stream Chris@16: ++line_no; Chris@16: std::getline(stream, line); Chris@16: if (!stream.good() && !stream.eof()) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "read error", "", line_no)); Chris@16: Chris@16: // If line is non-empty Chris@16: line = property_tree::detail::trim(line, stream.getloc()); Chris@16: if (!line.empty()) Chris@16: { Chris@16: // Comment, section or key? Chris@16: if (line[0] == semicolon || line[0] == hash) Chris@16: { Chris@16: // Ignore comments Chris@16: } Chris@16: else if (line[0] == lbracket) Chris@16: { Chris@16: // If the previous section was empty, drop it again. Chris@16: if (section && section->empty()) Chris@16: local.pop_back(); Chris@16: typename Str::size_type end = line.find(rbracket); Chris@16: if (end == Str::npos) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "unmatched '['", "", line_no)); Chris@16: Str key = property_tree::detail::trim( Chris@16: line.substr(1, end - 1), stream.getloc()); Chris@16: if (local.find(key) != local.not_found()) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "duplicate section name", "", line_no)); Chris@16: section = &local.push_back( Chris@16: std::make_pair(key, Ptree()))->second; Chris@16: } Chris@16: else Chris@16: { Chris@16: Ptree &container = section ? *section : local; Chris@16: typename Str::size_type eqpos = line.find(Ch('=')); Chris@16: if (eqpos == Str::npos) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "'=' character not found in line", "", line_no)); Chris@16: if (eqpos == 0) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "key expected", "", line_no)); Chris@16: Str key = property_tree::detail::trim( Chris@16: line.substr(0, eqpos), stream.getloc()); Chris@16: Str data = property_tree::detail::trim( Chris@16: line.substr(eqpos + 1, Str::npos), stream.getloc()); Chris@16: if (container.find(key) != container.not_found()) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "duplicate key name", "", line_no)); Chris@16: container.push_back(std::make_pair(key, Ptree(data))); Chris@16: } Chris@16: } Chris@16: } Chris@16: // If the last section was empty, drop it again. Chris@16: if (section && section->empty()) Chris@16: local.pop_back(); Chris@16: Chris@16: // Swap local ptree with result ptree Chris@16: pt.swap(local); Chris@16: Chris@16: } Chris@16: Chris@16: /** Chris@16: * Read INI from a the given file and translate it to a property tree. Chris@16: * @note Clears existing contents of property tree. In case of error the Chris@16: * property tree unmodified. Chris@16: * @throw ini_parser_error In case of error deserializing the property tree. Chris@16: * @param filename Name of file from which to read in the property tree. Chris@16: * @param[out] pt The property tree to populate. Chris@16: * @param loc The locale to use when reading in the file contents. Chris@16: */ Chris@16: template Chris@16: void read_ini(const std::string &filename, Chris@16: Ptree &pt, Chris@16: const std::locale &loc = std::locale()) Chris@16: { Chris@16: std::basic_ifstream Chris@16: stream(filename.c_str()); Chris@16: if (!stream) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "cannot open file", filename, 0)); Chris@16: stream.imbue(loc); Chris@16: try { Chris@16: read_ini(stream, pt); Chris@16: } Chris@16: catch (ini_parser_error &e) { Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: e.message(), filename, e.line())); Chris@16: } Chris@16: } Chris@16: Chris@16: namespace detail Chris@16: { Chris@16: template Chris@16: void check_dupes(const Ptree &pt) Chris@16: { Chris@16: if(pt.size() <= 1) Chris@16: return; Chris@16: const typename Ptree::key_type *lastkey = 0; Chris@16: typename Ptree::const_assoc_iterator it = pt.ordered_begin(), Chris@16: end = pt.not_found(); Chris@16: lastkey = &it->first; Chris@16: for(++it; it != end; ++it) { Chris@16: if(*lastkey == it->first) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "duplicate key", "", 0)); Chris@16: lastkey = &it->first; Chris@16: } Chris@16: } Chris@101: Chris@101: template Chris@101: void write_keys(std::basic_ostream< Chris@101: typename Ptree::key_type::value_type Chris@101: > &stream, Chris@101: const Ptree& pt, Chris@101: bool throw_on_children) Chris@101: { Chris@101: typedef typename Ptree::key_type::value_type Ch; Chris@101: for (typename Ptree::const_iterator it = pt.begin(), end = pt.end(); Chris@101: it != end; ++it) Chris@101: { Chris@101: if (!it->second.empty()) { Chris@101: if (throw_on_children) { Chris@101: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@101: "ptree is too deep", "", 0)); Chris@101: } Chris@101: continue; Chris@101: } Chris@101: stream << it->first << Ch('=') Chris@101: << it->second.template get_value< Chris@101: std::basic_string >() Chris@101: << Ch('\n'); Chris@101: } Chris@101: } Chris@101: Chris@101: template Chris@101: void write_top_level_keys(std::basic_ostream< Chris@101: typename Ptree::key_type::value_type Chris@101: > &stream, Chris@101: const Ptree& pt) Chris@101: { Chris@101: write_keys(stream, pt, false); Chris@101: } Chris@101: Chris@101: template Chris@101: void write_sections(std::basic_ostream< Chris@101: typename Ptree::key_type::value_type Chris@101: > &stream, Chris@101: const Ptree& pt) Chris@101: { Chris@101: typedef typename Ptree::key_type::value_type Ch; Chris@101: for (typename Ptree::const_iterator it = pt.begin(), end = pt.end(); Chris@101: it != end; ++it) Chris@101: { Chris@101: if (!it->second.empty()) { Chris@101: check_dupes(it->second); Chris@101: if (!it->second.data().empty()) Chris@101: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@101: "mixed data and children", "", 0)); Chris@101: stream << Ch('[') << it->first << Ch(']') << Ch('\n'); Chris@101: write_keys(stream, it->second, true); Chris@101: } Chris@101: } Chris@101: } Chris@16: } Chris@16: Chris@16: /** Chris@16: * Translates the property tree to INI and writes it the given output Chris@16: * stream. Chris@16: * @pre @e pt cannot have data in its root. Chris@16: * @pre @e pt cannot have keys both data and children. Chris@16: * @pre @e pt cannot be deeper than two levels. Chris@16: * @pre There cannot be duplicate keys on any given level of @e pt. Chris@16: * @throw ini_parser_error In case of error translating the property tree to Chris@16: * INI or writing to the output stream. Chris@16: * @param stream The stream to which to write the INI representation of the Chris@16: * property tree. Chris@16: * @param pt The property tree to tranlsate to INI and output. Chris@16: * @param flags The flags to use when writing the INI file. Chris@16: * No flags are currently supported. Chris@16: */ Chris@16: template Chris@16: void write_ini(std::basic_ostream< Chris@16: typename Ptree::key_type::value_type Chris@16: > &stream, Chris@16: const Ptree &pt, Chris@16: int flags = 0) Chris@16: { Chris@16: BOOST_ASSERT(validate_flags(flags)); Chris@16: (void)flags; Chris@16: Chris@16: if (!pt.data().empty()) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "ptree has data on root", "", 0)); Chris@101: detail::check_dupes(pt); Chris@16: Chris@101: detail::write_top_level_keys(stream, pt); Chris@101: detail::write_sections(stream, pt); Chris@16: } Chris@16: Chris@16: /** Chris@16: * Translates the property tree to INI and writes it the given file. Chris@16: * @pre @e pt cannot have data in its root. Chris@16: * @pre @e pt cannot have keys both data and children. Chris@16: * @pre @e pt cannot be deeper than two levels. Chris@16: * @pre There cannot be duplicate keys on any given level of @e pt. Chris@16: * @throw info_parser_error In case of error translating the property tree Chris@16: * to INI or writing to the file. Chris@16: * @param filename The name of the file to which to write the INI Chris@16: * representation of the property tree. Chris@16: * @param pt The property tree to tranlsate to INI and output. Chris@16: * @param flags The flags to use when writing the INI file. Chris@16: * The following flags are supported: Chris@16: * @li @c skip_ini_validity_check -- Skip check if ptree is a valid ini. The Chris@16: * validity check covers the preconditions but takes O(n log n) Chris@16: * time. Chris@16: * @param loc The locale to use when writing the file. Chris@16: */ Chris@16: template Chris@16: void write_ini(const std::string &filename, Chris@16: const Ptree &pt, Chris@16: int flags = 0, Chris@16: const std::locale &loc = std::locale()) Chris@16: { Chris@16: std::basic_ofstream Chris@16: stream(filename.c_str()); Chris@16: if (!stream) Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: "cannot open file", filename, 0)); Chris@16: stream.imbue(loc); Chris@16: try { Chris@16: write_ini(stream, pt, flags); Chris@16: } Chris@16: catch (ini_parser_error &e) { Chris@16: BOOST_PROPERTY_TREE_THROW(ini_parser_error( Chris@16: e.message(), filename, e.line())); Chris@16: } Chris@16: } Chris@16: Chris@16: } } } Chris@16: Chris@16: namespace boost { namespace property_tree Chris@16: { Chris@16: using ini_parser::ini_parser_error; Chris@16: using ini_parser::read_ini; Chris@16: using ini_parser::write_ini; Chris@16: } } Chris@16: Chris@16: #endif