1 /*
2 * libInstPatch
3 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; version 2.1
8 * of the License only.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA or on the web at http://www.gnu.org.
19 */
20 /**
21 * SECTION: IpatchXml
22 * @short_description: XML tree functions
23 * @see_also: IpatchXmlObject
24 * @stability: Stable
25 *
26 * Functions for manipulating XML node trees and saving/loading to/from
27 * XML content in strings or files. XML node trees use the glib GNode N-ary
28 * tree data type for added flexibility.
29 */
30 #include <string.h>
31 #include "IpatchXml.h"
32 #include "misc.h"
33
34 static gboolean xml_destroy_traverse_func(GNode *node, gpointer data);
35 static GNode *ipatch_xml_find_by_path_recurse(GNode *node, const char *path);
36 static void ipatch_xml_to_str_recurse(GString *str, GNode *node, guint indent,
37 guint inc);
38 static void
39 xml_start_element(GMarkupParseContext *context, const gchar *element_name,
40 const gchar **attribute_names, const gchar **attribute_values,
41 gpointer user_data, GError **error);
42 static void
43 xml_end_element(GMarkupParseContext *context, const gchar *element_name,
44 gpointer user_data, GError **error);
45 static void
46 xml_text(GMarkupParseContext *context, const gchar *text, gsize text_len,
47 gpointer user_data, GError **error);
48
49 /**
50 * ipatch_xml_new_node: (skip)
51 * @parent: (nullable): Parent node to add new
52 * node to as a child, or %NULL to create new root node
53 * @name: Name of the new XML node
54 * @value: (nullable): Text value to assign to the new node or %NULL
55 * @attr_name: (nullable): First attribute name to assign or %NULL
56 * @...: (type char*): If @attr_name was supplied first string value to be assigned should be
57 * the first parameter, additional name/value attribute string pairs may
58 * follow terminated by a %NULL name.
59 *
60 * Create a new XML tree node and append it to the given @parent, if supplied.
61 * Note that the returned GNode can be used with other N-Array glib operations.
62 *
63 * Returns: New XML tree node
64 */
65 GNode *
ipatch_xml_new_node(GNode * parent,const char * name,const char * value,const char * attr_name,...)66 ipatch_xml_new_node(GNode *parent, const char *name, const char *value,
67 const char *attr_name, ...)
68 {
69 IpatchXmlNode *xmlnode;
70 IpatchXmlAttr *attr;
71 va_list var_args;
72 char *vname, *vvalue;
73
74 g_return_val_if_fail(name != NULL, NULL);
75
76 xmlnode = ipatch_xml_node_new();
77 xmlnode->name = g_strdup(name);
78 xmlnode->value = g_strdup(value);
79 xmlnode->attributes = NULL;
80
81 if(attr_name)
82 {
83 va_start(var_args, attr_name);
84
85 attr = ipatch_xml_attr_new();
86 attr->name = g_strdup(attr_name);
87 attr->value = g_strdup(va_arg(var_args, char *));
88 xmlnode->attributes = g_list_append(xmlnode->attributes, attr);
89
90 while((vname = va_arg(var_args, char *)))
91 {
92 vvalue = va_arg(var_args, char *);
93
94 if(!vvalue)
95 {
96 continue;
97 }
98
99 attr = ipatch_xml_attr_new();
100 attr->name = g_strdup(vname);
101 attr->value = g_strdup(vvalue);
102 xmlnode->attributes = g_list_append(xmlnode->attributes, attr);
103 }
104
105 va_end(var_args);
106 }
107
108 return (parent ? g_node_append_data(parent, xmlnode) : g_node_new(xmlnode));
109 }
110
111 /**
112 * ipatch_xml_new_node_strv: (skip)
113 * @parent: (nullable): Parent node to add
114 * new node to as a child, or %NULL to create new root node
115 * @name: Name of the new XML node
116 * @value: (nullable): Text value to assign to the new node or %NULL
117 * @attr_names: (array zero-terminated=1) (nullable): %NULL terminated
118 * array of attribute names or %NULL
119 * @attr_values: (array zero-terminated=1) (nullable): %NULL terminated
120 * array of attribute values or %NULL
121 *
122 * Like ipatch_xml_new_node() but takes attribute name/values as separate strv
123 * arrays.
124 *
125 * Returns: New XML tree node
126 */
127 GNode *
ipatch_xml_new_node_strv(GNode * parent,const char * name,const char * value,const char ** attr_names,const char ** attr_values)128 ipatch_xml_new_node_strv(GNode *parent, const char *name, const char *value,
129 const char **attr_names, const char **attr_values)
130 {
131 IpatchXmlNode *xmlnode;
132 IpatchXmlAttr *attr;
133 int i;
134
135 g_return_val_if_fail(name != NULL, NULL);
136 g_return_val_if_fail(!attr_names == !attr_values, NULL);
137
138 xmlnode = ipatch_xml_node_new();
139 xmlnode->name = g_strdup(name);
140 xmlnode->value = g_strdup(value);
141 xmlnode->attributes = NULL;
142
143 if(attr_names)
144 {
145 for(i = 0; attr_names[i] && attr_values[i]; i++)
146 {
147 if(!attr_values[i])
148 {
149 continue;
150 }
151
152 attr = ipatch_xml_attr_new();
153 attr->name = g_strdup(attr_names[i]);
154 attr->value = g_strdup(attr_values[i]);
155 xmlnode->attributes = g_list_append(xmlnode->attributes, attr);
156 }
157 }
158
159 return (parent ? g_node_append_data(parent, xmlnode) : g_node_new(xmlnode));
160 }
161
162 #define QDATA(node) &(((IpatchXmlNode *)(node->data))->qdata)
163
164 /**
165 * ipatch_xml_get_data: (skip)
166 * @node: XML node
167 * @key: Name of the key
168 *
169 * Lookup data assigned to an XML node.
170 *
171 * Returns: The data pointer or %NULL if not set
172 */
173 gpointer
ipatch_xml_get_data(GNode * node,const char * key)174 ipatch_xml_get_data(GNode *node, const char *key)
175 {
176 g_return_val_if_fail(node != NULL, NULL);
177 return (g_datalist_get_data(QDATA(node), key));
178 }
179
180 /**
181 * ipatch_xml_set_data: (skip)
182 * @node: XML node
183 * @key: Name of the key
184 * @data: Data to associate with the key
185 *
186 * Assigns arbitrary data to an XML node specified by a @key.
187 */
188 void
ipatch_xml_set_data(GNode * node,const char * key,gpointer data)189 ipatch_xml_set_data(GNode *node, const char *key, gpointer data)
190 {
191 g_return_if_fail(node != NULL);
192 g_datalist_set_data(QDATA(node), key, data);
193 }
194
195 /**
196 * ipatch_xml_set_data_full: (skip)
197 * @node: XML node
198 * @key: Name of the key
199 * @data: Data to associate with the key
200 * @destroy_func: (nullable): Destroy function or %NULL
201 *
202 * Assigns arbitrary data to an XML node specified by a @key. Also assigns a
203 * @destroy_func callback to destroy the data when it is removed.
204 */
205 void
ipatch_xml_set_data_full(GNode * node,const char * key,gpointer data,GDestroyNotify destroy_func)206 ipatch_xml_set_data_full(GNode *node, const char *key, gpointer data,
207 GDestroyNotify destroy_func)
208 {
209 g_return_if_fail(node != NULL);
210 g_datalist_set_data_full(QDATA(node), key, data, destroy_func);
211 }
212
213 /**
214 * ipatch_xml_steal_data: (skip)
215 * @node: XML node
216 * @key: Name of the key
217 *
218 * Remove keyed data from an XML node, but don't call the data's destroy notify.
219 * Caller is thus given the ownership of the data.
220 *
221 * Returns: (transfer none): The data pointer or %NULL if not set
222 */
223 gpointer
ipatch_xml_steal_data(GNode * node,const char * key)224 ipatch_xml_steal_data(GNode *node, const char *key)
225 {
226 gpointer data;
227 GQuark quark;
228
229 g_return_val_if_fail(node != NULL, NULL);
230
231 quark = g_quark_try_string(key);
232
233 if(!quark)
234 {
235 return (NULL);
236 }
237
238 data = g_datalist_id_get_data(QDATA(node), quark);
239
240 if(data)
241 {
242 g_datalist_id_remove_no_notify(QDATA(node), quark);
243 }
244
245 return (data);
246 }
247
248 /**
249 * ipatch_xml_get_qdata: (skip)
250 * @node: XML node
251 * @quark: Quark key
252 *
253 * Lookup data assigned to an XML node using a quark. This is faster than
254 * ipatch_xml_get_data() since the key must be converted to a quark anyways.
255 *
256 * Returns: (transfer none): The data pointer or %NULL if not set
257 */
258 gpointer
ipatch_xml_get_qdata(GNode * node,GQuark quark)259 ipatch_xml_get_qdata(GNode *node, GQuark quark)
260 {
261 g_return_val_if_fail(node != NULL, NULL);
262 return (g_datalist_id_get_data(QDATA(node), quark));
263 }
264
265 /**
266 * ipatch_xml_set_qdata: (skip)
267 * @node: XML node
268 * @quark: Quark key
269 * @data: Data to associate with the key
270 *
271 * Assigns arbitrary data to an XML node specified by a @quark key. This is
272 * faster than ipatch_xml_set_data() since the key must be converted to a quark
273 * anyways.
274 */
275 void
ipatch_xml_set_qdata(GNode * node,GQuark quark,gpointer data)276 ipatch_xml_set_qdata(GNode *node, GQuark quark, gpointer data)
277 {
278 g_return_if_fail(node != NULL);
279 g_datalist_id_set_data(QDATA(node), quark, data);
280 }
281
282 /**
283 * ipatch_xml_set_qdata_full: (skip)
284 * @node: XML node
285 * @quark: Quark key
286 * @data: Data to associate with the key
287 * @destroy_func: (nullable): Destroy function or %NULL
288 *
289 * Assigns arbitrary data to an XML node specified by a @key. Also assigns a
290 * @destroy_func callback to destroy the data when it is removed. This is
291 * faster than ipatch_xml_set_data_full() since the key must be converted to a quark
292 * anyways.
293 */
294 void
ipatch_xml_set_qdata_full(GNode * node,GQuark quark,gpointer data,GDestroyNotify destroy_func)295 ipatch_xml_set_qdata_full(GNode *node, GQuark quark, gpointer data,
296 GDestroyNotify destroy_func)
297 {
298 g_return_if_fail(node != NULL);
299 g_datalist_id_set_data_full(QDATA(node), quark, data, destroy_func);
300 }
301
302 /**
303 * ipatch_xml_steal_qdata: (skip)
304 * @node: XML node
305 * @quark: Quark key
306 *
307 * Remove keyed data from an XML node, but don't call the data's destroy notify.
308 * Caller is thus given the ownership of the data. This is faster than
309 * ipatch_xml_steal_data() since the key must be converted to a quark
310 * anyways.
311 *
312 * Returns: (transfer none): The data pointer or %NULL if not set
313 */
314 gpointer
ipatch_xml_steal_qdata(GNode * node,GQuark quark)315 ipatch_xml_steal_qdata(GNode *node, GQuark quark)
316 {
317 gpointer data;
318
319 g_return_val_if_fail(node != NULL, NULL);
320
321 data = g_datalist_id_get_data(QDATA(node), quark);
322
323 if(data)
324 {
325 g_datalist_id_remove_no_notify(QDATA(node), quark);
326 }
327
328 return (data);
329 }
330
331 /**
332 * ipatch_xml_destroy: (skip)
333 * @node: Root of XML tree/sub tree to destroy
334 *
335 * Free an XML tree (a root @node and all its children). Does not need to be
336 * the actual root of a tree, i.e., can remove a sub tree.
337 */
338 void
ipatch_xml_destroy(GNode * node)339 ipatch_xml_destroy(GNode *node)
340 {
341 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_destroy_traverse_func, NULL);
342 g_node_destroy(node);
343 }
344
345 static gboolean
xml_destroy_traverse_func(GNode * node,gpointer data)346 xml_destroy_traverse_func(GNode *node, gpointer data)
347 {
348 ipatch_xml_node_free(node->data);
349 return (FALSE);
350 }
351
352 /**
353 * ipatch_xml_copy: (skip)
354 * @node: XML tree to copy
355 *
356 * Perform a deep copy on an XML tree.
357 *
358 * Returns: New duplicate XML tree.
359 */
360 GNode *
ipatch_xml_copy(GNode * node)361 ipatch_xml_copy(GNode *node)
362 {
363 g_return_val_if_fail(node != NULL, NULL);
364
365 return (g_node_copy_deep(node, (GCopyFunc)ipatch_xml_node_duplicate, NULL));
366 }
367
368 /**
369 * ipatch_xml_set_name: (skip)
370 * @node: XML node
371 * @name: Name to assign
372 *
373 * Set the name of an XML node.
374 */
375 void
ipatch_xml_set_name(GNode * node,const char * name)376 ipatch_xml_set_name(GNode *node, const char *name)
377 {
378 IpatchXmlNode *xmlnode;
379
380 g_return_if_fail(node != NULL);
381 g_return_if_fail(name != NULL);
382
383 xmlnode = node->data;
384 g_free(xmlnode->name);
385 xmlnode->name = g_strdup(name);
386 }
387
388 /**
389 * ipatch_xml_set_value: (skip)
390 * @node: XML node
391 * @value: Text value to assign or %NULL to clear it
392 *
393 * Set the text value of an XML node.
394 */
395 void
ipatch_xml_set_value(GNode * node,const char * value)396 ipatch_xml_set_value(GNode *node, const char *value)
397 {
398 IpatchXmlNode *xmlnode;
399
400 g_return_if_fail(node != NULL);
401
402 xmlnode = node->data;
403 g_free(xmlnode->value);
404 xmlnode->value = g_strdup(value);
405 }
406
407 /**
408 * ipatch_xml_set_value_printf: (skip)
409 * @node: XML node
410 * @format: Printf format
411 * @...: Printf arguments
412 *
413 * Assign a value to an XML node using a printf format and arguments.
414 */
415 void
ipatch_xml_set_value_printf(GNode * node,const char * format,...)416 ipatch_xml_set_value_printf(GNode *node, const char *format, ...)
417 {
418 va_list var_args;
419 char *value;
420
421 g_return_if_fail(node != NULL);
422 g_return_if_fail(format != NULL);
423
424 va_start(var_args, format);
425 value = g_strdup_vprintf(format, var_args);
426 va_end(var_args);
427
428 ipatch_xml_take_value(node, value);
429 }
430
431 /**
432 * ipatch_xml_take_name: (skip)
433 * @node: XML node
434 * @name: (nullable) (transfer full): Name to assign or %NULL to clear it
435 *
436 * Like ipatch_xml_set_name() but takes over the allocation of @name rather than
437 * duplicating it.
438 */
439 void
ipatch_xml_take_name(GNode * node,char * name)440 ipatch_xml_take_name(GNode *node, char *name)
441 {
442 IpatchXmlNode *xmlnode;
443
444 g_return_if_fail(node != NULL);
445 g_return_if_fail(name != NULL);
446
447 xmlnode = node->data;
448 g_free(xmlnode->name);
449 xmlnode->name = name;
450 }
451
452 /**
453 * ipatch_xml_take_value: (skip)
454 * @node: XML node
455 * @value: (transfer full): Text value to assign
456 *
457 * Like ipatch_xml_set_value() but takes over the allocation of @value rather than
458 * duplicating it.
459 */
460 void
ipatch_xml_take_value(GNode * node,char * value)461 ipatch_xml_take_value(GNode *node, char *value)
462 {
463 IpatchXmlNode *xmlnode;
464
465 g_return_if_fail(node != NULL);
466
467 xmlnode = node->data;
468 g_free(xmlnode->value);
469 xmlnode->value = value;
470 }
471
472 /**
473 * ipatch_xml_get_name: (skip)
474 * @node: XML node to get name of
475 *
476 * Get the name of an XML node.
477 *
478 * Returns: Name of XML node which is internal and should not be modified or freed.
479 */
480 G_CONST_RETURN char *
ipatch_xml_get_name(GNode * node)481 ipatch_xml_get_name(GNode *node)
482 {
483 g_return_val_if_fail(node != NULL, NULL);
484 return (((IpatchXmlNode *)(node->data))->name);
485 }
486
487 /**
488 * ipatch_xml_test_name: (skip)
489 * @node: XML node to get name of
490 * @cmpname: Name to compare to
491 *
492 * Test if the node has the given name.
493 *
494 * Returns: %TRUE if the node has the given name, %FALSE otherwise
495 */
496 gboolean
ipatch_xml_test_name(GNode * node,const char * cmpname)497 ipatch_xml_test_name(GNode *node, const char *cmpname)
498 {
499 const char *name;
500
501 g_return_val_if_fail(node != NULL, FALSE);
502 g_return_val_if_fail(cmpname != NULL, FALSE);
503
504 name = ipatch_xml_get_name(node);
505
506 return (name && strcmp(name, cmpname) == 0);
507 }
508
509 /**
510 * ipatch_xml_get_value: (skip)
511 * @node: XML node to get value of
512 *
513 * Get the text value of an XML node.
514 *
515 * Returns: Value of XML node or %NULL, which is internal and should not be
516 * modified or freed.
517 */
518 G_CONST_RETURN char *
ipatch_xml_get_value(GNode * node)519 ipatch_xml_get_value(GNode *node)
520 {
521 g_return_val_if_fail(node != NULL, NULL);
522 return (((IpatchXmlNode *)(node->data))->value);
523 }
524
525 /**
526 * ipatch_xml_dup_value: (skip)
527 * @node: XML node to duplicate value of
528 *
529 * Duplicate the text value of an XML node. Like ipatch_xml_get_value() but
530 * makes a copy of the value which the caller owns.
531 *
532 * Returns: Newly allocated duplicate value of XML node or %NULL.
533 */
534 char *
ipatch_xml_dup_value(GNode * node)535 ipatch_xml_dup_value(GNode *node)
536 {
537 g_return_val_if_fail(node != NULL, NULL);
538 return (g_strdup(((IpatchXmlNode *)(node->data))->value));
539 }
540
541 /**
542 * ipatch_xml_test_value: (skip)
543 * @node: XML node to get name of
544 * @cmpvalue: Value to compare to
545 *
546 * Test if the node has the given value.
547 *
548 * Returns: %TRUE if the node has the given value, %FALSE otherwise
549 */
550 gboolean
ipatch_xml_test_value(GNode * node,const char * cmpvalue)551 ipatch_xml_test_value(GNode *node, const char *cmpvalue)
552 {
553 const char *value;
554
555 g_return_val_if_fail(node != NULL, FALSE);
556 g_return_val_if_fail(cmpvalue != NULL, FALSE);
557
558 value = ipatch_xml_get_value(node);
559
560 return (value && strcmp(value, cmpvalue) == 0);
561 }
562
563 /**
564 * ipatch_xml_set_attribute: (skip)
565 * @node: XML node
566 * @attr_name: Attribute name to assign to
567 * @attr_value: (nullable): Attribute value to assign or %NULL to unset
568 *
569 * Set or unset an attribute of an XML node. If there is already an existing
570 * attribute with the given @attr_name, its value will be replaced.
571 */
572 void
ipatch_xml_set_attribute(GNode * node,const char * attr_name,const char * attr_value)573 ipatch_xml_set_attribute(GNode *node, const char *attr_name,
574 const char *attr_value)
575 {
576 IpatchXmlNode *xmlnode;
577 IpatchXmlAttr *attr;
578 GList *p;
579
580 g_return_if_fail(node != NULL);
581 g_return_if_fail(attr_name != NULL);
582
583 xmlnode = (IpatchXmlNode *)(node->data);
584
585 for(p = xmlnode->attributes; p; p = p->next)
586 {
587 attr = (IpatchXmlAttr *)(p->data);
588
589 if(strcmp(attr->name, attr_name) == 0)
590 {
591 if(attr_value)
592 {
593 g_free(attr->value);
594 attr->value = g_strdup(attr_value);
595 }
596 else
597 {
598 ipatch_xml_attr_free(attr);
599 xmlnode->attributes = g_list_delete_link(xmlnode->attributes, p);
600 }
601
602 return;
603 }
604 }
605
606 attr = ipatch_xml_attr_new();
607 attr->name = g_strdup(attr_name);
608 attr->value = g_strdup(attr_value);
609 xmlnode->attributes = g_list_append(xmlnode->attributes, attr);
610 }
611
612 /**
613 * ipatch_xml_set_attributes: (skip)
614 * @node: XML node
615 * @attr_name: First attribute name
616 * @attr_value: First attribute value
617 * @...: Additional name/value attribute strings, terminated by a %NULL name
618 *
619 * Set one or more attributes of an XML node.
620 */
621 void
ipatch_xml_set_attributes(GNode * node,const char * attr_name,const char * attr_value,const char * attr2_name,...)622 ipatch_xml_set_attributes(GNode *node, const char *attr_name,
623 const char *attr_value, const char *attr2_name, ...)
624 {
625 va_list var_args;
626 char *vname;
627
628 g_return_if_fail(node != NULL);
629 g_return_if_fail(attr_name != NULL);
630
631 ipatch_xml_set_attribute(node, attr_name, attr_value);
632
633 if(!attr2_name)
634 {
635 return;
636 }
637
638 va_start(var_args, attr2_name);
639
640 ipatch_xml_set_attribute(node, attr2_name, va_arg(var_args, char *));
641
642 while((vname = va_arg(var_args, char *)))
643 {
644 ipatch_xml_set_attribute(node, vname, va_arg(var_args, char *));
645 }
646
647 va_end(var_args);
648 }
649
650 /**
651 * ipatch_xml_get_attribute: (skip)
652 * @node: XML node
653 * @attr_name: Name of attribute
654 *
655 * Get the value of an attribute of an XML node.
656 *
657 * Returns: Value of the named attribute or %NULL if not found, value is internal
658 * and should not be modified or freed.
659 */
660 G_CONST_RETURN char *
ipatch_xml_get_attribute(GNode * node,const char * attr_name)661 ipatch_xml_get_attribute(GNode *node, const char *attr_name)
662 {
663 IpatchXmlAttr *attr;
664 GList *p;
665
666 g_return_val_if_fail(node != NULL, NULL);
667 g_return_val_if_fail(attr_name != NULL, NULL);
668
669 for(p = ((IpatchXmlNode *)(node->data))->attributes; p; p = p->next)
670 {
671 attr = (IpatchXmlAttr *)(p->data);
672
673 if(strcmp(attr->name, attr_name) == 0)
674 {
675 return (attr->value);
676 }
677 }
678
679 return (NULL);
680 }
681
682 /**
683 * ipatch_xml_test_attribute: (skip)
684 * @node: XML node
685 * @attr_name: Name of attribute
686 * @cmpval: Value to compare attribute to (%NULL to just check existence).
687 *
688 * Test if an attribute of an XML node is a given value or exists (@cmpval = %NULL).
689 *
690 * Returns: %TRUE if attribute exists and matches @cmpval (if set).
691 */
692 gboolean
ipatch_xml_test_attribute(GNode * node,const char * attr_name,const char * cmpval)693 ipatch_xml_test_attribute(GNode *node, const char *attr_name, const char *cmpval)
694 {
695 const char *attr_val;
696
697 g_return_val_if_fail(node != NULL, FALSE);
698 g_return_val_if_fail(attr_name != NULL, FALSE);
699
700 attr_val = ipatch_xml_get_attribute(node, attr_name);
701
702 return (attr_val && (!cmpval || strcmp(attr_val, cmpval) == 0));
703 }
704
705 /**
706 * ipatch_xml_find_child: (skip)
707 * @node: XML node
708 * @name: Node name of child to find
709 *
710 * Find a child node with the given @name. Only searches the children of @node
711 * and does not search recursively.
712 *
713 * Returns: (transfer none): Matching node or %NULL if not found.
714 */
715 GNode *
ipatch_xml_find_child(GNode * node,const char * name)716 ipatch_xml_find_child(GNode *node, const char *name)
717 {
718 IpatchXmlNode *xmlnode;
719 GNode *n;
720
721 g_return_val_if_fail(node != NULL, NULL);
722 g_return_val_if_fail(name != NULL, NULL);
723
724 for(n = node->children; n; n = n->next)
725 {
726 xmlnode = (IpatchXmlNode *)(n->data);
727
728 if(strcmp(xmlnode->name, name) == 0)
729 {
730 return (n);
731 }
732 }
733
734 return (NULL);
735 }
736
737 /**
738 * ipatch_xml_find_by_path: (skip)
739 * @node: XML node
740 * @path: Path specification in the form "name1.name2.name3" where each child
741 * of a node is separated by a '.' character.
742 *
743 * Get a node in a tree from a path string.
744 *
745 * Returns: (transfer none): Matching node or %NULL if not found.
746 */
747 GNode *
ipatch_xml_find_by_path(GNode * node,const char * path)748 ipatch_xml_find_by_path(GNode *node, const char *path)
749 {
750 g_return_val_if_fail(node != NULL, NULL);
751 g_return_val_if_fail(path != NULL, NULL);
752
753 return (ipatch_xml_find_by_path_recurse(node, path));
754 }
755
756 static GNode *
ipatch_xml_find_by_path_recurse(GNode * node,const char * path)757 ipatch_xml_find_by_path_recurse(GNode *node, const char *path)
758 {
759 IpatchXmlNode *xmlnode;
760 char *dot;
761 int len;
762 GNode *n;
763
764 dot = strchr(path, '.');
765 len = dot ? dot - path : strlen(path);
766
767 for(n = node->children; n; n = n->next)
768 {
769 xmlnode = (IpatchXmlNode *)(n->data);
770
771 if(strncmp(xmlnode->name, path, len) == 0)
772 {
773 if(!dot)
774 {
775 return (n);
776 }
777 else
778 {
779 return (ipatch_xml_find_by_path_recurse(n, dot + 1));
780 }
781 }
782 }
783
784 return (NULL);
785 }
786
787 /**
788 * ipatch_xml_to_str: (skip)
789 * @node: XML node
790 * @indent: Number of spaces of indent per level (0 for no indent)
791 *
792 * Render an XML tree to a string.
793 *
794 * Returns: Newly allocated string of XML content representing @node, free with
795 * g_free() when done using it.
796 */
797 char *
ipatch_xml_to_str(GNode * node,guint indent)798 ipatch_xml_to_str(GNode *node, guint indent)
799 {
800 GString *str;
801
802 g_return_val_if_fail(node != NULL, NULL);
803
804 str = g_string_new("");
805
806 ipatch_xml_to_str_recurse(str, node, 0, indent);
807
808 return (g_string_free(str, FALSE));
809 }
810
811 static void
ipatch_xml_to_str_recurse(GString * str,GNode * node,guint indent,guint inc)812 ipatch_xml_to_str_recurse(GString *str, GNode *node, guint indent, guint inc)
813 {
814 IpatchXmlNode *xmlnode;
815 char *esc;
816 GNode *n;
817 guint i;
818
819 xmlnode = (IpatchXmlNode *)(node->data);
820
821 for(i = 0; i < indent; i++)
822 {
823 g_string_append_c(str, ' ');
824 }
825
826 g_string_append_printf(str, "<%s", xmlnode->name);
827
828 if(xmlnode->attributes)
829 {
830 IpatchXmlAttr *attr;
831 GList *p;
832
833 for(p = xmlnode->attributes; p; p = p->next)
834 {
835 attr = (IpatchXmlAttr *)(p->data);
836 esc = g_markup_escape_text(attr->value, -1); /* ++ alloc */
837 g_string_append_printf(str, " %s=\"%s\"", attr->name, esc);
838 g_free(esc); /* -- free */
839 }
840 }
841
842 if(!xmlnode->value && !node->children)
843 {
844 g_string_append(str, "/>\n");
845 return;
846 }
847 else
848 {
849 g_string_append(str, ">");
850 }
851
852 if(xmlnode->value)
853 {
854 esc = g_markup_escape_text(xmlnode->value, -1); /* ++ alloc */
855 g_string_append(str, esc);
856 g_free(esc); /* -- free */
857 }
858
859 if(node->children)
860 {
861 g_string_append_c(str, '\n');
862
863 for(n = node->children; n; n = n->next)
864 {
865 ipatch_xml_to_str_recurse(str, n, indent + inc, inc);
866 }
867
868 for(i = 0; i < indent; i++)
869 {
870 g_string_append_c(str, ' ');
871 }
872 }
873
874 g_string_append_printf(str, "</%s>\n", xmlnode->name);
875 }
876
877 /**
878 * ipatch_xml_save_to_file: (skip)
879 * @node: XML tree to save
880 * @indent: Number of spaces to indent per level
881 * @filename: File name to save to
882 * @err: Location to store error info or %NULL to ignore
883 *
884 * Store an XML tree to a file.
885 *
886 * Returns: %TRUE on success, %FALSE otherwise (in which case @err may be set)
887 */
888 gboolean
ipatch_xml_save_to_file(GNode * node,guint indent,const char * filename,GError ** err)889 ipatch_xml_save_to_file(GNode *node, guint indent, const char *filename,
890 GError **err)
891 {
892 gboolean retval;
893 char *s;
894
895 s = ipatch_xml_to_str(node, indent); /* ++ alloc */
896
897 if(!s)
898 {
899 return (FALSE);
900 }
901
902 retval = g_file_set_contents(filename, s, -1, err);
903
904 g_free(s); /* -- free */
905
906 return (retval);
907 }
908
909 /**
910 * ipatch_xml_from_str: (skip)
911 * @str: XML content to parse
912 * @err: Location to store error info or %NULL to ignore
913 *
914 * Parse XML content into an XML node tree.
915 *
916 * Returns: Newly allocated XML node tree or
917 * %NULL on error (@err may be set), can be freed with ipatch_xml_destroy().
918 */
919 GNode *
ipatch_xml_from_str(const char * str,GError ** err)920 ipatch_xml_from_str(const char *str, GError **err)
921 {
922 GMarkupParseContext *ctx;
923
924 GMarkupParser parser =
925 {
926 xml_start_element,
927 xml_end_element,
928 xml_text
929 };
930
931 GNode *root = NULL;
932
933 ctx = g_markup_parse_context_new(&parser, 0, &root, NULL);
934
935 if(!g_markup_parse_context_parse(ctx, str, -1, err)
936 || !g_markup_parse_context_end_parse(ctx, err))
937 {
938 g_markup_parse_context_free(ctx);
939
940 if(root)
941 {
942 root = g_node_get_root(root);
943 ipatch_xml_destroy(root);
944 }
945
946 return (NULL);
947 }
948
949 g_markup_parse_context_free(ctx);
950
951 return (root);
952 }
953
954 static void
xml_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)955 xml_start_element(GMarkupParseContext *context, const gchar *element_name,
956 const gchar **attribute_names, const gchar **attribute_values,
957 gpointer user_data, GError **error)
958 {
959 GNode **node = (GNode **)user_data;
960 *node = ipatch_xml_new_node_strv(*node, element_name, NULL,
961 attribute_names, attribute_values);
962 }
963
964 static void
xml_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)965 xml_end_element(GMarkupParseContext *context, const gchar *element_name,
966 gpointer user_data, GError **error)
967 {
968 GNode **node = (GNode **)user_data;
969
970 if((*node)->parent)
971 {
972 *node = (*node)->parent;
973 }
974 }
975
976 static void
xml_text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)977 xml_text(GMarkupParseContext *context, const gchar *text, gsize text_len,
978 gpointer user_data, GError **error)
979 {
980 GNode **node = (GNode **)user_data;
981 IpatchXmlNode *xmlnode;
982
983 xmlnode = (IpatchXmlNode *)((*node)->data);
984 g_free(xmlnode->value);
985 xmlnode->value = g_strdup(text);
986 }
987
988 /**
989 * ipatch_xml_load_from_file: (skip)
990 * @filename: File name containing XML content to parse
991 * @err: Location to store error info or %NULL to ignore
992 *
993 * Parse an XML file into an XML node tree.
994 *
995 * Returns: Newly allocated XML node tree
996 * or %NULL on error (@err may be set), can be freed with ipatch_xml_destroy().
997 */
998 GNode *
ipatch_xml_load_from_file(const char * filename,GError ** err)999 ipatch_xml_load_from_file(const char *filename, GError **err)
1000 {
1001 GNode *node;
1002 char *str;
1003
1004 g_return_val_if_fail(filename != NULL, NULL);
1005 g_return_val_if_fail(!err || !*err, NULL);
1006
1007 if(!g_file_get_contents(filename, &str, NULL, err)) /* ++ alloc */
1008 {
1009 return (NULL);
1010 }
1011
1012 node = ipatch_xml_from_str(str, err);
1013
1014 g_free(str);
1015
1016 return (node);
1017 }
1018
1019 /**
1020 * ipatch_xml_node_new: (skip)
1021 *
1022 * Create a new XML node structure. Not normally used.
1023 *
1024 * Returns: New XML node structure, which should be added to a GNode.
1025 */
1026 IpatchXmlNode *
ipatch_xml_node_new(void)1027 ipatch_xml_node_new(void)
1028 {
1029 IpatchXmlNode *xmlnode;
1030
1031 xmlnode = g_slice_new0(IpatchXmlNode);
1032 g_datalist_init(&xmlnode->qdata);
1033
1034 return (xmlnode);
1035 }
1036
1037 /**
1038 * ipatch_xml_node_free: (skip)
1039 * @xmlnode: XML node structure to free
1040 *
1041 * Free an XML node structure and its contents. Not normally used.
1042 */
1043 void
ipatch_xml_node_free(IpatchXmlNode * xmlnode)1044 ipatch_xml_node_free(IpatchXmlNode *xmlnode)
1045 {
1046 GList *p;
1047
1048 g_return_if_fail(xmlnode != NULL);
1049
1050 g_free(xmlnode->name);
1051 g_free(xmlnode->value);
1052
1053 g_datalist_clear(&xmlnode->qdata);
1054
1055 for(p = xmlnode->attributes; p; p = g_list_delete_link(p, p))
1056 {
1057 ipatch_xml_attr_free(p->data);
1058 }
1059
1060 g_slice_free(IpatchXmlNode, xmlnode);
1061 }
1062
1063 /**
1064 * ipatch_xml_node_duplicate: (skip)
1065 * @xmlnode: XML node structure to duplicate
1066 *
1067 * Duplicate an XML node structure and its contents. Not normally used.
1068 * Note that arbitrary user data assigned to the XML node will not be duplicated.
1069 *
1070 * Returns: New duplicate of @xmlnode.
1071 */
1072 IpatchXmlNode *
ipatch_xml_node_duplicate(const IpatchXmlNode * xmlnode)1073 ipatch_xml_node_duplicate(const IpatchXmlNode *xmlnode)
1074 {
1075 IpatchXmlNode *dupnode;
1076 IpatchXmlAttr *dupattr;
1077 GList *p;
1078
1079 g_return_val_if_fail(xmlnode != NULL, NULL);
1080
1081 dupnode = ipatch_xml_node_new();
1082 dupnode->name = g_strdup(xmlnode->name);
1083 dupnode->value = g_strdup(xmlnode->value);
1084
1085 for(p = xmlnode->attributes; p; p = p->next)
1086 {
1087 dupattr = ipatch_xml_attr_duplicate(p->data);
1088 dupnode->attributes = g_list_prepend(dupnode->attributes, dupattr);
1089 }
1090
1091 dupnode->attributes = g_list_reverse(dupnode->attributes);
1092
1093 return (dupnode);
1094 }
1095
1096 /**
1097 * ipatch_xml_attr_new: (skip)
1098 *
1099 * Create a new XML attribute structure. Not normally used.
1100 *
1101 * Returns: New XML attribute structure which should be added to an #IpatchXmlNode
1102 * attributes list.
1103 */
1104 IpatchXmlAttr *
ipatch_xml_attr_new(void)1105 ipatch_xml_attr_new(void)
1106 {
1107 return (g_slice_new0(IpatchXmlAttr));
1108 }
1109
1110 /**
1111 * ipatch_xml_attr_free: (skip)
1112 * @attr: Attribute structure to free
1113 *
1114 * Free an XML attribute structure. Not normally used.
1115 */
1116 void
ipatch_xml_attr_free(IpatchXmlAttr * attr)1117 ipatch_xml_attr_free(IpatchXmlAttr *attr)
1118 {
1119 g_return_if_fail(attr != NULL);
1120 g_free(attr->name);
1121 g_free(attr->value);
1122 g_slice_free(IpatchXmlAttr, attr);
1123 }
1124
1125 /**
1126 * ipatch_xml_attr_duplicate: (skip)
1127 * @attr: Attribute structure to duplicate
1128 *
1129 * Duplicate an XML attribute structure. Not normally used.
1130 *
1131 * Returns: New duplicate attribute structure
1132 */
1133 IpatchXmlAttr *
ipatch_xml_attr_duplicate(const IpatchXmlAttr * attr)1134 ipatch_xml_attr_duplicate(const IpatchXmlAttr *attr)
1135 {
1136 IpatchXmlAttr *dupattr;
1137
1138 g_return_val_if_fail(attr != NULL, NULL);
1139
1140 dupattr = ipatch_xml_attr_new();
1141 dupattr->name = g_strdup(attr->name);
1142 dupattr->value = g_strdup(attr->value);
1143
1144 return (dupattr);
1145 }
1146