-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathconfiguration_XML.cpp
395 lines (327 loc) · 12.6 KB
/
configuration_XML.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/*---------------------------------------------------------------------------*\
*
* bitpit
*
* Copyright (C) 2015-2021 OPTIMAD engineering Srl
*
* -------------------------------------------------------------------------
* License
* This file is part of bitpit.
*
* bitpit is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License v3 (LGPL)
* as published by the Free Software Foundation.
*
* bitpit is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with bitpit. If not, see <http://www.gnu.org/licenses/>.
*
\*---------------------------------------------------------------------------*/
#include <libxml/encoding.h>
#include <libxml/parser.h>
#include <stdexcept>
#include <string>
#include <cstring>
#include "configuration_XML.hpp"
namespace bitpit {
namespace config {
namespace XML {
/*!
Default encoding.
*/
const std::string DEFAULT_ENCODING = "ISO-8859-1";
/*!
Read an XML node.
\param root is the root node to be read
\param config is the configuration where the options and section will be
stored
*/
void readNode(xmlNodePtr root, Config *config)
{
for (xmlNode *node = root; node; node = node->next) {
if (node->type != XML_ELEMENT_NODE) {
readNode(node->children, config);
continue;
}
bool isSection = (xmlChildElementCount(node) != 0);
std::string key(reinterpret_cast<const char*>(node->name));
if (isSection) {
Config::Section *section;
if (!config->isMultiSectionsEnabled() && config->hasSection(key)) {
section = &(config->getSection(key));
} else {
section = &(config->addSection(key));
}
readNode(node->children, section);
} else {
xmlChar *nodeContent = xmlNodeGetContent(node);
std::string value(reinterpret_cast<const char*>(nodeContent));
config->set(key, value);
xmlFree(nodeContent);
}
}
}
/*!
Write an XML node.
\param rootNode is the root node to be written
\param config is the configuration where the options and section will be
read
*/
void writeNode(xmlTextWriterPtr writer, const Config *config, const std::string &encoding)
{
// Write the options
for (auto &entry : config->getOptions()) {
const std::string &key = entry.first;
const std::string &value = entry.second;
xmlChar *elementName = encodeString(key, encoding);
xmlChar *elementText = encodeString(value, encoding);
int status = xmlTextWriterWriteFormatElement(writer, BAD_CAST elementName, "%s", elementText);
if (status < 0) {
throw std::runtime_error("Error at xmlTextWriterWriteFormatElement");
}
}
// Write the sections
for (auto &entry : config->getSections()) {
const std::string &key = entry.first;
const Config::Section *section = entry.second.get();
// Start the section
xmlChar *elementName = encodeString(key, encoding);
int status = xmlTextWriterStartElement(writer, BAD_CAST elementName);
if (status < 0) {
throw std::runtime_error("Error at xmlTextWriterStartElement");
}
// Write the section
writeNode(writer, section, encoding);
// End section
status = xmlTextWriterEndElement(writer);
if (status < 0) {
throw std::runtime_error("Error at xmlTextWriterEndElement");
}
}
}
/*!
Encodes the specified string into UTF-8 for processing with libxml2 APIs.
\param int is the string in a given encoding
\param encoding isthe encoding used
\result The converted UTF-8 string, or NULL in case of error.
*/
xmlChar * encodeString(const std::string &in, const std::string &encoding)
{
if (in.empty()) {
return nullptr;
}
xmlCharEncodingHandlerPtr handler = xmlFindCharEncodingHandler(encoding.c_str());
if (!handler) {
return nullptr;
}
int size = (int) in.length() + 1;
int out_size = size * 2 - 1;
xmlChar *out = (unsigned char *) xmlMalloc((size_t) out_size);
if (out == nullptr) {
return nullptr;
}
int temp = size - 1;
int ret = handler->input(out, &out_size, (const xmlChar *) in.c_str(), &temp);
if ((ret < 0) || (temp - size + 1)) {
xmlFree(out);
out = 0;
} else {
out = (unsigned char *) xmlRealloc(out, out_size + 1);
out[out_size] = 0; /*null terminating out */
}
return out;
}
/*!
Read the specified configuration file.
\param filename is the filename of the configuration file
\param rootname name of the root. If this name does not match xml doc root
name return an error
\param checkVersion boolean to enable the control of XML version
\param version number of version to check. If checkVersion is true and
version does not match the xml one, return an error
\param rootConfig pointer to Config tree to store data parsed from document.
*/
void readConfiguration(const std::string &filename, const std::string &rootname, bool checkVersion,
int version, 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 file
xmlDoc *doc = xmlReadFile(filename.c_str(), NULL, 0);
if (doc == nullptr) {
throw std::runtime_error("Could not parse XML configuration file \"" + filename + "\"");
}
// Get the root element
xmlNode * rootElement = xmlDocGetRootElement(doc);
// check if the root name is the requeste one
std::string rootXMLName(reinterpret_cast<const char*>(rootElement->name));
if (rootXMLName != rootname) {
throw std::runtime_error("The name of the root XML element is not \"" + rootname + "\"");
}
// Check if the version is supported
const xmlChar *versionAttributeName =
reinterpret_cast<const xmlChar *>("version");
if (checkVersion && xmlHasProp(rootElement, versionAttributeName)) {
xmlChar *versionValue = xmlGetProp(rootElement, versionAttributeName);
std::string versionString((char *) versionValue);
xmlFree(versionValue);
int versionXML;
std::istringstream(versionString) >> versionXML;
if (versionXML != version) {
throw std::runtime_error("The version of the XML file is not not \"" + std::to_string(version) + "\"");
}
}
readNode(rootElement->children, rootConfig);
// Clean-up
xmlFreeDoc(doc);
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.
\param filename is the filename where the configuration will be written to
\param rootname name of the root to assign to the XML file.
\param version number of version to assing to the XML file.
\param rootConfig pointer to the Config tree to be written on file.
*/
void writeConfiguration(const std::string &filename, const std::string &rootname, int version,
const Config *rootConfig)
{
if (!rootConfig) {
throw std::runtime_error("XML::writeConfiguration Null Config tree structure passed");
}
int status;
// Create a new XmlWriter for DOM tree, with no compression
xmlTextWriterPtr writer = xmlNewTextWriterFilename(filename.c_str(), 0);
if (writer == NULL) {
throw std::runtime_error("Error creating the xml writer");
}
xmlTextWriterSetIndent(writer, 1);
// 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");
}
// Add an attribute with version
std::ostringstream versionStream;
versionStream << version;
xmlChar *versionAttr = encodeString(versionStream.str(), DEFAULT_ENCODING);
status = xmlTextWriterWriteAttribute(writer, BAD_CAST "version", BAD_CAST versionAttr);
if (status < 0) {
throw std::runtime_error("Error at xmlTextWriterWriteAttribute");
}
// 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");
}
// Write the XML
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 source string to write to
\param rootConfig pointer to the Config tree to be stringfied.
\param rootname (optional) name of the root section. Default is "root".
*/
void writeBufferConfiguration(std::string &source, const Config *rootConfig, 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<char*>(buffer->content));
//free the buffer
xmlBufferFree(buffer);
}
}
}
}