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