Enabled g_mem_set_vtable through the configure option --with-overridable-allocators...
[mono.git] / eglib / src / gmarkup.c
1 /*
2  * gmakrup.c: Minimal XML markup reader.
3  *
4  * Unlike the GLib one, this can not be restarted with more text
5  * as the Mono use does not require it.
6  *
7  * Actually, with further thought, I think that this could be made
8  * to restart very easily.  The pos == end condition would mean
9  * "return to caller" and only at end parse this would be a fatal
10  * error.
11  *
12  * Not that it matters to Mono, but it is very simple to change, there
13  * is a tricky situation: there are a few places where we check p+n
14  * in the source, and that would have to change to be progressive, instead
15  * of depending on the string to be complete at that point, so we would
16  * have to introduce extra states to cope with that.
17  *
18  * Author:
19  *   Miguel de Icaza (miguel@novell.com)
20  *
21  * (C) 2006 Novell, Inc.
22  *
23  * Permission is hereby granted, free of charge, to any person obtaining
24  * a copy of this software and associated documentation files (the
25  * "Software"), to deal in the Software without restriction, including
26  * without limitation the rights to use, copy, modify, merge, publish,
27  * distribute, sublicense, and/or sell copies of the Software, and to
28  * permit persons to whom the Software is furnished to do so, subject to
29  * the following conditions:
30  *
31  * The above copyright notice and this permission notice shall be
32  * included in all copies or substantial portions of the Software.
33  *
34  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
37  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
39  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41  */
42 #include <stdio.h>
43 #include <ctype.h>
44 #include <glib.h>
45
46 #define set_error(msg, ...) do { if (error != NULL) *error = g_error_new (GINT_TO_POINTER (1), 1, msg, __VA_ARGS__); } while (0);
47
48 typedef enum {
49         START,
50         START_ELEMENT,
51         TEXT,
52         FLUSH_TEXT,
53         CLOSING_ELEMENT,
54         COMMENT,
55         SKIP_XML_DECLARATION
56 } ParseState;
57
58 struct _GMarkupParseContext {
59         GMarkupParser  parser;
60         gpointer       user_data;
61         GDestroyNotify user_data_dnotify;
62         ParseState     state;
63
64         /* Stores the name of the current element, so we can issue the end_element */
65         GSList         *level;
66
67         GString        *text;
68 };
69
70 GMarkupParseContext *
71 g_markup_parse_context_new (const GMarkupParser *parser,
72                             GMarkupParseFlags flags,
73                             gpointer user_data,
74                             GDestroyNotify user_data_dnotify)
75 {
76         GMarkupParseContext *context = g_new0 (GMarkupParseContext, 1);
77
78         context->parser = *parser;
79         context->user_data = user_data;
80         context->user_data_dnotify = user_data_dnotify;
81
82         return context;
83 }
84
85 void
86 g_markup_parse_context_free (GMarkupParseContext *context)
87 {
88         GSList *l;
89         
90         g_return_if_fail (context != NULL);
91
92         if (context->user_data_dnotify != NULL)
93                 (context->user_data_dnotify) (context->user_data);
94         
95         if (context->text != NULL)
96                 g_string_free (context->text, TRUE);
97         for (l = context->level; l; l = l->next)
98                 g_free (l->data);
99         g_slist_free (context->level);
100         g_free (context);
101 }
102
103 static gboolean
104 my_isspace (char c)
105 {
106         if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v')
107                 return TRUE;
108         return FALSE;
109 }
110
111 static gboolean
112 my_isalnum (char c)
113 {
114         if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
115                 return TRUE;
116         if (c >= '0' && c <= '9')
117                 return TRUE;
118
119         return FALSE;
120 }
121
122 static gboolean
123 my_isalpha (char c)
124 {
125         if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
126                 return TRUE;
127         return FALSE;
128 }
129
130 static const char *
131 skip_space (const char *p, const char *end)
132 {
133         for (; p < end && my_isspace (*p); p++)
134                 ;
135         return p;
136 }
137
138 static const char *
139 parse_value (const char *p, const char *end, char **value, GError **error)
140 {
141         const char *start;
142         int l;
143         
144         if (*p != '"'){
145                 set_error ("%s", "Expected the attribute value to start with a quote");
146                 return end;
147         }
148         start = ++p;
149         for (; p < end && *p != '"'; p++)
150                 ;
151         if (p == end)
152                 return end;
153         l = (int)(p - start);
154         p++;
155         *value = g_malloc (l + 1);
156         if (*value == NULL)
157                 return end;
158         strncpy (*value, start, l);
159         (*value) [l] = 0;
160         return p;
161 }
162
163 static const char *
164 parse_name (const char *p, const char *end, char **value)
165 {
166         const char *start = p;
167         int l;
168         
169         for (; p < end && my_isalnum (*p); p++)
170                 ;
171         if (p == end)
172                 return end;
173
174         l = (int)(p - start);
175         *value = g_malloc (l + 1);
176         if (*value == NULL)
177                 return end;
178         strncpy (*value, start, l);
179         (*value) [l] = 0;
180         return p;
181 }
182
183 static const char *
184 parse_attributes (const char *p, const char *end, char ***names, char ***values, GError **error, int *full_stop, int state)
185 {
186         int nnames = 0;
187
188         while (TRUE){
189                 p = skip_space (p, end);
190                 if (p == end)
191                         return end;
192                         
193                 if (*p == '>'){
194                         *full_stop = 0;
195                         return p; 
196                 }
197                 if (state == SKIP_XML_DECLARATION && *p == '?' && ((p+1) < end) && *(p+1) == '>'){
198                         *full_stop = 0;
199                         return p+1;
200                 }
201                 
202                 if (*p == '/' && ((p+1) < end && *(p+1) == '>')){
203                         *full_stop = 1;
204                         return p+1;
205                 } else {
206                         char *name, *value;
207                         
208                         p = parse_name (p, end, &name);
209                         if (p == end)
210                                 return p;
211
212                         p = skip_space (p, end);
213                         if (p == end){
214                                 g_free (name);
215                                 return p;
216                         }
217                         if (*p != '='){
218                                 set_error ("Expected an = after the attribute name `%s'", name);
219                                 g_free (name);
220                                 return end;
221                         }
222                         p++;
223                         p = skip_space (p, end);
224                         if (p == end){
225                                 g_free (name);
226                                 return end;
227                         }
228
229                         p = parse_value (p, end, &value, error);
230                         if (p == end){
231                                 g_free (name);
232                                 return p;
233                         }
234
235                         ++nnames;
236                         *names = g_realloc (*names, sizeof (char **) * (nnames+1));
237                         *values = g_realloc (*values, sizeof (char **) * (nnames+1));
238                         (*names) [nnames-1] = name;
239                         (*values) [nnames-1] = value;
240                         (*names) [nnames] = NULL;
241                         (*values) [nnames] = NULL;                      
242                 }
243         } 
244 }
245
246 static void
247 destroy_parse_state (GMarkupParseContext *context)
248 {
249         GSList *p;
250
251         for (p = context->level; p != NULL; p = p->next)
252                 g_free (p->data);
253         
254         g_slist_free (context->level);
255         if (context->text != NULL)
256                 g_string_free (context->text, TRUE);
257         context->text = NULL;
258         context->level = NULL;
259 }
260
261 gboolean
262 g_markup_parse_context_parse (GMarkupParseContext *context,
263                               const gchar *text, gssize text_len,
264                               GError **error)
265 {
266         const char *p,  *end;
267         
268         g_return_val_if_fail (context != NULL, FALSE);
269         g_return_val_if_fail (text != NULL, FALSE);
270         g_return_val_if_fail (text_len >= 0, FALSE);
271
272         end = text + text_len;
273         
274         for (p = text; p < end; p++){
275                 char c = *p;
276
277                 switch (context->state){
278                 case START:
279                         if (c == ' ' || c == '\t' || c == '\f' || c == '\n' || (c & 0x80))
280                                 continue;
281                         if (c == '<'){
282                                 if (p+1 < end && p [1] == '?'){
283                                         context->state = SKIP_XML_DECLARATION;
284                                         p++;
285                                 } else
286                                         context->state = START_ELEMENT;
287                                 continue;
288                         }
289                         set_error ("%s", "Expected < to start the document");
290                         goto fail;
291
292                 case SKIP_XML_DECLARATION:
293                 case START_ELEMENT: {
294                         const char *element_start = p, *element_end;
295                         char *ename = NULL;
296                         int full_stop = 0, l;
297                         gchar **names = NULL, **values = NULL;
298
299                         for (; p < end && my_isspace (*p); p++)
300                                 ;
301                         if (p == end){
302                                 set_error ("%s", "Unfinished element");
303                                 goto fail;
304                         }
305
306                         if (*p == '!' && (p+2 < end) && (p [1] == '-') && (p [2] == '-')){
307                                 context->state = COMMENT;
308                                 p += 2;
309                                 break;
310                         }
311                         
312                         if (!my_isalpha (*p)){
313                                 set_error ("%s", "Expected an element name");
314                                 goto fail;
315                         }
316                         
317                         for (++p; p < end && (my_isalnum (*p) || (*p == '.')); p++)
318                                 ;
319                         if (p == end){
320                                 set_error ("%s", "Expected an element");
321                                 goto fail;
322                         }
323                         element_end = p;
324                         
325                         for (; p < end && my_isspace (*p); p++)
326                                 ;
327                         if (p == end){
328                                 set_error ("%s", "Unfinished element");
329                                 goto fail;
330                         }
331                         p = parse_attributes (p, end, &names, &values, error, &full_stop, context->state);
332                         if (p == end){
333                                 if (names != NULL) {
334                                         g_strfreev (names);
335                                         g_strfreev (values);
336                                 }
337                                 /* Only set the error if parse_attributes did not */
338                                 if (error != NULL && *error == NULL)
339                                         set_error ("%s", "Unfinished sequence");
340                                 goto fail;
341                         }
342                         l = (int)(element_end - element_start);
343                         ename = g_malloc (l + 1);
344                         if (ename == NULL)
345                                 goto fail;
346                         strncpy (ename, element_start, l);
347                         ename [l] = 0;
348
349                         if (context->state == START_ELEMENT)
350                                 if (context->parser.start_element != NULL)
351                                         context->parser.start_element (context, ename,
352                                                                        (const gchar **) names,
353                                                                        (const gchar **) values,
354                                                                        context->user_data, error);
355
356                         if (names != NULL){
357                                 g_strfreev (names);
358                                 g_strfreev (values);
359                         }
360
361                         if (error != NULL && *error != NULL){
362                                 g_free (ename);
363                                 goto fail;
364                         }
365                         
366                         if (full_stop){
367                                 if (context->parser.end_element != NULL &&  context->state == START_ELEMENT){
368                                         context->parser.end_element (context, ename, context->user_data, error);
369                                         if (error != NULL && *error != NULL){
370                                                 g_free (ename);
371                                                 goto fail;
372                                         }
373                                 }
374                                 g_free (ename);
375                         } else {
376                                 context->level = g_slist_prepend (context->level, ename);
377                         }
378                         
379                         context->state = TEXT;
380                         break;
381                 } /* case START_ELEMENT */
382
383                 case TEXT: {
384                         if (c == '<'){
385                                 context->state = FLUSH_TEXT;
386                                 break;
387                         }
388                         if (context->parser.text != NULL){
389                                 if (context->text == NULL)
390                                         context->text = g_string_new ("");
391                                 g_string_append_c (context->text, c);
392                         }
393                         break;
394                 }
395
396                 case COMMENT:
397                         if (*p != '-')
398                                 break;
399                         if (p+2 < end && (p [1] == '-') && (p [2] == '>')){
400                                 context->state = TEXT;
401                                 p += 2;
402                                 break;
403                         }
404                         break;
405                         
406                 case FLUSH_TEXT:
407                         if (context->parser.text != NULL && context->text != NULL){
408                                 context->parser.text (context, context->text->str, context->text->len,
409                                                       context->user_data, error);
410                                 if (error != NULL && *error != NULL)
411                                         goto fail;
412                         }
413                         
414                         if (c == '/')
415                                 context->state = CLOSING_ELEMENT;
416                         else {
417                                 p--;
418                                 context->state = START_ELEMENT;
419                         }
420                         break;
421
422                 case CLOSING_ELEMENT: {
423                         GSList *current = context->level;
424                         char *text;
425
426                         if (context->level == NULL){
427                                 set_error ("%s", "Too many closing tags, not enough open tags");
428                                 goto fail;
429                         }
430                         
431                         text = current->data;
432                         if (context->parser.end_element != NULL){
433                                 context->parser.end_element (context, text, context->user_data, error);
434                                 if (error != NULL && *error != NULL){
435                                         g_free (text);
436                                         goto fail;
437                                 }
438                         }
439                         g_free (text);
440
441                         while (p < end && *p != '>')
442                                 p++;
443                         
444                         context->level = context->level->next;
445                         g_slist_free_1 (current);
446                         context->state = TEXT;
447                         break;
448                 } /* case CLOSING_ELEMENT */
449                         
450                 } /* switch */
451         }
452
453
454         return TRUE;
455  fail:
456         if (context->parser.error && error != NULL && *error)
457                 context->parser.error (context, *error, context->user_data);
458         
459         destroy_parse_state (context);
460         return FALSE;
461 }
462
463 gboolean
464 g_markup_parse_context_end_parse (GMarkupParseContext *context, GError **error)
465 {
466         g_return_val_if_fail (context != NULL, FALSE);
467
468         /*
469          * In our case, we always signal errors during parse, not at the end
470          * see the notes at the top of this file for details on how this
471          * could be moved here
472          */
473         return TRUE;
474 }