Skip to content

Commit

Permalink
IO: allow to read/write XML/JSON configurations from/to strings
Browse files Browse the repository at this point in the history
  • Loading branch information
andrea-iob committed Jan 19, 2024
1 parent aeb8383 commit 7b6dbd0
Show file tree
Hide file tree
Showing 9 changed files with 491 additions and 157 deletions.
92 changes: 85 additions & 7 deletions src/IO/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

#include <stringUtils.hpp>

#include <sstream>

namespace bitpit {

/*!
Expand Down Expand Up @@ -171,23 +173,52 @@ void ConfigParser::read(const std::string &filename, bool append)
extension = utils::string::trim(extension);
}

config::FileFormat fileFormat;
config::SourceFormat fileFormat;
if (extension == "xml" || extension == "XML") {
fileFormat = config::FILE_FORMAT_XML;
fileFormat = config::SOURCE_FORMAT_XML;
} else if (extension == "json" || extension == "JSON") {
fileFormat = config::FILE_FORMAT_JSON;
fileFormat = config::SOURCE_FORMAT_JSON;
} else {
throw std::runtime_error("ConfigParser::read - Unsupported file format");
}

bool checkFileVersion = false;
if (fileFormat == config::FILE_FORMAT_XML) {
if (fileFormat == config::SOURCE_FORMAT_XML) {
checkFileVersion = m_checkVersion;
}

config::tree::readConfiguration(filename, fileFormat, m_rootName, checkFileVersion, m_version, this);
}

/*!
Read the specified content.
Configuration can be read from a content either in XML or JSON format.
\param format is the format of the content
\param content is the content that will be read
\param append controls if the configuration file will be appended to the
current configuration or if the current configuration will be overwritten
with the contents of the configuration file.
*/
void ConfigParser::read(config::SourceFormat format, const std::string &content, bool append)
{
// Processing not-append requests
if (!append) {
clear();
}

// Read the configuration
bool checkFileVersion = false;
if (format == config::SOURCE_FORMAT_XML) {
checkFileVersion = m_checkVersion;
}

std::stringstream contentStream(content, std::ios_base::in | std::ios_base::out | std::ios_base::binary);

config::tree::readConfiguration(contentStream, format, m_rootName, checkFileVersion, m_version, this);
}

/*!
Write the configuration to the specified file.
Expand All @@ -205,18 +236,35 @@ void ConfigParser::write(const std::string &filename) const
extension = utils::string::trim(extension);
}

config::FileFormat fileFormat;
config::SourceFormat fileFormat;
if (extension == "xml" || extension == "XML") {
fileFormat = config::FILE_FORMAT_XML;
fileFormat = config::SOURCE_FORMAT_XML;
} else if (extension == "json" || extension == "JSON") {
fileFormat = config::FILE_FORMAT_JSON;
fileFormat = config::SOURCE_FORMAT_JSON;
} else {
throw std::runtime_error("ConfigParser::read - Unsupported file format");
}

config::tree::writeConfiguration(filename, fileFormat, m_rootName, m_version, this);
}

/*!
Write the configuration to the specified string.
Configuration can be written either in XML or JSON format.
\param format is the format that will be used to write the configuration
\param content on output will contain the configuration in the specified format
*/
void ConfigParser::write(config::SourceFormat format, std::string *content) const
{
std::stringstream contentStream(*content, std::ios_base::in | std::ios_base::out | std::ios_base::binary);

config::tree::writeConfiguration(contentStream, format, m_rootName, m_version, this);

contentStream.flush();
}

/*!
\class GlobalConfigParser
\ingroup Configuration
Expand Down Expand Up @@ -356,6 +404,23 @@ namespace config {
root.read(filename, append);
}

/*!
Read the specified configuration file.
Configuration file can be either XML or JSON files (if JSON support was
found at compile time).
\param format is the format of the content
\param content is the content that will be read
\param append controls if the configuration file will be appended to the
current configuration or if the current configuration will be overwritten
with the contents of the configuration file.
*/
void read(config::SourceFormat format, const std::string &content, bool append)
{
root.read(format, content, append);
}

/*!
Write the configuration to the specified file.
Expand All @@ -368,6 +433,19 @@ namespace config {
root.write(filename);
}

/*!
Write the configuration to the specified string.
Configuration can be written either in XML or JSON format.
\param format is the format that will be used to write the configuration
\param content on output will contain the configuration in the specified format
*/
void write(config::SourceFormat format, std::string *content)
{
root.write(format, content);
}

}

}
5 changes: 5 additions & 0 deletions src/IO/configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <memory>
#include <string>

#include "configuration_common.hpp"
#include "configuration_config.hpp"

namespace bitpit {
Expand All @@ -46,7 +47,9 @@ class ConfigParser : public Config
void reset(const std::string &rootName, int version, bool multiSections);

void read(const std::string &filename, bool append = true);
void read(config::SourceFormat format, const std::string &content, bool append = true);
void write(const std::string &filename) const;
void write(config::SourceFormat format, std::string *content) const;

private:
static const int VERSION_UNDEFINED;
Expand Down Expand Up @@ -98,7 +101,9 @@ namespace config {
void reset(const std::string &rootName, int version, bool multiSections);

void read(const std::string &filename, bool append = true);
void read(config::SourceFormat format, const std::string &content, bool append = true);
void write(const std::string &filename);
void write(config::SourceFormat format, std::string *content);

};

Expand Down
20 changes: 10 additions & 10 deletions src/IO/configuration_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,29 @@ namespace bitpit {
namespace config {

/*!
Check if the specified file format has a root section.
Check if the specified source format has a root section.
\param format is the format of the file
\result Return true if the specified file format has a root section, false otherwise.
\param format is the format of the source
\result Return true if the specified source format has a root section, false otherwise.
*/
bool hasRootSection(FileFormat format)
bool hasRootSection(SourceFormat format)
{
if (format == FILE_FORMAT_XML) {
if (format == SOURCE_FORMAT_XML) {
return true;
}

return false;
}

/*!
Check if the specified file format supports arrays.
Check if the specified source format supports arrays.
\param format is the format of the file
\result Return true if the specified file format support arrays, false otherwise.
\param format is the format of the source
\result Return true if the specified source format support arrays, false otherwise.
*/
bool hasArraySupport(FileFormat format)
bool hasArraySupport(SourceFormat format)
{
if (format == FILE_FORMAT_JSON) {
if (format == SOURCE_FORMAT_JSON) {
return true;
}

Expand Down
10 changes: 5 additions & 5 deletions src/IO/configuration_common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ namespace bitpit {

namespace config {

enum FileFormat {
FILE_FORMAT_JSON,
FILE_FORMAT_XML,
enum SourceFormat {
SOURCE_FORMAT_JSON,
SOURCE_FORMAT_XML,
};

bool hasRootSection(FileFormat format);
bool hasArraySupport(FileFormat format);
bool hasRootSection(SourceFormat format);
bool hasArraySupport(SourceFormat format);

}

Expand Down
141 changes: 17 additions & 124 deletions src/IO/configuration_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@

#include "configuration_tree.hpp"

#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/xml_parser.hpp>

#include <unordered_set>

namespace bitpit {
Expand All @@ -35,127 +32,6 @@ namespace config {

namespace tree {

/*!
Read the specified configuration file.
\param filename is the filename of the configuration file
\param format is the format of the file
\param rootname name of the root to assign to the configuration file
\param checkVersion boolean to enable the control of the version
\param version number of version to assign to the configuration file
\param rootConfig pointer to the configuration tree to be written on file
\param[in,out] rootConfig pointer to configuration tree to store data parsed from document.
*/
void readConfiguration(const std::string &filename, FileFormat format,
const std::string &rootname, bool checkVersion,
int version, Config *rootConfig)
{
if (!rootConfig) {
throw std::runtime_error("config::tree::readConfiguration Null Config tree structure passed");
}

// Read property tree
boost::property_tree::ptree propertyTree;
if (format == FILE_FORMAT_XML) {
boost::property_tree::read_xml(filename, propertyTree);
} else if (format == FILE_FORMAT_JSON) {
boost::property_tree::read_json(filename, propertyTree);
}

// Check if a root node exists
bool hasRootNode = hasRootSection(format);

// Validate root node
if (hasRootNode) {
// Get the root node
auto rootNode = propertyTree.begin();

// Check if the root name matches the requested one
if (rootNode->first != rootname) {
throw std::runtime_error("The name of the root element is not \"" + rootname + "\"");
}

// Check if the version matches the requested one
if (checkVersion) {
try {
int fileVersion = rootNode->second.get<int>("<xmlattr>.version");
if (fileVersion != version) {
throw std::runtime_error("The version of the configuration file is not not \"" + std::to_string(version) + "\"");
}
} catch (boost::property_tree::ptree_bad_path &error) {
throw std::runtime_error("Unable to identify the version of the configuration");
}
}
}

// Read configuration
const boost::property_tree::ptree *configTree;
if (hasRootNode) {
configTree = &(propertyTree.begin()->second);
} else {
configTree = &propertyTree;
}

importTree(*configTree, rootConfig);
}

/*!
Write the configuration to the specified file.
\param filename is the filename where the configuration will be written to
\param format is the format of the file
\param rootname name of the root to assign to the configuration file
\param version number of version to assign to the configuration file
\param rootConfig pointer to the configuration tree to be written on file
*/
void writeConfiguration(const std::string &filename, FileFormat format,
const std::string &rootname, int version,
const Config *rootConfig)
{
if (!rootConfig) {
throw std::runtime_error("config::tree::writeConfiguration Null Config tree structure passed");
}


// Check if a root node exists
bool hasRootNode = hasRootSection(format);

// Create an empty property tree
boost::property_tree::ptree propertyTree;

// Create the root node
if (hasRootNode) {
// Create the root
propertyTree.put<std::string>(rootname, "");

// Get the root node
auto rootNode = propertyTree.begin();

// Add an attribute with version
rootNode->second.put<int>("<xmlattr>.version", version);
}

// Export the configuration into the property tree
boost::property_tree::ptree *configTree;
if (hasRootNode) {
configTree = &(propertyTree.begin()->second);
} else {
configTree = &propertyTree;
}

bool areArraysAllowed = hasArraySupport(format);

exportTree(*rootConfig, areArraysAllowed, configTree);

// Write the file
if (format == FILE_FORMAT_XML) {
boost::property_tree::xml_writer_settings<std::string> settings(' ', 4);
write_xml(filename, propertyTree, std::locale(), settings);
} else if (format == FILE_FORMAT_JSON) {
write_json(filename, propertyTree, std::locale(), true);
}
}

/*!
Import the specified tree.
Expand Down Expand Up @@ -271,6 +147,23 @@ void exportTree(const Config &config, bool areArraysAllowed, boost::property_tre
}
}

/*!
Write the specified tree to the given file.
\param filename is the name of the configuration file
\param format is the format that will be used for writing the configuration
\param propertyTree is the property tree that will be written
*/
void writeTree(const std::string &filename, SourceFormat format, boost::property_tree::ptree &propertyTree)
{
if (format == SOURCE_FORMAT_XML) {
boost::property_tree::xml_writer_settings<std::string> settings(' ', 4);
write_xml(filename, propertyTree, std::locale(), settings);
} else if (format == SOURCE_FORMAT_JSON) {
write_json(filename, propertyTree, std::locale(), true);
}
}

/*!
Read the value of the specified node.
Expand Down
Loading

0 comments on commit 7b6dbd0

Please sign in to comment.