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/>.
30 /* if this is defined all attr.names and tag.names are stored
32 #if defined(SPARSE_MEMORY)
33 #include "stringtable.h"
35 static StringTable *xml_string_table;
36 static XMLTag *xml_copy_tag (XMLTag *tag);
37 static XMLAttr *xml_copy_attr (XMLAttr *attr);
38 static void xml_free_node (XMLNode *node);
39 static void xml_free_tag (XMLTag *tag);
40 static void xml_pop_tag (XMLFile *file);
41 static void xml_push_tag (XMLFile *file,
43 static gint xml_read_line (XMLFile *file);
44 static void xml_truncate_buf (XMLFile *file);
45 static gint xml_unescape_str (gchar *str);
47 static void xml_string_table_create(void)
49 if (xml_string_table == NULL)
50 xml_string_table = string_table_new();
52 #define XML_STRING_ADD(str) \
53 string_table_insert_string(xml_string_table, (str))
54 #define XML_STRING_FREE(str) \
55 string_table_free_string(xml_string_table, (str))
57 #define XML_STRING_TABLE_CREATE() \
58 xml_string_table_create()
60 #else /* !SPARSE_MEMORY */
62 #define XML_STRING_ADD(str) \
64 #define XML_STRING_FREE(str) \
67 #define XML_STRING_TABLE_CREATE()
69 #endif /* SPARSE_MEMORY */
71 static gint xml_get_parenthesis (XMLFile *file,
75 XMLFile *xml_open_file(const gchar *path)
79 cm_return_val_if_fail(path != NULL, NULL);
81 newfile = g_new(XMLFile, 1);
83 newfile->fp = g_fopen(path, "rb");
89 XML_STRING_TABLE_CREATE();
91 newfile->buf = g_string_new(NULL);
92 newfile->bufp = newfile->buf->str;
95 newfile->encoding = NULL;
96 newfile->tag_stack = NULL;
98 newfile->is_empty_element = FALSE;
103 void xml_close_file(XMLFile *file)
105 cm_return_if_fail(file != NULL);
107 if (file->fp) fclose(file->fp);
109 g_string_free(file->buf, TRUE);
112 g_free(file->encoding);
114 while (file->tag_stack != NULL)
120 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
126 while (xml_parse_next_tag(file) == 0) {
127 if (file->level < level) break;
128 if (file->level == level) {
129 g_warning("xml_build_tree(): Parse error");
133 tag = xml_get_current_tag(file);
135 xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
136 xmlnode->element = xml_get_element(file);
138 node = g_node_new(xmlnode);
140 node = g_node_append_data(parent, xmlnode);
142 xml_build_tree(file, node, file->level);
143 if (file->level == 0) break;
149 GNode *xml_parse_file(const gchar *path)
154 file = xml_open_file(path);
155 cm_return_val_if_fail(file != NULL, NULL);
159 node = xml_build_tree(file, NULL, file->level);
161 xml_close_file(file);
163 #if defined(SPARSE_MEMORY)
164 if (debug_get_mode())
165 string_table_get_stats(xml_string_table);
171 gint xml_get_dtd(XMLFile *file)
173 gchar buf[XMLBUFSIZE];
176 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
178 if ((*bufp++ == '?') &&
179 (bufp = strcasestr(bufp, "xml")) &&
180 (bufp = strcasestr(bufp + 3, "version")) &&
181 (bufp = strchr(bufp + 7, '?'))) {
182 file->dtd = g_strdup(buf);
183 if ((bufp = strcasestr(buf, "encoding=\""))) {
185 extract_quote(bufp, '"');
186 file->encoding = g_strdup(bufp);
187 file->need_codeconv =
188 strcmp2(bufp, CS_INTERNAL);
190 file->encoding = g_strdup(CS_INTERNAL);
191 file->need_codeconv = FALSE;
194 g_warning("Can't get XML DTD");
201 gint xml_parse_next_tag(XMLFile *file)
203 gchar buf[XMLBUFSIZE];
210 if (file->is_empty_element == TRUE) {
211 file->is_empty_element = FALSE;
216 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
217 g_warning("xml_parse_next_tag(): Can't parse next tag");
225 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
226 g_warning("xml_parse_next_tag(): Tag name mismatch: %s (%s)", buf, xml_get_current_tag(file)->tag);
233 if (len >= 7 && !strncmp(buf, "!-- ", 4) && !strncmp(buf+len-3, " --", 3)) {
238 tag = xml_tag_new(NULL);
239 xml_push_tag(file, tag);
241 if (len > 0 && buf[len - 1] == '/') {
242 file->is_empty_element = TRUE;
247 if (strlen(buf) == 0) {
248 g_warning("xml_parse_next_tag(): Tag name is empty");
252 while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++;
254 if (file->need_codeconv) {
255 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
257 tag->tag = XML_STRING_ADD(tag_str);
260 tag->tag = XML_STRING_ADD(buf);
262 tag->tag = XML_STRING_ADD(buf);
266 if (file->need_codeconv) {
267 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
269 tag->tag = XML_STRING_ADD(tag_str);
272 tag->tag = XML_STRING_ADD(buf);
274 tag->tag = XML_STRING_ADD(buf);
277 /* parse attributes ( name=value ) */
282 gchar *utf8_attr_name;
283 gchar *utf8_attr_value;
287 while (g_ascii_isspace(*bufp)) bufp++;
289 if ((p = strchr(attr_name, '=')) == NULL) {
290 g_warning("xml_parse_next_tag(): Syntax error in tag (a) %s", attr_name);
295 while (g_ascii_isspace(*bufp)) bufp++;
297 if (*bufp != '"' && *bufp != '\'') {
298 g_warning("xml_parse_next_tag(): Syntax error in tag (b) %s", bufp);
304 if ((p = strchr(attr_value, quote)) == NULL) {
305 g_warning("xml_parse_next_tag(): Syntax error in tag (c) %s", attr_value);
311 g_strchomp(attr_name);
312 xml_unescape_str(attr_value);
313 if (file->need_codeconv) {
314 utf8_attr_name = conv_codeset_strdup
315 (attr_name, file->encoding, CS_INTERNAL);
316 utf8_attr_value = conv_codeset_strdup
317 (attr_value, file->encoding, CS_INTERNAL);
319 utf8_attr_name = g_strdup(attr_name);
320 if (!utf8_attr_value)
321 utf8_attr_value = g_strdup(attr_value);
323 attr = xml_attr_new(utf8_attr_name, utf8_attr_value);
324 g_free(utf8_attr_value);
325 g_free(utf8_attr_name);
327 attr = xml_attr_new(attr_name, attr_value);
329 xml_tag_add_attr(tag, attr);
332 tag->attr = g_list_reverse(tag->attr);
337 static void xml_push_tag(XMLFile *file, XMLTag *tag)
339 cm_return_if_fail(tag != NULL);
341 file->tag_stack = g_list_prepend(file->tag_stack, tag);
345 static void xml_pop_tag(XMLFile *file)
349 if (!file->tag_stack) return;
351 tag = (XMLTag *)file->tag_stack->data;
354 file->tag_stack = g_list_remove(file->tag_stack, tag);
358 XMLTag *xml_get_current_tag(XMLFile *file)
361 return (XMLTag *)file->tag_stack->data;
366 GList *xml_get_current_tag_attr(XMLFile *file)
370 tag = xml_get_current_tag(file);
371 if (!tag) return NULL;
376 gchar *xml_get_element(XMLFile *file)
382 while ((end = strchr(file->bufp, '<')) == NULL)
383 if (xml_read_line(file) < 0) return NULL;
385 if (end == file->bufp)
388 str = g_strndup(file->bufp, end - file->bufp);
389 /* this is not XML1.0 strict */
391 xml_unescape_str(str);
394 xml_truncate_buf(file);
396 if (str[0] == '\0') {
401 if (!file->need_codeconv)
404 new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL);
406 new_str = g_strdup(str);
412 static gint xml_read_line(XMLFile *file)
414 gchar buf[XMLBUFSIZE];
417 if (fgets(buf, sizeof(buf), file->fp) == NULL)
420 index = file->bufp - file->buf->str;
422 g_string_append(file->buf, buf);
424 file->bufp = file->buf->str + index;
429 static void xml_truncate_buf(XMLFile *file)
433 len = file->bufp - file->buf->str;
435 g_string_erase(file->buf, 0, len);
436 file->bufp = file->buf->str;
440 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
444 tag = xml_get_current_tag(file);
446 if (tag && strcmp(tag->tag, name) == 0)
452 XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
456 node = g_new(XMLNode, 1);
458 node->element = g_strdup(text);
463 XMLTag *xml_tag_new(const gchar *tag)
467 new_tag = g_new(XMLTag, 1);
469 new_tag->tag = XML_STRING_ADD(tag);
472 new_tag->attr = NULL;
477 XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
481 new_attr = g_new(XMLAttr, 1);
482 new_attr->name = XML_STRING_ADD(name);
483 new_attr->value = g_strdup(value);
488 XMLAttr *xml_attr_new_int(const gchar *name, const gint value)
493 valuestr = g_strdup_printf("%d", value);
495 new_attr = g_new(XMLAttr, 1);
496 new_attr->name = XML_STRING_ADD(name);
497 new_attr->value = g_strdup(valuestr);
504 void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
506 tag->attr = g_list_prepend(tag->attr, attr);
509 static XMLTag *xml_copy_tag(XMLTag *tag)
515 new_tag = xml_tag_new(tag->tag);
516 for (list = tag->attr; list != NULL; list = list->next) {
517 attr = xml_copy_attr((XMLAttr *)list->data);
518 xml_tag_add_attr(new_tag, attr);
520 tag->attr = g_list_reverse(tag->attr);
525 static XMLAttr *xml_copy_attr(XMLAttr *attr)
527 return xml_attr_new(attr->name, attr->value);
530 static gint xml_unescape_str(gchar *str)
539 while ((start = strchr(p, '&')) != NULL) {
540 if ((end = strchr(start + 1, ';')) == NULL) {
541 g_warning("Unescaped '&' appeared");
545 len = end - start + 1;
551 Xstrndup_a(esc_str, start, len, return -1);
552 if (!strcmp(esc_str, "<"))
554 else if (!strcmp(esc_str, ">"))
556 else if (!strcmp(esc_str, "&"))
558 else if (!strcmp(esc_str, "'"))
560 else if (!strcmp(esc_str, """))
568 memmove(start + 1, end + 1, strlen(end + 1) + 1);
575 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
579 cm_return_val_if_fail(fp != NULL, -1);
583 for (p = str; *p != '\0'; p++) {
586 result = fputs("<", fp);
589 result = fputs(">", fp);
592 result = fputs("&", fp);
595 result = fputs("'", fp);
598 result = fputs(""", fp);
601 result = fputc(*p, fp);
605 return (result == EOF ? -1 : 0);
608 gint xml_file_put_xml_decl(FILE *fp)
610 cm_return_val_if_fail(fp != NULL, -1);
611 XML_STRING_TABLE_CREATE();
613 return fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL);
616 static void xml_free_node(XMLNode *node)
620 xml_free_tag(node->tag);
621 g_free(node->element);
625 static gboolean xml_free_func(GNode *node, gpointer data)
627 XMLNode *xmlnode = node->data;
629 xml_free_node(xmlnode);
633 void xml_free_tree(GNode *node)
635 cm_return_if_fail(node != NULL);
637 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
640 g_node_destroy(node);
643 static void xml_free_tag(XMLTag *tag)
647 XML_STRING_FREE(tag->tag);
648 while (tag->attr != NULL) {
649 XMLAttr *attr = (XMLAttr *)tag->attr->data;
650 tag->attr = g_list_remove(tag->attr, tag->attr->data);
651 XML_STRING_FREE(attr->name);
652 g_free(attr->value); /* __not__ XML_STRING_FREE */
658 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
665 while ((start = strchr(file->bufp, '<')) == NULL)
666 if (xml_read_line(file) < 0) return -1;
671 while ((end = strchr(file->bufp, '>')) == NULL)
672 if (xml_read_line(file) < 0) return -1;
674 strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
676 file->bufp = end + 1;
677 xml_truncate_buf(file);
685 g_warning("failed to write part of XML tree"); \
689 static int xml_write_tree_recursive(GNode *node, FILE *fp)
695 cm_return_val_if_fail(node != NULL, -1);
696 cm_return_val_if_fail(fp != NULL, -1);
698 depth = g_node_depth(node) - 1;
699 for (i = 0; i < depth; i++)
700 TRY(fputs(" ", fp) != EOF);
702 tag = ((XMLNode *) node->data)->tag;
704 TRY(fprintf(fp, "<%s", tag->tag) > 0);
706 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
707 XMLAttr *attr = (XMLAttr *) cur->data;
709 TRY(fprintf(fp, " %s=\"", attr->name) > 0);
710 TRY(xml_file_put_escape_str(fp, attr->value) == 0);
711 TRY(fputs("\"", fp) != EOF);
715 if (node->children) {
717 TRY(fputs(">\n", fp) != EOF);
719 child = node->children;
725 TRY(xml_write_tree_recursive(cur, fp) == 0);
728 for (i = 0; i < depth; i++)
729 TRY(fputs(" ", fp) != EOF);
730 TRY(fprintf(fp, "</%s>\n", tag->tag) > 0);
732 TRY(fputs(" />\n", fp) != EOF);
739 int xml_write_tree(GNode *node, FILE *fp)
741 return xml_write_tree_recursive(node, fp);
744 static gpointer copy_node_func(gpointer nodedata, gpointer data)
746 XMLNode *xmlnode = (XMLNode *) nodedata;
749 newxmlnode = g_new0(XMLNode, 1);
750 newxmlnode->tag = xml_copy_tag(xmlnode->tag);
751 newxmlnode->element = g_strdup(xmlnode->element);
756 GNode *xml_copy_tree(GNode *node)
758 return g_node_map(node, copy_node_func, NULL);