Statistics
| Revision:

root / src / xml.c @ 1

History | View | Annotate | Download (11.6 KB)

1
/*
2
 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3
 * Copyright (C) 1999-2004 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 "main.h"
27
#include "utils.h"
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 void xml_free_tag        (XMLTag                *tag);
63
static gint xml_get_parenthesis        (XMLFile        *file,
64
                                 gchar                *buf,
65
                                 gint                 len);
66

    
67
XMLFile *xml_open_file(const gchar *path)
68
{
69
        XMLFile *newfile;
70

    
71
        g_return_val_if_fail(path != NULL, NULL);
72

    
73
        XML_STRING_TABLE_CREATE();
74

    
75
        newfile = g_new(XMLFile, 1);
76

    
77
        newfile->fp = fopen(path, "rb");
78
        if (!newfile->fp) {
79
                g_free(newfile);
80
                return NULL;
81
        }
82

    
83
        newfile->buf = g_string_new(NULL);
84
        newfile->bufp = newfile->buf->str;
85

    
86
        newfile->dtd = 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

    
104
        while (file->tag_stack != NULL)
105
                xml_pop_tag(file);
106

    
107
        g_free(file);
108
}
109

    
110
static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
111
{
112
        GNode *node = NULL;
113
        XMLNode *xmlnode;
114
        XMLTag *tag;
115

    
116
        while (xml_parse_next_tag(file) == 0) {
117
                if (file->level < level) break;
118
                if (file->level == level) {
119
                        g_warning("xml_build_tree(): Parse error\n");
120
                        break;
121
                }
122

    
123
                tag = xml_get_current_tag(file);
124
                if (!tag) break;
125
                xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
126
                xmlnode->element = xml_get_element(file);
127
                if (!parent)
128
                        node = g_node_new(xmlnode);
129
                else
130
                        node = g_node_append_data(parent, xmlnode);
131

    
132
                xml_build_tree(file, node, file->level);
133
                if (file->level == 0) break;
134
        }
135

    
136
        return node;
137
}
138

    
139
GNode *xml_parse_file(const gchar *path)
140
{
141
        XMLFile *file;
142
        GNode *node;
143

    
144
        file = xml_open_file(path);
145
        g_return_val_if_fail(file != NULL, NULL);
146

    
147
        xml_get_dtd(file);
148

    
149
        node = xml_build_tree(file, NULL, file->level);
150

    
151
        xml_close_file(file);
152

    
153
#if defined(SPARSE_MEMORY)
154
        if (debug_mode)
155
                string_table_get_stats(xml_string_table);
156
#endif
157

    
158
        return node;
159
}
160

    
161
gint xml_get_dtd(XMLFile *file)
162
{
163
        gchar buf[XMLBUFSIZE];
164
        gchar *bufp = buf;
165

    
166
        if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
167

    
168
        if ((*bufp++ == '?') &&
169
            (bufp = strcasestr(bufp, "xml")) &&
170
            (bufp = strcasestr(bufp + 3, "version")) &&
171
            (bufp = strchr(bufp + 7, '?')))
172
                file->dtd = g_strdup(buf);
173
        else {
174
                g_warning("Can't get xml dtd\n");
175
                return -1;
176
        }
177

    
178
        return 0;
179
}
180

    
181
gint xml_parse_next_tag(XMLFile *file)
182
{
183
        gchar buf[XMLBUFSIZE];
184
        guchar *bufp = buf;
185
        XMLTag *tag;
186
        gint len;
187

    
188
        if (file->is_empty_element == TRUE) {
189
                file->is_empty_element = FALSE;
190
                xml_pop_tag(file);
191
                return 0;
192
        }
193

    
194
        if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
195
                g_warning("xml_parse_next_tag(): Can't parse next tag\n");
196
                return -1;
197
        }
198

    
199
        /* end-tag */
200
        if (buf[0] == '/') {
201
                if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
202
                        g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf);
203
                        return -1;
204
                }
205
                xml_pop_tag(file);
206
                return 0;
207
        }
208

    
209
        tag = xml_tag_new(NULL);
210
        xml_push_tag(file, tag);
211

    
212
        len = strlen(buf);
213
        if (len > 0 && buf[len - 1] == '/') {
214
                file->is_empty_element = TRUE;
215
                buf[len - 1] = '\0';
216
                g_strchomp(buf);
217
        }
218
        if (strlen(buf) == 0) {
219
                g_warning("xml_parse_next_tag(): Tag name is empty\n");
220
                return -1;
221
        }
222

    
223
        while (*bufp != '\0' && !isspace(*bufp)) bufp++;
224
        if (*bufp == '\0') {
225
                tag->tag = XML_STRING_ADD(buf);
226
                return 0;
227
        } else {
228
                *bufp++ = '\0';
229
                tag->tag = XML_STRING_ADD(buf);
230
        }
231

    
232
        /* parse attributes ( name=value ) */
233
        while (*bufp) {
234
                XMLAttr *attr;
235
                gchar *attr_name;
236
                gchar *attr_value;
237
                gchar *p;
238
                gchar quote;
239

    
240
                while (isspace(*bufp)) bufp++;
241
                attr_name = bufp;
242
                if ((p = strchr(attr_name, '=')) == NULL) {
243
                        g_warning("xml_parse_next_tag(): Syntax error in tag\n");
244
                        return -1;
245
                }
246
                bufp = p;
247
                *bufp++ = '\0';
248
                while (isspace(*bufp)) bufp++;
249

    
250
                if (*bufp != '"' && *bufp != '\'') {
251
                        g_warning("xml_parse_next_tag(): Syntax error in tag\n");
252
                        return -1;
253
                }
254
                quote = *bufp;
255
                bufp++;
256
                attr_value = bufp;
257
                if ((p = strchr(attr_value, quote)) == NULL) {
258
                        g_warning("xml_parse_next_tag(): Syntax error in tag\n");
259
                        return -1;
260
                }
261
                bufp = p;
262
                *bufp++ = '\0';
263

    
264
                g_strchomp(attr_name);
265
                xml_unescape_str(attr_value);
266

    
267
                attr = xml_attr_new(attr_name, attr_value);
268
                xml_tag_add_attr(tag, 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
XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
381
{
382
        XMLNode *node;
383

    
384
        node = g_new(XMLNode, 1);
385
        node->tag = tag;
386
        node->element = g_strdup(text);
387

    
388
        return node;
389
}
390

    
391
XMLTag *xml_tag_new(const gchar *tag)
392
{
393
        XMLTag *new_tag;
394

    
395
        new_tag = g_new(XMLTag, 1);
396
        if (tag)
397
                new_tag->tag = XML_STRING_ADD(tag);
398
        else
399
                new_tag->tag = NULL;
400
        new_tag->attr = NULL;
401

    
402
        return new_tag;
403
}
404

    
405
XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
406
{
407
        XMLAttr *new_attr;
408

    
409
        new_attr = g_new(XMLAttr, 1);
410
        new_attr->name = XML_STRING_ADD(name);
411
        new_attr->value = g_strdup(value);
412

    
413
        return new_attr;
414
}
415

    
416
void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
417
{
418
        tag->attr = g_list_append(tag->attr, attr);
419
}
420

    
421
XMLTag *xml_copy_tag(XMLTag *tag)
422
{
423
        XMLTag *new_tag;
424
        XMLAttr *attr;
425
        GList *list;
426

    
427
        new_tag = xml_tag_new(tag->tag);
428
        for (list = tag->attr; list != NULL; list = list->next) {
429
                attr = xml_copy_attr((XMLAttr *)list->data);
430
                xml_tag_add_attr(new_tag, attr);
431
        }
432

    
433
        return new_tag;
434
}
435

    
436
XMLAttr *xml_copy_attr(XMLAttr *attr)
437
{
438
        return xml_attr_new(attr->name, attr->value);
439
}
440

    
441
gint xml_unescape_str(gchar *str)
442
{
443
        gchar *start;
444
        gchar *end;
445
        gchar *p = str;
446
        gchar *esc_str;
447
        gchar ch;
448
        gint len;
449

    
450
        while ((start = strchr(p, '&')) != NULL) {
451
                if ((end = strchr(start + 1, ';')) == NULL) {
452
                        g_warning("Unescaped `&' appeared\n");
453
                        p = start + 1;
454
                        continue;
455
                }
456
                len = end - start + 1;
457
                if (len < 3) {
458
                        p = end + 1;
459
                        continue;
460
                }
461

    
462
                Xstrndup_a(esc_str, start, len, return -1);
463
                if (!strcmp(esc_str, "&lt;"))
464
                        ch = '<';
465
                else if (!strcmp(esc_str, "&gt;"))
466
                        ch = '>';
467
                else if (!strcmp(esc_str, "&amp;"))
468
                        ch = '&';
469
                else if (!strcmp(esc_str, "&apos;"))
470
                        ch = '\'';
471
                else if (!strcmp(esc_str, "&quot;"))
472
                        ch = '\"';
473
                else {
474
                        p = end + 1;
475
                        continue;
476
                }
477

    
478
                *start = ch;
479
                memmove(start + 1, end + 1, strlen(end + 1) + 1);
480
                p = start + 1;
481
        }
482

    
483
        return 0;
484
}
485

    
486
gint xml_file_put_escape_str(FILE *fp, const gchar *str)
487
{
488
        const gchar *p;
489

    
490
        g_return_val_if_fail(fp != NULL, -1);
491

    
492
        if (!str) return 0;
493

    
494
        for (p = str; *p != '\0'; p++) {
495
                switch (*p) {
496
                case '<':
497
                        fputs("&lt;", fp);
498
                        break;
499
                case '>':
500
                        fputs("&gt;", fp);
501
                        break;
502
                case '&':
503
                        fputs("&amp;", fp);
504
                        break;
505
                case '\'':
506
                        fputs("&apos;", fp);
507
                        break;
508
                case '\"':
509
                        fputs("&quot;", fp);
510
                        break;
511
                default:
512
                        fputc(*p, fp);
513
                }
514
        }
515

    
516
        return 0;
517
}
518

    
519
gint xml_file_put_xml_decl(FILE *fp)
520
{
521
        g_return_val_if_fail(fp != NULL, -1);
522

    
523
        fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n",
524
                conv_get_internal_charset_str());
525
        return 0;
526
}
527

    
528
gint xml_file_put_node(FILE *fp, XMLNode *node)
529
{
530
        GList *cur;
531

    
532
        g_return_val_if_fail(fp != NULL, -1);
533
        g_return_val_if_fail(node != NULL, -1);
534

    
535
        fprintf(fp, "<%s", node->tag->tag);
536

    
537
        for (cur = node->tag->attr; cur != NULL; cur = cur->next) {
538
                XMLAttr *attr = (XMLAttr *)cur->data;
539
                fprintf(fp, " %s=\"", attr->name);
540
                xml_file_put_escape_str(fp, attr->value);
541
                fputs("\"", fp);
542
        }
543

    
544
        if (node->element) {
545
                fputs(">", fp);
546
                xml_file_put_escape_str(fp, node->element);
547
                fprintf(fp, "</%s>\n", node->tag->tag);
548
        } else {
549
                fputs(" />\n", fp);
550
        }
551

    
552
        return 0;
553
}
554

    
555
void xml_free_node(XMLNode *node)
556
{
557
        if (!node) return;
558

    
559
        xml_free_tag(node->tag);
560
        g_free(node->element);
561
        g_free(node);
562
}
563

    
564
static gboolean xml_free_func(GNode *node, gpointer data)
565
{
566
        XMLNode *xmlnode = node->data;
567

    
568
        xml_free_node(xmlnode);
569
        return FALSE;
570
}
571

    
572
void xml_free_tree(GNode *node)
573
{
574
        g_return_if_fail(node != NULL);
575

    
576
        g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
577
                        NULL);
578

    
579
        g_node_destroy(node);
580
}
581

    
582
static void xml_free_tag(XMLTag *tag)
583
{
584
        if (!tag) return;
585

    
586
        XML_STRING_FREE(tag->tag);
587
        while (tag->attr != NULL) {
588
                XMLAttr *attr = (XMLAttr *)tag->attr->data;
589
                XML_STRING_FREE(attr->name);
590
                g_free(attr->value);
591
                g_free(attr);
592
                tag->attr = g_list_remove(tag->attr, tag->attr->data);
593
        }
594
        g_free(tag);
595
}
596

    
597
static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
598
{
599
        gchar *start;
600
        gchar *end;
601

    
602
        buf[0] = '\0';
603

    
604
        while ((start = strchr(file->bufp, '<')) == NULL)
605
                if (xml_read_line(file) < 0) return -1;
606

    
607
        start++;
608
        file->bufp = start;
609

    
610
        while ((end = strchr(file->bufp, '>')) == NULL)
611
                if (xml_read_line(file) < 0) return -1;
612

    
613
        strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
614
        g_strstrip(buf);
615
        file->bufp = end + 1;
616
        xml_truncate_buf(file);
617

    
618
        return 0;
619
}