2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 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 2 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, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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;
38 static void xml_string_table_create(void)
40 if (xml_string_table == NULL)
41 xml_string_table = string_table_new();
43 #define XML_STRING_ADD(str) \
44 string_table_insert_string(xml_string_table, (str))
45 #define XML_STRING_FREE(str) \
46 string_table_free_string(xml_string_table, (str))
48 #define XML_STRING_TABLE_CREATE() \
49 xml_string_table_create()
51 #else /* !SPARSE_MEMORY */
53 #define XML_STRING_ADD(str) \
55 #define XML_STRING_FREE(str) \
58 #define XML_STRING_TABLE_CREATE()
60 #endif /* SPARSE_MEMORY */
62 static gint xml_get_parenthesis (XMLFile *file,
66 XMLFile *xml_open_file(const gchar *path)
70 g_return_val_if_fail(path != NULL, NULL);
72 newfile = g_new(XMLFile, 1);
74 newfile->fp = g_fopen(path, "rb");
80 XML_STRING_TABLE_CREATE();
82 newfile->buf = g_string_new(NULL);
83 newfile->bufp = newfile->buf->str;
86 newfile->encoding = NULL;
87 newfile->tag_stack = NULL;
89 newfile->is_empty_element = FALSE;
94 void xml_close_file(XMLFile *file)
96 g_return_if_fail(file != NULL);
98 if (file->fp) fclose(file->fp);
100 g_string_free(file->buf, TRUE);
103 g_free(file->encoding);
105 while (file->tag_stack != NULL)
111 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
117 while (xml_parse_next_tag(file) == 0) {
118 if (file->level < level) break;
119 if (file->level == level) {
120 g_warning("xml_build_tree(): Parse error\n");
124 tag = xml_get_current_tag(file);
126 xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
127 xmlnode->element = xml_get_element(file);
129 node = g_node_new(xmlnode);
131 node = g_node_append_data(parent, xmlnode);
133 xml_build_tree(file, node, file->level);
134 if (file->level == 0) break;
140 GNode *xml_parse_file(const gchar *path)
145 file = xml_open_file(path);
146 g_return_val_if_fail(file != NULL, NULL);
150 node = xml_build_tree(file, NULL, file->level);
152 xml_close_file(file);
154 #if defined(SPARSE_MEMORY)
155 if (debug_get_mode())
156 string_table_get_stats(xml_string_table);
162 gint xml_get_dtd(XMLFile *file)
164 gchar buf[XMLBUFSIZE];
167 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
169 if ((*bufp++ == '?') &&
170 (bufp = strcasestr(bufp, "xml")) &&
171 (bufp = strcasestr(bufp + 3, "version")) &&
172 (bufp = strchr(bufp + 7, '?'))) {
173 file->dtd = g_strdup(buf);
174 if ((bufp = strcasestr(buf, "encoding=\""))) {
176 extract_quote(bufp, '"');
177 file->encoding = g_strdup(bufp);
179 file->encoding = g_strdup(CS_INTERNAL);
181 g_warning("Can't get xml dtd\n");
188 gint xml_parse_next_tag(XMLFile *file)
190 gchar buf[XMLBUFSIZE];
196 if (file->is_empty_element == TRUE) {
197 file->is_empty_element = FALSE;
202 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
203 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
209 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
210 g_warning("xml_parse_next_tag(): Tag name mismatch: %s (%s)\n", buf, xml_get_current_tag(file)->tag);
217 tag = xml_tag_new(NULL);
218 xml_push_tag(file, tag);
221 if (len > 0 && buf[len - 1] == '/') {
222 file->is_empty_element = TRUE;
226 if (strlen(buf) == 0) {
227 g_warning("xml_parse_next_tag(): Tag name is empty\n");
231 while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++;
233 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
235 tag->tag = XML_STRING_ADD(tag_str);
238 tag->tag = XML_STRING_ADD(buf);
242 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
244 tag->tag = XML_STRING_ADD(tag_str);
247 tag->tag = XML_STRING_ADD(buf);
250 /* parse attributes ( name=value ) */
255 gchar *utf8_attr_name;
256 gchar *utf8_attr_value;
260 while (g_ascii_isspace(*bufp)) bufp++;
262 if ((p = strchr(attr_name, '=')) == NULL) {
263 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
268 while (g_ascii_isspace(*bufp)) bufp++;
270 if (*bufp != '"' && *bufp != '\'') {
271 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
277 if ((p = strchr(attr_value, quote)) == NULL) {
278 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
284 g_strchomp(attr_name);
285 xml_unescape_str(attr_value);
286 utf8_attr_name = conv_codeset_strdup
287 (attr_name, file->encoding, CS_INTERNAL);
288 utf8_attr_value = conv_codeset_strdup
289 (attr_value, file->encoding, CS_INTERNAL);
291 utf8_attr_name = g_strdup(attr_name);
292 if (!utf8_attr_value)
293 utf8_attr_value = g_strdup(attr_value);
295 attr = xml_attr_new(utf8_attr_name, utf8_attr_value);
296 xml_tag_add_attr(tag, attr);
298 g_free(utf8_attr_value);
299 g_free(utf8_attr_name);
305 void xml_push_tag(XMLFile *file, XMLTag *tag)
307 g_return_if_fail(tag != NULL);
309 file->tag_stack = g_list_prepend(file->tag_stack, tag);
313 void xml_pop_tag(XMLFile *file)
317 if (!file->tag_stack) return;
319 tag = (XMLTag *)file->tag_stack->data;
322 file->tag_stack = g_list_remove(file->tag_stack, tag);
326 XMLTag *xml_get_current_tag(XMLFile *file)
329 return (XMLTag *)file->tag_stack->data;
334 GList *xml_get_current_tag_attr(XMLFile *file)
338 tag = xml_get_current_tag(file);
339 if (!tag) return NULL;
344 gchar *xml_get_element(XMLFile *file)
350 while ((end = strchr(file->bufp, '<')) == NULL)
351 if (xml_read_line(file) < 0) return NULL;
353 if (end == file->bufp)
356 str = g_strndup(file->bufp, end - file->bufp);
357 /* this is not XML1.0 strict */
359 xml_unescape_str(str);
362 xml_truncate_buf(file);
364 if (str[0] == '\0') {
369 new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL);
371 new_str = g_strdup(str);
377 gint xml_read_line(XMLFile *file)
379 gchar buf[XMLBUFSIZE];
382 if (fgets(buf, sizeof(buf), file->fp) == NULL)
385 index = file->bufp - file->buf->str;
387 g_string_append(file->buf, buf);
389 file->bufp = file->buf->str + index;
394 void xml_truncate_buf(XMLFile *file)
398 len = file->bufp - file->buf->str;
400 g_string_erase(file->buf, 0, len);
401 file->bufp = file->buf->str;
405 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
409 tag = xml_get_current_tag(file);
411 if (tag && strcmp(tag->tag, name) == 0)
417 XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
421 node = g_new(XMLNode, 1);
423 node->element = g_strdup(text);
428 XMLTag *xml_tag_new(const gchar *tag)
432 new_tag = g_new(XMLTag, 1);
434 new_tag->tag = XML_STRING_ADD(tag);
437 new_tag->attr = NULL;
442 XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
446 new_attr = g_new(XMLAttr, 1);
447 new_attr->name = XML_STRING_ADD(name);
448 new_attr->value = g_strdup(value);
453 XMLAttr *xml_attr_new_int(const gchar *name, const gint value)
458 valuestr = g_strdup_printf("%d", value);
460 new_attr = g_new(XMLAttr, 1);
461 new_attr->name = XML_STRING_ADD(name);
462 new_attr->value = g_strdup(valuestr);
469 void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
471 tag->attr = g_list_append(tag->attr, attr);
474 XMLTag *xml_copy_tag(XMLTag *tag)
480 new_tag = xml_tag_new(tag->tag);
481 for (list = tag->attr; list != NULL; list = list->next) {
482 attr = xml_copy_attr((XMLAttr *)list->data);
483 xml_tag_add_attr(new_tag, attr);
489 XMLAttr *xml_copy_attr(XMLAttr *attr)
491 return xml_attr_new(attr->name, attr->value);
494 gint xml_unescape_str(gchar *str)
503 while ((start = strchr(p, '&')) != NULL) {
504 if ((end = strchr(start + 1, ';')) == NULL) {
505 g_warning("Unescaped '&' appeared\n");
509 len = end - start + 1;
515 Xstrndup_a(esc_str, start, len, return -1);
516 if (!strcmp(esc_str, "<"))
518 else if (!strcmp(esc_str, ">"))
520 else if (!strcmp(esc_str, "&"))
522 else if (!strcmp(esc_str, "'"))
524 else if (!strcmp(esc_str, """))
532 memmove(start + 1, end + 1, strlen(end + 1) + 1);
539 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
541 const gchar *src_codeset = CS_INTERNAL;
542 const gchar *dest_codeset = CS_INTERNAL;
543 gchar *tmpstr = NULL;
546 g_return_val_if_fail(fp != NULL, -1);
550 tmpstr = conv_codeset_strdup(str, src_codeset, dest_codeset);
552 g_warning("xml_file_put_escape_str(): Faild to convert character set.");
553 tmpstr = g_strdup(str);
556 for (p = tmpstr; *p != '\0'; p++) {
559 result = fputs("<", fp);
562 result = fputs(">", fp);
565 result = fputs("&", fp);
568 result = fputs("'", fp);
571 result = fputs(""", fp);
574 result = fputc(*p, fp);
580 return (result == EOF ? -1 : 0);
583 gint xml_file_put_xml_decl(FILE *fp)
585 g_return_val_if_fail(fp != NULL, -1);
586 XML_STRING_TABLE_CREATE();
588 return fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL);
591 gint xml_file_put_node(FILE *fp, XMLNode *node)
595 g_return_val_if_fail(fp != NULL, -1);
596 g_return_val_if_fail(node != NULL, -1);
598 fprintf(fp, "<%s", node->tag->tag);
600 for (cur = node->tag->attr; cur != NULL; cur = cur->next) {
601 XMLAttr *attr = (XMLAttr *)cur->data;
602 fprintf(fp, " %s=\"", attr->name);
603 xml_file_put_escape_str(fp, attr->value);
609 xml_file_put_escape_str(fp, node->element);
610 fprintf(fp, "</%s>\n", node->tag->tag);
618 void xml_free_node(XMLNode *node)
622 xml_free_tag(node->tag);
623 g_free(node->element);
627 static gboolean xml_free_func(GNode *node, gpointer data)
629 XMLNode *xmlnode = node->data;
631 xml_free_node(xmlnode);
635 void xml_free_tree(GNode *node)
637 g_return_if_fail(node != NULL);
639 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
642 g_node_destroy(node);
645 void xml_free_tag(XMLTag *tag)
649 XML_STRING_FREE(tag->tag);
650 while (tag->attr != NULL) {
651 XMLAttr *attr = (XMLAttr *)tag->attr->data;
652 tag->attr = g_list_remove(tag->attr, tag->attr->data);
653 XML_STRING_FREE(attr->name);
654 g_free(attr->value); /* __not__ XML_STRING_FREE */
660 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
667 while ((start = strchr(file->bufp, '<')) == NULL)
668 if (xml_read_line(file) < 0) return -1;
673 while ((end = strchr(file->bufp, '>')) == NULL)
674 if (xml_read_line(file) < 0) return -1;
676 strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
678 file->bufp = end + 1;
679 xml_truncate_buf(file);
687 g_warning("failed to write part of xml tree\n"); \
691 static int xml_write_tree_recursive(GNode *node, FILE *fp)
697 g_return_val_if_fail(node != NULL, -1);
698 g_return_val_if_fail(fp != NULL, -1);
700 depth = g_node_depth(node) - 1;
701 for (i = 0; i < depth; i++)
702 TRY(fputs(" ", fp) != EOF);
704 tag = ((XMLNode *) node->data)->tag;
706 TRY(fprintf(fp, "<%s", tag->tag) > 0);
708 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
709 XMLAttr *attr = (XMLAttr *) cur->data;
711 TRY(fprintf(fp, " %s=\"", attr->name) > 0);
712 TRY(xml_file_put_escape_str(fp, attr->value) == 0);
713 TRY(fputs("\"", fp) != EOF);
717 if (node->children) {
721 child = node->children;
727 TRY(xml_write_tree_recursive(cur, fp) == 0);
730 for (i = 0; i < depth; i++)
731 TRY(fputs(" ", fp) != EOF);
732 TRY(fprintf(fp, "</%s>\n", tag->tag) > 0);
734 TRY(fputs(" />\n", fp) != EOF);
741 int xml_write_tree(GNode *node, FILE *fp)
743 return xml_write_tree_recursive(node, fp);
746 static gpointer copy_node_func(gpointer nodedata, gpointer data)
748 XMLNode *xmlnode = (XMLNode *) nodedata;
751 newxmlnode = g_new0(XMLNode, 1);
752 newxmlnode->tag = xml_copy_tag(xmlnode->tag);
753 newxmlnode->element = g_strdup(xmlnode->element);
758 GNode *xml_copy_tree(GNode *node)
760 return g_node_map(node, copy_node_func, NULL);