mirror of
https://github.com/polserver/polserver
synced 2026-08-13 08:23:08 -04:00
1088 lines
23 KiB
C++
1088 lines
23 KiB
C++
/** @file
|
|
*
|
|
* @par History
|
|
* - 2005/07/01 Shinigami: added ConfigFile::_modified (stat.st_mtime) to detect cfg file
|
|
* modification
|
|
* - 2009/08/25 Shinigami: STLport-5.2.1 fix: elem->type() check will use strlen() now
|
|
*/
|
|
|
|
|
|
#include "cfgfile.h"
|
|
|
|
#include <ctype.h>
|
|
#include <exception>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "cfgelem.h"
|
|
#include "clib.h"
|
|
#include "logfacility.h"
|
|
#include "stlutil.h"
|
|
#include "strutil.h"
|
|
#include <format/format.h>
|
|
|
|
|
|
namespace Pol
|
|
{
|
|
namespace Clib
|
|
{
|
|
namespace
|
|
{
|
|
bool commentline( const std::string& str )
|
|
{
|
|
#ifdef __GNUC__
|
|
return ( ( str[0] == '#' ) || ( str.substr( 0, 2 ) == "//" ) );
|
|
#else
|
|
return ( ( str[0] == '#' ) || ( str.compare( 0, 2, "//" ) == 0 ) );
|
|
#endif
|
|
}
|
|
} // namespace
|
|
|
|
ConfigProperty::ConfigProperty( std::string name, std::string value )
|
|
: name_( std::move( name ) ), value_( std::move( value ) )
|
|
{
|
|
}
|
|
|
|
ConfigElemBase::ConfigElemBase() : type_( "" ), rest_( "" ), _source( nullptr ) {}
|
|
size_t ConfigElemBase::estimateSize() const
|
|
{
|
|
return type_.capacity() + rest_.capacity() + sizeof( _source );
|
|
}
|
|
void ConfigElemBase::throw_error( const std::string& errmsg ) const
|
|
{
|
|
if ( _source != nullptr )
|
|
_source->display_error( errmsg, false, this );
|
|
throw std::runtime_error( "Configuration file error" );
|
|
}
|
|
unsigned short ConfigElemBase::string_to_u16( const char* prop, const std::string& str )
|
|
{
|
|
// FIXME isdigit isxdigit - +
|
|
// or, use endptr
|
|
// FIXME check range within unsigned short
|
|
|
|
char* endptr = nullptr;
|
|
unsigned short val = (unsigned short)strtoul( str.c_str(), &endptr, 0 );
|
|
if ( ( endptr != nullptr ) && ( *endptr != '\0' ) && !isspace( *endptr ) )
|
|
{
|
|
std::string errmsg;
|
|
errmsg = "Poorly formed number in property '";
|
|
errmsg += prop;
|
|
errmsg += "': " + str;
|
|
throw_error( errmsg );
|
|
}
|
|
return val;
|
|
}
|
|
|
|
ConfigElem::ConfigElem() : ConfigElemBase() {}
|
|
|
|
ConfigElem::~ConfigElem() {}
|
|
|
|
size_t ConfigElem::estimateSize() const
|
|
{
|
|
size_t size = ConfigElemBase::estimateSize();
|
|
for ( const auto& ele : properties )
|
|
size += ele.first.capacity() + ele.second.capacity() + ( sizeof( void* ) * 3 + 1 ) / 2;
|
|
return size;
|
|
}
|
|
|
|
|
|
const char* ConfigElemBase::type() const
|
|
{
|
|
return type_.c_str();
|
|
}
|
|
|
|
const char* ConfigElemBase::rest() const
|
|
{
|
|
return rest_.c_str();
|
|
}
|
|
|
|
void ConfigElem::set_rest( const char* rest )
|
|
{
|
|
rest_ = rest;
|
|
}
|
|
|
|
void ConfigElem::set_type( const char* type )
|
|
{
|
|
type_ = type;
|
|
}
|
|
|
|
void ConfigElem::set_source( const ConfigElem& elem )
|
|
{
|
|
_source = elem._source;
|
|
}
|
|
void ConfigElem::set_source( const ConfigSource* source )
|
|
{
|
|
_source = source;
|
|
}
|
|
|
|
bool ConfigElemBase::type_is( const char* type ) const
|
|
{
|
|
return ( stricmp( type_.c_str(), type ) == 0 );
|
|
}
|
|
|
|
bool ConfigElem::remove_first_prop( std::string* propname, std::string* value )
|
|
{
|
|
if ( properties.empty() )
|
|
return false;
|
|
|
|
auto itr = properties.begin();
|
|
*propname = ( *itr ).first;
|
|
*value = ( *itr ).second;
|
|
properties.erase( itr );
|
|
return true;
|
|
}
|
|
|
|
bool ConfigElem::has_prop( const char* propname ) const
|
|
{
|
|
return properties.count( propname ) != 0;
|
|
}
|
|
bool VectorConfigElem::has_prop( const char* propname ) const
|
|
{
|
|
for ( const auto& prop : properties )
|
|
{
|
|
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ConfigElem::remove_prop( const char* propname, std::string* value )
|
|
{
|
|
auto itr = properties.find( propname );
|
|
if ( itr != properties.end() )
|
|
{
|
|
*value = ( *itr ).second;
|
|
properties.erase( itr );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
bool VectorConfigElem::remove_prop( const char* propname, std::string* value )
|
|
{
|
|
auto itr = properties.begin(), end = properties.end();
|
|
for ( ; itr != end; ++itr )
|
|
{
|
|
ConfigProperty* prop = *itr;
|
|
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
|
|
{
|
|
*value = prop->value_;
|
|
delete prop;
|
|
properties.erase( itr );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ConfigElem::read_prop( const char* propname, std::string* value ) const
|
|
{
|
|
auto itr = properties.find( propname );
|
|
if ( itr != properties.end() )
|
|
{
|
|
*value = ( *itr ).second;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
bool VectorConfigElem::read_prop( const char* propname, std::string* value ) const
|
|
{
|
|
for ( const auto& prop : properties )
|
|
{
|
|
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
|
|
{
|
|
*value = prop->value_;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ConfigElem::get_prop( const char* propname, unsigned int* plong ) const
|
|
{
|
|
auto itr = properties.find( propname );
|
|
if ( itr != properties.end() )
|
|
{
|
|
*plong = strtoul( ( *itr ).second.c_str(), nullptr, 0 );
|
|
}
|
|
else
|
|
{
|
|
throw_error( "SERIAL property not found" );
|
|
}
|
|
}
|
|
|
|
bool ConfigElem::remove_prop( const char* propname, unsigned int* plong )
|
|
{
|
|
auto itr = properties.find( propname );
|
|
if ( itr != properties.end() )
|
|
{
|
|
*plong = strtoul( ( *itr ).second.c_str(), nullptr, 0 );
|
|
properties.erase( itr );
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
bool VectorConfigElem::remove_prop( const char* propname, unsigned int* plong )
|
|
{
|
|
auto itr = properties.begin(), end = properties.end();
|
|
for ( ; itr != end; ++itr )
|
|
{
|
|
ConfigProperty* prop = *itr;
|
|
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
|
|
{
|
|
*plong = strtoul( prop->value_.c_str(), nullptr, 0 );
|
|
delete prop;
|
|
properties.erase( itr );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ConfigElem::remove_prop( const char* propname, unsigned short* psval )
|
|
{
|
|
std::string temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
{
|
|
*psval = string_to_u16( propname, temp );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
VectorConfigElem::~VectorConfigElem() {}
|
|
|
|
bool VectorConfigElem::remove_prop( const char* propname, unsigned short* psval )
|
|
{
|
|
auto itr = properties.begin(), end = properties.end();
|
|
for ( ; itr != end; ++itr )
|
|
{
|
|
ConfigProperty* prop = *itr;
|
|
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
|
|
{
|
|
*psval = string_to_u16( propname, prop->value_ );
|
|
delete prop;
|
|
properties.erase( itr );
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ConfigElem::warn( const std::string& errmsg ) const
|
|
{
|
|
if ( _source != nullptr )
|
|
_source->display_error( errmsg, false, this, false );
|
|
}
|
|
|
|
void ConfigElem::warn_with_line( const std::string& errmsg ) const
|
|
{
|
|
if ( _source != nullptr )
|
|
_source->display_error( errmsg, true, this, false );
|
|
}
|
|
|
|
void ConfigElem::prop_not_found( const char* propname ) const
|
|
{
|
|
std::string errmsg( "Property '" );
|
|
errmsg += propname;
|
|
errmsg += "' was not found.";
|
|
throw_error( errmsg );
|
|
}
|
|
|
|
unsigned short ConfigElem::remove_ushort( const char* propname )
|
|
{
|
|
unsigned short temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
{
|
|
return temp;
|
|
}
|
|
else
|
|
{
|
|
prop_not_found( propname ); // prop_not_found throws
|
|
}
|
|
}
|
|
|
|
unsigned short ConfigElem::remove_ushort( const char* propname, unsigned short dflt )
|
|
{
|
|
unsigned short temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
return temp;
|
|
else
|
|
return dflt;
|
|
}
|
|
|
|
unsigned short ConfigElem::read_ushort( const char* propname, unsigned short dflt )
|
|
{
|
|
std::string temp;
|
|
if ( read_prop( propname, &temp ) )
|
|
return string_to_u16( propname, temp );
|
|
return dflt;
|
|
}
|
|
unsigned short ConfigElem::read_ushort( const char* propname )
|
|
{
|
|
std::string temp;
|
|
if ( read_prop( propname, &temp ) )
|
|
return string_to_u16( propname, temp );
|
|
prop_not_found( propname ); // prop_not_found throws
|
|
}
|
|
|
|
int ConfigElem::read_int( const char* propname, int dflt )
|
|
{
|
|
std::string temp;
|
|
if ( read_prop( propname, &temp ) )
|
|
return atoi( temp.c_str() );
|
|
return dflt;
|
|
}
|
|
int ConfigElem::read_int( const char* propname )
|
|
{
|
|
std::string temp;
|
|
if ( read_prop( propname, &temp ) )
|
|
return atoi( temp.c_str() );
|
|
prop_not_found( propname ); // prop_not_found throws
|
|
}
|
|
int ConfigElem::remove_int( const char* propname )
|
|
{
|
|
std::string temp = remove_string( propname );
|
|
|
|
return atoi( temp.c_str() );
|
|
}
|
|
|
|
int ConfigElem::remove_int( const char* propname, int dflt )
|
|
{
|
|
std::string temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
{
|
|
return atoi( temp.c_str() );
|
|
}
|
|
else
|
|
{
|
|
return dflt;
|
|
}
|
|
}
|
|
|
|
unsigned ConfigElem::remove_unsigned( const char* propname )
|
|
{
|
|
std::string temp = remove_string( propname );
|
|
|
|
return strtoul( temp.c_str(), nullptr, 0 ); // TODO check unsigned range
|
|
}
|
|
|
|
unsigned ConfigElem::remove_unsigned( const char* propname, int dflt )
|
|
{
|
|
std::string temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
{
|
|
return strtoul( temp.c_str(), nullptr, 0 ); // TODO check unsigned range
|
|
}
|
|
else
|
|
{
|
|
return dflt;
|
|
}
|
|
}
|
|
|
|
|
|
std::string ConfigElem::remove_string( const char* propname )
|
|
{
|
|
std::string temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
{
|
|
return temp;
|
|
}
|
|
else
|
|
{
|
|
prop_not_found( propname );
|
|
return ""; // this is not reached, prop_not_found throws
|
|
}
|
|
}
|
|
|
|
std::string ConfigElem::read_string( const char* propname ) const
|
|
{
|
|
std::string temp;
|
|
if ( read_prop( propname, &temp ) )
|
|
{
|
|
return temp;
|
|
}
|
|
else
|
|
{
|
|
prop_not_found( propname );
|
|
return ""; // this is not reached, prop_not_found throws
|
|
}
|
|
}
|
|
std::string ConfigElem::read_string( const char* propname, const char* dflt ) const
|
|
{
|
|
std::string temp;
|
|
if ( read_prop( propname, &temp ) )
|
|
return temp;
|
|
else
|
|
return dflt;
|
|
}
|
|
|
|
std::string ConfigElem::remove_string( const char* propname, const char* dflt )
|
|
{
|
|
std::string temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
return temp;
|
|
else
|
|
return dflt;
|
|
}
|
|
|
|
bool ConfigElem::remove_bool( const char* propname )
|
|
{
|
|
return remove_ushort( propname ) ? true : false;
|
|
}
|
|
|
|
bool ConfigElem::remove_bool( const char* propname, bool dflt )
|
|
{
|
|
return remove_ushort( propname, dflt ) ? true : false;
|
|
}
|
|
|
|
float ConfigElem::remove_float( const char* propname, float dflt )
|
|
{
|
|
std::string tmp;
|
|
if ( remove_prop( propname, &tmp ) )
|
|
{
|
|
return static_cast<float>( strtod( tmp.c_str(), nullptr ) );
|
|
}
|
|
else
|
|
{
|
|
return dflt;
|
|
}
|
|
}
|
|
double ConfigElem::remove_double( const char* propname, double dflt )
|
|
{
|
|
std::string tmp;
|
|
if ( remove_prop( propname, &tmp ) )
|
|
{
|
|
return strtod( tmp.c_str(), nullptr );
|
|
}
|
|
else
|
|
{
|
|
return dflt;
|
|
}
|
|
}
|
|
|
|
unsigned int ConfigElem::remove_ulong( const char* propname )
|
|
{
|
|
unsigned int temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
{
|
|
return temp;
|
|
}
|
|
else
|
|
{
|
|
prop_not_found( propname ); // prop_not_found throws
|
|
}
|
|
}
|
|
|
|
unsigned int ConfigElem::remove_ulong( const char* propname, unsigned int dflt )
|
|
{
|
|
unsigned int temp;
|
|
if ( remove_prop( propname, &temp ) )
|
|
return temp;
|
|
else
|
|
return dflt;
|
|
}
|
|
|
|
void ConfigElem::clear_prop( const char* propname )
|
|
{
|
|
unsigned int dummy;
|
|
while ( remove_prop( propname, &dummy ) )
|
|
continue;
|
|
}
|
|
|
|
void ConfigElem::add_prop( std::string propname, std::string propval )
|
|
{
|
|
properties.emplace( std::move( propname ), std::move( propval ) );
|
|
}
|
|
void VectorConfigElem::add_prop( std::string propname, std::string propval )
|
|
{
|
|
ConfigProperty* prop = new ConfigProperty( std::move( propname ), std::move( propval ) );
|
|
|
|
properties.push_back( prop );
|
|
}
|
|
|
|
void ConfigElem::add_prop( std::string propname, unsigned short sval )
|
|
{
|
|
properties.emplace( std::move( propname ), std::to_string( sval ) );
|
|
}
|
|
void ConfigElem::add_prop( std::string propname, short sval )
|
|
{
|
|
properties.emplace( std::move( propname ), std::to_string( sval ) );
|
|
}
|
|
|
|
void VectorConfigElem::add_prop( std::string propname, unsigned short sval )
|
|
{
|
|
auto prop = new ConfigProperty( std::move( propname ), std::to_string( sval ) );
|
|
|
|
properties.push_back( prop );
|
|
}
|
|
|
|
void ConfigElem::add_prop( std::string propname, unsigned int lval )
|
|
{
|
|
properties.emplace( std::move( propname ), std::to_string( lval ) );
|
|
}
|
|
void VectorConfigElem::add_prop( std::string propname, unsigned int lval )
|
|
{
|
|
auto prop = new ConfigProperty( std::move( propname ), std::to_string( lval ) );
|
|
|
|
properties.push_back( prop );
|
|
}
|
|
|
|
bool VectorConfigElem::remove_first_prop( std::string* propname, std::string* value )
|
|
{
|
|
if ( properties.empty() )
|
|
return false;
|
|
|
|
ConfigProperty* prop = properties.front();
|
|
*propname = prop->name_;
|
|
*value = prop->value_;
|
|
delete prop;
|
|
properties.erase( properties.begin() );
|
|
return true;
|
|
}
|
|
|
|
ConfigFile::ConfigFile( const char* i_filename, const char* allowed_types_str )
|
|
: _filename( "<n/a>" ),
|
|
_modified( 0 ),
|
|
#if CFGFILE_USES_IOSTREAMS
|
|
ifs(),
|
|
#else
|
|
fp( nullptr ),
|
|
#endif
|
|
_element_line_start( 0 ),
|
|
_cur_line( 0 )
|
|
{
|
|
init( i_filename, allowed_types_str );
|
|
}
|
|
|
|
ConfigFile::ConfigFile( const std::string& i_filename, const char* allowed_types_str )
|
|
: _filename( "<n/a>" ),
|
|
_modified( 0 ),
|
|
#if CFGFILE_USES_IOSTREAMS
|
|
ifs(),
|
|
#else
|
|
fp( nullptr ),
|
|
#endif
|
|
_element_line_start( 0 ),
|
|
_cur_line( 0 )
|
|
{
|
|
init( i_filename.c_str(), allowed_types_str );
|
|
}
|
|
|
|
void ConfigFile::init( const char* i_filename, const char* allowed_types_str )
|
|
{
|
|
if ( i_filename )
|
|
{
|
|
open( i_filename );
|
|
}
|
|
|
|
if ( allowed_types_str != nullptr )
|
|
{
|
|
ISTRINGSTREAM is( allowed_types_str );
|
|
std::string tag;
|
|
while ( is >> tag )
|
|
{
|
|
allowed_types_.insert( tag.c_str() );
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::string& ConfigFile::filename() const
|
|
{
|
|
return _filename;
|
|
}
|
|
time_t ConfigFile::modified() const
|
|
{
|
|
return _modified;
|
|
}
|
|
unsigned ConfigFile::element_line_start() const
|
|
{
|
|
return _element_line_start;
|
|
}
|
|
|
|
void ConfigFile::open( const char* i_filename )
|
|
{
|
|
_filename = i_filename;
|
|
|
|
#if CFGFILE_USES_IOSTREAMS
|
|
ifs.open( i_filename, ios::in );
|
|
|
|
if ( !ifs.is_open() )
|
|
{
|
|
ERROR_PRINT << "Unable to open configuration file " << _filename << "\n";
|
|
throw std::runtime_error( std::string( "Unable to open configuration file " ) + _filename );
|
|
}
|
|
#else
|
|
fp = fopen( i_filename, "rt" );
|
|
if ( !fp )
|
|
{
|
|
POLLOG_ERROR << "Unable to open configuration file " << _filename << " " << errno << ": "
|
|
<< std::strerror( errno ) << "\n";
|
|
throw std::runtime_error( std::string( "Unable to open configuration file " ) + _filename );
|
|
}
|
|
#endif
|
|
|
|
struct stat cfgstat;
|
|
stat( i_filename, &cfgstat );
|
|
_modified = cfgstat.st_mtime;
|
|
}
|
|
|
|
ConfigFile::~ConfigFile()
|
|
{
|
|
#if !CFGFILE_USES_IOSTREAMS
|
|
if ( fp )
|
|
fclose( fp );
|
|
fp = nullptr;
|
|
#endif
|
|
}
|
|
|
|
#if !CFGFILE_USES_IOSTREAMS
|
|
char ConfigFile::buffer[1024];
|
|
#endif
|
|
|
|
#if CFGFILE_USES_IOSTREAMS
|
|
// returns true if ended on a }, false if ended on EOF.
|
|
bool ConfigFile::read_properties( ConfigElem& elem )
|
|
{
|
|
std::string strbuf;
|
|
std::string propname, propvalue;
|
|
while ( getline( ifs, strbuf ) ) // get
|
|
{
|
|
if ( !cur_line )
|
|
remove_bom( &strbuf );
|
|
++cur_line;
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
ISTRINGSTREAM is( strbuf );
|
|
|
|
|
|
splitnamevalue( strbuf, propname, propvalue );
|
|
|
|
if ( propname.empty() || // empty line
|
|
commentline( propname ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( propname == "}" )
|
|
return true;
|
|
|
|
if ( propvalue[0] == '\"' )
|
|
convertquotedstring( propvalue );
|
|
|
|
ConfigProperty* prop = new ConfigProperty( propname, propvalue );
|
|
elem.properties.push_back( prop );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ConfigFile::_read( ConfigElem& elem )
|
|
{
|
|
while ( !elem.properties.empty() )
|
|
{
|
|
delete elem.properties.back();
|
|
elem.properties.pop_back();
|
|
}
|
|
elem.type_ = "";
|
|
elem.rest_ = "";
|
|
|
|
element_line_start = 0;
|
|
|
|
std::string strbuf;
|
|
while ( getline( ifs, strbuf ) )
|
|
{
|
|
if ( !cur_line )
|
|
remove_bom( &strbuf );
|
|
++cur_line;
|
|
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
|
|
string type, rest;
|
|
splitnamevalue( strbuf, type, rest );
|
|
|
|
if ( type.empty() || // empty line
|
|
commentline( type ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
element_line_start = cur_line;
|
|
|
|
elem.type_ = type;
|
|
|
|
if ( !allowed_types_.empty() )
|
|
{
|
|
if ( allowed_types_.find( type.c_str() ) == allowed_types_.end() )
|
|
{
|
|
e_ostringstream os;
|
|
os << "Unexpected type '" << type << "'" << endl;
|
|
os << "\tValid types are:";
|
|
for ( AllowedTypesCont::const_iterator itr = allowed_types_.begin();
|
|
itr != allowed_types_.end(); ++itr )
|
|
{
|
|
os << " " << ( *itr ).c_str();
|
|
}
|
|
throw std::runtime_error( os.e_str() );
|
|
}
|
|
}
|
|
|
|
elem.rest_ = rest;
|
|
|
|
if ( !getline( ifs, strbuf ) )
|
|
throw std::runtime_error( "File ends after element type -- expected a '{'" );
|
|
++cur_line;
|
|
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
if ( strbuf.empty() || strbuf[0] != '{' )
|
|
{
|
|
throw std::runtime_error( "Expected '{' on a blank line after element type" );
|
|
}
|
|
|
|
if ( read_properties( elem ) )
|
|
return true;
|
|
else
|
|
throw std::runtime_error( "Expected '}' on a blank line after element properties" );
|
|
}
|
|
return false;
|
|
}
|
|
#else
|
|
|
|
bool ConfigFile::readline( std::string& strbuf )
|
|
{
|
|
if ( !fgets( buffer, sizeof buffer, fp ) )
|
|
return false;
|
|
|
|
strbuf = "";
|
|
|
|
do
|
|
{
|
|
char* nl = strchr( buffer, '\n' );
|
|
if ( nl )
|
|
{
|
|
if ( nl != buffer && *( nl - 1 ) == '\r' )
|
|
--nl;
|
|
*nl = '\0';
|
|
strbuf += buffer;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
strbuf += buffer;
|
|
}
|
|
} while ( fgets( buffer, sizeof buffer, fp ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
// returns true if ended on a }, false if ended on EOF.
|
|
bool ConfigFile::read_properties( ConfigElem& elem )
|
|
{
|
|
static std::string strbuf;
|
|
static std::string propname, propvalue;
|
|
while ( readline( strbuf ) )
|
|
{
|
|
if ( !_cur_line )
|
|
remove_bom( &strbuf );
|
|
++_cur_line;
|
|
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
|
|
ISTRINGSTREAM is( strbuf );
|
|
|
|
|
|
splitnamevalue( strbuf, propname, propvalue );
|
|
|
|
if ( propname.empty() || // empty line
|
|
commentline( propname ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( propname == "}" )
|
|
return true;
|
|
|
|
if ( propvalue[0] == '\"' )
|
|
{
|
|
decodequotedstring( propvalue );
|
|
}
|
|
|
|
elem.properties.emplace( propname, propvalue );
|
|
}
|
|
return false;
|
|
}
|
|
bool ConfigFile::read_properties( VectorConfigElem& elem )
|
|
{
|
|
static std::string strbuf;
|
|
static std::string propname, propvalue;
|
|
while ( readline( strbuf ) )
|
|
{
|
|
if ( !_cur_line )
|
|
remove_bom( &strbuf );
|
|
++_cur_line;
|
|
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
|
|
ISTRINGSTREAM is( strbuf );
|
|
|
|
|
|
splitnamevalue( strbuf, propname, propvalue );
|
|
|
|
if ( propname.empty() || // empty line
|
|
commentline( propname ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( propname == "}" )
|
|
return true;
|
|
|
|
if ( propvalue[0] == '\"' )
|
|
{
|
|
decodequotedstring( propvalue );
|
|
}
|
|
|
|
auto prop = new ConfigProperty( propname, propvalue );
|
|
elem.properties.push_back( prop );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ConfigFile::_read( ConfigElem& elem )
|
|
{
|
|
elem.properties.clear();
|
|
|
|
elem.type_ = "";
|
|
elem.rest_ = "";
|
|
|
|
_element_line_start = 0;
|
|
|
|
std::string strbuf;
|
|
while ( readline( strbuf ) )
|
|
{
|
|
if ( !_cur_line )
|
|
remove_bom( &strbuf );
|
|
++_cur_line;
|
|
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
|
|
std::string type, rest;
|
|
splitnamevalue( strbuf, type, rest );
|
|
|
|
if ( type.empty() || // empty line
|
|
commentline( type ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_element_line_start = _cur_line;
|
|
|
|
elem.type_ = type;
|
|
|
|
if ( !allowed_types_.empty() )
|
|
{
|
|
if ( allowed_types_.find( type.c_str() ) == allowed_types_.end() )
|
|
{
|
|
OSTRINGSTREAM os;
|
|
os << "Unexpected type '" << type << "'" << std::endl;
|
|
os << "\tValid types are:";
|
|
for ( const auto& allowed : allowed_types_ )
|
|
{
|
|
os << " " << allowed.c_str();
|
|
}
|
|
throw std::runtime_error( OSTRINGSTREAM_STR( os ) );
|
|
}
|
|
}
|
|
|
|
elem.rest_ = rest;
|
|
|
|
if ( !fgets( buffer, sizeof buffer, fp ) )
|
|
throw std::runtime_error( "File ends after element type -- expected a '{'" );
|
|
strbuf = buffer;
|
|
++_cur_line;
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
|
|
if ( strbuf.empty() || strbuf[0] != '{' )
|
|
{
|
|
throw std::runtime_error( "Expected '{' on a blank line after element type" );
|
|
}
|
|
|
|
if ( read_properties( elem ) )
|
|
return true;
|
|
else
|
|
throw std::runtime_error( "Expected '}' on a blank line after element properties" );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ConfigFile::_read( VectorConfigElem& elem )
|
|
{
|
|
while ( !elem.properties.empty() )
|
|
{
|
|
delete elem.properties.back();
|
|
elem.properties.pop_back();
|
|
}
|
|
elem.type_ = "";
|
|
elem.rest_ = "";
|
|
|
|
_element_line_start = 0;
|
|
|
|
std::string strbuf;
|
|
while ( readline( strbuf ) )
|
|
{
|
|
if ( !_cur_line )
|
|
remove_bom( &strbuf );
|
|
++_cur_line;
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
|
|
std::string type, rest;
|
|
splitnamevalue( strbuf, type, rest );
|
|
|
|
if ( type.empty() || // empty line
|
|
commentline( type ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_element_line_start = _cur_line;
|
|
|
|
elem.type_ = type;
|
|
|
|
if ( !allowed_types_.empty() )
|
|
{
|
|
if ( allowed_types_.find( type.c_str() ) == allowed_types_.end() )
|
|
{
|
|
OSTRINGSTREAM os;
|
|
os << "Unexpected type '" << type << "'" << std::endl;
|
|
os << "\tValid types are:";
|
|
for ( const auto& allowed : allowed_types_ )
|
|
{
|
|
os << " " << allowed.c_str();
|
|
}
|
|
throw std::runtime_error( OSTRINGSTREAM_STR( os ) );
|
|
}
|
|
}
|
|
|
|
elem.rest_ = rest;
|
|
|
|
if ( !fgets( buffer, sizeof buffer, fp ) )
|
|
throw std::runtime_error( "File ends after element type -- expected a '{'" );
|
|
strbuf = buffer;
|
|
++_cur_line;
|
|
sanitizeUnicodeWithIso( &strbuf );
|
|
|
|
if ( strbuf.empty() || strbuf[0] != '{' )
|
|
{
|
|
throw std::runtime_error( "Expected '{' on a blank line after element type" );
|
|
}
|
|
|
|
if ( read_properties( elem ) )
|
|
return true;
|
|
else
|
|
throw std::runtime_error( "Expected '}' on a blank line after element properties" );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
void ConfigFile::display_error( const std::string& msg, bool show_curline,
|
|
const ConfigElemBase* elem, bool error ) const
|
|
{
|
|
bool showed_elem_line = false;
|
|
fmt::Writer tmp;
|
|
tmp << ( error ? "Error" : "Warning" ) << " reading configuration file " << _filename << ":\n"
|
|
<< "\t" << msg << "\n";
|
|
|
|
if ( elem != nullptr )
|
|
{
|
|
if ( strlen( elem->type() ) > 0 )
|
|
{
|
|
tmp << "\tElement: " << elem->type() << " " << elem->rest();
|
|
if ( _element_line_start )
|
|
tmp << ", found on line " << _element_line_start;
|
|
tmp << "\n";
|
|
showed_elem_line = true;
|
|
}
|
|
}
|
|
|
|
if ( show_curline )
|
|
tmp << "\tNear line: " << _cur_line << "\n";
|
|
if ( _element_line_start && !showed_elem_line )
|
|
tmp << "\tElement started on line: " << _element_line_start << "\n";
|
|
ERROR_PRINT << tmp.str();
|
|
}
|
|
|
|
void ConfigFile::display_and_rethrow_exception()
|
|
{
|
|
try
|
|
{
|
|
throw;
|
|
}
|
|
catch ( const char* msg )
|
|
{
|
|
display_error( msg );
|
|
}
|
|
catch ( std::string& str )
|
|
{
|
|
display_error( str );
|
|
}
|
|
catch ( std::exception& ex )
|
|
{
|
|
display_error( ex.what() );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
display_error( "(Generic exception)" );
|
|
}
|
|
|
|
throw std::runtime_error( "Configuration file error." );
|
|
}
|
|
|
|
bool ConfigFile::read( ConfigElem& elem )
|
|
{
|
|
try
|
|
{
|
|
elem._source = this;
|
|
return _read( elem );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
display_and_rethrow_exception(); // throws and doesn't return
|
|
}
|
|
}
|
|
|
|
void ConfigFile::readraw( ConfigElem& elem )
|
|
{
|
|
try
|
|
{
|
|
elem._source = this;
|
|
if ( read_properties( elem ) )
|
|
throw std::runtime_error( "unexpected '}' in file" );
|
|
}
|
|
catch ( ... )
|
|
{
|
|
display_and_rethrow_exception();
|
|
}
|
|
}
|
|
|
|
void StubConfigSource::display_error( const std::string& msg, bool /*show_curline*/,
|
|
const ConfigElemBase* /*elem*/, bool error ) const
|
|
{
|
|
ERROR_PRINT << ( error ? "Error" : "Warning" ) << " reading configuration element:"
|
|
<< "\t" << msg << "\n";
|
|
}
|
|
} // namespace Clib
|
|
} // namespace Pol
|