1.3.14
[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 no errors, we should have returned to USTATE_INSIDE_TEXT */
596   g_assert (context->state == STATE_ERROR ||
597             state == USTATE_INSIDE_TEXT);
598
599   if (context->state == STATE_ERROR)
600     {
601       g_string_free (str, TRUE);
602       *unescaped = NULL;
603       return FALSE;
604     }
605   else
606     {
607       *unescaped = g_string_free (str, FALSE);
608       return TRUE;
609     }
610
611 #undef MAX_ENT_LEN
612 }
613
614 static gboolean
615 advance_char (GMarkupParseContext *context)
616 {
617
618   context->iter = g_utf8_next_char (context->iter);
619   context->char_number += 1;
620   if (*context->iter == '\n')
621     {
622       context->line_number += 1;
623       context->char_number = 1;
624     }
625
626   return context->iter != context->current_text_end;
627 }
628
629 static gboolean
630 xml_isspace (char c)
631 {
632   return c == ' ' || c == '\t' || c == '\n' || c == '\r';
633 }
634
635 static void
636 skip_spaces (GMarkupParseContext *context)
637 {
638   do
639     {
640       if (!xml_isspace (*context->iter))
641         return;
642     }
643   while (advance_char (context));
644 }
645
646 static void
647 advance_to_name_end (GMarkupParseContext *context)
648 {
649   do
650     {
651       if (!is_name_char (g_utf8_get_char (context->iter)))
652         return;
653     }
654   while (advance_char (context));
655 }
656
657 static void
658 add_to_partial (GMarkupParseContext *context,
659                 const gchar         *text_start,
660                 const gchar         *text_end)
661 {
662   if (context->partial_chunk == NULL)
663     context->partial_chunk = g_string_new ("");
664
665   if (text_start != text_end)
666     g_string_append_len (context->partial_chunk, text_start,
667                          text_end - text_start);
668
669   /* Invariant here that partial_chunk exists */
670 }
671
672 static void
673 truncate_partial (GMarkupParseContext *context)
674 {
675   if (context->partial_chunk != NULL)
676     {
677       context->partial_chunk = g_string_truncate (context->partial_chunk, 0);
678     }
679 }
680
681 static const gchar*
682 current_element (GMarkupParseContext *context)
683 {
684   return context->tag_stack->data;
685 }
686
687 static const gchar*
688 current_attribute (GMarkupParseContext *context)
689 {
690   g_assert (context->cur_attr >= 0);
691   return context->attr_names[context->cur_attr];
692 }
693
694 static void
695 find_current_text_end (GMarkupParseContext *context)
696 {
697   /* This function must be safe (non-segfaulting) on invalid UTF8 */
698   const gchar *end = context->current_text + context->current_text_len;
699   const gchar *p;
700   const gchar *next;
701
702   g_assert (context->current_text_len > 0);
703
704   p = context->current_text;
705   next = g_utf8_find_next_char (p, end);
706
707   while (next)
708     {
709       p = next;
710       next = g_utf8_find_next_char (p, end);
711     }
712
713   /* p is now the start of the last character or character portion. */
714   g_assert (p != end);
715   next = g_utf8_next_char (p); /* this only touches *p, nothing beyond */
716
717   if (next == end)
718     {
719       /* whole character */
720       context->current_text_end = end;
721     }
722   else
723     {
724       /* portion */
725       context->leftover_char_portion = g_string_new_len (p, end - p);
726       context->current_text_len -= (end - p);
727       context->current_text_end = p;
728     }
729 }
730
731 static void
732 add_attribute (GMarkupParseContext *context, char *name)
733 {
734   if (context->cur_attr + 2 >= context->alloc_attrs)
735     {
736       context->alloc_attrs += 5; /* silly magic number */
737       context->attr_names = g_realloc (context->attr_names, sizeof(char*)*context->alloc_attrs);
738       context->attr_values = g_realloc (context->attr_values, sizeof(char*)*context->alloc_attrs);
739     }
740   context->cur_attr++;
741   context->attr_names[context->cur_attr] = name;
742   context->attr_values[context->cur_attr] = NULL;
743   context->attr_names[context->cur_attr+1] = NULL;
744   context->attr_values[context->cur_attr+1] = NULL;
745 }
746
747 /**
748  * g_markup_parse_context_parse:
749  * @context: a #GMarkupParseContext
750  * @text: chunk of text to parse
751  * @text_len: length of @text in bytes
752  * @error: return location for a #GError
753  * 
754  * Feed some data to the #GMarkupParseContext. The data need not
755  * be valid UTF-8; an error will be signaled if it's invalid.
756  * The data need not be an entire document; you can feed a document
757  * into the parser incrementally, via multiple calls to this function.
758  * Typically, as you receive data from a network connection or file,
759  * you feed each received chunk of data into this function, aborting
760  * the process if an error occurs. Once an error is reported, no further
761  * data may be fed to the #GMarkupParseContext; all errors are fatal.
762  * 
763  * Return value: %FALSE if an error occurred, %TRUE on success
764  **/
765 gboolean
766 g_markup_parse_context_parse (GMarkupParseContext *context,
767                               const gchar         *text,
768                               gssize               text_len,
769                               GError             **error)
770 {
771   const gchar *first_invalid;
772   
773   g_return_val_if_fail (context != NULL, FALSE);
774   g_return_val_if_fail (text != NULL, FALSE);
775   g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
776   g_return_val_if_fail (!context->parsing, FALSE);
777   
778   if (text_len < 0)
779     text_len = strlen (text);
780
781   if (text_len == 0)
782     return TRUE;
783   
784   context->parsing = TRUE;
785   
786   if (context->leftover_char_portion)
787     {
788       const gchar *first_char;
789
790       if ((*text & 0xc0) != 0x80)
791         first_char = text;
792       else
793         first_char = g_utf8_find_next_char (text, text + text_len);
794
795       if (first_char)
796         {
797           /* leftover_char_portion was completed. Parse it. */
798           GString *portion = context->leftover_char_portion;
799           
800           g_string_append_len (context->leftover_char_portion,
801                                text, first_char - text);
802
803           /* hacks to allow recursion */
804           context->parsing = FALSE;
805           context->leftover_char_portion = NULL;
806           
807           if (!g_markup_parse_context_parse (context,
808                                              portion->str, portion->len,
809                                              error))
810             {
811               g_assert (context->state == STATE_ERROR);
812             }
813           
814           g_string_free (portion, TRUE);
815           context->parsing = TRUE;
816
817           /* Skip the fraction of char that was in this text */
818           text_len -= (first_char - text);
819           text = first_char;
820         }
821       else
822         {
823           /* another little chunk of the leftover char; geez
824            * someone is inefficient.
825            */
826           g_string_append_len (context->leftover_char_portion,
827                                text, text_len);
828
829           if (context->leftover_char_portion->len > 7)
830             {
831               /* The leftover char portion is too big to be
832                * a UTF-8 character
833                */
834               set_error (context,
835                          error,
836                          G_MARKUP_ERROR_BAD_UTF8,
837                          _("Invalid UTF-8 encoded text"));
838             }
839           
840           goto finished;
841         }
842     }
843
844   context->current_text = text;
845   context->current_text_len = text_len;
846   context->iter = context->current_text;
847   context->start = context->iter;
848
849   /* Nothing left after finishing the leftover char, or nothing
850    * passed in to begin with.
851    */
852   if (context->current_text_len == 0)
853     goto finished;
854
855   /* find_current_text_end () assumes the string starts at
856    * a character start, so we need to validate at least
857    * that much. It doesn't assume any following bytes
858    * are valid.
859    */
860   if ((*context->current_text & 0xc0) == 0x80) /* not a char start */
861     {
862       set_error (context,
863                  error,
864                  G_MARKUP_ERROR_BAD_UTF8,
865                  _("Invalid UTF-8 encoded text"));
866       goto finished;
867     }
868
869   /* Initialize context->current_text_end, possibly adjusting
870    * current_text_len, and add any leftover char portion
871    */
872   find_current_text_end (context);
873
874   /* Validate UTF8 (must be done after we find the end, since
875    * we could have a trailing incomplete char)
876    */
877   if (!g_utf8_validate (context->current_text,
878                         context->current_text_len,
879                         &first_invalid))
880     {
881       gint newlines = 0;
882       const gchar *p;
883       p = context->current_text;
884       while (p != context->current_text_end)
885         {
886           if (*p == '\n')
887             ++newlines;
888           ++p;
889         }
890
891       context->line_number += newlines;
892
893       set_error (context,
894                  error,
895                  G_MARKUP_ERROR_BAD_UTF8,
896                  _("Invalid UTF-8 encoded text"));
897       goto finished;
898     }
899
900   while (context->iter != context->current_text_end)
901     {
902       switch (context->state)
903         {
904         case STATE_START:
905           /* Possible next state: AFTER_OPEN_ANGLE */
906
907           g_assert (context->tag_stack == NULL);
908
909           /* whitespace is ignored outside of any elements */
910           skip_spaces (context);
911
912           if (context->iter != context->current_text_end)
913             {
914               if (*context->iter == '<')
915                 {
916                   /* Move after the open angle */
917                   advance_char (context);
918
919                   context->state = STATE_AFTER_OPEN_ANGLE;
920
921                   /* this could start a passthrough */
922                   context->start = context->iter;
923
924                   /* document is now non-empty */
925                   context->document_empty = FALSE;
926                 }
927               else
928                 {
929                   set_error (context,
930                              error,
931                              G_MARKUP_ERROR_PARSE,
932                              _("Document must begin with an element (e.g. <book>)"));
933                 }
934             }
935           break;
936
937         case STATE_AFTER_OPEN_ANGLE:
938           /* Possible next states: INSIDE_OPEN_TAG_NAME,
939            *  AFTER_CLOSE_TAG_SLASH, INSIDE_PASSTHROUGH
940            */
941           if (*context->iter == '?' ||
942               *context->iter == '!')
943             {
944               /* include < in the passthrough */
945               const gchar *openangle = "<";
946               add_to_partial (context, openangle, openangle + 1);
947               context->start = context->iter;
948               context->state = STATE_INSIDE_PASSTHROUGH;
949             }
950           else if (*context->iter == '/')
951             {
952               /* move after it */
953               advance_char (context);
954
955               context->state = STATE_AFTER_CLOSE_TAG_SLASH;
956             }
957           else if (is_name_start_char (g_utf8_get_char (context->iter)))
958             {
959               context->state = STATE_INSIDE_OPEN_TAG_NAME;
960
961               /* start of tag name */
962               context->start = context->iter;
963             }
964           else
965             {
966               gchar buf[7];
967               set_error (context,
968                          error,
969                          G_MARKUP_ERROR_PARSE,
970                          _("'%s' is not a valid character following "
971                            "a '<' character; it may not begin an "
972                            "element name"),
973                          utf8_str (context->iter, buf));
974             }
975           break;
976
977           /* The AFTER_CLOSE_ANGLE state is actually sort of
978            * broken, because it doesn't correspond to a range
979            * of characters in the input stream as the others do,
980            * and thus makes things harder to conceptualize
981            */
982         case STATE_AFTER_CLOSE_ANGLE:
983           /* Possible next states: INSIDE_TEXT, STATE_START */
984           if (context->tag_stack == NULL)
985             {
986               context->start = NULL;
987               context->state = STATE_START;
988             }
989           else
990             {
991               context->start = context->iter;
992               context->state = STATE_INSIDE_TEXT;
993             }
994           break;
995
996         case STATE_AFTER_ELISION_SLASH:
997           /* Possible next state: AFTER_CLOSE_ANGLE */
998
999           {
1000             /* We need to pop the tag stack and call the end_element
1001              * function, since this is the close tag
1002              */
1003             GError *tmp_error = NULL;
1004           
1005             g_assert (context->tag_stack != NULL);
1006
1007             tmp_error = NULL;
1008             if (context->parser->end_element)
1009               (* context->parser->end_element) (context,
1010                                                 context->tag_stack->data,
1011                                                 context->user_data,
1012                                                 &tmp_error);
1013           
1014             if (tmp_error)
1015               {
1016                 mark_error (context, tmp_error);
1017                 g_propagate_error (error, tmp_error);
1018               }          
1019             else
1020               {
1021                 if (*context->iter == '>')
1022                   {
1023                     /* move after the close angle */
1024                     advance_char (context);
1025                     context->state = STATE_AFTER_CLOSE_ANGLE;
1026                   }
1027                 else
1028                   {
1029                     gchar buf[7];
1030                     set_error (context,
1031                                error,
1032                                G_MARKUP_ERROR_PARSE,
1033                                _("Odd character '%s', expected a '>' character "
1034                                  "to end the start tag of element '%s'"),
1035                                utf8_str (context->iter, buf),
1036                                current_element (context));
1037                   }
1038               }
1039
1040             g_free (context->tag_stack->data);
1041             context->tag_stack = g_slist_delete_link (context->tag_stack,
1042                                                       context->tag_stack);
1043           }
1044           break;
1045
1046         case STATE_INSIDE_OPEN_TAG_NAME:
1047           /* Possible next states: BETWEEN_ATTRIBUTES */
1048
1049           /* if there's a partial chunk then it's the first part of the
1050            * tag name. If there's a context->start then it's the start
1051            * of the tag name in current_text, the partial chunk goes
1052            * before that start though.
1053            */
1054           advance_to_name_end (context);
1055
1056           if (context->iter == context->current_text_end)
1057             {
1058               /* The name hasn't necessarily ended. Merge with
1059                * partial chunk, leave state unchanged.
1060                */
1061               add_to_partial (context, context->start, context->iter);
1062             }
1063           else
1064             {
1065               /* The name has ended. Combine it with the partial chunk
1066                * if any; push it on the stack; enter next state.
1067                */
1068               add_to_partial (context, context->start, context->iter);
1069               context->tag_stack =
1070                 g_slist_prepend (context->tag_stack,
1071                                  g_string_free (context->partial_chunk,
1072                                                 FALSE));
1073
1074               context->partial_chunk = NULL;
1075
1076               context->state = STATE_BETWEEN_ATTRIBUTES;
1077               context->start = NULL;
1078             }
1079           break;
1080
1081         case STATE_INSIDE_ATTRIBUTE_NAME:
1082           /* Possible next states: AFTER_ATTRIBUTE_EQUALS_SIGN */
1083
1084           /* read the full name, if we enter the equals sign state
1085            * then add the attribute to the list (without the value),
1086            * otherwise store a partial chunk to be prepended later.
1087            */
1088           advance_to_name_end (context);
1089
1090           if (context->iter == context->current_text_end)
1091             {
1092               /* The name hasn't necessarily ended. Merge with
1093                * partial chunk, leave state unchanged.
1094                */
1095               add_to_partial (context, context->start, context->iter);
1096             }
1097           else
1098             {
1099               /* The name has ended. Combine it with the partial chunk
1100                * if any; push it on the stack; enter next state.
1101                */
1102               add_to_partial (context, context->start, context->iter);
1103
1104               add_attribute (context, g_string_free (context->partial_chunk, FALSE));
1105
1106               context->partial_chunk = NULL;
1107               context->start = NULL;
1108
1109               if (*context->iter == '=')
1110                 {
1111                   advance_char (context);
1112                   context->state = STATE_AFTER_ATTRIBUTE_EQUALS_SIGN;
1113                 }
1114               else
1115                 {
1116                   gchar buf[7];
1117                   set_error (context,
1118                              error,
1119                              G_MARKUP_ERROR_PARSE,
1120                              _("Odd character '%s', expected a '=' after "
1121                                "attribute name '%s' of element '%s'"),
1122                              utf8_str (context->iter, buf),
1123                              current_attribute (context),
1124                              current_element (context));
1125
1126                 }
1127             }
1128           break;
1129
1130         case STATE_BETWEEN_ATTRIBUTES:
1131           /* Possible next states: AFTER_CLOSE_ANGLE,
1132            * AFTER_ELISION_SLASH, INSIDE_ATTRIBUTE_NAME
1133            */
1134           skip_spaces (context);
1135
1136           if (context->iter != context->current_text_end)
1137             {
1138               if (*context->iter == '/')
1139                 {
1140                   advance_char (context);
1141                   context->state = STATE_AFTER_ELISION_SLASH;
1142                 }
1143               else if (*context->iter == '>')
1144                 {
1145
1146                   advance_char (context);
1147                   context->state = STATE_AFTER_CLOSE_ANGLE;
1148                 }
1149               else if (is_name_start_char (g_utf8_get_char (context->iter)))
1150                 {
1151                   context->state = STATE_INSIDE_ATTRIBUTE_NAME;
1152                   /* start of attribute name */
1153                   context->start = context->iter;
1154                 }
1155               else
1156                 {
1157                   gchar buf[7];
1158                   set_error (context,
1159                              error,
1160                              G_MARKUP_ERROR_PARSE,
1161                              _("Odd character '%s', expected a '>' or '/' "
1162                                "character to end the start tag of "
1163                                "element '%s', or optionally an attribute; "
1164                                "perhaps you used an invalid character in "
1165                                "an attribute name"),
1166                              utf8_str (context->iter, buf),
1167                              current_element (context));
1168                 }
1169
1170               /* If we're done with attributes, invoke
1171                * the start_element callback
1172                */
1173               if (context->state == STATE_AFTER_ELISION_SLASH ||
1174                   context->state == STATE_AFTER_CLOSE_ANGLE)
1175                 {
1176                   const gchar *start_name;
1177                   /* Ugly, but the current code expects an empty array instead of NULL */
1178                   const gchar *empty = NULL;
1179                   const gchar **attr_names =  &empty;
1180                   const gchar **attr_values = &empty;
1181                   GError *tmp_error;
1182
1183                   /* Call user callback for element start */
1184                   start_name = current_element (context);
1185
1186                   if (context->cur_attr >= 0)
1187                     {
1188                       attr_names = (const gchar**)context->attr_names;
1189                       attr_values = (const gchar**)context->attr_values;
1190                     }
1191
1192                   tmp_error = NULL;
1193                   if (context->parser->start_element)
1194                     (* context->parser->start_element) (context,
1195                                                         start_name,
1196                                                         (const gchar **)attr_names,
1197                                                         (const gchar **)attr_values,
1198                                                         context->user_data,
1199                                                         &tmp_error);
1200
1201                   /* Go ahead and free the attributes. */
1202                   for (; context->cur_attr >= 0; context->cur_attr--)
1203                     {
1204                       int pos = context->cur_attr;
1205                       g_free (context->attr_names[pos]);
1206                       g_free (context->attr_values[pos]);
1207                       context->attr_names[pos] = context->attr_values[pos] = NULL;
1208                     }
1209                   g_assert (context->cur_attr == -1);
1210                   g_assert (context->attr_names == NULL ||
1211                             context->attr_names[0] == NULL);
1212                   g_assert (context->attr_values == NULL ||
1213                             context->attr_values[0] == NULL);
1214                   
1215                   if (tmp_error != NULL)
1216                     {
1217                       mark_error (context, tmp_error);
1218                       g_propagate_error (error, tmp_error);
1219                     }
1220                 }
1221             }
1222           break;
1223
1224         case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1225           /* Possible next state: INSIDE_ATTRIBUTE_VALUE_[SQ/DQ] */
1226           if (*context->iter == '"')
1227             {
1228               advance_char (context);
1229               context->state = STATE_INSIDE_ATTRIBUTE_VALUE_DQ;
1230               context->start = context->iter;
1231             }
1232           else if (*context->iter == '\'')
1233             {
1234               advance_char (context);
1235               context->state = STATE_INSIDE_ATTRIBUTE_VALUE_SQ;
1236               context->start = context->iter;
1237             }
1238           else
1239             {
1240               gchar buf[7];
1241               set_error (context,
1242                          error,
1243                          G_MARKUP_ERROR_PARSE,
1244                          _("Odd character '%s', expected an open quote mark "
1245                            "after the equals sign when giving value for "
1246                            "attribute '%s' of element '%s'"),
1247                          utf8_str (context->iter, buf),
1248                          current_attribute (context),
1249                          current_element (context));
1250             }
1251           break;
1252
1253         case STATE_INSIDE_ATTRIBUTE_VALUE_SQ:
1254         case STATE_INSIDE_ATTRIBUTE_VALUE_DQ:
1255           /* Possible next states: BETWEEN_ATTRIBUTES */
1256           {
1257             gchar delim;
1258
1259             if (context->state == STATE_INSIDE_ATTRIBUTE_VALUE_SQ) 
1260               {
1261                 delim = '\'';
1262               }
1263             else 
1264               {
1265                 delim = '"';
1266               }
1267
1268             do
1269               {
1270                 if (*context->iter == delim)
1271                   break;
1272               }
1273             while (advance_char (context));
1274           }
1275           if (context->iter == context->current_text_end)
1276             {
1277               /* The value hasn't necessarily ended. Merge with
1278                * partial chunk, leave state unchanged.
1279                */
1280               add_to_partial (context, context->start, context->iter);
1281             }
1282           else
1283             {
1284               /* The value has ended at the quote mark. Combine it
1285                * with the partial chunk if any; set it for the current
1286                * attribute.
1287                */
1288               add_to_partial (context, context->start, context->iter);
1289
1290               g_assert (context->cur_attr >= 0);
1291               
1292               if (unescape_text (context,
1293                                  context->partial_chunk->str,
1294                                  context->partial_chunk->str +
1295                                  context->partial_chunk->len,
1296                                  &context->attr_values[context->cur_attr],
1297                                  error))
1298                 {
1299                   /* success, advance past quote and set state. */
1300                   advance_char (context);
1301                   context->state = STATE_BETWEEN_ATTRIBUTES;
1302                   context->start = NULL;
1303                 }
1304               
1305               truncate_partial (context);
1306             }
1307           break;
1308
1309         case STATE_INSIDE_TEXT:
1310           /* Possible next states: AFTER_OPEN_ANGLE */
1311           do
1312             {
1313               if (*context->iter == '<')
1314                 break;
1315             }
1316           while (advance_char (context));
1317
1318           /* The text hasn't necessarily ended. Merge with
1319            * partial chunk, leave state unchanged.
1320            */
1321
1322           add_to_partial (context, context->start, context->iter);
1323
1324           if (context->iter != context->current_text_end)
1325             {
1326               gchar *unescaped = NULL;
1327
1328               /* The text has ended at the open angle. Call the text
1329                * callback.
1330                */
1331               
1332               if (unescape_text (context,
1333                                  context->partial_chunk->str,
1334                                  context->partial_chunk->str +
1335                                  context->partial_chunk->len,
1336                                  &unescaped,
1337                                  error))
1338                 {
1339                   GError *tmp_error = NULL;
1340
1341                   if (context->parser->text)
1342                     (*context->parser->text) (context,
1343                                               unescaped,
1344                                               strlen (unescaped),
1345                                               context->user_data,
1346                                               &tmp_error);
1347                   
1348                   g_free (unescaped);
1349
1350                   if (tmp_error == NULL)
1351                     {
1352                       /* advance past open angle and set state. */
1353                       advance_char (context);
1354                       context->state = STATE_AFTER_OPEN_ANGLE;
1355                       /* could begin a passthrough */
1356                       context->start = context->iter;
1357                     }
1358                   else
1359                     {
1360                       mark_error (context, tmp_error);
1361                       g_propagate_error (error, tmp_error);
1362                     }
1363                 }
1364
1365               truncate_partial (context);
1366             }
1367           break;
1368
1369         case STATE_AFTER_CLOSE_TAG_SLASH:
1370           /* Possible next state: INSIDE_CLOSE_TAG_NAME */
1371           if (is_name_start_char (g_utf8_get_char (context->iter)))
1372             {
1373               context->state = STATE_INSIDE_CLOSE_TAG_NAME;
1374
1375               /* start of tag name */
1376               context->start = context->iter;
1377             }
1378           else
1379             {
1380               gchar buf[7];
1381               set_error (context,
1382                          error,
1383                          G_MARKUP_ERROR_PARSE,
1384                          _("'%s' is not a valid character following "
1385                            "the characters '</'; '%s' may not begin an "
1386                            "element name"),
1387                          utf8_str (context->iter, buf),
1388                          utf8_str (context->iter, buf));
1389             }
1390           break;
1391
1392         case STATE_INSIDE_CLOSE_TAG_NAME:
1393           /* Possible next state: AFTER_CLOSE_ANGLE */
1394           advance_to_name_end (context);
1395
1396           if (context->iter == context->current_text_end)
1397             {
1398               /* The name hasn't necessarily ended. Merge with
1399                * partial chunk, leave state unchanged.
1400                */
1401               add_to_partial (context, context->start, context->iter);
1402             }
1403           else
1404             {
1405               /* The name has ended. Combine it with the partial chunk
1406                * if any; check that it matches stack top and pop
1407                * stack; invoke proper callback; enter next state.
1408                */
1409               gchar *close_name;
1410
1411               add_to_partial (context, context->start, context->iter);
1412
1413               close_name = g_string_free (context->partial_chunk, FALSE);
1414               context->partial_chunk = NULL;
1415               
1416               if (*context->iter != '>')
1417                 {
1418                   gchar buf[7];
1419                   set_error (context,
1420                              error,
1421                              G_MARKUP_ERROR_PARSE,
1422                              _("'%s' is not a valid character following "
1423                                "the close element name '%s'; the allowed "
1424                                "character is '>'"),
1425                              utf8_str (context->iter, buf),
1426                              close_name);
1427                 }
1428               else if (context->tag_stack == NULL)
1429                 {
1430                   set_error (context,
1431                              error,
1432                              G_MARKUP_ERROR_PARSE,
1433                              _("Element '%s' was closed, no element "
1434                                "is currently open"),
1435                              close_name);
1436                 }
1437               else if (strcmp (close_name, current_element (context)) != 0)
1438                 {
1439                   set_error (context,
1440                              error,
1441                              G_MARKUP_ERROR_PARSE,
1442                              _("Element '%s' was closed, but the currently "
1443                                "open element is '%s'"),
1444                              close_name,
1445                              current_element (context));
1446                 }
1447               else
1448                 {
1449                   GError *tmp_error;
1450                   advance_char (context);
1451                   context->state = STATE_AFTER_CLOSE_ANGLE;
1452                   context->start = NULL;
1453
1454                   /* call the end_element callback */
1455                   tmp_error = NULL;
1456                   if (context->parser->end_element)
1457                     (* context->parser->end_element) (context,
1458                                                       close_name,
1459                                                       context->user_data,
1460                                                       &tmp_error);
1461
1462                   
1463                   /* Pop the tag stack */
1464                   g_free (context->tag_stack->data);
1465                   context->tag_stack = g_slist_delete_link (context->tag_stack,
1466                                                             context->tag_stack);
1467                   
1468                   if (tmp_error)
1469                     {
1470                       mark_error (context, tmp_error);
1471                       g_propagate_error (error, tmp_error);
1472                     }
1473                 }
1474
1475               g_free (close_name);
1476             }
1477           break;
1478
1479         case STATE_INSIDE_PASSTHROUGH:
1480           /* Possible next state: AFTER_CLOSE_ANGLE */
1481           do
1482             {
1483               if (*context->iter == '>')
1484                 break;
1485             }
1486           while (advance_char (context));
1487
1488           if (context->iter == context->current_text_end)
1489             {
1490               /* The passthrough hasn't necessarily ended. Merge with
1491                * partial chunk, leave state unchanged.
1492                */
1493               add_to_partial (context, context->start, context->iter);
1494             }
1495           else
1496             {
1497               /* The passthrough has ended at the close angle. Combine
1498                * it with the partial chunk if any. Call the passthrough
1499                * callback. Note that the open/close angles are
1500                * included in the text of the passthrough.
1501                */
1502               GError *tmp_error = NULL;
1503
1504               advance_char (context); /* advance past close angle */
1505               add_to_partial (context, context->start, context->iter);
1506
1507               if (context->parser->passthrough)
1508                 (*context->parser->passthrough) (context,
1509                                                  context->partial_chunk->str,
1510                                                  context->partial_chunk->len,
1511                                                  context->user_data,
1512                                                  &tmp_error);
1513                   
1514               truncate_partial (context);
1515
1516               if (tmp_error == NULL)
1517                 {
1518                   context->state = STATE_AFTER_CLOSE_ANGLE;
1519                   context->start = context->iter; /* could begin text */
1520                 }
1521               else
1522                 {
1523                   mark_error (context, tmp_error);
1524                   g_propagate_error (error, tmp_error);
1525                 }
1526             }
1527           break;
1528
1529         case STATE_ERROR:
1530           goto finished;
1531           break;
1532
1533         default:
1534           g_assert_not_reached ();
1535           break;
1536         }
1537     }
1538
1539  finished:
1540   context->parsing = FALSE;
1541
1542   return context->state != STATE_ERROR;
1543 }
1544
1545 /**
1546  * g_markup_parse_context_end_parse:
1547  * @context: a #GMarkupParseContext
1548  * @error: return location for a #GError
1549  * 
1550  * Signals to the #GMarkupParseContext that all data has been
1551  * fed into the parse context with g_markup_parse_context_parse().
1552  * This function reports an error if the document isn't complete,
1553  * for example if elements are still open.
1554  * 
1555  * Return value: %TRUE on success, %FALSE if an error was set
1556  **/
1557 gboolean
1558 g_markup_parse_context_end_parse (GMarkupParseContext *context,
1559                                   GError             **error)
1560 {
1561   g_return_val_if_fail (context != NULL, FALSE);
1562   g_return_val_if_fail (!context->parsing, FALSE);
1563   g_return_val_if_fail (context->state != STATE_ERROR, FALSE);
1564
1565   if (context->partial_chunk != NULL)
1566     {
1567       g_string_free (context->partial_chunk, TRUE);
1568       context->partial_chunk = NULL;
1569     }
1570
1571   if (context->document_empty)
1572     {
1573       set_error (context, error, G_MARKUP_ERROR_EMPTY,
1574                  _("Document was empty or contained only whitespace"));
1575       return FALSE;
1576     }
1577   
1578   context->parsing = TRUE;
1579   
1580   switch (context->state)
1581     {
1582     case STATE_START:
1583       /* Nothing to do */
1584       break;
1585
1586     case STATE_AFTER_OPEN_ANGLE:
1587       set_error (context, error, G_MARKUP_ERROR_PARSE,
1588                  _("Document ended unexpectedly just after an open angle bracket '<'"));
1589       break;
1590
1591     case STATE_AFTER_CLOSE_ANGLE:
1592       if (context->tag_stack != NULL)
1593         {
1594           /* Error message the same as for INSIDE_TEXT */
1595           set_error (context, error, G_MARKUP_ERROR_PARSE,
1596                      _("Document ended unexpectedly with elements still open - "
1597                        "'%s' was the last element opened"),
1598                      current_element (context));
1599         }
1600       break;
1601       
1602     case STATE_AFTER_ELISION_SLASH:
1603       set_error (context, error, G_MARKUP_ERROR_PARSE,
1604                  _("Document ended unexpectedly, expected to see a close angle "
1605                    "bracket ending the tag <%s/>"), current_element (context));
1606       break;
1607
1608     case STATE_INSIDE_OPEN_TAG_NAME:
1609       set_error (context, error, G_MARKUP_ERROR_PARSE,
1610                  _("Document ended unexpectedly inside an element name"));
1611       break;
1612
1613     case STATE_INSIDE_ATTRIBUTE_NAME:
1614       set_error (context, error, G_MARKUP_ERROR_PARSE,
1615                  _("Document ended unexpectedly inside an attribute name"));
1616       break;
1617
1618     case STATE_BETWEEN_ATTRIBUTES:
1619       set_error (context, error, G_MARKUP_ERROR_PARSE,
1620                  _("Document ended unexpectedly inside an element-opening "
1621                    "tag."));
1622       break;
1623
1624     case STATE_AFTER_ATTRIBUTE_EQUALS_SIGN:
1625       set_error (context, error, G_MARKUP_ERROR_PARSE,
1626                  _("Document ended unexpectedly after the equals sign "
1627                    "following an attribute name; no attribute value"));
1628       break;
1629
1630     case STATE_INSIDE_ATTRIBUTE_VALUE_SQ:
1631     case STATE_INSIDE_ATTRIBUTE_VALUE_DQ:
1632       set_error (context, error, G_MARKUP_ERROR_PARSE,
1633                  _("Document ended unexpectedly while inside an attribute "
1634                    "value"));
1635       break;
1636
1637     case STATE_INSIDE_TEXT:
1638       g_assert (context->tag_stack != NULL);
1639       set_error (context, error, G_MARKUP_ERROR_PARSE,
1640                  _("Document ended unexpectedly with elements still open - "
1641                    "'%s' was the last element opened"),
1642                  current_element (context));
1643       break;
1644
1645     case STATE_AFTER_CLOSE_TAG_SLASH:
1646     case STATE_INSIDE_CLOSE_TAG_NAME:
1647       set_error (context, error, G_MARKUP_ERROR_PARSE,
1648                  _("Document ended unexpectedly inside the close tag for "
1649                    "element '%s'"), current_element);
1650       break;
1651
1652     case STATE_INSIDE_PASSTHROUGH:
1653       set_error (context, error, G_MARKUP_ERROR_PARSE,
1654                  _("Document ended unexpectedly inside a comment or "
1655                    "processing instruction"));
1656       break;
1657
1658     case STATE_ERROR:
1659     default:
1660       g_assert_not_reached ();
1661       break;
1662     }
1663
1664   context->parsing = FALSE;
1665
1666   return context->state != STATE_ERROR;
1667 }
1668
1669 /**
1670  * g_markup_parse_context_get_position:
1671  * @context: a #GMarkupParseContext
1672  * @line_number: return location for a line number, or %NULL
1673  * @char_number: return location for a char-on-line number, or %NULL
1674  *
1675  * Retrieves the current line number and the number of the character on
1676  * that line. Intended for use in error messages; there are no strict
1677  * semantics for what constitutes the "current" line number other than
1678  * "the best number we could come up with for error messages."
1679  * 
1680  **/
1681 void
1682 g_markup_parse_context_get_position (GMarkupParseContext *context,
1683                                      gint                *line_number,
1684                                      gint                *char_number)
1685 {
1686   g_return_if_fail (context != NULL);
1687
1688   if (line_number)
1689     *line_number = context->line_number;
1690
1691   if (char_number)
1692     *char_number = context->char_number;
1693 }
1694
1695 static void
1696 append_escaped_text (GString     *str,
1697                      const gchar *text,
1698                      gssize       length)    
1699 {
1700   const gchar *p;
1701   const gchar *end;
1702
1703   p = text;
1704   end = text + length;
1705
1706   while (p != end)
1707     {
1708       const gchar *next;
1709       next = g_utf8_next_char (p);
1710
1711       switch (*p)
1712         {
1713         case '&':
1714           g_string_append (str, "&amp;");
1715           break;
1716
1717         case '<':
1718           g_string_append (str, "&lt;");
1719           break;
1720
1721         case '>':
1722           g_string_append (str, "&gt;");
1723           break;
1724
1725         case '\'':
1726           g_string_append (str, "&apos;");
1727           break;
1728
1729         case '"':
1730           g_string_append (str, "&quot;");
1731           break;
1732
1733         default:
1734           g_string_append_len (str, p, next - p);
1735           break;
1736         }
1737
1738       p = next;
1739     }
1740 }
1741
1742 /**
1743  * g_markup_escape_text:
1744  * @text: some valid UTF-8 text
1745  * @length: length of @text in bytes
1746  * 
1747  * Escapes text so that the markup parser will parse it verbatim.
1748  * Less than, greater than, ampersand, etc. are replaced with the
1749  * corresponding entities. This function would typically be used
1750  * when writing out a file to be parsed with the markup parser.
1751  * 
1752  * Return value: escaped text
1753  **/
1754 gchar*
1755 g_markup_escape_text (const gchar *text,
1756                       gssize       length)  
1757 {
1758   GString *str;
1759
1760   g_return_val_if_fail (text != NULL, NULL);
1761
1762   if (length < 0)
1763     length = strlen (text);
1764
1765   str = g_string_new ("");
1766   append_escaped_text (str, text, length);
1767
1768   return g_string_free (str, FALSE);
1769 }