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; &lt; &gt; &quot; &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 < etc. this is obvious, for  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 "(ê 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 &"));
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: & " < > '"));
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 &"));
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 * |[ <Tag>Some text</Tag> ]|
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 <Tag>.
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