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