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