0.8.8claws77
[claws.git] / src / 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
28 #define SPARSE_MEMORY
29 /* if this is defined all attr.names and tag.names are stored
30  * in a hash table */
31 #if defined(SPARSE_MEMORY)
32 #include "stringtable.h" 
33
34 static StringTable *xml_string_table;
35
36 static void xml_string_table_create(void)
37 {
38         if (xml_string_table == NULL)
39                 xml_string_table = string_table_new();
40 }
41 #define XML_STRING_ADD(str) \
42         string_table_insert_string(xml_string_table, (str))
43 #define XML_STRING_FREE(str) \
44         string_table_free_string(xml_string_table, (str))
45
46 #define XML_STRING_TABLE_CREATE() \
47         xml_string_table_create()
48
49 #else /* !SPARSE_MEMORY */
50
51 #define XML_STRING_ADD(str) \
52         g_strdup(str)
53 #define XML_STRING_FREE(str) \
54         g_free(str)
55
56 #define XML_STRING_TABLE_CREATE()
57
58 #endif /* SPARSE_MEMORY */
59
60 static void xml_free_tag        (XMLTag         *tag);
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;
235                 gchar *attr_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                 attr->name  = XML_STRING_ADD(attr_name);
268                 attr->value = g_strdup(attr_value);
269                 tag->attr   = g_list_append(tag->attr, attr);
270         }
271
272         return 0;
273 }
274
275 void xml_push_tag(XMLFile *file, XMLTag *tag)
276 {
277         g_return_if_fail(tag != NULL);
278
279         file->tag_stack = g_list_prepend(file->tag_stack, tag);
280         file->level++;
281 }
282
283 void xml_pop_tag(XMLFile *file)
284 {
285         XMLTag *tag;
286
287         if (!file->tag_stack) return;
288
289         tag = (XMLTag *)file->tag_stack->data;
290
291         xml_free_tag(tag);
292         file->tag_stack = g_list_remove(file->tag_stack, tag);
293         file->level--;
294 }
295
296 XMLTag *xml_get_current_tag(XMLFile *file)
297 {
298         if (file->tag_stack)
299                 return (XMLTag *)file->tag_stack->data;
300         else
301                 return NULL;
302 }
303
304 GList *xml_get_current_tag_attr(XMLFile *file)
305 {
306         XMLTag *tag;
307
308         tag = xml_get_current_tag(file);
309         if (!tag) return NULL;
310
311         return tag->attr;
312 }
313
314 gchar *xml_get_element(XMLFile *file)
315 {
316         gchar *str;
317         gchar *end;
318
319         while ((end = strchr(file->bufp, '<')) == NULL)
320                 if (xml_read_line(file) < 0) return NULL;
321
322         if (end == file->bufp)
323                 return NULL;
324
325         str = g_strndup(file->bufp, end - file->bufp);
326         /* this is not XML1.0 strict */
327         g_strstrip(str);
328         xml_unescape_str(str);
329
330         file->bufp = end;
331         xml_truncate_buf(file);
332
333         if (str[0] == '\0') {
334                 g_free(str);
335                 return NULL;
336         }
337
338         return str;
339 }
340
341 gint xml_read_line(XMLFile *file)
342 {
343         gchar buf[XMLBUFSIZE];
344         gint index;
345
346         if (fgets(buf, sizeof(buf), file->fp) == NULL)
347                 return -1;
348
349         index = file->bufp - file->buf->str;
350
351         g_string_append(file->buf, buf);
352
353         file->bufp = file->buf->str + index;
354
355         return 0;
356 }
357
358 void xml_truncate_buf(XMLFile *file)
359 {
360         gint len;
361
362         len = file->bufp - file->buf->str;
363         if (len > 0) {
364                 g_string_erase(file->buf, 0, len);
365                 file->bufp = file->buf->str;
366         }
367 }
368
369 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
370 {
371         XMLTag *tag;
372
373         tag = xml_get_current_tag(file);
374
375         if (tag && strcmp(tag->tag, name) == 0)
376                 return TRUE;
377         else
378                 return FALSE;
379 }
380
381 XMLTag *xml_copy_tag(XMLTag *tag)
382 {
383         XMLTag *new_tag;
384         XMLAttr *attr;
385         GList *list;
386
387         new_tag = g_new(XMLTag, 1);
388         new_tag->tag = XML_STRING_ADD(tag->tag);
389         new_tag->attr = NULL;
390         for (list = tag->attr; list != NULL; list = list->next) {
391                 attr = xml_copy_attr((XMLAttr *)list->data);
392                 new_tag->attr = g_list_append(new_tag->attr, attr);
393         }
394
395         return new_tag;
396 }
397
398 XMLAttr *xml_copy_attr(XMLAttr *attr)
399 {
400         XMLAttr *new_attr;
401
402         new_attr = g_new(XMLAttr, 1);
403         new_attr->name  = XML_STRING_ADD(attr->name);
404         new_attr->value = g_strdup(attr->value);
405
406         return new_attr;
407 }
408
409 gint xml_unescape_str(gchar *str)
410 {
411         gchar *start;
412         gchar *end;
413         gchar *p = str;
414         gchar *esc_str;
415         gchar ch;
416         gint len;
417
418         while ((start = strchr(p, '&')) != NULL) {
419                 if ((end = strchr(start + 1, ';')) == NULL) {
420                         g_warning("Unescaped `&' appeared\n");
421                         p = start + 1;
422                         continue;
423                 }
424                 len = end - start + 1;
425                 if (len < 3) {
426                         p = end + 1;
427                         continue;
428                 }
429
430                 Xstrndup_a(esc_str, start, len, return -1);
431                 if (!strcmp(esc_str, "&lt;"))
432                         ch = '<';
433                 else if (!strcmp(esc_str, "&gt;"))
434                         ch = '>';
435                 else if (!strcmp(esc_str, "&amp;"))
436                         ch = '&';
437                 else if (!strcmp(esc_str, "&apos;"))
438                         ch = '\'';
439                 else if (!strcmp(esc_str, "&quot;"))
440                         ch = '\"';
441                 else {
442                         p = end + 1;
443                         continue;
444                 }
445
446                 *start = ch;
447                 memmove(start + 1, end + 1, strlen(end + 1) + 1);
448                 p = start + 1;
449         }
450
451         return 0;
452 }
453
454 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
455 {
456         const gchar *p;
457
458         g_return_val_if_fail(fp != NULL, -1);
459
460         if (!str) return 0;
461
462         for (p = str; *p != '\0'; p++) {
463                 switch (*p) {
464                 case '<':
465                         fputs("&lt;", fp);
466                         break;
467                 case '>':
468                         fputs("&gt;", fp);
469                         break;
470                 case '&':
471                         fputs("&amp;", fp);
472                         break;
473                 case '\'':
474                         fputs("&apos;", fp);
475                         break;
476                 case '\"':
477                         fputs("&quot;", fp);
478                         break;
479                 default:
480                         fputc(*p, fp);
481                 }
482         }
483
484         return 0;
485 }
486
487 void xml_free_node(XMLNode *node)
488 {
489         if (!node) return;
490
491         xml_free_tag(node->tag);
492         g_free(node->element);
493         g_free(node);
494 }
495
496 static gboolean xml_free_func(GNode *node, gpointer data)
497 {
498         XMLNode *xmlnode = node->data;
499
500         xml_free_node(xmlnode);
501         return FALSE;
502 }
503
504 void xml_free_tree(GNode *node)
505 {
506         g_return_if_fail(node != NULL);
507
508         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
509                         NULL);
510
511         g_node_destroy(node);
512 }
513
514 static void xml_free_tag(XMLTag *tag)
515 {
516         if (!tag) return;
517
518         XML_STRING_FREE(tag->tag);
519         while (tag->attr != NULL) {
520                 XMLAttr *attr = (XMLAttr *)tag->attr->data;
521                 XML_STRING_FREE(attr->name);
522                 g_free(attr->value);
523                 g_free(attr);
524                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
525         }
526         g_free(tag);
527 }
528
529 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
530 {
531         gchar *start;
532         gchar *end;
533
534         buf[0] = '\0';
535
536         while ((start = strchr(file->bufp, '<')) == NULL)
537                 if (xml_read_line(file) < 0) return -1;
538
539         start++;
540         file->bufp = start;
541
542         while ((end = strchr(file->bufp, '>')) == NULL)
543                 if (xml_read_line(file) < 0) return -1;
544
545         strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
546         g_strstrip(buf);
547         file->bufp = end + 1;
548         xml_truncate_buf(file);
549
550         return 0;
551 }