2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
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.
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.
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/>.
28 #include "../codeconv.h"
31 /* if this is defined all attr.names and tag.names are stored
33 #if defined(SPARSE_MEMORY)
34 #include "stringtable.h"
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,
44 static gint xml_read_line (XMLFile *file);
45 static void xml_truncate_buf (XMLFile *file);
46 static gint xml_unescape_str (gchar *str);
48 static void xml_string_table_create(void)
50 if (xml_string_table == NULL)
51 xml_string_table = string_table_new();
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))
58 #define XML_STRING_TABLE_CREATE() \
59 xml_string_table_create()
61 #else /* !SPARSE_MEMORY */
63 #define XML_STRING_ADD(str) \
65 #define XML_STRING_FREE(str) \
68 #define XML_STRING_TABLE_CREATE()
70 #endif /* SPARSE_MEMORY */
72 static gint xml_get_parenthesis (XMLFile *file,
76 XMLFile *xml_open_file(const gchar *path)
80 cm_return_val_if_fail(path != NULL, NULL);
82 newfile = g_new(XMLFile, 1);
84 newfile->fp = g_fopen(path, "rb");
90 XML_STRING_TABLE_CREATE();
92 newfile->buf = g_string_new(NULL);
93 newfile->bufp = newfile->buf->str;
96 newfile->encoding = NULL;
97 newfile->tag_stack = NULL;
99 newfile->is_empty_element = FALSE;
104 void xml_close_file(XMLFile *file)
106 cm_return_if_fail(file != NULL);
108 if (file->fp) fclose(file->fp);
110 g_string_free(file->buf, TRUE);
113 g_free(file->encoding);
115 while (file->tag_stack != NULL)
121 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
127 while (xml_parse_next_tag(file) == 0) {
128 if (file->level < level) break;
129 if (file->level == level) {
130 g_warning("xml_build_tree(): Parse error\n");
134 tag = xml_get_current_tag(file);
136 xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
137 xmlnode->element = xml_get_element(file);
139 node = g_node_new(xmlnode);
141 node = g_node_append_data(parent, xmlnode);
143 xml_build_tree(file, node, file->level);
144 if (file->level == 0) break;
150 GNode *xml_parse_file(const gchar *path)
155 file = xml_open_file(path);
156 cm_return_val_if_fail(file != NULL, NULL);
160 node = xml_build_tree(file, NULL, file->level);
162 xml_close_file(file);
164 #if defined(SPARSE_MEMORY)
165 if (debug_get_mode())
166 string_table_get_stats(xml_string_table);
172 gint xml_get_dtd(XMLFile *file)
174 gchar buf[XMLBUFSIZE];
177 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
179 if ((*bufp++ == '?') &&
180 (bufp = strcasestr(bufp, "xml")) &&
181 (bufp = strcasestr(bufp + 3, "version")) &&
182 (bufp = strchr(bufp + 7, '?'))) {
183 file->dtd = g_strdup(buf);
184 if ((bufp = strcasestr(buf, "encoding=\""))) {
186 extract_quote(bufp, '"');
187 file->encoding = g_strdup(bufp);
188 file->need_codeconv =
189 strcmp2(bufp, CS_INTERNAL);
191 file->encoding = g_strdup(CS_INTERNAL);
192 file->need_codeconv = FALSE;
195 g_warning("Can't get xml dtd\n");
202 gint xml_parse_next_tag(XMLFile *file)
204 gchar buf[XMLBUFSIZE];
211 if (file->is_empty_element == TRUE) {
212 file->is_empty_element = FALSE;
217 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
218 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
226 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
227 g_warning("xml_parse_next_tag(): Tag name mismatch: %s (%s)\n", buf, xml_get_current_tag(file)->tag);
234 if (len >= 7 && !strncmp(buf, "!-- ", 4) && !strncmp(buf+len-3, " --", 3)) {
239 tag = xml_tag_new(NULL);
240 xml_push_tag(file, tag);
242 if (len > 0 && buf[len - 1] == '/') {
243 file->is_empty_element = TRUE;
248 if (strlen(buf) == 0) {
249 g_warning("xml_parse_next_tag(): Tag name is empty\n");
253 while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++;
255 if (file->need_codeconv) {
256 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
258 tag->tag = XML_STRING_ADD(tag_str);
261 tag->tag = XML_STRING_ADD(buf);
263 tag->tag = XML_STRING_ADD(buf);
267 if (file->need_codeconv) {
268 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
270 tag->tag = XML_STRING_ADD(tag_str);
273 tag->tag = XML_STRING_ADD(buf);
275 tag->tag = XML_STRING_ADD(buf);
278 /* parse attributes ( name=value ) */
283 gchar *utf8_attr_name;
284 gchar *utf8_attr_value;
288 while (g_ascii_isspace(*bufp)) bufp++;
290 if ((p = strchr(attr_name, '=')) == NULL) {
291 g_warning("xml_parse_next_tag(): Syntax error in tag (a) %s\n", attr_name);
296 while (g_ascii_isspace(*bufp)) bufp++;
298 if (*bufp != '"' && *bufp != '\'') {
299 g_warning("xml_parse_next_tag(): Syntax error in tag (b) %s\n", bufp);
305 if ((p = strchr(attr_value, quote)) == NULL) {
306 g_warning("xml_parse_next_tag(): Syntax error in tag (c) %s\n", attr_value);
312 g_strchomp(attr_name);
313 xml_unescape_str(attr_value);
314 if (file->need_codeconv) {
315 utf8_attr_name = conv_codeset_strdup
316 (attr_name, file->encoding, CS_INTERNAL);
317 utf8_attr_value = conv_codeset_strdup
318 (attr_value, file->encoding, CS_INTERNAL);
320 utf8_attr_name = g_strdup(attr_name);
321 if (!utf8_attr_value)
322 utf8_attr_value = g_strdup(attr_value);
324 attr = xml_attr_new(utf8_attr_name, utf8_attr_value);
325 g_free(utf8_attr_value);
326 g_free(utf8_attr_name);
328 attr = xml_attr_new(attr_name, attr_value);
330 xml_tag_add_attr(tag, attr);
333 tag->attr = g_list_reverse(tag->attr);
338 static void xml_push_tag(XMLFile *file, XMLTag *tag)
340 cm_return_if_fail(tag != NULL);
342 file->tag_stack = g_list_prepend(file->tag_stack, tag);
346 static void xml_pop_tag(XMLFile *file)
350 if (!file->tag_stack) return;
352 tag = (XMLTag *)file->tag_stack->data;
355 file->tag_stack = g_list_remove(file->tag_stack, tag);
359 XMLTag *xml_get_current_tag(XMLFile *file)
362 return (XMLTag *)file->tag_stack->data;
367 GList *xml_get_current_tag_attr(XMLFile *file)
371 tag = xml_get_current_tag(file);
372 if (!tag) return NULL;
377 gchar *xml_get_element(XMLFile *file)
383 while ((end = strchr(file->bufp, '<')) == NULL)
384 if (xml_read_line(file) < 0) return NULL;
386 if (end == file->bufp)
389 str = g_strndup(file->bufp, end - file->bufp);
390 /* this is not XML1.0 strict */
392 xml_unescape_str(str);
395 xml_truncate_buf(file);
397 if (str[0] == '\0') {
402 if (!file->need_codeconv)
405 new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL);
407 new_str = g_strdup(str);
413 static gint xml_read_line(XMLFile *file)
415 gchar buf[XMLBUFSIZE];
418 if (fgets(buf, sizeof(buf), file->fp) == NULL)
421 index = file->bufp - file->buf->str;
423 g_string_append(file->buf, buf);
425 file->bufp = file->buf->str + index;
430 static void xml_truncate_buf(XMLFile *file)
434 len = file->bufp - file->buf->str;
436 g_string_erase(file->buf, 0, len);
437 file->bufp = file->buf->str;
441 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
445 tag = xml_get_current_tag(file);
447 if (tag && strcmp(tag->tag, name) == 0)
453 XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
457 node = g_new(XMLNode, 1);
459 node->element = g_strdup(text);
464 XMLTag *xml_tag_new(const gchar *tag)
468 new_tag = g_new(XMLTag, 1);
470 new_tag->tag = XML_STRING_ADD(tag);
473 new_tag->attr = NULL;
478 XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
482 new_attr = g_new(XMLAttr, 1);
483 new_attr->name = XML_STRING_ADD(name);
484 new_attr->value = g_strdup(value);
489 XMLAttr *xml_attr_new_int(const gchar *name, const gint value)
494 valuestr = g_strdup_printf("%d", value);
496 new_attr = g_new(XMLAttr, 1);
497 new_attr->name = XML_STRING_ADD(name);
498 new_attr->value = g_strdup(valuestr);
505 void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
507 tag->attr = g_list_prepend(tag->attr, attr);
510 static XMLTag *xml_copy_tag(XMLTag *tag)
516 new_tag = xml_tag_new(tag->tag);
517 for (list = tag->attr; list != NULL; list = list->next) {
518 attr = xml_copy_attr((XMLAttr *)list->data);
519 xml_tag_add_attr(new_tag, attr);
521 tag->attr = g_list_reverse(tag->attr);
526 static XMLAttr *xml_copy_attr(XMLAttr *attr)
528 return xml_attr_new(attr->name, attr->value);
531 static gint xml_unescape_str(gchar *str)
540 while ((start = strchr(p, '&')) != NULL) {
541 if ((end = strchr(start + 1, ';')) == NULL) {
542 g_warning("Unescaped '&' appeared\n");
546 len = end - start + 1;
552 Xstrndup_a(esc_str, start, len, return -1);
553 if (!strcmp(esc_str, "<"))
555 else if (!strcmp(esc_str, ">"))
557 else if (!strcmp(esc_str, "&"))
559 else if (!strcmp(esc_str, "'"))
561 else if (!strcmp(esc_str, """))
569 memmove(start + 1, end + 1, strlen(end + 1) + 1);
576 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
580 cm_return_val_if_fail(fp != NULL, -1);
584 for (p = str; *p != '\0'; p++) {
587 result = fputs("<", fp);
590 result = fputs(">", fp);
593 result = fputs("&", fp);
596 result = fputs("'", fp);
599 result = fputs(""", fp);
602 result = fputc(*p, fp);
606 return (result == EOF ? -1 : 0);
609 gint xml_file_put_xml_decl(FILE *fp)
611 cm_return_val_if_fail(fp != NULL, -1);
612 XML_STRING_TABLE_CREATE();
614 return fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL);
617 static void xml_free_node(XMLNode *node)
621 xml_free_tag(node->tag);
622 g_free(node->element);
626 static gboolean xml_free_func(GNode *node, gpointer data)
628 XMLNode *xmlnode = node->data;
630 xml_free_node(xmlnode);
634 void xml_free_tree(GNode *node)
636 cm_return_if_fail(node != NULL);
638 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
641 g_node_destroy(node);
644 static void xml_free_tag(XMLTag *tag)
648 XML_STRING_FREE(tag->tag);
649 while (tag->attr != NULL) {
650 XMLAttr *attr = (XMLAttr *)tag->attr->data;
651 tag->attr = g_list_remove(tag->attr, tag->attr->data);
652 XML_STRING_FREE(attr->name);
653 g_free(attr->value); /* __not__ XML_STRING_FREE */
659 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
666 while ((start = strchr(file->bufp, '<')) == NULL)
667 if (xml_read_line(file) < 0) return -1;
672 while ((end = strchr(file->bufp, '>')) == NULL)
673 if (xml_read_line(file) < 0) return -1;
675 strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
677 file->bufp = end + 1;
678 xml_truncate_buf(file);
686 g_warning("failed to write part of xml tree\n"); \
690 static int xml_write_tree_recursive(GNode *node, FILE *fp)
696 cm_return_val_if_fail(node != NULL, -1);
697 cm_return_val_if_fail(fp != NULL, -1);
699 depth = g_node_depth(node) - 1;
700 for (i = 0; i < depth; i++)
701 TRY(fputs(" ", fp) != EOF);
703 tag = ((XMLNode *) node->data)->tag;
705 TRY(fprintf(fp, "<%s", tag->tag) > 0);
707 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
708 XMLAttr *attr = (XMLAttr *) cur->data;
710 TRY(fprintf(fp, " %s=\"", attr->name) > 0);
711 TRY(xml_file_put_escape_str(fp, attr->value) == 0);
712 TRY(fputs("\"", fp) != EOF);
716 if (node->children) {
718 TRY(fputs(">\n", fp) != EOF);
720 child = node->children;
726 TRY(xml_write_tree_recursive(cur, fp) == 0);
729 for (i = 0; i < depth; i++)
730 TRY(fputs(" ", fp) != EOF);
731 TRY(fprintf(fp, "</%s>\n", tag->tag) > 0);
733 TRY(fputs(" />\n", fp) != EOF);
740 int xml_write_tree(GNode *node, FILE *fp)
742 return xml_write_tree_recursive(node, fp);
745 static gpointer copy_node_func(gpointer nodedata, gpointer data)
747 XMLNode *xmlnode = (XMLNode *) nodedata;
750 newxmlnode = g_new0(XMLNode, 1);
751 newxmlnode->tag = xml_copy_tag(xmlnode->tag);
752 newxmlnode->element = g_strdup(xmlnode->element);
757 GNode *xml_copy_tree(GNode *node)
759 return g_node_map(node, copy_node_func, NULL);