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