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