|
|
@ -9,6 +9,9 @@ |
|
|
|
@license LGPL, see file <a href="license.html">COPYING</a> |
|
|
|
@license LGPL, see file <a href="license.html">COPYING</a> |
|
|
|
|
|
|
|
|
|
|
|
$Log$ |
|
|
|
$Log$ |
|
|
|
|
|
|
|
Revision 1.2 2005/01/28 07:49:32 marc |
|
|
|
|
|
|
|
Save configuration using file.hpp |
|
|
|
|
|
|
|
|
|
|
|
Revision 1.1 2005/01/07 00:31:38 marc |
|
|
|
Revision 1.1 2005/01/07 00:31:38 marc |
|
|
|
initial version |
|
|
|
initial version |
|
|
|
|
|
|
|
|
|
|
@ -24,6 +27,8 @@ namespace mrw { |
|
|
|
|
|
|
|
|
|
|
|
/** @defgroup config Configuration File Handler
|
|
|
|
/** @defgroup config Configuration File Handler
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pre #include <mrw/configfile.hpp> |
|
|
|
|
|
|
|
|
|
|
|
Read configuration parameters from a file. |
|
|
|
Read configuration parameters from a file. |
|
|
|
|
|
|
|
|
|
|
|
@section configSyntax Configuration File Syntax |
|
|
|
@section configSyntax Configuration File Syntax |
|
|
@ -162,178 +167,268 @@ namespace mrw { |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
//@{
|
|
|
|
//@{
|
|
|
|
|
|
|
|
|
|
|
|
/// Parse configuration file and access the contents.
|
|
|
|
//============================================================================
|
|
|
|
/** Parse a configuration file that consists of sections containing
|
|
|
|
/** @brief Parse configuration file and access the contents.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Parse a configuration file that consists of sections containing |
|
|
|
variables with values. This class does not store anything in the |
|
|
|
variables with values. This class does not store anything in the |
|
|
|
file, even if variables have changed. |
|
|
|
file, even if variables have changed. |
|
|
|
@see If you want %to save changes %to the file, use |
|
|
|
|
|
|
|
@ref ConfigFileWriter. |
|
|
|
@see If you want %to save changes %to the file, use @ref ConfigFileWriter. |
|
|
|
@see For details, see @ref configSyntax. */ |
|
|
|
@see For details, see @ref configSyntax. |
|
|
|
|
|
|
|
*/ |
|
|
|
class ConfigFileReader { |
|
|
|
class ConfigFileReader { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//............................................................... typedefs
|
|
|
|
public: |
|
|
|
public: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------ Value
|
|
|
|
/// A configuration file value.
|
|
|
|
/// A configuration file value.
|
|
|
|
class Value { |
|
|
|
class Value { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//............................................................ methods
|
|
|
|
public: |
|
|
|
public: |
|
|
|
/// Get the value string.
|
|
|
|
|
|
|
|
/** Get the value string. Trailing and leading white spaces have
|
|
|
|
/** @brief Get the value string.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Get the value string. Trailing and leading white spaces have |
|
|
|
been truncated. If the variable was quoted, quotes have been |
|
|
|
been truncated. If the variable was quoted, quotes have been |
|
|
|
removed. |
|
|
|
removed. |
|
|
|
@return the variable's value as string */ |
|
|
|
|
|
|
|
|
|
|
|
@return the variable's value as string |
|
|
|
|
|
|
|
*/ |
|
|
|
operator const std::string&() const throw() { |
|
|
|
operator const std::string&() const throw() { |
|
|
|
return value; |
|
|
|
return value; |
|
|
|
} |
|
|
|
} |
|
|
|
/// Get the value string.
|
|
|
|
|
|
|
|
/** Alternative access to the variable's contents. Trailing and
|
|
|
|
/** @brief Get the value string.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Alternative access to the variable's contents. Trailing and |
|
|
|
leading white spaces have been truncated. If the variable |
|
|
|
leading white spaces have been truncated. If the variable |
|
|
|
was quoted, quotes have been removed. |
|
|
|
was quoted, quotes have been removed. |
|
|
|
@return the variable's value as string */ |
|
|
|
|
|
|
|
|
|
|
|
@return the variable's value as string |
|
|
|
|
|
|
|
*/ |
|
|
|
const std::string& operator()() const throw() { |
|
|
|
const std::string& operator()() const throw() { |
|
|
|
return value; |
|
|
|
return value; |
|
|
|
} |
|
|
|
} |
|
|
|
/// Assign a new value.
|
|
|
|
|
|
|
|
/** Assign a new value. The new value is lost if you use the
|
|
|
|
/** @brief Assign a new value.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Assign a new value. The new value is lost if you use the |
|
|
|
ConfigFileReader, but it is stored in the destructor, if you |
|
|
|
ConfigFileReader, but it is stored in the destructor, if you |
|
|
|
use the ConfigFileWriter instead. */ |
|
|
|
use the ConfigFileWriter instead. |
|
|
|
|
|
|
|
*/ |
|
|
|
Value& operator=(const std::string& newValue) throw() { |
|
|
|
Value& operator=(const std::string& newValue) throw() { |
|
|
|
changed = true; |
|
|
|
changed = true; |
|
|
|
value = newValue; |
|
|
|
value = newValue; |
|
|
|
return *this; |
|
|
|
return *this; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Compare the value to a string.
|
|
|
|
/// Compare the value to a string.
|
|
|
|
bool operator==(const std::string& o) const throw() { |
|
|
|
bool operator==(const std::string& o) const throw() { |
|
|
|
return o==value; |
|
|
|
return o==value; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Compare a string to the value.
|
|
|
|
/// Compare a string to the value.
|
|
|
|
friend bool operator==(const std::string& o, const Value& v) throw() { |
|
|
|
friend bool operator==(const std::string& o, const Value& v) throw() { |
|
|
|
return v==o; |
|
|
|
return v==o; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//............................................................ methods
|
|
|
|
protected: |
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
|
|
friend class ConfigFileReader; |
|
|
|
friend class ConfigFileReader; |
|
|
|
friend class ConfigFileWriter; |
|
|
|
friend class ConfigFileWriter; |
|
|
|
Value(const std::string& v, |
|
|
|
|
|
|
|
std::string::size_type s, std::string::size_type e) throw(): |
|
|
|
Value(const std::string& v, std::string::size_type s, |
|
|
|
value(v), changed(false), start(s), end(e) { |
|
|
|
std::string::size_type e) throw(): |
|
|
|
} |
|
|
|
value(v), changed(false), start(s), end(e) {} |
|
|
|
|
|
|
|
|
|
|
|
Value(const std::string& v) throw(): |
|
|
|
Value(const std::string& v) throw(): |
|
|
|
value(v), changed(true), start(0), end(0) { |
|
|
|
value(v), changed(true), start(0), end(0) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//.......................................................... variables
|
|
|
|
|
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
|
|
std::string value; |
|
|
|
std::string value; |
|
|
|
bool changed; |
|
|
|
bool changed; |
|
|
|
std::string::size_type start; |
|
|
|
std::string::size_type start; |
|
|
|
std::string::size_type end; |
|
|
|
std::string::size_type end; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//............................................................ methods
|
|
|
|
private: |
|
|
|
private: |
|
|
|
|
|
|
|
|
|
|
|
Value(); // not implemented, must be initialized
|
|
|
|
Value(); // not implemented, must be initialized
|
|
|
|
Value& operator=(const Value&); // not implemented, no assignment
|
|
|
|
Value& operator=(const Value&); // not implemented, no assignment
|
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
/// Uninitialized construction.
|
|
|
|
//------------------------------------------------------------------------
|
|
|
|
/** Uninitialized construction. Use this constructor, if you want
|
|
|
|
|
|
|
|
to call @ref load() later. */ |
|
|
|
//................................................................ methods
|
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** @brief Uninitialized construction.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Uninitialized construction. Use this constructor, if you |
|
|
|
|
|
|
|
want to call @ref load() later. |
|
|
|
|
|
|
|
*/ |
|
|
|
ConfigFileReader() throw() {} |
|
|
|
ConfigFileReader() throw() {} |
|
|
|
/// Parse a file at construction.
|
|
|
|
|
|
|
|
/** This should be the normal case: load and parse the file at
|
|
|
|
/** @brief Parse a file at construction.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This should be the normal case: load and parse the file at |
|
|
|
construction. |
|
|
|
construction. |
|
|
|
|
|
|
|
|
|
|
|
@param filename the name of the file to read |
|
|
|
@param filename the name of the file to read |
|
|
|
@throw mrw::invalid_argument |
|
|
|
@throw mrw::invalid_argument |
|
|
|
- if the file does not exist |
|
|
|
- if the file does not exist |
|
|
|
- if reading the file fails |
|
|
|
- if reading the file fails |
|
|
|
- if there is an unterminated section name in the file |
|
|
|
- if there is an unterminated section name in the file |
|
|
|
- if there is an empty variable name in the file */ |
|
|
|
- if there is an empty variable name in the file |
|
|
|
|
|
|
|
*/ |
|
|
|
ConfigFileReader(const std::string& filename) throw(std::exception): |
|
|
|
ConfigFileReader(const std::string& filename) throw(std::exception): |
|
|
|
_filename(filename) { |
|
|
|
_filename(filename) { |
|
|
|
reload(); |
|
|
|
reload(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Copy construct from other ConfigFileReader.
|
|
|
|
/// Copy construct from other ConfigFileReader.
|
|
|
|
ConfigFileReader(const ConfigFileReader& o) throw(): |
|
|
|
ConfigFileReader(const ConfigFileReader& o) throw(): |
|
|
|
_filename(o._filename), _values(o._values) { |
|
|
|
_filename(o._filename), _values(o._values) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
virtual ~ConfigFileReader() throw() {} |
|
|
|
virtual ~ConfigFileReader() throw() {} |
|
|
|
|
|
|
|
|
|
|
|
/// Copy from other ConfigFileReader.
|
|
|
|
/// Copy from other ConfigFileReader.
|
|
|
|
ConfigFileReader& operator=(const ConfigFileReader& o) { |
|
|
|
ConfigFileReader& operator=(const ConfigFileReader& o) { |
|
|
|
_filename = o._filename; |
|
|
|
_filename = o._filename; |
|
|
|
_values = o._values; |
|
|
|
_values = o._values; |
|
|
|
return *this; |
|
|
|
return *this; |
|
|
|
} |
|
|
|
} |
|
|
|
/// Access a value read from the file.
|
|
|
|
|
|
|
|
/** Access a value read from the file.
|
|
|
|
/** @brief Access a value read from the file.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Access a value read from the file. |
|
|
|
|
|
|
|
|
|
|
|
@param section the section in the configuration file |
|
|
|
@param section the section in the configuration file |
|
|
|
@param name the variable name |
|
|
|
@param name the variable name |
|
|
|
@param default_ the default value that is set, if the variable is |
|
|
|
@param default_ the default value that is set, if the variable is |
|
|
|
not configured |
|
|
|
not configured |
|
|
|
@return the Value you are looking for */ |
|
|
|
@return the Value you are looking for |
|
|
|
|
|
|
|
*/ |
|
|
|
Value& operator()(const std::string& section, const std::string& name, |
|
|
|
Value& operator()(const std::string& section, const std::string& name, |
|
|
|
const std::string& default_) |
|
|
|
const std::string& default_) |
|
|
|
throw(std::bad_exception); |
|
|
|
throw(std::bad_exception); |
|
|
|
/// Load and parse a new file.
|
|
|
|
|
|
|
|
/** Load and parse a new file.
|
|
|
|
/** @brief Load and parse a new file.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Load and parse a new file. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@param filename the name of the file to load |
|
|
|
@throw mrw::invalid_argument |
|
|
|
@throw mrw::invalid_argument |
|
|
|
- if the file does not exist |
|
|
|
- if the file does not exist |
|
|
|
- if reading the file fails |
|
|
|
- if reading the file fails |
|
|
|
- if there is an unterminated section name in the file |
|
|
|
- if there is an unterminated section name in the file |
|
|
|
- if there is an empty variable name in the file */ |
|
|
|
- if there is an empty variable name in the file |
|
|
|
|
|
|
|
*/ |
|
|
|
ConfigFileReader& load(const std::string& filename) throw(std::exception); |
|
|
|
ConfigFileReader& load(const std::string& filename) throw(std::exception); |
|
|
|
/// Reload the last file again.
|
|
|
|
|
|
|
|
/** Reload the last file again.
|
|
|
|
/** @brief Reload the last file again.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Reload the last file again. |
|
|
|
|
|
|
|
|
|
|
|
@throw mrw::invalid_argument |
|
|
|
@throw mrw::invalid_argument |
|
|
|
- if the file does not exist |
|
|
|
- if the file does not exist |
|
|
|
- if reading the file fails |
|
|
|
- if reading the file fails |
|
|
|
- if there is an unterminated section name in the file |
|
|
|
- if there is an unterminated section name in the file |
|
|
|
- if there is an empty variable name in the file */ |
|
|
|
- if there is an empty variable name in the file |
|
|
|
|
|
|
|
*/ |
|
|
|
virtual ConfigFileReader& reload() throw(std::exception); |
|
|
|
virtual ConfigFileReader& reload() throw(std::exception); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//............................................................... typedefs
|
|
|
|
protected: |
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
|
|
typedef std::map< std::string, std::map<std::string, Value> > Values; |
|
|
|
typedef std::map< std::string, std::map<std::string, Value> > Values; |
|
|
|
typedef std::map<std::string, std::string::size_type> Sections; |
|
|
|
typedef std::map<std::string, std::string::size_type> Sections; |
|
|
|
std::string readFile() const throw(std::exception); |
|
|
|
|
|
|
|
|
|
|
|
//.............................................................. variables
|
|
|
|
|
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
std::string _filename; |
|
|
|
|
|
|
|
Values _values; |
|
|
|
|
|
|
|
Sections _sections; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//................................................................ methods
|
|
|
|
|
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
|
|
ConfigFileReader& parse(const std::string& file) throw(std::exception); |
|
|
|
ConfigFileReader& parse(const std::string& file) throw(std::exception); |
|
|
|
|
|
|
|
|
|
|
|
std::string parseSection(const std::string& file, |
|
|
|
std::string parseSection(const std::string& file, |
|
|
|
std::string::size_type& pos) |
|
|
|
std::string::size_type& pos) |
|
|
|
const throw(std::exception); |
|
|
|
const throw(std::exception); |
|
|
|
|
|
|
|
|
|
|
|
Values::mapped_type::value_type parseValue(const std::string& file, |
|
|
|
Values::mapped_type::value_type parseValue(const std::string& file, |
|
|
|
std::string::size_type& pos) |
|
|
|
std::string::size_type& pos) |
|
|
|
const throw(std::exception); |
|
|
|
const throw(std::exception); |
|
|
|
|
|
|
|
|
|
|
|
std::string parseComment(const std::string& file, |
|
|
|
std::string parseComment(const std::string& file, |
|
|
|
std::string::size_type& pos) |
|
|
|
std::string::size_type& pos) |
|
|
|
const throw(std::bad_exception); |
|
|
|
const throw(std::bad_exception); |
|
|
|
std::string _filename; |
|
|
|
|
|
|
|
Values _values; |
|
|
|
|
|
|
|
Sections _sections; |
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//============================================================================
|
|
|
|
/** @brief Parse configuration file and offer read / write
|
|
|
|
/** @brief Parse configuration file and offer read / write
|
|
|
|
access to the contents. |
|
|
|
access to the contents. |
|
|
|
|
|
|
|
|
|
|
|
Parse a configuration file that consists of sections containing |
|
|
|
Parse a configuration file that consists of sections containing |
|
|
|
variables with values. This class stores the file in the destructor |
|
|
|
variables with values. This class stores the file in the destructor |
|
|
|
if variables have changed. |
|
|
|
if variables have changed. |
|
|
|
@see If you want %to leave the file untouched, use |
|
|
|
|
|
|
|
@ref ConfigFileReader. |
|
|
|
@see If you want %to leave the file untouched, use @ref ConfigFileReader. |
|
|
|
@see For details, see @ref configSyntax. */ |
|
|
|
@see For details, see @ref configSyntax. |
|
|
|
|
|
|
|
*/ |
|
|
|
class ConfigFileWriter: virtual public ConfigFileReader { |
|
|
|
class ConfigFileWriter: virtual public ConfigFileReader { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//................................................................ methods
|
|
|
|
public: |
|
|
|
public: |
|
|
|
/// Uninitialized construction.
|
|
|
|
|
|
|
|
/** @copydoc ConfigFileReader() */ |
|
|
|
/** @brief Uninitialized construction.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@copydoc ConfigFileReader() |
|
|
|
|
|
|
|
*/ |
|
|
|
ConfigFileWriter() throw() {} |
|
|
|
ConfigFileWriter() throw() {} |
|
|
|
/// Parse a file at construction.
|
|
|
|
|
|
|
|
/** @copydoc ConfigFileReader(const std::string& filename) */ |
|
|
|
/** @brief Parse a file at construction.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@copydoc ConfigFileReader(const std::string&) |
|
|
|
|
|
|
|
*/ |
|
|
|
ConfigFileWriter(const std::string& filename) throw(std::exception) { |
|
|
|
ConfigFileWriter(const std::string& filename) throw(std::exception) { |
|
|
|
load(filename); |
|
|
|
load(filename); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Copy construct from other ConfigFileWriter.
|
|
|
|
/// Copy construct from other ConfigFileWriter.
|
|
|
|
ConfigFileWriter(const ConfigFileWriter& o) throw(): |
|
|
|
ConfigFileWriter(const ConfigFileWriter& o) throw(): |
|
|
|
ConfigFileReader(o) { |
|
|
|
ConfigFileReader(o) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// The file is stored at destruction.
|
|
|
|
/// The file is stored at destruction.
|
|
|
|
virtual ~ConfigFileWriter() throw() { |
|
|
|
virtual ~ConfigFileWriter() throw() { |
|
|
|
save(); |
|
|
|
save(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Copy from other ConfigFileWriter.
|
|
|
|
/// Copy from other ConfigFileWriter.
|
|
|
|
ConfigFileWriter& operator=(const ConfigFileWriter& o) { |
|
|
|
ConfigFileWriter& operator=(const ConfigFileWriter& o) { |
|
|
|
ConfigFileReader::operator=(o); |
|
|
|
ConfigFileReader::operator=(o); |
|
|
|
return *this; |
|
|
|
return *this; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// GNU g++ prior to 3.4 does not implement covariant returns
|
|
|
|
// GNU g++ prior to 3.4 does not implement covariant returns
|
|
|
|
# if __GNUC__ == 3 && __GNUC_MINOR__ < 4 |
|
|
|
# if __GNUC__ == 3 && __GNUC_MINOR__ < 4 |
|
|
|
/// Reload the last file again.
|
|
|
|
/** @brief Reload the last file again.
|
|
|
|
/** @copydoc mrw::ConfigFileReader::reload()
|
|
|
|
|
|
|
|
|
|
|
|
@copydoc mrw::ConfigFileReader::reload() |
|
|
|
|
|
|
|
|
|
|
|
@bug Bug in GNU g++ compiler: |
|
|
|
@bug Bug in GNU g++ compiler: |
|
|
|
return type should be <code>ConfigFileWriter&</code>, |
|
|
|
return type should be <code>ConfigFileWriter&</code>, |
|
|
|
but the compiler sais:<br> |
|
|
|
but the compiler sais:<br> |
|
|
@ -342,19 +437,29 @@ namespace mrw { |
|
|
|
covariant returns</code><br> |
|
|
|
covariant returns</code><br> |
|
|
|
The problem is fixed since GNU g++ 3.4, so the signature |
|
|
|
The problem is fixed since GNU g++ 3.4, so the signature |
|
|
|
will be changed as soon as you upgrade to the new compiler |
|
|
|
will be changed as soon as you upgrade to the new compiler |
|
|
|
version. */ |
|
|
|
version. |
|
|
|
|
|
|
|
*/ |
|
|
|
virtual ConfigFileReader& reload() throw(std::exception); |
|
|
|
virtual ConfigFileReader& reload() throw(std::exception); |
|
|
|
# else |
|
|
|
# else |
|
|
|
/// Reload the last file again.
|
|
|
|
/** @brief Reload the last file again.
|
|
|
|
/** @copydoc mrw::ConfigFileReader::reload() */ |
|
|
|
|
|
|
|
|
|
|
|
@copydoc mrw::ConfigFileReader::reload() |
|
|
|
|
|
|
|
*/ |
|
|
|
virtual ConfigFileWriter& reload() throw(std::exception); |
|
|
|
virtual ConfigFileWriter& reload() throw(std::exception); |
|
|
|
# endif |
|
|
|
# endif |
|
|
|
/// Saves changes back to the file.
|
|
|
|
|
|
|
|
/** All changed parameters are stored in the configuration
|
|
|
|
/** @brief Saves changes back to the file.
|
|
|
|
file. The rest of the file is left untouched. */ |
|
|
|
|
|
|
|
|
|
|
|
All changed parameters are stored in the configuration |
|
|
|
|
|
|
|
file. The rest of the file is left untouched. |
|
|
|
|
|
|
|
*/ |
|
|
|
ConfigFileWriter& save() throw(std::exception); |
|
|
|
ConfigFileWriter& save() throw(std::exception); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//.............................................................. variables
|
|
|
|
protected: |
|
|
|
protected: |
|
|
|
|
|
|
|
|
|
|
|
std::string _file; |
|
|
|
std::string _file; |
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
//@}
|
|
|
|
//@}
|
|
|
|