// ---------------------------------------------------------------------------- // Copyright (C) 2002-2006 Marcin Kalicinski // Copyright (C) 2009 Sebastian Redl // // Modified to support comments: (C) 2016 Jan Krieger (Heidelberger Druckmaschinen) // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // // For more information, see www.boost.org // ---------------------------------------------------------------------------- #ifndef BOOST_PROPERTY_TREE_INI_PARSER_WITH_COMMENTS_HPP_INCLUDED #define BOOST_PROPERTY_TREE_INI_PARSER_WITH_COMMENTS_HPP_INCLUDED #include #include #include #include #include #include #include #include #include namespace boost { namespace property_tree { namespace ini_parser_with_comments { template inline Str comment_key() { return Str("inicomment"); } /** * Determines whether the @c flags are valid for use with the ini_parser. * @param flags value to check for validity as flags to ini_parser. * @return true if the flags are valid, false otherwise. */ inline bool validate_flags(int flags) { return flags == 0; } /** Indicates an error parsing INI formatted data. */ class ini_parser_error: public file_parser_error { public: /** * Construct an @c ini_parser_error * @param message Message describing the parser error. * @param filename The name of the file being parsed containing the * error. * @param line The line in the given file where an error was * encountered. */ ini_parser_error(const std::string &message, const std::string &filename, unsigned long line) : file_parser_error(message, filename, line) { } }; /** * Read INI from a the given stream and translate it to a property tree. * @note Clears existing contents of property tree. In case of error * the property tree is not modified. * @throw ini_parser_error If a format violation is found. * @param stream Stream from which to read in the property tree. * @param[out] pt The property tree to populate. * * Comments will be stored in the propertyTree alongside the actual property * in an additional entry \c key.inicomment (wie von comment_key() zurückgegeben). */ template void read_ini(std::basic_istream< typename Ptree::key_type::value_type> &stream, Ptree &pt) { typedef typename Ptree::key_type::value_type Ch; typedef std::basic_string Str; const Ch semicolon = stream.widen(';'); const Ch hash = stream.widen('#'); const Ch lbracket = stream.widen('['); const Ch rbracket = stream.widen(']'); const Str commentKey = comment_key(); Ptree local; unsigned long line_no = 0; Ptree *section = 0; Str line; Str lastComment; // For all lines while (stream.good()) { // Get line from stream ++line_no; std::getline(stream, line); if (!stream.good() && !stream.eof()) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "read error", "", line_no)); // If line is non-empty line = property_tree::detail::trim(line, stream.getloc()); if (!line.empty()) { // Comment, section or key? if (line[0] == semicolon || line[0] == hash) { // Save comments to intermediate storage if (lastComment.size()>0) lastComment+=Ch('\n'); if (line.size()>=2) { if (std::isspace(line[1]) )lastComment+=line.substr(2); else lastComment+=line.substr(1); } } else if (line[0] == lbracket) { // If the previous section was empty, drop it again. if (section && section->empty()) local.pop_back(); typename Str::size_type end = line.find(rbracket); if (end == Str::npos) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "unmatched '['", "", line_no)); Str key = property_tree::detail::trim( line.substr(1, end - 1), stream.getloc()); if (local.find(key) != local.not_found()) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "duplicate section name", "", line_no)); section = &local.push_back( std::make_pair(key, Ptree()))->second; if (lastComment.size()>0) { if (section) section->put(commentKey, lastComment); lastComment.clear(); } } else { Ptree &container = section ? *section : local; typename Str::size_type eqpos = line.find(Ch('=')); if (eqpos == Str::npos) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "'=' character not found in line", "", line_no)); if (eqpos == 0) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "key expected", "", line_no)); Str key = property_tree::detail::trim( line.substr(0, eqpos), stream.getloc()); Str data = property_tree::detail::trim( line.substr(eqpos + 1, Str::npos), stream.getloc()); if (container.find(key) != container.not_found()) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "duplicate key name", "", line_no)); Ptree* section= &container.push_back(std::make_pair(key, Ptree(data)))->second; if (lastComment.size()>0) { if (section) section->put(commentKey, lastComment); lastComment.clear(); } } } else { lastComment += Ch('\n'); } } // If the last section was empty, drop it again. if (section && section->empty()) local.pop_back(); // Swap local ptree with result ptree pt.swap(local); } /** * Read INI from a the given file and translate it to a property tree. * @note Clears existing contents of property tree. In case of error the * property tree unmodified. * @throw ini_parser_error In case of error deserializing the property tree. * @param filename Name of file from which to read in the property tree. * @param[out] pt The property tree to populate. * @param loc The locale to use when reading in the file contents. */ template void read_ini(const std::string &filename, Ptree &pt, const std::locale &loc = std::locale()) { std::basic_ifstream stream(filename.c_str()); if (!stream) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "cannot open file", filename, 0)); stream.imbue(loc); try { read_ini(stream, pt); } catch (ini_parser_error &e) { BOOST_PROPERTY_TREE_THROW(ini_parser_error( e.message(), filename, e.line())); } } namespace detail { template void check_dupes(const Ptree &pt) { if(pt.size() <= 1) return; const typename Ptree::key_type *lastkey = 0; typename Ptree::const_assoc_iterator it = pt.ordered_begin(), end = pt.not_found(); lastkey = &it->first; for(++it; it != end; ++it) { if(*lastkey == it->first) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "duplicate key", "", 0)); lastkey = &it->first; } } template void write_comment(std::basic_ostream< typename Ptree::key_type::value_type > &stream, const typename Ptree::key_type& comment, const typename Ptree::key_type& commentStart) { if (comment.size()<=0) return; typename Ptree::key_type line; bool hadNonEmptyLine=false; for (size_t i=0; i0 || hadNonEmptyLine) { // dump comment line stream<0); } else if (comment[i]!='\r') { // Ignore '\r' (for Windows!) line+=comment[i]; } } if (line.size()>0 || hadNonEmptyLine) { // dump comment line stream< void write_keys(std::basic_ostream< typename Ptree::key_type::value_type > &stream, const Ptree& pt, bool throw_on_children, const typename Ptree::key_type& commentKey, const typename Ptree::key_type& commentStart) { typedef typename Ptree::key_type::value_type Ch; for (typename Ptree::const_iterator it = pt.begin(), end = pt.end(); it != end; ++it) { if (it->first!=commentKey) { // We ignore the ".comment"-nodes, as these have special meaning! // check for existence of comment node boost::optional comment = it->second.get_optional(commentKey); if (!it->second.empty() && !(it->second.size()==1 && comment)) { //only two depth-levels are allowd in INI-files ... but we also have to filter out the additional .comment nodes if (throw_on_children) { BOOST_PROPERTY_TREE_THROW(ini_parser_error( "ptree is too deep (only two depth steps alowed in INI files)", "", 0)); } continue; } if (comment) { // ... if it exists: output the comment write_comment(stream, *comment, commentStart); } stream << it->first << Ch('=') << it->second.template get_value< std::basic_string >() << Ch('\n'); } } } template void write_top_level_keys(std::basic_ostream< typename Ptree::key_type::value_type > &stream, const Ptree& pt, const typename Ptree::key_type& commentKey, const typename Ptree::key_type& commentStart) { write_keys(stream, pt, false, commentKey, commentStart); } template void write_sections(std::basic_ostream< typename Ptree::key_type::value_type > &stream, const Ptree& pt, const typename Ptree::key_type& commentKey, const typename Ptree::key_type& commentStart) { typedef typename Ptree::key_type::value_type Ch; for (typename Ptree::const_iterator it = pt.begin(), end = pt.end(); it != end; ++it) { if (!it->second.empty()) { check_dupes(it->second); if (!it->second.data().empty()) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "mixed data and children", "", 0)); // empty lines in front of a new section to better separate it from other sections stream << Ch('\n') << Ch('\n'); // check for existence of comment node boost::optional comment=pt.get_optional(it->first+"."+commentKey); if (comment) { std::string c=*comment; // eat linebreak from start of comment to account for the two explicit \n inserted above! while (c.size()>0 && c[0]=='\n') c=c.substr(1); // ... if it exists: output the comment if (c.size()>0) write_comment(stream, c, commentStart); } stream << Ch('[') << it->first << Ch(']') << Ch('\n'); write_keys(stream, it->second, true, commentKey, commentStart); } } } } /** * Translates the property tree to INI and writes it the given output * stream. * @pre @e pt cannot have data in its root. * @pre @e pt cannot have keys both data and children. * @pre @e pt cannot be deeper than two levels. * @pre There cannot be duplicate keys on any given level of @e pt. * @throw ini_parser_error In case of error translating the property tree to * INI or writing to the output stream. * @param stream The stream to which to write the INI representation of the * property tree. * @param pt The property tree to tranlsate to INI and output. * @param flags The flags to use when writing the INI file. * No flags are currently supported. */ template void write_ini(std::basic_ostream< typename Ptree::key_type::value_type > &stream, const Ptree &pt, int flags = 0) { BOOST_ASSERT(validate_flags(flags)); (void)flags; const Ptree::key_type commentKey=comment_key(); const Ptree::key_type commentStart="; "; if (!pt.data().empty()) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "ptree has data on root", "", 0)); detail::check_dupes(pt); detail::write_top_level_keys(stream, pt, commentKey, commentStart); detail::write_sections(stream, pt, commentKey, commentStart); } /** * Translates the property tree to INI and writes it the given file. * @pre @e pt cannot have data in its root. * @pre @e pt cannot have keys both data and children. * @pre @e pt cannot be deeper than two levels. * @pre There cannot be duplicate keys on any given level of @e pt. * @throw info_parser_error In case of error translating the property tree * to INI or writing to the file. * @param filename The name of the file to which to write the INI * representation of the property tree. * @param pt The property tree to tranlsate to INI and output. * @param flags The flags to use when writing the INI file. * The following flags are supported: * @li @c skip_ini_validity_check -- Skip check if ptree is a valid ini. The * validity check covers the preconditions but takes O(n log n) * time. * @param loc The locale to use when writing the file. */ template void write_ini(const std::string &filename, const Ptree &pt, int flags = 0, const std::locale &loc = std::locale()) { std::basic_ofstream stream(filename.c_str()); if (!stream) BOOST_PROPERTY_TREE_THROW(ini_parser_error( "cannot open file", filename, 0)); stream.imbue(loc); try { write_ini(stream, pt, flags); } catch (ini_parser_error &e) { BOOST_PROPERTY_TREE_THROW(ini_parser_error( e.message(), filename, e.line())); } } } } } namespace boost { namespace property_tree { using ini_parser_with_comments::ini_parser_error; using ini_parser_with_comments::read_ini; using ini_parser_with_comments::write_ini; } } #endif // BOOST_PROPERTY_TREE_INI_PARSER_WITH_COMMENTS_HPP_INCLUDED