summaryrefslogtreecommitdiffstats
path: root/common/eurephia_xml.c
blob: c31cc5dec6b81a71b4a5fc26f3503e29da60b160 (plain)
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
/* eurephia_xml.c  --  Generic helper functions for XML parsing
 *
 *  GPLv2 only - Copyright (C) 2008 - 2012
 *               David Sommerseth <dazo@users.sourceforge.net>
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; version 2
 *  of the License.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/**
 * @file   eurephia_xml.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2008-12-15
 *
 * @brief  Generic XML parser functions
 *
 *
 */

#ifdef HAVE_LIBXML2
#include <stdarg.h>
#include <string.h>
#include <assert.h>

#include <libxml/tree.h>
#include <libxml/xmlstring.h>

#include <eurephia_nullsafe.h>
#include <eurephia_log.h>
#include <eurephia_xml.h>


/**
 * String replace in a xmlChar based string
 *
 * @param str xmlChar input string
 * @param s   search for this character
 * @param r   replace the character with this one
 */
void xmlReplaceChars(xmlChar *str, char s, char r) {
        if( str != NULL ) {
                xmlChar *ptr = str;

                while( *ptr != '\0' ) {
                        if( *ptr == s ) {
                                *ptr = r;
                        }
                        ptr++;
                }
        }
}


/**
 * Retrieves a given XML node attribute/property
 *
 * @param attr xmlAttr pointer from an xmlNode pointer.
 * @param key  The attribute name to search for
 *
 * @return The value of the found attribute.  If not found, NULL is returned.
 */
char *xmlGetAttrValue(xmlAttr *attr, const char *key) {
        xmlAttr *aptr;
        xmlChar *x_key = NULL;

        x_key = xmlCharStrdup(key);
        assert( x_key != NULL );

        for( aptr = attr; aptr != NULL; aptr = aptr->next ) {
                if( xmlStrcmp(aptr->name, x_key) == 0 ) {
                        // FIXME: Should find a better way to return UTF-8 data
                        free_nullsafe(NULL, x_key);
                        return (char *)(aptr->children != NULL ? aptr->children->content : NULL);
                }
        }
        free_nullsafe(NULL, x_key);
        return NULL;
}


/**
 * Loops through a xmlNode chain to look for a given tag.  The search is not recursive.
 *
 * @param node xmlNode pointer where to look
 * @param key  the name of the XML tag to find
 *
 * @return xmlNode pointer to the found xmlNode.  NULL is returned if not found.
 */
xmlNode *xmlFindNode(xmlNode *node, const char *key) {
        xmlNode *nptr = NULL;
        xmlChar *x_key = NULL;

        if( (node == NULL) || (node->children == NULL) ) {
                return NULL;
        }

        x_key = xmlCharStrdup(key);
        assert( x_key != NULL );

        for( nptr = node->children; nptr != NULL; nptr = nptr->next ) {
                if( xmlStrcmp(nptr->name, x_key) == 0 ) {
                        free_nullsafe(NULL, x_key);
                        return nptr;
                }
        }
        free_nullsafe(NULL, x_key);
        return NULL;
}


/**
 * Simple function for creating a new eurephia XML document.  On failure, this function will cause
 * an assertion error.
 *
 * @param ctx           eurephiaCTX
 * @param format        Format version of the eurephia document (int value)
 * @param eurephiaRoot  The name of the root tag of the resulting XML document
 * @param doc           xmlDoc pointer to the new document
 * @param root_n        xmlNode pointer to the root element of the document
 *
 * @return returns always 1.
 */
int eurephiaXML_CreateDoc(eurephiaCTX *ctx, int format, const char *eurephiaRoot,
                          xmlDoc **doc, xmlNode **root_n)
{
        char tmp[34];

        // Create a new XML document
        *doc = xmlNewDoc((xmlChar *)"1.0");
        assert(*doc != NULL);

        // Set the XML root to be <eurephia/>
        *root_n = xmlNewNode(NULL, (xmlChar *)"eurephia");
        assert(*root_n != NULL);

        // Add eurephia XML document format version id
        snprintf(tmp, 33, "%i%c", format, '\0');        xmlNewProp(*root_n, (xmlChar *)"format", (xmlChar *)tmp);
        xmlDocSetRootElement(*doc, *root_n);

        // Add the eurephia XML root (always inside the <eurephia/> tags)
        *root_n = xmlNewChild(*root_n, NULL, (xmlChar*)eurephiaRoot, NULL);

        return 1;
}


/**
 * Get the root node of an eurephia XML document.  This function also validates the basic structure
 * of the document and makes sure the format version of the document is valid.
 *
 * @param ctx         eurephiaCTX
 * @param doc         xmlDoc pointer to the XML document
 * @param nodeset     The expected root node to be found.
 * @param req_format  The minimum format version to be accepted
 *
 * @return Returns pointer to the given xmlNode tag.  On failure, NULL is returned.
 */
xmlNode *eurephiaXML_getRoot(eurephiaCTX *ctx, xmlDoc *doc, const char *nodeset, int req_format) {
        xmlNode *root = NULL;
        char *xmlformat_str = NULL;
        int xmlformat = 0;

        root = xmlDocGetRootElement(doc);
        if( (root == NULL) || (xmlStrcmp(root->name, (xmlChar *)"eurephia") != 0) ) {
                eurephia_log(ctx, LOG_FATAL, 0, "Could not find eurephia XML root element.  "
                             "Not a valid eurephia XML document.");
                return NULL;
        }

        xmlformat_str = xmlGetAttrValue(root->properties, "format");
        xmlformat = atoi_nullsafe(xmlformat_str);
        if( xmlformat < req_format ) {
                eurephia_log(ctx, LOG_ERROR, 0, "eurephia XML document format is not supported. "
                             "The XML document uses '%s', while we need minimum '%i'", xmlformat_str, req_format);
                return NULL;
        }

        return (nodeset != NULL ? xmlFindNode(root, nodeset) : root->children);
}


/**
 * Creates a simple result message, formatted as an XML document.
 *
 * @param ctx     eurephiaCTX
 * @param type    Can be exmlRESULT or exmlERROR.  The former is used for informational messages.
 * @param info_n  xmlNode with more details about the result
 * @param fmt     stdarg format string
 *
 * @return Returns a valid eurephia ResultMsg XML document as a properly formatted result message.
 *         On failure, NULL is returned
 *
 * Skeleton for a eurephia ResultMsg XML document
 * @code
 * <eurephia format="1">
 *     <Result status="{Result|Error}">
 *          <Message>{String containing a descriptive message}</Message>
 *         [<Details>{xmlNode including children with more detailed information}</Details>]
 *     </Result>
 * </eurephia>
 * @endcode
 * The status attribute is set to "Result" on success, and "Error" in error situations
 */
 xmlDoc *eurephiaXML_ResultMsg(eurephiaCTX *ctx, exmlResultType type, xmlNode *info_n, const char *fmt, ... ) {
        va_list ap;
        xmlChar msg[2050], *xmlfmt = NULL;
        xmlDoc  *msgdoc = NULL;
        xmlNode *msg_n = NULL;

        memset(&msg, 0, 2050);
        xmlfmt = xmlCharStrdup(fmt);
        assert( xmlfmt != NULL );

        va_start(ap, fmt);
        xmlStrVPrintf(msg, 2048, xmlfmt, ap);
        va_end(ap);
        free_nullsafe(ctx, xmlfmt);

        eurephiaXML_CreateDoc(ctx, 1, "Result", &msgdoc, &msg_n);
        assert( (msgdoc != NULL) && (msg_n != NULL) );

        switch( type ) {
        case exmlRESULT:
                xmlNewProp(msg_n, (xmlChar *) "status", (xmlChar *) "Result");
                break;

        case exmlERROR:
                xmlNewProp(msg_n, (xmlChar *) "status", (xmlChar *) "Error");
                break;

        default:
                eurephia_log(ctx, LOG_ERROR, 0, "Wrong XML result message type (%i)", type);
                return NULL;
        }
        xmlNewChild(msg_n, NULL, (xmlChar *) "Message", msg);

        if( info_n != NULL ) {
                // Create a new child tag (Details) and copy the info nodes into this tag
                xmlAddChild(xmlNewChild(msg_n, NULL, (xmlChar *) "Details", NULL),
                            xmlCopyNode(info_n, 1));
        }

        return msgdoc;
}


/**
 * Checks if the given XML document is an eurephia ResultMsg XML document
 *
 * @param ctx    eurephiaCTX
 * @param resxml XML document to validate
 *
 * @return Returns 1 if the input XML document is a ResultMsg document.  Otherwise 0
 */
unsigned int eurephiaXML_IsResultMsg(eurephiaCTX *ctx, xmlDoc *resxml) {
        xmlNode *node = NULL;

        assert( ctx != NULL );
        if( resxml == NULL ) {
                return 0;
        }
        node = eurephiaXML_getRoot(ctx, resxml, "Result", 1);
        return (node != NULL ? 1 : 0);
}

/**
 * Parses an eurephia Result XML document
 *
 * @param ctx    eurephiaCTX
 * @param resxml The result XML document, as produced by eurephiaXML_ResultMsg()
 *
 * @return Returns a pointer to an eurephiaRESULT structure containing the results.
 *         On failure NULL is returned.  This structure can be freed with free_nullsafe().
 *
 * @remark If the result XML document is freed, the information in eurephiaRESULT will be invalidated
 *         Immediately.  However, the eurephiaRESULT pointer must still be freed.
 * @see eurephiaXML_ResultMsg()
 */
eurephiaRESULT *eurephiaXML_ParseResultMsg(eurephiaCTX *ctx, xmlDoc *resxml) {
        eurephiaRESULT *res = NULL;
        xmlNode *res_n = NULL;
        char *str = NULL;

        assert( ctx != NULL );
        if( resxml == NULL ) {
                return NULL;
        }

        res_n = eurephiaXML_getRoot(ctx, resxml, "Result", 1);
        if( res_n == NULL) {
                eurephia_log(ctx, LOG_ERROR, 0, "Could not find a valid <Result> tag");
                return NULL;
        }

        res = (eurephiaRESULT *) malloc_nullsafe(ctx, sizeof(eurephiaRESULT) + 2);
        assert( res != NULL );

        str = xmlGetAttrValue(res_n->properties, "status");
        if( strcmp(str, "Error") == 0 ) {
                res->resultType = exmlERROR;
        } else if( strcmp(str, "Result") == 0 ) {
                res->resultType = exmlRESULT;
        } else {
                free_nullsafe(ctx, res);
                eurephia_log(ctx, LOG_ERROR, 0, "Invalid result status");
                return NULL;
        }

        res->message = xmlGetNodeContent(res_n, "Message");
        res->details = xmlFindNode(res_n, "Details");
        return res;
}


/**
 * Return the text content of a given xmlNode
 *
 * @param n xmlNode to extract the value from.
 *
 * @return returns a char pointer with the text contents of an xmlNode.
 */
inline char *xmlExtractContent(xmlNode *n) {
        // FIXME: Should find better way how to return UTF-8 data
        return (char *) (((n != NULL) && (n->children != NULL)) ? n->children->content : NULL);
}


/**
 * Get the text contents of a given xmlNode
 *
 * @param node An xmlNode pointer where to look for the contents
 * @param key  Name of the tag to retrieve the content of.
 *
 * @return Returns a string with the text content, if the node is found.  Otherwise, NULL is returned.
 */
inline char *xmlGetNodeContent(xmlNode *node, const char *key) {
        return xmlExtractContent(xmlFindNode(node, key));
}
#endif