initial message templates support
[claws.git] / src / xml.c
blobb2761153396962b9be1467e26b10fce1c444b4e9
1 /*
2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999,2000 Hiroyuki Yamamoto
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.
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.
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.
20 #include <glib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <ctype.h>
25 #include "xml.h"
26 #include "utils.h"
28 static void xml_free_tag (XMLTag *tag);
29 static gint xml_get_parenthesis (XMLFile *file,
30 gchar *buf,
31 gint len);
33 XMLFile *xml_open_file(const gchar *path)
35 XMLFile *newfile;
37 g_return_val_if_fail(path != NULL, NULL);
39 newfile = g_new(XMLFile, 1);
41 newfile->fp = fopen(path, "r");
42 if (!newfile->fp) {
43 g_free(newfile);
44 return NULL;
47 newfile->buf = g_string_new(NULL);
48 newfile->bufp = newfile->buf->str;
50 newfile->dtd = NULL;
51 newfile->tag_stack = NULL;
52 newfile->level = 0;
53 newfile->is_empty_element = FALSE;
55 return newfile;
58 void xml_close_file(XMLFile *file)
60 g_return_if_fail(file != NULL);
62 if (file->fp) fclose(file->fp);
64 g_string_free(file->buf, TRUE);
66 g_free(file->dtd);
68 while (file->tag_stack != NULL)
69 xml_pop_tag(file);
71 g_free(file);
74 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
76 GNode *node = NULL;
77 XMLNode *xmlnode;
78 XMLTag *tag;
80 while (xml_parse_next_tag(file) == 0) {
81 if (file->level < level) break;
82 if (file->level == level) {
83 g_warning("xml_build_tree(): Parse error\n");
84 break;
87 tag = xml_get_current_tag(file);
88 if (!tag) break;
89 xmlnode = g_new(XMLNode, 1);
90 xmlnode->tag = xml_copy_tag(tag);
91 xmlnode->element = xml_get_element(file);
92 if (!parent)
93 node = g_node_new(xmlnode);
94 else
95 node = g_node_append_data(parent, xmlnode);
97 xml_build_tree(file, node, file->level);
98 if (file->level == 0) break;
101 return node;
104 GNode *xml_parse_file(const gchar *path)
106 XMLFile *file;
107 GNode *node;
109 file = xml_open_file(path);
110 g_return_val_if_fail(file != NULL, NULL);
112 xml_get_dtd(file);
114 node = xml_build_tree(file, NULL, file->level);
116 xml_close_file(file);
117 return node;
120 gint xml_get_dtd(XMLFile *file)
122 gchar buf[XMLBUFSIZE];
123 gchar *bufp = buf;
125 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
127 if ((*bufp++ == '?') &&
128 (bufp = strcasestr(bufp, "xml")) &&
129 (bufp = strcasestr(bufp + 3, "version")) &&
130 (bufp = strchr(bufp + 7, '?')))
131 file->dtd = g_strdup(buf);
132 else {
133 g_warning("Can't get xml dtd\n");
134 return -1;
137 return 0;
140 gint xml_parse_next_tag(XMLFile *file)
142 gchar buf[XMLBUFSIZE];
143 gchar *bufp = buf;
144 XMLTag *tag;
145 gint len;
147 if (file->is_empty_element == TRUE) {
148 file->is_empty_element = FALSE;
149 xml_pop_tag(file);
150 return 0;
153 if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
154 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
155 return -1;
158 /* end-tag */
159 if (buf[0] == '/') {
160 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
161 g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf);
162 return -1;
164 xml_pop_tag(file);
165 return 0;
168 tag = g_new0(XMLTag, 1);
169 xml_push_tag(file, tag);
171 len = strlen(buf);
172 if (len > 0 && buf[len - 1] == '/') {
173 file->is_empty_element = TRUE;
174 buf[len - 1] = '\0';
175 g_strchomp(buf);
177 if (strlen(buf) == 0) {
178 g_warning("xml_parse_next_tag(): Tag name is empty\n");
179 return -1;
182 while (*bufp != '\0' && !isspace(*bufp)) bufp++;
183 if (*bufp == '\0') {
184 tag->tag = g_strdup(buf);
185 return 0;
186 } else {
187 *bufp++ = '\0';
188 tag->tag = g_strdup(buf);
191 /* parse attributes ( name=value ) */
192 while (*bufp) {
193 XMLAttr *attr;
194 gchar *attr_name;
195 gchar *attr_value;
196 gchar *p;
197 gchar quote;
199 while (isspace(*bufp)) bufp++;
200 attr_name = bufp;
201 if ((p = strchr(attr_name, '=')) == NULL) {
202 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
203 return -1;
205 bufp = p;
206 *bufp++ = '\0';
207 while (isspace(*bufp)) bufp++;
209 if (*bufp != '"' && *bufp != '\'') {
210 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
211 return -1;
213 quote = *bufp;
214 bufp++;
215 attr_value = bufp;
216 if ((p = strchr(attr_value, quote)) == NULL) {
217 g_warning("xml_parse_next_tag(): Syntax error in tag\n");
218 return -1;
220 bufp = p;
221 *bufp++ = '\0';
223 g_strchomp(attr_name);
224 xml_unescape_str(attr_value);
226 attr = g_new(XMLAttr, 1);
227 attr->name = g_strdup(attr_name);
228 attr->value = g_strdup(attr_value);
229 tag->attr = g_list_append(tag->attr, attr);
232 return 0;
235 void xml_push_tag(XMLFile *file, XMLTag *tag)
237 g_return_if_fail(tag != NULL);
239 file->tag_stack = g_list_prepend(file->tag_stack, tag);
240 file->level++;
243 void xml_pop_tag(XMLFile *file)
245 XMLTag *tag;
247 if (!file->tag_stack) return;
249 tag = (XMLTag *)file->tag_stack->data;
251 xml_free_tag(tag);
252 file->tag_stack = g_list_remove(file->tag_stack, tag);
253 file->level--;
256 XMLTag *xml_get_current_tag(XMLFile *file)
258 if (file->tag_stack)
259 return (XMLTag *)file->tag_stack->data;
260 else
261 return NULL;
264 GList *xml_get_current_tag_attr(XMLFile *file)
266 XMLTag *tag;
268 tag = xml_get_current_tag(file);
269 if (!tag) return NULL;
271 return tag->attr;
274 gchar *xml_get_element(XMLFile *file)
276 gchar *str;
277 gchar *end;
279 while ((end = strchr(file->bufp, '<')) == NULL)
280 if (xml_read_line(file) < 0) return NULL;
282 if (end == file->bufp)
283 return NULL;
285 str = g_strndup(file->bufp, end - file->bufp);
286 /* this is not XML1.0 strict */
287 g_strstrip(str);
288 xml_unescape_str(str);
290 file->bufp = end;
291 xml_truncate_buf(file);
293 if (str[0] == '\0') {
294 g_free(str);
295 return NULL;
298 return str;
301 gint xml_read_line(XMLFile *file)
303 gchar buf[XMLBUFSIZE];
304 gint index;
306 if (fgets(buf, sizeof(buf), file->fp) == NULL)
307 return -1;
309 index = file->bufp - file->buf->str;
311 g_string_append(file->buf, buf);
313 file->bufp = file->buf->str + index;
315 return 0;
318 void xml_truncate_buf(XMLFile *file)
320 gint len;
322 len = file->bufp - file->buf->str;
323 if (len > 0) {
324 g_string_erase(file->buf, 0, len);
325 file->bufp = file->buf->str;
329 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
331 XMLTag *tag;
333 tag = xml_get_current_tag(file);
335 if (tag && strcmp(tag->tag, name) == 0)
336 return TRUE;
337 else
338 return FALSE;
341 XMLTag *xml_copy_tag(XMLTag *tag)
343 XMLTag *new_tag;
344 XMLAttr *attr;
345 GList *list;
347 new_tag = g_new(XMLTag, 1);
348 new_tag->tag = g_strdup(tag->tag);
349 new_tag->attr = NULL;
350 for (list = tag->attr; list != NULL; list = list->next) {
351 attr = xml_copy_attr((XMLAttr *)list->data);
352 new_tag->attr = g_list_append(new_tag->attr, attr);
355 return new_tag;
358 XMLAttr *xml_copy_attr(XMLAttr *attr)
360 XMLAttr *new_attr;
362 new_attr = g_new(XMLAttr, 1);
363 new_attr->name = g_strdup(attr->name);
364 new_attr->value = g_strdup(attr->value);
366 return new_attr;
369 gint xml_unescape_str(gchar *str)
371 gchar *start;
372 gchar *end;
373 gchar *p = str;
374 gchar *esc_str;
375 gchar ch;
376 gint len;
378 while ((start = strchr(p, '&')) != NULL) {
379 if ((end = strchr(start + 1, ';')) == NULL) {
380 g_warning("Unescaped `&' appeared\n");
381 p = start + 1;
382 continue;
384 len = end - start + 1;
385 if (len < 3) {
386 p = end + 1;
387 continue;
390 Xstrndup_a(esc_str, start, len, return -1);
391 if (!strcmp(esc_str, "&lt;"))
392 ch = '<';
393 else if (!strcmp(esc_str, "&gt;"))
394 ch = '>';
395 else if (!strcmp(esc_str, "&amp;"))
396 ch = '&';
397 else if (!strcmp(esc_str, "&apos;"))
398 ch = '\'';
399 else if (!strcmp(esc_str, "&quot;"))
400 ch = '\"';
401 else {
402 p = end + 1;
403 continue;
406 *start = ch;
407 memmove(start + 1, end + 1, strlen(end + 1) + 1);
408 p = start + 1;
411 return 0;
414 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
416 const gchar *p;
418 g_return_val_if_fail(fp != NULL, -1);
420 if (!str) return 0;
422 for (p = str; *p != '\0'; p++) {
423 switch (*p) {
424 case '<':
425 fputs("&lt;", fp);
426 break;
427 case '>':
428 fputs("&gt;", fp);
429 break;
430 case '&':
431 fputs("&amp;", fp);
432 break;
433 case '\'':
434 fputs("&apos;", fp);
435 break;
436 case '\"':
437 fputs("&quot;", fp);
438 break;
439 default:
440 fputc(*p, fp);
444 return 0;
447 void xml_free_node(XMLNode *node)
449 if (!node) return;
451 xml_free_tag(node->tag);
452 g_free(node->element);
453 g_free(node);
456 static gboolean xml_free_func(GNode *node, gpointer data)
458 XMLNode *xmlnode = node->data;
460 xml_free_node(xmlnode);
461 return FALSE;
464 void xml_free_tree(GNode *node)
466 g_return_if_fail(node != NULL);
468 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
469 NULL);
471 g_node_destroy(node);
474 static void xml_free_tag(XMLTag *tag)
476 if (!tag) return;
478 g_free(tag->tag);
479 while (tag->attr != NULL) {
480 XMLAttr *attr = (XMLAttr *)tag->attr->data;
481 g_free(attr->name);
482 g_free(attr->value);
483 g_free(attr);
484 tag->attr = g_list_remove(tag->attr, tag->attr->data);
486 g_free(tag);
489 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
491 gchar *start;
492 gchar *end;
494 buf[0] = '\0';
496 while ((start = strchr(file->bufp, '<')) == NULL)
497 if (xml_read_line(file) < 0) return -1;
499 start++;
500 file->bufp = start;
502 while ((end = strchr(file->bufp, '>')) == NULL)
503 if (xml_read_line(file) < 0) return -1;
505 strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
506 g_strstrip(buf);
507 file->bufp = end + 1;
508 xml_truncate_buf(file);
510 return 0;