2005-09-30 [paul] 1.9.14cvs63
[claws.git] / src / common / xml.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 Hiroyuki Yamamoto
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 2 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, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
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 /* UGLY! */
28 #include "../codeconv.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
38 static void xml_string_table_create(void)
39 {
40         if (xml_string_table == NULL)
41                 xml_string_table = string_table_new();
42 }
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))
47
48 #define XML_STRING_TABLE_CREATE() \
49         xml_string_table_create()
50
51 #else /* !SPARSE_MEMORY */
52
53 #define XML_STRING_ADD(str) \
54         g_strdup(str)
55 #define XML_STRING_FREE(str) \
56         g_free(str)
57
58 #define XML_STRING_TABLE_CREATE()
59
60 #endif /* SPARSE_MEMORY */
61
62 static gint xml_get_parenthesis (XMLFile        *file,
63                                  gchar          *buf,
64                                  gint            len);
65
66 XMLFile *xml_open_file(const gchar *path)
67 {
68         XMLFile *newfile;
69
70         g_return_val_if_fail(path != NULL, NULL);
71
72         newfile = g_new(XMLFile, 1);
73
74         newfile->fp = g_fopen(path, "rb");
75         if (!newfile->fp) {
76                 g_free(newfile);
77                 return NULL;
78         }
79
80         XML_STRING_TABLE_CREATE();
81
82         newfile->buf = g_string_new(NULL);
83         newfile->bufp = newfile->buf->str;
84
85         newfile->dtd = NULL;
86         newfile->encoding = NULL;
87         newfile->tag_stack = NULL;
88         newfile->level = 0;
89         newfile->is_empty_element = FALSE;
90
91         return newfile;
92 }
93
94 void xml_close_file(XMLFile *file)
95 {
96         g_return_if_fail(file != NULL);
97
98         if (file->fp) fclose(file->fp);
99
100         g_string_free(file->buf, TRUE);
101
102         g_free(file->dtd);
103         g_free(file->encoding);
104
105         while (file->tag_stack != NULL)
106                 xml_pop_tag(file);
107
108         g_free(file);
109 }
110
111 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
112 {
113         GNode *node = NULL;
114         XMLNode *xmlnode;
115         XMLTag *tag;
116
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");
121                         break;
122                 }
123
124                 tag = xml_get_current_tag(file);
125                 if (!tag) break;
126                 xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
127                 xmlnode->element = xml_get_element(file);
128                 if (!parent)
129                         node = g_node_new(xmlnode);
130                 else
131                         node = g_node_append_data(parent, xmlnode);
132
133                 xml_build_tree(file, node, file->level);
134                 if (file->level == 0) break;
135         }
136
137         return node;
138 }
139
140 GNode *xml_parse_file(const gchar *path)
141 {
142         XMLFile *file;
143         GNode *node;
144
145         file = xml_open_file(path);
146         g_return_val_if_fail(file != NULL, NULL);
147
148         xml_get_dtd(file);
149
150         node = xml_build_tree(file, NULL, file->level);
151
152         xml_close_file(file);
153
154 #if defined(SPARSE_MEMORY)
155         if (debug_get_mode())
156                 string_table_get_stats(xml_string_table);
157 #endif
158
159         return node;
160 }
161
162 gint xml_get_dtd(XMLFile *file)
163 {
164         gchar buf[XMLBUFSIZE];
165         gchar *bufp = buf;
166
167         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
168
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=\""))) {
175                         bufp += 9;
176                         extract_quote(bufp, '"');
177                         file->encoding = g_strdup(bufp);
178                 } else
179                         file->encoding = g_strdup(CS_INTERNAL);
180         } else {
181                 g_warning("Can't get xml dtd\n");
182                 return -1;
183         }
184
185         return 0;
186 }
187
188 gint xml_parse_next_tag(XMLFile *file)
189 {
190         gchar buf[XMLBUFSIZE];
191         gchar *bufp = buf;
192         gchar *tag_str;
193         XMLTag *tag;
194         gint len;
195
196         if (file->is_empty_element == TRUE) {
197                 file->is_empty_element = FALSE;
198                 xml_pop_tag(file);
199                 return 0;
200         }
201
202         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
203                 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
204                 return -1;
205         }
206
207         /* end-tag */
208         if (buf[0] == '/') {
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);
211                         return -1;
212                 }
213                 xml_pop_tag(file);
214                 return 0;
215         }
216
217         tag = xml_tag_new(NULL);
218         xml_push_tag(file, tag);
219
220         len = strlen(buf);
221         if (len > 0 && buf[len - 1] == '/') {
222                 file->is_empty_element = TRUE;
223                 buf[len - 1] = '\0';
224                 g_strchomp(buf);
225         }
226         if (strlen(buf) == 0) {
227                 g_warning("xml_parse_next_tag(): Tag name is empty\n");
228                 return -1;
229         }
230
231         while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++;
232         if (*bufp == '\0') {
233                 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
234                 if (tag_str) {
235                         tag->tag = XML_STRING_ADD(tag_str);
236                         g_free(tag_str);
237                 } else
238                         tag->tag = XML_STRING_ADD(buf);
239                 return 0;
240         } else {
241                 *bufp++ = '\0';
242                 tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
243                 if (tag_str) {
244                         tag->tag = XML_STRING_ADD(tag_str);
245                         g_free(tag_str);
246                 } else
247                         tag->tag = XML_STRING_ADD(buf);
248         }
249
250         /* parse attributes ( name=value ) */
251         while (*bufp) {
252                 XMLAttr *attr;
253                 gchar *attr_name;
254                 gchar *attr_value;
255                 gchar *utf8_attr_name;
256                 gchar *utf8_attr_value;
257                 gchar *p;
258                 gchar quote;
259
260                 while (g_ascii_isspace(*bufp)) bufp++;
261                 attr_name = bufp;
262                 if ((p = strchr(attr_name, '=')) == NULL) {
263                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
264                         return -1;
265                 }
266                 bufp = p;
267                 *bufp++ = '\0';
268                 while (g_ascii_isspace(*bufp)) bufp++;
269
270                 if (*bufp != '"' && *bufp != '\'') {
271                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
272                         return -1;
273                 }
274                 quote = *bufp;
275                 bufp++;
276                 attr_value = bufp;
277                 if ((p = strchr(attr_value, quote)) == NULL) {
278                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
279                         return -1;
280                 }
281                 bufp = p;
282                 *bufp++ = '\0';
283
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);
290                 if (!utf8_attr_name)
291                         utf8_attr_name = g_strdup(attr_name);
292                 if (!utf8_attr_value)
293                         utf8_attr_value = g_strdup(attr_value);
294
295                 attr = xml_attr_new(utf8_attr_name, utf8_attr_value);
296                 xml_tag_add_attr(tag, attr);
297
298                 g_free(utf8_attr_value);
299                 g_free(utf8_attr_name);
300         }
301
302         return 0;
303 }
304
305 void xml_push_tag(XMLFile *file, XMLTag *tag)
306 {
307         g_return_if_fail(tag != NULL);
308
309         file->tag_stack = g_list_prepend(file->tag_stack, tag);
310         file->level++;
311 }
312
313 void xml_pop_tag(XMLFile *file)
314 {
315         XMLTag *tag;
316
317         if (!file->tag_stack) return;
318
319         tag = (XMLTag *)file->tag_stack->data;
320
321         xml_free_tag(tag);
322         file->tag_stack = g_list_remove(file->tag_stack, tag);
323         file->level--;
324 }
325
326 XMLTag *xml_get_current_tag(XMLFile *file)
327 {
328         if (file->tag_stack)
329                 return (XMLTag *)file->tag_stack->data;
330         else
331                 return NULL;
332 }
333
334 GList *xml_get_current_tag_attr(XMLFile *file)
335 {
336         XMLTag *tag;
337
338         tag = xml_get_current_tag(file);
339         if (!tag) return NULL;
340
341         return tag->attr;
342 }
343
344 gchar *xml_get_element(XMLFile *file)
345 {
346         gchar *str;
347         gchar *new_str;
348         gchar *end;
349
350         while ((end = strchr(file->bufp, '<')) == NULL)
351                 if (xml_read_line(file) < 0) return NULL;
352
353         if (end == file->bufp)
354                 return NULL;
355
356         str = g_strndup(file->bufp, end - file->bufp);
357         /* this is not XML1.0 strict */
358         g_strstrip(str);
359         xml_unescape_str(str);
360
361         file->bufp = end;
362         xml_truncate_buf(file);
363
364         if (str[0] == '\0') {
365                 g_free(str);
366                 return NULL;
367         }
368
369         new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL);
370         if (!new_str)
371                 new_str = g_strdup(str);
372         g_free(str);
373
374         return new_str;
375 }
376
377 gint xml_read_line(XMLFile *file)
378 {
379         gchar buf[XMLBUFSIZE];
380         gint index;
381
382         if (fgets(buf, sizeof(buf), file->fp) == NULL)
383                 return -1;
384
385         index = file->bufp - file->buf->str;
386
387         g_string_append(file->buf, buf);
388
389         file->bufp = file->buf->str + index;
390
391         return 0;
392 }
393
394 void xml_truncate_buf(XMLFile *file)
395 {
396         gint len;
397
398         len = file->bufp - file->buf->str;
399         if (len > 0) {
400                 g_string_erase(file->buf, 0, len);
401                 file->bufp = file->buf->str;
402         }
403 }
404
405 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
406 {
407         XMLTag *tag;
408
409         tag = xml_get_current_tag(file);
410
411         if (tag && strcmp(tag->tag, name) == 0)
412                 return TRUE;
413         else
414                 return FALSE;
415 }
416
417 XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
418 {
419         XMLNode *node;
420
421         node = g_new(XMLNode, 1);
422         node->tag = tag;
423         node->element = g_strdup(text);
424
425         return node;
426 }
427
428 XMLTag *xml_tag_new(const gchar *tag)
429 {
430         XMLTag *new_tag;
431  
432         new_tag = g_new(XMLTag, 1);
433         if (tag)
434                 new_tag->tag = XML_STRING_ADD(tag);
435         else
436                 new_tag->tag = NULL;
437         new_tag->attr = NULL;
438  
439         return new_tag;
440 }
441
442 XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
443 {
444         XMLAttr *new_attr;
445  
446         new_attr = g_new(XMLAttr, 1);
447         new_attr->name = XML_STRING_ADD(name);
448         new_attr->value = g_strdup(value);
449  
450         return new_attr;
451 }
452
453 XMLAttr *xml_attr_new_int(const gchar *name, const gint value)
454 {
455         XMLAttr *new_attr;
456         gchar *valuestr;
457
458         valuestr = g_strdup_printf("%d", value);
459
460         new_attr = g_new(XMLAttr, 1);
461         new_attr->name = XML_STRING_ADD(name);
462         new_attr->value = g_strdup(valuestr);
463  
464         g_free(valuestr);
465
466         return new_attr;
467 }
468
469 void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
470 {
471         tag->attr = g_list_append(tag->attr, attr);
472 }
473
474 XMLTag *xml_copy_tag(XMLTag *tag)
475 {
476         XMLTag *new_tag;
477         XMLAttr *attr;
478         GList *list;
479
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);
484         }
485
486         return new_tag;
487 }
488
489 XMLAttr *xml_copy_attr(XMLAttr *attr)
490 {
491         return xml_attr_new(attr->name, attr->value);
492 }
493
494 gint xml_unescape_str(gchar *str)
495 {
496         gchar *start;
497         gchar *end;
498         gchar *p = str;
499         gchar *esc_str;
500         gchar ch;
501         gint len;
502
503         while ((start = strchr(p, '&')) != NULL) {
504                 if ((end = strchr(start + 1, ';')) == NULL) {
505                         g_warning("Unescaped '&' appeared\n");
506                         p = start + 1;
507                         continue;
508                 }
509                 len = end - start + 1;
510                 if (len < 3) {
511                         p = end + 1;
512                         continue;
513                 }
514
515                 Xstrndup_a(esc_str, start, len, return -1);
516                 if (!strcmp(esc_str, "&lt;"))
517                         ch = '<';
518                 else if (!strcmp(esc_str, "&gt;"))
519                         ch = '>';
520                 else if (!strcmp(esc_str, "&amp;"))
521                         ch = '&';
522                 else if (!strcmp(esc_str, "&apos;"))
523                         ch = '\'';
524                 else if (!strcmp(esc_str, "&quot;"))
525                         ch = '\"';
526                 else {
527                         p = end + 1;
528                         continue;
529                 }
530
531                 *start = ch;
532                 memmove(start + 1, end + 1, strlen(end + 1) + 1);
533                 p = start + 1;
534         }
535
536         return 0;
537 }
538
539 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
540 {
541         const gchar *src_codeset = CS_INTERNAL;
542         const gchar *dest_codeset = CS_INTERNAL;
543         gchar *tmpstr = NULL;
544         const gchar *p;
545
546         g_return_val_if_fail(fp != NULL, -1);
547
548         if (!str) return 0;
549
550         tmpstr = conv_codeset_strdup(str, src_codeset, dest_codeset);
551         if (!tmpstr) {
552                 g_warning("xml_file_put_escape_str(): Faild to convert character set.");
553                 tmpstr = g_strdup(str);
554         }
555
556         for (p = tmpstr; *p != '\0'; p++) {
557                 switch (*p) {
558                 case '<':
559                         fputs("&lt;", fp);
560                         break;
561                 case '>':
562                         fputs("&gt;", fp);
563                         break;
564                 case '&':
565                         fputs("&amp;", fp);
566                         break;
567                 case '\'':
568                         fputs("&apos;", fp);
569                         break;
570                 case '\"':
571                         fputs("&quot;", fp);
572                         break;
573                 default:
574                         fputc(*p, fp);
575                 }
576         }
577
578         g_free(tmpstr);
579
580         return 0;
581 }
582
583 gint xml_file_put_xml_decl(FILE *fp)
584 {
585         g_return_val_if_fail(fp != NULL, -1);
586         XML_STRING_TABLE_CREATE();
587
588         fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL);
589         return 0;
590 }
591
592 gint xml_file_put_node(FILE *fp, XMLNode *node)
593 {
594         GList *cur;
595
596         g_return_val_if_fail(fp != NULL, -1);
597         g_return_val_if_fail(node != NULL, -1);
598
599         fprintf(fp, "<%s", node->tag->tag);
600
601         for (cur = node->tag->attr; cur != NULL; cur = cur->next) {
602                 XMLAttr *attr = (XMLAttr *)cur->data;
603                 fprintf(fp, " %s=\"", attr->name);
604                 xml_file_put_escape_str(fp, attr->value);
605                 fputs("\"", fp);
606         }
607
608         if (node->element) {
609                 fputs(">", fp);
610                 xml_file_put_escape_str(fp, node->element);
611                 fprintf(fp, "</%s>\n", node->tag->tag);
612         } else {
613                 fputs(" />\n", fp);
614         }
615
616         return 0;
617 }
618
619 void xml_free_node(XMLNode *node)
620 {
621         if (!node) return;
622
623         xml_free_tag(node->tag);
624         g_free(node->element);
625         g_free(node);
626 }
627
628 static gboolean xml_free_func(GNode *node, gpointer data)
629 {
630         XMLNode *xmlnode = node->data;
631
632         xml_free_node(xmlnode);
633         return FALSE;
634 }
635
636 void xml_free_tree(GNode *node)
637 {
638         g_return_if_fail(node != NULL);
639
640         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
641                         NULL);
642
643         g_node_destroy(node);
644 }
645
646 void xml_free_tag(XMLTag *tag)
647 {
648         if (!tag) return;
649
650         XML_STRING_FREE(tag->tag);
651         while (tag->attr != NULL) {
652                 XMLAttr *attr = (XMLAttr *)tag->attr->data;
653                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
654                 XML_STRING_FREE(attr->name);
655                 g_free(attr->value); /* __not__ XML_STRING_FREE */
656                 g_free(attr);
657         }
658         g_free(tag);
659 }
660
661 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
662 {
663         gchar *start;
664         gchar *end;
665
666         buf[0] = '\0';
667
668         while ((start = strchr(file->bufp, '<')) == NULL)
669                 if (xml_read_line(file) < 0) return -1;
670
671         start++;
672         file->bufp = start;
673
674         while ((end = strchr(file->bufp, '>')) == NULL)
675                 if (xml_read_line(file) < 0) return -1;
676
677         strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
678         g_strstrip(buf);
679         file->bufp = end + 1;
680         xml_truncate_buf(file);
681
682         return 0;
683 }
684
685 static void xml_write_tree_recursive(GNode *node, FILE *fp)
686 {
687         gint i, depth;
688         XMLTag *tag;
689         GList *cur;
690
691         g_return_if_fail(node != NULL);
692         g_return_if_fail(fp != NULL);
693
694         depth = g_node_depth(node) - 1;
695         for (i = 0; i < depth; i++)
696                 fputs("    ", fp);
697         tag = ((XMLNode *) node->data)->tag;
698
699         fprintf(fp, "<%s", tag->tag);
700         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
701                 XMLAttr *attr = (XMLAttr *) cur->data;
702
703                 fprintf(fp, " %s=\"", attr->name);
704                 xml_file_put_escape_str(fp, attr->value);
705                 fputs("\"", fp);
706         }
707
708         if (node->children) {
709                 GNode *child;
710                 fputs(">\n", fp);
711
712                 child = node->children;
713                 while (child) {
714                         GNode *cur;
715
716                         cur = child;
717                         child = cur->next;
718                         xml_write_tree_recursive(cur, fp);
719                 }
720
721                 for (i = 0; i < depth; i++)
722                         fputs("    ", fp);
723                 fprintf(fp, "</%s>\n", tag->tag);
724         } else
725                 fputs(" />\n", fp);
726 }
727
728 void xml_write_tree(GNode *node, FILE *fp)
729 {
730         xml_write_tree_recursive(node, fp);
731 }
732
733 static gpointer copy_node_func(gpointer nodedata, gpointer data)
734 {
735         XMLNode *xmlnode = (XMLNode *) nodedata;
736         XMLNode *newxmlnode;
737         
738         newxmlnode = g_new0(XMLNode, 1);
739         newxmlnode->tag = xml_copy_tag(xmlnode->tag);
740         newxmlnode->element = g_strdup(xmlnode->element);
741
742         return newxmlnode;
743 }
744
745 GNode *xml_copy_tree(GNode *node)
746 {
747         return g_node_map(node, copy_node_func, NULL);
748 }