Branch data Line data Source code
1 : : /* gmarkup.c - Simple XML-like parser
2 : : *
3 : : * Copyright 2000, 2003 Red Hat, Inc.
4 : : * Copyright 2007, 2008 Ryan Lortie <desrt@desrt.ca>
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2.1 of the License, or (at your option) any later version.
12 : : *
13 : : * This library is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General Public License
19 : : * along with this library; if not, see <http://www.gnu.org/licenses/>.
20 : : */
21 : :
22 : : #include "config.h"
23 : :
24 : : #include <stdarg.h>
25 : : #include <string.h>
26 : : #include <stdio.h>
27 : : #include <stdlib.h>
28 : : #include <errno.h>
29 : :
30 : : #include "gmarkup.h"
31 : :
32 : : #include "gatomic.h"
33 : : #include "gslice.h"
34 : : #include "galloca.h"
35 : : #include "gstrfuncs.h"
36 : : #include "gstring.h"
37 : : #include "gtestutils.h"
38 : : #include "glibintl.h"
39 : : #include "gthread.h"
40 : :
41 : 678 : G_DEFINE_QUARK (g-markup-error-quark, g_markup_error)
42 : :
43 : : typedef enum
44 : : {
45 : : STATE_INITIAL,
46 : : STATE_AFTER_BOM1,
47 : : STATE_AFTER_BOM2,
48 : : STATE_START,
49 : : STATE_AFTER_OPEN_ANGLE,
50 : : STATE_AFTER_CLOSE_ANGLE,
51 : : STATE_AFTER_ELISION_SLASH, /* the slash that obviates need for end element */
52 : : STATE_INSIDE_OPEN_TAG_NAME,
53 : : STATE_INSIDE_ATTRIBUTE_NAME,
54 : : STATE_AFTER_ATTRIBUTE_NAME,
55 : : STATE_BETWEEN_ATTRIBUTES,
56 : : STATE_AFTER_ATTRIBUTE_EQUALS_SIGN,
57 : : STATE_INSIDE_ATTRIBUTE_VALUE_SQ,
58 : : STATE_INSIDE_ATTRIBUTE_VALUE_DQ,
59 : : STATE_INSIDE_TEXT,
60 : : STATE_AFTER_CLOSE_TAG_SLASH,
61 : : STATE_INSIDE_CLOSE_TAG_NAME,
62 : : STATE_AFTER_CLOSE_TAG_NAME,
63 : : STATE_INSIDE_PASSTHROUGH,
64 : : STATE_ERROR
65 : : } GMarkupParseState;
66 : :
67 : : typedef struct
68 : : {
69 : : const char *prev_element;
70 : : const GMarkupParser *prev_parser;
71 : : gpointer prev_user_data;
72 : : } GMarkupRecursionTracker;
73 : :
74 : : typedef struct
75 : : {
76 : : size_t lines; /* 1-based, in characters */
77 : : size_t chars; /* 1-based, in characters */
78 : : size_t offset; /* 0-based, in bytes */
79 : : } MarkupLocation;
80 : :
81 : : struct _GMarkupParseContext
82 : : {
83 : : const GMarkupParser *parser;
84 : :
85 : : gint ref_count; /* (atomic) */
86 : :
87 : : GMarkupParseFlags flags;
88 : :
89 : : MarkupLocation pos;
90 : : MarkupLocation tag_start;
91 : : MarkupLocation attr_start;
92 : :
93 : : GMarkupParseState state;
94 : :
95 : : gpointer user_data;
96 : : GDestroyNotify dnotify;
97 : :
98 : : /* A piece of character data or an element that
99 : : * hasn't "ended" yet so we haven't yet called
100 : : * the callback for it.
101 : : */
102 : : GString *partial_chunk;
103 : : GSList *spare_chunks;
104 : :
105 : : GSList *tag_stack;
106 : : GSList *tag_stack_gstr;
107 : : GSList *spare_list_nodes;
108 : :
109 : : GString **attr_names;
110 : : GString **attr_values;
111 : : MarkupLocation *attr_pos; /* nullable, owned */
112 : : MarkupLocation *attr_end; /* nullable, owned */
113 : : gint cur_attr;
114 : : gint alloc_attrs;
115 : :
116 : : const gchar *current_text;
117 : : gssize current_text_len;
118 : : const gchar *current_text_end;
119 : :
120 : : /* used to save the start of the last interesting thingy */
121 : : const gchar *start;
122 : :
123 : : const gchar *iter;
124 : :
125 : : guint document_empty : 1;
126 : : guint parsing : 1;
127 : : guint awaiting_pop : 1;
128 : : gint balance;
129 : :
130 : : /* subparser support */
131 : : GSList *subparser_stack; /* (GMarkupRecursionTracker *) */
132 : : const char *subparser_element;
133 : : gpointer held_user_data;
134 : : };
135 : :
136 : : /*
137 : : * Helpers to reduce our allocation overhead, we have
138 : : * a well defined allocation lifecycle.
139 : : */
140 : : static GSList *
141 : 11636740 : get_list_node (GMarkupParseContext *context, gpointer data)
142 : : {
143 : : GSList *node;
144 : 11636740 : if (context->spare_list_nodes != NULL)
145 : : {
146 : 11612780 : node = context->spare_list_nodes;
147 : 11612780 : context->spare_list_nodes = g_slist_remove_link (context->spare_list_nodes, node);
148 : : }
149 : : else
150 : 23960 : node = g_slist_alloc();
151 : 11636740 : node->data = data;
152 : 11636740 : return node;
153 : : }
154 : :
155 : : static void
156 : 11615469 : free_list_node (GMarkupParseContext *context, GSList *node)
157 : : {
158 : 11615469 : node->data = NULL;
159 : 11615469 : context->spare_list_nodes = g_slist_concat (node, context->spare_list_nodes);
160 : 11615469 : }
161 : :
162 : : /**
163 : : * g_markup_parse_context_new:
164 : : * @parser: a #GMarkupParser
165 : : * @flags: one or more #GMarkupParseFlags
166 : : * @user_data: user data to pass to #GMarkupParser functions
167 : : * @user_data_dnotify: user data destroy notifier called when
168 : : * the parse context is freed
169 : : *
170 : : * Creates a new parse context. A parse context is used to parse
171 : : * marked-up documents. You can feed any number of documents into
172 : : * a context, as long as no errors occur; once an error occurs,
173 : : * the parse context can't continue to parse text (you have to
174 : : * free it and create a new parse context).
175 : : *
176 : : * Returns: a new #GMarkupParseContext
177 : : **/
178 : : GMarkupParseContext *
179 : 1049 : g_markup_parse_context_new (const GMarkupParser *parser,
180 : : GMarkupParseFlags flags,
181 : : gpointer user_data,
182 : : GDestroyNotify user_data_dnotify)
183 : : {
184 : : GMarkupParseContext *context;
185 : :
186 : 1049 : g_return_val_if_fail (parser != NULL, NULL);
187 : :
188 : 1049 : context = g_new (GMarkupParseContext, 1);
189 : :
190 : 1049 : context->ref_count = 1;
191 : 1049 : context->parser = parser;
192 : 1049 : context->flags = flags;
193 : 1049 : context->user_data = user_data;
194 : 1049 : context->dnotify = user_data_dnotify;
195 : :
196 : 1049 : context->pos.lines = 1;
197 : 1049 : context->pos.chars = 1;
198 : 1049 : context->pos.offset = 0;
199 : :
200 : 1049 : context->tag_start.lines = 1;
201 : 1049 : context->tag_start.chars = 1;
202 : 1049 : context->tag_start.offset = 0;
203 : :
204 : 1049 : context->partial_chunk = NULL;
205 : 1049 : context->spare_chunks = NULL;
206 : 1049 : context->spare_list_nodes = NULL;
207 : :
208 : 1049 : context->state = STATE_INITIAL;
209 : 1049 : context->tag_stack = NULL;
210 : 1049 : context->tag_stack_gstr = NULL;
211 : 1049 : context->attr_names = NULL;
212 : 1049 : context->attr_values = NULL;
213 : 1049 : context->attr_pos = NULL;
214 : 1049 : context->attr_end = NULL;
215 : 1049 : context->cur_attr = -1;
216 : 1049 : context->alloc_attrs = 0;
217 : :
218 : 1049 : context->current_text = NULL;
219 : 1049 : context->current_text_len = -1;
220 : 1049 : context->current_text_end = NULL;
221 : :
222 : 1049 : context->start = NULL;
223 : 1049 : context->iter = NULL;
224 : :
225 : 1049 : context->document_empty = TRUE;
226 : 1049 : context->parsing = FALSE;
227 : :
228 : 1049 : context->awaiting_pop = FALSE;
229 : 1049 : context->subparser_stack = NULL;
230 : 1049 : context->subparser_element = NULL;
231 : :
232 : : /* this is only looked at if awaiting_pop = TRUE. initialise anyway. */
233 : 1049 : context->held_user_data = NULL;
234 : :
235 : 1049 : context->balance = 0;
236 : :
237 : 1049 : return context;
238 : : }
239 : :
240 : : /**
241 : : * g_markup_parse_context_ref:
242 : : * @context: a #GMarkupParseContext
243 : : *
244 : : * Increases the reference count of @context.
245 : : *
246 : : * Returns: the same @context
247 : : *
248 : : * Since: 2.36
249 : : **/
250 : : GMarkupParseContext *
251 : 1 : g_markup_parse_context_ref (GMarkupParseContext *context)
252 : : {
253 : 1 : g_return_val_if_fail (context != NULL, NULL);
254 : 1 : g_return_val_if_fail (context->ref_count > 0, NULL);
255 : :
256 : 1 : g_atomic_int_inc (&context->ref_count);
257 : :
258 : 1 : return context;
259 : : }
260 : :
261 : : /**
262 : : * g_markup_parse_context_unref:
263 : : * @context: a #GMarkupParseContext
264 : : *
265 : : * Decreases the reference count of @context. When its reference count
266 : : * drops to 0, it is freed.
267 : : *
268 : : * Since: 2.36
269 : : **/
270 : : void
271 : 3 : g_markup_parse_context_unref (GMarkupParseContext *context)
272 : : {
273 : 3 : g_return_if_fail (context != NULL);
274 : 3 : g_return_if_fail (context->ref_count > 0);
275 : :
276 : 3 : if (g_atomic_int_dec_and_test (&context->ref_count))
277 : 2 : g_markup_parse_context_free (context);
278 : : }
279 : :
280 : : static void
281 : 19972 : string_full_free (gpointer ptr)
282 : : {
283 : 19972 : g_string_free (ptr, TRUE);
284 : 19972 : }
285 : :
286 : : static void clear_attributes (GMarkupParseContext *context);
287 : :
288 : : /**
289 : : * g_markup_parse_context_free:
290 : : * @context: a #GMarkupParseContext
291 : : *
292 : : * Frees a #GMarkupParseContext.
293 : : *
294 : : * This function can't be called from inside one of the
295 : : * #GMarkupParser functions or while a subparser is pushed.
296 : : */
297 : : void
298 : 985 : g_markup_parse_context_free (GMarkupParseContext *context)
299 : : {
300 : 985 : g_return_if_fail (context != NULL);
301 : 985 : g_return_if_fail (!context->parsing);
302 : 985 : g_return_if_fail (!context->subparser_stack);
303 : 985 : g_return_if_fail (!context->awaiting_pop);
304 : :
305 : 985 : if (context->dnotify)
306 : 214 : (* context->dnotify) (context->user_data);
307 : :
308 : 985 : clear_attributes (context);
309 : 985 : g_free (context->attr_names);
310 : 985 : g_free (context->attr_values);
311 : 985 : g_free (context->attr_pos);
312 : 985 : g_free (context->attr_end);
313 : :
314 : 985 : g_slist_free_full (context->tag_stack_gstr, string_full_free);
315 : 985 : g_slist_free (context->tag_stack);
316 : :
317 : 985 : g_slist_free_full (context->spare_chunks, string_full_free);
318 : 985 : g_slist_free (context->spare_list_nodes);
319 : :
320 : 985 : if (context->partial_chunk)
321 : 196 : g_string_free (context->partial_chunk, TRUE);
322 : :
323 : 985 : g_free (context);
324 : : }
325 : :
326 : : static void pop_subparser_stack (GMarkupParseContext *context);
327 : :
328 : : static void
329 : 641 : mark_error (GMarkupParseContext *context,
330 : : GError *error)
331 : : {
332 : 641 : context->state = STATE_ERROR;
333 : :
334 : 641 : if (context->parser->error)
335 : 458 : (*context->parser->error) (context, error, context->user_data);
336 : :
337 : : /* report the error all the way up to free all the user-data */
338 : 1285 : while (context->subparser_stack)
339 : : {
340 : 3 : pop_subparser_stack (context);
341 : 3 : context->awaiting_pop = FALSE; /* already been freed */
342 : :
343 : 3 : if (context->parser->error)
344 : 0 : (*context->parser->error) (context, error, context->user_data);
345 : : }
346 : 641 : }
347 : :
348 : : static void
349 : : set_error (GMarkupParseContext *context,
350 : : GError **error,
351 : : GMarkupError code,
352 : : const gchar *format,
353 : : ...) G_GNUC_PRINTF (4, 5);
354 : :
355 : : static void
356 : 398 : set_error_literal (GMarkupParseContext *context,
357 : : GError **error,
358 : : GMarkupError code,
359 : : const gchar *message)
360 : : {
361 : : GError *tmp_error;
362 : :
363 : 398 : tmp_error = g_error_new_literal (G_MARKUP_ERROR, code, message);
364 : :
365 : 398 : g_prefix_error (&tmp_error,
366 : : _("Error on line %" G_GSIZE_FORMAT " char %" G_GSIZE_FORMAT ": "),
367 : : context->pos.lines,
368 : : context->pos.chars);
369 : :
370 : 398 : mark_error (context, tmp_error);
371 : :
372 : 398 : g_propagate_error (error, tmp_error);
373 : 398 : }
374 : :
375 : : G_GNUC_PRINTF(4, 5)
376 : : static void
377 : 264 : set_error (GMarkupParseContext *context,
378 : : GError **error,
379 : : GMarkupError code,
380 : : const gchar *format,
381 : : ...)
382 : : {
383 : : gchar *s;
384 : : gchar *s_valid;
385 : : va_list args;
386 : :
387 : 264 : va_start (args, format);
388 : 264 : s = g_strdup_vprintf (format, args);
389 : 264 : va_end (args);
390 : :
391 : : /* Make sure that the GError message is valid UTF-8
392 : : * even if it is complaining about invalid UTF-8 in the markup
393 : : */
394 : 264 : s_valid = g_utf8_make_valid (s, -1);
395 : 264 : set_error_literal (context, error, code, s);
396 : :
397 : 264 : g_free (s);
398 : 264 : g_free (s_valid);
399 : 264 : }
400 : :
401 : : static void
402 : 116 : propagate_error (GMarkupParseContext *context,
403 : : GError **dest,
404 : : GError *src)
405 : : {
406 : 116 : if (context->flags & G_MARKUP_PREFIX_ERROR_POSITION)
407 : 47 : g_prefix_error (&src,
408 : : _("Error on line %" G_GSIZE_FORMAT " char %" G_GSIZE_FORMAT ": "),
409 : : context->pos.lines,
410 : : context->pos.chars);
411 : :
412 : 116 : mark_error (context, src);
413 : :
414 : 116 : g_propagate_error (dest, src);
415 : 116 : }
416 : :
417 : : #define IS_COMMON_NAME_END_CHAR(c) \
418 : : ((c) == '=' || (c) == '/' || (c) == '>' || (c) == ' ')
419 : :
420 : : static gboolean
421 : 48 : slow_name_validate (GMarkupParseContext *context,
422 : : const gchar *name,
423 : : GError **error)
424 : : {
425 : 48 : const gchar *p = name;
426 : :
427 : 48 : if (!g_utf8_validate (name, -1, NULL))
428 : : {
429 : 8 : set_error (context, error, G_MARKUP_ERROR_BAD_UTF8,
430 : : _("Invalid UTF-8 encoded text in name — not valid “%s”"), name);
431 : 8 : return FALSE;
432 : : }
433 : :
434 : 40 : if (!(g_ascii_isalpha (*p) ||
435 : 16 : (!IS_COMMON_NAME_END_CHAR (*p) &&
436 : 16 : (*p == '_' ||
437 : 16 : *p == ':' ||
438 : 16 : g_unichar_isalpha (g_utf8_get_char (p))))))
439 : : {
440 : 16 : set_error (context, error, G_MARKUP_ERROR_PARSE,
441 : : _("“%s” is not a valid name"), name);
442 : 16 : return FALSE;
443 : : }
444 : :
445 : 96 : for (p = g_utf8_next_char (name); *p != '\0'; p = g_utf8_next_char (p))
446 : : {
447 : : /* is_name_char */
448 : 88 : if (!(g_ascii_isalnum (*p) ||
449 : 40 : (!IS_COMMON_NAME_END_CHAR (*p) &&
450 : 40 : (*p == '.' ||
451 : 40 : *p == '-' ||
452 : 40 : *p == '_' ||
453 : 40 : *p == ':' ||
454 : 40 : g_unichar_isalpha (g_utf8_get_char (p))))))
455 : : {
456 : 16 : set_error (context, error, G_MARKUP_ERROR_PARSE,
457 : 16 : _("“%s” is not a valid name: “%c”"), name, *p);
458 : 16 : return FALSE;
459 : : }
460 : : }
461 : 8 : return TRUE;
462 : : }
463 : :
464 : : /*
465 : : * Use me for elements, attributes etc.
466 : : */
467 : : static gboolean
468 : 5063763 : name_validate (GMarkupParseContext *context,
469 : : const gchar *name,
470 : : GError **error)
471 : : {
472 : : char mask;
473 : : const char *p;
474 : :
475 : : /* name start char */
476 : 5063763 : p = name;
477 : 5063763 : if (G_UNLIKELY (IS_COMMON_NAME_END_CHAR (*p) ||
478 : : !(g_ascii_isalpha (*p) || *p == '_' || *p == ':')))
479 : 16 : goto slow_validate;
480 : :
481 : 37697221 : for (mask = *p++; *p != '\0'; p++)
482 : : {
483 : 32633506 : mask |= *p;
484 : :
485 : : /* is_name_char */
486 : 32633506 : if (G_UNLIKELY (!(g_ascii_isalnum (*p) ||
487 : : (!IS_COMMON_NAME_END_CHAR (*p) &&
488 : : (*p == '.' ||
489 : : *p == '-' ||
490 : : *p == '_' ||
491 : : *p == ':')))))
492 : 32 : goto slow_validate;
493 : : }
494 : :
495 : 5063715 : if (mask & 0x80) /* un-common / non-ascii */
496 : 0 : goto slow_validate;
497 : :
498 : 5063715 : return TRUE;
499 : :
500 : 48 : slow_validate:
501 : 48 : return slow_name_validate (context, name, error);
502 : : }
503 : :
504 : : static gboolean
505 : 6985 : text_validate (GMarkupParseContext *context,
506 : : const gchar *p,
507 : : gsize len,
508 : : GError **error)
509 : : {
510 : 6985 : if (!g_utf8_validate_len (p, len, NULL))
511 : : {
512 : 8 : set_error (context, error, G_MARKUP_ERROR_BAD_UTF8,
513 : : _("Invalid UTF-8 encoded text in name — not valid “%s”"), p);
514 : 8 : return FALSE;
515 : : }
516 : : else
517 : 6977 : return TRUE;
518 : : }
519 : :
520 : : static gchar*
521 : 113 : char_str (gunichar c,
522 : : gchar *buf)
523 : : {
524 : 113 : memset (buf, 0, 8);
525 : 113 : g_unichar_to_utf8 (c, buf);
526 : 113 : return buf;
527 : : }
528 : :
529 : : /* Format the next UTF-8 character as a gchar* for printing in error output
530 : : * when we encounter a syntax error. This correctly handles invalid UTF-8,
531 : : * emitting it as hex escapes. */
532 : : static gchar*
533 : 105 : utf8_str (const gchar *utf8,
534 : : gsize max_len,
535 : : gchar *buf)
536 : : {
537 : 105 : gunichar c = g_utf8_get_char_validated (utf8, max_len);
538 : 105 : if (c == (gunichar) -1 || c == (gunichar) -2)
539 : 32 : {
540 : 32 : guchar ch = (max_len > 0) ? (guchar) *utf8 : 0;
541 : 32 : gchar *temp = g_strdup_printf ("\\x%02x", (guint) ch);
542 : 32 : memset (buf, 0, 8);
543 : 32 : memcpy (buf, temp, strlen (temp));
544 : 32 : g_free (temp);
545 : : }
546 : : else
547 : 73 : char_str (c, buf);
548 : 105 : return buf;
549 : : }
550 : :
551 : : G_GNUC_PRINTF(5, 6)
552 : : static void
553 : 108 : set_unescape_error (GMarkupParseContext *context,
554 : : GError **error,
555 : : const gchar *remaining_text,
556 : : GMarkupError code,
557 : : const gchar *format,
558 : : ...)
559 : : {
560 : : GError *tmp_error;
561 : : gchar *s;
562 : : va_list args;
563 : : gint remaining_newlines;
564 : : const gchar *p;
565 : :
566 : 108 : remaining_newlines = 0;
567 : 108 : p = remaining_text;
568 : 1086 : while (*p != '\0')
569 : : {
570 : 978 : if (*p == '\n')
571 : 16 : ++remaining_newlines;
572 : 978 : ++p;
573 : : }
574 : :
575 : 108 : va_start (args, format);
576 : 108 : s = g_strdup_vprintf (format, args);
577 : 108 : va_end (args);
578 : :
579 : 108 : tmp_error = g_error_new (G_MARKUP_ERROR,
580 : : code,
581 : : _("Error on line %" G_GSIZE_FORMAT ": %s"),
582 : 108 : context->pos.lines - remaining_newlines,
583 : : s);
584 : :
585 : 108 : g_free (s);
586 : :
587 : 108 : mark_error (context, tmp_error);
588 : :
589 : 108 : g_propagate_error (error, tmp_error);
590 : 108 : }
591 : :
592 : : /*
593 : : * re-write the GString in-place, unescaping anything that escaped.
594 : : * most XML does not contain entities, or escaping.
595 : : */
596 : : static gboolean
597 : 6146930 : unescape_gstring_inplace (GMarkupParseContext *context,
598 : : GString *string,
599 : : gboolean *is_ascii,
600 : : GError **error)
601 : : {
602 : : char mask, *to;
603 : : const char *from;
604 : : gboolean normalize_attribute;
605 : :
606 : 6146930 : *is_ascii = FALSE;
607 : :
608 : : /* are we unescaping an attribute or not ? */
609 : 6146930 : if (context->state == STATE_INSIDE_ATTRIBUTE_VALUE_SQ ||
610 : 6133965 : context->state == STATE_INSIDE_ATTRIBUTE_VALUE_DQ)
611 : 3509424 : normalize_attribute = TRUE;
612 : : else
613 : 2637506 : normalize_attribute = FALSE;
614 : :
615 : : /*
616 : : * Meeks' theorem: unescaping can only shrink text.
617 : : * for < etc. this is obvious, for  more
618 : : * thought is required, but this is patently so.
619 : : */
620 : 6146930 : mask = 0;
621 : 108330241 : for (from = to = string->str; *from != '\0'; from++, to++)
622 : : {
623 : 102183419 : *to = *from;
624 : :
625 : 102183419 : mask |= *to;
626 : 102183419 : if (normalize_attribute && (*to == '\t' || *to == '\n'))
627 : 40 : *to = ' ';
628 : 102183419 : if (*to == '\r')
629 : : {
630 : 32 : *to = normalize_attribute ? ' ' : '\n';
631 : 32 : if (from[1] == '\n')
632 : 24 : from++;
633 : : }
634 : 102183419 : if (*from == '&')
635 : : {
636 : 27674 : from++;
637 : 27674 : if (*from == '#')
638 : : {
639 : 80 : gint base = 10;
640 : : gulong l;
641 : 80 : gchar *end = NULL;
642 : :
643 : 80 : from++;
644 : :
645 : 80 : if (*from == 'x')
646 : : {
647 : 24 : base = 16;
648 : 24 : from++;
649 : : }
650 : :
651 : 80 : errno = 0;
652 : 80 : l = strtoul (from, &end, base);
653 : :
654 : 80 : if (end == from || errno != 0)
655 : : {
656 : 24 : set_unescape_error (context, error,
657 : : from, G_MARKUP_ERROR_PARSE,
658 : : _("Failed to parse “%-.*s”, which "
659 : : "should have been a digit "
660 : : "inside a character reference "
661 : : "(ê for example) — perhaps "
662 : : "the digit is too large"),
663 : 24 : (int)(end - from), from);
664 : 40 : return FALSE;
665 : : }
666 : 56 : else if (*end != ';')
667 : : {
668 : 8 : set_unescape_error (context, error,
669 : : from, G_MARKUP_ERROR_PARSE,
670 : : _("Character reference did not end with a "
671 : : "semicolon; "
672 : : "most likely you used an ampersand "
673 : : "character without intending to start "
674 : : "an entity — escape ampersand as &"));
675 : 8 : return FALSE;
676 : : }
677 : : else
678 : : {
679 : : /* characters XML 1.1 permits */
680 : 48 : if ((0 < l && l <= 0xD7FF) ||
681 : 8 : (0xE000 <= l && l <= 0xFFFD) ||
682 : 0 : (0x10000 <= l && l <= 0x10FFFF))
683 : 40 : {
684 : : gchar buf[8];
685 : 40 : char_str (l, buf);
686 : 40 : strcpy (to, buf);
687 : 40 : to += strlen (buf) - 1;
688 : 40 : from = end;
689 : 40 : if (l >= 0x80) /* not ascii */
690 : 0 : mask |= 0x80;
691 : : }
692 : : else
693 : : {
694 : 8 : set_unescape_error (context, error,
695 : : from, G_MARKUP_ERROR_PARSE,
696 : : _("Character reference “%-.*s” does not "
697 : : "encode a permitted character"),
698 : 8 : (int)(end - from), from);
699 : 8 : return FALSE;
700 : : }
701 : : }
702 : : }
703 : :
704 : 27594 : else if (strncmp (from, "lt;", 3) == 0)
705 : : {
706 : 8564 : *to = '<';
707 : 8564 : from += 2;
708 : : }
709 : 19030 : else if (strncmp (from, "gt;", 3) == 0)
710 : : {
711 : 11776 : *to = '>';
712 : 11776 : from += 2;
713 : : }
714 : 7254 : else if (strncmp (from, "amp;", 4) == 0)
715 : : {
716 : 7162 : *to = '&';
717 : 7162 : from += 3;
718 : : }
719 : 92 : else if (strncmp (from, "quot;", 5) == 0)
720 : : {
721 : 12 : *to = '"';
722 : 12 : from += 4;
723 : : }
724 : 80 : else if (strncmp (from, "apos;", 5) == 0)
725 : : {
726 : 12 : *to = '\'';
727 : 12 : from += 4;
728 : : }
729 : : else
730 : : {
731 : 68 : if (*from == ';')
732 : 8 : set_unescape_error (context, error,
733 : : from, G_MARKUP_ERROR_PARSE,
734 : : _("Empty entity “&;” seen; valid "
735 : : "entities are: & " < > '"));
736 : : else
737 : : {
738 : 60 : const char *end = strchr (from, ';');
739 : 60 : if (end)
740 : 32 : set_unescape_error (context, error,
741 : : from, G_MARKUP_ERROR_PARSE,
742 : : _("Entity name “%-.*s” is not known"),
743 : 32 : (int)(end - from), from);
744 : : else
745 : 28 : set_unescape_error (context, error,
746 : : from, G_MARKUP_ERROR_PARSE,
747 : : _("Entity did not end with a semicolon; "
748 : : "most likely you used an ampersand "
749 : : "character without intending to start "
750 : : "an entity — escape ampersand as &"));
751 : : }
752 : 68 : return FALSE;
753 : : }
754 : : }
755 : : }
756 : :
757 : 6146822 : g_assert (to - string->str <= (gssize) string->len);
758 : 6146822 : if (to - string->str != (gssize) string->len)
759 : 5811 : g_string_truncate (string, to - string->str);
760 : :
761 : 6146822 : *is_ascii = !(mask & 0x80);
762 : :
763 : 6146822 : return TRUE;
764 : : }
765 : :
766 : : static inline gboolean
767 : 189713240 : advance_char (GMarkupParseContext *context)
768 : : {
769 : 189713240 : context->iter++;
770 : 189713240 : context->pos.chars++;
771 : 189713240 : context->pos.offset++;
772 : :
773 : 189713240 : if (G_UNLIKELY (context->iter == context->current_text_end))
774 : 24855 : return FALSE;
775 : :
776 : 189688385 : else if (G_UNLIKELY (*context->iter == '\n'))
777 : : {
778 : 4351098 : context->pos.lines++;
779 : 4351098 : context->pos.chars = 1;
780 : : }
781 : :
782 : 189688385 : return TRUE;
783 : : }
784 : :
785 : : static inline gboolean
786 : 83210675 : xml_isspace (char c)
787 : : {
788 : 83210675 : return c == ' ' || c == '\t' || c == '\n' || c == '\r';
789 : : }
790 : :
791 : : static void
792 : 13169876 : skip_spaces (GMarkupParseContext *context)
793 : : {
794 : : do
795 : : {
796 : 37707410 : if (!xml_isspace (*context->iter))
797 : 13168129 : return;
798 : : }
799 : 24539281 : while (advance_char (context));
800 : : }
801 : :
802 : : static void
803 : 6155795 : advance_to_name_end (GMarkupParseContext *context)
804 : : {
805 : : do
806 : : {
807 : 51650987 : if (IS_COMMON_NAME_END_CHAR (*(context->iter)))
808 : 6147722 : return;
809 : 45503265 : if (xml_isspace (*(context->iter)))
810 : 40 : return;
811 : : }
812 : 45503225 : while (advance_char (context));
813 : : }
814 : :
815 : : static void
816 : 8572500 : release_chunk (GMarkupParseContext *context, GString *str)
817 : : {
818 : : GSList *node;
819 : 8572500 : if (!str)
820 : 72 : return;
821 : 8572428 : if (str->allocated_len > 256)
822 : : { /* large strings are unusual and worth freeing */
823 : 44454 : g_string_free (str, TRUE);
824 : 44454 : return;
825 : : }
826 : : g_string_truncate (str, 0);
827 : 8527974 : node = get_list_node (context, str);
828 : 8527974 : context->spare_chunks = g_slist_concat (node, context->spare_chunks);
829 : : }
830 : :
831 : : static void
832 : 12312659 : add_to_partial (GMarkupParseContext *context,
833 : : const gchar *text_start,
834 : : const gchar *text_end)
835 : : {
836 : 12312659 : if (context->partial_chunk == NULL)
837 : : { /* allocate a new chunk to parse into */
838 : :
839 : 8573978 : if (context->spare_chunks != NULL)
840 : : {
841 : 8508389 : GSList *node = context->spare_chunks;
842 : 8508389 : context->spare_chunks = g_slist_remove_link (context->spare_chunks, node);
843 : 8508389 : context->partial_chunk = node->data;
844 : 8508389 : free_list_node (context, node);
845 : : }
846 : : else
847 : 65589 : context->partial_chunk = g_string_sized_new (MAX (28, text_end - text_start));
848 : : }
849 : :
850 : 12312659 : if (text_start != text_end)
851 : 12302039 : g_string_append_len (context->partial_chunk,
852 : : text_start, text_end - text_start);
853 : 12312659 : }
854 : :
855 : : static inline void
856 : 7231345 : truncate_partial (GMarkupParseContext *context)
857 : : {
858 : 7231345 : if (context->partial_chunk != NULL)
859 : 3721937 : g_string_truncate (context->partial_chunk, 0);
860 : 7231345 : }
861 : :
862 : : static inline const gchar*
863 : 5756023 : current_element (GMarkupParseContext *context)
864 : : {
865 : 5756023 : return context->tag_stack->data;
866 : : }
867 : :
868 : : static void
869 : 24 : pop_subparser_stack (GMarkupParseContext *context)
870 : : {
871 : : GMarkupRecursionTracker *tracker;
872 : :
873 : 24 : g_assert (context->subparser_stack);
874 : :
875 : 24 : tracker = context->subparser_stack->data;
876 : :
877 : 24 : context->awaiting_pop = TRUE;
878 : 24 : context->held_user_data = context->user_data;
879 : :
880 : 24 : context->user_data = tracker->prev_user_data;
881 : 24 : context->parser = tracker->prev_parser;
882 : 24 : context->subparser_element = tracker->prev_element;
883 : 24 : g_slice_free (GMarkupRecursionTracker, tracker);
884 : :
885 : 24 : context->subparser_stack = g_slist_delete_link (context->subparser_stack,
886 : : context->subparser_stack);
887 : 24 : }
888 : :
889 : : static void
890 : 1554383 : push_partial_as_tag (GMarkupParseContext *context)
891 : : {
892 : 1554383 : GString *str = context->partial_chunk;
893 : :
894 : : /* sadly, this is exported by gmarkup_get_element_stack as-is */
895 : 1554383 : context->tag_stack = g_slist_concat (get_list_node (context, str->str), context->tag_stack);
896 : 1554383 : context->tag_stack_gstr = g_slist_concat (get_list_node (context, str), context->tag_stack_gstr);
897 : :
898 : 1554383 : context->partial_chunk = NULL;
899 : 1554383 : }
900 : :
901 : : static void
902 : 1553540 : pop_tag (GMarkupParseContext *context)
903 : : {
904 : : GSList *nodea, *nodeb;
905 : :
906 : 1553540 : nodea = context->tag_stack;
907 : 1553540 : nodeb = context->tag_stack_gstr;
908 : 1553540 : release_chunk (context, nodeb->data);
909 : 1553540 : context->tag_stack = g_slist_remove_link (context->tag_stack, nodea);
910 : 1553540 : context->tag_stack_gstr = g_slist_remove_link (context->tag_stack_gstr, nodeb);
911 : 1553540 : free_list_node (context, nodea);
912 : 1553540 : free_list_node (context, nodeb);
913 : 1553540 : }
914 : :
915 : : static void
916 : 1553540 : possibly_finish_subparser (GMarkupParseContext *context)
917 : : {
918 : 1553540 : if (current_element (context) == context->subparser_element)
919 : 21 : pop_subparser_stack (context);
920 : 1553540 : }
921 : :
922 : : static void
923 : 1553528 : ensure_no_outstanding_subparser (GMarkupParseContext *context)
924 : : {
925 : 1553528 : if (context->awaiting_pop)
926 : 0 : g_critical ("During the first end_element call after invoking a "
927 : : "subparser you must pop the subparser stack and handle "
928 : : "the freeing of the subparser user_data. This can be "
929 : : "done by calling the end function of the subparser. "
930 : : "Very probably, your program just leaked memory.");
931 : :
932 : : /* let valgrind watch the pointer disappear... */
933 : 1553528 : context->held_user_data = NULL;
934 : 1553528 : context->awaiting_pop = FALSE;
935 : 1553528 : }
936 : :
937 : : static const gchar*
938 : 40 : current_attribute (GMarkupParseContext *context)
939 : : {
940 : 40 : g_assert (context->cur_attr >= 0);
941 : 40 : return context->attr_names[context->cur_attr]->str;
942 : : }
943 : :
944 : : static gboolean
945 : 3509488 : add_attribute (GMarkupParseContext *context, GString *str)
946 : : {
947 : : /* Sanity check on the number of attributes. */
948 : 3509488 : if (context->cur_attr >= 1000)
949 : 8 : return FALSE;
950 : :
951 : 3509480 : if (context->cur_attr + 2 >= context->alloc_attrs)
952 : : {
953 : 2232 : context->alloc_attrs += 5; /* silly magic number */
954 : 2232 : context->attr_names = g_realloc_n (context->attr_names, context->alloc_attrs, sizeof (GString*));
955 : 2232 : context->attr_values = g_realloc_n (context->attr_values, context->alloc_attrs, sizeof (GString*));
956 : 2232 : context->attr_pos = g_realloc_n (context->attr_pos, context->alloc_attrs, sizeof (MarkupLocation));
957 : 2232 : context->attr_end = g_realloc_n (context->attr_end, context->alloc_attrs, sizeof (MarkupLocation));
958 : : }
959 : 3509480 : context->cur_attr++;
960 : 3509480 : context->attr_names[context->cur_attr] = str;
961 : 3509480 : context->attr_values[context->cur_attr] = NULL;
962 : 3509480 : context->attr_names[context->cur_attr+1] = NULL;
963 : 3509480 : context->attr_values[context->cur_attr+1] = NULL;
964 : 3509480 : context->attr_pos[context->cur_attr] = context->attr_start;
965 : 3509480 : context->attr_end[context->cur_attr] = context->pos; /* Will be overwritten later */
966 : :
967 : 3509480 : return TRUE;
968 : : }
969 : :
970 : : static void
971 : 1555232 : clear_attributes (GMarkupParseContext *context)
972 : : {
973 : : /* Go ahead and free the attributes. */
974 : 5064712 : for (; context->cur_attr >= 0; context->cur_attr--)
975 : : {
976 : 3509480 : int pos = context->cur_attr;
977 : 3509480 : release_chunk (context, context->attr_names[pos]);
978 : 3509480 : release_chunk (context, context->attr_values[pos]);
979 : 3509480 : context->attr_names[pos] = context->attr_values[pos] = NULL;
980 : : }
981 : 1555232 : g_assert (context->cur_attr == -1);
982 : 1555232 : g_assert (context->attr_names == NULL ||
983 : : context->attr_names[0] == NULL);
984 : 1555232 : g_assert (context->attr_values == NULL ||
985 : : context->attr_values[0] == NULL);
986 : 1555232 : }
987 : :
988 : : /* This has to be a separate function to ensure the alloca's
989 : : * are unwound on exit - otherwise we grow & blow the stack
990 : : * with large documents
991 : : */
992 : : static inline void
993 : 1554279 : emit_start_element (GMarkupParseContext *context,
994 : : GError **error)
995 : : {
996 : 1554279 : int i, j = 0;
997 : : const gchar *start_name;
998 : : const gchar **attr_names;
999 : : const gchar **attr_values;
1000 : : GError *tmp_error;
1001 : :
1002 : : /* In case we want to ignore qualified tags and we see that we have
1003 : : * one here, we push a subparser. This will ignore all tags inside of
1004 : : * the qualified tag.
1005 : : *
1006 : : * We deal with the end of the subparser from emit_end_element.
1007 : : */
1008 : 1554279 : if ((context->flags & G_MARKUP_IGNORE_QUALIFIED) && strchr (current_element (context), ':'))
1009 : : {
1010 : : static const GMarkupParser ignore_parser = { 0 };
1011 : 12 : g_markup_parse_context_push (context, &ignore_parser, NULL);
1012 : 12 : clear_attributes (context);
1013 : 44 : return;
1014 : : }
1015 : :
1016 : 1554267 : attr_names = g_newa (const gchar *, context->cur_attr + 2);
1017 : 1554267 : attr_values = g_newa (const gchar *, context->cur_attr + 2);
1018 : 5055658 : for (i = 0; i < context->cur_attr + 1; i++)
1019 : : {
1020 : : /* Possibly omit qualified attribute names from the list */
1021 : 3501391 : if ((context->flags & G_MARKUP_IGNORE_QUALIFIED) && strchr (context->attr_names[i]->str, ':'))
1022 : 4 : continue;
1023 : :
1024 : 3501387 : attr_names[j] = context->attr_names[i]->str;
1025 : 3501387 : attr_values[j] = context->attr_values[i]->str;
1026 : 3501387 : context->attr_pos[j] = context->attr_pos[i];
1027 : 3501387 : context->attr_end[j] = context->attr_end[i];
1028 : 3501387 : j++;
1029 : : }
1030 : 1554267 : attr_names[j] = NULL;
1031 : 1554267 : attr_values[j] = NULL;
1032 : :
1033 : : /* Call user callback for element start */
1034 : 1554267 : tmp_error = NULL;
1035 : 1554267 : start_name = current_element (context);
1036 : :
1037 : 1554267 : if (!name_validate (context, start_name, error))
1038 : 32 : return;
1039 : :
1040 : 1554235 : if (context->parser->start_element)
1041 : 1553639 : (* context->parser->start_element) (context,
1042 : : start_name,
1043 : : (const gchar **)attr_names,
1044 : : (const gchar **)attr_values,
1045 : : context->user_data,
1046 : : &tmp_error);
1047 : :
1048 : 1554235 : clear_attributes (context);
1049 : :
1050 : 1554235 : if (tmp_error != NULL)
1051 : 116 : propagate_error (context, error, tmp_error);
1052 : : }
1053 : :
1054 : : static void
1055 : 1553540 : emit_end_element (GMarkupParseContext *context,
1056 : : GError **error)
1057 : : {
1058 : : /* We need to pop the tag stack and call the end_element
1059 : : * function, since this is the close tag
1060 : : */
1061 : 1553540 : GError *tmp_error = NULL;
1062 : :
1063 : 1553540 : g_assert (context->tag_stack != NULL);
1064 : :
1065 : 1553540 : possibly_finish_subparser (context);
1066 : :
1067 : : /* We might have just returned from our ignore subparser */
1068 : 1553540 : if ((context->flags & G_MARKUP_IGNORE_QUALIFIED) && strchr (current_element (context), ':'))
1069 : : {
1070 : 12 : g_markup_parse_context_pop (context);
1071 : 12 : pop_tag (context);
1072 : 12 : return;
1073 : : }
1074 : :
1075 : 1553528 : tmp_error = NULL;
1076 : 1553528 : if (context->parser->end_element)
1077 : 1553121 : (* context->parser->end_element) (context,
1078 : : current_element (context),
1079 : : context->user_data,
1080 : : &tmp_error);
1081 : :
1082 : 1553528 : ensure_no_outstanding_subparser (context);
1083 : :
1084 : 1553528 : if (tmp_error)
1085 : : {
1086 : 19 : mark_error (context, tmp_error);
1087 : 19 : g_propagate_error (error, tmp_error);
1088 : : }
1089 : :
1090 : 1553528 : pop_tag (context);
1091 : : }
1092 : :
1093 : : /**
1094 : : * g_markup_parse_context_parse:
1095 : : * @context: a #GMarkupParseContext
1096 : : * @text: chunk of text to parse
1097 : : * @text_len: length of @text in bytes
1098 : : * @error: return location for a #GError
1099 : : *
1100 : : * Feed some data to the #GMarkupParseContext.
1101 : : *
1102 : : * The data need not be valid UTF-8; an error will be signaled if
1103 : : * it's invalid. The data need not be an entire document; you can
1104 : : * feed a document into the parser incrementally, via multiple calls
1105 : : * to this function. Typically, as you receive data from a network
1106 : : * connection or file, you feed each received chunk of data into this
1107 : : * function, aborting the process if an error occurs. Once an error
1108 : : * is reported, no further data may be fed to the #GMarkupParseContext;
1109 : : * all errors are fatal.
1110 : : *
1111 : : * Returns: %FALSE if an error occurred, %TRUE on success
1112 : : */
1113 : : gboolean
1114 : 25331 : g_markup_parse_context_parse (GMarkupParseContext *context,
1115 : : const gchar *text,
1116 : : gssize text_len,
1117 : : GError **error)
1118 : : {
1119 : 25331 : g_return_val_if_fail (context != NULL, FALSE);
1120 : 25331 : g_return_val_if_fail (text != NULL, FALSE);
1121 : 25331 : g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
1122 : 25331 : g_return_val_if_fail (!context->parsing, FALSE);
1123 : :
1124 : 25331 : if (text_len < 0)
1125 : 104 : text_len = strlen (text);
1126 : :
1127 : 25331 : if (text_len == 0)
1128 : 5 : return TRUE;
1129 : :
1130 : 25326 : context->parsing = TRUE;
1131 : :
1132 : 25326 : context->current_text = text;
1133 : 25326 : context->current_text_len = text_len;
1134 : 25326 : context->current_text_end = context->current_text + text_len;
1135 : 25326 : context->iter = context->current_text;
1136 : 25326 : context->start = context->iter;
1137 : :
1138 : 32338599 : while (context->iter != context->current_text_end)
1139 : : {
1140 : 32313744 : switch (context->state)
1141 : : {
1142 : 1035 : case STATE_INITIAL:
1143 : 1035 : if ((guchar) *context->iter == 0xef)
1144 : : {
1145 : 24 : advance_char (context);
1146 : 24 : context->state = STATE_AFTER_BOM1;
1147 : : }
1148 : : else
1149 : : {
1150 : 1011 : context->state = STATE_START;
1151 : : }
1152 : 1035 : break;
1153 : :
1154 : 24 : case STATE_AFTER_BOM1:
1155 : 24 : if ((guchar) *context->iter == 0xbb)
1156 : : {
1157 : 16 : advance_char (context);
1158 : 16 : context->state = STATE_AFTER_BOM2;
1159 : : }
1160 : : else
1161 : : {
1162 : 8 : set_error_literal (context,
1163 : : error,
1164 : : G_MARKUP_ERROR_PARSE,
1165 : : _("Invalid byte order mark"));
1166 : : }
1167 : 24 : break;
1168 : :
1169 : 16 : case STATE_AFTER_BOM2:
1170 : 16 : if ((guchar) *context->iter == 0xbf)
1171 : : {
1172 : 8 : advance_char (context);
1173 : 8 : context->state = STATE_START;
1174 : : }
1175 : : else
1176 : : {
1177 : 8 : set_error_literal (context,
1178 : : error,
1179 : : G_MARKUP_ERROR_PARSE,
1180 : : _("Invalid byte order mark"));
1181 : : }
1182 : 16 : break;
1183 : :
1184 : 1876 : case STATE_START:
1185 : : /* Possible next state: AFTER_OPEN_ANGLE */
1186 : :
1187 : 1876 : g_assert (context->tag_stack == NULL);
1188 : :
1189 : : /* whitespace is ignored outside of any elements */
1190 : 1876 : skip_spaces (context);
1191 : :
1192 : 1876 : if (context->iter != context->current_text_end)
1193 : : {
1194 : 1527 : if (*context->iter == '<')
1195 : : {
1196 : : /* Move after the open angle */
1197 : 1493 : advance_char (context);
1198 : :
1199 : 1493 : context->state = STATE_AFTER_OPEN_ANGLE;
1200 : :
1201 : : /* this could start a passthrough */
1202 : 1493 : context->start = context->iter;
1203 : :
1204 : : /* document is now non-empty */
1205 : 1493 : context->document_empty = FALSE;
1206 : : }
1207 : : else
1208 : : {
1209 : 34 : set_error_literal (context,
1210 : : error,
1211 : : G_MARKUP_ERROR_PARSE,
1212 : : _("Document must begin with an element (e.g. <book>)"));
1213 : : }
1214 : : }
1215 : 1876 : break;
1216 : :
1217 : 2638891 : case STATE_AFTER_OPEN_ANGLE:
1218 : : /* Possible next states: INSIDE_OPEN_TAG_NAME,
1219 : : * AFTER_CLOSE_TAG_SLASH, INSIDE_PASSTHROUGH
1220 : : */
1221 : 2638891 : context->tag_start.lines = context->pos.lines;
1222 : 2638891 : context->tag_start.chars = context->pos.chars - 1;
1223 : 2638891 : context->tag_start.offset = context->pos.offset - 1;
1224 : :
1225 : 2638891 : if (*context->iter == '?' ||
1226 : 2638697 : *context->iter == '!')
1227 : 556 : {
1228 : : /* include < in the passthrough */
1229 : 556 : const gchar *openangle = "<";
1230 : 556 : add_to_partial (context, openangle, openangle + 1);
1231 : 556 : context->start = context->iter;
1232 : 556 : context->balance = 1;
1233 : 556 : context->state = STATE_INSIDE_PASSTHROUGH;
1234 : : }
1235 : 2638335 : else if (*context->iter == '/')
1236 : : {
1237 : : /* move after it */
1238 : 1083919 : advance_char (context);
1239 : :
1240 : 1083919 : context->state = STATE_AFTER_CLOSE_TAG_SLASH;
1241 : : }
1242 : 1554416 : else if (!IS_COMMON_NAME_END_CHAR (*(context->iter)))
1243 : : {
1244 : 1554399 : context->state = STATE_INSIDE_OPEN_TAG_NAME;
1245 : :
1246 : : /* start of tag name */
1247 : 1554399 : context->start = context->iter;
1248 : : }
1249 : : else
1250 : : {
1251 : : gchar buf[8];
1252 : :
1253 : 17 : set_error (context,
1254 : : error,
1255 : : G_MARKUP_ERROR_PARSE,
1256 : : _("“%s” is not a valid character following "
1257 : : "a “<” character; it may not begin an "
1258 : : "element name"),
1259 : : utf8_str (context->iter,
1260 : 17 : context->current_text_end - context->iter, buf));
1261 : : }
1262 : 2638891 : break;
1263 : :
1264 : : /* The AFTER_CLOSE_ANGLE state is actually sort of
1265 : : * broken, because it doesn't correspond to a range
1266 : : * of characters in the input stream as the others do,
1267 : : * and thus makes things harder to conceptualize
1268 : : */
1269 : 2638352 : case STATE_AFTER_CLOSE_ANGLE:
1270 : : /* Possible next states: INSIDE_TEXT, STATE_START */
1271 : 2638352 : if (context->tag_stack == NULL)
1272 : : {
1273 : 838 : context->start = NULL;
1274 : 838 : context->state = STATE_START;
1275 : : }
1276 : : else
1277 : : {
1278 : 2637514 : context->start = context->iter;
1279 : 2637514 : context->state = STATE_INSIDE_TEXT;
1280 : : }
1281 : 2638352 : break;
1282 : :
1283 : 469733 : case STATE_AFTER_ELISION_SLASH:
1284 : : /* Possible next state: AFTER_CLOSE_ANGLE */
1285 : 469733 : if (*context->iter == '>')
1286 : : {
1287 : : /* move after the close angle */
1288 : 469709 : advance_char (context);
1289 : 469709 : context->state = STATE_AFTER_CLOSE_ANGLE;
1290 : 469709 : emit_end_element (context, error);
1291 : : }
1292 : : else
1293 : : {
1294 : : gchar buf[8];
1295 : :
1296 : 48 : set_error (context,
1297 : : error,
1298 : : G_MARKUP_ERROR_PARSE,
1299 : : _("Odd character “%s”, expected a “>” character "
1300 : : "to end the empty-element tag “%s”"),
1301 : : utf8_str (context->iter,
1302 : 24 : context->current_text_end - context->iter, buf),
1303 : : current_element (context));
1304 : : }
1305 : 469733 : break;
1306 : :
1307 : 1554989 : case STATE_INSIDE_OPEN_TAG_NAME:
1308 : : /* Possible next states: BETWEEN_ATTRIBUTES */
1309 : :
1310 : : /* if there's a partial chunk then it's the first part of the
1311 : : * tag name. If there's a context->start then it's the start
1312 : : * of the tag name in current_text, the partial chunk goes
1313 : : * before that start though.
1314 : : */
1315 : 1554989 : advance_to_name_end (context);
1316 : :
1317 : 1554989 : if (context->iter == context->current_text_end)
1318 : : {
1319 : : /* The name hasn't necessarily ended. Merge with
1320 : : * partial chunk, leave state unchanged.
1321 : : */
1322 : 606 : add_to_partial (context, context->start, context->iter);
1323 : : }
1324 : : else
1325 : : {
1326 : : /* The name has ended. Combine it with the partial chunk
1327 : : * if any; push it on the stack; enter next state.
1328 : : */
1329 : 1554383 : add_to_partial (context, context->start, context->iter);
1330 : 1554383 : push_partial_as_tag (context);
1331 : :
1332 : 1554383 : context->state = STATE_BETWEEN_ATTRIBUTES;
1333 : 1554383 : context->start = NULL;
1334 : : }
1335 : 1554989 : break;
1336 : :
1337 : 3516673 : case STATE_INSIDE_ATTRIBUTE_NAME:
1338 : : /* Possible next states: AFTER_ATTRIBUTE_NAME */
1339 : :
1340 : 3516673 : if (!context->partial_chunk || context->partial_chunk->len == 0)
1341 : 3509504 : context->attr_start = context->pos;
1342 : :
1343 : 3516673 : advance_to_name_end (context);
1344 : 3516673 : add_to_partial (context, context->start, context->iter);
1345 : :
1346 : : /* read the full name, if we enter the equals sign state
1347 : : * then add the attribute to the list (without the value),
1348 : : * otherwise store a partial chunk to be prepended later.
1349 : : */
1350 : 3516673 : if (context->iter != context->current_text_end)
1351 : 3509496 : context->state = STATE_AFTER_ATTRIBUTE_NAME;
1352 : 3516673 : break;
1353 : :
1354 : 3509505 : case STATE_AFTER_ATTRIBUTE_NAME:
1355 : : /* Possible next states: AFTER_ATTRIBUTE_EQUALS_SIGN */
1356 : :
1357 : 3509505 : skip_spaces (context);
1358 : :
1359 : 3509505 : if (context->iter != context->current_text_end)
1360 : : {
1361 : : /* The name has ended. Combine it with the partial chunk
1362 : : * if any; push it on the stack; enter next state.
1363 : : */
1364 : 3509496 : if (!name_validate (context, context->partial_chunk->str, error))
1365 : 8 : break;
1366 : :
1367 : 3509488 : if (!add_attribute (context, context->partial_chunk))
1368 : : {
1369 : 8 : set_error (context,
1370 : : error,
1371 : : G_MARKUP_ERROR_PARSE,
1372 : : _("Too many attributes in element “%s”"),
1373 : : current_element (context));
1374 : 8 : break;
1375 : : }
1376 : :
1377 : 3509480 : context->partial_chunk = NULL;
1378 : 3509480 : context->start = NULL;
1379 : :
1380 : 3509480 : if (*context->iter == '=')
1381 : : {
1382 : 3509456 : advance_char (context);
1383 : 3509456 : context->state = STATE_AFTER_ATTRIBUTE_EQUALS_SIGN;
1384 : : }
1385 : : else
1386 : : {
1387 : : gchar buf[8];
1388 : :
1389 : 48 : set_error (context,
1390 : : error,
1391 : : G_MARKUP_ERROR_PARSE,
1392 : : _("Odd character “%s”, expected a “=” after "
1393 : : "attribute name “%s” of element “%s”"),
1394 : : utf8_str (context->iter,
1395 : 24 : context->current_text_end - context->iter, buf),
1396 : : current_attribute (context),
1397 : : current_element (context));
1398 : :
1399 : : }
1400 : : }
1401 : 3509489 : break;
1402 : :
1403 : 5065155 : case STATE_BETWEEN_ATTRIBUTES:
1404 : : /* Possible next states: AFTER_CLOSE_ANGLE,
1405 : : * AFTER_ELISION_SLASH, INSIDE_ATTRIBUTE_NAME
1406 : : */
1407 : 5065155 : skip_spaces (context);
1408 : :
1409 : 5065155 : if (context->iter != context->current_text_end)
1410 : : {
1411 : 5063783 : if (*context->iter == '/')
1412 : : {
1413 : 469820 : advance_char (context);
1414 : 469820 : context->state = STATE_AFTER_ELISION_SLASH;
1415 : : }
1416 : 4593963 : else if (*context->iter == '>')
1417 : : {
1418 : 1084459 : advance_char (context);
1419 : 1084459 : context->state = STATE_AFTER_CLOSE_ANGLE;
1420 : : }
1421 : 3509504 : else if (!IS_COMMON_NAME_END_CHAR (*(context->iter)))
1422 : : {
1423 : 3509504 : context->state = STATE_INSIDE_ATTRIBUTE_NAME;
1424 : : /* start of attribute name */
1425 : 3509504 : context->start = context->iter;
1426 : : }
1427 : : else
1428 : : {
1429 : : gchar buf[8];
1430 : :
1431 : 0 : set_error (context,
1432 : : error,
1433 : : G_MARKUP_ERROR_PARSE,
1434 : : _("Odd character “%s”, expected a “>” or “/” "
1435 : : "character to end the start tag of "
1436 : : "element “%s”, or optionally an attribute; "
1437 : : "perhaps you used an invalid character in "
1438 : : "an attribute name"),
1439 : : utf8_str (context->iter,
1440 : 0 : context->current_text_end - context->iter, buf),
1441 : : current_element (context));
1442 : : }
1443 : :
1444 : : /* If we're done with attributes, invoke
1445 : : * the start_element callback
1446 : : */
1447 : 5063783 : if (context->state == STATE_AFTER_ELISION_SLASH ||
1448 : 4593963 : context->state == STATE_AFTER_CLOSE_ANGLE)
1449 : 1554279 : emit_start_element (context, error);
1450 : : }
1451 : 5065155 : break;
1452 : :
1453 : 3509453 : case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1454 : : /* Possible next state: INSIDE_ATTRIBUTE_VALUE_[SQ/DQ] */
1455 : :
1456 : 3509453 : skip_spaces (context);
1457 : :
1458 : 3509453 : if (context->iter != context->current_text_end)
1459 : : {
1460 : 3509448 : if (*context->iter == '"')
1461 : : {
1462 : 3496467 : advance_char (context);
1463 : 3496467 : context->state = STATE_INSIDE_ATTRIBUTE_VALUE_DQ;
1464 : 3496467 : context->start = context->iter;
1465 : : }
1466 : 12981 : else if (*context->iter == '\'')
1467 : : {
1468 : 12965 : advance_char (context);
1469 : 12965 : context->state = STATE_INSIDE_ATTRIBUTE_VALUE_SQ;
1470 : 12965 : context->start = context->iter;
1471 : : }
1472 : : else
1473 : : {
1474 : : gchar buf[8];
1475 : :
1476 : 32 : set_error (context,
1477 : : error,
1478 : : G_MARKUP_ERROR_PARSE,
1479 : : _("Odd character “%s”, expected an open quote mark "
1480 : : "after the equals sign when giving value for "
1481 : : "attribute “%s” of element “%s”"),
1482 : : utf8_str (context->iter,
1483 : 16 : context->current_text_end - context->iter, buf),
1484 : : current_attribute (context),
1485 : : current_element (context));
1486 : : }
1487 : : }
1488 : 3509453 : break;
1489 : :
1490 : 3509632 : case STATE_INSIDE_ATTRIBUTE_VALUE_SQ:
1491 : : case STATE_INSIDE_ATTRIBUTE_VALUE_DQ:
1492 : : /* Possible next states: BETWEEN_ATTRIBUTES */
1493 : : {
1494 : : gchar delim;
1495 : :
1496 : 3509632 : if (context->state == STATE_INSIDE_ATTRIBUTE_VALUE_SQ)
1497 : : {
1498 : 12970 : delim = '\'';
1499 : : }
1500 : : else
1501 : : {
1502 : 3496662 : delim = '"';
1503 : : }
1504 : :
1505 : : do
1506 : : {
1507 : 33808722 : if (*context->iter == delim)
1508 : 3509424 : break;
1509 : : }
1510 : 30299298 : while (advance_char (context));
1511 : : }
1512 : 3509632 : if (context->iter == context->current_text_end)
1513 : : {
1514 : : /* The value hasn't necessarily ended. Merge with
1515 : : * partial chunk, leave state unchanged.
1516 : : */
1517 : 208 : add_to_partial (context, context->start, context->iter);
1518 : : }
1519 : : else
1520 : : {
1521 : : gboolean is_ascii;
1522 : : /* The value has ended at the quote mark. Combine it
1523 : : * with the partial chunk if any; set it for the current
1524 : : * attribute.
1525 : : */
1526 : 3509424 : add_to_partial (context, context->start, context->iter);
1527 : :
1528 : 3509424 : g_assert (context->cur_attr >= 0);
1529 : :
1530 : 3509424 : if (unescape_gstring_inplace (context, context->partial_chunk, &is_ascii, error) &&
1531 : 3509416 : (is_ascii || text_validate (context, context->partial_chunk->str,
1532 : 8 : context->partial_chunk->len, error)))
1533 : : {
1534 : : /* success, advance past quote and set state. */
1535 : 3509408 : context->attr_values[context->cur_attr] = context->partial_chunk;
1536 : 3509408 : context->partial_chunk = NULL;
1537 : 3509408 : advance_char (context);
1538 : 3509408 : context->attr_end[context->cur_attr] = context->pos;
1539 : 3509408 : context->state = STATE_BETWEEN_ATTRIBUTES;
1540 : 3509408 : context->start = NULL;
1541 : : }
1542 : :
1543 : 3509424 : truncate_partial (context);
1544 : : }
1545 : 3509632 : break;
1546 : :
1547 : 74612705 : case STATE_INSIDE_TEXT:
1548 : : /* Possible next states: AFTER_OPEN_ANGLE */
1549 : : do
1550 : : {
1551 : 74612705 : if (*context->iter == '<')
1552 : 2637506 : break;
1553 : : }
1554 : 71975199 : while (advance_char (context));
1555 : :
1556 : : /* The text hasn't necessarily ended. Merge with
1557 : : * partial chunk, leave state unchanged.
1558 : : */
1559 : :
1560 : 2644933 : add_to_partial (context, context->start, context->iter);
1561 : :
1562 : 2644933 : if (context->iter != context->current_text_end)
1563 : : {
1564 : : gboolean is_ascii;
1565 : :
1566 : : /* The text has ended at the open angle. Call the text
1567 : : * callback.
1568 : : */
1569 : 2637506 : if (unescape_gstring_inplace (context, context->partial_chunk, &is_ascii, error) &&
1570 : 2644380 : (is_ascii || text_validate (context, context->partial_chunk->str,
1571 : 6966 : context->partial_chunk->len, error)))
1572 : : {
1573 : 2637406 : GError *tmp_error = NULL;
1574 : :
1575 : 2637406 : if (context->parser->text)
1576 : 1316295 : (*context->parser->text) (context,
1577 : 1316295 : context->partial_chunk->str,
1578 : 1316295 : context->partial_chunk->len,
1579 : : context->user_data,
1580 : : &tmp_error);
1581 : :
1582 : 2637406 : if (tmp_error == NULL)
1583 : : {
1584 : : /* advance past open angle and set state. */
1585 : 2637406 : advance_char (context);
1586 : 2637406 : context->state = STATE_AFTER_OPEN_ANGLE;
1587 : : /* could begin a passthrough */
1588 : 2637406 : context->start = context->iter;
1589 : : }
1590 : : else
1591 : 0 : propagate_error (context, error, tmp_error);
1592 : : }
1593 : :
1594 : 2637506 : truncate_partial (context);
1595 : : }
1596 : 2644933 : break;
1597 : :
1598 : 1083899 : case STATE_AFTER_CLOSE_TAG_SLASH:
1599 : : /* Possible next state: INSIDE_CLOSE_TAG_NAME */
1600 : 1083899 : if (!IS_COMMON_NAME_END_CHAR (*(context->iter)))
1601 : : {
1602 : 1083891 : context->state = STATE_INSIDE_CLOSE_TAG_NAME;
1603 : :
1604 : : /* start of tag name */
1605 : 1083891 : context->start = context->iter;
1606 : : }
1607 : : else
1608 : : {
1609 : : gchar buf[8];
1610 : :
1611 : 8 : set_error (context,
1612 : : error,
1613 : : G_MARKUP_ERROR_PARSE,
1614 : : _("“%s” is not a valid character following "
1615 : : "the characters “</”; “%s” may not begin an "
1616 : : "element name"),
1617 : : utf8_str (context->iter,
1618 : 8 : context->current_text_end - context->iter, buf),
1619 : : utf8_str (context->iter,
1620 : 8 : context->current_text_end - context->iter, buf));
1621 : : }
1622 : 1083899 : break;
1623 : :
1624 : 1084133 : case STATE_INSIDE_CLOSE_TAG_NAME:
1625 : : /* Possible next state: AFTER_CLOSE_TAG_NAME */
1626 : 1084133 : advance_to_name_end (context);
1627 : 1084133 : add_to_partial (context, context->start, context->iter);
1628 : :
1629 : 1084133 : if (context->iter != context->current_text_end)
1630 : 1083883 : context->state = STATE_AFTER_CLOSE_TAG_NAME;
1631 : 1084133 : break;
1632 : :
1633 : 1083887 : case STATE_AFTER_CLOSE_TAG_NAME:
1634 : : /* Possible next state: AFTER_CLOSE_TAG_SLASH */
1635 : :
1636 : 1083887 : skip_spaces (context);
1637 : :
1638 : 1083887 : if (context->iter != context->current_text_end)
1639 : : {
1640 : : GString *close_name;
1641 : :
1642 : 1083875 : close_name = context->partial_chunk;
1643 : 1083875 : context->partial_chunk = NULL;
1644 : :
1645 : 1083875 : if (*context->iter != '>')
1646 : : {
1647 : : gchar buf[8];
1648 : :
1649 : 8 : set_error (context,
1650 : : error,
1651 : : G_MARKUP_ERROR_PARSE,
1652 : : _("“%s” is not a valid character following "
1653 : : "the close element name “%s”; the allowed "
1654 : : "character is “>”"),
1655 : : utf8_str (context->iter,
1656 : 8 : context->current_text_end - context->iter, buf),
1657 : : close_name->str);
1658 : : }
1659 : 1083867 : else if (context->tag_stack == NULL)
1660 : : {
1661 : 16 : set_error (context,
1662 : : error,
1663 : : G_MARKUP_ERROR_PARSE,
1664 : : _("Element “%s” was closed, no element "
1665 : : "is currently open"),
1666 : : close_name->str);
1667 : : }
1668 : 1083851 : else if (strcmp (close_name->str, current_element (context)) != 0)
1669 : : {
1670 : 20 : set_error (context,
1671 : : error,
1672 : : G_MARKUP_ERROR_PARSE,
1673 : : _("Element “%s” was closed, but the currently "
1674 : : "open element is “%s”"),
1675 : : close_name->str,
1676 : : current_element (context));
1677 : : }
1678 : : else
1679 : : {
1680 : 1083831 : advance_char (context);
1681 : 1083831 : context->state = STATE_AFTER_CLOSE_ANGLE;
1682 : 1083831 : context->start = NULL;
1683 : :
1684 : 1083831 : emit_end_element (context, error);
1685 : : }
1686 : 1083875 : context->partial_chunk = close_name;
1687 : 1083875 : truncate_partial (context);
1688 : : }
1689 : 1083887 : break;
1690 : :
1691 : 37256 : case STATE_INSIDE_PASSTHROUGH:
1692 : : /* Possible next state: AFTER_CLOSE_ANGLE */
1693 : : do
1694 : : {
1695 : 37256 : if (*context->iter == '<')
1696 : 116 : context->balance++;
1697 : 37256 : if (*context->iter == '>')
1698 : : {
1699 : : gchar *str;
1700 : : gsize len;
1701 : :
1702 : 656 : context->balance--;
1703 : 656 : add_to_partial (context, context->start, context->iter);
1704 : 656 : context->start = context->iter;
1705 : :
1706 : 656 : str = context->partial_chunk->str;
1707 : 656 : len = context->partial_chunk->len;
1708 : :
1709 : 656 : if (str[1] == '?' && str[len - 1] == '?')
1710 : 186 : break;
1711 : 470 : if (strncmp (str, "<!--", 4) == 0 &&
1712 : 176 : strcmp (str + len - 2, "--") == 0)
1713 : 176 : break;
1714 : 294 : if (strncmp (str, "<![CDATA[", 9) == 0 &&
1715 : 99 : strcmp (str + len - 2, "]]") == 0)
1716 : 33 : break;
1717 : 261 : if (strncmp (str, "<!DOCTYPE", 9) == 0 &&
1718 : 195 : context->balance == 0)
1719 : 145 : break;
1720 : : }
1721 : : }
1722 : 36716 : while (advance_char (context));
1723 : :
1724 : 1087 : if (context->iter == context->current_text_end)
1725 : : {
1726 : : /* The passthrough hasn't necessarily ended. Merge with
1727 : : * partial chunk, leave state unchanged.
1728 : : */
1729 : 547 : add_to_partial (context, context->start, context->iter);
1730 : : }
1731 : : else
1732 : : {
1733 : : /* The passthrough has ended at the close angle. Combine
1734 : : * it with the partial chunk if any. Call the passthrough
1735 : : * callback. Note that the open/close angles are
1736 : : * included in the text of the passthrough.
1737 : : */
1738 : 540 : GError *tmp_error = NULL;
1739 : :
1740 : 540 : advance_char (context); /* advance past close angle */
1741 : 540 : add_to_partial (context, context->start, context->iter);
1742 : :
1743 : 540 : if (context->flags & G_MARKUP_TREAT_CDATA_AS_TEXT &&
1744 : 41 : strncmp (context->partial_chunk->str, "<![CDATA[", 9) == 0)
1745 : : {
1746 : 20 : if (context->parser->text &&
1747 : 3 : text_validate (context,
1748 : 3 : context->partial_chunk->str + 9,
1749 : 3 : context->partial_chunk->len - 12,
1750 : : error))
1751 : 3 : (*context->parser->text) (context,
1752 : 3 : context->partial_chunk->str + 9,
1753 : 3 : context->partial_chunk->len - 12,
1754 : : context->user_data,
1755 : : &tmp_error);
1756 : : }
1757 : 531 : else if (context->parser->passthrough &&
1758 : 8 : text_validate (context,
1759 : 8 : context->partial_chunk->str,
1760 : 8 : context->partial_chunk->len,
1761 : : error))
1762 : 8 : (*context->parser->passthrough) (context,
1763 : 8 : context->partial_chunk->str,
1764 : 8 : context->partial_chunk->len,
1765 : : context->user_data,
1766 : : &tmp_error);
1767 : :
1768 : 540 : truncate_partial (context);
1769 : :
1770 : 540 : if (tmp_error == NULL)
1771 : : {
1772 : 540 : context->state = STATE_AFTER_CLOSE_ANGLE;
1773 : 540 : context->start = context->iter; /* could begin text */
1774 : : }
1775 : : else
1776 : 0 : propagate_error (context, error, tmp_error);
1777 : : }
1778 : 1087 : break;
1779 : :
1780 : 471 : case STATE_ERROR:
1781 : 471 : goto finished;
1782 : : break;
1783 : :
1784 : 0 : default:
1785 : : g_assert_not_reached ();
1786 : : break;
1787 : : }
1788 : : }
1789 : :
1790 : 24855 : finished:
1791 : 25326 : context->parsing = FALSE;
1792 : :
1793 : 25326 : return context->state != STATE_ERROR;
1794 : : }
1795 : :
1796 : : /**
1797 : : * g_markup_parse_context_end_parse:
1798 : : * @context: a #GMarkupParseContext
1799 : : * @error: return location for a #GError
1800 : : *
1801 : : * Signals to the #GMarkupParseContext that all data has been
1802 : : * fed into the parse context with g_markup_parse_context_parse().
1803 : : *
1804 : : * This function reports an error if the document isn't complete,
1805 : : * for example if elements are still open.
1806 : : *
1807 : : * Returns: %TRUE on success, %FALSE if an error was set
1808 : : */
1809 : : gboolean
1810 : 564 : g_markup_parse_context_end_parse (GMarkupParseContext *context,
1811 : : GError **error)
1812 : : {
1813 : 564 : g_return_val_if_fail (context != NULL, FALSE);
1814 : 564 : g_return_val_if_fail (!context->parsing, FALSE);
1815 : 564 : g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
1816 : :
1817 : 563 : if (context->partial_chunk != NULL)
1818 : : {
1819 : 494 : g_string_free (context->partial_chunk, TRUE);
1820 : 494 : context->partial_chunk = NULL;
1821 : : }
1822 : :
1823 : 563 : if (context->document_empty)
1824 : : {
1825 : 12 : set_error_literal (context, error, G_MARKUP_ERROR_EMPTY,
1826 : : _("Document was empty or contained only whitespace"));
1827 : 12 : return FALSE;
1828 : : }
1829 : :
1830 : 551 : context->parsing = TRUE;
1831 : :
1832 : 551 : switch (context->state)
1833 : : {
1834 : 329 : case STATE_START:
1835 : : /* Nothing to do */
1836 : 329 : break;
1837 : :
1838 : 8 : case STATE_AFTER_OPEN_ANGLE:
1839 : 8 : set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
1840 : : _("Document ended unexpectedly just after an open angle bracket “<”"));
1841 : 8 : break;
1842 : :
1843 : 94 : case STATE_AFTER_CLOSE_ANGLE:
1844 : 94 : if (context->tag_stack != NULL)
1845 : : {
1846 : : /* Error message the same as for INSIDE_TEXT */
1847 : 19 : set_error (context, error, G_MARKUP_ERROR_PARSE,
1848 : : _("Document ended unexpectedly with elements still open — "
1849 : : "“%s” was the last element opened"),
1850 : : current_element (context));
1851 : : }
1852 : 94 : break;
1853 : :
1854 : 12 : case STATE_AFTER_ELISION_SLASH:
1855 : 12 : set_error (context, error, G_MARKUP_ERROR_PARSE,
1856 : : _("Document ended unexpectedly, expected to see a close angle "
1857 : : "bracket ending the tag <%s/>"), current_element (context));
1858 : 12 : break;
1859 : :
1860 : 16 : case STATE_INSIDE_OPEN_TAG_NAME:
1861 : 16 : set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
1862 : : _("Document ended unexpectedly inside an element name"));
1863 : 16 : break;
1864 : :
1865 : 8 : case STATE_INSIDE_ATTRIBUTE_NAME:
1866 : : case STATE_AFTER_ATTRIBUTE_NAME:
1867 : 8 : set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
1868 : : _("Document ended unexpectedly inside an attribute name"));
1869 : 8 : break;
1870 : :
1871 : 8 : case STATE_BETWEEN_ATTRIBUTES:
1872 : 8 : set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
1873 : : _("Document ended unexpectedly inside an element-opening "
1874 : : "tag."));
1875 : 8 : break;
1876 : :
1877 : 8 : case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1878 : 8 : set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
1879 : : _("Document ended unexpectedly after the equals sign "
1880 : : "following an attribute name; no attribute value"));
1881 : 8 : break;
1882 : :
1883 : 8 : case STATE_INSIDE_ATTRIBUTE_VALUE_SQ:
1884 : : case STATE_INSIDE_ATTRIBUTE_VALUE_DQ:
1885 : 8 : set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
1886 : : _("Document ended unexpectedly while inside an attribute "
1887 : : "value"));
1888 : 8 : break;
1889 : :
1890 : 8 : case STATE_INSIDE_TEXT:
1891 : 8 : g_assert (context->tag_stack != NULL);
1892 : 8 : set_error (context, error, G_MARKUP_ERROR_PARSE,
1893 : : _("Document ended unexpectedly with elements still open — "
1894 : : "“%s” was the last element opened"),
1895 : : current_element (context));
1896 : 8 : break;
1897 : :
1898 : 36 : case STATE_AFTER_CLOSE_TAG_SLASH:
1899 : : case STATE_INSIDE_CLOSE_TAG_NAME:
1900 : : case STATE_AFTER_CLOSE_TAG_NAME:
1901 : 36 : if (context->tag_stack != NULL)
1902 : 8 : set_error (context, error, G_MARKUP_ERROR_PARSE,
1903 : : _("Document ended unexpectedly inside the close tag for "
1904 : : "element “%s”"), current_element (context));
1905 : : else
1906 : 28 : set_error (context, error, G_MARKUP_ERROR_PARSE,
1907 : : _("Document ended unexpectedly inside the close tag for an "
1908 : : "unopened element"));
1909 : 36 : break;
1910 : :
1911 : 16 : case STATE_INSIDE_PASSTHROUGH:
1912 : 16 : set_error_literal (context, error, G_MARKUP_ERROR_PARSE,
1913 : : _("Document ended unexpectedly inside a comment or "
1914 : : "processing instruction"));
1915 : 16 : break;
1916 : :
1917 : 0 : case STATE_ERROR:
1918 : : default:
1919 : : g_assert_not_reached ();
1920 : : break;
1921 : : }
1922 : :
1923 : 551 : context->parsing = FALSE;
1924 : :
1925 : 551 : return context->state != STATE_ERROR;
1926 : : }
1927 : :
1928 : : /**
1929 : : * g_markup_parse_context_get_element:
1930 : : * @context: a #GMarkupParseContext
1931 : : *
1932 : : * Retrieves the name of the currently open element.
1933 : : *
1934 : : * If called from the start_element or end_element handlers this will
1935 : : * give the element_name as passed to those functions. For the parent
1936 : : * elements, see g_markup_parse_context_get_element_stack().
1937 : : *
1938 : : * Returns: the name of the currently open element, or %NULL
1939 : : *
1940 : : * Since: 2.2
1941 : : */
1942 : : const gchar *
1943 : 4 : g_markup_parse_context_get_element (GMarkupParseContext *context)
1944 : : {
1945 : 4 : g_return_val_if_fail (context != NULL, NULL);
1946 : :
1947 : 4 : if (context->tag_stack == NULL)
1948 : 0 : return NULL;
1949 : : else
1950 : 4 : return current_element (context);
1951 : : }
1952 : :
1953 : : /**
1954 : : * g_markup_parse_context_get_element_stack:
1955 : : * @context: a #GMarkupParseContext
1956 : : *
1957 : : * Retrieves the element stack from the internal state of the parser.
1958 : : *
1959 : : * The returned #GSList is a list of strings where the first item is
1960 : : * the currently open tag (as would be returned by
1961 : : * g_markup_parse_context_get_element()) and the next item is its
1962 : : * immediate parent.
1963 : : *
1964 : : * This function is intended to be used in the start_element and
1965 : : * end_element handlers where g_markup_parse_context_get_element()
1966 : : * would merely return the name of the element that is being
1967 : : * processed.
1968 : : *
1969 : : * Returns: (element-type utf8): the element stack, which must not be modified
1970 : : *
1971 : : * Since: 2.16
1972 : : */
1973 : : const GSList *
1974 : 5667 : g_markup_parse_context_get_element_stack (GMarkupParseContext *context)
1975 : : {
1976 : 5667 : g_return_val_if_fail (context != NULL, NULL);
1977 : 5667 : return context->tag_stack;
1978 : : }
1979 : :
1980 : : /**
1981 : : * g_markup_parse_context_get_position:
1982 : : * @context: a #GMarkupParseContext
1983 : : * @line_number: (out) (optional): return location for a line number, or %NULL
1984 : : * @char_number: (out) (optional): return location for a char-on-line number, or %NULL
1985 : : *
1986 : : * Retrieves the current line number and the number of the character on
1987 : : * that line. Intended for use in error messages; there are no strict
1988 : : * semantics for what constitutes the "current" line number other than
1989 : : * "the best number we could come up with for error messages."
1990 : : */
1991 : : void
1992 : 331 : g_markup_parse_context_get_position (GMarkupParseContext *context,
1993 : : gint *line_number,
1994 : : gint *char_number)
1995 : : {
1996 : 331 : g_return_if_fail (context != NULL);
1997 : :
1998 : 331 : if (line_number)
1999 : 331 : *line_number = context->pos.lines;
2000 : :
2001 : 331 : if (char_number)
2002 : 331 : *char_number = context->pos.chars;
2003 : : }
2004 : :
2005 : : /**
2006 : : * g_markup_parse_context_get_offset:
2007 : : * @context: a #GMarkupParseContext
2008 : : *
2009 : : * Retrieves the current offset from the beginning of the document,
2010 : : * in bytes.
2011 : : *
2012 : : * The information is meant to accompany the values returned by
2013 : : * [method@GLib.MarkupParseContext.get_position], and comes with the
2014 : : * same accuracy guarantees.
2015 : : *
2016 : : * Returns: the offset
2017 : : *
2018 : : * Since: 2.88
2019 : : */
2020 : : gsize
2021 : 218 : g_markup_parse_context_get_offset (GMarkupParseContext *context)
2022 : : {
2023 : 218 : g_return_val_if_fail (context != NULL, 0);
2024 : :
2025 : 218 : return context->pos.offset;
2026 : : }
2027 : :
2028 : : /**
2029 : : * g_markup_parse_context_get_tag_start:
2030 : : * @context: a #GMarkupParseContext
2031 : : * @line_number: (out): return location for the line number
2032 : : * @char_number: (out): return location for the character number
2033 : : * @offset: (out): return location for offset from the beginning of the document
2034 : : *
2035 : : * Retrieves the start position of the current start or end tag.
2036 : : *
2037 : : * This function can be used in the `start_element` or `end_element`
2038 : : * callbacks to obtain location information for error reporting.
2039 : : *
2040 : : * Calling it outside of these callbacks has undefined results.
2041 : : *
2042 : : * Note that @line_number and @char_number are intended for human
2043 : : * readable error messages and are therefore 1-based and in Unicode
2044 : : * characters. @offset on the other hand is meant for programmatic
2045 : : * use, and thus is 0-based and in bytes.
2046 : : *
2047 : : * The information is meant to accompany the values returned by
2048 : : * [method@GLib.MarkupParseContext.get_position], and comes with the
2049 : : * same accuracy guarantees.
2050 : : *
2051 : : * Since: 2.88
2052 : : */
2053 : : void
2054 : 142 : g_markup_parse_context_get_tag_start (GMarkupParseContext *context,
2055 : : gsize *line_number,
2056 : : gsize *char_number,
2057 : : gsize *offset)
2058 : : {
2059 : 142 : g_return_if_fail (context != NULL);
2060 : 142 : g_return_if_fail (line_number != NULL);
2061 : 142 : g_return_if_fail (char_number != NULL);
2062 : 142 : g_return_if_fail (offset != NULL);
2063 : :
2064 : 142 : *line_number = context->tag_start.lines;
2065 : 142 : *char_number = context->tag_start.chars;
2066 : 142 : *offset = context->tag_start.offset;
2067 : : }
2068 : :
2069 : : /**
2070 : : * g_markup_parse_context_get_attribute_position:
2071 : : * @context: a #GMarkupParseContext
2072 : : * @attr: the index of the attribute to query
2073 : : * @start_lines: (out) (optional): return location for the line number of the attribute assignment start
2074 : : * @start_chars: (out) (optional): return location for the character number of the attribute assignment start
2075 : : * @start_offset: (out) (optional): return location for offset of the attribute assignment
2076 : : * @end_lines: (out) (optional): return location for the line number of the attribute assignment end
2077 : : * @end_chars: (out) (optional): return location for the character number of the attribute assignment end
2078 : : * @end_offset: (out) (optional): return location for offset of the attribute assignment end
2079 : : *
2080 : : * Retrieves the start and end positions of an attribute assignment
2081 : : * in a start tag.
2082 : : *
2083 : : * This function can be used in the `start_element` callback to
2084 : : * obtain location information for error reporting.
2085 : : *
2086 : : * Calling it outside of the `start_element` callback
2087 : : * has undefined results.
2088 : : *
2089 : : * Note that @line_number and @char_number are intended for human
2090 : : * readable error messages and are therefore 1-based and in Unicode
2091 : : * characters. @offset on the other hand is meant for programmatic
2092 : : * use, and thus is 0-based and in bytes.
2093 : : *
2094 : : * The information is meant to accompany the values returned by
2095 : : * [method@GLib.MarkupParseContext.get_position], and comes with the
2096 : : * same accuracy guarantees.
2097 : : *
2098 : : * Since: 2.90
2099 : : */
2100 : : void
2101 : 41 : g_markup_parse_context_get_attribute_position (GMarkupParseContext *context,
2102 : : unsigned int attr,
2103 : : size_t *start_lines,
2104 : : size_t *start_chars,
2105 : : size_t *start_offset,
2106 : : size_t *end_lines,
2107 : : size_t *end_chars,
2108 : : size_t *end_offset)
2109 : : {
2110 : 41 : g_return_if_fail (context != NULL);
2111 : 41 : g_return_if_fail (context->cur_attr >= 0 && attr <= (unsigned int) context->cur_attr);
2112 : :
2113 : 41 : if (start_lines)
2114 : 41 : *start_lines = context->attr_pos[attr].lines;
2115 : 41 : if (start_chars)
2116 : 41 : *start_chars = context->attr_pos[attr].chars;
2117 : 41 : if (start_offset)
2118 : 41 : *start_offset = context->attr_pos[attr].offset;
2119 : 41 : if (end_lines)
2120 : 41 : *end_lines = context->attr_end[attr].lines;
2121 : 41 : if (end_chars)
2122 : 41 : *end_chars = context->attr_end[attr].chars;
2123 : 41 : if (end_offset)
2124 : 41 : *end_offset = context->attr_end[attr].offset;
2125 : : }
2126 : :
2127 : : /**
2128 : : * g_markup_parse_context_get_user_data:
2129 : : * @context: a #GMarkupParseContext
2130 : : *
2131 : : * Returns the user_data associated with @context.
2132 : : *
2133 : : * This will either be the user_data that was provided to
2134 : : * g_markup_parse_context_new() or to the most recent call
2135 : : * of g_markup_parse_context_push().
2136 : : *
2137 : : * Returns: the provided user_data. The returned data belongs to
2138 : : * the markup context and will be freed when
2139 : : * g_markup_parse_context_free() is called.
2140 : : *
2141 : : * Since: 2.18
2142 : : */
2143 : : gpointer
2144 : 76 : g_markup_parse_context_get_user_data (GMarkupParseContext *context)
2145 : : {
2146 : 76 : return context->user_data;
2147 : : }
2148 : :
2149 : : /**
2150 : : * g_markup_parse_context_push:
2151 : : * @context: a #GMarkupParseContext
2152 : : * @parser: a #GMarkupParser
2153 : : * @user_data: user data to pass to #GMarkupParser functions
2154 : : *
2155 : : * Temporarily redirects markup data to a sub-parser.
2156 : : *
2157 : : * This function may only be called from the start_element handler of
2158 : : * a #GMarkupParser. It must be matched with a corresponding call to
2159 : : * g_markup_parse_context_pop() in the matching end_element handler
2160 : : * (except in the case that the parser aborts due to an error).
2161 : : *
2162 : : * All tags, text and other data between the matching tags is
2163 : : * redirected to the subparser given by @parser. @user_data is used
2164 : : * as the user_data for that parser. @user_data is also passed to the
2165 : : * error callback in the event that an error occurs. This includes
2166 : : * errors that occur in subparsers of the subparser.
2167 : : *
2168 : : * The end tag matching the start tag for which this call was made is
2169 : : * handled by the previous parser (which is given its own user_data)
2170 : : * which is why g_markup_parse_context_pop() is provided to allow "one
2171 : : * last access" to the @user_data provided to this function. In the
2172 : : * case of error, the @user_data provided here is passed directly to
2173 : : * the error callback of the subparser and g_markup_parse_context_pop()
2174 : : * should not be called. In either case, if @user_data was allocated
2175 : : * then it ought to be freed from both of these locations.
2176 : : *
2177 : : * This function is not intended to be directly called by users
2178 : : * interested in invoking subparsers. Instead, it is intended to be
2179 : : * used by the subparsers themselves to implement a higher-level
2180 : : * interface.
2181 : : *
2182 : : * As an example, see the following implementation of a simple
2183 : : * parser that counts the number of tags encountered.
2184 : : *
2185 : : * |[<!-- language="C" -->
2186 : : * typedef struct
2187 : : * {
2188 : : * gint tag_count;
2189 : : * } CounterData;
2190 : : *
2191 : : * static void
2192 : : * counter_start_element (GMarkupParseContext *context,
2193 : : * const gchar *element_name,
2194 : : * const gchar **attribute_names,
2195 : : * const gchar **attribute_values,
2196 : : * gpointer user_data,
2197 : : * GError **error)
2198 : : * {
2199 : : * CounterData *data = user_data;
2200 : : *
2201 : : * data->tag_count++;
2202 : : * }
2203 : : *
2204 : : * static void
2205 : : * counter_error (GMarkupParseContext *context,
2206 : : * GError *error,
2207 : : * gpointer user_data)
2208 : : * {
2209 : : * CounterData *data = user_data;
2210 : : *
2211 : : * g_slice_free (CounterData, data);
2212 : : * }
2213 : : *
2214 : : * static GMarkupParser counter_subparser =
2215 : : * {
2216 : : * counter_start_element,
2217 : : * NULL,
2218 : : * NULL,
2219 : : * NULL,
2220 : : * counter_error
2221 : : * };
2222 : : * ]|
2223 : : *
2224 : : * In order to allow this parser to be easily used as a subparser, the
2225 : : * following interface is provided:
2226 : : *
2227 : : * |[<!-- language="C" -->
2228 : : * void
2229 : : * start_counting (GMarkupParseContext *context)
2230 : : * {
2231 : : * CounterData *data = g_slice_new (CounterData);
2232 : : *
2233 : : * data->tag_count = 0;
2234 : : * g_markup_parse_context_push (context, &counter_subparser, data);
2235 : : * }
2236 : : *
2237 : : * gint
2238 : : * end_counting (GMarkupParseContext *context)
2239 : : * {
2240 : : * CounterData *data = g_markup_parse_context_pop (context);
2241 : : * int result;
2242 : : *
2243 : : * result = data->tag_count;
2244 : : * g_slice_free (CounterData, data);
2245 : : *
2246 : : * return result;
2247 : : * }
2248 : : * ]|
2249 : : *
2250 : : * The subparser would then be used as follows:
2251 : : *
2252 : : * |[<!-- language="C" -->
2253 : : * static void start_element (context, element_name, ...)
2254 : : * {
2255 : : * if (strcmp (element_name, "count-these") == 0)
2256 : : * start_counting (context);
2257 : : *
2258 : : * // else, handle other tags...
2259 : : * }
2260 : : *
2261 : : * static void end_element (context, element_name, ...)
2262 : : * {
2263 : : * if (strcmp (element_name, "count-these") == 0)
2264 : : * g_print ("Counted %d tags\n", end_counting (context));
2265 : : *
2266 : : * // else, handle other tags...
2267 : : * }
2268 : : * ]|
2269 : : *
2270 : : * Since: 2.18
2271 : : **/
2272 : : void
2273 : 24 : g_markup_parse_context_push (GMarkupParseContext *context,
2274 : : const GMarkupParser *parser,
2275 : : gpointer user_data)
2276 : : {
2277 : : GMarkupRecursionTracker *tracker;
2278 : :
2279 : 24 : tracker = g_slice_new (GMarkupRecursionTracker);
2280 : 24 : tracker->prev_element = context->subparser_element;
2281 : 24 : tracker->prev_parser = context->parser;
2282 : 24 : tracker->prev_user_data = context->user_data;
2283 : :
2284 : 24 : context->subparser_element = current_element (context);
2285 : 24 : context->parser = parser;
2286 : 24 : context->user_data = user_data;
2287 : :
2288 : 24 : context->subparser_stack = g_slist_prepend (context->subparser_stack,
2289 : : tracker);
2290 : 24 : }
2291 : :
2292 : : /**
2293 : : * g_markup_parse_context_pop:
2294 : : * @context: a #GMarkupParseContext
2295 : : *
2296 : : * Completes the process of a temporary sub-parser redirection.
2297 : : *
2298 : : * This function exists to collect the user_data allocated by a
2299 : : * matching call to g_markup_parse_context_push(). It must be called
2300 : : * in the end_element handler corresponding to the start_element
2301 : : * handler during which g_markup_parse_context_push() was called.
2302 : : * You must not call this function from the error callback -- the
2303 : : * @user_data is provided directly to the callback in that case.
2304 : : *
2305 : : * This function is not intended to be directly called by users
2306 : : * interested in invoking subparsers. Instead, it is intended to
2307 : : * be used by the subparsers themselves to implement a higher-level
2308 : : * interface.
2309 : : *
2310 : : * Returns: the user data passed to g_markup_parse_context_push()
2311 : : *
2312 : : * Since: 2.18
2313 : : */
2314 : : gpointer
2315 : 21 : g_markup_parse_context_pop (GMarkupParseContext *context)
2316 : : {
2317 : : gpointer user_data;
2318 : :
2319 : 21 : if (!context->awaiting_pop)
2320 : 0 : possibly_finish_subparser (context);
2321 : :
2322 : 21 : g_assert (context->awaiting_pop);
2323 : :
2324 : 21 : context->awaiting_pop = FALSE;
2325 : :
2326 : : /* valgrind friendliness */
2327 : 21 : user_data = context->held_user_data;
2328 : 21 : context->held_user_data = NULL;
2329 : :
2330 : 21 : return user_data;
2331 : : }
2332 : :
2333 : : #define APPEND_TEXT_AND_SEEK(_str, _start, _end) \
2334 : : G_STMT_START { \
2335 : : if (_end > _start) \
2336 : : g_string_append_len (_str, _start, _end - _start); \
2337 : : _start = ++_end; \
2338 : : } G_STMT_END
2339 : :
2340 : : /*
2341 : : * https://www.w3.org/TR/REC-xml/ defines the set of valid
2342 : : * characters as:
2343 : : * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
2344 : : *
2345 : : * That is, from non-ASCII UTF-8 character set, only 0xC27F - 0xC284 and
2346 : : * 0xC286 - 0xC29F have to be escaped (excluding the surrogate blocks).
2347 : : * Corresponding Unicode code points are [0x7F-0x84] and [0x86-0x9F].
2348 : : *
2349 : : * So instead of using costly g_utf8_next_char or similar UTF8 functions, it's
2350 : : * better to read each byte, and make an exception for 0xC2XX.
2351 : : */
2352 : : static void
2353 : 5377 : append_escaped_text (GString *str,
2354 : : const gchar *text,
2355 : : gssize length)
2356 : : {
2357 : : const gchar *p, *pending;
2358 : : const gchar *end;
2359 : :
2360 : 5377 : p = pending = text;
2361 : 5377 : end = text + length;
2362 : :
2363 : 76911 : while (p < end && pending < end)
2364 : : {
2365 : 71534 : guchar c = (guchar) *pending;
2366 : :
2367 : 71534 : switch (c)
2368 : : {
2369 : 42 : case '&':
2370 : 42 : APPEND_TEXT_AND_SEEK (str, p, pending);
2371 : 42 : g_string_append (str, "&");
2372 : 42 : break;
2373 : :
2374 : 8 : case '<':
2375 : 8 : APPEND_TEXT_AND_SEEK (str, p, pending);
2376 : 8 : g_string_append (str, "<");
2377 : 8 : break;
2378 : :
2379 : 8 : case '>':
2380 : 8 : APPEND_TEXT_AND_SEEK (str, p, pending);
2381 : 8 : g_string_append (str, ">");
2382 : 8 : break;
2383 : :
2384 : 31 : case '\'':
2385 : 31 : APPEND_TEXT_AND_SEEK (str, p, pending);
2386 : 31 : g_string_append (str, "'");
2387 : 31 : break;
2388 : :
2389 : 14 : case '"':
2390 : 14 : APPEND_TEXT_AND_SEEK (str, p, pending);
2391 : 14 : g_string_append (str, """);
2392 : 14 : break;
2393 : :
2394 : 71431 : default:
2395 : 71431 : if ((0x1 <= c && c <= 0x8) ||
2396 : 71425 : (0xb <= c && c <= 0xc) ||
2397 : 71421 : (0xe <= c && c <= 0x1f) ||
2398 : : (c == 0x7f))
2399 : : {
2400 : 16 : APPEND_TEXT_AND_SEEK (str, p, pending);
2401 : 16 : g_string_append_printf (str, "&#x%x;", c);
2402 : : }
2403 : : /* The utf-8 control characters to escape begins with 0xc2 byte */
2404 : 71415 : else if (c == 0xc2)
2405 : : {
2406 : 18 : gunichar u = g_utf8_get_char_validated (pending, end - pending);
2407 : :
2408 : 18 : if ((0x7f < u && u <= 0x84) ||
2409 : 12 : (0x86 <= u && u <= 0x9f))
2410 : : {
2411 : 10 : APPEND_TEXT_AND_SEEK (str, p, pending);
2412 : 10 : g_string_append_printf (str, "&#x%x;", u);
2413 : :
2414 : : /*
2415 : : * We have appended a two byte character above, which
2416 : : * is one byte ahead of what we read on every loop.
2417 : : * Increment to skip 0xc2 and point to the right location.
2418 : : */
2419 : 10 : p++;
2420 : : }
2421 : : else
2422 : : {
2423 : : /* Not the UTF-8 control characters we’re looking for, or an
2424 : : * invalid or partial encoding. Pass it through. */
2425 : 8 : pending++;
2426 : : }
2427 : : }
2428 : : else
2429 : 71397 : pending++;
2430 : 71431 : break;
2431 : : }
2432 : : }
2433 : :
2434 : 5377 : if (pending > p)
2435 : 5269 : g_string_append_len (str, p, pending - p);
2436 : 5377 : }
2437 : :
2438 : : #undef APPEND_TEXT_AND_SEEK
2439 : :
2440 : : /**
2441 : : * g_markup_escape_text:
2442 : : * @text: some valid UTF-8 text
2443 : : * @length: length of @text in bytes, or -1 if the text is nul-terminated
2444 : : *
2445 : : * Escapes text so that the markup parser will parse it verbatim.
2446 : : * Less than, greater than, ampersand, etc. are replaced with the
2447 : : * corresponding entities. This function would typically be used
2448 : : * when writing out a file to be parsed with the markup parser.
2449 : : *
2450 : : * Note that this function doesn't protect whitespace and line endings
2451 : : * from being processed according to the XML rules for normalization
2452 : : * of line endings and attribute values.
2453 : : *
2454 : : * Note also that this function will produce character references in
2455 : : * the range of  ...  for all control sequences
2456 : : * except for tabstop, newline and carriage return. The character
2457 : : * references in this range are not valid XML 1.0, but they are
2458 : : * valid XML 1.1 and will be accepted by the GMarkup parser.
2459 : : *
2460 : : * Returns: a newly allocated string with the escaped text
2461 : : */
2462 : : gchar*
2463 : 5377 : g_markup_escape_text (const gchar *text,
2464 : : gssize length)
2465 : : {
2466 : : GString *str;
2467 : :
2468 : 5377 : g_return_val_if_fail (text != NULL, NULL);
2469 : :
2470 : 5377 : if (length < 0)
2471 : 394 : length = strlen (text);
2472 : :
2473 : : /* prealloc at least as long as original text */
2474 : 5377 : str = g_string_sized_new (length);
2475 : 5377 : append_escaped_text (str, text, length);
2476 : :
2477 : 5377 : return g_string_free (str, FALSE);
2478 : : }
2479 : :
2480 : : /*
2481 : : * find_conversion:
2482 : : * @format: a printf-style format string
2483 : : * @after: location to store a pointer to the character after
2484 : : * the returned conversion. On a %NULL return, returns the
2485 : : * pointer to the trailing NUL in the string
2486 : : *
2487 : : * Find the next conversion in a printf-style format string.
2488 : : * Partially based on code from printf-parser.c,
2489 : : * Copyright (C) 1999-2000, 2002-2003 Free Software Foundation, Inc.
2490 : : *
2491 : : * Returns: pointer to the next conversion in @format,
2492 : : * or %NULL, if none.
2493 : : */
2494 : : static const char *
2495 : 14114 : find_conversion (const char *format,
2496 : : const char **after)
2497 : : {
2498 : 14114 : const char *start = format;
2499 : : const char *cp;
2500 : :
2501 : 153482 : while (*start != '\0' && *start != '%')
2502 : 139368 : start++;
2503 : :
2504 : 14114 : if (*start == '\0')
2505 : : {
2506 : 4232 : *after = start;
2507 : 4232 : return NULL;
2508 : : }
2509 : :
2510 : 9882 : cp = start + 1;
2511 : :
2512 : 9882 : if (*cp == '\0')
2513 : : {
2514 : 0 : *after = cp;
2515 : 0 : return NULL;
2516 : : }
2517 : :
2518 : : /* Test for positional argument. */
2519 : 9882 : if (*cp >= '0' && *cp <= '9')
2520 : : {
2521 : : const char *np;
2522 : :
2523 : 8 : for (np = cp; *np >= '0' && *np <= '9'; np++)
2524 : : ;
2525 : 4 : if (*np == '$')
2526 : 4 : cp = np + 1;
2527 : : }
2528 : :
2529 : : /* Skip the flags. */
2530 : : for (;;)
2531 : : {
2532 : 9884 : if (*cp == '\'' ||
2533 : 9884 : *cp == '-' ||
2534 : 9882 : *cp == '+' ||
2535 : 9882 : *cp == ' ' ||
2536 : 9882 : *cp == '#' ||
2537 : 9882 : *cp == '0')
2538 : 2 : cp++;
2539 : : else
2540 : : break;
2541 : : }
2542 : :
2543 : : /* Skip the field width. */
2544 : 9882 : if (*cp == '*')
2545 : : {
2546 : 252 : cp++;
2547 : :
2548 : : /* Test for positional argument. */
2549 : 252 : if (*cp >= '0' && *cp <= '9')
2550 : : {
2551 : : const char *np;
2552 : :
2553 : 0 : for (np = cp; *np >= '0' && *np <= '9'; np++)
2554 : : ;
2555 : 0 : if (*np == '$')
2556 : 0 : cp = np + 1;
2557 : : }
2558 : : }
2559 : : else
2560 : : {
2561 : 9632 : for (; *cp >= '0' && *cp <= '9'; cp++)
2562 : : ;
2563 : : }
2564 : :
2565 : : /* Skip the precision. */
2566 : 9882 : if (*cp == '.')
2567 : : {
2568 : 4 : cp++;
2569 : 4 : if (*cp == '*')
2570 : : {
2571 : : /* Test for positional argument. */
2572 : 0 : if (*cp >= '0' && *cp <= '9')
2573 : : {
2574 : : const char *np;
2575 : :
2576 : 0 : for (np = cp; *np >= '0' && *np <= '9'; np++)
2577 : : ;
2578 : 0 : if (*np == '$')
2579 : 0 : cp = np + 1;
2580 : : }
2581 : : }
2582 : : else
2583 : : {
2584 : 8 : for (; *cp >= '0' && *cp <= '9'; cp++)
2585 : : ;
2586 : : }
2587 : : }
2588 : :
2589 : : /* Skip argument type/size specifiers. */
2590 : 9882 : while (*cp == 'h' ||
2591 : 9884 : *cp == 'L' ||
2592 : 9882 : *cp == 'l' ||
2593 : 9882 : *cp == 'j' ||
2594 : 9882 : *cp == 'z' ||
2595 : 19766 : *cp == 'Z' ||
2596 : 9882 : *cp == 't')
2597 : 2 : cp++;
2598 : :
2599 : : /* Skip the conversion character. */
2600 : 9882 : cp++;
2601 : :
2602 : 9882 : *after = cp;
2603 : 9882 : return start;
2604 : : }
2605 : :
2606 : : /**
2607 : : * g_markup_vprintf_escaped:
2608 : : * @format: printf() style format string
2609 : : * @args: variable argument list, similar to vprintf()
2610 : : *
2611 : : * Formats the data in @args according to @format, escaping
2612 : : * all string and character arguments in the fashion
2613 : : * of g_markup_escape_text(). See g_markup_printf_escaped().
2614 : : *
2615 : : * Returns: newly allocated result from formatting
2616 : : * operation. Free with g_free().
2617 : : *
2618 : : * Since: 2.4
2619 : : */
2620 : : #pragma GCC diagnostic push
2621 : : #pragma GCC diagnostic ignored "-Wformat-nonliteral"
2622 : :
2623 : : gchar *
2624 : 2116 : g_markup_vprintf_escaped (const gchar *format,
2625 : : va_list args)
2626 : : {
2627 : : GString *format1;
2628 : : GString *format2;
2629 : 2116 : GString *result = NULL;
2630 : 2116 : gchar *output1 = NULL;
2631 : 2116 : gchar *output2 = NULL;
2632 : : const char *p, *op1, *op2;
2633 : : va_list args2;
2634 : :
2635 : : /* The technique here, is that we make two format strings that
2636 : : * have the identical conversions in the identical order to the
2637 : : * original strings, but differ in the text in-between. We
2638 : : * then use the normal g_strdup_vprintf() to format the arguments
2639 : : * with the two new format strings. By comparing the results,
2640 : : * we can figure out what segments of the output come from
2641 : : * the original format string, and what from the arguments,
2642 : : * and thus know what portions of the string to escape.
2643 : : *
2644 : : * For instance, for:
2645 : : *
2646 : : * g_markup_printf_escaped ("%s ate %d apples", "Susan & Fred", 5);
2647 : : *
2648 : : * We form the two format strings "%sX%dX" and %sY%sY". The results
2649 : : * of formatting with those two strings are
2650 : : *
2651 : : * "%sX%dX" => "Susan & FredX5X"
2652 : : * "%sY%dY" => "Susan & FredY5Y"
2653 : : *
2654 : : * To find the span of the first argument, we find the first position
2655 : : * where the two arguments differ, which tells us that the first
2656 : : * argument formatted to "Susan & Fred". We then escape that
2657 : : * to "Susan & Fred" and join up with the intermediate portions
2658 : : * of the format string and the second argument to get
2659 : : * "Susan & Fred ate 5 apples".
2660 : : */
2661 : :
2662 : : /* Create the two modified format strings
2663 : : */
2664 : 2116 : format1 = g_string_new (NULL);
2665 : 2116 : format2 = g_string_new (NULL);
2666 : 2116 : p = format;
2667 : : while (TRUE)
2668 : 4941 : {
2669 : : const char *after;
2670 : 7057 : const char *conv = find_conversion (p, &after);
2671 : 7057 : if (!conv)
2672 : 2116 : break;
2673 : :
2674 : 4941 : g_string_append_len (format1, conv, after - conv);
2675 : : g_string_append_c (format1, 'X');
2676 : 4941 : g_string_append_len (format2, conv, after - conv);
2677 : : g_string_append_c (format2, 'Y');
2678 : :
2679 : 4941 : p = after;
2680 : : }
2681 : :
2682 : : /* Use them to format the arguments
2683 : : */
2684 : 2116 : va_copy (args2, args);
2685 : :
2686 : 2116 : output1 = g_strdup_vprintf (format1->str, args);
2687 : :
2688 : 2116 : if (!output1)
2689 : : {
2690 : 0 : va_end (args2);
2691 : 0 : goto cleanup;
2692 : : }
2693 : :
2694 : 2116 : output2 = g_strdup_vprintf (format2->str, args2);
2695 : 2116 : va_end (args2);
2696 : 2116 : if (!output2)
2697 : 0 : goto cleanup;
2698 : 2116 : result = g_string_new (NULL);
2699 : :
2700 : : /* Iterate through the original format string again,
2701 : : * copying the non-conversion portions and the escaped
2702 : : * converted arguments to the output string.
2703 : : */
2704 : 2116 : op1 = output1;
2705 : 2116 : op2 = output2;
2706 : 2116 : p = format;
2707 : : while (TRUE)
2708 : 4941 : {
2709 : : const char *after;
2710 : : const char *output_start;
2711 : 7057 : const char *conv = find_conversion (p, &after);
2712 : : char *escaped;
2713 : :
2714 : 7057 : if (!conv) /* The end, after points to the trailing \0 */
2715 : : {
2716 : 2116 : g_string_append_len (result, p, after - p);
2717 : 2116 : break;
2718 : : }
2719 : :
2720 : 4941 : g_string_append_len (result, p, conv - p);
2721 : 4941 : output_start = op1;
2722 : 67083 : while (*op1 == *op2)
2723 : : {
2724 : 62142 : op1++;
2725 : 62142 : op2++;
2726 : : }
2727 : :
2728 : 4941 : escaped = g_markup_escape_text (output_start, op1 - output_start);
2729 : : g_string_append (result, escaped);
2730 : 4941 : g_free (escaped);
2731 : :
2732 : 4941 : p = after;
2733 : 4941 : op1++;
2734 : 4941 : op2++;
2735 : : }
2736 : :
2737 : 2116 : cleanup:
2738 : 2116 : g_string_free (format1, TRUE);
2739 : 2116 : g_string_free (format2, TRUE);
2740 : 2116 : g_free (output1);
2741 : 2116 : g_free (output2);
2742 : :
2743 : 2116 : if (result)
2744 : 2116 : return g_string_free (result, FALSE);
2745 : : else
2746 : 0 : return NULL;
2747 : : }
2748 : :
2749 : : #pragma GCC diagnostic pop
2750 : :
2751 : : /**
2752 : : * g_markup_printf_escaped:
2753 : : * @format: printf() style format string
2754 : : * @...: the arguments to insert in the format string
2755 : : *
2756 : : * Formats arguments according to @format, escaping
2757 : : * all string and character arguments in the fashion
2758 : : * of g_markup_escape_text(). This is useful when you
2759 : : * want to insert literal strings into XML-style markup
2760 : : * output, without having to worry that the strings
2761 : : * might themselves contain markup.
2762 : : *
2763 : : * |[<!-- language="C" -->
2764 : : * const char *store = "Fortnum & Mason";
2765 : : * const char *item = "Tea";
2766 : : * char *output;
2767 : : *
2768 : : * output = g_markup_printf_escaped ("<purchase>"
2769 : : * "<store>%s</store>"
2770 : : * "<item>%s</item>"
2771 : : * "</purchase>",
2772 : : * store, item);
2773 : : * ]|
2774 : : *
2775 : : * Returns: newly allocated result from formatting
2776 : : * operation. Free with g_free().
2777 : : *
2778 : : * Since: 2.4
2779 : : */
2780 : : gchar *
2781 : 134 : g_markup_printf_escaped (const gchar *format, ...)
2782 : : {
2783 : : char *result;
2784 : : va_list args;
2785 : :
2786 : 134 : va_start (args, format);
2787 : 134 : result = g_markup_vprintf_escaped (format, args);
2788 : 134 : va_end (args);
2789 : :
2790 : 134 : return result;
2791 : : }
2792 : :
2793 : : static gboolean
2794 : 19 : g_markup_parse_boolean (const char *string,
2795 : : gboolean *value)
2796 : : {
2797 : 19 : char const * const falses[] = { "false", "f", "no", "n", "0" };
2798 : 19 : char const * const trues[] = { "true", "t", "yes", "y", "1" };
2799 : : gsize i;
2800 : :
2801 : 102 : for (i = 0; i < G_N_ELEMENTS (falses); i++)
2802 : : {
2803 : 87 : if (g_ascii_strcasecmp (string, falses[i]) == 0)
2804 : : {
2805 : 4 : if (value != NULL)
2806 : 4 : *value = FALSE;
2807 : :
2808 : 4 : return TRUE;
2809 : : }
2810 : : }
2811 : :
2812 : 51 : for (i = 0; i < G_N_ELEMENTS (trues); i++)
2813 : : {
2814 : 49 : if (g_ascii_strcasecmp (string, trues[i]) == 0)
2815 : : {
2816 : 13 : if (value != NULL)
2817 : 13 : *value = TRUE;
2818 : :
2819 : 13 : return TRUE;
2820 : : }
2821 : : }
2822 : :
2823 : 2 : return FALSE;
2824 : : }
2825 : :
2826 : : /**
2827 : : * GMarkupCollectType:
2828 : : * @G_MARKUP_COLLECT_INVALID: used to terminate the list of attributes
2829 : : * to collect
2830 : : * @G_MARKUP_COLLECT_STRING: collect the string pointer directly from
2831 : : * the attribute_values[] array. Expects a parameter of type (const
2832 : : * char **). If %G_MARKUP_COLLECT_OPTIONAL is specified and the
2833 : : * attribute isn't present then the pointer will be set to %NULL
2834 : : * @G_MARKUP_COLLECT_STRDUP: as with %G_MARKUP_COLLECT_STRING, but
2835 : : * expects a parameter of type (char **) and g_strdup()s the
2836 : : * returned pointer. The pointer must be freed with g_free()
2837 : : * @G_MARKUP_COLLECT_BOOLEAN: expects a parameter of type (`gboolean *`)
2838 : : * and parses the attribute value as a boolean. Sets %FALSE if the
2839 : : * attribute isn't present. Valid boolean values consist of
2840 : : * (case-insensitive) "false", "f", "no", "n", "0" and "true", "t",
2841 : : * "yes", "y", "1"
2842 : : * @G_MARKUP_COLLECT_TRISTATE: as with %G_MARKUP_COLLECT_BOOLEAN, but
2843 : : * in the case of a missing attribute a value is set that compares
2844 : : * equal to neither %FALSE nor %TRUE %G_MARKUP_COLLECT_OPTIONAL is
2845 : : * implied
2846 : : * @G_MARKUP_COLLECT_OPTIONAL: can be bitwise ORed with the other fields.
2847 : : * If present, allows the attribute not to appear. A default value
2848 : : * is set depending on what value type is used
2849 : : *
2850 : : * A mixed enumerated type and flags field. You must specify one type
2851 : : * (string, strdup, boolean, tristate). Additionally, you may optionally
2852 : : * bitwise OR the type with the flag %G_MARKUP_COLLECT_OPTIONAL.
2853 : : *
2854 : : * It is likely that this enum will be extended in the future to
2855 : : * support other types.
2856 : : */
2857 : :
2858 : : /**
2859 : : * g_markup_collect_attributes:
2860 : : * @element_name: the current tag name
2861 : : * @attribute_names: the attribute names
2862 : : * @attribute_values: the attribute values
2863 : : * @error: a pointer to a #GError or %NULL
2864 : : * @first_type: the #GMarkupCollectType of the first attribute
2865 : : * @first_attr: the name of the first attribute
2866 : : * @...: a pointer to the storage location of the first attribute
2867 : : * (or %NULL), followed by more types names and pointers, ending
2868 : : * with %G_MARKUP_COLLECT_INVALID
2869 : : *
2870 : : * Collects the attributes of the element from the data passed to the
2871 : : * #GMarkupParser start_element function, dealing with common error
2872 : : * conditions and supporting boolean values.
2873 : : *
2874 : : * This utility function is not required to write a parser but can save
2875 : : * a lot of typing.
2876 : : *
2877 : : * The @element_name, @attribute_names, @attribute_values and @error
2878 : : * parameters passed to the start_element callback should be passed
2879 : : * unmodified to this function.
2880 : : *
2881 : : * Following these arguments is a list of "supported" attributes to collect.
2882 : : * It is an error to specify multiple attributes with the same name. If any
2883 : : * attribute not in the list appears in the @attribute_names array then an
2884 : : * unknown attribute error will result.
2885 : : *
2886 : : * The #GMarkupCollectType field allows specifying the type of collection
2887 : : * to perform and if a given attribute must appear or is optional.
2888 : : *
2889 : : * The attribute name is simply the name of the attribute to collect.
2890 : : *
2891 : : * The pointer should be of the appropriate type (see the descriptions
2892 : : * under #GMarkupCollectType) and may be %NULL in case a particular
2893 : : * attribute is to be allowed but ignored.
2894 : : *
2895 : : * This function deals with issuing errors for missing attributes
2896 : : * (of type %G_MARKUP_ERROR_MISSING_ATTRIBUTE), unknown attributes
2897 : : * (of type %G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE) and duplicate
2898 : : * attributes (of type %G_MARKUP_ERROR_INVALID_CONTENT) as well
2899 : : * as parse errors for boolean-valued attributes (again of type
2900 : : * %G_MARKUP_ERROR_INVALID_CONTENT). In all of these cases %FALSE
2901 : : * will be returned and @error will be set as appropriate.
2902 : : *
2903 : : * Returns: %TRUE if successful
2904 : : *
2905 : : * Since: 2.16
2906 : : **/
2907 : : gboolean
2908 : 5670 : g_markup_collect_attributes (const gchar *element_name,
2909 : : const gchar **attribute_names,
2910 : : const gchar **attribute_values,
2911 : : GError **error,
2912 : : GMarkupCollectType first_type,
2913 : : const gchar *first_attr,
2914 : : ...)
2915 : : {
2916 : : GMarkupCollectType type;
2917 : : const gchar *attr;
2918 : : guint64 collected;
2919 : : int written;
2920 : : va_list ap;
2921 : : int i;
2922 : :
2923 : 5670 : type = first_type;
2924 : 5670 : attr = first_attr;
2925 : 5670 : collected = 0;
2926 : 5670 : written = 0;
2927 : :
2928 : 5670 : va_start (ap, first_attr);
2929 : 20768 : while (type != G_MARKUP_COLLECT_INVALID)
2930 : : {
2931 : : gboolean mandatory;
2932 : : const gchar *value;
2933 : :
2934 : 15104 : mandatory = !(type & G_MARKUP_COLLECT_OPTIONAL);
2935 : 15104 : type &= (G_MARKUP_COLLECT_OPTIONAL - 1);
2936 : :
2937 : : /* tristate records a value != TRUE and != FALSE
2938 : : * for the case where the attribute is missing
2939 : : */
2940 : 15104 : if (type == G_MARKUP_COLLECT_TRISTATE)
2941 : 9 : mandatory = FALSE;
2942 : :
2943 : 28739 : for (i = 0; attribute_names[i]; i++)
2944 : 25463 : if (i >= 40 || !(collected & (G_GUINT64_CONSTANT(1) << i)))
2945 : 18600 : if (!strcmp (attribute_names[i], attr))
2946 : 11828 : break;
2947 : :
2948 : : /* ISO C99 only promises that the user can pass up to 127 arguments.
2949 : : * Subtracting the first 4 arguments plus the final NULL and dividing
2950 : : * by 3 arguments per collected attribute, we are left with a maximum
2951 : : * number of supported attributes of (127 - 5) / 3 = 40.
2952 : : *
2953 : : * In reality, nobody is ever going to call us with anywhere close to
2954 : : * 40 attributes to collect, so it is safe to assume that if i > 40
2955 : : * then the user has given some invalid or repeated arguments. These
2956 : : * problems will be caught and reported at the end of the function.
2957 : : *
2958 : : * We know at this point that we have an error, but we don't know
2959 : : * what error it is, so just continue...
2960 : : */
2961 : 15104 : if (i < 40)
2962 : 15095 : collected |= (G_GUINT64_CONSTANT(1) << i);
2963 : :
2964 : 15104 : value = attribute_values[i];
2965 : :
2966 : 15104 : if (value == NULL && mandatory)
2967 : : {
2968 : 4 : g_set_error (error, G_MARKUP_ERROR,
2969 : : G_MARKUP_ERROR_MISSING_ATTRIBUTE,
2970 : : "element '%s' requires attribute '%s'",
2971 : : element_name, attr);
2972 : :
2973 : 4 : va_end (ap);
2974 : 4 : goto failure;
2975 : : }
2976 : :
2977 : 15100 : switch (type)
2978 : : {
2979 : 14914 : case G_MARKUP_COLLECT_STRING:
2980 : : {
2981 : : const char **str_ptr;
2982 : :
2983 : 14914 : str_ptr = va_arg (ap, const char **);
2984 : :
2985 : 14914 : if (str_ptr != NULL)
2986 : 13746 : *str_ptr = value;
2987 : : }
2988 : 14914 : break;
2989 : :
2990 : 141 : case G_MARKUP_COLLECT_STRDUP:
2991 : : {
2992 : : char **str_ptr;
2993 : :
2994 : 141 : str_ptr = va_arg (ap, char **);
2995 : :
2996 : 141 : if (str_ptr != NULL)
2997 : 141 : *str_ptr = g_strdup (value);
2998 : : }
2999 : 141 : break;
3000 : :
3001 : 45 : case G_MARKUP_COLLECT_BOOLEAN:
3002 : : case G_MARKUP_COLLECT_TRISTATE:
3003 : 45 : if (value == NULL)
3004 : : {
3005 : : gboolean *bool_ptr;
3006 : :
3007 : 26 : bool_ptr = va_arg (ap, gboolean *);
3008 : :
3009 : 26 : if (bool_ptr != NULL)
3010 : : {
3011 : 26 : if (type == G_MARKUP_COLLECT_TRISTATE)
3012 : : /* constructivists rejoice!
3013 : : * neither false nor true...
3014 : : */
3015 : 6 : *bool_ptr = -1;
3016 : :
3017 : : else /* G_MARKUP_COLLECT_BOOLEAN */
3018 : 20 : *bool_ptr = FALSE;
3019 : : }
3020 : : }
3021 : : else
3022 : : {
3023 : 19 : if (!g_markup_parse_boolean (value, va_arg (ap, gboolean *)))
3024 : : {
3025 : 2 : g_set_error (error, G_MARKUP_ERROR,
3026 : : G_MARKUP_ERROR_INVALID_CONTENT,
3027 : : "element '%s', attribute '%s', value '%s' "
3028 : : "cannot be parsed as a boolean value",
3029 : : element_name, attr, value);
3030 : :
3031 : 2 : va_end (ap);
3032 : 2 : goto failure;
3033 : : }
3034 : : }
3035 : :
3036 : 43 : break;
3037 : :
3038 : 0 : default:
3039 : : g_assert_not_reached ();
3040 : : }
3041 : :
3042 : 15098 : written++;
3043 : 15098 : type = va_arg (ap, GMarkupCollectType);
3044 : 15098 : if (type != G_MARKUP_COLLECT_INVALID)
3045 : 9474 : attr = va_arg (ap, const char *);
3046 : : }
3047 : 5664 : va_end (ap);
3048 : :
3049 : : /* ensure we collected all the arguments */
3050 : 17486 : for (i = 0; attribute_names[i]; i++)
3051 : 11831 : if ((collected & (G_GUINT64_CONSTANT(1) << i)) == 0)
3052 : : {
3053 : : /* attribute not collected: could be caused by two things.
3054 : : *
3055 : : * 1) it doesn't exist in our list of attributes
3056 : : * 2) it existed but was matched by a duplicate attribute earlier
3057 : : *
3058 : : * find out.
3059 : : */
3060 : : int j;
3061 : :
3062 : 14 : for (j = 0; j < i; j++)
3063 : 10 : if (strcmp (attribute_names[i], attribute_names[j]) == 0)
3064 : : /* duplicate! */
3065 : 5 : break;
3066 : :
3067 : : /* j is now the first occurrence of attribute_names[i] */
3068 : 9 : if (i == j)
3069 : 4 : g_set_error (error, G_MARKUP_ERROR,
3070 : : G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
3071 : : "attribute '%s' invalid for element '%s'",
3072 : 4 : attribute_names[i], element_name);
3073 : : else
3074 : 5 : g_set_error (error, G_MARKUP_ERROR,
3075 : : G_MARKUP_ERROR_INVALID_CONTENT,
3076 : : "attribute '%s' given multiple times for element '%s'",
3077 : 5 : attribute_names[i], element_name);
3078 : :
3079 : 9 : goto failure;
3080 : : }
3081 : :
3082 : 5655 : return TRUE;
3083 : :
3084 : 15 : failure:
3085 : : /* replay the above to free allocations */
3086 : 15 : type = first_type;
3087 : :
3088 : 15 : va_start (ap, first_attr);
3089 : 61 : while (type != G_MARKUP_COLLECT_INVALID)
3090 : : {
3091 : : gpointer ptr;
3092 : :
3093 : 46 : ptr = va_arg (ap, gpointer);
3094 : :
3095 : 46 : if (ptr != NULL)
3096 : : {
3097 : 44 : switch (type & (G_MARKUP_COLLECT_OPTIONAL - 1))
3098 : : {
3099 : 14 : case G_MARKUP_COLLECT_STRDUP:
3100 : 14 : if (written)
3101 : 10 : g_free (*(char **) ptr);
3102 : 14 : *(char **) ptr = NULL;
3103 : 14 : break;
3104 : :
3105 : 15 : case G_MARKUP_COLLECT_STRING:
3106 : 15 : *(char **) ptr = NULL;
3107 : 15 : break;
3108 : :
3109 : 10 : case G_MARKUP_COLLECT_BOOLEAN:
3110 : 10 : *(gboolean *) ptr = FALSE;
3111 : 10 : break;
3112 : :
3113 : 5 : case G_MARKUP_COLLECT_TRISTATE:
3114 : 5 : *(gboolean *) ptr = -1;
3115 : 5 : break;
3116 : : }
3117 : : }
3118 : :
3119 : 46 : type = va_arg (ap, GMarkupCollectType);
3120 : 46 : if (type != G_MARKUP_COLLECT_INVALID)
3121 : : {
3122 : 32 : attr = va_arg (ap, const char *);
3123 : : (void) attr;
3124 : : }
3125 : : }
3126 : 15 : va_end (ap);
3127 : :
3128 : 15 : return FALSE;
3129 : : }
|