generic-poky/openembedded/packages/gtk+/gtk+-2.6.4-1.osso7/gtktextbufferserialize.c.diff

1689 lines
40 KiB
Diff

--- gtk+-2.6.4/gtk/gtktextbufferserialize.c 1970-01-01 02:00:00.000000000 +0200
+++ gtk+-2.6.4/gtk/gtktextbufferserialize.c 2005-04-06 16:19:38.024757720 +0300
@@ -0,0 +1,1685 @@
+/* gtktextbufferserialize.c
+ *
+ * Copyright (C) 2001 Havoc Pennington
+ * Copyright (C) 2004 Nokia
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* FIXME: We should use other error codes for the
+ * parts that deal with the format errors
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include "gdk-pixbuf/gdk-pixdata.h"
+#include "gtktextbufferserialize.h"
+#include "gtkintl.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+typedef struct
+{
+ GString *tag_table_str;
+ GString *text_str;
+ GHashTable *tags;
+ GtkTextIter start, end;
+
+ gint n_pixbufs;
+ GList *pixbufs;
+} SerializationContext;
+
+static gchar *
+serialize_value (GValue *value)
+{
+ if (g_value_type_transformable (value->g_type, G_TYPE_STRING))
+ {
+ GValue text_value = { 0 };
+ gchar *tmp;
+
+ g_value_init (&text_value, G_TYPE_STRING);
+ g_value_transform (value, &text_value);
+
+ tmp = g_markup_escape_text (g_value_get_string (&text_value), -1);
+ g_value_unset (&text_value);
+
+ return tmp;
+ }
+ else if (value->g_type == GDK_TYPE_COLOR)
+ {
+ GdkColor *color = g_value_get_boxed (value);
+
+ return g_strdup_printf ("%x:%x:%x", color->red, color->green, color->blue);
+ }
+ else
+ {
+ g_warning ("Type %s is not serializable\n", g_type_name (value->g_type));
+ }
+
+ return NULL;
+}
+
+static gboolean
+deserialize_value (const gchar *str, GValue *value)
+{
+ if (g_value_type_transformable (G_TYPE_STRING, value->g_type))
+ {
+ GValue text_value = { 0 };
+ gboolean retval;
+
+ g_value_init (&text_value, G_TYPE_STRING);
+ g_value_set_static_string (&text_value, str);
+
+ retval = g_value_transform (&text_value, value);
+ g_value_unset (&text_value);
+
+ return retval;
+ }
+ else if (value->g_type == G_TYPE_BOOLEAN)
+ {
+ gboolean v;
+
+ v = strcmp (str, "TRUE") == 0;
+
+ g_value_set_boolean (value, v);
+
+ return TRUE;
+ }
+ else if (value->g_type == G_TYPE_INT)
+ {
+ gchar *tmp;
+ int v;
+
+ v = strtol (str, &tmp, 10);
+
+ if (tmp == NULL || tmp == str)
+ return FALSE;
+
+ g_value_set_int (value, v);
+
+ return TRUE;
+ }
+ else if (value->g_type == G_TYPE_DOUBLE)
+ {
+ gchar *tmp;
+ gdouble v;
+
+ v = g_ascii_strtod (str, &tmp);
+
+ if (tmp == NULL || tmp == str)
+ return FALSE;
+
+ g_value_set_double (value, v);
+
+ return TRUE;
+ }
+ else if (value->g_type == GDK_TYPE_COLOR)
+ {
+ GdkColor color;
+ const gchar *old;
+ gchar *tmp;
+
+ old = str;
+ color.red = strtol (old, &tmp, 16);
+
+ if (tmp == NULL || tmp == old)
+ return FALSE;
+
+ old = tmp;
+ if (*old++ != ':')
+ return FALSE;
+
+ color.green = strtol (old, &tmp, 16);
+ if (tmp == NULL || tmp == old)
+ return FALSE;
+
+ old = tmp;
+ if (*old++ != ':')
+ return FALSE;
+
+ color.blue = strtol (old, &tmp, 16);
+
+ if (tmp == NULL || tmp == old || *tmp != '\0')
+ return FALSE;
+
+ g_value_set_boxed (value, &color);
+
+ return TRUE;
+ }
+ else if (G_VALUE_HOLDS_ENUM (value))
+ {
+ GEnumClass *class = G_ENUM_CLASS (g_type_class_peek (value->g_type));
+ GEnumValue *enum_value;
+
+ enum_value = g_enum_get_value_by_name (class, str);
+
+ if (enum_value)
+ {
+ g_value_set_enum (value, enum_value->value);
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else
+ {
+ g_warning ("Type %s can not be deserialized\n", g_type_name (value->g_type));
+ }
+
+ return FALSE;
+}
+
+/* Checks if a param is set, or if it's the default value */
+static gboolean
+is_param_set (GObject *object, GParamSpec *pspec, GValue *value)
+{
+ /* We need to special case some attributes here */
+ if (strcmp (pspec->name, "background-gdk") == 0)
+ {
+ gboolean is_set;
+
+ g_object_get (object, "background-set", &is_set, NULL);
+
+ if (is_set)
+ {
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ g_object_get_property (object, pspec->name, value);
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else if (strcmp (pspec->name, "foreground-gdk") == 0)
+ {
+ gboolean is_set;
+
+ g_object_get (object, "foreground-set", &is_set, NULL);
+
+ if (is_set)
+ {
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ g_object_get_property (object, pspec->name, value);
+
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+ else
+ {
+ gboolean is_set;
+ gchar *is_set_name;
+
+ is_set_name = g_strdup_printf ("%s-set", pspec->name);
+
+ if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), is_set_name) == NULL)
+ {
+ g_free (is_set_name);
+ return FALSE;
+ }
+ else
+ {
+ g_object_get (object, is_set_name, &is_set, NULL);
+
+ if (!is_set)
+ {
+ g_free (is_set_name);
+ return FALSE;
+ }
+
+ g_free (is_set_name);
+
+ g_value_init (value, G_PARAM_SPEC_VALUE_TYPE (pspec));
+
+ g_object_get_property (object, pspec->name, value);
+
+ if (g_param_value_defaults (pspec, value))
+ {
+ g_value_unset (value);
+
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+}
+
+static void
+serialize_tag (gpointer key, gpointer data, gpointer user_data)
+{
+ SerializationContext *context = user_data;
+ GtkTextTag *tag = data;
+ gchar *tag_name;
+ GParamSpec **pspecs;
+ guint n_pspecs;
+ int i;
+
+ tag_name = g_markup_escape_text (tag->name, -1);
+ g_string_append_printf (context->tag_table_str, " <tag name=\"%s\" priority=\"%d\">\n", tag_name, tag->priority);
+
+ /* Serialize properties */
+ pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (tag), &n_pspecs);
+
+ for (i = 0; i < n_pspecs; i++)
+ {
+ GValue value = { 0 };
+ gchar *tmp, *tmp2;
+
+ if (!(pspecs[i]->flags & G_PARAM_READABLE) ||
+ !(pspecs[i]->flags & G_PARAM_WRITABLE))
+ continue;
+
+ if (!is_param_set (G_OBJECT (tag), pspecs[i], &value))
+ continue;
+
+ /* Now serialize the attr */
+ tmp = g_markup_escape_text (pspecs[i]->name, -1);
+ g_string_append_printf (context->tag_table_str, " <attr name=\"%s\" ", tmp);
+ g_free (tmp);
+
+ tmp = g_markup_escape_text (g_type_name (pspecs[i]->value_type), -1);
+ tmp2 = serialize_value (&value);
+ g_string_append_printf (context->tag_table_str, "type=\"%s\" value=\"%s\" />\n", tmp, tmp2);
+
+ g_free (tmp);
+ g_free (tmp2);
+
+ g_value_unset (&value);
+ }
+
+ g_free (pspecs);
+
+ g_string_append (context->tag_table_str, " </tag>\n");
+ g_free (tag_name);
+}
+
+static void
+serialize_tags (SerializationContext *context)
+{
+ g_string_append (context->tag_table_str, " <text_view_markup>\n");
+ g_string_append (context->tag_table_str, " <tags>\n");
+ g_hash_table_foreach (context->tags, serialize_tag, context);
+ g_string_append (context->tag_table_str, " </tags>\n");
+}
+
+#if 0
+static void
+dump_tag_list (const gchar *str, GList *list)
+{
+ g_print ("%s: ", str);
+
+ if (!list)
+ g_print ("(empty)");
+ else
+ {
+ while (list)
+ {
+ g_print ("%s ", ((GtkTextTag *)list->data)->name);
+ list = list->next;
+ }
+ }
+
+ g_print ("\n");
+}
+#endif
+
+static void
+find_list_delta (GSList *old_list, GSList *new_list,
+ GList **added, GList **removed)
+{
+ GSList *tmp;
+ GList *tmp_added, *tmp_removed;
+
+ tmp_added = NULL;
+ tmp_removed = NULL;
+
+ /* Find added tags */
+ tmp = new_list;
+ while (tmp)
+ {
+ if (!g_slist_find (old_list, tmp->data))
+ tmp_added = g_list_prepend (tmp_added, tmp->data);
+
+ tmp = tmp->next;
+ }
+
+ *added = tmp_added;
+
+ /* Find removed tags */
+ tmp = old_list;
+ while (tmp)
+ {
+ if (!g_slist_find (new_list, tmp->data))
+ tmp_removed = g_list_prepend (tmp_removed, tmp->data);
+
+ tmp = tmp->next;
+ }
+
+ /* We reverse the list here to match the xml semantics */
+ *removed = g_list_reverse (tmp_removed);
+}
+
+static void
+serialize_section_header (GString *str,
+ const gchar *name,
+ gint length)
+{
+ g_return_if_fail (strlen (name) == 8);
+
+ g_string_append (str, name);
+
+ g_string_append_c (str, length >> 24);
+
+ g_string_append_c (str, (length >> 16) & 0xff);
+ g_string_append_c (str, (length >> 8) & 0xff);
+ g_string_append_c (str, length & 0xff);
+}
+
+static void
+serialize_text (GtkTextBuffer *buffer, SerializationContext *context)
+{
+ GtkTextIter iter, old_iter;
+ GSList *tag_list, *new_tag_list;
+ GQueue *active_tags;
+ int i;
+
+ g_string_append (context->text_str, "<text>");
+
+ iter = context->start;
+ tag_list = NULL;
+ active_tags = g_queue_new ();
+
+ do
+ {
+ GList *added, *removed;
+ GList *tmp;
+ gchar *tmp_text, *escaped_text;
+
+ new_tag_list = gtk_text_iter_get_tags (&iter);
+ find_list_delta (tag_list, new_tag_list, &added, &removed);
+
+ /* Handle removed tags */
+ tmp = removed;
+ while (tmp)
+ {
+ GtkTextTag *tag = tmp->data;
+
+ g_string_append (context->text_str, "</apply_tag>");
+
+ /* We might need to drop some of the tags and re-add them afterwards */
+ while (g_queue_peek_head (active_tags) != tag &&
+ !g_queue_is_empty (active_tags))
+ {
+ added = g_list_prepend (added, g_queue_pop_head (active_tags));
+ g_string_append_printf (context->text_str, "</apply_tag>");
+ }
+
+ g_queue_pop_head (active_tags);
+
+ tmp = tmp->next;
+ }
+
+ /* Handle added tags */
+ tmp = added;
+ while (tmp)
+ {
+ GtkTextTag *tag = tmp->data;
+ gchar *tag_name;
+
+ /* Add it to the tag hash table */
+ g_hash_table_insert (context->tags, tag, tag);
+
+ tag_name = g_markup_escape_text (tag->name, -1);
+
+ g_string_append_printf (context->text_str, "<apply_tag name=\"%s\">", tag_name);
+ g_free (tag_name);
+
+ g_queue_push_head (active_tags, tag);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (tag_list);
+ tag_list = new_tag_list;
+
+ old_iter = iter;
+
+ /* Now try to go to either the next tag toggle, or if a pixbuf appears */
+ while (TRUE)
+ {
+ gunichar ch = gtk_text_iter_get_char (&iter);
+
+ if (ch == 0xFFFC)
+ {
+ GdkPixbuf *pixbuf = gtk_text_iter_get_pixbuf (&iter);
+
+ if (pixbuf) {
+ g_string_append_printf (context->text_str, "<pixbuf index=\"%d\" />", context->n_pixbufs);
+
+ context->n_pixbufs++;
+ context->pixbufs = g_list_prepend (context->pixbufs, pixbuf);
+ }
+ }
+
+ gtk_text_iter_forward_char (&iter);
+
+ if (gtk_text_iter_toggles_tag (&iter, NULL))
+ break;
+ }
+
+ /* We might have moved too far */
+ if (gtk_text_iter_compare (&iter, &context->end) > 0)
+ iter = context->end;
+
+ /* Append the text */
+ tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
+ escaped_text = g_markup_escape_text (tmp_text, -1);
+ g_free (tmp_text);
+
+ g_string_append (context->text_str, escaped_text);
+ g_free (escaped_text);
+ }
+ while (!gtk_text_iter_equal (&iter, &context->end));
+
+ /* Close any open tags */
+ for (i = 0; i < g_queue_get_length (active_tags); i++) {
+ g_string_append (context->text_str, "</apply_tag>");
+ }
+ g_queue_free (active_tags);
+ g_string_append (context->text_str, "</text>\n</text_view_markup>\n");
+}
+
+static void
+serialize_pixbufs (SerializationContext *context,
+ GString *text)
+{
+ GList *list;
+
+ for (list = context->pixbufs; list != NULL; list = list->next)
+ {
+ GdkPixbuf *pixbuf = list->data;
+ GdkPixdata pixdata;
+ guint8 *tmp;
+ guint len;
+
+ gdk_pixdata_from_pixbuf (&pixdata, pixbuf, FALSE);
+ tmp = gdk_pixdata_serialize (&pixdata, &len);
+
+ serialize_section_header (text, "PDPIXBUF", len);
+ g_string_append_len (text, tmp, len);
+ g_free (tmp);
+ }
+}
+
+gchar *
+gtk_text_buffer_serialize_rich_text (GtkTextBuffer *buffer,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gint *len)
+{
+ SerializationContext context;
+ GString *text;
+
+ context.tags = g_hash_table_new (NULL, NULL);
+ context.text_str = g_string_new (NULL);
+ context.tag_table_str = g_string_new (NULL);
+ context.start = *start;
+ context.end = *end;
+ context.n_pixbufs = 0;
+ context.pixbufs = NULL;
+
+ /* We need to serialize the text before the tag table so we know
+ what tags are used */
+ serialize_text (buffer, &context);
+ serialize_tags (&context);
+
+ text = g_string_new (NULL);
+ serialize_section_header (text, "RICHTEXT", context.tag_table_str->len + context.text_str->len);
+
+ g_print ("when serializing length is: %d\n", context.tag_table_str->len + context.text_str->len);
+
+ g_string_append_len (text, context.tag_table_str->str, context.tag_table_str->len);
+ g_string_append_len (text, context.text_str->str, context.text_str->len);
+
+ context.pixbufs = g_list_reverse (context.pixbufs);
+ serialize_pixbufs (&context, text);
+
+ g_hash_table_destroy (context.tags);
+ g_list_free (context.pixbufs);
+ g_string_free (context.text_str, TRUE);
+ g_string_free (context.tag_table_str, TRUE);
+
+ *len = text->len;
+
+ return g_string_free (text, FALSE);
+}
+
+typedef enum
+{
+ STATE_START,
+ STATE_TEXT_VIEW_MARKUP,
+ STATE_TAGS,
+ STATE_TAG,
+ STATE_ATTR,
+ STATE_TEXT,
+ STATE_APPLY_TAG,
+ STATE_PIXBUF
+} ParseState;
+
+typedef struct
+{
+ gchar *text;
+ GdkPixbuf *pixbuf;
+ GSList *tags;
+} TextSpan;
+
+typedef struct
+{
+ GtkTextTag *tag;
+ gint prio;
+} TextTagPrio;
+
+typedef struct
+{
+ GSList *states;
+
+ GList *headers;
+
+ GtkTextBuffer *buffer;
+
+ /* Tags that are defined in <tag> elements */
+ GHashTable *defined_tags;
+
+ /* Tag name substitutions */
+ GHashTable *substitutions;
+
+ /* Current tag */
+ GtkTextTag *current_tag;
+
+ /* Priority of current tag */
+ gint current_tag_prio;
+
+ /* Tags and their priorities */
+ GList *tag_priorities;
+
+ GSList *tag_stack;
+
+ GList *spans;
+
+ gboolean create_tags;
+
+ gboolean parsed_text;
+ gboolean parsed_tags;
+} ParseInfo;
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ int line, ch;
+ va_list args;
+ char *str;
+
+ g_markup_parse_context_get_position (context, &line, &ch);
+
+ va_start (args, format);
+ str = g_strdup_vprintf (format, args);
+ va_end (args);
+
+ g_set_error (err, error_domain, error_code,
+ ("Line %d character %d: %s"),
+ line, ch, str);
+
+ g_free (str);
+}
+
+static void
+push_state (ParseInfo *info,
+ ParseState state)
+{
+ info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
+}
+
+static void
+pop_state (ParseInfo *info)
+{
+ g_return_if_fail (info->states != NULL);
+
+ info->states = g_slist_remove (info->states, info->states->data);
+}
+
+static ParseState
+peek_state (ParseInfo *info)
+{
+ g_return_val_if_fail (info->states != NULL, STATE_START);
+
+ return GPOINTER_TO_INT (info->states->data);
+}
+
+#define ELEMENT_IS(name) (strcmp (element_name, (name)) == 0)
+
+typedef struct
+{
+ const char *name;
+ const char **retloc;
+} LocateAttr;
+
+static gboolean
+locate_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error,
+ const char *first_attribute_name,
+ const char **first_attribute_retloc,
+ ...)
+{
+ va_list args;
+ const char *name;
+ const char **retloc;
+ int n_attrs;
+#define MAX_ATTRS 24
+ LocateAttr attrs[MAX_ATTRS];
+ gboolean retval;
+ int i;
+
+ g_return_val_if_fail (first_attribute_name != NULL, FALSE);
+ g_return_val_if_fail (first_attribute_retloc != NULL, FALSE);
+
+ retval = TRUE;
+
+ n_attrs = 1;
+ attrs[0].name = first_attribute_name;
+ attrs[0].retloc = first_attribute_retloc;
+ *first_attribute_retloc = NULL;
+
+ va_start (args, first_attribute_retloc);
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+
+ while (name != NULL)
+ {
+ g_return_val_if_fail (retloc != NULL, FALSE);
+
+ g_assert (n_attrs < MAX_ATTRS);
+
+ attrs[n_attrs].name = name;
+ attrs[n_attrs].retloc = retloc;
+ n_attrs += 1;
+ *retloc = NULL;
+
+ name = va_arg (args, const char*);
+ retloc = va_arg (args, const char**);
+ }
+
+ va_end (args);
+
+ if (!retval)
+ return retval;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ int j;
+ gboolean found;
+
+ found = FALSE;
+ j = 0;
+ while (j < n_attrs)
+ {
+ if (strcmp (attrs[j].name, attribute_names[i]) == 0)
+ {
+ retloc = attrs[j].retloc;
+
+ if (*retloc != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" repeated twice on the same <%s> element"),
+ attrs[j].name, element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ *retloc = attribute_values[i];
+ found = TRUE;
+ }
+
+ ++j;
+ }
+
+ if (!found)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" is invalid on <%s> element in this context"),
+ attribute_names[i], element_name);
+ retval = FALSE;
+ goto out;
+ }
+
+ ++i;
+ }
+
+ out:
+ return retval;
+}
+
+static gboolean
+check_no_attributes (GMarkupParseContext *context,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ GError **error)
+{
+ if (attribute_names[0] != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Attribute \"%s\" is invalid on <%s> element in this context"),
+ attribute_names[0], element_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static const gchar *
+tag_exists (GMarkupParseContext *context,
+ const gchar *name,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *real_name;
+
+ if (info->create_tags)
+ {
+ /* First, try the substitutions */
+ real_name = g_hash_table_lookup (info->substitutions, name);
+
+ if (real_name)
+ return real_name;
+
+ /* Next, try the list of defined tags */
+ if (g_hash_table_lookup (info->defined_tags, name) != NULL)
+ return name;
+
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" has not been defined."), name);
+
+ return NULL;
+ }
+ else
+ {
+ if (gtk_text_tag_table_lookup (info->buffer->tag_table, name) != NULL)
+ return name;
+
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" does not exist in buffer and tags can not be created."), name);
+
+ return NULL;
+ }
+}
+
+typedef struct
+{
+ const gchar *id;
+ gint length;
+ const gchar *start;
+} Header;
+
+static GdkPixbuf *
+get_pixbuf_from_headers (GList *headers, int id, GError **error)
+{
+ Header *header;
+ GdkPixdata pixdata;
+ GdkPixbuf *pixbuf;
+
+ header = g_list_nth_data (headers, id);
+
+ if (!header)
+ return NULL;
+
+ if (!gdk_pixdata_deserialize (&pixdata, header->length, header->start, error))
+ return NULL;
+
+ pixbuf = gdk_pixbuf_from_pixdata (&pixdata, TRUE, error);
+
+ g_print ("pixbuf is: %p\n", pixbuf);
+
+ return pixbuf;
+}
+
+static void
+parse_apply_tag_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *name, *tag_name, *id;
+
+ g_assert (peek_state (info) == STATE_TEXT ||
+ peek_state (info) == STATE_APPLY_TAG);
+
+ if (ELEMENT_IS ("apply_tag"))
+ {
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, error,
+ "name", &name, NULL))
+ return;
+
+ tag_name = tag_exists (context, name, info, error);
+
+ if (!tag_name)
+ return;
+
+ info->tag_stack = g_slist_prepend (info->tag_stack, g_strdup (tag_name));
+
+ push_state (info, STATE_APPLY_TAG);
+ }
+ else if (ELEMENT_IS ("pixbuf"))
+ {
+ int int_id;
+ GdkPixbuf *pixbuf;
+ TextSpan *span;
+
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, error,
+ "index", &id, NULL))
+ return;
+
+ int_id = atoi (id);
+ pixbuf = get_pixbuf_from_headers (info->headers, int_id, error);
+
+ span = g_new0 (TextSpan, 1);
+ span->pixbuf = pixbuf;
+ span->tags = NULL;
+
+ info->spans = g_list_prepend (info->spans, span);
+
+ if (!pixbuf)
+ return;
+
+ push_state (info, STATE_PIXBUF);
+ }
+ else
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, peek_state(info) == STATE_TEXT ? "text" : "apply_tag");
+}
+
+static void
+parse_attr_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *name, *type, *value;
+ GType gtype;
+ GValue gvalue = { 0 };
+ GParamSpec *pspec;
+
+ g_assert (peek_state (info) == STATE_TAG);
+
+ if (ELEMENT_IS ("attr"))
+ {
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, error,
+ "name", &name, "type", &type, "value", &value, NULL))
+ return;
+
+ gtype = g_type_from_name (type);
+
+ if (gtype == G_TYPE_INVALID)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid attribute type"), type);
+ return;
+ }
+
+ if (!(pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (info->current_tag), name)))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid attribute name"), name);
+ return;
+ }
+
+ g_value_init (&gvalue, gtype);
+
+ if (!deserialize_value (value, &gvalue))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" could not be converted to a value of type \"%s\" for attribute \"%s\""),
+ value, type, name);
+ return;
+ }
+
+ if (g_param_value_validate (pspec, &gvalue))
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("\"%s\" is not a valid value of for attribute \"%s\""),
+ value, name);
+ g_value_unset (&gvalue);
+ return;
+ }
+
+ g_object_set_property (G_OBJECT (info->current_tag),
+ name, &gvalue);
+
+ g_value_unset (&gvalue);
+
+ push_state (info, STATE_ATTR);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "tag");
+ }
+}
+
+
+static gchar *
+get_tag_name (ParseInfo *info,
+ const gchar *tag_name)
+{
+ gchar *name;
+ gint i;
+
+ name = g_strdup (tag_name);
+
+ if (!info->create_tags)
+ return name;
+
+ i = 0;
+
+ while (gtk_text_tag_table_lookup (info->buffer->tag_table, name) != NULL)
+ {
+ g_free (name);
+ name = g_strdup_printf ("%s-%d", tag_name, ++i);
+ }
+
+ if (i != 0)
+ {
+ g_hash_table_insert (info->substitutions, g_strdup (tag_name), g_strdup (name));
+ }
+
+ return name;
+}
+
+static void
+parse_tag_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ const gchar *name, *priority;
+ gchar *tag_name;
+ gint prio;
+ gchar *tmp;
+
+ g_assert (peek_state (info) == STATE_TAGS);
+
+ if (ELEMENT_IS ("tag"))
+ {
+ if (!locate_attributes (context, element_name, attribute_names, attribute_values, error,
+ "name", &name, "priority", &priority, NULL))
+ return;
+
+ if (g_hash_table_lookup (info->defined_tags, name) != NULL)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" already defined"), name);
+ return;
+ }
+
+ prio = strtol (priority, &tmp, 10);
+
+ if (tmp == NULL || tmp == priority)
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Tag \"%s\" has invalid priority \"%s\""), name, priority);
+ return;
+ }
+
+ tag_name = get_tag_name (info, name);
+ info->current_tag = gtk_text_tag_new (tag_name);
+ info->current_tag_prio = prio;
+
+ g_free (tag_name);
+
+ push_state (info, STATE_TAG);
+ }
+ else
+ {
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "tags");
+ }
+}
+
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ if (ELEMENT_IS ("text_view_markup"))
+ {
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values, error))
+ return;
+
+ push_state (info, STATE_TEXT_VIEW_MARKUP);
+ break;
+ }
+ else
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Outermost element in text must be <text_view_markup> not <%s>"),
+ element_name);
+ break;
+ case STATE_TEXT_VIEW_MARKUP:
+ if (ELEMENT_IS ("tags"))
+ {
+ if (info->parsed_tags)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A <tags> element has already been specified"));
+ return;
+ }
+
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values, error))
+ return;
+
+ push_state (info, STATE_TAGS);
+ break;
+ }
+ else if (ELEMENT_IS ("text"))
+ {
+ if (info->parsed_text)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A <text> element has already been specified"));
+ return;
+ }
+ else if (!info->parsed_tags)
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("A <text> element can't occur before a <tags> element"));
+ return;
+ }
+
+ if (!check_no_attributes (context, element_name,
+ attribute_names, attribute_values, error))
+ return;
+
+ push_state (info, STATE_TEXT);
+ break;
+ }
+ else
+ set_error (error, context,
+ G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Element <%s> is not allowed below <%s>"),
+ element_name, "text_view_markup");
+ break;
+ case STATE_TAGS:
+ parse_tag_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_TAG:
+ parse_attr_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ case STATE_TEXT:
+ case STATE_APPLY_TAG:
+ parse_apply_tag_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gint
+sort_tag_prio (TextTagPrio *a,
+ TextTagPrio *b)
+{
+ if (a->prio < b->prio)
+ return -1;
+ else if (a->prio > b->prio)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+ gchar *tmp;
+ GList *list;
+
+ switch (peek_state (info))
+ {
+ case STATE_TAGS:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
+
+ info->parsed_tags = TRUE;
+
+ /* Sort list and add the tags */
+ info->tag_priorities = g_list_sort (info->tag_priorities,
+ (GCompareFunc)sort_tag_prio);
+ list = info->tag_priorities;
+ while (list)
+ {
+ TextTagPrio *prio = list->data;
+
+ if (info->create_tags)
+ gtk_text_tag_table_add (info->buffer->tag_table, prio->tag);
+
+ g_object_unref (prio->tag);
+ prio->tag = NULL;
+
+ list = list->next;
+ }
+
+ break;
+ case STATE_TAG:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TAGS);
+
+ /* Add tag to defined tags hash */
+ tmp = g_strdup (info->current_tag->name);
+ g_hash_table_insert (info->defined_tags,
+ tmp, tmp);
+
+ if (info->create_tags)
+ {
+ TextTagPrio *prio;
+
+ /* add the tag to the list */
+ prio = g_new0 (TextTagPrio, 1);
+ prio->prio = info->current_tag_prio;
+ prio->tag = info->current_tag;
+
+ info->tag_priorities = g_list_prepend (info->tag_priorities, prio);
+ }
+
+ info->current_tag = NULL;
+ break;
+ case STATE_ATTR:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TAG);
+ break;
+ case STATE_APPLY_TAG:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_APPLY_TAG ||
+ peek_state (info) == STATE_TEXT);
+
+ /* Pop tag */
+ g_free (info->tag_stack->data);
+ info->tag_stack = g_slist_delete_link (info->tag_stack,
+ info->tag_stack);
+
+ break;
+ case STATE_TEXT:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_TEXT_VIEW_MARKUP);
+
+ info->spans = g_list_reverse (info->spans);
+ info->parsed_text = TRUE;
+ break;
+ case STATE_TEXT_VIEW_MARKUP:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_START);
+ break;
+ case STATE_PIXBUF:
+ pop_state (info);
+ g_assert (peek_state (info) == STATE_APPLY_TAG ||
+ peek_state (info) == STATE_TEXT);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+all_whitespace (const char *text,
+ int text_len)
+{
+ const char *p;
+ const char *end;
+
+ p = text;
+ end = text + text_len;
+
+ while (p != end)
+ {
+ if (!g_ascii_isspace (*p))
+ return FALSE;
+
+ p = g_utf8_next_char (p);
+ }
+
+ return TRUE;
+}
+
+static GSList *
+copy_tag_list (GSList *tag_list)
+{
+ GSList *tmp = NULL;
+
+ while (tag_list)
+ {
+ tmp = g_slist_prepend (tmp, g_strdup (tag_list->data));
+
+ tag_list = tag_list->next;
+ }
+
+ return tmp;
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+ TextSpan *span;
+
+ if (all_whitespace (text, text_len) &&
+ peek_state (info) != STATE_TEXT &&
+ peek_state (info) != STATE_APPLY_TAG)
+ return;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ g_assert_not_reached (); /* gmarkup shouldn't do this */
+ break;
+ case STATE_TEXT:
+ case STATE_APPLY_TAG:
+ if (text_len == 0)
+ return;
+
+ span = g_new0 (TextSpan, 1);
+ span->text = g_strndup (text, text_len);
+ span->tags = copy_tag_list (info->tag_stack);
+
+ info->spans = g_list_prepend (info->spans, span);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+parse_info_init (ParseInfo *info,
+ GtkTextBuffer *buffer,
+ gboolean create_tags,
+ GList *headers)
+{
+ info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+
+ info->create_tags = create_tags;
+ info->headers = headers;
+ info->defined_tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ info->substitutions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ info->tag_stack = NULL;
+ info->spans = NULL;
+ info->parsed_text = FALSE;
+ info->parsed_tags = FALSE;
+ info->current_tag = NULL;
+ info->current_tag_prio = -1;
+ info->tag_priorities = NULL;
+
+ info->buffer = buffer;
+}
+
+static void
+text_span_free (TextSpan *span)
+{
+ GSList *tmp;
+
+ g_free (span->text);
+
+ tmp = span->tags;
+ while (tmp)
+ {
+ g_free (tmp->data);
+
+ tmp = tmp->next;
+ }
+ g_slist_free (span->tags);
+ g_free (span);
+}
+
+static void
+parse_info_free (ParseInfo *info)
+{
+ GSList *slist;
+ GList *list;
+
+ slist = info->tag_stack;
+ while (slist)
+ {
+ g_free (slist->data);
+
+ slist = slist->next;
+ }
+
+ g_slist_free (info->tag_stack);
+ g_slist_free (info->states);
+
+ g_hash_table_destroy (info->substitutions);
+ g_hash_table_destroy (info->defined_tags);
+
+ if (info->current_tag)
+ g_object_unref (info->current_tag);
+
+ list = info->spans;
+ while (list)
+ {
+ text_span_free (list->data);
+
+ list = list->next;
+ }
+ g_list_free (info->spans);
+
+ list = info->tag_priorities;
+ while (list)
+ {
+ TextTagPrio *prio = list->data;
+
+ if (prio->tag)
+ g_object_unref (prio->tag);
+ g_free (prio);
+
+ list = list->next;
+ }
+ g_list_free (info->tag_priorities);
+
+}
+
+static const gchar *
+get_tag_substitution (ParseInfo *info,
+ const gchar *name)
+{
+ gchar *subst;
+
+ if ((subst = g_hash_table_lookup (info->substitutions, name)))
+ return subst;
+ else
+ return name;
+}
+
+static void
+insert_text (ParseInfo *info,
+ GtkTextIter *iter)
+{
+ GtkTextIter start_iter;
+ GtkTextMark *mark;
+ GList *tmp;
+ GSList *tags;
+
+ start_iter = *iter;
+
+ mark = gtk_text_buffer_create_mark (info->buffer, "deserialize_insert_point",
+ &start_iter, TRUE);
+
+ tmp = info->spans;
+ while (tmp)
+ {
+ TextSpan *span = tmp->data;
+
+ if (span->text)
+ gtk_text_buffer_insert (info->buffer, iter, span->text, -1);
+ else
+ {
+ gtk_text_buffer_insert_pixbuf (info->buffer, iter, span->pixbuf);
+ g_object_unref (span->pixbuf);
+ }
+ gtk_text_buffer_get_iter_at_mark (info->buffer, &start_iter, mark);
+
+ /* Apply tags */
+ tags = span->tags;
+ while (tags)
+ {
+ const gchar *tag_name = get_tag_substitution (info, tags->data);
+
+ gtk_text_buffer_apply_tag_by_name (info->buffer, tag_name,
+ &start_iter, iter);
+
+ tags = tags->next;
+ }
+
+ gtk_text_buffer_move_mark (info->buffer, mark, iter);
+
+ tmp = tmp->next;
+ }
+
+ gtk_text_buffer_delete_mark (info->buffer, mark);
+}
+
+
+
+static int
+read_int (const guchar *start)
+{
+ int result;
+
+ result =
+ start[0] << 24 |
+ start[1] << 16 |
+ start[2] << 8 |
+ start[3];
+
+ return result;
+}
+
+static gboolean
+header_is (Header *header, const gchar *id)
+{
+ return (strncmp (header->id, id, 8) == 0);
+}
+
+static GList *
+read_headers (const gchar *start,
+ gint len,
+ GError **error)
+{
+ int i = 0;
+ int section_len;
+ Header *header;
+ GList *headers = NULL;
+
+ while (i < len)
+ {
+ if (i + 12 >= len)
+ goto error;
+
+ if (strncmp (start + i, "RICHTEXT", 8) == 0 ||
+ strncmp (start + i, "PIXBDATA", 8) == 0)
+ {
+
+ section_len = read_int (start + i + 8);
+
+ if (i + 12 + section_len > len)
+ goto error;
+
+ header = g_new0 (Header, 1);
+ header->id = start + i;
+ header->length = section_len;
+ header->start = start + i + 12;
+
+ i += 12 + section_len;
+
+ headers = g_list_prepend (headers, header);
+ }
+ else
+ break;
+
+ }
+
+ return g_list_reverse (headers);
+
+ error:
+ g_list_foreach (headers, (GFunc) g_free, NULL);
+ g_list_free (headers);
+
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Serialized data is malformed"));
+
+ return NULL;
+}
+
+static gboolean
+deserialize_text (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ const gchar *text,
+ gint len,
+ gboolean create_tags,
+ GError **error,
+ GList *headers)
+{
+ GMarkupParseContext *context;
+ ParseInfo info;
+ gboolean retval = FALSE;
+
+
+ static GMarkupParser rich_text_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+ };
+
+ parse_info_init (&info, buffer, create_tags, headers);
+
+ context = g_markup_parse_context_new (&rich_text_parser,
+ 0, &info, NULL);
+
+ if (!g_markup_parse_context_parse (context,
+ text,
+ len,
+ error))
+ goto out;
+
+ if (!g_markup_parse_context_end_parse (context, error))
+ goto out;
+
+ retval = TRUE;
+
+ /* Now insert the text */
+ insert_text (&info, iter);
+
+ out:
+ parse_info_free (&info);
+
+ g_markup_parse_context_free (context);
+
+ return retval;
+}
+
+gboolean
+gtk_text_buffer_deserialize_rich_text (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ const gchar *text,
+ gint len,
+ gboolean create_tags,
+ GError **error)
+{
+ GList *headers;
+ Header *header;
+ gboolean retval;
+
+ headers = read_headers (text, len, error);
+
+ if (!headers)
+ return FALSE;
+
+ header = headers->data;
+ if (!header_is (header, "RICHTEXT"))
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("Serialized data is malformed. First section isn't RICHTEXT"));
+
+ retval = FALSE;
+ goto out;
+ }
+
+ retval = deserialize_text (buffer, iter,
+ header->start, header->length,
+ create_tags, error, headers->next);
+
+ out:
+ g_list_foreach (headers, (GFunc)g_free, NULL);
+ g_list_free (headers);
+
+ return retval;
+}