Skip to content

Commit ecbd298

Browse files
committed
IO/config: parse from XML/JSON formatted string
added Config specialization class (ConfigStringParser) to read and write XML (or JSON) directly on c++ string. The class is useful to exhange info without passing from filesystem each time. Enhanced IO integration tests 2 e 4 to prove the capabilities of the new class.
1 parent 48fb520 commit ecbd298

8 files changed

+422
-0
lines changed

src/IO/configuration.cpp

+110
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include "configuration.hpp"
2727
#include "configuration_XML.hpp"
28+
#include "compiler.hpp"
2829

2930
#if HAS_RAPIDJSON_LIB
3031
#include "configuration_JSON.hpp"
@@ -360,4 +361,113 @@ namespace config {
360361

361362
}
362363

364+
/*!
365+
\class ConfigStringParser
366+
\ingroup Configuration
367+
\brief Configuration parser to c++ string
368+
369+
This class implements a configuration parser to absorb/flush directly on
370+
c++ string buffers.
371+
*/
372+
373+
374+
/*!
375+
Construct a new parser.
376+
377+
\param XMLorJSON false to activate XML format, true for JSON format if supported,
378+
otherwise fallback automatically to XML.
379+
*/
380+
ConfigStringParser::ConfigStringParser(bool XMLorJSON)
381+
: Config(false)
382+
{
383+
#if HAS_RAPIDJSON_LIB
384+
m_xmlOrJson = XMLorJSON;
385+
#else
386+
BITPIT_UNUSED(XMLorJSON);
387+
//revert to default XML
388+
m_xmlOrJson = false;
389+
#endif
390+
}
391+
392+
/*!
393+
Construct a new parser, activating multiSections support.
394+
395+
\param XMLorJSON false to activate XML format, true for JSON format if supported,
396+
otherwise fallback automatically to XML.
397+
\param multiSections if set to true the configuration parser will allow
398+
multiple sections with the same name
399+
*/
400+
ConfigStringParser::ConfigStringParser(bool XMLorJSON, bool multiSections)
401+
: Config(multiSections)
402+
{
403+
#if HAS_RAPIDJSON_LIB
404+
m_xmlOrJson = XMLorJSON;
405+
#else
406+
BITPIT_UNUSED(XMLorJSON);
407+
//revert to default XML
408+
m_xmlOrJson = false;
409+
#endif
410+
}
411+
412+
/*!
413+
Return a bool to identify the format targeted by the class to parse/write the
414+
buffer string. 0 is XML, 1 is JSON
415+
416+
\return boolean identifying the target format
417+
*/
418+
bool ConfigStringParser::getFormat()
419+
{
420+
return m_xmlOrJson;
363421
}
422+
423+
/*!
424+
Absorb the targeted buffer string formatted as specified in class construction
425+
(XML or JSON).
426+
427+
\param source target buffer string to read
428+
\param append controls if the buffer string contents will be appended to the
429+
current configuration or if the current configuration will be overwritten
430+
by them.
431+
*/
432+
void ConfigStringParser::read(const std::string &source, bool append)
433+
{
434+
// Processing not-append requests
435+
if (!append) {
436+
Config::clear();
437+
}
438+
439+
if(m_xmlOrJson){
440+
#if HAS_RAPIDJSON_LIB
441+
config::JSON::readBufferConfiguration(source, this);
442+
#endif
443+
}else{
444+
config::XML::readBufferConfiguration(source, this);
445+
}
446+
}
447+
448+
/*!
449+
Flush the configuration to a buffer string, with the format specified
450+
in class construction (XML or JSON).
451+
452+
\param source target buffer string to write on
453+
\param append controls if the configuration contents will be appended to the
454+
target string or if the string will be overwritten by them.
455+
*/
456+
void ConfigStringParser::write(std::string &source, bool append) const
457+
{
458+
// Processing not-append requests
459+
if (!append) {
460+
source.clear();
461+
}
462+
463+
if(m_xmlOrJson){
464+
#if HAS_RAPIDJSON_LIB
465+
config::JSON::writeBufferConfiguration(source, this);
466+
#endif
467+
}else{
468+
config::XML::writeBufferConfiguration(source, this, "root");
469+
}
470+
471+
}
472+
473+
}

src/IO/configuration.hpp

+17
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,23 @@ namespace config {
102102

103103
};
104104

105+
// Configuration parsing from/ writing to string
106+
class ConfigStringParser : public Config
107+
{
108+
109+
public:
110+
ConfigStringParser(bool XMLorJSON = false);
111+
ConfigStringParser(bool XMLorJSON, bool multiSections);
112+
113+
bool getFormat();
114+
115+
void read(const std::string &source, bool append = true);
116+
void write(std::string & source, bool append = true) const;
117+
118+
private:
119+
bool m_xmlOrJson;
120+
};
121+
105122
}
106123

107124
#endif

src/IO/configuration_JSON.cpp

+73
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,42 @@ void readConfiguration(const std::string &filename, Config *rootConfig)
109109
readNode("", jsonRoot, rootConfig);
110110
}
111111

112+
/*!
113+
Reading a json dictionary from plain c++ string and fill a bitpit::Config tree
114+
accordingly.
115+
116+
String Encoding is always considered of UTF-8 type .
117+
118+
\param[in] source string with JSON contents
119+
\param[in,out] rootConfig is a pointer to Config tree that will be used
120+
to store the data read from the string
121+
*/
122+
void readBufferConfiguration(const std::string &source, Config *rootConfig)
123+
{
124+
if (!rootConfig) {
125+
throw std::runtime_error("JSON::readDoc Null Config tree structure passed");
126+
}
127+
128+
// Open a UTF-8 compliant rapidjson::StringStream
129+
rapidjson::StringStream str(source.c_str());
130+
// Parse stream directly
131+
rapidjson::Document jsonRoot;
132+
jsonRoot.ParseStream(str);
133+
134+
// Handle parse errors
135+
if (jsonRoot.HasParseError()) {
136+
std::string message = "JSON:readBufferConfiguration error of type : ";
137+
message += std::string(rapidjson::GetParseError_En(jsonRoot.GetParseError()));
138+
throw std::runtime_error(message.c_str());
139+
}
140+
141+
// Root should be an object, bitpit doens't support root arrays.
142+
assert(jsonRoot.IsObject() && "JSON:readBufferConfiguration parsed document root is not an object");
143+
144+
// Fill the configuration
145+
readNode("", jsonRoot, rootConfig);
146+
}
147+
112148
/*!
113149
Read recursively a json object content and fill accordingly the Config tree
114150
branch.
@@ -246,6 +282,43 @@ void writeConfiguration(const std::string &filename, const Config *rootConfig, b
246282
std::fclose(fp);
247283
}
248284

285+
/*!
286+
Write a bitpit::Config tree contents to json formatted c++ string.
287+
288+
NOTE: JSON root document object does not have a key name unlike XML.
289+
290+
Tree contents will be written to a plain c++ string, with UTF-8 standard encoding, and
291+
appended to any previous content of the source string.
292+
293+
\param[in] source string to write JSON contents.
294+
\param[in] rootConfig pointer to the Config tree to be written on target string
295+
*/
296+
void writeBufferConfiguration(std::string &source, const Config *rootConfig)
297+
{
298+
if (!rootConfig) {
299+
throw std::runtime_error("JSON::writeConfiguration Null Config tree structure passed");
300+
}
301+
302+
// DOM Document is GenericDocument<UTF8<>>
303+
rapidjson::Document jsonRoot;
304+
305+
// Get the allocator
306+
rapidjson::Document::AllocatorType &allocator = jsonRoot.GetAllocator();
307+
308+
// Create a root node and recursively fill it
309+
jsonRoot.SetObject();
310+
writeNode(rootConfig, jsonRoot, allocator);
311+
312+
// Write on a buffer with initial capacity 1024
313+
rapidjson::StringBuffer buffer(0, 1024);
314+
315+
// Use single-ultracompact UTF-8 JSON format
316+
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
317+
jsonRoot.Accept(writer);
318+
319+
source += std::string(buffer.GetString());
320+
}
321+
249322
/*!
250323
Write recursively Config tree contents to JSON objects
251324

src/IO/configuration_JSON.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ namespace config {
3636
namespace JSON {
3737

3838
void readConfiguration(const std::string &filename, Config *rootConfig);
39+
void readBufferConfiguration(const std::string &source, Config *rootConfig);
3940
void readNode(const std::string &key, const rapidjson::Value &value, Config *config);
4041

4142
void writeConfiguration(const std::string &filename, const Config *rootConfig, bool prettify = true);
43+
void writeBufferConfiguration(std::string &source, const Config *rootConfig);
4244
void writeNode(const Config *config, rapidjson::Value &rootJSONData, rapidjson::Document::AllocatorType &allocator);
4345

4446
std::string decodeValue(const rapidjson::Value &value);

src/IO/configuration_XML.cpp

+105
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <libxml/parser.h>
2727
#include <stdexcept>
2828
#include <string>
29+
#include <cstring>
2930

3031
#include "configuration_XML.hpp"
3132

@@ -216,6 +217,41 @@ void readConfiguration(const std::string &filename, const std::string &rootname,
216217
xmlCleanupParser();
217218
}
218219

220+
/*!
221+
Read xml information from a plain xml-formatted std::string and fill the Config tree.
222+
The method is meant for general-purpose xml info absorbing. So no version checking
223+
is done in this context, nor rootname one.
224+
\param source string containing all the info formatted in XML style.
225+
\param rootConfig pointer to Config tree to store data parsed from the string.
226+
*/
227+
void readBufferConfiguration(const std::string &source, Config *rootConfig)
228+
{
229+
if (!rootConfig) {
230+
throw std::runtime_error("XML::readConfiguration Null Config tree structure passed");
231+
}
232+
233+
// Macro to check API for match with the DLL we are using
234+
LIBXML_TEST_VERSION
235+
236+
// Read the XML string
237+
const char * cstr = source.c_str();
238+
xmlDoc *doc = xmlParseMemory(cstr, strlen(cstr));
239+
if (doc == nullptr) {
240+
throw std::runtime_error("Could not parse XML configuration string: \"" + source + "\"");
241+
}
242+
243+
// Get the root element
244+
xmlNode * rootElement = xmlDocGetRootElement(doc);
245+
246+
//read it as usual
247+
readNode(rootElement->children, rootConfig);
248+
249+
// Clean-up
250+
xmlFreeDoc(doc);
251+
xmlCleanupParser();
252+
}
253+
254+
219255
/*!
220256
Write the configuration to the specified file.
221257
@@ -283,6 +319,75 @@ void writeConfiguration(const std::string &filename, const std::string &rootname
283319
xmlFreeTextWriter(writer);
284320
}
285321

322+
/*!
323+
Write the Config Tree to a c++ string (xml-stringfication). All contents will
324+
be appended to the target source string.
325+
The method is meant for general-purpose xml info flushing.
326+
\param source string to write to
327+
\param rootConfig pointer to the Config tree to be stringfied.
328+
\param rootname (optional) name of the root section. Default is "root".
329+
*/
330+
void writeBufferConfiguration(std::string &source, const Config *rootConfig, const std::string &rootname)
331+
{
332+
if (!rootConfig) {
333+
throw std::runtime_error("XML::writeConfiguration Null Config tree structure passed");
334+
}
335+
336+
int status;
337+
338+
xmlBufferPtr buffer = xmlBufferCreate();
339+
if (buffer == NULL) {
340+
throw std::runtime_error("Error creating the writing buffer");
341+
}
342+
// Create a new XmlWriter for DOM tree acting on memory buffer, with no compression
343+
xmlTextWriterPtr writer = xmlNewTextWriterMemory(buffer, 0);
344+
if (writer == NULL) {
345+
throw std::runtime_error("Error creating the xml buffer writer");
346+
}
347+
//no indent.
348+
xmlTextWriterSetIndent(writer, 0);
349+
350+
// Start the document
351+
status = xmlTextWriterStartDocument(writer, NULL, DEFAULT_ENCODING.c_str(), NULL);
352+
if (status < 0) {
353+
throw std::runtime_error("Error at xmlTextWriterStartDocument");
354+
}
355+
356+
// Start the root element
357+
xmlChar *elementName = encodeString(rootname, DEFAULT_ENCODING);
358+
status = xmlTextWriterStartElement(writer, BAD_CAST elementName);
359+
if (status < 0) {
360+
throw std::runtime_error("Error at xmlTextWriterStartElement");
361+
}
362+
363+
// Attribute version is not relevant in this context.
364+
365+
// Write the configuration
366+
writeNode(writer, rootConfig, DEFAULT_ENCODING);
367+
368+
// End section
369+
status = xmlTextWriterEndElement(writer);
370+
if (status < 0) {
371+
throw std::runtime_error("Error at xmlTextWriterEndElement");
372+
}
373+
374+
// Close the document
375+
status = xmlTextWriterEndDocument(writer);
376+
if (status < 0) {
377+
throw std::runtime_error("Error at xmlTextWriterEndDocument");
378+
}
379+
380+
// free the XML writer
381+
xmlFreeTextWriter(writer);
382+
383+
//buffer is still hanging on, append its contents to the output string
384+
//xmlChar (aka unsigned char) simple casting to char should be enough
385+
source += std::string(reinterpret_cast<char*>(buffer->content));
386+
387+
//free the buffer
388+
xmlBufferFree(buffer);
389+
}
390+
286391
}
287392

288393
}

src/IO/configuration_XML.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ namespace XML {
3838
extern const std::string DEFAULT_ENCODING;
3939

4040
void readConfiguration(const std::string &filename, const std::string &rootname, bool checkVersion, int version, Config *rootConfig);
41+
void readBufferConfiguration(const std::string &source, Config *rootConfig);
4142
void readNode(xmlNodePtr root, Config *config);
4243

4344
void writeConfiguration(const std::string &filename, const std::string & rootname, int version, const Config *rootConfig);
45+
void writeBufferConfiguration(std::string &source, const Config *rootConfig, const std::string &rootname = "root");
4446
void writeNode(xmlTextWriterPtr writer, const Config *config, const std::string &encoding = DEFAULT_ENCODING);
4547

4648
xmlChar * encodeString(const std::string &in, const std::string &encoding);

0 commit comments

Comments
 (0)