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