NULL-terminate context->attr_values so g_strfreev() is safe. Would
[platform/upstream/glib.git] / glib / gmarkup.c
1 /* gmarkup.c - Simple XML-like parser
2  *
3  *  Copyright 2000 Red Hat, Inc.
4  *
5  * GLib is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * GLib is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with GLib; see the file COPYING.LIB.  If not,
17  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  *   Boston, MA 02111-1307, USA.
19  */
20
21 #include "glib.h"
22
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <errno.h>
27
28 #include "glibintl.h"
29
30 GQuark
31 g_markup_error_quark ()
32 {
33   static GQuark error_quark = 0;
34
35   if (error_quark == 0)
36     error_quark = g_quark_from_static_string ("g-markup-error-quark");
37
38   return error_quark;
39 }
40
41 typedef enum
42 {
43   STATE_START,
44   STATE_AFTER_OPEN_ANGLE,
45   STATE_AFTER_CLOSE_ANGLE,
46   STATE_AFTER_ELISION_SLASH, /* the slash that obviates need for end element */
47   STATE_INSIDE_OPEN_TAG_NAME,
48   STATE_INSIDE_ATTRIBUTE_NAME,
49   STATE_BETWEEN_ATTRIBUTES,
50   STATE_AFTER_ATTRIBUTE_EQUALS_SIGN,
51   STATE_INSIDE_ATTRIBUTE_VALUE,
52   STATE_INSIDE_TEXT,
53   STATE_AFTER_CLOSE_TAG_SLASH,
54   STATE_INSIDE_CLOSE_TAG_NAME,
55   STATE_INSIDE_PASSTHROUGH,
56   STATE_ERROR
57 } GMarkupParseState;
58
59 struct _GMarkupParseContext
60 {
61   const GMarkupParser *parser;
62
63   GMarkupParseFlags flags;
64
65   gint line_number;
66   gint char_number;
67
68   gpointer user_data;
69   GDestroyNotify dnotify;
70
71   /* A piece of character data or an element that
72    * hasn't "ended" yet so we haven't yet called
73    * the callback for it.
74    */
75   GString *partial_chunk;
76
77   GMarkupParseState state;
78   GSList *tag_stack;
79   gchar **attr_names;
80   gchar **attr_values;
81   gint cur_attr;
82   gint alloc_attrs;
83
84   const gchar *current_text;
85   gssize       current_text_len;      
86   const gchar *current_text_end;
87
88   GString *leftover_char_portion;
89
90   /* used to save the start of the last interesting thingy */
91   const gchar *start;
92
93   const gchar *iter;
94
95   guint document_empty : 1;
96   guint parsing : 1;
97 };
98
99 /**
100  * g_markup_parse_context_new:
101  * @parser: a #GMarkupParser
102  * @flags: one or more #GMarkupParseFlags
103  * @user_data: user data to pass to #GMarkupParser functions
104  * @user_data_dnotify: user data destroy notifier called when the parse context is freed
105  * 
106  * Creates a new parse context. A parse context is used to parse
107  * marked-up documents. You can feed any number of documents into
108  * a context, as long as no errors occur; once an error occurs,
109  * the parse context can't continue to parse text (you have to free it
110  * and create a new parse context).
111  * 
112  * Return value: a new #GMarkupParseContext
113  **/
114 GMarkupParseContext *
115 g_markup_parse_context_new (const GMarkupParser *parser,
116                             GMarkupParseFlags    flags,
117                             gpointer             user_data,
118                             GDestroyNotify       user_data_dnotify)
119 {
120   GMarkupParseContext *context;
121
122   g_return_val_if_fail (parser != NULL, NULL);
123
124   context = g_new (GMarkupParseContext, 1);
125
126   context->parser = parser;
127   context->flags = flags;
128   context->user_data = user_data;
129   context->dnotify = user_data_dnotify;
130
131   context->line_number = 1;
132   context->char_number = 1;
133
134   context->partial_chunk = NULL;
135
136   context->state = STATE_START;
137   context->tag_stack = NULL;
138   context->attr_names = NULL;
139   context->attr_values = NULL;
140   context->cur_attr = -1;
141   context->alloc_attrs = 0;
142
143   context->current_text = NULL;
144   context->current_text_len = -1;
145   context->current_text_end = NULL;
146   context->leftover_char_portion = NULL;
147
148   context->start = NULL;
149   context->iter = NULL;
150
151   context->document_empty = TRUE;
152   context->parsing = FALSE;
153
154   return context;
155 }
156
157 /**
158  * g_markup_parse_context_free:
159  * @context: a #GMarkupParseContext
160  * 
161  * Frees a #GMarkupParseContext. Can't be called from inside
162  * one of the #GMarkupParser functions.
163  * 
164  **/
165 void
166 g_markup_parse_context_free (GMarkupParseContext *context)
167 {
168   g_return_if_fail (context != NULL);
169   g_return_if_fail (!context->parsing);
170
171   if (context->dnotify)
172     (* context->dnotify) (context->user_data);
173
174   g_strfreev (context->attr_names);
175   g_strfreev (context->attr_values);
176
177   g_slist_foreach (context->tag_stack, (GFunc)g_free, NULL);
178   g_slist_free (context->tag_stack);
179
180   if (context->partial_chunk)
181     g_string_free (context->partial_chunk, TRUE);
182
183   if (context->leftover_char_portion)
184     g_string_free (context->leftover_char_portion, TRUE);
185
186   g_free (context);
187 }
188
189 static void
190 mark_error (GMarkupParseContext *context,
191             GError              *error)
192 {
193   context->state = STATE_ERROR;
194
195   if (context->parser->error)
196     (*context->parser->error) (context, error, context->user_data);
197 }
198
199 static void
200 set_error (GMarkupParseContext *context,
201            GError             **error,
202            GMarkupError         code,
203            const gchar         *format,
204            ...)
205 {
206   GError *tmp_error;
207   gchar *s;
208   va_list args;
209
210   va_start (args, format);
211   s = g_strdup_vprintf (format, args);
212   va_end (args);
213
214   tmp_error = g_error_new (G_MARKUP_ERROR,
215                            code,
216                            _("Error on line %d char %d: %s"),
217                            context->line_number,
218                            context->char_number,
219                            s);
220
221   g_free (s);
222
223   mark_error (context, tmp_error);
224
225   g_propagate_error (error, tmp_error);
226 }
227
228 static gboolean
229 is_name_start_char (gunichar c)
230 {
231   if (g_unichar_isalpha (c) ||
232       c == '_' ||
233       c == ':')
234     return TRUE;
235   else
236     return FALSE;
237 }
238
239 static gboolean
240 is_name_char (gunichar c)
241 {
242   if (g_unichar_isalnum (c) ||
243       c == '.' ||
244       c == '-' ||
245       c == '_' ||
246       c == ':')
247     return TRUE;
248   else
249     return FALSE;
250 }
251
252
253 static gchar*
254 char_str (gunichar c,
255           gchar   *buf)
256 {
257   memset (buf, 0, 7);
258   g_unichar_to_utf8 (c, buf);
259   return buf;
260 }
261
262 static gchar*
263 utf8_str (const gchar *utf8,
264           gchar       *buf)
265 {
266   char_str (g_utf8_get_char (utf8), buf);
267   return buf;
268 }
269
270 static void
271 set_unescape_error (GMarkupParseContext *context,
272                     GError             **error,
273                     const gchar         *remaining_text,
274                     const gchar         *remaining_text_end,
275                     GMarkupError         code,
276                     const gchar         *format,
277                     ...)
278 {
279   GError *tmp_error;
280   gchar *s;
281   va_list args;
282   gint remaining_newlines;
283   const gchar *p;
284
285   remaining_newlines = 0;
286   p = remaining_text;
287   while (p != remaining_text_end)
288     {
289       if (*p == '\n')
290         ++remaining_newlines;
291       ++p;
292     }
293
294   va_start (args, format);
295   s = g_strdup_vprintf (format, args);
296   va_end (args);
297
298   tmp_error = g_error_new (G_MARKUP_ERROR,
299                            code,
300                            _("Error on line %d: %s"),
301                            context->line_number - remaining_newlines,
302                            s);
303
304   g_free (s);
305
306   mark_error (context, tmp_error);
307
308   g_propagate_error (error, tmp_error);
309 }
310
311 typedef enum
312 {
313   USTATE_INSIDE_TEXT,
314   USTATE_AFTER_AMPERSAND,
315   USTATE_INSIDE_ENTITY_NAME,
316   USTATE_AFTER_CHARREF_HASH
317 } UnescapeState;
318
319 static gboolean
320 unescape_text (GMarkupParseContext *context,
321                const gchar         *text,
322                const gchar         *text_end,
323                gchar              **unescaped,
324                GError             **error)
325 {
326 #define MAX_ENT_LEN 5
327   GString *str;
328   const gchar *p;
329   UnescapeState state;
330   const gchar *start;
331
332   str = g_string_new ("");
333
334   state = USTATE_INSIDE_TEXT;
335   p = text;
336   start = p;
337   while (p != text_end && context->state != STATE_ERROR)
338     {
339       g_assert (p < text_end);
340       
341       switch (state)
342         {
343         case USTATE_INSIDE_TEXT:
344           {
345             while (p != text_end && *p != '&')
346               p = g_utf8_next_char (p);
347
348             if (p != start)
349               {
350                 g_string_append_len (str, start, p - start);
351
352                 start = NULL;
353               }
354             
355             if (p != text_end && *p == '&')
356               {
357                 p = g_utf8_next_char (p);
358                 state = USTATE_AFTER_AMPERSAND;
359               }
360           }
361           break;
362
363         case USTATE_AFTER_AMPERSAND:
364           {
365             if (*p == '#')
366               {
367                 p = g_utf8_next_char (p);
368
369                 start = p;
370                 state = USTATE_AFTER_CHARREF_HASH;
371               }
372             else if (!is_name_start_char (g_utf8_get_char (p)))
373               {
374                 if (*p == ';')
375                   {
376                     set_unescape_error (context, error,
377                                         p, text_end,
378                                         G_MARKUP_ERROR_PARSE,
379                                         _("Empty entity '&;' seen; valid "
380                                           "entities are: &amp; &quot; &lt; &gt; &apos;"));
381                   }
382                 else
383                   {
384                     gchar buf[7];
385
386                     set_unescape_error (context, error,
387                                         p, text_end,
388                                         G_MARKUP_ERROR_PARSE,
389                                         _("Character '%s' is not valid at "
390                                           "the start of an entity name; "
391                                           "the & character begins an entity; "
392                                           "if this ampersand isn't supposed "
393                                           "to be an entity, escape it as "
394                                           "&amp;"),
395                                         utf8_str (p, buf));
396                   }
397               }
398             else
399               {
400                 start = p;
401                 state = USTATE_INSIDE_ENTITY_NAME;
402               }
403           }
404           break;
405
406
407         case USTATE_INSIDE_ENTITY_NAME:
408           {
409             gchar buf[MAX_ENT_LEN+1] = {
410               '\0', '\0', '\0', '\0', '\0', '\0'
411             };
412             gchar *dest;
413
414             while (p != text_end)
415               {
416                 if (*p == ';')
417                   break;
418                 else if (!is_name_char (*p))
419                   {
420                     gchar ubuf[7];
421
422                     set_unescape_error (context, error,
423                                         p, text_end,
424                                         G_MARKUP_ERROR_PARSE,
425                                         _("Character '%s' is not valid "
426                                           "inside an entity name"),
427                                         utf8_str (p, ubuf));
428                     break;
429                   }
430
431                 p = g_utf8_next_char (p);
432               }
433
434             if (context->state != STATE_ERROR)
435               {
436                 if (p != text_end)
437                   {
438                     const gchar *src;
439                 
440                     src = start;
441                     dest = buf;
442                     while (src != p)
443                       {
444                         *dest = *src;
445                         ++dest;
446                         ++src;
447                       }
448
449                     /* move to after semicolon */
450                     p = g_utf8_next_char (p);
451                     start = p;
452                     state = USTATE_INSIDE_TEXT;
453
454                     if (strcmp (buf, "lt") == 0)
455                       g_string_append_c (str, '<');
456                     else if (strcmp (buf, "gt") == 0)
457                       g_string_append_c (str, '>');
458                     else if (strcmp (buf, "amp") == 0)
459                       g_string_append_c (str, '&');
460                     else if (strcmp (buf, "quot") == 0)
461                       g_string_append_c (str, '"');
462                     else if (strcmp (buf, "apos") == 0)
463                       g_string_append_c (str, '\'');
464                     else
465                       {
466                         set_unescape_error (context, error,
467                                             p, text_end,
468                                             G_MARKUP_ERROR_PARSE,
469                                             _("Entity name '%s' is not known"),
470                                             buf);
471                       }
472                   }
473                 else
474                   {
475                     set_unescape_error (context, error,
476                                         /* give line number of the & */
477                                         start, text_end,
478                                         G_MARKUP_ERROR_PARSE,
479                                         _("Entity did not end with a semicolon; "
480                                           "most likely you used an ampersand "
481                                           "character without intending to start "
482                                           "an entity - escape ampersand as &amp;"));
483                   }
484               }
485           }
486           break;
487
488         case USTATE_AFTER_CHARREF_HASH:
489           {
490             gboolean is_hex = FALSE;
491             if (*p == 'x')
492               {
493                 is_hex = TRUE;
494                 p = g_utf8_next_char (p);
495                 start = p;
496               }
497
498             while (p != text_end && *p != ';')
499               p = g_utf8_next_char (p);
500
501             if (p != text_end)
502               {
503                 g_assert (*p == ';');
504
505                 /* digit is between start and p */
506
507                 if (start != p)
508                   {
509                     gchar *digit = g_strndup (start, p - start);
510                     gulong l;
511                     gchar *end = NULL;
512                     gchar *digit_end = digit + (p - start);
513                     
514                     errno = 0;
515                     if (is_hex)
516                       l = strtoul (digit, &end, 16);
517                     else
518                       l = strtoul (digit, &end, 10);
519
520                     if (end != digit_end || errno != 0)
521                       {
522                         set_unescape_error (context, error,
523                                             start, text_end,
524                                             G_MARKUP_ERROR_PARSE,
525                                             _("Failed to parse '%s', which "
526                                               "should have been a digit "
527                                               "inside a character reference "
528                                               "(&#234; for example) - perhaps "
529                                               "the digit is too large"),
530                                             digit);
531                       }
532                     else
533                       {
534                         /* characters XML permits */
535                         if (l == 0x9 ||
536                             l == 0xA ||
537                             l == 0xD ||
538                             (l >= 0x20 && l <= 0xD7FF) ||
539                             (l >= 0xE000 && l <= 0xFFFD) ||
540                             (l >= 0x10000 && l <= 0x10FFFF))
541                           {
542                             gchar buf[7];
543                             g_string_append (str, char_str (l, buf));
544                           }
545                         else
546                           {
547                             set_unescape_error (context, error,
548                                                 start, text_end,
549                                                 G_MARKUP_ERROR_PARSE,
550                                                 _("Character reference '%s' does not encode a permitted character"),
551                                                 digit);
552                           }
553                       }
554
555                     g_free (digit);
556
557                     /* Move to next state */
558                     p = g_utf8_next_char (p); /* past semicolon */
559                     start = p;
560                     state = USTATE_INSIDE_TEXT;
561                   }
562                 else
563                   {
564                     set_unescape_error (context, error,
565                                         start, text_end,
566                                         G_MARKUP_ERROR_PARSE,
567                                         _("Empty character reference; "
568                                           "should include a digit such as "
569                                           "&#454;"));
570                   }
571               }
572             else
573               {
574                 set_unescape_error (context, error,
575                                     start, text_end,
576                                     G_MARKUP_ERROR_PARSE,
577                                     _("Character reference did not end with a "
578                                       "semicolon; "
579                                       "most likely you used an ampersand "
580                                       "character without intending to start "
581                                       "an entity - escape ampersand as &amp;"));
582               }
583           }
584           break;
585
586         default:
587           g_assert_not_reached ();
588           break;
589         }
590     }
591
592   /* If no errors, we should have returned to USTATE_INSIDE_TEXT */
593   g_assert (context->state == STATE_ERROR ||
594             state == USTATE_INSIDE_TEXT);
595
596   if (context->state == STATE_ERROR)
597     {
598       g_string_free (str, TRUE);
599       *unescaped = NULL;
600       return FALSE;
601     }
602   else
603     {
604       *unescaped = g_string_free (str, FALSE);
605       return TRUE;
606     }
607
608 #undef MAX_ENT_LEN
609 }
610
611 static gboolean
612 advance_char (GMarkupParseContext *context)
613 {
614
615   context->iter = g_utf8_next_char (context->iter);
616   context->char_number += 1;
617   if (*context->iter == '\n')
618     {
619       context->line_number += 1;
620       context->char_number = 1;
621     }
622
623   return context->iter != context->current_text_end;
624 }
625
626 static void
627 skip_spaces (GMarkupParseContext *context)
628 {
629   do
630     {
631       if (!g_unichar_isspace (g_utf8_get_char (context->iter)))
632         return;
633     }
634   while (advance_char (context));
635 }
636
637 static void
638 advance_to_name_end (GMarkupParseContext *context)
639 {
640   do
641     {
642       if (!is_name_char (g_utf8_get_char (context->iter)))
643         return;
644     }
645   while (advance_char (context));
646 }
647
648 static void
649 add_to_partial (GMarkupParseContext *context,
650                 const gchar         *text_start,
651                 const gchar         *text_end)
652 {
653   if (context->partial_chunk == NULL)
654     context->partial_chunk = g_string_new ("");
655
656   if (text_start != text_end)
657     g_string_append_len (context->partial_chunk, text_start,
658                          text_end - text_start);
659
660   /* Invariant here that partial_chunk exists */
661 }
662
663 static void
664 truncate_partial (GMarkupParseContext *context)
665 {
666   if (context->partial_chunk != NULL)
667     {
668       context->partial_chunk = g_string_truncate (context->partial_chunk, 0);
669     }
670 }
671
672 static const gchar*
673 current_element (GMarkupParseContext *context)
674 {
675   return context->tag_stack->data;
676 }
677
678 static const gchar*
679 current_attribute (GMarkupParseContext *context)
680 {
681   g_assert (context->cur_attr >= 0);
682   return context->attr_names[context->cur_attr];
683 }
684
685 static void
686 find_current_text_end (GMarkupParseContext *context)
687 {
688   /* This function must be safe (non-segfaulting) on invalid UTF8 */
689   const gchar *end = context->current_text + context->current_text_len;
690   const gchar *p;
691   const gchar *next;
692
693   g_assert (context->current_text_len > 0);
694
695   p = context->current_text;
696   next = g_utf8_find_next_char (p, end);
697
698   while (next)
699     {
700       p = next;
701       next = g_utf8_find_next_char (p, end);
702     }
703
704   /* p is now the start of the last character or character portion. */
705   g_assert (p != end);
706   next = g_utf8_next_char (p); /* this only touches *p, nothing beyond */
707
708   if (next == end)
709     {
710       /* whole character */
711       context->current_text_end = end;
712     }
713   else
714     {
715       /* portion */
716       context->leftover_char_portion = g_string_new_len (p, end - p);
717       context->current_text_len -= (end - p);
718       context->current_text_end = p;
719     }
720 }
721
722 static void
723 add_attribute (GMarkupParseContext *context, char *name)
724 {
725   if (context->cur_attr + 2 >= context->alloc_attrs)
726     {
727       context->alloc_attrs += 5; /* silly magic number */
728       context->attr_names = g_realloc (context->attr_names, sizeof(char*)*context->alloc_attrs);
729       context->attr_values = g_realloc (context->attr_values, sizeof(char*)*context->alloc_attrs);
730     }
731   context->cur_attr++;
732   context->attr_names[context->cur_attr] = name;
733   context->attr_values[context->cur_attr] = NULL;
734   context->attr_names[context->cur_attr+1] = NULL;
735   context->attr_values[context->cur_attr+1] = NULL;
736 }
737
738 /**
739  * g_markup_parse_context_parse:
740  * @context: a #GMarkupParseContext
741  * @text: chunk of text to parse
742  * @text_len: length of @text in bytes
743  * @error: return location for a #GError
744  * 
745  * Feed some data to the #GMarkupParseContext. The data need not
746  * be valid UTF-8; an error will be signaled if it's invalid.
747  * The data need not be an entire document; you can feed a document
748  * into the parser incrementally, via multiple calls to this function.
749  * Typically, as you receive data from a network connection or file,
750  * you feed each received chunk of data into this function, aborting
751  * the process if an error occurs. Once an error is reported, no further
752  * data may be fed to the #GMarkupParseContext; all errors are fatal.
753  * 
754  * Return value: %FALSE if an error occurred, %TRUE on success
755  **/
756 gboolean
757 g_markup_parse_context_parse (GMarkupParseContext *context,
758                               const gchar         *text,
759                               gssize               text_len,
760                               GError             **error)
761 {
762   const gchar *first_invalid;
763   
764   g_return_val_if_fail (context != NULL, FALSE);
765   g_return_val_if_fail (text != NULL, FALSE);
766   g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
767   g_return_val_if_fail (!context->parsing, FALSE);
768   
769   if (text_len < 0)
770     text_len = strlen (text);
771
772   if (text_len == 0)
773     return TRUE;
774   
775   context->parsing = TRUE;
776   
777   if (context->leftover_char_portion)
778     {
779       const gchar *first_char;
780
781       if ((*text & 0xc0) != 0x80)
782         first_char = text;
783       else
784         first_char = g_utf8_find_next_char (text, text + text_len);
785
786       if (first_char)
787         {
788           /* leftover_char_portion was completed. Parse it. */
789           GString *portion = context->leftover_char_portion;
790           
791           g_string_append_len (context->leftover_char_portion,
792                                text, first_char - text);
793
794           /* hacks to allow recursion */
795           context->parsing = FALSE;
796           context->leftover_char_portion = NULL;
797           
798           if (!g_markup_parse_context_parse (context,
799                                              portion->str, portion->len,
800                                              error))
801             {
802               g_assert (context->state == STATE_ERROR);
803             }
804           
805           g_string_free (portion, TRUE);
806           context->parsing = TRUE;
807
808           /* Skip the fraction of char that was in this text */
809           text_len -= (first_char - text);
810           text = first_char;
811         }
812       else
813         {
814           /* another little chunk of the leftover char; geez
815            * someone is inefficient.
816            */
817           g_string_append_len (context->leftover_char_portion,
818                                text, text_len);
819
820           if (context->leftover_char_portion->len > 7)
821             {
822               /* The leftover char portion is too big to be
823                * a UTF-8 character
824                */
825               set_error (context,
826                          error,
827                          G_MARKUP_ERROR_BAD_UTF8,
828                          _("Invalid UTF-8 encoded text"));
829             }
830           
831           goto finished;
832         }
833     }
834
835   context->current_text = text;
836   context->current_text_len = text_len;
837   context->iter = context->current_text;
838   context->start = context->iter;
839
840   /* Nothing left after finishing the leftover char, or nothing
841    * passed in to begin with.
842    */
843   if (context->current_text_len == 0)
844     goto finished;
845
846   /* find_current_text_end () assumes the string starts at
847    * a character start, so we need to validate at least
848    * that much. It doesn't assume any following bytes
849    * are valid.
850    */
851   if ((*context->current_text & 0xc0) == 0x80) /* not a char start */
852     {
853       set_error (context,
854                  error,
855                  G_MARKUP_ERROR_BAD_UTF8,
856                  _("Invalid UTF-8 encoded text"));
857       goto finished;
858     }
859
860   /* Initialize context->current_text_end, possibly adjusting
861    * current_text_len, and add any leftover char portion
862    */
863   find_current_text_end (context);
864
865   /* Validate UTF8 (must be done after we find the end, since
866    * we could have a trailing incomplete char)
867    */
868   if (!g_utf8_validate (context->current_text,
869                         context->current_text_len,
870                         &first_invalid))
871     {
872       gint newlines = 0;
873       const gchar *p;
874       p = context->current_text;
875       while (p != context->current_text_end)
876         {
877           if (*p == '\n')
878             ++newlines;
879           ++p;
880         }
881
882       context->line_number += newlines;
883
884       set_error (context,
885                  error,
886                  G_MARKUP_ERROR_BAD_UTF8,
887                  _("Invalid UTF-8 encoded text"));
888       goto finished;
889     }
890
891   while (context->iter != context->current_text_end)
892     {
893       switch (context->state)
894         {
895         case STATE_START:
896           /* Possible next state: AFTER_OPEN_ANGLE */
897
898           g_assert (context->tag_stack == NULL);
899
900           /* whitespace is ignored outside of any elements */
901           skip_spaces (context);
902
903           if (context->iter != context->current_text_end)
904             {
905               if (*context->iter == '<')
906                 {
907                   /* Move after the open angle */
908                   advance_char (context);
909
910                   context->state = STATE_AFTER_OPEN_ANGLE;
911
912                   /* this could start a passthrough */
913                   context->start = context->iter;
914
915                   /* document is now non-empty */
916                   context->document_empty = FALSE;
917                 }
918               else
919                 {
920                   set_error (context,
921                              error,
922                              G_MARKUP_ERROR_PARSE,
923                              _("Document must begin with an element (e.g. <book>)"));
924                 }
925             }
926           break;
927
928         case STATE_AFTER_OPEN_ANGLE:
929           /* Possible next states: INSIDE_OPEN_TAG_NAME,
930            *  AFTER_CLOSE_TAG_SLASH, INSIDE_PASSTHROUGH
931            */
932           if (*context->iter == '?' ||
933               *context->iter == '!')
934             {
935               /* include < in the passthrough */
936               const gchar *openangle = "<";
937               add_to_partial (context, openangle, openangle + 1);
938               context->start = context->iter;
939               context->state = STATE_INSIDE_PASSTHROUGH;
940             }
941           else if (*context->iter == '/')
942             {
943               /* move after it */
944               advance_char (context);
945
946               context->state = STATE_AFTER_CLOSE_TAG_SLASH;
947             }
948           else if (is_name_start_char (g_utf8_get_char (context->iter)))
949             {
950               context->state = STATE_INSIDE_OPEN_TAG_NAME;
951
952               /* start of tag name */
953               context->start = context->iter;
954             }
955           else
956             {
957               gchar buf[7];
958               set_error (context,
959                          error,
960                          G_MARKUP_ERROR_PARSE,
961                          _("'%s' is not a valid character following "
962                            "a '<' character; it may not begin an "
963                            "element name"),
964                          utf8_str (context->iter, buf));
965             }
966           break;
967
968           /* The AFTER_CLOSE_ANGLE state is actually sort of
969            * broken, because it doesn't correspond to a range
970            * of characters in the input stream as the others do,
971            * and thus makes things harder to conceptualize
972            */
973         case STATE_AFTER_CLOSE_ANGLE:
974           /* Possible next states: INSIDE_TEXT, STATE_START */
975           if (context->tag_stack == NULL)
976             {
977               context->start = NULL;
978               context->state = STATE_START;
979             }
980           else
981             {
982               context->start = context->iter;
983               context->state = STATE_INSIDE_TEXT;
984             }
985           break;
986
987         case STATE_AFTER_ELISION_SLASH:
988           /* Possible next state: AFTER_CLOSE_ANGLE */
989
990           {
991             /* We need to pop the tag stack and call the end_element
992              * function, since this is the close tag
993              */
994             GError *tmp_error = NULL;
995           
996             g_assert (context->tag_stack != NULL);
997
998             tmp_error = NULL;
999             if (context->parser->end_element)
1000               (* context->parser->end_element) (context,
1001                                                 context->tag_stack->data,
1002                                                 context->user_data,
1003                                                 &tmp_error);
1004           
1005             if (tmp_error)
1006               {
1007                 mark_error (context, tmp_error);
1008                 g_propagate_error (error, tmp_error);
1009               }          
1010             else
1011               {
1012                 if (*context->iter == '>')
1013                   {
1014                     /* move after the close angle */
1015                     advance_char (context);
1016                     context->state = STATE_AFTER_CLOSE_ANGLE;
1017                   }
1018                 else
1019                   {
1020                     gchar buf[7];
1021                     set_error (context,
1022                                error,
1023                                G_MARKUP_ERROR_PARSE,
1024                                _("Odd character '%s', expected a '>' character "
1025                                  "to end the start tag of element '%s'"),
1026                                utf8_str (context->iter, buf),
1027                                current_element (context));
1028                   }
1029               }
1030
1031             g_free (context->tag_stack->data);
1032             context->tag_stack = g_slist_delete_link (context->tag_stack,
1033                                                       context->tag_stack);
1034           }
1035           break;
1036
1037         case STATE_INSIDE_OPEN_TAG_NAME:
1038           /* Possible next states: BETWEEN_ATTRIBUTES */
1039
1040           /* if there's a partial chunk then it's the first part of the
1041            * tag name. If there's a context->start then it's the start
1042            * of the tag name in current_text, the partial chunk goes
1043            * before that start though.
1044            */
1045           advance_to_name_end (context);
1046
1047           if (context->iter == context->current_text_end)
1048             {
1049               /* The name hasn't necessarily ended. Merge with
1050                * partial chunk, leave state unchanged.
1051                */
1052               add_to_partial (context, context->start, context->iter);
1053             }
1054           else
1055             {
1056               /* The name has ended. Combine it with the partial chunk
1057                * if any; push it on the stack; enter next state.
1058                */
1059               add_to_partial (context, context->start, context->iter);
1060               context->tag_stack =
1061                 g_slist_prepend (context->tag_stack,
1062                                  g_string_free (context->partial_chunk,
1063                                                 FALSE));
1064
1065               context->partial_chunk = NULL;
1066
1067               context->state = STATE_BETWEEN_ATTRIBUTES;
1068               context->start = NULL;
1069             }
1070           break;
1071
1072         case STATE_INSIDE_ATTRIBUTE_NAME:
1073           /* Possible next states: AFTER_ATTRIBUTE_EQUALS_SIGN */
1074
1075           /* read the full name, if we enter the equals sign state
1076            * then add the attribute to the list (without the value),
1077            * otherwise store a partial chunk to be prepended later.
1078            */
1079           advance_to_name_end (context);
1080
1081           if (context->iter == context->current_text_end)
1082             {
1083               /* The name hasn't necessarily ended. Merge with
1084                * partial chunk, leave state unchanged.
1085                */
1086               add_to_partial (context, context->start, context->iter);
1087             }
1088           else
1089             {
1090               /* The name has ended. Combine it with the partial chunk
1091                * if any; push it on the stack; enter next state.
1092                */
1093               add_to_partial (context, context->start, context->iter);
1094
1095               add_attribute (context, g_string_free (context->partial_chunk, FALSE));
1096
1097               context->partial_chunk = NULL;
1098               context->start = NULL;
1099
1100               if (*context->iter == '=')
1101                 {
1102                   advance_char (context);
1103                   context->state = STATE_AFTER_ATTRIBUTE_EQUALS_SIGN;
1104                 }
1105               else
1106                 {
1107                   gchar buf[7];
1108                   set_error (context,
1109                              error,
1110                              G_MARKUP_ERROR_PARSE,
1111                              _("Odd character '%s', expected a '=' after "
1112                                "attribute name '%s' of element '%s'"),
1113                              utf8_str (context->iter, buf),
1114                              current_attribute (context),
1115                              current_element (context));
1116
1117                 }
1118             }
1119           break;
1120
1121         case STATE_BETWEEN_ATTRIBUTES:
1122           /* Possible next states: AFTER_CLOSE_ANGLE,
1123            * AFTER_ELISION_SLASH, INSIDE_ATTRIBUTE_NAME
1124            */
1125           skip_spaces (context);
1126
1127           if (context->iter != context->current_text_end)
1128             {
1129               if (*context->iter == '/')
1130                 {
1131                   advance_char (context);
1132                   context->state = STATE_AFTER_ELISION_SLASH;
1133                 }
1134               else if (*context->iter == '>')
1135                 {
1136
1137                   advance_char (context);
1138                   context->state = STATE_AFTER_CLOSE_ANGLE;
1139                 }
1140               else if (is_name_start_char (g_utf8_get_char (context->iter)))
1141                 {
1142                   context->state = STATE_INSIDE_ATTRIBUTE_NAME;
1143                   /* start of attribute name */
1144                   context->start = context->iter;
1145                 }
1146               else
1147                 {
1148                   gchar buf[7];
1149                   set_error (context,
1150                              error,
1151                              G_MARKUP_ERROR_PARSE,
1152                              _("Odd character '%s', expected a '>' or '/' "
1153                                "character to end the start tag of "
1154                                "element '%s', or optionally an attribute; "
1155                                "perhaps you used an invalid character in "
1156                                "an attribute name"),
1157                              utf8_str (context->iter, buf),
1158                              current_element (context));
1159                 }
1160
1161               /* If we're done with attributes, invoke
1162                * the start_element callback
1163                */
1164               if (context->state == STATE_AFTER_ELISION_SLASH ||
1165                   context->state == STATE_AFTER_CLOSE_ANGLE)
1166                 {
1167                   const gchar *start_name;
1168                   /* Ugly, but the current code expects an empty array instead of NULL */
1169                   const gchar *empty = NULL;
1170                   const gchar **attr_names =  &empty;
1171                   const gchar **attr_values = &empty;
1172                   GError *tmp_error;
1173
1174                   /* Call user callback for element start */
1175                   start_name = current_element (context);
1176
1177                   if (context->cur_attr >= 0)
1178                     {
1179                       attr_names = (const gchar**)context->attr_names;
1180                       attr_values = (const gchar**)context->attr_values;
1181                     }
1182
1183                   tmp_error = NULL;
1184                   if (context->parser->start_element)
1185                     (* context->parser->start_element) (context,
1186                                                         start_name,
1187                                                         (const gchar **)attr_names,
1188                                                         (const gchar **)attr_values,
1189                                                         context->user_data,
1190                                                         &tmp_error);
1191
1192                   /* Go ahead and free the attributes. */
1193                   for (; context->cur_attr >= 0; context->cur_attr--)
1194                     {
1195                       int pos = context->cur_attr;
1196                       g_free (context->attr_names[pos]);
1197                       g_free (context->attr_values[pos]);
1198                       context->attr_names[pos] = context->attr_values[pos] = NULL;
1199                     }
1200                   g_assert (context->cur_attr == -1);
1201                   g_assert (context->attr_names == NULL ||
1202                             context->attr_names[0] == NULL);
1203                   g_assert (context->attr_values == NULL ||
1204                             context->attr_values[0] == NULL);
1205                   
1206                   if (tmp_error != NULL)
1207                     {
1208                       mark_error (context, tmp_error);
1209                       g_propagate_error (error, tmp_error);
1210                     }
1211                 }
1212             }
1213           break;
1214
1215         case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1216           /* Possible next state: INSIDE_ATTRIBUTE_VALUE */
1217           if (*context->iter == '"')
1218             {
1219               advance_char (context);
1220               context->state = STATE_INSIDE_ATTRIBUTE_VALUE;
1221               context->start = context->iter;
1222             }
1223           else
1224             {
1225               gchar buf[7];
1226               set_error (context,
1227                          error,
1228                          G_MARKUP_ERROR_PARSE,
1229                          _("Odd character '%s', expected an open quote mark "
1230                            "after the equals sign when giving value for "
1231                            "attribute '%s' of element '%s'"),
1232                          utf8_str (context->iter, buf),
1233                          current_attribute (context),
1234                          current_element (context));
1235             }
1236           break;
1237
1238         case STATE_INSIDE_ATTRIBUTE_VALUE:
1239           /* Possible next states: BETWEEN_ATTRIBUTES */
1240           do
1241             {
1242               if (*context->iter == '"')
1243                 break;
1244             }
1245           while (advance_char (context));
1246
1247           if (context->iter == context->current_text_end)
1248             {
1249               /* The value hasn't necessarily ended. Merge with
1250                * partial chunk, leave state unchanged.
1251                */
1252               add_to_partial (context, context->start, context->iter);
1253             }
1254           else
1255             {
1256               /* The value has ended at the quote mark. Combine it
1257                * with the partial chunk if any; set it for the current
1258                * attribute.
1259                */
1260               add_to_partial (context, context->start, context->iter);
1261
1262               g_assert (context->cur_attr >= 0);
1263               
1264               if (unescape_text (context,
1265                                  context->partial_chunk->str,
1266                                  context->partial_chunk->str +
1267                                  context->partial_chunk->len,
1268                                  &context->attr_values[context->cur_attr],
1269                                  error))
1270                 {
1271                   /* success, advance past quote and set state. */
1272                   advance_char (context);
1273                   context->state = STATE_BETWEEN_ATTRIBUTES;
1274                   context->start = NULL;
1275                 }
1276               
1277               truncate_partial (context);
1278             }
1279           break;
1280
1281         case STATE_INSIDE_TEXT:
1282           /* Possible next states: AFTER_OPEN_ANGLE */
1283           do
1284             {
1285               if (*context->iter == '<')
1286                 break;
1287             }
1288           while (advance_char (context));
1289
1290           /* The text hasn't necessarily ended. Merge with
1291            * partial chunk, leave state unchanged.
1292            */
1293
1294           add_to_partial (context, context->start, context->iter);
1295
1296           if (context->iter != context->current_text_end)
1297             {
1298               gchar *unescaped = NULL;
1299
1300               /* The text has ended at the open angle. Call the text
1301                * callback.
1302                */
1303               
1304               if (unescape_text (context,
1305                                  context->partial_chunk->str,
1306                                  context->partial_chunk->str +
1307                                  context->partial_chunk->len,
1308                                  &unescaped,
1309                                  error))
1310                 {
1311                   GError *tmp_error = NULL;
1312
1313                   if (context->parser->text)
1314                     (*context->parser->text) (context,
1315                                               unescaped,
1316                                               strlen (unescaped),
1317                                               context->user_data,
1318                                               &tmp_error);
1319                   
1320                   g_free (unescaped);
1321
1322                   if (tmp_error == NULL)
1323                     {
1324                       /* advance past open angle and set state. */
1325                       advance_char (context);
1326                       context->state = STATE_AFTER_OPEN_ANGLE;
1327                       /* could begin a passthrough */
1328                       context->start = context->iter;
1329                     }
1330                   else
1331                     {
1332                       mark_error (context, tmp_error);
1333                       g_propagate_error (error, tmp_error);
1334                     }
1335                 }
1336
1337               truncate_partial (context);
1338             }
1339           break;
1340
1341         case STATE_AFTER_CLOSE_TAG_SLASH:
1342           /* Possible next state: INSIDE_CLOSE_TAG_NAME */
1343           if (is_name_start_char (g_utf8_get_char (context->iter)))
1344             {
1345               context->state = STATE_INSIDE_CLOSE_TAG_NAME;
1346
1347               /* start of tag name */
1348               context->start = context->iter;
1349             }
1350           else
1351             {
1352               gchar buf[7];
1353               set_error (context,
1354                          error,
1355                          G_MARKUP_ERROR_PARSE,
1356                          _("'%s' is not a valid character following "
1357                            "the characters '</'; '%s' may not begin an "
1358                            "element name"),
1359                          utf8_str (context->iter, buf),
1360                          utf8_str (context->iter, buf));
1361             }
1362           break;
1363
1364         case STATE_INSIDE_CLOSE_TAG_NAME:
1365           /* Possible next state: AFTER_CLOSE_ANGLE */
1366           advance_to_name_end (context);
1367
1368           if (context->iter == context->current_text_end)
1369             {
1370               /* The name hasn't necessarily ended. Merge with
1371                * partial chunk, leave state unchanged.
1372                */
1373               add_to_partial (context, context->start, context->iter);
1374             }
1375           else
1376             {
1377               /* The name has ended. Combine it with the partial chunk
1378                * if any; check that it matches stack top and pop
1379                * stack; invoke proper callback; enter next state.
1380                */
1381               gchar *close_name;
1382
1383               add_to_partial (context, context->start, context->iter);
1384
1385               close_name = g_string_free (context->partial_chunk, FALSE);
1386               context->partial_chunk = NULL;
1387               
1388               if (context->tag_stack == NULL)
1389                 {
1390                   set_error (context,
1391                              error,
1392                              G_MARKUP_ERROR_PARSE,
1393                              _("Element '%s' was closed, no element "
1394                                "is currently open"),
1395                              close_name);
1396                 }
1397               else if (strcmp (close_name, current_element (context)) != 0)
1398                 {
1399                   set_error (context,
1400                              error,
1401                              G_MARKUP_ERROR_PARSE,
1402                              _("Element '%s' was closed, but the currently "
1403                                "open element is '%s'"),
1404                              close_name,
1405                              current_element (context));
1406                 }
1407               else if (*context->iter != '>')
1408                 {
1409                   gchar buf[7];
1410                   set_error (context,
1411                              error,
1412                              G_MARKUP_ERROR_PARSE,
1413                              _("'%s' is not a valid character following "
1414                                "the close element name '%s'; the allowed "
1415                                "character is '>'"),
1416                              utf8_str (context->iter, buf),
1417                              close_name);
1418                 }
1419               else
1420                 {
1421                   GError *tmp_error;
1422                   advance_char (context);
1423                   context->state = STATE_AFTER_CLOSE_ANGLE;
1424                   context->start = NULL;
1425
1426                   /* call the end_element callback */
1427                   tmp_error = NULL;
1428                   if (context->parser->end_element)
1429                     (* context->parser->end_element) (context,
1430                                                       close_name,
1431                                                       context->user_data,
1432                                                       &tmp_error);
1433
1434                   
1435                   /* Pop the tag stack */
1436                   g_free (context->tag_stack->data);
1437                   context->tag_stack = g_slist_delete_link (context->tag_stack,
1438                                                             context->tag_stack);
1439                   
1440                   if (tmp_error)
1441                     {
1442                       mark_error (context, tmp_error);
1443                       g_propagate_error (error, tmp_error);
1444                     }
1445                 }
1446
1447               g_free (close_name);
1448             }
1449           break;
1450
1451         case STATE_INSIDE_PASSTHROUGH:
1452           /* Possible next state: AFTER_CLOSE_ANGLE */
1453           do
1454             {
1455               if (*context->iter == '>')
1456                 break;
1457             }
1458           while (advance_char (context));
1459
1460           if (context->iter == context->current_text_end)
1461             {
1462               /* The passthrough hasn't necessarily ended. Merge with
1463                * partial chunk, leave state unchanged.
1464                */
1465               add_to_partial (context, context->start, context->iter);
1466             }
1467           else
1468             {
1469               /* The passthrough has ended at the close angle. Combine
1470                * it with the partial chunk if any. Call the passthrough
1471                * callback. Note that the open/close angles are
1472                * included in the text of the passthrough.
1473                */
1474               GError *tmp_error = NULL;
1475
1476               advance_char (context); /* advance past close angle */
1477               add_to_partial (context, context->start, context->iter);
1478
1479               if (context->parser->passthrough)
1480                 (*context->parser->passthrough) (context,
1481                                                  context->partial_chunk->str,
1482                                                  context->partial_chunk->len,
1483                                                  context->user_data,
1484                                                  &tmp_error);
1485                   
1486               truncate_partial (context);
1487
1488               if (tmp_error == NULL)
1489                 {
1490                   context->state = STATE_AFTER_CLOSE_ANGLE;
1491                   context->start = context->iter; /* could begin text */
1492                 }
1493               else
1494                 {
1495                   mark_error (context, tmp_error);
1496                   g_propagate_error (error, tmp_error);
1497                 }
1498             }
1499           break;
1500
1501         case STATE_ERROR:
1502           goto finished;
1503           break;
1504
1505         default:
1506           g_assert_not_reached ();
1507           break;
1508         }
1509     }
1510
1511  finished:
1512   context->parsing = FALSE;
1513
1514   return context->state != STATE_ERROR;
1515 }
1516
1517 /**
1518  * g_markup_parse_context_end_parse:
1519  * @context: a #GMarkupParseContext
1520  * @error: return location for a #GError
1521  * 
1522  * Signals to the #GMarkupParseContext that all data has been
1523  * fed into the parse context with g_markup_parse_context_parse().
1524  * This function reports an error if the document isn't complete,
1525  * for example if elements are still open.
1526  * 
1527  * Return value: %TRUE on success, %FALSE if an error was set
1528  **/
1529 gboolean
1530 g_markup_parse_context_end_parse (GMarkupParseContext *context,
1531                                   GError             **error)
1532 {
1533   g_return_val_if_fail (context != NULL, FALSE);
1534   g_return_val_if_fail (!context->parsing, FALSE);
1535   g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
1536
1537   if (context->partial_chunk != NULL)
1538     {
1539       g_string_free (context->partial_chunk, TRUE);
1540       context->partial_chunk = NULL;
1541     }
1542
1543   if (context->document_empty)
1544     {
1545       set_error (context, error, G_MARKUP_ERROR_EMPTY,
1546                  _("Document was empty or contained only whitespace"));
1547       return FALSE;
1548     }
1549   
1550   context->parsing = TRUE;
1551   
1552   switch (context->state)
1553     {
1554     case STATE_START:
1555       /* Nothing to do */
1556       break;
1557
1558     case STATE_AFTER_OPEN_ANGLE:
1559       set_error (context, error, G_MARKUP_ERROR_PARSE,
1560                  _("Document ended unexpectedly just after an open angle bracket '<'"));
1561       break;
1562
1563     case STATE_AFTER_CLOSE_ANGLE:
1564       if (context->tag_stack != NULL)
1565         {
1566           /* Error message the same as for INSIDE_TEXT */
1567           set_error (context, error, G_MARKUP_ERROR_PARSE,
1568                      _("Document ended unexpectedly with elements still open - "
1569                        "'%s' was the last element opened"),
1570                      current_element (context));
1571         }
1572       break;
1573       
1574     case STATE_AFTER_ELISION_SLASH:
1575       set_error (context, error, G_MARKUP_ERROR_PARSE,
1576                  _("Document ended unexpectedly, expected to see a close angle "
1577                    "bracket ending the tag <%s/>"), current_element (context));
1578       break;
1579
1580     case STATE_INSIDE_OPEN_TAG_NAME:
1581       set_error (context, error, G_MARKUP_ERROR_PARSE,
1582                  _("Document ended unexpectedly inside an element name"));
1583       break;
1584
1585     case STATE_INSIDE_ATTRIBUTE_NAME:
1586       set_error (context, error, G_MARKUP_ERROR_PARSE,
1587                  _("Document ended unexpectedly inside an attribute name"));
1588       break;
1589
1590     case STATE_BETWEEN_ATTRIBUTES:
1591       set_error (context, error, G_MARKUP_ERROR_PARSE,
1592                  _("Document ended unexpectedly inside an element-opening "
1593                    "tag."));
1594       break;
1595
1596     case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1597       set_error (context, error, G_MARKUP_ERROR_PARSE,
1598                  _("Document ended unexpectedly after the equals sign "
1599                    "following an attribute name; no attribute value"));
1600       break;
1601
1602     case STATE_INSIDE_ATTRIBUTE_VALUE:
1603       set_error (context, error, G_MARKUP_ERROR_PARSE,
1604                  _("Document ended unexpectedly while inside an attribute "
1605                    "value"));
1606       break;
1607
1608     case STATE_INSIDE_TEXT:
1609       g_assert (context->tag_stack != NULL);
1610       set_error (context, error, G_MARKUP_ERROR_PARSE,
1611                  _("Document ended unexpectedly with elements still open - "
1612                    "'%s' was the last element opened"),
1613                  current_element (context));
1614       break;
1615
1616     case STATE_AFTER_CLOSE_TAG_SLASH:
1617     case STATE_INSIDE_CLOSE_TAG_NAME:
1618       set_error (context, error, G_MARKUP_ERROR_PARSE,
1619                  _("Document ended unexpectedly inside the close tag for "
1620                    "element '%s'"), current_element);
1621       break;
1622
1623     case STATE_INSIDE_PASSTHROUGH:
1624       set_error (context, error, G_MARKUP_ERROR_PARSE,
1625                  _("Document ended unexpectedly inside a comment or "
1626                    "processing instruction"));
1627       break;
1628
1629     case STATE_ERROR:
1630     default:
1631       g_assert_not_reached ();
1632       break;
1633     }
1634
1635   context->parsing = FALSE;
1636
1637   return context->state != STATE_ERROR;
1638 }
1639
1640 /**
1641  * g_markup_parse_context_get_position:
1642  * @context: a #GMarkupParseContext
1643  * @line_number: return location for a line number, or %NULL
1644  * @char_number: return location for a char-on-line number, or %NULL
1645  *
1646  * Retrieves the current line number and the number of the character on
1647  * that line. Intended for use in error messages; there are no strict
1648  * semantics for what constitutes the "current" line number other than
1649  * "the best number we could come up with for error messages."
1650  * 
1651  **/
1652 void
1653 g_markup_parse_context_get_position (GMarkupParseContext *context,
1654                                      gint                *line_number,
1655                                      gint                *char_number)
1656 {
1657   g_return_if_fail (context != NULL);
1658
1659   if (line_number)
1660     *line_number = context->line_number;
1661
1662   if (char_number)
1663     *char_number = context->char_number;
1664 }
1665
1666 static void
1667 append_escaped_text (GString     *str,
1668                      const gchar *text,
1669                      gssize       length)    
1670 {
1671   const gchar *p;
1672   const gchar *end;
1673
1674   p = text;
1675   end = text + length;
1676
1677   while (p != end)
1678     {
1679       const gchar *next;
1680       next = g_utf8_next_char (p);
1681
1682       switch (*p)
1683         {
1684         case '&':
1685           g_string_append (str, "&amp;");
1686           break;
1687
1688         case '<':
1689           g_string_append (str, "&lt;");
1690           break;
1691
1692         case '>':
1693           g_string_append (str, "&gt;");
1694           break;
1695
1696         case '\'':
1697           g_string_append (str, "&apos;");
1698           break;
1699
1700         case '"':
1701           g_string_append (str, "&quot;");
1702           break;
1703
1704         default:
1705           g_string_append_len (str, p, next - p);
1706           break;
1707         }
1708
1709       p = next;
1710     }
1711 }
1712
1713 /**
1714  * g_markup_escape_text:
1715  * @text: some valid UTF-8 text
1716  * @length: length of @text in bytes
1717  * 
1718  * Escapes text so that the markup parser will parse it verbatim.
1719  * Less than, greater than, ampersand, etc. are replaced with the
1720  * corresponding entities. This function would typically be used
1721  * when writing out a file to be parsed with the markup parser.
1722  * 
1723  * Return value: escaped text
1724  **/
1725 gchar*
1726 g_markup_escape_text (const gchar *text,
1727                       gssize       length)  
1728 {
1729   GString *str;
1730
1731   g_return_val_if_fail (text != NULL, NULL);
1732
1733   if (length < 0)
1734     length = strlen (text);
1735
1736   str = g_string_new ("");
1737   append_escaped_text (str, text, length);
1738
1739   return g_string_free (str, FALSE);
1740 }