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