sync 098claws
[claws.git] / src / common / xml.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2001 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 #include "../codeconv.h"
28
29 #define SPARSE_MEMORY
30 /* if this is defined all attr.names and tag.names are stored
31  * in a hash table */
32 #if defined(SPARSE_MEMORY)
33 #include "stringtable.h" 
34
35 static StringTable *xml_string_table;
36
37 static void xml_string_table_create(void)
38 {
39         if (xml_string_table == NULL)
40                 xml_string_table = string_table_new();
41 }
42 #define XML_STRING_ADD(str) \
43         string_table_insert_string(xml_string_table, (str))
44 #define XML_STRING_FREE(str) \
45         string_table_free_string(xml_string_table, (str))
46
47 #define XML_STRING_TABLE_CREATE() \
48         xml_string_table_create()
49
50 #else /* !SPARSE_MEMORY */
51
52 #define XML_STRING_ADD(str) \
53         g_strdup(str)
54 #define XML_STRING_FREE(str) \
55         g_free(str)
56
57 #define XML_STRING_TABLE_CREATE()
58
59 #endif /* SPARSE_MEMORY */
60
61 static gint xml_get_parenthesis (XMLFile        *file,
62                                  gchar          *buf,
63                                  gint            len);
64
65 XMLFile *xml_open_file(const gchar *path)
66 {
67         XMLFile *newfile;
68
69         g_return_val_if_fail(path != NULL, NULL);
70
71         XML_STRING_TABLE_CREATE();
72
73         newfile = g_new(XMLFile, 1);
74
75         newfile->fp = fopen(path, "rb");
76         if (!newfile->fp) {
77                 g_free(newfile);
78                 return NULL;
79         }
80
81         newfile->buf = g_string_new(NULL);
82         newfile->bufp = newfile->buf->str;
83
84         newfile->dtd = NULL;
85         newfile->tag_stack = NULL;
86         newfile->level = 0;
87         newfile->is_empty_element = FALSE;
88
89         return newfile;
90 }
91
92 void xml_close_file(XMLFile *file)
93 {
94         g_return_if_fail(file != NULL);
95
96         if (file->fp) fclose(file->fp);
97
98         g_string_free(file->buf, TRUE);
99
100         g_free(file->dtd);
101
102         while (file->tag_stack != NULL)
103                 xml_pop_tag(file);
104
105         g_free(file);
106 }
107
108 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
109 {
110         GNode *node = NULL;
111         XMLNode *xmlnode;
112         XMLTag *tag;
113
114         while (xml_parse_next_tag(file) == 0) {
115                 if (file->level < level) break;
116                 if (file->level == level) {
117                         g_warning("xml_build_tree(): Parse error\n");
118                         break;
119                 }
120
121                 tag = xml_get_current_tag(file);
122                 if (!tag) break;
123                 xmlnode = g_new(XMLNode, 1);
124                 xmlnode->tag = xml_copy_tag(tag);
125                 xmlnode->element = xml_get_element(file);
126                 if (!parent)
127                         node = g_node_new(xmlnode);
128                 else
129                         node = g_node_append_data(parent, xmlnode);
130
131                 xml_build_tree(file, node, file->level);
132                 if (file->level == 0) break;
133         }
134
135         return node;
136 }
137
138 GNode *xml_parse_file(const gchar *path)
139 {
140         XMLFile *file;
141         GNode *node;
142
143         file = xml_open_file(path);
144         g_return_val_if_fail(file != NULL, NULL);
145
146         xml_get_dtd(file);
147
148         node = xml_build_tree(file, NULL, file->level);
149
150         xml_close_file(file);
151
152 #if defined(SPARSE_MEMORY)
153         if (debug_get_mode())
154                 string_table_get_stats(xml_string_table);
155 #endif
156
157         return node;
158 }
159
160 gint xml_get_dtd(XMLFile *file)
161 {
162         gchar buf[XMLBUFSIZE];
163         gchar *bufp = buf;
164
165         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
166
167         if ((*bufp++ == '?') &&
168             (bufp = strcasestr(bufp, "xml")) &&
169             (bufp = strcasestr(bufp + 3, "version")) &&
170             (bufp = strchr(bufp + 7, '?')))
171                 file->dtd = g_strdup(buf);
172         else {
173                 g_warning("Can't get xml dtd\n");
174                 return -1;
175         }
176
177         return 0;
178 }
179
180 gint xml_parse_next_tag(XMLFile *file)
181 {
182         gchar buf[XMLBUFSIZE];
183         gchar *bufp = buf;
184         XMLTag *tag;
185         gint len;
186
187         if (file->is_empty_element == TRUE) {
188                 file->is_empty_element = FALSE;
189                 xml_pop_tag(file);
190                 return 0;
191         }
192
193         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
194                 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
195                 return -1;
196         }
197
198         /* end-tag */
199         if (buf[0] == '/') {
200                 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
201                         g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf);
202                         return -1;
203                 }
204                 xml_pop_tag(file);
205                 return 0;
206         }
207
208         tag = g_new0(XMLTag, 1);
209         xml_push_tag(file, tag);
210
211         len = strlen(buf);
212         if (len > 0 && buf[len - 1] == '/') {
213                 file->is_empty_element = TRUE;
214                 buf[len - 1] = '\0';
215                 g_strchomp(buf);
216         }
217         if (strlen(buf) == 0) {
218                 g_warning("xml_parse_next_tag(): Tag name is empty\n");
219                 return -1;
220         }
221
222         while (*bufp != '\0' && !isspace(*bufp)) bufp++;
223         if (*bufp == '\0') {
224                 tag->tag = XML_STRING_ADD(buf);
225                 return 0;
226         } else {
227                 *bufp++ = '\0';
228                 tag->tag = XML_STRING_ADD(buf);
229         }
230
231         /* parse attributes ( name=value ) */
232         while (*bufp) {
233                 XMLAttr *attr;
234                 gchar *attr_name, *attr_value;
235                 gchar *utf8attr_name, *utf8attr_value;
236                 gchar *p;
237                 gchar quote;
238
239                 while (isspace(*bufp)) bufp++;
240                 attr_name = bufp;
241                 if ((p = strchr(attr_name, '=')) == NULL) {
242                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
243                         return -1;
244                 }
245                 bufp = p;
246                 *bufp++ = '\0';
247                 while (isspace(*bufp)) bufp++;
248
249                 if (*bufp != '"' && *bufp != '\'') {
250                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
251                         return -1;
252                 }
253                 quote = *bufp;
254                 bufp++;
255                 attr_value = bufp;
256                 if ((p = strchr(attr_value, quote)) == NULL) {
257                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
258                         return -1;
259                 }
260                 bufp = p;
261                 *bufp++ = '\0';
262
263                 g_strchomp(attr_name);
264                 xml_unescape_str(attr_value);
265
266                 attr = g_new(XMLAttr, 1);
267 #warning FIXME_GTK2
268                 utf8attr_name  = conv_codeset_strdup
269                                         (attr_name,
270                                          conv_get_current_charset_str(),
271                                          CS_UTF_8);
272                 utf8attr_value = conv_codeset_strdup
273                                         (attr_value,
274                                          conv_get_current_charset_str(),
275                                          CS_UTF_8);
276                 if (!utf8attr_name) {
277                         g_warning("xml_parse_next_tag(): "
278                                   "faild to convert character set of attr_name\n");
279                         utf8attr_name = g_strdup(attr_name);
280                 }
281                 if (!utf8attr_value) {
282                         g_warning("xml_parse_next_tag(): "
283                                   "faild to convert character set of attr_value\n");
284                         utf8attr_value = g_strdup(attr_value);
285                 }
286
287                 attr->name  = XML_STRING_ADD(utf8attr_name);
288                 attr->value = utf8attr_value;
289                 tag->attr   = g_list_append(tag->attr, attr);
290
291                 g_free(utf8attr_name);
292         }
293
294         return 0;
295 }
296
297 void xml_push_tag(XMLFile *file, XMLTag *tag)
298 {
299         g_return_if_fail(tag != NULL);
300
301         file->tag_stack = g_list_prepend(file->tag_stack, tag);
302         file->level++;
303 }
304
305 void xml_pop_tag(XMLFile *file)
306 {
307         XMLTag *tag;
308
309         if (!file->tag_stack) return;
310
311         tag = (XMLTag *)file->tag_stack->data;
312
313         xml_free_tag(tag);
314         file->tag_stack = g_list_remove(file->tag_stack, tag);
315         file->level--;
316 }
317
318 XMLTag *xml_get_current_tag(XMLFile *file)
319 {
320         if (file->tag_stack)
321                 return (XMLTag *)file->tag_stack->data;
322         else
323                 return NULL;
324 }
325
326 GList *xml_get_current_tag_attr(XMLFile *file)
327 {
328         XMLTag *tag;
329
330         tag = xml_get_current_tag(file);
331         if (!tag) return NULL;
332
333         return tag->attr;
334 }
335
336 gchar *xml_get_element(XMLFile *file)
337 {
338         gchar *str;
339         gchar *end;
340         gchar *utf8str;
341
342         while ((end = strchr(file->bufp, '<')) == NULL)
343                 if (xml_read_line(file) < 0) return NULL;
344
345         if (end == file->bufp)
346                 return NULL;
347
348         str = g_strndup(file->bufp, end - file->bufp);
349         /* this is not XML1.0 strict */
350         g_strstrip(str);
351         xml_unescape_str(str);
352
353         file->bufp = end;
354         xml_truncate_buf(file);
355
356         if (str[0] == '\0') {
357                 g_free(str);
358                 return NULL;
359         }
360
361         utf8str = conv_codeset_strdup
362                         (str,
363                          conv_get_current_charset_str(),
364                          CS_UTF_8);
365         if (!utf8str) {
366                 g_warning("xml_get_element(): "
367                           "faild to convert character set.\n");
368                 utf8str = str;
369         } else
370                 g_free(str);
371
372         return utf8str;
373 }
374
375 gint xml_read_line(XMLFile *file)
376 {
377         gchar buf[XMLBUFSIZE];
378         gint index;
379
380         if (fgets(buf, sizeof(buf), file->fp) == NULL)
381                 return -1;
382
383         index = file->bufp - file->buf->str;
384
385         g_string_append(file->buf, buf);
386
387         file->bufp = file->buf->str + index;
388
389         return 0;
390 }
391
392 void xml_truncate_buf(XMLFile *file)
393 {
394         gint len;
395
396         len = file->bufp - file->buf->str;
397         if (len > 0) {
398                 g_string_erase(file->buf, 0, len);
399                 file->bufp = file->buf->str;
400         }
401 }
402
403 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
404 {
405         XMLTag *tag;
406
407         tag = xml_get_current_tag(file);
408
409         if (tag && strcmp(tag->tag, name) == 0)
410                 return TRUE;
411         else
412                 return FALSE;
413 }
414
415 XMLTag *xml_copy_tag(XMLTag *tag)
416 {
417         XMLTag *new_tag;
418         XMLAttr *attr;
419         GList *list;
420
421         new_tag = g_new(XMLTag, 1);
422         new_tag->tag = XML_STRING_ADD(tag->tag);
423         new_tag->attr = NULL;
424         for (list = tag->attr; list != NULL; list = list->next) {
425                 attr = xml_copy_attr((XMLAttr *)list->data);
426                 new_tag->attr = g_list_append(new_tag->attr, attr);
427         }
428
429         return new_tag;
430 }
431
432 XMLAttr *xml_copy_attr(XMLAttr *attr)
433 {
434         XMLAttr *new_attr;
435
436         new_attr = g_new(XMLAttr, 1);
437         new_attr->name  = XML_STRING_ADD(attr->name);
438         new_attr->value = g_strdup(attr->value);
439
440         return new_attr;
441 }
442
443 gint xml_unescape_str(gchar *str)
444 {
445         gchar *start;
446         gchar *end;
447         gchar *p = str;
448         gchar *esc_str;
449         gchar ch;
450         gint len;
451
452         while ((start = strchr(p, '&')) != NULL) {
453                 if ((end = strchr(start + 1, ';')) == NULL) {
454                         g_warning("Unescaped `&' appeared\n");
455                         p = start + 1;
456                         continue;
457                 }
458                 len = end - start + 1;
459                 if (len < 3) {
460                         p = end + 1;
461                         continue;
462                 }
463
464                 Xstrndup_a(esc_str, start, len, return -1);
465                 if (!strcmp(esc_str, "&lt;"))
466                         ch = '<';
467                 else if (!strcmp(esc_str, "&gt;"))
468                         ch = '>';
469                 else if (!strcmp(esc_str, "&amp;"))
470                         ch = '&';
471                 else if (!strcmp(esc_str, "&apos;"))
472                         ch = '\'';
473                 else if (!strcmp(esc_str, "&quot;"))
474                         ch = '\"';
475                 else {
476                         p = end + 1;
477                         continue;
478                 }
479
480                 *start = ch;
481                 memmove(start + 1, end + 1, strlen(end + 1) + 1);
482                 p = start + 1;
483         }
484
485         return 0;
486 }
487
488 #warning FIXME_GTK2
489 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
490 {
491         const gchar *src_codeset = CS_UTF_8;
492         const gchar *dest_codeset = conv_get_current_charset_str();
493         gchar *tmpstr = NULL;
494         const gchar *p;
495
496         g_return_val_if_fail(fp != NULL, -1);
497
498         if (!str) return 0;
499
500         tmpstr = conv_codeset_strdup(str, src_codeset, dest_codeset);
501         if (!tmpstr) {
502                 g_warning("xml_file_put_escape_str(): Faild to convert character set.");
503                 tmpstr = g_strdup(str);
504         }
505
506         for (p = tmpstr; *p != '\0'; p++) {
507                 switch (*p) {
508                 case '<':
509                         fputs("&lt;", fp);
510                         break;
511                 case '>':
512                         fputs("&gt;", fp);
513                         break;
514                 case '&':
515                         fputs("&amp;", fp);
516                         break;
517                 case '\'':
518                         fputs("&apos;", fp);
519                         break;
520                 case '\"':
521                         fputs("&quot;", fp);
522                         break;
523                 default:
524                         fputc(*p, fp);
525                 }
526         }
527
528         g_free(tmpstr);
529
530         return 0;
531 }
532
533 void xml_free_node(XMLNode *node)
534 {
535         if (!node) return;
536
537         xml_free_tag(node->tag);
538         g_free(node->element);
539         g_free(node);
540 }
541
542 static gboolean xml_free_func(GNode *node, gpointer data)
543 {
544         XMLNode *xmlnode = node->data;
545
546         xml_free_node(xmlnode);
547         return FALSE;
548 }
549
550 void xml_free_tree(GNode *node)
551 {
552         g_return_if_fail(node != NULL);
553
554         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
555                         NULL);
556
557         g_node_destroy(node);
558 }
559
560 void xml_free_tag(XMLTag *tag)
561 {
562         if (!tag) return;
563
564         XML_STRING_FREE(tag->tag);
565         while (tag->attr != NULL) {
566                 XMLAttr *attr = (XMLAttr *)tag->attr->data;
567                 XML_STRING_FREE(attr->name);
568                 g_free(attr->value);
569                 g_free(attr);
570                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
571         }
572         g_free(tag);
573 }
574
575 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
576 {
577         gchar *start;
578         gchar *end;
579
580         buf[0] = '\0';
581
582         while ((start = strchr(file->bufp, '<')) == NULL)
583                 if (xml_read_line(file) < 0) return -1;
584
585         start++;
586         file->bufp = start;
587
588         while ((end = strchr(file->bufp, '>')) == NULL)
589                 if (xml_read_line(file) < 0) return -1;
590
591         strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
592         g_strstrip(buf);
593         file->bufp = end + 1;
594         xml_truncate_buf(file);
595
596         return 0;
597 }
598
599 void xml_tag_add_attr(XMLTag *tag, const gchar *name, gchar *value)
600 {
601         XMLAttr *attr;
602
603         attr = g_new0(XMLAttr, 1);
604         attr->name = XML_STRING_ADD(name);
605         attr->value = value;
606
607         tag->attr = g_list_append(tag->attr, attr);
608 }