New test.
[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 <glib.h>
44
45 #define set_error(msg...) do { if (error != NULL) *error = g_error_new (GINT_TO_POINTER (1), 1, msg); } while (0);
46
47 typedef enum {
48         START,
49         START_ELEMENT,
50         TEXT,
51         FLUSH_TEXT,
52         CLOSING_ELEMENT,
53         COMMENT
54 } ParseState;
55
56 struct _GMarkupParseContext {
57         GMarkupParser  parser;
58         gpointer       user_data;
59         GDestroyNotify user_data_dnotify;
60         ParseState     state;
61
62         /* Stores the name of the current element, so we can issue the end_element */
63         GSList         *level;
64
65         GString        *text;
66 };
67
68 GMarkupParseContext *
69 g_markup_parse_context_new (const GMarkupParser *parser,
70                             GMarkupParseFlags flags,
71                             gpointer user_data,
72                             GDestroyNotify user_data_dnotify)
73 {
74         GMarkupParseContext *context = g_new0 (GMarkupParseContext, 1);
75
76         context->parser = *parser;
77         context->user_data = user_data;
78         context->user_data_dnotify = user_data_dnotify;
79
80         return context;
81 }
82
83 void
84 g_markup_parse_context_free (GMarkupParseContext *context)
85 {
86         GSList *l;
87         
88         g_return_if_fail (context != NULL);
89
90         if (context->user_data_dnotify != NULL)
91                 (context->user_data_dnotify) (context->user_data);
92         
93         if (context->text != NULL)
94                 g_string_free (context->text, TRUE);
95         for (l = context->level; l; l = l->next)
96                 g_free (l->data);
97         g_slist_free (context->level);
98         g_free (context);
99 }
100
101 static const char *
102 skip_space (const char *p, const char *end)
103 {
104         for (; p < end && isspace (*p); p++)
105                 ;
106         return p;
107 }
108
109 static const char *
110 parse_value (const char *p, const char *end, char **value, GError **error)
111 {
112         const char *start;
113         int l;
114         
115         if (*p != '"'){
116                 set_error ("Expected the attribute value to start with a quote");
117                 return end;
118         }
119         start = ++p;
120         for (++p; p < end && *p != '"'; p++)
121         if (p == end)
122                 return end;
123         l = p - start;
124         p++;
125         *value = malloc (l + 1);
126         if (*value == NULL)
127                 return end;
128         strncpy (*value, start, l);
129         (*value) [l] = 0;
130         return p;
131 }
132
133 static const char *
134 parse_name (const char *p, const char *end, char **value)
135 {
136         const char *start = p;
137         int l;
138         
139         for (; p < end && isalnum (*p); p++)
140                 ;
141         if (p == end)
142                 return end;
143
144         l = p - start;
145         *value = malloc (l + 1);
146         if (*value == NULL)
147                 return end;
148         strncpy (*value, start, l);
149         (*value) [l] = 0;
150         return p;
151 }
152
153 static const char *
154 parse_attributes (const char *p, const char *end, char ***names, char ***values, GError **error, int *full_stop)
155 {
156         int nnames = 0;
157
158         while (TRUE){
159                 p = skip_space (p, end);
160                 if (p == end)
161                         return end;
162                         
163                 if (*p == '>'){
164                         *full_stop = 0;
165                         return p; 
166                 }
167                 if (*p == '/' && ((p+1) < end && *(p+1) == '>')){
168                         *full_stop = 1;
169                         return p+1;
170                 } else {
171                         char *name, *value;
172                         
173                         p = parse_name (p, end, &name);
174                         if (p == end)
175                                 return p;
176
177                         p = skip_space (p, end);
178                         if (p == end){
179                                 free (name);
180                                 return p;
181                         }
182                         if (*p != '='){
183                                 set_error ("Expected an = after the attribute name `%s'", name);
184                                 free (name);
185                                 return end;
186                         }
187                         p++;
188                         p = skip_space (p, end);
189                         if (p == end){
190                                 free (name);
191                                 return end;
192                         }
193
194                         p = parse_value (p, end, &value, error);
195                         if (p == end){
196                                 free (name);
197                                 return p;
198                         }
199
200                         ++nnames;
201                         *names = g_realloc (*names, sizeof (char **) * (nnames+1));
202                         *values = g_realloc (*values, sizeof (char **) * (nnames+1));
203                         (*names) [nnames-1] = name;
204                         (*values) [nnames-1] = value;
205                         (*names) [nnames] = NULL;
206                         (*values) [nnames] = NULL;                      
207                 }
208         } 
209 }
210
211 static void
212 destroy_parse_state (GMarkupParseContext *context)
213 {
214         GSList *p;
215
216         for (p = context->level; p != NULL; p = p->next)
217                 g_free (p->data);
218         
219         g_slist_free (context->level);
220         if (context->text != NULL)
221                 g_string_free (context->text, TRUE);
222         context->text = NULL;
223         context->level = NULL;
224 }
225
226 gboolean
227 g_markup_parse_context_parse (GMarkupParseContext *context,
228                               const gchar *text, gssize text_len,
229                               GError **error)
230 {
231         const char *p,  *end;
232         
233         g_return_val_if_fail (context != NULL, FALSE);
234         g_return_val_if_fail (text != NULL, FALSE);
235         g_return_val_if_fail (text_len >= 0, FALSE);
236
237         end = text + text_len;
238         
239         for (p = text; p < end; p++){
240                 char c = *p;
241                 
242                 switch (context->state){
243                 case START:
244                         if (c == ' ' || c == '\t' || c == '\f' || c == '\n')
245                                 continue;
246                         if (c == '<'){
247                                 context->state = START_ELEMENT;
248                                 continue;
249                         }
250                         set_error ("Expected < to start the document");
251                         goto fail;
252
253
254                 case START_ELEMENT: {
255                         const char *element_start = p, *element_end;
256                         char *ename = NULL;
257                         int full_stop = 0, l;
258                         gchar **names = NULL, **values = NULL;
259
260                         for (; p < end && isspace (*p); p++)
261                                 ;
262                         if (p == end){
263                                 set_error ("Unfinished element");
264                                 goto fail;
265                         }
266
267                         if (*p == '!' && (p+2 < end) && (p [1] == '-') && (p [2] == '-')){
268                                 context->state = COMMENT;
269                                 p += 2;
270                                 break;
271                         }
272                         
273                         if (!(isascii (*p) && isalpha (*p))){
274                                 set_error ("Expected an element name");
275                                 goto fail;
276                         }
277                         
278                         for (++p; p < end && isalnum (*p); p++)
279                                 ;
280                         if (p == end){
281                                 set_error ("Expected an element");
282                                 goto fail;
283                         }
284                         element_end = p;
285                         
286                         for (; p < end && isspace (*p); p++)
287                                 ;
288                         if (p == end){
289                                 set_error ("Unfinished element");
290                                 goto fail;
291                         }
292                         p = parse_attributes (p, end, &names, &values, error, &full_stop);
293                         if (p == end){
294                                 if (names != NULL) {
295                                         g_strfreev (names);
296                                         g_strfreev (values);
297                                 }
298                                 /* Only set the error if parse_attributes did not */
299                                 if (error != NULL && *error == NULL)
300                                         set_error ("Unfinished sequence");
301                                 goto fail;
302                         }
303                         l = element_end - element_start;
304                         ename = malloc (l + 1);
305                         if (ename == NULL)
306                                 goto fail;
307                         strncpy (ename, element_start, l);
308                         ename [l] = 0;
309                         
310                         if (context->parser.start_element != NULL)
311                                 context->parser.start_element (context, ename,
312                                                                (const gchar **) names,
313                                                                (const gchar **) values,
314                                                                context->user_data, error);
315
316                         if (names != NULL){
317                                 g_strfreev (names);
318                                 g_strfreev (values);
319                         }
320
321                         if (error != NULL && *error != NULL){
322                                 free (ename);
323                                 goto fail;
324                         }
325                         
326                         if (full_stop){
327                                 if (context->parser.end_element != NULL){
328                                         context->parser.end_element (context, ename, context->user_data, error);
329                                         if (error != NULL && *error != NULL){
330                                                 free (ename);
331                                                 goto fail;
332                                         }
333                                 }
334                                 free (ename);
335                         } else
336                                 context->level = g_slist_prepend (context->level, ename);
337                         
338                         context->state = TEXT;
339                         break;
340                 } /* case START_ELEMENT */
341
342                 case TEXT: {
343                         if (c == '<'){
344                                 context->state = FLUSH_TEXT;
345                                 break;
346                         }
347                         if (context->parser.text != NULL){
348                                 if (context->text == NULL)
349                                         context->text = g_string_new ("");
350                                 g_string_append_c (context->text, c);
351                         }
352                         break;
353                 }
354
355                 case COMMENT:
356                         if (*p != '-')
357                                 break;
358                         if (p+2 < end && (p [1] == '-') && (p [2] == '>')){
359                                 context->state = TEXT;
360                                 p += 2;
361                                 break;
362                         }
363
364                         
365                 case FLUSH_TEXT:
366                         if (context->parser.text != NULL){
367                                 context->parser.text (context, context->text->str, context->text->len,
368                                                       context->user_data, error);
369                                 if (error != NULL && *error != NULL)
370                                         goto fail;
371                         }
372                         
373                         if (c == '/')
374                                 context->state = CLOSING_ELEMENT;
375                         else {
376                                 p--;
377                                 context->state = START_ELEMENT;
378                         }
379                         break;
380
381                 case CLOSING_ELEMENT: {
382                         GSList *current = context->level;
383                         char *text;
384
385                         if (context->level == NULL){
386                                 set_error ("Too many closing tags, not enough open tags");
387                                 goto fail;
388                         }
389                         text = current->data;
390                         
391                         if (context->parser.end_element != NULL){
392                                 context->parser.end_element (context, text, context->user_data, error);
393                                 if (error != NULL && *error != NULL){
394                                         free (text);
395                                         goto fail;
396                                 }
397                         }
398                         free (text);
399                         
400                         context->level = context->level->next;
401                         g_slist_free_1 (current);
402                         break;
403                 } /* case CLOSING_ELEMENT */
404                         
405                 } /* switch */
406         }
407
408
409         return TRUE;
410  fail:
411         if (context->parser.error)
412                 context->parser.error (context, *error, context->user_data);
413         
414         destroy_parse_state (context);
415         return FALSE;
416 }
417
418 gboolean
419 g_markup_parse_context_end_parse (GMarkupParseContext *context, GError **error)
420 {
421         g_return_val_if_fail (context != NULL, FALSE);
422
423         /*
424          * In our case, we always signal errors during parse, not at the end
425          * see the notes at the top of this file for details on how this
426          * could be moved here
427          */
428         return TRUE;
429 }