2 * gmakrup.c: Minimal XML markup reader.
4 * Unlike the GLib one, this can not be restarted with more text
5 * as the Mono use does not require it.
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
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.
19 * Miguel de Icaza (miguel@novell.com)
21 * (C) 2006 Novell, Inc.
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:
31 * The above copyright notice and this permission notice shall be
32 * included in all copies or substantial portions of the Software.
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.
45 #define set_error(msg...) do { if (error != NULL) *error = g_error_new (GINT_TO_POINTER (1), 1, msg); } while (0);
56 struct _GMarkupParseContext {
59 GDestroyNotify user_data_dnotify;
62 /* Stores the name of the current element, so we can issue the end_element */
69 g_markup_parse_context_new (const GMarkupParser *parser,
70 GMarkupParseFlags flags,
72 GDestroyNotify user_data_dnotify)
74 GMarkupParseContext *context = g_new0 (GMarkupParseContext, 1);
76 context->parser = *parser;
77 context->user_data = user_data;
78 context->user_data_dnotify = user_data_dnotify;
84 g_markup_parse_context_free (GMarkupParseContext *context)
88 g_return_if_fail (context != NULL);
90 if (context->user_data_dnotify != NULL)
91 (context->user_data_dnotify) (context->user_data);
93 if (context->text != NULL)
94 g_string_free (context->text, TRUE);
95 for (l = context->level; l; l = l->next)
97 g_slist_free (context->level);
102 skip_space (const char *p, const char *end)
104 for (; p < end && isspace (*p); p++)
110 parse_value (const char *p, const char *end, char **value, GError **error)
116 set_error ("Expected the attribute value to start with a quote");
120 for (++p; p < end && *p != '"'; p++)
125 *value = malloc (l + 1);
128 strncpy (*value, start, l);
134 parse_name (const char *p, const char *end, char **value)
136 const char *start = p;
139 for (; p < end && isalnum (*p); p++)
145 *value = malloc (l + 1);
148 strncpy (*value, start, l);
154 parse_attributes (const char *p, const char *end, char ***names, char ***values, GError **error, int *full_stop)
159 p = skip_space (p, end);
167 if (*p == '/' && ((p+1) < end && *(p+1) == '>')){
173 p = parse_name (p, end, &name);
177 p = skip_space (p, end);
183 set_error ("Expected an = after the attribute name `%s'", name);
188 p = skip_space (p, end);
194 p = parse_value (p, end, &value, error);
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;
212 destroy_parse_state (GMarkupParseContext *context)
216 for (p = context->level; p != NULL; p = p->next)
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;
227 g_markup_parse_context_parse (GMarkupParseContext *context,
228 const gchar *text, gssize text_len,
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);
237 end = text + text_len;
239 for (p = text; p < end; p++){
242 switch (context->state){
244 if (c == ' ' || c == '\t' || c == '\f' || c == '\n')
247 context->state = START_ELEMENT;
250 set_error ("Expected < to start the document");
254 case START_ELEMENT: {
255 const char *element_start = p, *element_end;
257 int full_stop = 0, l;
258 gchar **names = NULL, **values = NULL;
260 for (; p < end && isspace (*p); p++)
263 set_error ("Unfinished element");
267 if (*p == '!' && (p+2 < end) && (p [1] == '-') && (p [2] == '-')){
268 context->state = COMMENT;
273 if (!(isascii (*p) && isalpha (*p))){
274 set_error ("Expected an element name");
278 for (++p; p < end && isalnum (*p); p++)
281 set_error ("Expected an element");
286 for (; p < end && isspace (*p); p++)
289 set_error ("Unfinished element");
292 p = parse_attributes (p, end, &names, &values, error, &full_stop);
298 /* Only set the error if parse_attributes did not */
299 if (error != NULL && *error == NULL)
300 set_error ("Unfinished sequence");
303 l = element_end - element_start;
304 ename = malloc (l + 1);
307 strncpy (ename, element_start, l);
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);
321 if (error != NULL && *error != NULL){
327 if (context->parser.end_element != NULL){
328 context->parser.end_element (context, ename, context->user_data, error);
329 if (error != NULL && *error != NULL){
336 context->level = g_slist_prepend (context->level, ename);
338 context->state = TEXT;
340 } /* case START_ELEMENT */
344 context->state = FLUSH_TEXT;
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);
358 if (p+2 < end && (p [1] == '-') && (p [2] == '>')){
359 context->state = 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)
374 context->state = CLOSING_ELEMENT;
377 context->state = START_ELEMENT;
381 case CLOSING_ELEMENT: {
382 GSList *current = context->level;
385 if (context->level == NULL){
386 set_error ("Too many closing tags, not enough open tags");
389 text = current->data;
391 if (context->parser.end_element != NULL){
392 context->parser.end_element (context, text, context->user_data, error);
393 if (error != NULL && *error != NULL){
400 context->level = context->level->next;
401 g_slist_free_1 (current);
403 } /* case CLOSING_ELEMENT */
411 if (context->parser.error)
412 context->parser.error (context, *error, context->user_data);
414 destroy_parse_state (context);
419 g_markup_parse_context_end_parse (GMarkupParseContext *context, GError **error)
421 g_return_val_if_fail (context != NULL, FALSE);
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