// ---------------------------------------------------------------------------- | |
// Copyright (C) 2002-2006 Marcin Kalicinski | |
// Copyright (C) 2009 Sebastian Redl | |
// | |
// 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_HPP_INCLUDED | |
#define BOOST_PROPERTY_TREE_INI_PARSER_HPP_INCLUDED | |
#include <boost/property_tree/ptree.hpp> | |
#include <boost/property_tree/detail/ptree_utils.hpp> | |
#include <boost/property_tree/detail/file_parser_error.hpp> | |
#include <fstream> | |
#include <string> | |
#include <sstream> | |
#include <stdexcept> | |
#include <locale> | |
namespace boost { namespace property_tree { namespace ini_parser | |
{ | |
/** | |
* 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. | |
*/ | |
template<class Ptree> | |
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<Ch> Str; | |
const Ch semicolon = stream.widen(';'); | |
const Ch lbracket = stream.widen('['); | |
const Ch rbracket = stream.widen(']'); | |
Ptree local; | |
unsigned long line_no = 0; | |
Ptree *section = 0; | |
Str line; | |
// 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) | |
{ | |
// Ignore comments | |
} | |
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; | |
} | |
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)); | |
container.push_back(std::make_pair(key, Ptree(data))); | |
} | |
} | |
} | |
// 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<class Ptree> | |
void read_ini(const std::string &filename, | |
Ptree &pt, | |
const std::locale &loc = std::locale()) | |
{ | |
std::basic_ifstream<typename Ptree::key_type::value_type> | |
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<class Ptree> | |
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; | |
} | |
} | |
} | |
/** | |
* 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<class Ptree> | |
void write_ini(std::basic_ostream< | |
typename Ptree::key_type::value_type | |
> &stream, | |
const Ptree &pt, | |
int flags = 0) | |
{ | |
using detail::check_dupes; | |
typedef typename Ptree::key_type::value_type Ch; | |
typedef std::basic_string<Ch> Str; | |
BOOST_ASSERT(validate_flags(flags)); | |
(void)flags; | |
if (!pt.data().empty()) | |
BOOST_PROPERTY_TREE_THROW(ini_parser_error( | |
"ptree has data on root", "", 0)); | |
check_dupes(pt); | |
for (typename Ptree::const_iterator it = pt.begin(), end = pt.end(); | |
it != end; ++it) | |
{ | |
check_dupes(it->second); | |
if (it->second.empty()) { | |
stream << it->first << Ch('=') | |
<< it->second.template get_value< | |
std::basic_string<Ch> >() | |
<< Ch('\n'); | |
} else { | |
if (!it->second.data().empty()) | |
BOOST_PROPERTY_TREE_THROW(ini_parser_error( | |
"mixed data and children", "", 0)); | |
stream << Ch('[') << it->first << Ch(']') << Ch('\n'); | |
for (typename Ptree::const_iterator it2 = it->second.begin(), | |
end2 = it->second.end(); it2 != end2; ++it2) | |
{ | |
if (!it2->second.empty()) | |
BOOST_PROPERTY_TREE_THROW(ini_parser_error( | |
"ptree is too deep", "", 0)); | |
stream << it2->first << Ch('=') | |
<< it2->second.template get_value< | |
std::basic_string<Ch> >() | |
<< Ch('\n'); | |
} | |
} | |
} | |
} | |
/** | |
* 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 <tt>O(n log n)</tt> | |
* time. | |
* @param loc The locale to use when writing the file. | |
*/ | |
template<class Ptree> | |
void write_ini(const std::string &filename, | |
const Ptree &pt, | |
int flags = 0, | |
const std::locale &loc = std::locale()) | |
{ | |
std::basic_ofstream<typename Ptree::key_type::value_type> | |
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::ini_parser_error; | |
using ini_parser::read_ini; | |
using ini_parser::write_ini; | |
} } | |
#endif |