Fix mispelling in help output. (#53952, Skip Montanaro)
[platform/upstream/glib.git] / 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   gint         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,
544                                              char_str (l, buf));
545                           }
546                         else
547                           {
548                             set_unescape_error (context, error,
549                                                 start, text_end,
550                                                 G_MARKUP_ERROR_PARSE,
551                                                 _("Character reference '%s' does not encode a permitted character"),
552                                                 digit);
553                           }
554                       }
555
556                     g_free (digit);
557
558                     /* Move to next state */
559                     p = g_utf8_next_char (p); /* past semicolon */
560                     start = p;
561                     state = USTATE_INSIDE_TEXT;
562                   }
563                 else
564                   {
565                     set_unescape_error (context, error,
566                                         start, text_end,
567                                         G_MARKUP_ERROR_PARSE,
568                                         _("Empty character reference; "
569                                           "should include a digit such as "
570                                           "&#454;"));
571                   }
572               }
573             else
574               {
575                 set_unescape_error (context, error,
576                                     start, text_end,
577                                     G_MARKUP_ERROR_PARSE,
578                                     _("Character reference did not end with a "
579                                       "semicolon; "
580                                       "most likely you used an ampersand "
581                                       "character without intending to start "
582                                       "an entity - escape ampersand as &amp;"));
583               }
584           }
585           break;
586
587         default:
588           g_assert_not_reached ();
589           break;
590         }
591     }
592
593   /* If no errors, we should have returned to USTATE_INSIDE_TEXT */
594   g_assert (context->state == STATE_ERROR ||
595             state == USTATE_INSIDE_TEXT);
596
597   if (context->state == STATE_ERROR)
598     {
599       g_string_free (str, TRUE);
600       *unescaped = NULL;
601       return FALSE;
602     }
603   else
604     {
605       *unescaped = g_string_free (str, FALSE);
606       return TRUE;
607     }
608
609 #undef MAX_ENT_LEN
610 }
611
612 static gboolean
613 advance_char (GMarkupParseContext *context)
614 {
615
616   context->iter = g_utf8_next_char (context->iter);
617   context->char_number += 1;
618   if (*context->iter == '\n')
619     {
620       context->line_number += 1;
621       context->char_number = 1;
622     }
623
624   return context->iter != context->current_text_end;
625 }
626
627 static void
628 skip_spaces (GMarkupParseContext *context)
629 {
630   do
631     {
632       if (!g_unichar_isspace (g_utf8_get_char (context->iter)))
633         return;
634     }
635   while (advance_char (context));
636 }
637
638 static void
639 advance_to_name_end (GMarkupParseContext *context)
640 {
641   do
642     {
643       if (!is_name_char (g_utf8_get_char (context->iter)))
644         return;
645     }
646   while (advance_char (context));
647 }
648
649 static void
650 add_to_partial (GMarkupParseContext *context,
651                 const gchar         *text_start,
652                 const gchar         *text_end)
653 {
654   if (context->partial_chunk == NULL)
655     context->partial_chunk = g_string_new ("");
656
657   if (text_start != text_end)
658     g_string_append_len (context->partial_chunk, text_start,
659                          text_end - text_start);
660
661   /* Invariant here that partial_chunk exists */
662 }
663
664 static void
665 truncate_partial (GMarkupParseContext *context)
666 {
667   if (context->partial_chunk != NULL)
668     {
669       context->partial_chunk = g_string_truncate (context->partial_chunk, 0);
670     }
671 }
672
673 static const gchar*
674 current_element (GMarkupParseContext *context)
675 {
676   return context->tag_stack->data;
677 }
678
679 static const gchar*
680 current_attribute (GMarkupParseContext *context)
681 {
682   g_assert (context->cur_attr >= 0);
683   return context->attr_names[context->cur_attr];
684 }
685
686 static void
687 find_current_text_end (GMarkupParseContext *context)
688 {
689   /* This function must be safe (non-segfaulting) on invalid UTF8 */
690   const gchar *end = context->current_text + context->current_text_len;
691   const gchar *p;
692   const gchar *next;
693
694   g_assert (context->current_text_len > 0);
695
696   p = context->current_text;
697   next = g_utf8_find_next_char (p, end);
698
699   while (next)
700     {
701       p = next;
702       next = g_utf8_find_next_char (p, end);
703     }
704
705   /* p is now the start of the last character or character portion. */
706   g_assert (p != end);
707   next = g_utf8_next_char (p); /* this only touches *p, nothing beyond */
708
709   if (next == end)
710     {
711       /* whole character */
712       context->current_text_end = end;
713     }
714   else
715     {
716       /* portion */
717       context->leftover_char_portion = g_string_new_len (p, end - p);
718       context->current_text_len -= (end - p);
719       context->current_text_end = p;
720     }
721 }
722
723 static void
724 add_attribute (GMarkupParseContext *context, char *name)
725 {
726   if (context->cur_attr + 2 >= context->alloc_attrs)
727     {
728       context->alloc_attrs += 5; /* silly magic number */
729       context->attr_names = g_realloc (context->attr_names, sizeof(char*)*context->alloc_attrs);
730       context->attr_values = g_realloc (context->attr_values, sizeof(char*)*context->alloc_attrs);
731     }
732   context->cur_attr++;
733   context->attr_names[context->cur_attr] = name;
734   context->attr_values[context->cur_attr] = NULL;
735   context->attr_names[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                               gint                 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                   context->cur_attr = -1;
1201
1202                   if (tmp_error != NULL)
1203                     {
1204                       mark_error (context, tmp_error);
1205                       g_propagate_error (error, tmp_error);
1206                     }
1207                 }
1208             }
1209           break;
1210
1211         case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1212           /* Possible next state: INSIDE_ATTRIBUTE_VALUE */
1213           if (*context->iter == '"')
1214             {
1215               advance_char (context);
1216               context->state = STATE_INSIDE_ATTRIBUTE_VALUE;
1217               context->start = context->iter;
1218             }
1219           else
1220             {
1221               gchar buf[7];
1222               set_error (context,
1223                          error,
1224                          G_MARKUP_ERROR_PARSE,
1225                          _("Odd character '%s', expected an open quote mark "
1226                            "after the equals sign when giving value for "
1227                            "attribute '%s' of element '%s'"),
1228                          utf8_str (context->iter, buf),
1229                          current_attribute (context),
1230                          current_element (context));
1231             }
1232           break;
1233
1234         case STATE_INSIDE_ATTRIBUTE_VALUE:
1235           /* Possible next states: BETWEEN_ATTRIBUTES */
1236           do
1237             {
1238               if (*context->iter == '"')
1239                 break;
1240             }
1241           while (advance_char (context));
1242
1243           if (context->iter == context->current_text_end)
1244             {
1245               /* The value hasn't necessarily ended. Merge with
1246                * partial chunk, leave state unchanged.
1247                */
1248               add_to_partial (context, context->start, context->iter);
1249             }
1250           else
1251             {
1252               /* The value has ended at the quote mark. Combine it
1253                * with the partial chunk if any; set it for the current
1254                * attribute.
1255                */
1256               add_to_partial (context, context->start, context->iter);
1257
1258               g_assert (context->cur_attr >= 0);
1259               
1260               if (unescape_text (context,
1261                                  context->partial_chunk->str,
1262                                  context->partial_chunk->str +
1263                                  context->partial_chunk->len,
1264                                  &context->attr_values[context->cur_attr],
1265                                  error))
1266                 {
1267                   /* success, advance past quote and set state. */
1268                   advance_char (context);
1269                   context->state = STATE_BETWEEN_ATTRIBUTES;
1270                   context->start = NULL;
1271                 }
1272               
1273               truncate_partial (context);
1274             }
1275           break;
1276
1277         case STATE_INSIDE_TEXT:
1278           /* Possible next states: AFTER_OPEN_ANGLE */
1279           do
1280             {
1281               if (*context->iter == '<')
1282                 break;
1283             }
1284           while (advance_char (context));
1285
1286           /* The text hasn't necessarily ended. Merge with
1287            * partial chunk, leave state unchanged.
1288            */
1289
1290           add_to_partial (context, context->start, context->iter);
1291
1292           if (context->iter != context->current_text_end)
1293             {
1294               gchar *unescaped = NULL;
1295
1296               /* The text has ended at the open angle. Call the text
1297                * callback.
1298                */
1299               
1300               if (unescape_text (context,
1301                                  context->partial_chunk->str,
1302                                  context->partial_chunk->str +
1303                                  context->partial_chunk->len,
1304                                  &unescaped,
1305                                  error))
1306                 {
1307                   GError *tmp_error = NULL;
1308
1309                   if (context->parser->text)
1310                     (*context->parser->text) (context,
1311                                               unescaped,
1312                                               strlen (unescaped),
1313                                               context->user_data,
1314                                               &tmp_error);
1315                   
1316                   g_free (unescaped);
1317
1318                   if (tmp_error == NULL)
1319                     {
1320                       /* advance past open angle and set state. */
1321                       advance_char (context);
1322                       context->state = STATE_AFTER_OPEN_ANGLE;
1323                       /* could begin a passthrough */
1324                       context->start = context->iter;
1325                     }
1326                   else
1327                     {
1328                       mark_error (context, tmp_error);
1329                       g_propagate_error (error, tmp_error);
1330                     }
1331                 }
1332
1333               truncate_partial (context);
1334             }
1335           break;
1336
1337         case STATE_AFTER_CLOSE_TAG_SLASH:
1338           /* Possible next state: INSIDE_CLOSE_TAG_NAME */
1339           if (is_name_start_char (g_utf8_get_char (context->iter)))
1340             {
1341               context->state = STATE_INSIDE_CLOSE_TAG_NAME;
1342
1343               /* start of tag name */
1344               context->start = context->iter;
1345             }
1346           else
1347             {
1348               gchar buf[7];
1349               set_error (context,
1350                          error,
1351                          G_MARKUP_ERROR_PARSE,
1352                          _("'%s' is not a valid character following "
1353                            "the characters '</'; '%s' may not begin an "
1354                            "element name"),
1355                          utf8_str (context->iter, buf),
1356                          utf8_str (context->iter, buf));
1357             }
1358           break;
1359
1360         case STATE_INSIDE_CLOSE_TAG_NAME:
1361           /* Possible next state: AFTER_CLOSE_ANGLE */
1362           advance_to_name_end (context);
1363
1364           if (context->iter == context->current_text_end)
1365             {
1366               /* The name hasn't necessarily ended. Merge with
1367                * partial chunk, leave state unchanged.
1368                */
1369               add_to_partial (context, context->start, context->iter);
1370             }
1371           else
1372             {
1373               /* The name has ended. Combine it with the partial chunk
1374                * if any; check that it matches stack top and pop
1375                * stack; invoke proper callback; enter next state.
1376                */
1377               gchar *close_name;
1378
1379               add_to_partial (context, context->start, context->iter);
1380
1381               close_name = g_string_free (context->partial_chunk, FALSE);
1382               context->partial_chunk = NULL;
1383               
1384               if (context->tag_stack == NULL)
1385                 {
1386                   set_error (context,
1387                              error,
1388                              G_MARKUP_ERROR_PARSE,
1389                              _("Element '%s' was closed, no element "
1390                                "is currently open"),
1391                              close_name);
1392                 }
1393               else if (strcmp (close_name, current_element (context)) != 0)
1394                 {
1395                   set_error (context,
1396                              error,
1397                              G_MARKUP_ERROR_PARSE,
1398                              _("Element '%s' was closed, but the currently "
1399                                "open element is '%s'"),
1400                              close_name,
1401                              current_element (context));
1402                 }
1403               else if (*context->iter != '>')
1404                 {
1405                   gchar buf[7];
1406                   set_error (context,
1407                              error,
1408                              G_MARKUP_ERROR_PARSE,
1409                              _("'%s' is not a valid character following "
1410                                "the close element name '%s'; the allowed "
1411                                "character is '>'"),
1412                              utf8_str (context->iter, buf),
1413                              close_name);
1414                 }
1415               else
1416                 {
1417                   GError *tmp_error;
1418                   advance_char (context);
1419                   context->state = STATE_AFTER_CLOSE_ANGLE;
1420                   context->start = NULL;
1421
1422                   /* call the end_element callback */
1423                   tmp_error = NULL;
1424                   if (context->parser->end_element)
1425                     (* context->parser->end_element) (context,
1426                                                       close_name,
1427                                                       context->user_data,
1428                                                       &tmp_error);
1429
1430                   
1431                   /* Pop the tag stack */
1432                   g_free (context->tag_stack->data);
1433                   context->tag_stack = g_slist_delete_link (context->tag_stack,
1434                                                             context->tag_stack);
1435                   
1436                   if (tmp_error)
1437                     {
1438                       mark_error (context, tmp_error);
1439                       g_propagate_error (error, tmp_error);
1440                     }
1441                 }
1442
1443               g_free (close_name);
1444             }
1445           break;
1446
1447         case STATE_INSIDE_PASSTHROUGH:
1448           /* Possible next state: AFTER_CLOSE_ANGLE */
1449           do
1450             {
1451               if (*context->iter == '>')
1452                 break;
1453             }
1454           while (advance_char (context));
1455
1456           if (context->iter == context->current_text_end)
1457             {
1458               /* The passthrough hasn't necessarily ended. Merge with
1459                * partial chunk, leave state unchanged.
1460                */
1461               add_to_partial (context, context->start, context->iter);
1462             }
1463           else
1464             {
1465               /* The passthrough has ended at the close angle. Combine
1466                * it with the partial chunk if any. Call the passthrough
1467                * callback. Note that the open/close angles are
1468                * included in the text of the passthrough.
1469                */
1470               GError *tmp_error = NULL;
1471
1472               advance_char (context); /* advance past close angle */
1473               add_to_partial (context, context->start, context->iter);
1474
1475               if (context->parser->passthrough)
1476                 (*context->parser->passthrough) (context,
1477                                                  context->partial_chunk->str,
1478                                                  context->partial_chunk->len,
1479                                                  context->user_data,
1480                                                  &tmp_error);
1481                   
1482               truncate_partial (context);
1483
1484               if (tmp_error == NULL)
1485                 {
1486                   context->state = STATE_AFTER_CLOSE_ANGLE;
1487                   context->start = context->iter; /* could begin text */
1488                 }
1489               else
1490                 {
1491                   mark_error (context, tmp_error);
1492                   g_propagate_error (error, tmp_error);
1493                 }
1494             }
1495           break;
1496
1497         case STATE_ERROR:
1498           goto finished;
1499           break;
1500
1501         default:
1502           g_assert_not_reached ();
1503           break;
1504         }
1505     }
1506
1507  finished:
1508   context->parsing = FALSE;
1509
1510   return context->state != STATE_ERROR;
1511 }
1512
1513 /**
1514  * g_markup_parse_context_end_parse:
1515  * @context: a #GMarkupParseContext
1516  * @error: return location for a #GError
1517  * 
1518  * Signals to the #GMarkupParseContext that all data has been
1519  * fed into the parse context with g_markup_parse_context_parse().
1520  * This function reports an error if the document isn't complete,
1521  * for example if elements are still open.
1522  * 
1523  * Return value: %TRUE on success, %FALSE if an error was set
1524  **/
1525 gboolean
1526 g_markup_parse_context_end_parse (GMarkupParseContext *context,
1527                                   GError             **error)
1528 {
1529   g_return_val_if_fail (context != NULL, FALSE);
1530   g_return_val_if_fail (!context->parsing, FALSE);
1531   g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
1532
1533   if (context->partial_chunk != NULL)
1534     {
1535       g_string_free (context->partial_chunk, TRUE);
1536       context->partial_chunk = NULL;
1537     }
1538
1539   if (context->document_empty)
1540     {
1541       set_error (context, error, G_MARKUP_ERROR_EMPTY,
1542                  _("Document was empty or contained only whitespace"));
1543       return FALSE;
1544     }
1545   
1546   context->parsing = TRUE;
1547   
1548   switch (context->state)
1549     {
1550     case STATE_START:
1551       /* Nothing to do */
1552       break;
1553
1554     case STATE_AFTER_OPEN_ANGLE:
1555       set_error (context, error, G_MARKUP_ERROR_PARSE,
1556                  _("Document ended unexpectedly just after an open angle bracket '<'"));
1557       break;
1558
1559     case STATE_AFTER_CLOSE_ANGLE:
1560       if (context->tag_stack != NULL)
1561         {
1562           /* Error message the same as for INSIDE_TEXT */
1563           set_error (context, error, G_MARKUP_ERROR_PARSE,
1564                      _("Document ended unexpectedly with elements still open - "
1565                        "'%s' was the last element opened"),
1566                      current_element (context));
1567         }
1568       break;
1569       
1570     case STATE_AFTER_ELISION_SLASH:
1571       set_error (context, error, G_MARKUP_ERROR_PARSE,
1572                  _("Document ended unexpectedly, expected to see a close angle "
1573                    "bracket ending the tag <%s/>"), current_element (context));
1574       break;
1575
1576     case STATE_INSIDE_OPEN_TAG_NAME:
1577       set_error (context, error, G_MARKUP_ERROR_PARSE,
1578                  _("Document ended unexpectedly inside an element name"));
1579       break;
1580
1581     case STATE_INSIDE_ATTRIBUTE_NAME:
1582       set_error (context, error, G_MARKUP_ERROR_PARSE,
1583                  _("Document ended unexpectedly inside an attribute name"));
1584       break;
1585
1586     case STATE_BETWEEN_ATTRIBUTES:
1587       set_error (context, error, G_MARKUP_ERROR_PARSE,
1588                  _("Document ended unexpectedly inside an element-opening "
1589                    "tag."));
1590       break;
1591
1592     case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1593       set_error (context, error, G_MARKUP_ERROR_PARSE,
1594                  _("Document ended unexpectedly after the equals sign "
1595                    "following an attribute name; no attribute value"));
1596       break;
1597
1598     case STATE_INSIDE_ATTRIBUTE_VALUE:
1599       set_error (context, error, G_MARKUP_ERROR_PARSE,
1600                  _("Document ended unexpectedly while inside an attribute "
1601                    "value"));
1602       break;
1603
1604     case STATE_INSIDE_TEXT:
1605       g_assert (context->tag_stack != NULL);
1606       set_error (context, error, G_MARKUP_ERROR_PARSE,
1607                  _("Document ended unexpectedly with elements still open - "
1608                    "'%s' was the last element opened"),
1609                  current_element (context));
1610       break;
1611
1612     case STATE_AFTER_CLOSE_TAG_SLASH:
1613     case STATE_INSIDE_CLOSE_TAG_NAME:
1614       set_error (context, error, G_MARKUP_ERROR_PARSE,
1615                  _("Document ended unexpectedly inside the close tag for"
1616                    "element '%s'"), current_element);
1617       break;
1618
1619     case STATE_INSIDE_PASSTHROUGH:
1620       set_error (context, error, G_MARKUP_ERROR_PARSE,
1621                  _("Document ended unexpectedly inside a comment or "
1622                    "processing instruction"));
1623       break;
1624
1625     case STATE_ERROR:
1626     default:
1627       g_assert_not_reached ();
1628       break;
1629     }
1630
1631   context->parsing = FALSE;
1632
1633   return context->state != STATE_ERROR;
1634 }
1635
1636 /**
1637  * g_markup_parse_context_get_position:
1638  * @context: a #GMarkupParseContext
1639  * @line_number: return location for a line number, or %NULL
1640  * @char_number: return location for a char-on-line number, or %NULL
1641  *
1642  * Retrieves the current line number and the number of the character on
1643  * that line. Intended for use in error messages; there are no strict
1644  * semantics for what constitutes the "current" line number other than
1645  * "the best number we could come up with for error messages."
1646  * 
1647  **/
1648 void
1649 g_markup_parse_context_get_position (GMarkupParseContext *context,
1650                                      gint                *line_number,
1651                                      gint                *char_number)
1652 {
1653   g_return_if_fail (context != NULL);
1654
1655   if (line_number)
1656     *line_number = context->line_number;
1657
1658   if (char_number)
1659     *char_number = context->char_number;
1660 }
1661
1662 static void
1663 append_escaped_text (GString     *str,
1664                      const gchar *text,
1665                      gint         length)
1666 {
1667   const gchar *p;
1668   const gchar *end;
1669
1670   p = text;
1671   end = text + length;
1672
1673   while (p != end)
1674     {
1675       const gchar *next;
1676       next = g_utf8_next_char (p);
1677
1678       switch (*p)
1679         {
1680         case '&':
1681           g_string_append (str, "&amp;");
1682           break;
1683
1684         case '<':
1685           g_string_append (str, "&lt;");
1686           break;
1687
1688         case '>':
1689           g_string_append (str, "&gt;");
1690           break;
1691
1692         case '\'':
1693           g_string_append (str, "&apos;");
1694           break;
1695
1696         case '"':
1697           g_string_append (str, "&quot;");
1698           break;
1699
1700         default:
1701           g_string_append_len (str, p, next - p);
1702           break;
1703         }
1704
1705       p = next;
1706     }
1707 }
1708
1709 /**
1710  * g_markup_escape_text:
1711  * @text: some valid UTF-8 text
1712  * @length: length of @text in bytes
1713  * 
1714  * Escapes text so that the markup parser will parse it verbatim.
1715  * Less than, greater than, ampersand, etc. are replaced with the
1716  * corresponding entities. This function would typically be used
1717  * when writing out a file to be parsed with the markup parser.
1718  * 
1719  * Return value: escaped text
1720  **/
1721 gchar*
1722 g_markup_escape_text (const gchar *text,
1723                       gint         length)
1724 {
1725   GString *str;
1726
1727   g_return_val_if_fail (text != NULL, NULL);
1728
1729   if (length < 0)
1730     length = strlen (text);
1731
1732   str = g_string_new ("");
1733   append_escaped_text (str, text, length);
1734
1735   return g_string_free (str, FALSE);
1736 }