diff --git a/src/IO/configuration.cpp b/src/IO/configuration.cpp index 751d01fcac..0c659c53d4 100644 --- a/src/IO/configuration.cpp +++ b/src/IO/configuration.cpp @@ -25,6 +25,7 @@ #include "configuration.hpp" #include "configuration_XML.hpp" +#include "compiler.hpp" #if HAS_RAPIDJSON_LIB #include "configuration_JSON.hpp" @@ -360,4 +361,109 @@ namespace config { } +/*! + \class ConfigStringParser + \ingroup Configuration + \brief Configuration parser to c++ string + + This class implements a configuration parser to absorb/flush directly on + c++ string buffers. +*/ + + +/*! + Construct a new parser. + + \param format type of format to parse, of ConfigStringParser::Format enum type +*/ +ConfigStringParser::ConfigStringParser(ConfigStringParser::Format format) + : Config(false) +{ +#if HAS_RAPIDJSON_LIB + m_format = format; +#else + BITPIT_UNUSED(format); + //revert to default XML + m_format = Format::XML; +#endif +} + +/*! + Construct a new parser, activating multiSections support. + + \param format type of format to parse, of ConfigStringParser::Format enum type + \param multiSections if set to true the configuration parser will allow + multiple sections with the same name +*/ +ConfigStringParser::ConfigStringParser(ConfigStringParser::Format format, bool multiSections) + : Config(multiSections) +{ +#if HAS_RAPIDJSON_LIB + m_format = format; +#else + BITPIT_UNUSED(format); + //revert to default XML + m_format = Format::XML; +#endif +} + +/*! + \return the format of the class in ConfigStringParser::Format enum type +*/ +ConfigStringParser::Format +ConfigStringParser::getFormat() +{ + return m_format; } + +/*! + Absorb the targeted buffer string formatted as specified in class construction + (XML or JSON). + + \param source target buffer string to read + \param append controls if the buffer string contents will be appended to the + current configuration or if the current configuration will be overwritten + by them. +*/ +void ConfigStringParser::read(const std::string &source, bool append) +{ + // Processing not-append requests + if (!append) { + Config::clear(); + } + + if(m_format == Format::JSON){ +#if HAS_RAPIDJSON_LIB + config::JSON::readBufferConfiguration(source, this); +#endif + }else{ + config::XML::readBufferConfiguration(source, this); + } +} + +/*! + Flush the configuration to a buffer string, with the format specified + in class construction (XML or JSON). + + \param source target buffer string to write on + \param append controls if the configuration contents will be appended to the + target string or if the string will be overwritten by them. +*/ +void ConfigStringParser::write(std::string &source, bool append) const +{ + // Processing not-append requests + if (!append) { + source.clear(); + } + + if(m_format == Format::JSON){ +#if HAS_RAPIDJSON_LIB + config::JSON::writeBufferConfiguration(this, source); +#endif + }else{ + config::XML::writeBufferConfiguration(this, source, "root"); + } + +} + +} \ No newline at end of file diff --git a/src/IO/configuration.hpp b/src/IO/configuration.hpp index 9bdb59e902..32a20ebd25 100644 --- a/src/IO/configuration.hpp +++ b/src/IO/configuration.hpp @@ -102,6 +102,33 @@ namespace config { }; +// Configuration parsing from/ writing to string +class ConfigStringParser : public Config +{ + +public: + + /*! + * @ingroup Configuration + * Enum class defining format allowed in ConfigStringParser class + */ + enum class Format{ + XML = 0,/**< xml formatted */ + JSON= 1 /**< json formatted */ + }; + + ConfigStringParser(Format format = Format::XML); + ConfigStringParser(Format format, bool multiSections); + + Format getFormat(); + + void read(const std::string &source, bool append = true); + void write(std::string & source, bool append = true) const; + +private: + Format m_format; +}; + } #endif diff --git a/src/IO/configuration_JSON.cpp b/src/IO/configuration_JSON.cpp index 7c798b1b11..0071be832d 100644 --- a/src/IO/configuration_JSON.cpp +++ b/src/IO/configuration_JSON.cpp @@ -109,6 +109,42 @@ void readConfiguration(const std::string &filename, Config *rootConfig) readNode("", jsonRoot, rootConfig); } +/*! + Reading a json dictionary from plain c++ string and fill a bitpit::Config tree + accordingly. + + String Encoding is always considered of UTF-8 type . + + \param[in] source string with JSON contents + \param[in,out] rootConfig is a pointer to Config tree that will be used + to store the data read from the string +*/ +void readBufferConfiguration(const std::string &source, Config *rootConfig) +{ + if (!rootConfig) { + throw std::runtime_error("JSON::readDoc Null Config tree structure passed"); + } + + // Open a UTF-8 compliant rapidjson::StringStream + rapidjson::StringStream str(source.c_str()); + // Parse stream directly + rapidjson::Document jsonRoot; + jsonRoot.ParseStream(str); + + // Handle parse errors + if (jsonRoot.HasParseError()) { + std::string message = "JSON:readBufferConfiguration error of type : "; + message += std::string(rapidjson::GetParseError_En(jsonRoot.GetParseError())); + throw std::runtime_error(message.c_str()); + } + + // Root should be an object, bitpit doens't support root arrays. + assert(jsonRoot.IsObject() && "JSON:readBufferConfiguration parsed document root is not an object"); + + // Fill the configuration + readNode("", jsonRoot, rootConfig); +} + /*! Read recursively a json object content and fill accordingly the Config tree branch. @@ -246,6 +282,44 @@ void writeConfiguration(const std::string &filename, const Config *rootConfig, b std::fclose(fp); } +/*! + Write a bitpit::Config tree contents to json formatted c++ string. + + NOTE: JSON root document object does not have a key name unlike XML. + + Tree contents will be written to a plain c++ string, with UTF-8 standard encoding, and + appended to any previous content of the source string. + + \param[in] rootConfig pointer to the Config tree to be written on target string + \param[inout] source string to write JSON contents. + +*/ +void writeBufferConfiguration(const Config *rootConfig, std::string &source) +{ + if (!rootConfig) { + throw std::runtime_error("JSON::writeConfiguration Null Config tree structure passed"); + } + + // DOM Document is GenericDocument> + rapidjson::Document jsonRoot; + + // Get the allocator + rapidjson::Document::AllocatorType &allocator = jsonRoot.GetAllocator(); + + // Create a root node and recursively fill it + jsonRoot.SetObject(); + writeNode(rootConfig, jsonRoot, allocator); + + // Write on a buffer with initial capacity 1024 + rapidjson::StringBuffer buffer(0, 1024); + + // Use single-ultracompact UTF-8 JSON format + rapidjson::Writer writer(buffer); + jsonRoot.Accept(writer); + + source += std::string(buffer.GetString()); +} + /*! Write recursively Config tree contents to JSON objects diff --git a/src/IO/configuration_JSON.hpp b/src/IO/configuration_JSON.hpp index 1a63eca245..4cfcdb492a 100644 --- a/src/IO/configuration_JSON.hpp +++ b/src/IO/configuration_JSON.hpp @@ -36,9 +36,11 @@ namespace config { namespace JSON { void readConfiguration(const std::string &filename, Config *rootConfig); +void readBufferConfiguration(const std::string &source, Config *rootConfig); void readNode(const std::string &key, const rapidjson::Value &value, Config *config); void writeConfiguration(const std::string &filename, const Config *rootConfig, bool prettify = true); +void writeBufferConfiguration(const Config *rootConfig, std::string &source); void writeNode(const Config *config, rapidjson::Value &rootJSONData, rapidjson::Document::AllocatorType &allocator); std::string decodeValue(const rapidjson::Value &value); diff --git a/src/IO/configuration_XML.cpp b/src/IO/configuration_XML.cpp index 189bd81172..2bf81b5ebd 100644 --- a/src/IO/configuration_XML.cpp +++ b/src/IO/configuration_XML.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "configuration_XML.hpp" @@ -216,6 +217,41 @@ void readConfiguration(const std::string &filename, const std::string &rootname, xmlCleanupParser(); } +/*! + Read xml information from a plain xml-formatted std::string and fill the Config tree. + The method is meant for general-purpose xml info absorbing. So no version checking + is done in this context, nor rootname one. + \param source string containing all the info formatted in XML style. + \param rootConfig pointer to Config tree to store data parsed from the string. +*/ +void readBufferConfiguration(const std::string &source, Config *rootConfig) +{ + if (!rootConfig) { + throw std::runtime_error("XML::readConfiguration Null Config tree structure passed"); + } + + // Macro to check API for match with the DLL we are using + LIBXML_TEST_VERSION + + // Read the XML string + const char * cstr = source.c_str(); + xmlDoc *doc = xmlParseMemory(cstr, strlen(cstr)); + if (doc == nullptr) { + throw std::runtime_error("Could not parse XML configuration string: \"" + source + "\""); + } + + // Get the root element + xmlNode * rootElement = xmlDocGetRootElement(doc); + + //read it as usual + readNode(rootElement->children, rootConfig); + + // Clean-up + xmlFreeDoc(doc); + xmlCleanupParser(); +} + + /*! Write the configuration to the specified file. @@ -283,6 +319,75 @@ void writeConfiguration(const std::string &filename, const std::string &rootname xmlFreeTextWriter(writer); } +/*! + Write the Config Tree to a c++ string (xml-stringfication). All contents will + be appended to the target source string. + The method is meant for general-purpose xml info flushing. + \param rootConfig pointer to the Config tree to be stringfied. + \param source string to write to + \param rootname (optional) name of the root section. Default is "root". +*/ +void writeBufferConfiguration(const Config *rootConfig, std::string &source, const std::string &rootname) +{ + if (!rootConfig) { + throw std::runtime_error("XML::writeConfiguration Null Config tree structure passed"); + } + + int status; + + xmlBufferPtr buffer = xmlBufferCreate(); + if (buffer == NULL) { + throw std::runtime_error("Error creating the writing buffer"); + } + // Create a new XmlWriter for DOM tree acting on memory buffer, with no compression + xmlTextWriterPtr writer = xmlNewTextWriterMemory(buffer, 0); + if (writer == NULL) { + throw std::runtime_error("Error creating the xml buffer writer"); + } + //no indent. + xmlTextWriterSetIndent(writer, 0); + + // Start the document + status = xmlTextWriterStartDocument(writer, NULL, DEFAULT_ENCODING.c_str(), NULL); + if (status < 0) { + throw std::runtime_error("Error at xmlTextWriterStartDocument"); + } + + // Start the root element + xmlChar *elementName = encodeString(rootname, DEFAULT_ENCODING); + status = xmlTextWriterStartElement(writer, BAD_CAST elementName); + if (status < 0) { + throw std::runtime_error("Error at xmlTextWriterStartElement"); + } + + // Attribute version is not relevant in this context. + + // Write the configuration + writeNode(writer, rootConfig, DEFAULT_ENCODING); + + // End section + status = xmlTextWriterEndElement(writer); + if (status < 0) { + throw std::runtime_error("Error at xmlTextWriterEndElement"); + } + + // Close the document + status = xmlTextWriterEndDocument(writer); + if (status < 0) { + throw std::runtime_error("Error at xmlTextWriterEndDocument"); + } + + // free the XML writer + xmlFreeTextWriter(writer); + + //buffer is still hanging on, append its contents to the output string + //xmlChar (aka unsigned char) simple casting to char should be enough + source += std::string(reinterpret_cast(buffer->content)); + + //free the buffer + xmlBufferFree(buffer); +} + } } diff --git a/src/IO/configuration_XML.hpp b/src/IO/configuration_XML.hpp index 46ba9ef271..c63e525ee3 100644 --- a/src/IO/configuration_XML.hpp +++ b/src/IO/configuration_XML.hpp @@ -38,9 +38,11 @@ namespace XML { extern const std::string DEFAULT_ENCODING; void readConfiguration(const std::string &filename, const std::string &rootname, bool checkVersion, int version, Config *rootConfig); +void readBufferConfiguration(const std::string &source, Config *rootConfig); void readNode(xmlNodePtr root, Config *config); void writeConfiguration(const std::string &filename, const std::string & rootname, int version, const Config *rootConfig); +void writeBufferConfiguration(const Config *rootConfig, std::string &source, const std::string &rootname = "root"); void writeNode(xmlTextWriterPtr writer, const Config *config, const std::string &encoding = DEFAULT_ENCODING); xmlChar * encodeString(const std::string &in, const std::string &encoding); diff --git a/test/integration_tests/IO/test_IO_00002.cpp b/test/integration_tests/IO/test_IO_00002.cpp index 5d2b881a85..e963d8483f 100644 --- a/test/integration_tests/IO/test_IO_00002.cpp +++ b/test/integration_tests/IO/test_IO_00002.cpp @@ -100,6 +100,61 @@ int subtest_001() return 0; } +/*! +* Subtest 002 +* +* Testing configuration string parser, to absorb/flush json contents directly on string. +*/ +int subtest_002() +{ + std::cout << std::endl; + std::cout << std::endl; + std::cout << std::endl; + + std::cout << "Testing configuration JSON stringfication" << std::endl; + std::cout << std::endl; + + //create a sample config tree. + std::shared_ptr writeStr = std::make_shared(ConfigStringParser::Format::JSON, true); //enable json format, enable multisection. + + //fill the writeStr tree with some data + writeStr->set("ActivateOption", "1"); + writeStr->set("Origin", "Albuquerque"); + writeStr->set("Span", "180.0 48.0 46.0"); + writeStr->set("Dimension", "5 7 3"); + { + auto &subsect = writeStr->addSection("UserData"); + subsect.set("Name","Walter"); + subsect.set("Surname","White"); + subsect.set("Nick","Heisenberg"); + subsect.set("PassPhrase", "Say my name!"); + } + { + auto &subsect = writeStr->addSection("UserData"); + subsect.set("Name","Jesse"); + subsect.set("Surname","Pinkman"); + subsect.set("Nick","Captain Cook"); + subsect.set("PassPhrase", "Yo!"); + } + + std::string bufferJSON; + //write writeStr Content to the string buffer + writeStr->write(bufferJSON); + + std::cout< parseStr=std::make_shared(ConfigStringParser::Format::JSON, true); //enable json format, enable multisection. + parseStr->read(bufferJSON); + + bool check = parseStr->hasSection("UserData"); + check = check && parseStr->hasOption("Origin"); + check = check && parseStr->getSections("UserData").size() == 2; + + + return int(!check); +} + /*! * Main program. */ @@ -122,6 +177,7 @@ int main(int argc, char *argv[]) int status; try { status = subtest_001(); + status = std::max(status, subtest_002()); if (status != 0) { return status; } diff --git a/test/integration_tests/IO/test_IO_00004.cpp b/test/integration_tests/IO/test_IO_00004.cpp index 4a55e05c43..f89e895556 100644 --- a/test/integration_tests/IO/test_IO_00004.cpp +++ b/test/integration_tests/IO/test_IO_00004.cpp @@ -109,6 +109,62 @@ int subtest_001() return 0; } + +/*! +* Subtest 002 +* +* Testing configuration string parser, to absorb/flush xml contents directly on string. +*/ +int subtest_002() +{ + std::cout << std::endl; + std::cout << std::endl; + std::cout << std::endl; + + std::cout << "Testing configuration XML stringfication" << std::endl; + std::cout << std::endl; + + //create a sample config tree. + std::shared_ptr writeStr = std::make_shared(ConfigStringParser::Format::XML, true); //enable xml format, enable multisection. + + //fill the writeStr tree with some data + writeStr->set("ActivateOption", "1"); + writeStr->set("Origin", "Albuquerque"); + writeStr->set("Span", "180.0 48.0 46.0"); + writeStr->set("Dimension", "5 7 3"); + { + auto &subsect = writeStr->addSection("UserData"); + subsect.set("Name","Walter"); + subsect.set("Surname","White"); + subsect.set("Nick","Heisenberg"); + subsect.set("PassPhrase", "Say my name!"); + } + { + auto &subsect = writeStr->addSection("UserData"); + subsect.set("Name","Jesse"); + subsect.set("Surname","Pinkman"); + subsect.set("Nick","Captain Cook"); + subsect.set("PassPhrase", "Yo!"); + } + + std::string bufferXML; + //write writeStr Content to the string buffer + writeStr->write(bufferXML); + + std::cout< parseStr = std::make_shared(ConfigStringParser::Format::XML, true); //enable xml format, enable multisection. + parseStr->read(bufferXML); + + bool check = parseStr->hasSection("UserData"); + check = check && parseStr->hasOption("Origin"); + check = check && parseStr->getSections("UserData").size() == 2; + + + return int(!check); +} + /*! * Main program. */ @@ -130,6 +186,7 @@ int main(int argc, char *argv[]) int status; try { status = subtest_001(); + status = std::max(status, subtest_002()); if (status != 0) { return status; }