]> Pileus Git - ~andy/gtk/blob - gtk/gtktextbufferserialize.c
Move GtkTextBuffer sealed attributes to private struct.
[~andy/gtk] / gtk / gtktextbufferserialize.c
1 /* gtktextbufferserialize.c
2  *
3  * Copyright (C) 2001 Havoc Pennington
4  * Copyright (C) 2004 Nokia Corporation
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /* FIXME: We should use other error codes for the
23  * parts that deal with the format errors
24  */
25
26 #include "config.h"
27
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31
32 #include "gdk-pixbuf/gdk-pixdata.h"
33 #include "gtktextbufferserialize.h"
34 #include "gtkintl.h"
35
36
37 typedef struct
38 {
39   GString *tag_table_str;
40   GString *text_str;
41   GHashTable *tags;
42   GtkTextIter start, end;
43
44   gint n_pixbufs;
45   GList *pixbufs;
46   gint tag_id;
47   GHashTable *tag_id_tags;
48 } SerializationContext;
49
50 static gchar *
51 serialize_value (GValue *value)
52 {
53   if (g_value_type_transformable (value->g_type, G_TYPE_STRING))
54     {
55       GValue text_value = { 0 };
56       gchar *tmp;
57
58       g_value_init (&text_value, G_TYPE_STRING);
59       g_value_transform (value, &text_value);
60
61       tmp = g_markup_escape_text (g_value_get_string (&text_value), -1);
62       g_value_unset (&text_value);
63
64       return tmp;
65     }
66   else if (value->g_type == GDK_TYPE_COLOR)
67     {
68       GdkColor *color = g_value_get_boxed (value);
69
70       return g_strdup_printf ("%x:%x:%x", color->red, color->green, color->blue);
71     }
72   else if (g_type_is_a (value->g_type, GDK_TYPE_DRAWABLE))
73     {
74       /* Don't do anything */
75     }
76   else
77     {
78       g_warning ("Type %s is not serializable\n", g_type_name (value->g_type));
79     }
80
81   return NULL;
82 }
83
84 static gboolean
85 deserialize_value (const gchar *str,
86                    GValue      *value)
87 {
88   if (g_value_type_transformable (G_TYPE_STRING, value->g_type))
89     {
90       GValue text_value = { 0 };
91       gboolean retval;
92
93       g_value_init (&text_value, G_TYPE_STRING);
94       g_value_set_static_string (&text_value, str);
95
96       retval = g_value_transform (&text_value, value);
97       g_value_unset (&text_value);
98
99       return retval;
100     }
101   else if (value->g_type == G_TYPE_BOOLEAN)
102     {
103       gboolean v;
104
105       v = strcmp (str, "TRUE") == 0;
106
107       g_value_set_boolean (value, v);
108
109       return TRUE;
110     }
111   else if (value->g_type == G_TYPE_INT)
112     {
113       gchar *tmp;
114       int v;
115
116       v = strtol (str, &tmp, 10);
117
118       if (tmp == NULL || tmp == str)
119         return FALSE;
120
121       g_value_set_int (value, v);
122
123       return TRUE;
124     }
125   else if (value->g_type == G_TYPE_DOUBLE)
126     {
127       gchar *tmp;
128       gdouble v;
129
130       v = g_ascii_strtod (str, &tmp);
131
132       if (tmp == NULL || tmp == str)
133         return FALSE;
134
135       g_value_set_double (value, v);
136
137       return TRUE;
138     }
139   else if (value->g_type == GDK_TYPE_COLOR)
140     {
141       GdkColor color;
142       const gchar *old;
143       gchar *tmp;
144
145       old = str;
146       color.red = strtol (old, &tmp, 16);
147
148       if (tmp == NULL || tmp == old)
149         return FALSE;
150
151       old = tmp;
152       if (*old++ != ':')
153         return FALSE;
154
155       color.green = strtol (old, &tmp, 16);
156       if (tmp == NULL || tmp == old)
157         return FALSE;
158
159       old = tmp;
160       if (*old++ != ':')
161         return FALSE;
162
163       color.blue = strtol (old, &tmp, 16);
164
165       if (tmp == NULL || tmp == old || *tmp != '\0')
166         return FALSE;
167
168       g_value_set_boxed (value, &color);
169
170       return TRUE;
171     }
172   else if (G_VALUE_HOLDS_ENUM (value))
173     {
174       GEnumClass *class = G_ENUM_CLASS (g_type_class_peek (value->g_type));
175       GEnumValue *enum_value;
176
177       enum_value = g_enum_get_value_by_name (class, str);
178
179       if (enum_value)
180         {
181           g_value_set_enum (value, enum_value->value);
182           return TRUE;
183         }
184
185       return FALSE;
186     }
187   else
188     {
189       g_warning ("Type %s can not be deserialized\n", g_type_name (value->g_type));
190     }
191
192   return FALSE;
193 }
194
195 /* Checks if a param is set, or if it's the default value */
196 static gboolean
197 is_param_set (GObject    *object,
198               GParamSpec *pspec,
199               GValue     *value)
200 {
201   /* We need to special case some attributes here */
202   if (strcmp (pspec->name, "background-gdk") == 0)
203     {
204       gboolean is_set;
205
206       g_object_get (object, "background-set", &is_set, NULL);
207
208       if (is_set)
209         {
210           g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
211
212           g_object_get_property (object, pspec->name, value);
213
214           return TRUE;
215         }
216
217       return FALSE;
218     }
219   else if (strcmp (pspec->name, "foreground-gdk") == 0)
220     {
221       gboolean is_set;
222
223       g_object_get (object, "foreground-set", &is_set, NULL);
224
225       if (is_set)
226         {
227           g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
228
229           g_object_get_property (object, pspec->name, value);
230
231           return TRUE;
232         }
233
234       return FALSE;
235     }
236   else
237     {
238       gboolean is_set;
239       gchar *is_set_name;
240
241       is_set_name = g_strdup_printf ("%s-set", pspec->name);
242
243       if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), is_set_name) == NULL)
244         {
245           g_free (is_set_name);
246           return FALSE;
247         }
248       else
249         {
250           g_object_get (object, is_set_name, &is_set, NULL);
251
252           if (!is_set)
253             {
254               g_free (is_set_name);
255               return FALSE;
256             }
257
258           g_free (is_set_name);
259
260           g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
261
262           g_object_get_property (object, pspec->name, value);
263
264           if (g_param_value_defaults (pspec, value))
265             {
266               g_value_unset (value);
267
268               return FALSE;
269             }
270         }
271       return TRUE;
272     }
273 }
274
275 static void
276 serialize_tag (gpointer key,
277                gpointer data,
278                gpointer user_data)
279 {
280   SerializationContext *context = user_data;
281   GtkTextTag *tag = data;
282   gchar *tag_name;
283   gint tag_id;
284   GParamSpec **pspecs;
285   guint n_pspecs;
286   int i;
287
288   g_string_append (context->tag_table_str, "  <tag ");
289
290   /* Handle anonymous tags */
291   if (tag->name)
292     {
293       tag_name = g_markup_escape_text (tag->name, -1);
294       g_string_append_printf (context->tag_table_str, "name=\"%s\"", tag_name);
295       g_free (tag_name);
296     }
297   else
298     {
299       tag_id = GPOINTER_TO_INT (g_hash_table_lookup (context->tag_id_tags, tag));
300
301       g_string_append_printf (context->tag_table_str, "id=\"%d\"", tag_id);
302     }
303
304   g_string_append_printf (context->tag_table_str, " priority=\"%d\">\n", tag->priority);
305
306   /* Serialize properties */
307   pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (tag), &n_pspecs);
308
309   for (i = 0; i < n_pspecs; i++)
310     {
311       GValue value = { 0 };
312       gchar *tmp, *tmp2;
313
314       if (!(pspecs[i]->flags & G_PARAM_READABLE) ||
315           !(pspecs[i]->flags & G_PARAM_WRITABLE))
316         continue;
317
318       if (!is_param_set (G_OBJECT (tag), pspecs[i], &value))
319         continue;
320
321       /* Now serialize the attr */
322       tmp2 = serialize_value (&value);
323
324       if (tmp2)
325         {
326           tmp = g_markup_escape_text (pspecs[i]->name, -1);
327           g_string_append_printf (context->tag_table_str, "   <attr name=\"%s\" ", tmp);
328           g_free (tmp);
329
330           tmp = g_markup_escape_text (g_type_name (pspecs[i]->value_type), -1);
331           g_string_append_printf (context->tag_table_str, "type=\"%s\" value=\"%s\" />\n", tmp, tmp2);
332
333           g_free (tmp);
334           g_free (tmp2);
335         }
336
337       g_value_unset (&value);
338     }
339
340   g_free (pspecs);
341
342   g_string_append (context->tag_table_str, "  </tag>\n");
343 }
344
345 static void
346 serialize_tags (SerializationContext *context)
347 {
348   g_string_append (context->tag_table_str, " <text_view_markup>\n");
349   g_string_append (context->tag_table_str, " <tags>\n");
350   g_hash_table_foreach (context->tags, serialize_tag, context);
351   g_string_append (context->tag_table_str, " </tags>\n");
352 }
353
354 #if 0
355 static void
356 dump_tag_list (const gchar *str,
357                GList       *list)
358 {
359   g_print ("%s: ", str);
360
361   if (!list)
362     g_print ("(empty)");
363   else
364     {
365       while (list)
366         {
367           g_print ("%s ", ((GtkTextTag *)list->data)->name);
368           list = list->next;
369         }
370     }
371
372   g_print ("\n");
373 }
374 #endif
375
376 static void
377 find_list_delta (GSList  *old_list,
378                  GSList  *new_list,
379                  GList  **added,
380                  GList  **removed)
381 {
382   GSList *tmp;
383   GList *tmp_added, *tmp_removed;
384
385   tmp_added = NULL;
386   tmp_removed = NULL;
387
388   /* Find added tags */
389   tmp = new_list;
390   while (tmp)
391     {
392       if (!g_slist_find (old_list, tmp->data))
393         tmp_added = g_list_prepend (tmp_added, tmp->data);
394
395       tmp = tmp->next;
396     }
397
398   *added = tmp_added;
399
400   /* Find removed tags */
401   tmp = old_list;
402   while (tmp)
403     {
404       if (!g_slist_find (new_list, tmp->data))
405         tmp_removed = g_list_prepend (tmp_removed, tmp->data);
406
407       tmp = tmp->next;
408     }
409
410   /* We reverse the list here to match the xml semantics */
411   *removed = g_list_reverse (tmp_removed);
412 }
413
414 static void
415 serialize_section_header (GString     *str,
416                           const gchar *name,
417                           gint         length)
418 {
419   g_return_if_fail (strlen (name) == 26);
420
421   g_string_append (str, name);
422
423   g_string_append_c (str, length >> 24);
424
425   g_string_append_c (str, (length >> 16) & 0xff);
426   g_string_append_c (str, (length >> 8) & 0xff);
427   g_string_append_c (str, length & 0xff);
428 }
429
430 static void
431 serialize_text (GtkTextBuffer        *buffer,
432                 SerializationContext *context)
433 {
434   GtkTextIter iter, old_iter;
435   GSList *tag_list, *new_tag_list;
436   GSList *active_tags;
437
438   g_string_append (context->text_str, "<text>");
439
440   iter = context->start;
441   tag_list = NULL;
442   active_tags = NULL;
443
444   do
445     {
446       GList *added, *removed;
447       GList *tmp;
448       gchar *tmp_text, *escaped_text;
449
450       new_tag_list = gtk_text_iter_get_tags (&iter);
451       find_list_delta (tag_list, new_tag_list, &added, &removed);
452
453       /* Handle removed tags */
454       for (tmp = removed; tmp; tmp = tmp->next)
455         {
456           GtkTextTag *tag = tmp->data;
457
458           /* Only close the tag if we didn't close it before (by using
459            * the stack logic in the while() loop below)
460            */
461           if (g_slist_find (active_tags, tag))
462             {
463               g_string_append (context->text_str, "</apply_tag>");
464
465               /* Drop all tags that were opened after this one (which are
466                * above this on in the stack)
467                */
468               while (active_tags->data != tag)
469                 {
470                   added = g_list_prepend (added, active_tags->data);
471                   active_tags = g_slist_remove (active_tags, active_tags->data);
472                   g_string_append_printf (context->text_str, "</apply_tag>");
473                 }
474
475               active_tags = g_slist_remove (active_tags, active_tags->data);
476             }
477         }
478
479       /* Handle added tags */
480       for (tmp = added; tmp; tmp = tmp->next)
481         {
482           GtkTextTag *tag = tmp->data;
483           gchar *tag_name;
484
485           /* Add it to the tag hash table */
486           g_hash_table_insert (context->tags, tag, tag);
487
488           if (tag->name)
489             {
490               tag_name = g_markup_escape_text (tag->name, -1);
491
492               g_string_append_printf (context->text_str, "<apply_tag name=\"%s\">", tag_name);
493               g_free (tag_name);
494             }
495           else
496             {
497               gpointer tag_id;
498
499               /* We've got an anonymous tag, find out if it's been
500                  used before */
501               if (!g_hash_table_lookup_extended (context->tag_id_tags, tag, NULL, &tag_id))
502                 {
503                   tag_id = GINT_TO_POINTER (context->tag_id++);
504
505                   g_hash_table_insert (context->tag_id_tags, tag, tag_id);
506                 }
507
508               g_string_append_printf (context->text_str, "<apply_tag id=\"%d\">", GPOINTER_TO_INT (tag_id));
509             }
510
511           active_tags = g_slist_prepend (active_tags, tag);
512         }
513
514       g_slist_free (tag_list);
515       tag_list = new_tag_list;
516
517       g_list_free (added);
518       g_list_free (removed);
519
520       old_iter = iter;
521
522       /* Now try to go to either the next tag toggle, or if a pixbuf appears */
523       while (TRUE)
524         {
525           gunichar ch = gtk_text_iter_get_char (&iter);
526
527           if (ch == 0xFFFC)
528             {
529               GdkPixbuf *pixbuf = gtk_text_iter_get_pixbuf (&iter);
530
531               if (pixbuf)
532                 {
533                   /* Append the text before the pixbuf */
534                   tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
535                   escaped_text = g_markup_escape_text (tmp_text, -1);
536                   g_free (tmp_text);
537
538                   /* Forward so we don't get the 0xfffc char */
539                   gtk_text_iter_forward_char (&iter);
540                   old_iter = iter;
541
542                   g_string_append (context->text_str, escaped_text);
543                   g_free (escaped_text);
544
545                   g_string_append_printf (context->text_str, "<pixbuf index=\"%d\" />", context->n_pixbufs);
546
547                   context->n_pixbufs++;
548                   context->pixbufs = g_list_prepend (context->pixbufs, pixbuf);
549                 }
550             }
551           else if (ch == 0)
552             {
553                 break;
554             }
555           else
556             gtk_text_iter_forward_char (&iter);
557
558           if (gtk_text_iter_toggles_tag (&iter, NULL))
559             break;
560         }
561
562       /* We might have moved too far */
563       if (gtk_text_iter_compare (&iter, &context->end) > 0)
564         iter = context->end;
565
566       /* Append the text */
567       tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
568       escaped_text = g_markup_escape_text (tmp_text, -1);
569       g_free (tmp_text);
570
571       g_string_append (context->text_str, escaped_text);
572       g_free (escaped_text);
573     }
574   while (!gtk_text_iter_equal (&iter, &context->end));
575
576   /* Close any open tags */
577   for (tag_list = active_tags; tag_list; tag_list = tag_list->next)
578     g_string_append (context->text_str, "</apply_tag>");
579
580   g_slist_free (active_tags);
581   g_string_append (context->text_str, "</text>\n</text_view_markup>\n");
582 }
583
584 static void
585 serialize_pixbufs (SerializationContext *context,
586                    GString              *text)
587 {
588   GList *list;
589
590   for (list = context->pixbufs; list != NULL; list = list->next)
591     {
592       GdkPixbuf *pixbuf = list->data;
593       GdkPixdata pixdata;
594       guint8 *tmp;
595       guint len;
596
597       gdk_pixdata_from_pixbuf (&pixdata, pixbuf, FALSE);
598       tmp = gdk_pixdata_serialize (&pixdata, &len);
599
600       serialize_section_header (text, "GTKTEXTBUFFERPIXBDATA-0001", len);
601       g_string_append_len (text, (gchar *) tmp, len);
602       g_free (tmp);
603     }
604 }
605
606 guint8 *
607 _gtk_text_buffer_serialize_rich_text (GtkTextBuffer     *register_buffer,
608                                       GtkTextBuffer     *content_buffer,
609                                       const GtkTextIter *start,
610                                       const GtkTextIter *end,
611                                       gsize             *length,
612                                       gpointer           user_data)
613 {
614   SerializationContext context;
615   GString *text;
616
617   context.tags = g_hash_table_new (NULL, NULL);
618   context.text_str = g_string_new (NULL);
619   context.tag_table_str = g_string_new (NULL);
620   context.start = *start;
621   context.end = *end;
622   context.n_pixbufs = 0;
623   context.pixbufs = NULL;
624   context.tag_id = 0;
625   context.tag_id_tags = g_hash_table_new (NULL, NULL);
626
627   /* We need to serialize the text before the tag table so we know
628      what tags are used */
629   serialize_text (content_buffer, &context);
630   serialize_tags (&context);
631
632   text = g_string_new (NULL);
633   serialize_section_header (text, "GTKTEXTBUFFERCONTENTS-0001",
634                             context.tag_table_str->len + context.text_str->len);
635
636   g_string_append_len (text, context.tag_table_str->str, context.tag_table_str->len);
637   g_string_append_len (text, context.text_str->str, context.text_str->len);
638
639   context.pixbufs = g_list_reverse (context.pixbufs);
640   serialize_pixbufs (&context, text);
641
642   g_hash_table_destroy (context.tags);
643   g_list_free (context.pixbufs);
644   g_string_free (context.text_str, TRUE);
645   g_string_free (context.tag_table_str, TRUE);
646   g_hash_table_destroy (context.tag_id_tags);
647
648   *length = text->len;
649
650   return (guint8 *) g_string_free (text, FALSE);
651 }
652
653 typedef enum
654 {
655   STATE_START,
656   STATE_TEXT_VIEW_MARKUP,
657   STATE_TAGS,
658   STATE_TAG,
659   STATE_ATTR,
660   STATE_TEXT,
661   STATE_APPLY_TAG,
662   STATE_PIXBUF
663 } ParseState;
664
665 typedef struct
666 {
667   gchar *text;
668   GdkPixbuf *pixbuf;
669   GSList *tags;
670 } TextSpan;
671
672 typedef struct
673 {
674   GtkTextTag *tag;
675   gint prio;
676 } TextTagPrio;
677
678 typedef struct
679 {
680   GSList *states;
681
682   GList *headers;
683
684   GtkTextBuffer *buffer;
685
686   /* Tags that are defined in <tag> elements */
687   GHashTable *defined_tags;
688
689   /* Tags that are anonymous */
690   GHashTable *anonymous_tags;
691
692   /* Tag name substitutions */
693   GHashTable *substitutions;
694
695   /* Current tag */
696   GtkTextTag *current_tag;
697
698   /* Priority of current tag */
699   gint current_tag_prio;
700
701   /* Id of current tag */
702   gint current_tag_id;
703
704   /* Tags and their priorities */
705   GList *tag_priorities;
706
707   GSList *tag_stack;
708
709   GList *spans;
710
711   gboolean create_tags;
712
713   gboolean parsed_text;
714   gboolean parsed_tags;
715 } ParseInfo;
716
717 static void
718 set_error (GError              **err,
719            GMarkupParseContext  *context,
720            int                   error_domain,
721            int                   error_code,
722            const char           *format,
723            ...)
724 {
725   int line, ch;
726   va_list args;
727   char *str;
728
729   g_markup_parse_context_get_position (context, &line, &ch);
730
731   va_start (args, format);
732   str = g_strdup_vprintf (format, args);
733   va_end (args);
734
735   g_set_error (err, error_domain, error_code,
736                ("Line %d character %d: %s"),
737                line, ch, str);
738
739   g_free (str);
740 }
741
742 static void
743 push_state (ParseInfo  *info,
744             ParseState  state)
745 {
746   info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
747 }
748
749 static void
750 pop_state (ParseInfo *info)
751 {
752   g_return_if_fail (info->states != NULL);
753
754   info->states = g_slist_remove (info->states, info->states->data);
755 }
756
757 static ParseState
758 peek_state (ParseInfo *info)
759 {
760   g_return_val_if_fail (info->states != NULL, STATE_START);
761
762   return GPOINTER_TO_INT (info->states->data);
763 }
764
765 #define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
766
767
768 static gboolean
769 check_id_or_name (GMarkupParseContext  *context,
770                   const gchar          *element_name,
771                   const gchar         **attribute_names,
772                   const gchar         **attribute_values,
773                   gint                 *id,
774                   const gchar         **name,
775                   GError              **error)
776 {
777   gboolean has_id = FALSE;
778   gboolean has_name = FALSE;
779   int i;
780
781   *id = 0;
782   *name = NULL;
783
784   for (i = 0; attribute_names[i] != NULL; i++)
785     {
786       if (strcmp (attribute_names[i], "name") == 0)
787         {
788           *name = attribute_values[i];
789
790           if (has_id)
791             {
792               set_error (error, context,
793                          G_MARKUP_ERROR,
794                          G_MARKUP_ERROR_PARSE,
795                          _("Both \"id\" and \"name\" were found on the <%s> element"),
796                          element_name);
797               return FALSE;
798             }
799
800           if (has_name)
801             {
802               set_error (error, context,
803                          G_MARKUP_ERROR,
804                          G_MARKUP_ERROR_PARSE,
805                          _("The attribute \"%s\" was found twice on the <%s> element"),
806                          "name", element_name);
807               return FALSE;
808             }
809
810           has_name = TRUE;
811         }
812       else if (strcmp (attribute_names[i], "id") == 0)
813         {
814           gchar *tmp;
815
816           if (has_name)
817             {
818               set_error (error, context,
819                          G_MARKUP_ERROR,
820                          G_MARKUP_ERROR_PARSE,
821                          _("Both \"id\" and \"name\" were found on the <%s> element"),
822                          element_name);
823               return FALSE;
824             }
825
826           if (has_id)
827             {
828               set_error (error, context,
829                          G_MARKUP_ERROR,
830                          G_MARKUP_ERROR_PARSE,
831                          _("The attribute \"%s\" was found twice on the <%s> element"),
832                          "id", element_name);
833               return FALSE;
834             }
835
836           has_id = TRUE;
837
838           /* Try parsing the integer */
839           *id = strtol (attribute_values[i], &tmp, 10);
840
841           if (tmp == NULL || tmp == attribute_values[i])
842             {
843               set_error (error, context,
844                          G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
845                          _("<%s> element has invalid id \"%s\""), attribute_values[i]);
846               return FALSE;
847             }
848         }
849     }
850
851   if (!has_id && !has_name)
852     {
853       set_error (error, context,
854                  G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
855                  _("<%s> element has neither a \"name\" nor an \"id\" attribute"), element_name);
856       return FALSE;
857     }
858
859   return TRUE;
860 }
861
862 typedef struct
863 {
864   const char  *name;
865   const char **retloc;
866 } LocateAttr;
867
868 static gboolean
869 locate_attributes (GMarkupParseContext  *context,
870                    const char           *element_name,
871                    const char          **attribute_names,
872                    const char          **attribute_values,
873                    gboolean              allow_unknown_attrs,
874                    GError              **error,
875                    const char           *first_attribute_name,
876                    const char          **first_attribute_retloc,
877                    ...)
878 {
879   va_list args;
880   const char *name;
881   const char **retloc;
882   int n_attrs;
883 #define MAX_ATTRS 24
884   LocateAttr attrs[MAX_ATTRS];
885   gboolean retval;
886   int i;
887
888   g_return_val_if_fail (first_attribute_name != NULL, FALSE);
889   g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
890
891   retval = TRUE;
892
893   n_attrs = 1;
894   attrs[0].name = first_attribute_name;
895   attrs[0].retloc = first_attribute_retloc;
896   *first_attribute_retloc = NULL;
897
898   va_start (args, first_attribute_retloc);
899
900   name = va_arg (args, const char*);
901   retloc = va_arg (args, const char**);
902
903   while (name != NULL)
904     {
905       g_return_val_if_fail (retloc != NULL, FALSE);
906
907       g_assert (n_attrs < MAX_ATTRS);
908
909       attrs[n_attrs].name = name;
910       attrs[n_attrs].retloc = retloc;
911       n_attrs += 1;
912       *retloc = NULL;
913
914       name = va_arg (args, const char*);
915       retloc = va_arg (args, const char**);
916     }
917
918   va_end (args);
919
920   if (!retval)
921     return retval;
922
923   i = 0;
924   while (attribute_names[i])
925     {
926       int j;
927       gboolean found;
928
929       found = FALSE;
930       j = 0;
931       while (j < n_attrs)
932         {
933           if (strcmp (attrs[j].name, attribute_names[i]) == 0)
934             {
935               retloc = attrs[j].retloc;
936
937               if (*retloc != NULL)
938                 {
939                   set_error (error, context,
940                              G_MARKUP_ERROR,
941                              G_MARKUP_ERROR_PARSE,
942                              _("Attribute \"%s\" repeated twice on the same <%s> element"),
943                              attrs[j].name, element_name);
944                   retval = FALSE;
945                   goto out;
946                 }
947
948               *retloc = attribute_values[i];
949               found = TRUE;
950             }
951
952           ++j;
953         }
954
955       if (!found && !allow_unknown_attrs)
956         {
957           set_error (error, context,
958                      G_MARKUP_ERROR,
959                      G_MARKUP_ERROR_PARSE,
960                      _("Attribute \"%s\" is invalid on <%s> element in this context"),
961                      attribute_names[i], element_name);
962           retval = FALSE;
963           goto out;
964         }
965
966       ++i;
967     }
968
969  out:
970   return retval;
971 }
972
973 static gboolean
974 check_no_attributes (GMarkupParseContext  *context,
975                      const char           *element_name,
976                      const char          **attribute_names,
977                      const char          **attribute_values,
978                      GError              **error)
979 {
980   if (attribute_names[0] != NULL)
981     {
982       set_error (error, context,
983                  G_MARKUP_ERROR,
984                  G_MARKUP_ERROR_PARSE,
985                  _("Attribute \"%s\" is invalid on <%s> element in this context"),
986                  attribute_names[0], element_name);
987       return FALSE;
988     }
989
990   return TRUE;
991 }
992
993 static GtkTextTag *
994 tag_exists (GMarkupParseContext *context,
995             const gchar         *name,
996             gint                 id,
997             ParseInfo           *info,
998             GError             **error)
999 {
1000   GtkTextTagTable *tag_table;
1001   const gchar *real_name;
1002
1003   tag_table = gtk_text_buffer_get_tag_table (info->buffer);
1004
1005   if (info->create_tags)
1006     {
1007       /* If we have an anonymous tag, just return it directly */
1008       if (!name)
1009         return g_hash_table_lookup (info->anonymous_tags,
1010                                     GINT_TO_POINTER (id));
1011
1012       /* First, try the substitutions */
1013       real_name = g_hash_table_lookup (info->substitutions, name);
1014
1015       if (real_name)
1016         return gtk_text_tag_table_lookup (tag_table, real_name);
1017
1018       /* Next, try the list of defined tags */
1019       if (g_hash_table_lookup (info->defined_tags, name) != NULL)
1020         return gtk_text_tag_table_lookup (tag_table, name);
1021
1022       set_error (error, context,
1023                  G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1024                  _("Tag \"%s\" has not been defined."), name);
1025
1026       return NULL;
1027     }
1028   else
1029     {
1030       GtkTextTag *tag;
1031
1032       if (!name)
1033         {
1034           set_error (error, context,
1035                      G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1036                      _("Anonymous tag found and tags can not be created."));
1037           return NULL;
1038         }
1039
1040       tag = gtk_text_tag_table_lookup (tag_table, name);
1041
1042       if (tag)
1043         return tag;
1044
1045       set_error (error, context,
1046                  G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1047                  _("Tag \"%s\" does not exist in buffer and tags can not be created."), name);
1048
1049       return NULL;
1050     }
1051 }
1052
1053 typedef struct
1054 {
1055   const gchar *id;
1056   gint length;
1057   const gchar *start;
1058 } Header;
1059
1060 static GdkPixbuf *
1061 get_pixbuf_from_headers (GList   *headers,
1062                          int      id,
1063                          GError **error)
1064 {
1065   Header *header;
1066   GdkPixdata pixdata;
1067   GdkPixbuf *pixbuf;
1068
1069   header = g_list_nth_data (headers, id);
1070
1071   if (!header)
1072     return NULL;
1073
1074   if (!gdk_pixdata_deserialize (&pixdata, header->length,
1075                                 (const guint8 *) header->start, error))
1076     return NULL;
1077
1078   pixbuf = gdk_pixbuf_from_pixdata (&pixdata, TRUE, error);
1079
1080   return pixbuf;
1081 }
1082
1083 static void
1084 parse_apply_tag_element (GMarkupParseContext  *context,
1085                          const gchar          *element_name,
1086                          const gchar         **attribute_names,
1087                          const gchar         **attribute_values,
1088                          ParseInfo            *info,
1089                          GError              **error)
1090 {
1091   const gchar *name, *priority;
1092   gint id;
1093   GtkTextTag *tag;
1094
1095   g_assert (peek_state (info) == STATE_TEXT ||
1096             peek_state (info) == STATE_APPLY_TAG);
1097
1098   if (ELEMENT_IS ("apply_tag"))
1099     {
1100       if (!locate_attributes (context, element_name, attribute_names, attribute_values, TRUE, error,
1101                               "priority", &priority, NULL))
1102         return;
1103
1104       if (!check_id_or_name (context, element_name, attribute_names, attribute_values,
1105                              &id, &name, error))
1106         return;
1107
1108
1109       tag = tag_exists (context, name, id, info, error);
1110
1111       if (!tag)
1112         return;
1113
1114       info->tag_stack = g_slist_prepend (info->tag_stack, tag);
1115
1116       push_state (info, STATE_APPLY_TAG);
1117     }
1118   else if (ELEMENT_IS ("pixbuf"))
1119     {
1120       int int_id;
1121       GdkPixbuf *pixbuf;
1122       TextSpan *span;
1123       const gchar *pixbuf_id;
1124
1125       if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, error,
1126                               "index", &pixbuf_id, NULL))
1127         return;
1128
1129       int_id = atoi (pixbuf_id);
1130       pixbuf = get_pixbuf_from_headers (info->headers, int_id, error);
1131
1132       span = g_new0 (TextSpan, 1);
1133       span->pixbuf = pixbuf;
1134       span->tags = NULL;
1135
1136       info->spans = g_list_prepend (info->spans, span);
1137
1138       if (!pixbuf)
1139         return;
1140
1141       push_state (info, STATE_PIXBUF);
1142     }
1143   else
1144     set_error (error, context,
1145                G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1146                _("Element <%s> is not allowed below <%s>"),
1147                element_name, peek_state(info) == STATE_TEXT ? "text" : "apply_tag");
1148 }
1149
1150 static void
1151 parse_attr_element (GMarkupParseContext  *context,
1152                     const gchar          *element_name,
1153                     const gchar         **attribute_names,
1154                     const gchar         **attribute_values,
1155                     ParseInfo            *info,
1156                     GError              **error)
1157 {
1158   const gchar *name, *type, *value;
1159   GType gtype;
1160   GValue gvalue = { 0 };
1161   GParamSpec *pspec;
1162
1163   g_assert (peek_state (info) == STATE_TAG);
1164
1165   if (ELEMENT_IS ("attr"))
1166     {
1167       if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, error,
1168                               "name", &name, "type", &type, "value", &value, NULL))
1169         return;
1170
1171       gtype = g_type_from_name (type);
1172
1173       if (gtype == G_TYPE_INVALID)
1174         {
1175           set_error (error, context,
1176                      G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1177                      _("\"%s\" is not a valid attribute type"), type);
1178           return;
1179         }
1180
1181       if (!(pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info->current_tag), name)))
1182         {
1183           set_error (error, context,
1184                      G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1185                      _("\"%s\" is not a valid attribute name"), name);
1186           return;
1187         }
1188
1189       g_value_init (&gvalue, gtype);
1190
1191       if (!deserialize_value (value, &gvalue))
1192         {
1193           set_error (error, context,
1194                      G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1195                      _("\"%s\" could not be converted to a value of type \"%s\" for attribute \"%s\""),
1196                      value, type, name);
1197           return;
1198         }
1199
1200       if (g_param_value_validate (pspec, &gvalue))
1201         {
1202           set_error (error, context,
1203                      G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1204                      _("\"%s\" is not a valid value for attribute \"%s\""),
1205                      value, name);
1206           g_value_unset (&gvalue);
1207           return;
1208         }
1209
1210       g_object_set_property (G_OBJECT (info->current_tag),
1211                              name, &gvalue);
1212
1213       g_value_unset (&gvalue);
1214
1215       push_state (info, STATE_ATTR);
1216     }
1217   else
1218     {
1219       set_error (error, context,
1220                  G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1221                  _("Element <%s> is not allowed below <%s>"),
1222                  element_name, "tag");
1223     }
1224 }
1225
1226
1227 static gchar *
1228 get_tag_name (ParseInfo   *info,
1229               const gchar *tag_name)
1230 {
1231   GtkTextTagTable *tag_table;
1232   gchar *name;
1233   gint i;
1234
1235   name = g_strdup (tag_name);
1236
1237   if (!info->create_tags)
1238     return name;
1239
1240   i = 0;
1241   tag_table = gtk_text_buffer_get_tag_table (info->buffer);
1242
1243   while (gtk_text_tag_table_lookup (tag_table, name) != NULL)
1244     {
1245       g_free (name);
1246       name = g_strdup_printf ("%s-%d", tag_name, ++i);
1247     }
1248
1249   if (i != 0)
1250     {
1251       g_hash_table_insert (info->substitutions, g_strdup (tag_name), g_strdup (name));
1252     }
1253
1254   return name;
1255 }
1256
1257 static void
1258 parse_tag_element (GMarkupParseContext  *context,
1259                    const gchar          *element_name,
1260                    const gchar         **attribute_names,
1261                    const gchar         **attribute_values,
1262                    ParseInfo            *info,
1263                    GError              **error)
1264 {
1265   const gchar *name, *priority;
1266   gchar *tag_name;
1267   gint id;
1268   gint prio;
1269   gchar *tmp;
1270
1271   g_assert (peek_state (info) == STATE_TAGS);
1272
1273   if (ELEMENT_IS ("tag"))
1274     {
1275       if (!locate_attributes (context, element_name, attribute_names, attribute_values, TRUE, error,
1276                               "priority", &priority, NULL))
1277         return;
1278
1279       if (!check_id_or_name (context, element_name, attribute_names, attribute_values,
1280                              &id, &name, error))
1281         return;
1282
1283       if (name)
1284         {
1285           if (g_hash_table_lookup (info->defined_tags, name) != NULL)
1286             {
1287               set_error (error, context,
1288                          G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1289                          _("Tag \"%s\" already defined"), name);
1290               return;
1291             }
1292         }
1293
1294       prio = strtol (priority, &tmp, 10);
1295
1296       if (tmp == NULL || tmp == priority)
1297         {
1298           set_error (error, context,
1299                      G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1300                      _("Tag \"%s\" has invalid priority \"%s\""), name, priority);
1301           return;
1302         }
1303
1304       if (name)
1305         {
1306           tag_name = get_tag_name (info, name);
1307           info->current_tag = gtk_text_tag_new (tag_name);
1308           g_free (tag_name);
1309         }
1310       else
1311         {
1312           info->current_tag = gtk_text_tag_new (NULL);
1313           info->current_tag_id = id;
1314         }
1315
1316       info->current_tag_prio = prio;
1317
1318       push_state (info, STATE_TAG);
1319     }
1320   else
1321     {
1322       set_error (error, context,
1323                  G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1324                  _("Element <%s> is not allowed below <%s>"),
1325                  element_name, "tags");
1326     }
1327 }
1328
1329 static void
1330 start_element_handler (GMarkupParseContext  *context,
1331                        const gchar          *element_name,
1332                        const gchar         **attribute_names,
1333                        const gchar         **attribute_values,
1334                        gpointer              user_data,
1335                        GError              **error)
1336 {
1337   ParseInfo *info = user_data;
1338
1339   switch (peek_state (info))
1340     {
1341     case STATE_START:
1342       if (ELEMENT_IS ("text_view_markup"))
1343         {
1344           if (!check_no_attributes (context, element_name,
1345                                     attribute_names, attribute_values, error))
1346             return;
1347
1348           push_state (info, STATE_TEXT_VIEW_MARKUP);
1349           break;
1350         }
1351       else
1352         set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1353                    _("Outermost element in text must be <text_view_markup> not <%s>"),
1354                    element_name);
1355       break;
1356     case STATE_TEXT_VIEW_MARKUP:
1357       if (ELEMENT_IS ("tags"))
1358         {
1359           if (info->parsed_tags)
1360             {
1361               set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1362                          _("A <%s> element has already been specified"), "tags");
1363               return;
1364             }
1365
1366           if (!check_no_attributes (context, element_name,
1367                                     attribute_names, attribute_values, error))
1368             return;
1369
1370           push_state (info, STATE_TAGS);
1371           break;
1372         }
1373       else if (ELEMENT_IS ("text"))
1374         {
1375           if (info->parsed_text)
1376             {
1377               set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1378                          _("A <%s> element has already been specified"), "text");
1379               return;
1380             }
1381           else if (!info->parsed_tags)
1382             {
1383               set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1384                          _("A <text> element can't occur before a <tags> element"));
1385               return;
1386             }
1387
1388           if (!check_no_attributes (context, element_name,
1389                                     attribute_names, attribute_values, error))
1390             return;
1391
1392           push_state (info, STATE_TEXT);
1393           break;
1394         }
1395       else
1396         set_error (error, context,
1397                    G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
1398                  _("Element <%s> is not allowed below <%s>"),
1399                    element_name, "text_view_markup");
1400       break;
1401     case STATE_TAGS:
1402       parse_tag_element (context, element_name,
1403                          attribute_names, attribute_values,
1404                          info, error);
1405       break;
1406     case STATE_TAG:
1407       parse_attr_element (context, element_name,
1408                           attribute_names, attribute_values,
1409                           info, error);
1410       break;
1411     case STATE_TEXT:
1412     case STATE_APPLY_TAG:
1413       parse_apply_tag_element (context, element_name,
1414                                attribute_names, attribute_values,
1415                                info, error);
1416       break;
1417     default:
1418       g_assert_not_reached ();
1419       break;
1420     }
1421 }
1422
1423 static gint
1424 sort_tag_prio (TextTagPrio *a,
1425                TextTagPrio *b)
1426 {
1427   if (a->prio < b->prio)
1428     return -1;
1429   else if (a->prio > b->prio)
1430     return 1;
1431   else
1432     return 0;
1433 }
1434
1435 static void
1436 end_element_handler (GMarkupParseContext  *context,
1437                      const gchar          *element_name,
1438                      gpointer              user_data,
1439                      GError              **error)
1440 {
1441   ParseInfo *info = user_data;
1442   gchar *tmp;
1443   GList *list;
1444
1445   switch (peek_state (info))
1446     {
1447     case STATE_TAGS:
1448       pop_state (info);
1449       g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
1450
1451       info->parsed_tags = TRUE;
1452
1453       /* Sort list and add the tags */
1454       info->tag_priorities = g_list_sort (info->tag_priorities,
1455                                           (GCompareFunc)sort_tag_prio);
1456       list = info->tag_priorities;
1457       while (list)
1458         {
1459           TextTagPrio *prio = list->data;
1460
1461           if (info->create_tags)
1462             gtk_text_tag_table_add (gtk_text_buffer_get_tag_table (info->buffer),
1463                                     prio->tag);
1464
1465           g_object_unref (prio->tag);
1466           prio->tag = NULL;
1467
1468           list = list->next;
1469         }
1470
1471       break;
1472     case STATE_TAG:
1473       pop_state (info);
1474       g_assert (peek_state (info) == STATE_TAGS);
1475
1476       if (info->current_tag->name)
1477         {
1478           /* Add tag to defined tags hash */
1479           tmp = g_strdup (info->current_tag->name);
1480           g_hash_table_insert (info->defined_tags,
1481                                tmp, tmp);
1482         }
1483       else
1484         {
1485           g_hash_table_insert (info->anonymous_tags,
1486                                GINT_TO_POINTER (info->current_tag_id),
1487                                info->current_tag);
1488         }
1489
1490       if (info->create_tags)
1491         {
1492           TextTagPrio *prio;
1493
1494           /* add the tag to the list */
1495           prio = g_new0 (TextTagPrio, 1);
1496           prio->prio = info->current_tag_prio;
1497           prio->tag = info->current_tag;
1498
1499           info->tag_priorities = g_list_prepend (info->tag_priorities, prio);
1500         }
1501
1502       info->current_tag = NULL;
1503       break;
1504     case STATE_ATTR:
1505       pop_state (info);
1506       g_assert (peek_state (info) == STATE_TAG);
1507       break;
1508     case STATE_APPLY_TAG:
1509       pop_state (info);
1510       g_assert (peek_state (info) == STATE_APPLY_TAG ||
1511                 peek_state (info) == STATE_TEXT);
1512
1513       /* Pop tag */
1514       info->tag_stack = g_slist_delete_link (info->tag_stack,
1515                                              info->tag_stack);
1516
1517       break;
1518     case STATE_TEXT:
1519       pop_state (info);
1520       g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
1521
1522       info->spans = g_list_reverse (info->spans);
1523       info->parsed_text = TRUE;
1524       break;
1525     case STATE_TEXT_VIEW_MARKUP:
1526       pop_state (info);
1527       g_assert (peek_state (info) == STATE_START);
1528       break;
1529     case STATE_PIXBUF:
1530       pop_state (info);
1531       g_assert (peek_state (info) == STATE_APPLY_TAG ||
1532                 peek_state (info) == STATE_TEXT);
1533       break;
1534     default:
1535       g_assert_not_reached ();
1536       break;
1537     }
1538 }
1539
1540 static gboolean
1541 all_whitespace (const char *text,
1542                 int         text_len)
1543 {
1544   const char *p;
1545   const char *end;
1546
1547   p = text;
1548   end = text + text_len;
1549
1550   while (p != end)
1551     {
1552       if (!g_ascii_isspace (*p))
1553         return FALSE;
1554
1555       p = g_utf8_next_char (p);
1556     }
1557
1558   return TRUE;
1559 }
1560
1561 static void
1562 text_handler (GMarkupParseContext  *context,
1563               const gchar          *text,
1564               gsize                 text_len,
1565               gpointer              user_data,
1566               GError              **error)
1567 {
1568   ParseInfo *info = user_data;
1569   TextSpan *span;
1570
1571   if (all_whitespace (text, text_len) &&
1572       peek_state (info) != STATE_TEXT &&
1573       peek_state (info) != STATE_APPLY_TAG)
1574     return;
1575
1576   switch (peek_state (info))
1577     {
1578     case STATE_START:
1579       g_assert_not_reached (); /* gmarkup shouldn't do this */
1580       break;
1581     case STATE_TEXT:
1582     case STATE_APPLY_TAG:
1583       if (text_len == 0)
1584         return;
1585
1586       span = g_new0 (TextSpan, 1);
1587       span->text = g_strndup (text, text_len);
1588       span->tags = g_slist_copy (info->tag_stack);
1589
1590       info->spans = g_list_prepend (info->spans, span);
1591       break;
1592     default:
1593       g_assert_not_reached ();
1594       break;
1595     }
1596 }
1597
1598 static void
1599 parse_info_init (ParseInfo     *info,
1600                  GtkTextBuffer *buffer,
1601                  gboolean       create_tags,
1602                  GList         *headers)
1603 {
1604   info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
1605
1606   info->create_tags = create_tags;
1607   info->headers = headers;
1608   info->defined_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1609   info->substitutions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1610   info->anonymous_tags = g_hash_table_new_full (NULL, NULL, NULL, NULL);
1611   info->tag_stack = NULL;
1612   info->spans = NULL;
1613   info->parsed_text = FALSE;
1614   info->parsed_tags = FALSE;
1615   info->current_tag = NULL;
1616   info->current_tag_prio = -1;
1617   info->tag_priorities = NULL;
1618
1619   info->buffer = buffer;
1620 }
1621
1622 static void
1623 text_span_free (TextSpan *span)
1624 {
1625   g_free (span->text);
1626   g_slist_free (span->tags);
1627   g_free (span);
1628 }
1629
1630 static void
1631 parse_info_free (ParseInfo *info)
1632 {
1633   GList *list;
1634
1635   g_slist_free (info->tag_stack);
1636   g_slist_free (info->states);
1637
1638   g_hash_table_destroy (info->substitutions);
1639   g_hash_table_destroy (info->defined_tags);
1640
1641   if (info->current_tag)
1642     g_object_unref (info->current_tag);
1643
1644   list = info->spans;
1645   while (list)
1646     {
1647       text_span_free (list->data);
1648
1649       list = list->next;
1650     }
1651   g_list_free (info->spans);
1652
1653   list = info->tag_priorities;
1654   while (list)
1655     {
1656       TextTagPrio *prio = list->data;
1657
1658       if (prio->tag)
1659         g_object_unref (prio->tag);
1660       g_free (prio);
1661
1662       list = list->next;
1663     }
1664   g_list_free (info->tag_priorities);
1665
1666 }
1667
1668 static void
1669 insert_text (ParseInfo   *info,
1670              GtkTextIter *iter)
1671 {
1672   GtkTextIter start_iter;
1673   GtkTextMark *mark;
1674   GList *tmp;
1675   GSList *tags;
1676
1677   start_iter = *iter;
1678
1679   mark = gtk_text_buffer_create_mark (info->buffer, "deserialize_insert_point",
1680                                       &start_iter, TRUE);
1681
1682   tmp = info->spans;
1683   while (tmp)
1684     {
1685       TextSpan *span = tmp->data;
1686
1687       if (span->text)
1688         gtk_text_buffer_insert (info->buffer, iter, span->text, -1);
1689       else
1690         {
1691           gtk_text_buffer_insert_pixbuf (info->buffer, iter, span->pixbuf);
1692           g_object_unref (span->pixbuf);
1693         }
1694       gtk_text_buffer_get_iter_at_mark (info->buffer, &start_iter, mark);
1695
1696       /* Apply tags */
1697       tags = span->tags;
1698       while (tags)
1699         {
1700           GtkTextTag *tag = tags->data;
1701
1702           gtk_text_buffer_apply_tag (info->buffer, tag,
1703                                      &start_iter, iter);
1704
1705           tags = tags->next;
1706         }
1707
1708       gtk_text_buffer_move_mark (info->buffer, mark, iter);
1709
1710       tmp = tmp->next;
1711     }
1712
1713   gtk_text_buffer_delete_mark (info->buffer, mark);
1714 }
1715
1716
1717
1718 static int
1719 read_int (const guchar *start)
1720 {
1721   int result;
1722
1723   result =
1724     start[0] << 24 |
1725     start[1] << 16 |
1726     start[2] << 8 |
1727     start[3];
1728
1729   return result;
1730 }
1731
1732 static gboolean
1733 header_is (Header      *header,
1734            const gchar *id)
1735 {
1736   return (strncmp (header->id, id, strlen (id)) == 0);
1737 }
1738
1739 static GList *
1740 read_headers (const gchar *start,
1741               gint         len,
1742               GError     **error)
1743 {
1744   int i = 0;
1745   int section_len;
1746   Header *header;
1747   GList *headers = NULL;
1748
1749   while (i < len)
1750     {
1751       if (i + 30 >= len)
1752         goto error;
1753
1754       if (strncmp (start + i, "GTKTEXTBUFFERCONTENTS-0001", 26) == 0 ||
1755           strncmp (start + i, "GTKTEXTBUFFERPIXBDATA-0001", 26) == 0)
1756         {
1757           section_len = read_int ((const guchar *) start + i + 26);
1758
1759           if (i + 30 + section_len > len)
1760             goto error;
1761
1762           header = g_new0 (Header, 1);
1763           header->id = start + i;
1764           header->length = section_len;
1765           header->start = start + i + 30;
1766
1767           i += 30 + section_len;
1768
1769           headers = g_list_prepend (headers, header);
1770         }
1771       else
1772         break;
1773     }
1774
1775   return g_list_reverse (headers);
1776
1777  error:
1778   g_list_foreach (headers, (GFunc) g_free, NULL);
1779   g_list_free (headers);
1780
1781   g_set_error_literal (error,
1782                        G_MARKUP_ERROR,
1783                        G_MARKUP_ERROR_PARSE,
1784                        _("Serialized data is malformed"));
1785
1786   return NULL;
1787 }
1788
1789 static gboolean
1790 deserialize_text (GtkTextBuffer *buffer,
1791                   GtkTextIter   *iter,
1792                   const gchar   *text,
1793                   gint           len,
1794                   gboolean       create_tags,
1795                   GError       **error,
1796                   GList         *headers)
1797 {
1798   GMarkupParseContext *context;
1799   ParseInfo info;
1800   gboolean retval = FALSE;
1801
1802   static const GMarkupParser rich_text_parser = {
1803     start_element_handler,
1804     end_element_handler,
1805     text_handler,
1806     NULL,
1807     NULL
1808   };
1809
1810   parse_info_init (&info, buffer, create_tags, headers);
1811
1812   context = g_markup_parse_context_new (&rich_text_parser,
1813                                         0, &info, NULL);
1814
1815   if (!g_markup_parse_context_parse (context,
1816                                      text,
1817                                      len,
1818                                      error))
1819     goto out;
1820
1821   if (!g_markup_parse_context_end_parse (context, error))
1822     goto out;
1823
1824   retval = TRUE;
1825
1826   /* Now insert the text */
1827   insert_text (&info, iter);
1828
1829  out:
1830   parse_info_free (&info);
1831
1832   g_markup_parse_context_free (context);
1833
1834   return retval;
1835 }
1836
1837 gboolean
1838 _gtk_text_buffer_deserialize_rich_text (GtkTextBuffer *register_buffer,
1839                                         GtkTextBuffer *content_buffer,
1840                                         GtkTextIter   *iter,
1841                                         const guint8  *text,
1842                                         gsize          length,
1843                                         gboolean       create_tags,
1844                                         gpointer       user_data,
1845                                         GError       **error)
1846 {
1847   GList *headers;
1848   Header *header;
1849   gboolean retval;
1850
1851   headers = read_headers ((gchar *) text, length, error);
1852
1853   if (!headers)
1854     return FALSE;
1855
1856   header = headers->data;
1857   if (!header_is (header, "GTKTEXTBUFFERCONTENTS-0001"))
1858     {
1859       g_set_error_literal (error,
1860                            G_MARKUP_ERROR,
1861                            G_MARKUP_ERROR_PARSE,
1862                            _("Serialized data is malformed. First section isn't GTKTEXTBUFFERCONTENTS-0001"));
1863
1864       retval = FALSE;
1865       goto out;
1866     }
1867
1868   retval = deserialize_text (content_buffer, iter,
1869                              header->start, header->length,
1870                              create_tags, error, headers->next);
1871
1872  out:
1873   g_list_foreach (headers, (GFunc)g_free, NULL);
1874   g_list_free (headers);
1875
1876   return retval;
1877 }