1 /*
2  *      fm-xml-file.c
3  *
4  *      Copyright 2013 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
5  *
6  *      This file is a part of libfm-extra package.
7  *
8  *      This library is free software; you can redistribute it and/or
9  *      modify it under the terms of the GNU Lesser General Public
10  *      License as published by the Free Software Foundation; either
11  *      version 2.1 of the License, or (at your option) any later version.
12  *
13  *      This library is distributed in the hope that it will be useful,
14  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *      Lesser General Public License for more details.
17  *
18  *      You should have received a copy of the GNU Lesser General Public
19  *      License along with this library; if not, write to the Free Software
20  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  */
22 
23 /**
24  * SECTION:fm-xml-file
25  * @short_description: Simple XML parser.
26  * @title: FmXmlFile
27  *
28  * @include: libfm/fm-extra.h
29  *
30  * The FmXmlFile represents content of some XML file in form that can
31  * be altered and saved later.
32  *
33  * This parser has some simplifications on XML parsing:
34  * * Only UTF-8 encoding is supported
35  * * No user-defined entities, those should be converted externally
36  * * Processing instructions, comments and the doctype declaration are parsed but are not interpreted in any way
37  * The markup format does support:
38  * * Elements
39  * * Attributes
40  * * 5 standard entities: &amp;amp; &amp;lt; &amp;gt; &amp;quot; &amp;apos;
41  * * Character references
42  * * Sections marked as CDATA
43  *
44  * The application should respect g_type_init() if this parser is used
45  * without usage of libfm.
46  */
47 
48 #ifdef HAVE_CONFIG_H
49 #include <config.h>
50 #endif
51 
52 #include "fm-xml-file.h"
53 
54 #include <glib/gi18n-lib.h>
55 #include "glib-compat.h"
56 
57 #include <stdlib.h>
58 #include <errno.h>
59 
60 typedef struct
61 {
62     gchar *name;
63     FmXmlFileHandler handler;
64     gboolean in_line : 1;
65 } FmXmlFileTagDesc;
66 
67 struct _FmXmlFile
68 {
69     GObject parent;
70     GList *items;
71     GString *data;
72     char *comment_pre;
73     FmXmlFileItem *current_item;
74     FmXmlFileTagDesc *tags; /* tags[0].name contains DTD */
75     guint n_tags; /* number of elements in tags */
76     guint line, pos;
77 };
78 
79 struct _FmXmlFileClass
80 {
81     GObjectClass parent_class;
82 };
83 
84 struct _FmXmlFileItem
85 {
86     FmXmlFileTag tag;
87     union {
88         gchar *tag_name; /* only for tag == FM_XML_FILE_TAG_NOT_HANDLED */
89         gchar *text; /* only for tag == FM_XML_FILE_TEXT, NULL if directive */
90     };
91     char **attribute_names;
92     char **attribute_values;
93     FmXmlFile *file;
94     FmXmlFileItem *parent;
95     GList **parent_list; /* points to file->items or to parent->children */
96     GList *children;
97     gchar *comment; /* a little trick: it is equal to text if it is CDATA */
98 };
99 
100 
101 G_DEFINE_TYPE(FmXmlFile, fm_xml_file, G_TYPE_OBJECT);
102 
fm_xml_file_finalize(GObject * object)103 static void fm_xml_file_finalize(GObject *object)
104 {
105     FmXmlFile *self;
106     guint i;
107 
108     g_return_if_fail(object != NULL);
109     g_return_if_fail(FM_IS_XML_FILE(object));
110 
111     self = (FmXmlFile*)object;
112     self->current_item = NULL; /* we destroying it */
113     while (self->items)
114     {
115         g_assert(((FmXmlFileItem*)self->items->data)->file == self);
116         g_assert(((FmXmlFileItem*)self->items->data)->parent == NULL);
117         fm_xml_file_item_destroy(self->items->data);
118     }
119     for (i = 0; i < self->n_tags; i++)
120         g_free(self->tags[i].name);
121     g_free(self->tags);
122     if (self->data)
123         g_string_free(self->data, TRUE);
124     g_free(self->comment_pre);
125 
126     G_OBJECT_CLASS(fm_xml_file_parent_class)->finalize(object);
127 }
128 
fm_xml_file_class_init(FmXmlFileClass * klass)129 static void fm_xml_file_class_init(FmXmlFileClass *klass)
130 {
131     GObjectClass *g_object_class;
132 
133     g_object_class = G_OBJECT_CLASS(klass);
134     g_object_class->finalize = fm_xml_file_finalize;
135 }
136 
fm_xml_file_init(FmXmlFile * self)137 static void fm_xml_file_init(FmXmlFile *self)
138 {
139     self->tags = g_new0(FmXmlFileTagDesc, 1);
140     self->n_tags = 1;
141     self->line = 1;
142 }
143 
144 /**
145  * fm_xml_file_new
146  * @sibling: (allow-none): container to copy handlers data
147  *
148  * Creates new empty #FmXmlFile container. If @sibling is not %NULL
149  * then new container will have callbacks identical to set in @sibling.
150  * Use @sibling parameter if you need to work with few XML files that
151  * share the same schema or if you need to use the same tag ids for more
152  * than one file.
153  *
154  * Returns: (transfer full): newly created object.
155  *
156  * Since: 1.2.0
157  */
fm_xml_file_new(FmXmlFile * sibling)158 FmXmlFile *fm_xml_file_new(FmXmlFile *sibling)
159 {
160     FmXmlFile *self;
161     FmXmlFileTag i;
162 
163     self = (FmXmlFile*)g_object_new(FM_XML_FILE_TYPE, NULL);
164     if (sibling && sibling->n_tags > 1)
165     {
166         self->n_tags = sibling->n_tags;
167         self->tags = g_renew(FmXmlFileTagDesc, self->tags, self->n_tags);
168         for (i = 1; i < self->n_tags; i++)
169         {
170             self->tags[i].name = g_strdup(sibling->tags[i].name);
171             self->tags[i].handler = sibling->tags[i].handler;
172         }
173     }
174     return self;
175 }
176 
177 /**
178  * fm_xml_file_set_handler
179  * @file: the parser container
180  * @tag: tag to use @handler for
181  * @handler: callback for @tag
182  * @in_line: %TRUE if tag should not receive new line by fm_xml_file_to_data()
183  * @error: (allow-none) (out): location to save error
184  *
185  * Sets @handler for @file to be called on parse when @tag is found
186  * in XML data. This function will fail if some handler for @tag was
187  * aready set, in this case @error will be set appropriately.
188  *
189  * Returns: id for the @tag.
190  *
191  * Since: 1.2.0
192  */
fm_xml_file_set_handler(FmXmlFile * file,const char * tag,FmXmlFileHandler handler,gboolean in_line,GError ** error)193 FmXmlFileTag fm_xml_file_set_handler(FmXmlFile *file, const char *tag,
194                                      FmXmlFileHandler handler, gboolean in_line,
195                                      GError **error)
196 {
197     FmXmlFileTag i;
198 
199     g_return_val_if_fail(file != NULL && FM_IS_XML_FILE(file), FM_XML_FILE_TAG_NOT_HANDLED);
200     g_return_val_if_fail(handler != NULL, FM_XML_FILE_TAG_NOT_HANDLED);
201     g_return_val_if_fail(tag != NULL, FM_XML_FILE_TAG_NOT_HANDLED);
202     for (i = 1; i < file->n_tags; i++)
203         if (strcmp(file->tags[i].name, tag) == 0)
204         {
205             g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
206                         _("Duplicate handler for tag <%s>"), tag);
207             return i;
208         }
209     file->tags = g_renew(FmXmlFileTagDesc, file->tags, i + 1);
210     file->tags[i].name = g_strdup(tag);
211     file->tags[i].handler = handler;
212     file->tags[i].in_line = in_line;
213     file->n_tags = i + 1;
214     /* g_debug("XML parser: added handler '%s' id %u", tag, (guint)i); */
215     return i;
216 }
217 
218 
219 /* parse */
220 
221 /*
222  * This function was taken from GLib sources and adapted to be used here.
223  * Copyright 2000, 2003 Red Hat, Inc.
224  *
225  * re-write the GString in-place, unescaping anything that escaped.
226  * most XML does not contain entities, or escaping.
227  */
228 static gboolean
unescape_gstring_inplace(GString * string,guint * line_num,guint * pos,gboolean normalize_attribute,GError ** error)229 unescape_gstring_inplace (//GMarkupParseContext  *context,
230                           GString              *string,
231                           //gboolean             *is_ascii,
232                           guint                *line_num,
233                           guint                *pos,
234                           gboolean              normalize_attribute,
235                           GError              **error)
236 {
237   //char mask, *to;
238   char *to;
239   //int line_num = 1;
240   const char *from, *sol;
241 
242   //*is_ascii = FALSE;
243 
244   /*
245    * Meeks' theorum: unescaping can only shrink text.
246    * for &lt; etc. this is obvious, for &#xffff; more
247    * thought is required, but this is patently so.
248    */
249   //mask = 0;
250   for (from = to = string->str; *from != '\0'; from++, to++)
251     {
252       *to = *from;
253 
254       //mask |= *to;
255       if (*to == '\n')
256       {
257         (*line_num)++;
258         *pos = 0;
259       }
260       if (normalize_attribute && (*to == '\t' || *to == '\n'))
261         *to = ' ';
262       if (*to == '\r')
263         {
264           *to = normalize_attribute ? ' ' : '\n';
265           if (from[1] == '\n')
266           {
267             from++;
268             (*line_num)++;
269             *pos = 0;
270           }
271         }
272       sol = from;
273       if (*from == '&')
274         {
275           from++;
276           if (*from == '#')
277             {
278               gboolean is_hex = FALSE;
279               gulong l;
280               gchar *end = NULL;
281 
282               from++;
283 
284               if (*from == 'x')
285                 {
286                   is_hex = TRUE;
287                   from++;
288                 }
289 
290               /* digit is between start and p */
291               errno = 0;
292               if (is_hex)
293                 l = strtoul (from, &end, 16);
294               else
295                 l = strtoul (from, &end, 10);
296 
297               if (end == from || errno != 0)
298                 {
299                   g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
300                               _("Failed to parse '%-.*s', which "
301                                 "should have been a digit "
302                                 "inside a character reference "
303                                 "(&#234; for example) - perhaps "
304                                 "the digit is too large"),
305                               (int)(end - from), from);
306                   return FALSE;
307                 }
308               else if (*end != ';')
309                 {
310                   g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
311                               _("Character reference did not end with a "
312                                 "semicolon; "
313                                 "most likely you used an ampersand "
314                                 "character without intending to start "
315                                 "an entity - escape ampersand as &amp;"));
316                   return FALSE;
317                 }
318               else
319                 {
320                   /* characters XML 1.1 permits */
321                   if ((0 < l && l <= 0xD7FF) ||
322                       (0xE000 <= l && l <= 0xFFFD) ||
323                       (0x10000 <= l && l <= 0x10FFFF))
324                     {
325                       gchar buf[8];
326                       memset (buf, 0, 8);
327                       g_unichar_to_utf8 (l, buf);
328                       strncpy (to, buf, 8);
329                       to += strlen (buf) - 1;
330                       from = end;
331                       //if (l >= 0x80) /* not ascii */
332                         //mask |= 0x80;
333                     }
334                   else
335                     {
336                       g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
337                                   _("Character reference '%-.*s' does not "
338                                     "encode a permitted character"),
339                                   (int)(end - from), from);
340                       return FALSE;
341                     }
342                 }
343             }
344 
345           else if (strncmp (from, "lt;", 3) == 0)
346             {
347               *to = '<';
348               from += 2;
349             }
350           else if (strncmp (from, "gt;", 3) == 0)
351             {
352               *to = '>';
353               from += 2;
354             }
355           else if (strncmp (from, "amp;", 4) == 0)
356             {
357               *to = '&';
358               from += 3;
359             }
360           else if (strncmp (from, "quot;", 5) == 0)
361             {
362               *to = '"';
363               from += 4;
364             }
365           else if (strncmp (from, "apos;", 5) == 0)
366             {
367               *to = '\'';
368               from += 4;
369             }
370           else
371             {
372               if (*from == ';')
373                 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
374                             _("Empty entity '&;' seen; valid "
375                               "entities are: &amp; &quot; &lt; &gt; &apos;"));
376               else
377                 {
378                   const char *end = strchr (from, ';');
379                   if (end)
380                     g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
381                                 _("Entity name '%-.*s' is not known"),
382                                 (int)(end-from), from);
383                   else
384                     g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
385                                 _("Entity did not end with a semicolon; "
386                                   "most likely you used an ampersand "
387                                   "character without intending to start "
388                                   "an entity - escape ampersand as &amp;"));
389                 }
390               return FALSE;
391             }
392         }
393       *pos += (from - sol) + 1;
394     }
395 
396   /* g_debug("unescape_gstring_inplace completed"); */
397   g_assert (to - string->str <= (gint)string->len);
398   if (to - string->str != (gint)string->len)
399     g_string_truncate (string, to - string->str);
400 
401   //*is_ascii = !(mask & 0x80);
402 
403   return TRUE;
404 }
405 
406 
_update_file_ptr_part(FmXmlFile * file,const char * start,const char * end)407 static inline void _update_file_ptr_part(FmXmlFile *file, const char *start,
408                                          const char *end)
409 {
410     while (start < end)
411     {
412         if (*start == '\n')
413         {
414             file->line++;
415             file->pos = 0;
416         }
417         else
418             /* FIXME: advance by chars not bytes? */
419             file->pos++;
420         start++;
421     }
422 }
423 
_update_file_ptr(FmXmlFile * file,int add_cols)424 static inline void _update_file_ptr(FmXmlFile *file, int add_cols)
425 {
426     guint i;
427     char *p;
428 
429     for (i = file->data->len, p = file->data->str; i > 0; i--, p++)
430     {
431         if (*p == '\n')
432         {
433             file->line++;
434             file->pos = 0;
435         }
436         else
437             /* FIXME: advance by chars not bytes? */
438             file->pos++;
439     }
440     file->pos += add_cols;
441 }
442 
_is_space(char c)443 static inline gboolean _is_space(char c)
444 {
445     return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
446 }
447 
448 /**
449  * fm_xml_file_parse_data
450  * @file: the parser container
451  * @text: data to parse
452  * @size: size of @text
453  * @error: (allow-none) (out): location to save error
454  * @user_data: data to pass to handlers
455  *
456  * Parses next chunk of @text data. Parsing stops at end of data or at any
457  * error. In latter case @error will be set appropriately.
458  *
459  * See also: fm_xml_file_finish_parse().
460  *
461  * Returns: %FALSE if parsing failed.
462  *
463  * Since: 1.2.0
464  */
fm_xml_file_parse_data(FmXmlFile * file,const char * text,gsize size,GError ** error,gpointer user_data)465 gboolean fm_xml_file_parse_data(FmXmlFile *file, const char *text,
466                                 gsize size, GError **error, gpointer user_data)
467 {
468     gsize ptr, len;
469     char *dst, *end, *tag, *name, *value;
470     GString *buff;
471     FmXmlFileItem *item;
472     gboolean closing, selfdo;
473     FmXmlFileTag i;
474     char **attrib_names, **attrib_values;
475     guint attribs;
476     char quote;
477 
478     g_return_val_if_fail(file != NULL && FM_IS_XML_FILE(file), FALSE);
479 _restart:
480     if (size == 0)
481         return TRUE;
482     /* if file->data has '<' as first char then we stopped at tag */
483     if (file->data && file->data->len && file->data->str[0] == '<')
484     {
485         for (ptr = 0; ptr < size; ptr++)
486             if (text[ptr] == '>')
487                 break;
488         if (ptr == size) /* still no end of that tag */
489         {
490             g_string_append_len(file->data, text, size);
491             return TRUE;
492         }
493         /* we got a complete tag, nice, let parse it */
494         g_string_append_len(file->data, text, ptr);
495         ptr++;
496         text += ptr;
497         size -= ptr;
498         /* check for CDATA first */
499         if (file->data->len >= 11 /* <![CDATA[]] */ &&
500             strncmp(file->data->str, "<![CDATA[", 9) == 0)
501         {
502             end = file->data->str + file->data->len;
503             if (end[-2] != ']' || end[-1] != ']') /* find end of CDATA */
504             {
505                 g_string_append_c(file->data, '>');
506                 goto _restart;
507             }
508             if (file->current_item == NULL) /* CDATA at top level! */
509                 g_warning("FmXmlFile: line %u: junk CDATA in XML file ignored",
510                           file->line);
511             else
512             {
513                 item = fm_xml_file_item_new(FM_XML_FILE_TEXT);
514                 item->text = item->comment = g_strndup(&file->data->str[9],
515                                                        file->data->len - 11);
516                 fm_xml_file_item_append_child(file->current_item, item);
517             }
518             _update_file_ptr(file, 1);
519             g_string_truncate(file->data, 0);
520             goto _restart;
521         }
522         /* check for comment */
523         if (file->data->len >= 7 /* <!-- -- */ &&
524             strncmp(file->data->str, "<!--", 4) == 0)
525         {
526             end = file->data->str + file->data->len;
527             if (end[-2] != '-' || end[-1] != '-') /* find end of comment */
528             {
529                 g_string_append_c(file->data, '>');
530                 goto _restart;
531             }
532             g_free(file->comment_pre);
533             /* FIXME: not ignore duplicate comments */
534             if (_is_space(end[-3]))
535                 file->comment_pre = g_strndup(&file->data->str[5],
536                                               file->data->len - 8);
537             else /* FIXME: check: XML spec says it should be not '-' */
538                 file->comment_pre = g_strndup(&file->data->str[5],
539                                               file->data->len - 7);
540             _update_file_ptr(file, 1);
541             g_string_truncate(file->data, 0);
542             goto _restart;
543         }
544         /* check for DTD - it may be only at top level */
545         if (file->current_item == NULL && file->data->len >= 10 &&
546             strncmp(file->data->str, "<!DOCTYPE", 9) == 0 &&
547             _is_space(file->data->str[9]))
548         {
549             /* FIXME: can DTD contain any tags? count '<' and '>' pairs */
550             if (file->tags[0].name) /* duplicate DTD! */
551                 g_warning("FmXmlFile: line %u: duplicate DTD, ignored",
552                           file->line);
553             else
554                 file->tags[0].name = g_strndup(&file->data->str[10],
555                                                file->data->len - 10);
556             _update_file_ptr(file, 1);
557             g_string_truncate(file->data, 0);
558             goto _restart;
559         }
560         /* support directives such as <?xml ..... ?> */
561         if (file->data->len >= 4 /* <?x? */ &&
562             file->data->str[1] == '?' &&
563             file->data->str[file->data->len-1] == '?')
564         {
565             item = fm_xml_file_item_new(FM_XML_FILE_TEXT);
566             item->comment = g_strndup(&file->data->str[2], file->data->len - 3);
567             if (file->current_item != NULL)
568                 fm_xml_file_item_append_child(file->current_item, item);
569             else
570             {
571                 item->file = file;
572                 item->parent_list = &file->items;
573                 file->items = g_list_append(file->items, item);
574             }
575             _update_file_ptr(file, 1);
576             g_string_truncate(file->data, 0);
577             goto _restart;
578         }
579         closing = (file->data->str[1] == '/');
580         end = file->data->str + file->data->len;
581         selfdo = (!closing && end[-1] == '/');
582         if (selfdo)
583             end--;
584         tag = closing ? &file->data->str[2] : &file->data->str[1];
585         for (dst = tag; dst < end; dst++)
586             if (_is_space(*dst))
587                 break;
588         _update_file_ptr_part(file, file->data->str, dst + 1);
589         *dst = '\0'; /* terminate the tag */
590         if (closing)
591         {
592             if (dst != end) /* we got a space char in closing tag */
593             {
594                 g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
595                                     _("Space isn't allowed in the close tag"));
596                 return FALSE;
597             }
598             /* g_debug("XML parser: found closing tag '%s' for %p at %d:%d", tag,
599                     file->current_item, file->line, file->pos); */
600             item = file->current_item;
601             if (item == NULL) /* no tag to close */
602             {
603                 g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
604                             _("Element '%s' was closed but no element was opened"),
605                             tag);
606                 return FALSE;
607             }
608             else
609             {
610                 char *tagname;
611 
612                 if (item->tag == FM_XML_FILE_TAG_NOT_HANDLED)
613                     tagname = item->tag_name;
614                 else
615                     tagname = file->tags[item->tag].name;
616                 if (strcmp(tag, tagname)) /* closing tag doesn't match */
617                 {
618                     /* FIXME: validate tag so be more verbose on error */
619                     g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
620                                 _("Element '%s' was closed but the currently "
621                                   "open element is '%s'"), tag, tagname);
622                     return FALSE;
623                 }
624                 file->current_item = item->parent;
625 _close_the_tag:
626                 /* g_debug("XML parser: close the tag '%s'", tag); */
627                 g_string_truncate(file->data, 0);
628                 if (item->tag != FM_XML_FILE_TAG_NOT_HANDLED)
629                 {
630                     if (!file->tags[item->tag].handler(item, item->children,
631                                                        item->attribute_names,
632                                                        item->attribute_values,
633                                                        item->attribute_names ? g_strv_length(item->attribute_names) : 0,
634                                                        file->line,
635                                                        file->pos,
636                                                        error, user_data))
637                         return FALSE;
638                 }
639                 file->pos++; /* '>' */
640                 goto _restart;
641             }
642         }
643         else /* opening tag */
644         {
645             /* g_debug("XML parser: found opening tag '%s'", tag); */
646             /* parse and check tag name */
647             for (i = 1; i < file->n_tags; i++)
648                 if (strcmp(file->tags[i].name, tag) == 0)
649                     break;
650             if (i == file->n_tags)
651                 /* FIXME: do name validation */
652                 i = FM_XML_FILE_TAG_NOT_HANDLED;
653             /* parse and check attributes */
654             attribs = 0;
655             attrib_names = attrib_values = NULL;
656             while (dst < end)
657             {
658                 name = &dst[1]; /* skip this space */
659                 while (name < end && _is_space(*name))
660                     name++;
661                 value = name;
662                 while (value < end && !_is_space(*value) && *value != '=')
663                     value++;
664                 len = value - name;
665                 _update_file_ptr_part(file, dst, value);
666                 /* FIXME: skip spaces before =? */
667                 if (value + 3 <= end && *value == '=') /* minimum is ="" */
668                 {
669                     value++;
670                     file->pos++; /* '=' */
671                     /* FIXME: skip spaces after =? */
672                     quote = *value++;
673                     if (quote != '\'' && quote != '"')
674                     {
675                         g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
676                                     _("Invalid char '%c' at start of attribute value"),
677                                     quote);
678                         goto _attr_error;
679                     }
680                     file->pos++; /* quote char */
681                     for (ptr = 0; &value[ptr] < end; ptr++)
682                         if (value[ptr] == quote)
683                             break;
684                     if (&value[ptr] == end)
685                     {
686                         g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
687                                     _("Invalid char '%c' at end of attribute value,"
688                                       " expected '%c'"), value[ptr-1], quote);
689                         goto _attr_error;
690                     }
691                     buff = g_string_new_len(value, ptr);
692                     if (!unescape_gstring_inplace(buff, &file->line,
693                                                   &file->pos, TRUE, error))
694                     {
695                         g_string_free(buff, TRUE);
696 _attr_error:
697                         for (i = 0; i < attribs; i++)
698                         {
699                             g_free(attrib_names[i]);
700                             g_free(attrib_values[i]);
701                         }
702                         g_free(attrib_names);
703                         g_free(attrib_values);
704                         return FALSE;
705                     }
706                     dst = &value[ptr+1];
707                     value = g_string_free(buff, FALSE);
708                     file->pos++; /* end quote char */
709                 }
710                 else
711                 {
712                     dst = value;
713                     value = NULL;
714                     /* FIXME: isn't it error? */
715                 }
716                 attrib_names = g_renew(char *, attrib_names, attribs + 2);
717                 attrib_values = g_renew(char *, attrib_values, attribs + 2);
718                 attrib_names[attribs] = g_strndup(name, len);
719                 attrib_values[attribs] = value;
720                 attribs++;
721             }
722             if (attribs > 0)
723             {
724                 attrib_names[attribs] = NULL;
725                 attrib_values[attribs] = NULL;
726             }
727             /* create new item */
728             item = fm_xml_file_item_new(i);
729             item->attribute_names = attrib_names;
730             item->attribute_values = attrib_values;
731             if (i == FM_XML_FILE_TAG_NOT_HANDLED)
732                 item->tag_name = g_strdup(tag);
733             /* insert new item into the container */
734             item->comment = file->comment_pre;
735             file->comment_pre = NULL;
736             if (file->current_item)
737                 fm_xml_file_item_append_child(file->current_item, item);
738             else
739             {
740                 item->file = file;
741                 item->parent_list = &file->items;
742                 file->items = g_list_append(file->items, item);
743             }
744             file->pos++; /* '>' or '/' */
745             if (selfdo) /* simple self-closing tag */
746                 goto _close_the_tag;
747             file->current_item = item;
748             g_string_truncate(file->data, 0);
749             goto _restart;
750         }
751     }
752     /* otherwise we stopped at some data somewhere */
753     else
754     {
755         if (!file->data || file->data->len == 0) while (size > 0)
756         {
757             /* skip leading spaces */
758             if (*text == '\n')
759             {
760                 file->line++;
761                 file->pos = 0;
762             }
763             else if (*text == ' ' || *text == '\t' || *text == '\r')
764                 file->pos++;
765             else
766                 break;
767             text++;
768             size--;
769         }
770         for (ptr = 0; ptr < size; ptr++)
771             if (text[ptr] == '<')
772                 break;
773         if (file->data == NULL)
774             file->data = g_string_new_len(text, ptr);
775         else if (ptr > 0)
776             g_string_append_len(file->data, text, ptr);
777         if (ptr == size) /* still no end of text */
778             return TRUE;
779         /* if(ptr>0) g_debug("XML parser: got text '%s'", file->data->str); */
780         if (ptr == 0) ; /* no text */
781         else if (file->current_item == NULL) /* text at top level! */
782         {
783             g_warning("FmXmlFile: line %u: junk data in XML file ignored",
784                       file->line);
785             _update_file_ptr(file, 0);
786         }
787         else if (unescape_gstring_inplace(file->data, &file->line,
788                                           &file->pos, FALSE, error))
789         {
790             item = fm_xml_file_item_new(FM_XML_FILE_TEXT);
791             item->text = g_strndup(file->data->str, file->data->len);
792             item->comment = file->comment_pre;
793             file->comment_pre = NULL;
794             fm_xml_file_item_append_child(file->current_item, item);
795             /* FIXME: truncate ending spaces from item->text */
796         }
797         else
798             return FALSE;
799         ptr++;
800         text += ptr;
801         size -= ptr;
802         g_string_assign(file->data, "<");
803         goto _restart;
804     }
805     /* error if reached */
806 }
807 
808 /**
809  * fm_xml_file_finish_parse
810  * @file: the parser container
811  * @error: (allow-none) (out): location to save error
812  *
813  * Ends parsing of data and retrieves final status. If XML was invalid
814  * then returns %NULL and sets @error appropriately.
815  * This function can be called more than once.
816  *
817  * See also: fm_xml_file_parse_data().
818  * See also: fm_xml_file_item_get_children().
819  *
820  * Returns: (transfer container) (element-type FmXmlFileItem): contents of XML
821  *
822  * Since: 1.2.0
823  */
fm_xml_file_finish_parse(FmXmlFile * file,GError ** error)824 GList *fm_xml_file_finish_parse(FmXmlFile *file, GError **error)
825 {
826     g_return_val_if_fail(file != NULL && FM_IS_XML_FILE(file), NULL);
827     if (file->current_item)
828     {
829         if (file->current_item->tag == FM_XML_FILE_TEXT &&
830             file->current_item->parent == NULL)
831             g_warning("FmXmlFile: junk at end of XML");
832         else
833         {
834             g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
835                                 _("Document ended unexpectedly"));
836             /* FIXME: analize content of file->data to be more verbose */
837             return NULL;
838         }
839     }
840     else if (file->items == NULL)
841     {
842         g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY,
843                             _("Document was empty or contained only whitespace"));
844         return NULL;
845     }
846     /* FIXME: check if file->comment_pre is NULL */
847     return g_list_copy(file->items);
848 }
849 
850 /**
851  * fm_xml_file_get_current_line
852  * @file: the parser container
853  * @pos: (allow-none) (out): location to save line position
854  *
855  * Retrieves the line where parser has stopped.
856  *
857  * Returns: line num (starting from 1).
858  *
859  * Since: 1.2.0
860  */
fm_xml_file_get_current_line(FmXmlFile * file,gint * pos)861 gint fm_xml_file_get_current_line(FmXmlFile *file, gint *pos)
862 {
863     if (file == NULL || !FM_IS_XML_FILE(file))
864         return 0;
865     if (pos)
866         *pos = file->pos;
867     return file->line;
868 }
869 
870 /**
871  * fm_xml_file_get_dtd
872  * @file: the parser container
873  *
874  * Retrieves DTD description for XML data in the container. Returned data
875  * are owned by @file and should not be modified by caller.
876  *
877  * Returns: (transfer none): DTD description.
878  *
879  * Since: 1.2.0
880  */
fm_xml_file_get_dtd(FmXmlFile * file)881 const char *fm_xml_file_get_dtd(FmXmlFile *file)
882 {
883     if(file == NULL)
884         return NULL;
885     return file->tags[0].name;
886 }
887 
888 
889 /* item manipulations */
890 
891 /**
892  * fm_xml_file_item_new
893  * @tag: tag id for new item
894  *
895  * Creates new unattached XML item.
896  *
897  * Returns: (transfer full): newly allocated #FmXmlFileItem.
898  *
899  * Since: 1.2.0
900  */
fm_xml_file_item_new(FmXmlFileTag tag)901 FmXmlFileItem *fm_xml_file_item_new(FmXmlFileTag tag)
902 {
903     FmXmlFileItem *item = g_slice_new0(FmXmlFileItem);
904 
905     item->tag = tag;
906     return item;
907 }
908 
909 /**
910  * fm_xml_file_item_append_text
911  * @item: item to append text
912  * @text: text to append
913  * @text_size: length of text in bytes, or -1 if the text is nul-terminated
914  * @cdata: %TRUE if @text should be saved as CDATA array
915  *
916  * Appends @text after last element contained in @item.
917  *
918  * Since: 1.2.0
919  */
fm_xml_file_item_append_text(FmXmlFileItem * item,const char * text,gssize text_size,gboolean cdata)920 void fm_xml_file_item_append_text(FmXmlFileItem *item, const char *text,
921                                   gssize text_size, gboolean cdata)
922 {
923     FmXmlFileItem *text_item;
924 
925     g_return_if_fail(item != NULL);
926     if (text == NULL || text_size == 0)
927         return;
928     text_item = fm_xml_file_item_new(FM_XML_FILE_TEXT);
929     if (text_size > 0)
930         text_item->text = g_strndup(text, text_size);
931     else
932         text_item->text = g_strdup(text);
933     if (cdata)
934         text_item->comment = text_item->text;
935     fm_xml_file_item_append_child(item, text_item);
936 }
937 
_xml_item_is_busy(FmXmlFileItem * item)938 static inline gboolean _xml_item_is_busy(FmXmlFileItem *item)
939 {
940     register FmXmlFileItem *test;
941 
942     if (item->file)
943         for (test = item->file->current_item; test; test = test->parent)
944             if (test == item)
945             {
946                 /* g_debug("*** item %p is busy", item); */
947                 return TRUE;
948             }
949     return FALSE;
950 }
951 
_reassign_xml_file(FmXmlFileItem * item,FmXmlFile * file)952 static void _reassign_xml_file(FmXmlFileItem *item, FmXmlFile *file)
953 {
954     GList *chl;
955 
956     /* do it recursively */
957     for (chl = item->children; chl; chl = chl->next)
958         _reassign_xml_file(chl->data, file);
959     item->file = file;
960 }
961 
962 /**
963  * fm_xml_file_item_append_child
964  * @item: item to append child
965  * @child: the child item to append
966  *
967  * Appends @child after last element contained in @item. If the @child
968  * already was in the XML structure then it will be moved to the new
969  * place instead.
970  * Behavior after moving between different containers is undefined.
971  *
972  * Returns: %FALSE if @child is busy thus cannot be moved.
973  *
974  * Since: 1.2.0
975  */
fm_xml_file_item_append_child(FmXmlFileItem * item,FmXmlFileItem * child)976 gboolean fm_xml_file_item_append_child(FmXmlFileItem *item, FmXmlFileItem *child)
977 {
978     g_return_val_if_fail(item != NULL && child != NULL, FALSE);
979     if (_xml_item_is_busy(child))
980         return FALSE; /* cannot move item right now */
981     if (child->parent_list) /* remove from old list */
982     {
983         /* g_debug("moving item %p(%d) from parser %p into %p as child of %p", child, (int)child->tag, child->file, item->file, item); */
984         g_assert(child->file != NULL && g_list_find(*child->parent_list, child) != NULL);
985         *child->parent_list = g_list_remove(*child->parent_list, child);
986     }
987     /* else
988         g_debug("adding item %p(%d) into parser %p as child of %p", child, (int)child->tag, item->file, item); */
989     item->children = g_list_append(item->children, child);
990     child->parent_list = &item->children;
991     child->parent = item;
992     if (child->file != item->file)
993         _reassign_xml_file(child, item->file);
994     return TRUE;
995 }
996 
997 /**
998  * fm_xml_file_item_set_comment
999  * @item: element to set
1000  * @comment: (allow-none): new comment
1001  *
1002  * Changes comment that is prepended to @item.
1003  *
1004  * Since: 1.2.0
1005  */
fm_xml_file_item_set_comment(FmXmlFileItem * item,const char * comment)1006 void fm_xml_file_item_set_comment(FmXmlFileItem *item, const char *comment)
1007 {
1008     g_return_if_fail(item != NULL);
1009     g_free(item->comment);
1010     item->comment = g_strdup(comment);
1011 }
1012 
1013 /**
1014  * fm_xml_file_item_set_attribute
1015  * @item: element to update
1016  * @name: attribute name
1017  * @value: (allow-none): attribute data
1018  *
1019  * Changes data for the attribute of some @item with new @value. If such
1020  * attribute wasn't set then adds it for the @item. If @value is %NULL
1021  * then the attribute will be unset from the @item.
1022  *
1023  * Returns: %TRUE if attribute was set successfully.
1024  *
1025  * Since: 1.2.0
1026  */
fm_xml_file_item_set_attribute(FmXmlFileItem * item,const char * name,const char * value)1027 gboolean fm_xml_file_item_set_attribute(FmXmlFileItem *item,
1028                                         const char *name, const char *value)
1029 {
1030     int i, n_attr;
1031 
1032     g_return_val_if_fail(item != NULL, FALSE);
1033     g_return_val_if_fail(name != NULL, FALSE);
1034     if (item->attribute_names == NULL && value == NULL)
1035         return TRUE;
1036     if (item->attribute_names == NULL)
1037     {
1038         item->attribute_names = g_new(char *, 2);
1039         item->attribute_values = g_new(char *, 2);
1040         item->attribute_names[0] = g_strdup(name);
1041         item->attribute_values[0] = g_strdup(value);
1042         item->attribute_names[1] = NULL;
1043         item->attribute_values[1] = NULL;
1044         return TRUE;
1045     }
1046     for (i = -1, n_attr = 0; item->attribute_names[n_attr] != NULL; n_attr++)
1047         if (strcmp(item->attribute_names[n_attr], name) == 0)
1048             i = n_attr;
1049     if (i < 0)
1050     {
1051         if (value == NULL) /* already unset */
1052             return TRUE;
1053         item->attribute_names = g_renew(char *, item->attribute_names, n_attr + 2);
1054         item->attribute_values = g_renew(char *, item->attribute_values, n_attr + 2);
1055         item->attribute_names[n_attr] = g_strdup(name);
1056         item->attribute_values[n_attr] = g_strdup(value);
1057         item->attribute_names[n_attr+1] = NULL;
1058         item->attribute_values[n_attr+1] = NULL;
1059     }
1060     else if (value != NULL) /* value changed */
1061     {
1062         g_free(item->attribute_values[i]);
1063         item->attribute_values[i] = g_strdup(value);
1064     }
1065     else if (n_attr == 1) /* no more attributes left */
1066     {
1067         g_strfreev(item->attribute_names);
1068         g_strfreev(item->attribute_values);
1069         item->attribute_names = NULL;
1070         item->attribute_values = NULL;
1071     }
1072     else /* replace removed attribute with last one if it wasn't last */
1073     {
1074         g_free(item->attribute_names[i]);
1075         g_free(item->attribute_values[i]);
1076         if (i < n_attr - 1)
1077         {
1078             item->attribute_names[i] = item->attribute_names[n_attr-1];
1079             item->attribute_values[i] = item->attribute_values[n_attr-1];
1080         }
1081         item->attribute_names[n_attr-1] = NULL;
1082         item->attribute_values[n_attr-1] = NULL;
1083     }
1084     return TRUE;
1085 }
1086 
1087 /**
1088  * fm_xml_file_item_destroy
1089  * @item: element to destroy
1090  *
1091  * Removes element and its children from its parent, and frees all
1092  * data.
1093  *
1094  * Returns: %FALSE if @item is busy thus cannot be destroyed.
1095  *
1096  * Since: 1.2.0
1097  */
fm_xml_file_item_destroy(FmXmlFileItem * item)1098 gboolean fm_xml_file_item_destroy(FmXmlFileItem *item)
1099 {
1100     g_return_val_if_fail(item != NULL, FALSE);
1101     if (_xml_item_is_busy(item))
1102         return FALSE;
1103     while (item->children)
1104     {
1105         g_assert(((FmXmlFileItem*)item->children->data)->file == item->file);
1106         g_assert(((FmXmlFileItem*)item->children->data)->parent == item);
1107         fm_xml_file_item_destroy(item->children->data);
1108     }
1109     if (item->parent_list)
1110     {
1111         /* g_debug("removing item %p from parser %p", item, item->file); */
1112         g_assert(item->file != NULL && g_list_find(*item->parent_list, item) != NULL);
1113         *item->parent_list = g_list_remove(*item->parent_list, item);
1114     }
1115     if (item->text != item->comment)
1116         g_free(item->comment);
1117     g_free(item->text);
1118     g_strfreev(item->attribute_names);
1119     g_strfreev(item->attribute_values);
1120     g_slice_free(FmXmlFileItem, item);
1121     return TRUE;
1122 }
1123 
1124 /**
1125  * fm_xml_file_insert_before
1126  * @item: item to insert before it
1127  * @new_item: new item to insert
1128  *
1129  * Inserts @new_item before @item that is already in XML structure. If
1130  * @new_item is already in the XML structure then it will be moved to
1131  * the new place instead.
1132  * Behavior after moving between defferent containers is undefined.
1133  *
1134  * Returns: %TRUE in case of success.
1135  *
1136  * Since: 1.2.0
1137  */
fm_xml_file_insert_before(FmXmlFileItem * item,FmXmlFileItem * new_item)1138 gboolean fm_xml_file_insert_before(FmXmlFileItem *item, FmXmlFileItem *new_item)
1139 {
1140     GList *sibling;
1141 
1142     g_return_val_if_fail(item != NULL && new_item != NULL, FALSE);
1143     sibling = g_list_find(*item->parent_list, item);
1144     if (sibling == NULL) /* no such item found */
1145     {
1146         /* g_critical("item %p not found in %p", item, item->parent); */
1147         return FALSE;
1148     }
1149     if (_xml_item_is_busy(new_item))
1150         return FALSE; /* cannot move item right now */
1151     if (new_item->parent_list) /* remove from old list */
1152     {
1153         /* g_debug("moving item %p (parent=%p) from parser %p into %p", new_item, item, new_item->file, item->file); */
1154         g_assert(new_item->file != NULL && g_list_find(*new_item->parent_list, new_item) != NULL);
1155         *new_item->parent_list = g_list_remove(*new_item->parent_list, new_item);
1156     }
1157     /* else
1158         g_debug("inserting item %p (parent=%p) into parser %p", item, new_item, item->file); */
1159     *item->parent_list = g_list_insert_before(*item->parent_list, sibling, new_item);
1160     new_item->parent_list = item->parent_list;
1161     new_item->parent = item->parent;
1162     if (new_item->file != item->file)
1163         _reassign_xml_file(new_item, item->file);
1164     return TRUE;
1165 }
1166 
1167 /**
1168  * fm_xml_file_insert_first
1169  * @file: the parser container
1170  * @new_item: new item to insert
1171  *
1172  * Inserts @new_item as very first element of XML data in container.
1173  *
1174  * Returns: %TRUE in case of success.
1175  *
1176  * Since: 1.2.0
1177  */
fm_xml_file_insert_first(FmXmlFile * file,FmXmlFileItem * new_item)1178 gboolean fm_xml_file_insert_first(FmXmlFile *file, FmXmlFileItem *new_item)
1179 {
1180     g_return_val_if_fail(file != NULL && FM_IS_XML_FILE(file), FALSE);
1181     g_return_val_if_fail(new_item != NULL, FALSE);
1182     if (_xml_item_is_busy(new_item))
1183         return FALSE; /* cannot move item right now */
1184     if (new_item->parent_list)
1185     {
1186         /* g_debug("moving item %p from parser %p into %p", new_item, new_item->file, file); */
1187         g_assert(new_item->file != NULL && g_list_find(*new_item->parent_list, new_item) != NULL);
1188         *new_item->parent_list = g_list_remove(*new_item->parent_list, new_item);
1189     }
1190     file->items = g_list_prepend(file->items, new_item);
1191     new_item->parent_list = &file->items;
1192     new_item->parent = NULL;
1193     if (new_item->file != file)
1194         _reassign_xml_file(new_item, file);
1195     return TRUE;
1196 }
1197 
1198 
1199 /* save XML */
1200 
1201 /**
1202  * fm_xml_file_set_dtd
1203  * @file: the parser container
1204  * @dtd: DTD description for XML data
1205  * @error: (allow-none) (out): location to save error
1206  *
1207  * Changes DTD description for XML data in the container.
1208  *
1209  * Since: 1.2.0
1210  */
fm_xml_file_set_dtd(FmXmlFile * file,const char * dtd,GError ** error)1211 void fm_xml_file_set_dtd(FmXmlFile *file, const char *dtd, GError **error)
1212 {
1213     if(file == NULL)
1214         return;
1215     /* FIXME: validate dtd */
1216     g_free(file->tags[0].name);
1217     file->tags[0].name = g_strdup(dtd);
1218 }
1219 
_parser_item_to_gstring(FmXmlFile * file,GString * string,FmXmlFileItem * item,GString * prefix,gboolean * has_nl,GError ** error)1220 static gboolean _parser_item_to_gstring(FmXmlFile *file, GString *string,
1221                                         FmXmlFileItem *item, GString *prefix,
1222                                         gboolean *has_nl, GError **error)
1223 {
1224     const char *tag_name;
1225     GList *l;
1226 
1227     /* open the tag */
1228     switch (item->tag)
1229     {
1230     case FM_XML_FILE_TAG_NOT_HANDLED:
1231         if (item->tag_name == NULL)
1232             goto _no_tag;
1233         tag_name = item->tag_name;
1234         goto _do_tag;
1235     case FM_XML_FILE_TEXT:
1236         if (item->text == item->comment) /* CDATA */
1237             g_string_append_printf(string, "<![CDATA[%s]]>", item->text);
1238         else if (item->text) /* just text */
1239         {
1240             char *escaped;
1241 
1242             if (item->comment != NULL)
1243                 g_string_append_printf(string, "<!-- %s -->", item->comment);
1244             escaped = g_markup_escape_text(item->text, -1);
1245             g_string_append(string, escaped);
1246             g_free(escaped);
1247         }
1248         else /* processing directive */
1249         {
1250             g_string_append_printf(string, "%s<?%s?>", prefix->str, item->comment);
1251             *has_nl = TRUE;
1252         }
1253         return TRUE;
1254     default:
1255         if (item->tag >= file->n_tags)
1256         {
1257 _no_tag:
1258             g_set_error_literal(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
1259                                 _("fm_xml_file_to_data: XML data error"));
1260             return FALSE;
1261         }
1262         tag_name = file->tags[item->tag].name;
1263 _do_tag:
1264         /* do comment */
1265         if (item->comment != NULL)
1266             g_string_append_printf(string, "%s<!-- %s -->", prefix->str,
1267                                    item->comment);
1268         else if (item->attribute_names == NULL && item->children == NULL &&
1269                  file->tags[item->tag].in_line)
1270         {
1271             /* don't add prefix if it is simple tag such as <br/> */
1272             g_string_append_printf(string, "<%s/>", tag_name);
1273             return TRUE;
1274         }
1275         /* start the tag */
1276         g_string_append_printf(string, "%s<%s", prefix->str, tag_name);
1277         /* do attributes */
1278         if (item->attribute_names)
1279         {
1280             char **name = item->attribute_names;
1281             char **value = item->attribute_values;
1282             while (*name)
1283             {
1284                 if (*value)
1285                 {
1286                     char *escaped = g_markup_escape_text(*value, -1);
1287                     g_string_append_printf(string, " %s='%s'", *name, escaped);
1288                     g_free(escaped);
1289                 } /* else error? */
1290                 name++;
1291                 value++;
1292             }
1293         }
1294         if (item->children == NULL)
1295         {
1296             /* handle empty tags such as <tag attr='value'/> */
1297             g_string_append(string, "/>");
1298             *has_nl = TRUE;
1299             return TRUE;
1300         }
1301         g_string_append_c(string, '>');
1302     }
1303     /* do with children */
1304     *has_nl = FALSE; /* to collect data from nested elements */
1305     g_string_append(prefix, "    ");
1306     for (l = item->children; l; l = l->next)
1307         if (!_parser_item_to_gstring(file, string, l->data, prefix, has_nl, error))
1308             break;
1309     g_string_truncate(prefix, prefix->len - 4);
1310     if (l != NULL) /* failed */
1311         return FALSE;
1312     /* close the tag */
1313     g_string_append_printf(string, "%s</%s>", (*has_nl) ? prefix->str : "",
1314                            tag_name);
1315     *has_nl = TRUE; /* it was prefixed above */
1316     return TRUE;
1317 }
1318 
1319 /**
1320  * fm_xml_file_to_data
1321  * @file: the parser container
1322  * @text_size: (allow-none) (out): location to save size of returned data
1323  * @error: (allow-none) (out): location to save error
1324  *
1325  * Prepares string representation (XML text) for the data that are in
1326  * the container. Returned data should be freed with g_free() after
1327  * usage.
1328  *
1329  * Returns: (transfer full): XML text representing data in @file.
1330  *
1331  * Since: 1.2.0
1332  */
fm_xml_file_to_data(FmXmlFile * file,gsize * text_size,GError ** error)1333 char *fm_xml_file_to_data(FmXmlFile *file, gsize *text_size, GError **error)
1334 {
1335     GString *string, *prefix;
1336     GList *l;
1337     gboolean has_nl = FALSE;
1338 
1339     g_return_val_if_fail(file != NULL && FM_IS_XML_FILE(file), NULL);
1340     string = g_string_sized_new(512);
1341     prefix = g_string_new("\n");
1342     if (G_LIKELY(file->tags[0].name))
1343         g_string_printf(string, "<!DOCTYPE %s>", file->tags[0].name);
1344     for (l = file->items; l; l = l->next)
1345         if (!_parser_item_to_gstring(file, string, l->data, prefix, &has_nl, error))
1346             break; /* if failed then l != NULL */
1347     g_string_free(prefix, TRUE);
1348     if (text_size)
1349         *text_size = string->len;
1350     return g_string_free(string, (l != NULL)); /* returns NULL if failed */
1351 }
1352 
1353 
1354 /* item data accessor functions */
1355 
1356 /**
1357  * fm_xml_file_item_get_comment
1358  * @item: the file element to inspect
1359  *
1360  * If an element @item has a comment ahead of it then retrieves that
1361  * comment. The returned data are owned by @item and should not be freed
1362  * nor otherwise altered by caller.
1363  *
1364  * Returns: (transfer none): comment or %NULL if no comment is set.
1365  *
1366  * Since: 1.2.0
1367  */
fm_xml_file_item_get_comment(FmXmlFileItem * item)1368 const char *fm_xml_file_item_get_comment(FmXmlFileItem *item)
1369 {
1370     g_return_val_if_fail(item != NULL, NULL);
1371     return item->comment;
1372 }
1373 
1374 /**
1375  * fm_xml_file_item_get_children
1376  * @item: the file element to inspect
1377  *
1378  * Retrieves list of children for @item that are known to the parser.
1379  * Returned list should be freed by g_list_free() after usage.
1380  *
1381  * Note: any text between opening tag and closing tag such as
1382  * |[ &lt;Tag&gt;Some text&lt;/Tag&gt; ]|
1383  * is presented as a child of special type #FM_XML_FILE_TEXT and can be
1384  * retrieved from that child, not from the item representing &lt;Tag&gt;.
1385  *
1386  * See also: fm_xml_file_item_get_data().
1387  *
1388  * Returns: (transfer container) (element-type FmXmlFileItem): children list.
1389  *
1390  * Since: 1.2.0
1391  */
fm_xml_file_item_get_children(FmXmlFileItem * item)1392 GList *fm_xml_file_item_get_children(FmXmlFileItem *item)
1393 {
1394     g_return_val_if_fail(item != NULL, NULL);
1395     return g_list_copy(item->children);
1396 }
1397 
1398 /**
1399  * fm_xml_file_item_find_child
1400  * @item: the file element to inspect
1401  * @tag: tag id to search among children
1402  *
1403  * Searches for first child of @item which have child with tag id @tag.
1404  * Returned data are owned by file and should not be freed by caller.
1405  *
1406  * Returns: (transfer none): found child or %NULL if no such child was found.
1407  *
1408  * Since: 1.2.0
1409  */
fm_xml_file_item_find_child(FmXmlFileItem * item,FmXmlFileTag tag)1410 FmXmlFileItem *fm_xml_file_item_find_child(FmXmlFileItem *item, FmXmlFileTag tag)
1411 {
1412     GList *l;
1413 
1414     for (l = item->children; l; l = l->next)
1415         if (((FmXmlFileItem*)l->data)->tag == tag)
1416             return (FmXmlFileItem*)l->data;
1417     return NULL;
1418 }
1419 
1420 /**
1421  * fm_xml_file_item_get_tag
1422  * @item: the file element to inspect
1423  *
1424  * Retrieves tag id of @item.
1425  *
1426  * Returns: tag id.
1427  *
1428  * Since: 1.2.0
1429  */
fm_xml_file_item_get_tag(FmXmlFileItem * item)1430 FmXmlFileTag fm_xml_file_item_get_tag(FmXmlFileItem *item)
1431 {
1432     g_return_val_if_fail(item != NULL, FM_XML_FILE_TAG_NOT_HANDLED);
1433     return item->tag;
1434 }
1435 
1436 /**
1437  * fm_xml_file_item_get_data
1438  * @item: the file element to inspect
1439  * @text_size: (allow-none) (out): location to save data size
1440  *
1441  * Retrieves text data from @item of type #FM_XML_FILE_TEXT. Returned
1442  * data are owned by file and should not be freed nor altered.
1443  *
1444  * Returns: (transfer none): text data or %NULL if @item isn't text data.
1445  *
1446  * Since: 1.2.0
1447  */
fm_xml_file_item_get_data(FmXmlFileItem * item,gsize * text_size)1448 const char *fm_xml_file_item_get_data(FmXmlFileItem *item, gsize *text_size)
1449 {
1450     if (text_size)
1451         *text_size = 0;
1452     g_return_val_if_fail(item != NULL, NULL);
1453     if (item->tag != FM_XML_FILE_TEXT)
1454         return NULL;
1455     if (text_size && item->text != NULL)
1456         *text_size = strlen(item->text);
1457     return item->text;
1458 }
1459 
1460 /**
1461  * fm_xml_file_item_get_parent
1462  * @item: the file element to inspect
1463  *
1464  * Retrieves parent element of @item if the @item has one. Returned data
1465  * are owned by file and should not be freed by caller.
1466  *
1467  * Returns: (transfer none): parent element or %NULL if element has no parent.
1468  *
1469  * Since: 1.2.0
1470  */
fm_xml_file_item_get_parent(FmXmlFileItem * item)1471 FmXmlFileItem *fm_xml_file_item_get_parent(FmXmlFileItem *item)
1472 {
1473     g_return_val_if_fail(item != NULL, NULL);
1474     return item->parent;
1475 }
1476 
1477 /**
1478  * fm_xml_file_get_tag_name
1479  * @file: the parser container
1480  * @tag: the tag id to inspect
1481  *
1482  * Retrieves tag for its id. Returned data are owned by @file and should
1483  * not be modified by caller.
1484  *
1485  * Returns: (transfer none): tag string representation.
1486  *
1487  * Since: 1.2.0
1488  */
fm_xml_file_get_tag_name(FmXmlFile * file,FmXmlFileTag tag)1489 const char *fm_xml_file_get_tag_name(FmXmlFile *file, FmXmlFileTag tag)
1490 {
1491     g_return_val_if_fail(file != NULL && FM_IS_XML_FILE(file), NULL);
1492     g_return_val_if_fail(tag > 0 && tag < file->n_tags, NULL);
1493     return file->tags[tag].name;
1494 }
1495 
1496 /**
1497  * fm_xml_file_item_get_tag_name
1498  * @item: the file element to inspect
1499  *
1500  * Retrieves tag for its id. Returned data are owned by @item and should
1501  * not be modified by caller.
1502  *
1503  * Returns: (transfer none): tag string representation or %NULL if @item is text.
1504  *
1505  * Since: 1.2.0
1506  */
fm_xml_file_item_get_tag_name(FmXmlFileItem * item)1507 const char *fm_xml_file_item_get_tag_name(FmXmlFileItem *item)
1508 {
1509     g_return_val_if_fail(item != NULL, NULL);
1510     switch (item->tag)
1511     {
1512     case FM_XML_FILE_TEXT:
1513         return NULL;
1514     case FM_XML_FILE_TAG_NOT_HANDLED:
1515         return item->tag_name;
1516     default:
1517         return item->file->tags[item->tag].name;
1518     }
1519 }
1520