talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

xml.c (16030B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
     17  *
     18  */
     19 
     20 #include <glib.h>
     21 #include <stdio.h>
     22 #include <string.h>
     23 #include <ctype.h>
     24 
     25 #include "xml.h"
     26 #include "utils.h"
     27 #include "codeconv.h"
     28 #include "file-utils.h"
     29 
     30 #define SPARSE_MEMORY
     31 /* if this is defined all attr.names and tag.names are stored
     32  * in a hash table */
     33 #if defined(SPARSE_MEMORY)
     34 #include "stringtable.h"
     35 
     36 static StringTable *xml_string_table;
     37 static XMLTag  *xml_copy_tag		(XMLTag		*tag);
     38 static XMLAttr *xml_copy_attr		(XMLAttr	*attr);
     39 static void xml_free_node		(XMLNode	*node);
     40 static void xml_free_tag		(XMLTag 	*tag);
     41 static void xml_pop_tag		(XMLFile	*file);
     42 static void xml_push_tag		(XMLFile	*file,
     43 				 XMLTag		*tag);
     44 static gint xml_read_line		(XMLFile	*file);
     45 static void xml_truncate_buf		(XMLFile	*file);
     46 static gint xml_unescape_str		(gchar		*str);
     47 
     48 static void xml_string_table_create(void)
     49 {
     50 	if (xml_string_table == NULL)
     51 		xml_string_table = string_table_new();
     52 }
     53 #define XML_STRING_ADD(str) \
     54 	string_table_insert_string(xml_string_table, (str))
     55 #define XML_STRING_FREE(str) \
     56 	string_table_free_string(xml_string_table, (str))
     57 
     58 #define XML_STRING_TABLE_CREATE() \
     59 	xml_string_table_create()
     60 
     61 #else /* !SPARSE_MEMORY */
     62 
     63 #define XML_STRING_ADD(str) \
     64 	g_strdup(str)
     65 #define XML_STRING_FREE(str) \
     66 	g_free(str)
     67 
     68 #define XML_STRING_TABLE_CREATE()
     69 
     70 #endif /* SPARSE_MEMORY */
     71 
     72 static gint xml_get_parenthesis	(XMLFile	*file,
     73 				 gchar		*buf,
     74 				 gint		 len);
     75 
     76 XMLFile *xml_open_file(const gchar *path)
     77 {
     78 	XMLFile *newfile;
     79 
     80 	cm_return_val_if_fail(path != NULL, NULL);
     81 
     82 	newfile = g_new(XMLFile, 1);
     83 
     84 	newfile->fp = g_fopen(path, "rb");
     85 	if (!newfile->fp) {
     86 		FILE_OP_ERROR(path, "fopen");
     87 		g_free(newfile);
     88 		return NULL;
     89 	}
     90 
     91 	XML_STRING_TABLE_CREATE();
     92 
     93 	newfile->buf = g_string_new(NULL);
     94 	newfile->bufp = newfile->buf->str;
     95 
     96 	newfile->dtd = NULL;
     97 	newfile->encoding = NULL;
     98 	newfile->tag_stack = NULL;
     99 	newfile->level = 0;
    100 	newfile->is_empty_element = FALSE;
    101 
    102 	newfile->path = g_strdup(path);
    103 
    104 	return newfile;
    105 }
    106 
    107 void xml_close_file(XMLFile *file)
    108 {
    109 	cm_return_if_fail(file != NULL);
    110 
    111 	if (file->fp) fclose(file->fp);
    112 
    113 	g_string_free(file->buf, TRUE);
    114 
    115 	g_free(file->dtd);
    116 	g_free(file->encoding);
    117 	g_free(file->path);
    118 
    119 	while (file->tag_stack != NULL)
    120 		xml_pop_tag(file);
    121 
    122 	g_free(file);
    123 }
    124 
    125 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
    126 {
    127 	GNode *node = NULL;
    128 	XMLNode *xmlnode;
    129 	XMLTag *tag;
    130 
    131 	while (xml_parse_next_tag(file) == 0) {
    132 		if (file->level < level) break;
    133 		if (file->level == level) {
    134 			g_warning("xml_build_tree(): parse error in %s", file->path);
    135 			break;
    136 		}
    137 
    138 		tag = xml_get_current_tag(file);
    139 		if (!tag) break;
    140 		xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
    141 		xmlnode->element = xml_get_element(file);
    142 		if (!parent)
    143 			node = g_node_new(xmlnode);
    144 		else
    145 			node = g_node_append_data(parent, xmlnode);
    146 
    147 		xml_build_tree(file, node, file->level);
    148 		if (file->level == 0) break;
    149 	}
    150 
    151 	return node;
    152 }
    153 
    154 GNode *xml_parse_file(const gchar *path)
    155 {
    156 	XMLFile *file;
    157 	GNode *node;
    158 
    159 	file = xml_open_file(path);
    160 	if (file == NULL)
    161 		return NULL;
    162 
    163 	xml_get_dtd(file);
    164 
    165 	node = xml_build_tree(file, NULL, file->level);
    166 
    167 	xml_close_file(file);
    168 
    169 #if defined(SPARSE_MEMORY)
    170 	if (debug_get_mode())
    171 		string_table_get_stats(xml_string_table);
    172 #endif
    173 
    174 	return node;
    175 }
    176 
    177 gint xml_get_dtd(XMLFile *file)
    178 {
    179 	gchar buf[XMLBUFSIZE];
    180 	gchar *bufp = buf;
    181 
    182 	if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
    183 
    184 	if ((*bufp++ == '?') &&
    185 	    (bufp = strcasestr(bufp, "xml")) &&
    186 	    (bufp = strcasestr(bufp + 3, "version")) &&
    187 	    (bufp = strchr(bufp + 7, '?'))) {
    188 		file->dtd = g_strdup(buf);
    189 		if ((bufp = strcasestr(buf, "encoding=\""))) {
    190 			bufp += 9;
    191 			extract_quote(bufp, '"');
    192 			file->encoding = g_strdup(bufp);
    193 			file->need_codeconv =
    194 				g_strcmp0(bufp, CS_INTERNAL);
    195 		} else {
    196 			file->encoding = g_strdup(CS_INTERNAL);
    197 			file->need_codeconv = FALSE;
    198 		}
    199 	} else {
    200 		g_warning("can't get XML DTD in %s", file->path);
    201 		return -1;
    202 	}
    203 
    204 	return 0;
    205 }
    206 
    207 gint xml_parse_next_tag(XMLFile *file)
    208 {
    209 	gchar buf[XMLBUFSIZE];
    210 	gchar *bufp = buf;
    211 	gchar *tag_str;
    212 	XMLTag *tag;
    213 	gint len;
    214 
    215 next:
    216 	if (file->is_empty_element == TRUE) {
    217 		file->is_empty_element = FALSE;
    218 		xml_pop_tag(file);
    219 		return 0;
    220 	}
    221 
    222 	if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
    223 		g_warning("xml_parse_next_tag(): can't parse next tag  in %s", file->path);
    224 		return -1;
    225 	}
    226 
    227 	len = strlen(buf);
    228 
    229 	/* end-tag */
    230 	if (buf[0] == '/') {
    231 		if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
    232 			g_warning("xml_parse_next_tag(): tag name mismatch in %s : %s (%s)", file->path, buf, xml_get_current_tag(file)->tag);
    233 			return -1;
    234 		}
    235 		xml_pop_tag(file);
    236 		return 0;
    237 	}
    238 
    239 	if (len >= 7 && !strncmp(buf, "!-- ", 4) && !strncmp(buf+len-3, " --", 3)) {
    240 		/* skip comment */
    241 		goto next;
    242 	}
    243 
    244 	tag = xml_tag_new(NULL);
    245 	xml_push_tag(file, tag);
    246 
    247 	if (len > 0 && buf[len - 1] == '/') {
    248 		file->is_empty_element = TRUE;
    249 		buf[len - 1] = '\0';
    250 		g_strchomp(buf);
    251 	}
    252 
    253 	if (strlen(buf) == 0) {
    254 		g_warning("xml_parse_next_tag(): tag name is empty in %s", file->path);
    255 		return -1;
    256 	}
    257 
    258 	while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++;
    259 	if (*bufp == '\0') {
    260 		if (file->need_codeconv) {
    261 			tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
    262 			if (tag_str) {
    263 				tag->tag = XML_STRING_ADD(tag_str);
    264 				g_free(tag_str);
    265 			} else
    266 				tag->tag = XML_STRING_ADD(buf);
    267 		} else
    268 			tag->tag = XML_STRING_ADD(buf);
    269 		return 0;
    270 	} else {
    271 		*bufp++ = '\0';
    272 		if (file->need_codeconv) {
    273 			tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
    274 			if (tag_str) {
    275 				tag->tag = XML_STRING_ADD(tag_str);
    276 				g_free(tag_str);
    277 			} else
    278 				tag->tag = XML_STRING_ADD(buf);
    279 		} else
    280 			tag->tag = XML_STRING_ADD(buf);
    281 	}
    282 
    283 	/* parse attributes ( name=value ) */
    284 	while (*bufp) {
    285 		XMLAttr *attr;
    286 		gchar *attr_name;
    287 		gchar *attr_value;
    288 		gchar *utf8_attr_name;
    289 		gchar *utf8_attr_value;
    290 		gchar *p;
    291 		gchar quote;
    292 
    293 		while (g_ascii_isspace(*bufp)) bufp++;
    294 		attr_name = bufp;
    295 		if ((p = strchr(attr_name, '=')) == NULL) {
    296 			g_warning("xml_parse_next_tag(): syntax error in %s, tag (a) %s", file->path, attr_name);
    297 			return -1;
    298 		}
    299 		bufp = p;
    300 		*bufp++ = '\0';
    301 		while (g_ascii_isspace(*bufp)) bufp++;
    302 
    303 		if (*bufp != '"' && *bufp != '\'') {
    304 			g_warning("xml_parse_next_tag(): syntax error in %s, tag (b) %s", file->path, bufp);
    305 			return -1;
    306 		}
    307 		quote = *bufp;
    308 		bufp++;
    309 		attr_value = bufp;
    310 		if ((p = strchr(attr_value, quote)) == NULL) {
    311 			g_warning("xml_parse_next_tag(): syntax error in %s, tag (c) %s", file->path, attr_value);
    312 			return -1;
    313 		}
    314 		bufp = p;
    315 		*bufp++ = '\0';
    316 
    317 		g_strchomp(attr_name);
    318 		xml_unescape_str(attr_value);
    319 		if (file->need_codeconv) {
    320 			utf8_attr_name = conv_codeset_strdup
    321 				(attr_name, file->encoding, CS_INTERNAL);
    322 			utf8_attr_value = conv_codeset_strdup
    323 				(attr_value, file->encoding, CS_INTERNAL);
    324 			if (!utf8_attr_name)
    325 				utf8_attr_name = g_strdup(attr_name);
    326 			if (!utf8_attr_value)
    327 				utf8_attr_value = g_strdup(attr_value);
    328 
    329 			attr = xml_attr_new(utf8_attr_name, utf8_attr_value);
    330 			g_free(utf8_attr_value);
    331 			g_free(utf8_attr_name);
    332 		} else {
    333 			attr = xml_attr_new(attr_name, attr_value);
    334 		}
    335 		xml_tag_add_attr(tag, attr);
    336 
    337 	}
    338 	tag->attr = g_list_reverse(tag->attr);
    339 
    340 	return 0;
    341 }
    342 
    343 static void xml_push_tag(XMLFile *file, XMLTag *tag)
    344 {
    345 	cm_return_if_fail(tag != NULL);
    346 
    347 	file->tag_stack = g_list_prepend(file->tag_stack, tag);
    348 	file->level++;
    349 }
    350 
    351 static void xml_pop_tag(XMLFile *file)
    352 {
    353 	XMLTag *tag;
    354 
    355 	if (!file->tag_stack) return;
    356 
    357 	tag = (XMLTag *)file->tag_stack->data;
    358 
    359 	file->tag_stack = g_list_remove(file->tag_stack, tag);
    360 	xml_free_tag(tag);
    361 	file->level--;
    362 }
    363 
    364 XMLTag *xml_get_current_tag(XMLFile *file)
    365 {
    366 	if (file->tag_stack)
    367 		return (XMLTag *)file->tag_stack->data;
    368 	else
    369 		return NULL;
    370 }
    371 
    372 GList *xml_get_current_tag_attr(XMLFile *file)
    373 {
    374 	XMLTag *tag;
    375 
    376 	tag = xml_get_current_tag(file);
    377 	if (!tag) return NULL;
    378 
    379 	return tag->attr;
    380 }
    381 
    382 gchar *xml_get_element(XMLFile *file)
    383 {
    384 	gchar *str;
    385 	gchar *new_str;
    386 	gchar *end;
    387 
    388 	while ((end = strchr(file->bufp, '<')) == NULL)
    389 		if (xml_read_line(file) < 0) return NULL;
    390 
    391 	if (end == file->bufp)
    392 		return NULL;
    393 
    394 	str = g_strndup(file->bufp, end - file->bufp);
    395 	/* this is not XML1.0 strict */
    396 	g_strstrip(str);
    397 	xml_unescape_str(str);
    398 
    399 	file->bufp = end;
    400 	xml_truncate_buf(file);
    401 
    402 	if (str[0] == '\0') {
    403 		g_free(str);
    404 		return NULL;
    405 	}
    406 
    407 	if (!file->need_codeconv)
    408 		return str;
    409 
    410 	new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL);
    411 	if (!new_str)
    412 		new_str = g_strdup(str);
    413 	g_free(str);
    414 
    415 	return new_str;
    416 }
    417 
    418 static gint xml_read_line(XMLFile *file)
    419 {
    420 	gchar buf[XMLBUFSIZE];
    421 	gint index;
    422 
    423 	if (fgets(buf, sizeof(buf), file->fp) == NULL)
    424 		return -1;
    425 
    426 	index = file->bufp - file->buf->str;
    427 
    428 	g_string_append(file->buf, buf);
    429 
    430 	file->bufp = file->buf->str + index;
    431 
    432 	return 0;
    433 }
    434 
    435 static void xml_truncate_buf(XMLFile *file)
    436 {
    437 	gint len;
    438 
    439 	len = file->bufp - file->buf->str;
    440 	if (len > 0) {
    441 		g_string_erase(file->buf, 0, len);
    442 		file->bufp = file->buf->str;
    443 	}
    444 }
    445 
    446 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
    447 {
    448 	XMLTag *tag;
    449 
    450 	tag = xml_get_current_tag(file);
    451 
    452 	if (tag && strcmp(tag->tag, name) == 0)
    453 		return TRUE;
    454 	else
    455 		return FALSE;
    456 }
    457 
    458 XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
    459 {
    460 	XMLNode *node;
    461 
    462 	node = g_new(XMLNode, 1);
    463 	node->tag = tag;
    464 	node->element = g_strdup(text);
    465 
    466         return node;
    467 }
    468 
    469 XMLTag *xml_tag_new(const gchar *tag)
    470 {
    471 	XMLTag *new_tag;
    472 
    473 	new_tag = g_new(XMLTag, 1);
    474 	if (tag)
    475 		new_tag->tag = XML_STRING_ADD(tag);
    476 	else
    477 		new_tag->tag = NULL;
    478 	new_tag->attr = NULL;
    479 
    480 	return new_tag;
    481 }
    482 
    483 XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
    484 {
    485 	XMLAttr *new_attr;
    486 
    487 	new_attr = g_new(XMLAttr, 1);
    488 	new_attr->name = XML_STRING_ADD(name);
    489 	new_attr->value = g_strdup(value);
    490 
    491 	return new_attr;
    492 }
    493 
    494 XMLAttr *xml_attr_new_int(const gchar *name, const gint value)
    495 {
    496 	XMLAttr *new_attr;
    497 	gchar *valuestr;
    498 
    499 	valuestr = g_strdup_printf("%d", value);
    500 
    501 	new_attr = g_new(XMLAttr, 1);
    502 	new_attr->name = XML_STRING_ADD(name);
    503 	new_attr->value = valuestr;
    504 
    505 	return new_attr;
    506 }
    507 
    508 XMLAttr *xml_attr_new_time_t(const gchar *name, const time_t value)
    509 {
    510 	XMLAttr *new_attr;
    511 	gchar *valuestr;
    512 
    513 	valuestr = g_strdup_printf("%lld", value);
    514 
    515 	new_attr = g_new(XMLAttr, 1);
    516 	new_attr->name = XML_STRING_ADD(name);
    517 	new_attr->value = valuestr;
    518 
    519 	return new_attr;
    520 }
    521 
    522 void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
    523 {
    524 	tag->attr = g_list_prepend(tag->attr, attr);
    525 }
    526 
    527 static XMLTag *xml_copy_tag(XMLTag *tag)
    528 {
    529 	XMLTag *new_tag;
    530 	XMLAttr *attr;
    531 	GList *list;
    532 
    533 	new_tag = xml_tag_new(tag->tag);
    534 	for (list = tag->attr; list != NULL; list = list->next) {
    535 		attr = xml_copy_attr((XMLAttr *)list->data);
    536 		xml_tag_add_attr(new_tag, attr);
    537 	}
    538 	tag->attr = g_list_reverse(tag->attr);
    539 
    540 	return new_tag;
    541 }
    542 
    543 static XMLAttr *xml_copy_attr(XMLAttr *attr)
    544 {
    545 	return xml_attr_new(attr->name, attr->value);
    546 }
    547 
    548 static gint xml_unescape_str(gchar *str)
    549 {
    550 	gchar *start;
    551 	gchar *end;
    552 	gchar *p = str;
    553 	gchar *esc_str;
    554 	gchar ch;
    555 	gint len;
    556 
    557 	while ((start = strchr(p, '&')) != NULL) {
    558 		if ((end = strchr(start + 1, ';')) == NULL) {
    559 			g_warning("unescaped '&' appeared");
    560 			p = start + 1;
    561 			continue;
    562 		}
    563 		len = end - start + 1;
    564 		if (len < 3) {
    565 			p = end + 1;
    566 			continue;
    567 		}
    568 
    569 		Xstrndup_a(esc_str, start, len, return -1);
    570 		if (!strcmp(esc_str, "&lt;"))
    571 			ch = '<';
    572 		else if (!strcmp(esc_str, "&gt;"))
    573 			ch = '>';
    574 		else if (!strcmp(esc_str, "&amp;"))
    575 			ch = '&';
    576 		else if (!strcmp(esc_str, "&apos;"))
    577 			ch = '\'';
    578 		else if (!strcmp(esc_str, "&quot;"))
    579 			ch = '\"';
    580 		else {
    581 			p = end + 1;
    582 			continue;
    583 		}
    584 
    585 		*start = ch;
    586 		memmove(start + 1, end + 1, strlen(end + 1) + 1);
    587 		p = start + 1;
    588 	}
    589 
    590 	return 0;
    591 }
    592 
    593 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
    594 {
    595 	const gchar *p;
    596 	int result = 0;
    597 	cm_return_val_if_fail(fp != NULL, -1);
    598 
    599 	if (!str) return 0;
    600 
    601 	for (p = str; *p != '\0'; p++) {
    602 		switch (*p) {
    603 		case '<':
    604 			result = fputs("&lt;", fp);
    605 			break;
    606 		case '>':
    607 			result = fputs("&gt;", fp);
    608 			break;
    609 		case '&':
    610 			result = fputs("&amp;", fp);
    611 			break;
    612 		case '\'':
    613 			result = fputs("&apos;", fp);
    614 			break;
    615 		case '\"':
    616 			result = fputs("&quot;", fp);
    617 			break;
    618 		default:
    619 			result = fputc(*p, fp);
    620 		}
    621 	}
    622 
    623 	return (result == EOF ? -1 : 0);
    624 }
    625 
    626 gint xml_file_put_xml_decl(FILE *fp)
    627 {
    628 	cm_return_val_if_fail(fp != NULL, -1);
    629 	XML_STRING_TABLE_CREATE();
    630 
    631 	return fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL);
    632 }
    633 
    634 static void xml_free_node(XMLNode *node)
    635 {
    636 	if (!node) return;
    637 
    638 	xml_free_tag(node->tag);
    639 	g_free(node->element);
    640 	g_free(node);
    641 }
    642 
    643 static gboolean xml_free_func(GNode *node, gpointer data)
    644 {
    645 	XMLNode *xmlnode = node->data;
    646 
    647 	xml_free_node(xmlnode);
    648 	return FALSE;
    649 }
    650 
    651 void xml_free_tree(GNode *node)
    652 {
    653 	cm_return_if_fail(node != NULL);
    654 
    655 	g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
    656 			NULL);
    657 
    658 	g_node_destroy(node);
    659 }
    660 
    661 static void xml_free_tag(XMLTag *tag)
    662 {
    663 	if (!tag) return;
    664 
    665 	XML_STRING_FREE(tag->tag);
    666 	while (tag->attr != NULL) {
    667 		XMLAttr *attr = (XMLAttr *)tag->attr->data;
    668 		tag->attr = g_list_remove(tag->attr, tag->attr->data);
    669 		XML_STRING_FREE(attr->name);
    670 		g_free(attr->value); /* __not__ XML_STRING_FREE */
    671 		g_free(attr);
    672 	}
    673 	g_free(tag);
    674 }
    675 
    676 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
    677 {
    678 	gchar *start;
    679 	gchar *end;
    680 
    681 	buf[0] = '\0';
    682 
    683 	while ((start = strchr(file->bufp, '<')) == NULL)
    684 		if (xml_read_line(file) < 0) return -1;
    685 
    686 	start++;
    687 	file->bufp = start;
    688 
    689 	while ((end = strchr(file->bufp, '>')) == NULL)
    690 		if (xml_read_line(file) < 0) return -1;
    691 
    692 	strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
    693 	g_strstrip(buf);
    694 	file->bufp = end + 1;
    695 	xml_truncate_buf(file);
    696 
    697 	return 0;
    698 }
    699 
    700 #define TRY(func) \
    701 if (!(func)) \
    702 { \
    703 	g_warning("failed to write part of XML tree"); \
    704 	return -1; \
    705 } \
    706 
    707 static int xml_write_tree_recursive(GNode *node, FILE *fp)
    708 {
    709 	gint i, depth;
    710 	XMLTag *tag;
    711 	GList *cur;
    712 
    713 	cm_return_val_if_fail(node != NULL, -1);
    714 	cm_return_val_if_fail(fp != NULL, -1);
    715 
    716 	depth = g_node_depth(node) - 1;
    717 	for (i = 0; i < depth; i++)
    718 		TRY(fputs("    ", fp) != EOF);
    719 
    720 	tag = ((XMLNode *) node->data)->tag;
    721 
    722 	TRY(fprintf(fp, "<%s", tag->tag) > 0);
    723 
    724 	for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
    725 		XMLAttr *attr = (XMLAttr *) cur->data;
    726 
    727 		TRY(fprintf(fp, " %s=\"", attr->name) > 0);
    728 		TRY(xml_file_put_escape_str(fp, attr->value) == 0);
    729 		TRY(fputs("\"", fp) != EOF);
    730 
    731 	}
    732 
    733 	if (node->children) {
    734 		GNode *child;
    735 		TRY(fputs(">\n", fp) != EOF);
    736 
    737 		child = node->children;
    738 		while (child) {
    739 			GNode *cur;
    740 
    741 			cur = child;
    742 			child = cur->next;
    743 			TRY(xml_write_tree_recursive(cur, fp) == 0);
    744 		}
    745 
    746 		for (i = 0; i < depth; i++)
    747 			TRY(fputs("    ", fp) != EOF);
    748 		TRY(fprintf(fp, "</%s>\n", tag->tag) > 0);
    749 	} else
    750 		TRY(fputs(" />\n", fp) != EOF);
    751 
    752 	return 0;
    753 }
    754 
    755 #undef TRY
    756 
    757 int xml_write_tree(GNode *node, FILE *fp)
    758 {
    759 	return xml_write_tree_recursive(node, fp);
    760 }
    761 
    762 static gpointer copy_node_func(gpointer nodedata, gpointer data)
    763 {
    764 	XMLNode *xmlnode = (XMLNode *) nodedata;
    765 	XMLNode *newxmlnode;
    766 
    767 	newxmlnode = g_new0(XMLNode, 1);
    768 	newxmlnode->tag = xml_copy_tag(xmlnode->tag);
    769 	newxmlnode->element = g_strdup(xmlnode->element);
    770 
    771 	return newxmlnode;
    772 }
    773 
    774 GNode *xml_copy_tree(GNode *node)
    775 {
    776 	return g_node_map(node, copy_node_func, NULL);
    777 }