2 * Claws Mail -- 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/>.
22 #include "claws-features.h"
33 #include "file-utils.h"
36 /* if this is defined all attr.names and tag.names are stored
38 #if defined(SPARSE_MEMORY)
39 #include "stringtable.h"
41 static StringTable
*xml_string_table
;
42 static XMLTag
*xml_copy_tag (XMLTag
*tag
);
43 static XMLAttr
*xml_copy_attr (XMLAttr
*attr
);
44 static void xml_free_node (XMLNode
*node
);
45 static void xml_free_tag (XMLTag
*tag
);
46 static void xml_pop_tag (XMLFile
*file
);
47 static void xml_push_tag (XMLFile
*file
,
49 static gint
xml_read_line (XMLFile
*file
);
50 static void xml_truncate_buf (XMLFile
*file
);
51 static gint
xml_unescape_str (gchar
*str
);
53 static void xml_string_table_create(void)
55 if (xml_string_table
== NULL
)
56 xml_string_table
= string_table_new();
58 #define XML_STRING_ADD(str) \
59 string_table_insert_string(xml_string_table, (str))
60 #define XML_STRING_FREE(str) \
61 string_table_free_string(xml_string_table, (str))
63 #define XML_STRING_TABLE_CREATE() \
64 xml_string_table_create()
66 #else /* !SPARSE_MEMORY */
68 #define XML_STRING_ADD(str) \
70 #define XML_STRING_FREE(str) \
73 #define XML_STRING_TABLE_CREATE()
75 #endif /* SPARSE_MEMORY */
77 static gint
xml_get_parenthesis (XMLFile
*file
,
81 XMLFile
*xml_open_file(const gchar
*path
)
85 cm_return_val_if_fail(path
!= NULL
, NULL
);
87 newfile
= g_new(XMLFile
, 1);
89 newfile
->fp
= claws_fopen(path
, "rb");
91 FILE_OP_ERROR(path
, "fopen");
96 XML_STRING_TABLE_CREATE();
98 newfile
->buf
= g_string_new(NULL
);
99 newfile
->bufp
= newfile
->buf
->str
;
102 newfile
->encoding
= NULL
;
103 newfile
->tag_stack
= NULL
;
105 newfile
->is_empty_element
= FALSE
;
107 newfile
->path
= g_strdup(path
);
112 void xml_close_file(XMLFile
*file
)
114 cm_return_if_fail(file
!= NULL
);
116 if (file
->fp
) claws_fclose(file
->fp
);
118 g_string_free(file
->buf
, TRUE
);
121 g_free(file
->encoding
);
124 while (file
->tag_stack
!= NULL
)
130 static GNode
*xml_build_tree(XMLFile
*file
, GNode
*parent
, guint level
)
136 while (xml_parse_next_tag(file
) == 0) {
137 if (file
->level
< level
) break;
138 if (file
->level
== level
) {
139 g_warning("xml_build_tree(): parse error in %s", file
->path
);
143 tag
= xml_get_current_tag(file
);
145 xmlnode
= xml_node_new(xml_copy_tag(tag
), NULL
);
146 xmlnode
->element
= xml_get_element(file
);
148 node
= g_node_new(xmlnode
);
150 node
= g_node_append_data(parent
, xmlnode
);
152 xml_build_tree(file
, node
, file
->level
);
153 if (file
->level
== 0) break;
159 GNode
*xml_parse_file(const gchar
*path
)
164 file
= xml_open_file(path
);
170 node
= xml_build_tree(file
, NULL
, file
->level
);
172 xml_close_file(file
);
174 #if defined(SPARSE_MEMORY)
175 if (debug_get_mode())
176 string_table_get_stats(xml_string_table
);
182 gint
xml_get_dtd(XMLFile
*file
)
184 gchar buf
[XMLBUFSIZE
];
187 if (xml_get_parenthesis(file
, buf
, sizeof(buf
)) < 0) return -1;
189 if ((*bufp
++ == '?') &&
190 (bufp
= strcasestr(bufp
, "xml")) &&
191 (bufp
= strcasestr(bufp
+ 3, "version")) &&
192 (bufp
= strchr(bufp
+ 7, '?'))) {
193 file
->dtd
= g_strdup(buf
);
194 if ((bufp
= strcasestr(buf
, "encoding=\""))) {
196 extract_quote(bufp
, '"');
197 file
->encoding
= g_strdup(bufp
);
198 file
->need_codeconv
=
199 g_strcmp0(bufp
, CS_INTERNAL
);
201 file
->encoding
= g_strdup(CS_INTERNAL
);
202 file
->need_codeconv
= FALSE
;
205 g_warning("can't get XML DTD in %s", file
->path
);
212 gint
xml_parse_next_tag(XMLFile
*file
)
214 gchar buf
[XMLBUFSIZE
];
221 if (file
->is_empty_element
== TRUE
) {
222 file
->is_empty_element
= FALSE
;
227 if (xml_get_parenthesis(file
, buf
, sizeof(buf
)) < 0) {
228 g_warning("xml_parse_next_tag(): can't parse next tag in %s", file
->path
);
236 if (strcmp(xml_get_current_tag(file
)->tag
, buf
+ 1) != 0) {
237 g_warning("xml_parse_next_tag(): tag name mismatch in %s : %s (%s)", file
->path
, buf
, xml_get_current_tag(file
)->tag
);
244 if (len
>= 7 && !strncmp(buf
, "!-- ", 4) && !strncmp(buf
+len
-3, " --", 3)) {
249 tag
= xml_tag_new(NULL
);
250 xml_push_tag(file
, tag
);
252 if (len
> 0 && buf
[len
- 1] == '/') {
253 file
->is_empty_element
= TRUE
;
258 if (strlen(buf
) == 0) {
259 g_warning("xml_parse_next_tag(): tag name is empty in %s", file
->path
);
263 while (*bufp
!= '\0' && !g_ascii_isspace(*bufp
)) bufp
++;
265 if (file
->need_codeconv
) {
266 tag_str
= conv_codeset_strdup(buf
, file
->encoding
, CS_INTERNAL
);
268 tag
->tag
= XML_STRING_ADD(tag_str
);
271 tag
->tag
= XML_STRING_ADD(buf
);
273 tag
->tag
= XML_STRING_ADD(buf
);
277 if (file
->need_codeconv
) {
278 tag_str
= conv_codeset_strdup(buf
, file
->encoding
, CS_INTERNAL
);
280 tag
->tag
= XML_STRING_ADD(tag_str
);
283 tag
->tag
= XML_STRING_ADD(buf
);
285 tag
->tag
= XML_STRING_ADD(buf
);
288 /* parse attributes ( name=value ) */
293 gchar
*utf8_attr_name
;
294 gchar
*utf8_attr_value
;
298 while (g_ascii_isspace(*bufp
)) bufp
++;
300 if ((p
= strchr(attr_name
, '=')) == NULL
) {
301 g_warning("xml_parse_next_tag(): syntax error in %s, tag (a) %s", file
->path
, attr_name
);
306 while (g_ascii_isspace(*bufp
)) bufp
++;
308 if (*bufp
!= '"' && *bufp
!= '\'') {
309 g_warning("xml_parse_next_tag(): syntax error in %s, tag (b) %s", file
->path
, bufp
);
315 if ((p
= strchr(attr_value
, quote
)) == NULL
) {
316 g_warning("xml_parse_next_tag(): syntax error in %s, tag (c) %s", file
->path
, attr_value
);
322 g_strchomp(attr_name
);
323 xml_unescape_str(attr_value
);
324 if (file
->need_codeconv
) {
325 utf8_attr_name
= conv_codeset_strdup
326 (attr_name
, file
->encoding
, CS_INTERNAL
);
327 utf8_attr_value
= conv_codeset_strdup
328 (attr_value
, file
->encoding
, CS_INTERNAL
);
330 utf8_attr_name
= g_strdup(attr_name
);
331 if (!utf8_attr_value
)
332 utf8_attr_value
= g_strdup(attr_value
);
334 attr
= xml_attr_new(utf8_attr_name
, utf8_attr_value
);
335 g_free(utf8_attr_value
);
336 g_free(utf8_attr_name
);
338 attr
= xml_attr_new(attr_name
, attr_value
);
340 xml_tag_add_attr(tag
, attr
);
343 tag
->attr
= g_list_reverse(tag
->attr
);
348 static void xml_push_tag(XMLFile
*file
, XMLTag
*tag
)
350 cm_return_if_fail(tag
!= NULL
);
352 file
->tag_stack
= g_list_prepend(file
->tag_stack
, tag
);
356 static void xml_pop_tag(XMLFile
*file
)
360 if (!file
->tag_stack
) return;
362 tag
= (XMLTag
*)file
->tag_stack
->data
;
365 file
->tag_stack
= g_list_remove(file
->tag_stack
, tag
);
369 XMLTag
*xml_get_current_tag(XMLFile
*file
)
372 return (XMLTag
*)file
->tag_stack
->data
;
377 GList
*xml_get_current_tag_attr(XMLFile
*file
)
381 tag
= xml_get_current_tag(file
);
382 if (!tag
) return NULL
;
387 gchar
*xml_get_element(XMLFile
*file
)
393 while ((end
= strchr(file
->bufp
, '<')) == NULL
)
394 if (xml_read_line(file
) < 0) return NULL
;
396 if (end
== file
->bufp
)
399 str
= g_strndup(file
->bufp
, end
- file
->bufp
);
400 /* this is not XML1.0 strict */
402 xml_unescape_str(str
);
405 xml_truncate_buf(file
);
407 if (str
[0] == '\0') {
412 if (!file
->need_codeconv
)
415 new_str
= conv_codeset_strdup(str
, file
->encoding
, CS_INTERNAL
);
417 new_str
= g_strdup(str
);
423 static gint
xml_read_line(XMLFile
*file
)
425 gchar buf
[XMLBUFSIZE
];
428 if (claws_fgets(buf
, sizeof(buf
), file
->fp
) == NULL
)
431 index
= file
->bufp
- file
->buf
->str
;
433 g_string_append(file
->buf
, buf
);
435 file
->bufp
= file
->buf
->str
+ index
;
440 static void xml_truncate_buf(XMLFile
*file
)
444 len
= file
->bufp
- file
->buf
->str
;
446 g_string_erase(file
->buf
, 0, len
);
447 file
->bufp
= file
->buf
->str
;
451 gboolean
xml_compare_tag(XMLFile
*file
, const gchar
*name
)
455 tag
= xml_get_current_tag(file
);
457 if (tag
&& strcmp(tag
->tag
, name
) == 0)
463 XMLNode
*xml_node_new(XMLTag
*tag
, const gchar
*text
)
467 node
= g_new(XMLNode
, 1);
469 node
->element
= g_strdup(text
);
474 XMLTag
*xml_tag_new(const gchar
*tag
)
478 new_tag
= g_new(XMLTag
, 1);
480 new_tag
->tag
= XML_STRING_ADD(tag
);
483 new_tag
->attr
= NULL
;
488 XMLAttr
*xml_attr_new(const gchar
*name
, const gchar
*value
)
492 new_attr
= g_new(XMLAttr
, 1);
493 new_attr
->name
= XML_STRING_ADD(name
);
494 new_attr
->value
= g_strdup(value
);
499 XMLAttr
*xml_attr_new_int(const gchar
*name
, const gint value
)
504 valuestr
= g_strdup_printf("%d", value
);
506 new_attr
= g_new(XMLAttr
, 1);
507 new_attr
->name
= XML_STRING_ADD(name
);
508 new_attr
->value
= valuestr
;
513 void xml_tag_add_attr(XMLTag
*tag
, XMLAttr
*attr
)
515 tag
->attr
= g_list_prepend(tag
->attr
, attr
);
518 static XMLTag
*xml_copy_tag(XMLTag
*tag
)
524 new_tag
= xml_tag_new(tag
->tag
);
525 for (list
= tag
->attr
; list
!= NULL
; list
= list
->next
) {
526 attr
= xml_copy_attr((XMLAttr
*)list
->data
);
527 xml_tag_add_attr(new_tag
, attr
);
529 tag
->attr
= g_list_reverse(tag
->attr
);
534 static XMLAttr
*xml_copy_attr(XMLAttr
*attr
)
536 return xml_attr_new(attr
->name
, attr
->value
);
539 static gint
xml_unescape_str(gchar
*str
)
548 while ((start
= strchr(p
, '&')) != NULL
) {
549 if ((end
= strchr(start
+ 1, ';')) == NULL
) {
550 g_warning("unescaped '&' appeared");
554 len
= end
- start
+ 1;
560 Xstrndup_a(esc_str
, start
, len
, return -1);
561 if (!strcmp(esc_str
, "<"))
563 else if (!strcmp(esc_str
, ">"))
565 else if (!strcmp(esc_str
, "&"))
567 else if (!strcmp(esc_str
, "'"))
569 else if (!strcmp(esc_str
, """))
577 memmove(start
+ 1, end
+ 1, strlen(end
+ 1) + 1);
584 gint
xml_file_put_escape_str(FILE *fp
, const gchar
*str
)
588 cm_return_val_if_fail(fp
!= NULL
, -1);
592 for (p
= str
; *p
!= '\0'; p
++) {
595 result
= claws_fputs("<", fp
);
598 result
= claws_fputs(">", fp
);
601 result
= claws_fputs("&", fp
);
604 result
= claws_fputs("'", fp
);
607 result
= claws_fputs(""", fp
);
610 result
= claws_fputc(*p
, fp
);
614 return (result
== EOF
? -1 : 0);
617 gint
xml_file_put_xml_decl(FILE *fp
)
619 cm_return_val_if_fail(fp
!= NULL
, -1);
620 XML_STRING_TABLE_CREATE();
622 return fprintf(fp
, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL
);
625 static void xml_free_node(XMLNode
*node
)
629 xml_free_tag(node
->tag
);
630 g_free(node
->element
);
634 static gboolean
xml_free_func(GNode
*node
, gpointer data
)
636 XMLNode
*xmlnode
= node
->data
;
638 xml_free_node(xmlnode
);
642 void xml_free_tree(GNode
*node
)
644 cm_return_if_fail(node
!= NULL
);
646 g_node_traverse(node
, G_PRE_ORDER
, G_TRAVERSE_ALL
, -1, xml_free_func
,
649 g_node_destroy(node
);
652 static void xml_free_tag(XMLTag
*tag
)
656 XML_STRING_FREE(tag
->tag
);
657 while (tag
->attr
!= NULL
) {
658 XMLAttr
*attr
= (XMLAttr
*)tag
->attr
->data
;
659 tag
->attr
= g_list_remove(tag
->attr
, tag
->attr
->data
);
660 XML_STRING_FREE(attr
->name
);
661 g_free(attr
->value
); /* __not__ XML_STRING_FREE */
667 static gint
xml_get_parenthesis(XMLFile
*file
, gchar
*buf
, gint len
)
674 while ((start
= strchr(file
->bufp
, '<')) == NULL
)
675 if (xml_read_line(file
) < 0) return -1;
680 while ((end
= strchr(file
->bufp
, '>')) == NULL
)
681 if (xml_read_line(file
) < 0) return -1;
683 strncpy2(buf
, file
->bufp
, MIN(end
- file
->bufp
+ 1, len
));
685 file
->bufp
= end
+ 1;
686 xml_truncate_buf(file
);
694 g_warning("failed to write part of XML tree"); \
698 static int xml_write_tree_recursive(GNode *node, FILE *fp)
704 cm_return_val_if_fail(node
!= NULL
, -1);
705 cm_return_val_if_fail(fp
!= NULL
, -1);
707 depth
= g_node_depth(node
) - 1;
708 for (i
= 0; i
< depth
; i
++)
709 TRY(claws_fputs(" ", fp
) != EOF
);
711 tag
= ((XMLNode
*) node
->data
)->tag
;
713 TRY(fprintf(fp
, "<%s", tag
->tag
) > 0);
715 for (cur
= tag
->attr
; cur
!= NULL
; cur
= g_list_next(cur
)) {
716 XMLAttr
*attr
= (XMLAttr
*) cur
->data
;
718 TRY(fprintf(fp
, " %s=\"", attr
->name
) > 0);
719 TRY(xml_file_put_escape_str(fp
, attr
->value
) == 0);
720 TRY(claws_fputs("\"", fp
) != EOF
);
724 if (node
->children
) {
726 TRY(claws_fputs(">\n", fp
) != EOF
);
728 child
= node
->children
;
734 TRY(xml_write_tree_recursive(cur
, fp
) == 0);
737 for (i
= 0; i
< depth
; i
++)
738 TRY(claws_fputs(" ", fp
) != EOF
);
739 TRY(fprintf(fp
, "</%s>\n", tag
->tag
) > 0);
741 TRY(claws_fputs(" />\n", fp
) != EOF
);
748 int xml_write_tree(GNode
*node
, FILE *fp
)
750 return xml_write_tree_recursive(node
, fp
);
753 static gpointer
copy_node_func(gpointer nodedata
, gpointer data
)
755 XMLNode
*xmlnode
= (XMLNode
*) nodedata
;
758 newxmlnode
= g_new0(XMLNode
, 1);
759 newxmlnode
->tag
= xml_copy_tag(xmlnode
->tag
);
760 newxmlnode
->element
= g_strdup(xmlnode
->element
);
765 GNode
*xml_copy_tree(GNode
*node
)
767 return g_node_map(node
, copy_node_func
, NULL
);