| /* gtktextbufferserialize.c |
| * |
| * Copyright (C) 2001 Havoc Pennington |
| * Copyright (C) 2004 Nokia Corporation |
| * |
| * 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, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| /* FIXME: We should use other error codes for the |
| * parts that deal with the format errors |
| */ |
| |
| #include "config.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| #include "gdk-pixbuf/gdk-pixdata.h" |
| #include "gtktextbufferserialize.h" |
| #include "gtktexttagprivate.h" |
| #include "gtkintl.h" |
| |
| |
| typedef struct |
| { |
| GString *tag_table_str; |
| GString *text_str; |
| GHashTable *tags; |
| GtkTextIter start, end; |
| |
| gint n_pixbufs; |
| GList *pixbufs; |
| gint tag_id; |
| GHashTable *tag_id_tags; |
| } SerializationContext; |
| |
| static gchar * |
| serialize_value (GValue *value) |
| { |
| if (g_value_type_transformable (value->g_type, G_TYPE_STRING)) |
| { |
| GValue text_value = G_VALUE_INIT; |
| 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 = G_VALUE_INIT; |
| 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; |
| |
| errno = 0; |
| v = g_ascii_strtoll (str, &tmp, 10); |
| |
| if (errno || 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; |
| tmp = NULL; |
| errno = 0; |
| color.red = g_ascii_strtoll (old, &tmp, 16); |
| |
| if (errno || tmp == old) |
| return FALSE; |
| |
| old = tmp; |
| if (*old++ != ':') |
| return FALSE; |
| |
| tmp = NULL; |
| errno = 0; |
| color.green = g_ascii_strtoll (old, &tmp, 16); |
| if (errno || tmp == old) |
| return FALSE; |
| |
| old = tmp; |
| if (*old++ != ':') |
| return FALSE; |
| |
| tmp = NULL; |
| errno = 0; |
| color.blue = g_ascii_strtoll (old, &tmp, 16); |
| |
| if (errno || 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; |
| gint tag_id; |
| GParamSpec **pspecs; |
| guint n_pspecs; |
| int i; |
| |
| g_string_append (context->tag_table_str, " <tag "); |
| |
| /* Handle anonymous tags */ |
| if (tag->priv->name) |
| { |
| tag_name = g_markup_escape_text (tag->priv->name, -1); |
| g_string_append_printf (context->tag_table_str, "name=\"%s\"", tag_name); |
| g_free (tag_name); |
| } |
| else |
| { |
| tag_id = GPOINTER_TO_INT (g_hash_table_lookup (context->tag_id_tags, tag)); |
| |
| g_string_append_printf (context->tag_table_str, "id=\"%d\"", tag_id); |
| } |
| |
| g_string_append_printf (context->tag_table_str, " priority=\"%d\">\n", tag->priv->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 = G_VALUE_INIT; |
| 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 */ |
| tmp2 = serialize_value (&value); |
| |
| if (tmp2) |
| { |
| 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); |
| 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"); |
| } |
| |
| 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) == 26); |
| |
| 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; |
| GSList *active_tags; |
| |
| g_string_append (context->text_str, "<text>"); |
| |
| iter = context->start; |
| tag_list = NULL; |
| active_tags = NULL; |
| |
| 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 */ |
| for (tmp = removed; tmp; tmp = tmp->next) |
| { |
| GtkTextTag *tag = tmp->data; |
| |
| /* Only close the tag if we didn't close it before (by using |
| * the stack logic in the while() loop below) |
| */ |
| if (g_slist_find (active_tags, tag)) |
| { |
| g_string_append (context->text_str, "</apply_tag>"); |
| |
| /* Drop all tags that were opened after this one (which are |
| * above this on in the stack) |
| */ |
| while (active_tags->data != tag) |
| { |
| added = g_list_prepend (added, active_tags->data); |
| active_tags = g_slist_remove (active_tags, active_tags->data); |
| g_string_append_printf (context->text_str, "</apply_tag>"); |
| } |
| |
| active_tags = g_slist_remove (active_tags, active_tags->data); |
| } |
| } |
| |
| /* Handle added tags */ |
| for (tmp = added; tmp; tmp = tmp->next) |
| { |
| GtkTextTag *tag = tmp->data; |
| gchar *tag_name; |
| |
| /* Add it to the tag hash table */ |
| g_hash_table_insert (context->tags, tag, tag); |
| |
| if (tag->priv->name) |
| { |
| tag_name = g_markup_escape_text (tag->priv->name, -1); |
| |
| g_string_append_printf (context->text_str, "<apply_tag name=\"%s\">", tag_name); |
| g_free (tag_name); |
| } |
| else |
| { |
| gpointer tag_id; |
| |
| /* We've got an anonymous tag, find out if it's been |
| used before */ |
| if (!g_hash_table_lookup_extended (context->tag_id_tags, tag, NULL, &tag_id)) |
| { |
| tag_id = GINT_TO_POINTER (context->tag_id++); |
| |
| g_hash_table_insert (context->tag_id_tags, tag, tag_id); |
| } |
| |
| g_string_append_printf (context->text_str, "<apply_tag id=\"%d\">", GPOINTER_TO_INT (tag_id)); |
| } |
| |
| active_tags = g_slist_prepend (active_tags, tag); |
| } |
| |
| g_slist_free (tag_list); |
| tag_list = new_tag_list; |
| |
| g_list_free (added); |
| g_list_free (removed); |
| |
| 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) |
| { |
| /* Append the text before the pixbuf */ |
| tmp_text = gtk_text_iter_get_slice (&old_iter, &iter); |
| escaped_text = g_markup_escape_text (tmp_text, -1); |
| g_free (tmp_text); |
| |
| /* Forward so we don't get the 0xfffc char */ |
| gtk_text_iter_forward_char (&iter); |
| old_iter = iter; |
| |
| g_string_append (context->text_str, escaped_text); |
| g_free (escaped_text); |
| |
| g_string_append_printf (context->text_str, "<pixbuf index=\"%d\" />", context->n_pixbufs); |
| |
| context->n_pixbufs++; |
| context->pixbufs = g_list_prepend (context->pixbufs, pixbuf); |
| } |
| } |
| else if (ch == 0) |
| { |
| break; |
| } |
| else |
| 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 (tag_list = active_tags; tag_list; tag_list = tag_list->next) |
| g_string_append (context->text_str, "</apply_tag>"); |
| |
| g_slist_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, "GTKTEXTBUFFERPIXBDATA-0001", len); |
| g_string_append_len (text, (gchar *) tmp, len); |
| g_free (tmp); |
| } |
| } |
| |
| guint8 * |
| _gtk_text_buffer_serialize_rich_text (GtkTextBuffer *register_buffer, |
| GtkTextBuffer *content_buffer, |
| const GtkTextIter *start, |
| const GtkTextIter *end, |
| gsize *length, |
| gpointer user_data) |
| { |
| 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; |
| context.tag_id = 0; |
| context.tag_id_tags = g_hash_table_new (NULL, NULL); |
| |
| /* We need to serialize the text before the tag table so we know |
| what tags are used */ |
| serialize_text (content_buffer, &context); |
| serialize_tags (&context); |
| |
| text = g_string_new (NULL); |
| serialize_section_header (text, "GTKTEXTBUFFERCONTENTS-0001", |
| 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); |
| g_hash_table_destroy (context.tag_id_tags); |
| |
| *length = text->len; |
| |
| return (guint8 *) 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; |
| |
| /* Tags that are anonymous */ |
| GHashTable *anonymous_tags; |
| |
| /* Tag name substitutions */ |
| GHashTable *substitutions; |
| |
| /* Current tag */ |
| GtkTextTag *current_tag; |
| |
| /* Priority of current tag */ |
| gint current_tag_prio; |
| |
| /* Id of current tag */ |
| gint current_tag_id; |
| |
| /* 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) |
| |
| |
| static gboolean |
| check_id_or_name (GMarkupParseContext *context, |
| const gchar *element_name, |
| const gchar **attribute_names, |
| const gchar **attribute_values, |
| gint *id, |
| const gchar **name, |
| GError **error) |
| { |
| gboolean has_id = FALSE; |
| gboolean has_name = FALSE; |
| int i; |
| |
| *id = 0; |
| *name = NULL; |
| |
| for (i = 0; attribute_names[i] != NULL; i++) |
| { |
| if (strcmp (attribute_names[i], "name") == 0) |
| { |
| *name = attribute_values[i]; |
| |
| if (has_id) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, |
| G_MARKUP_ERROR_PARSE, |
| _("Both \"id\" and \"name\" were found on the <%s> element"), |
| element_name); |
| return FALSE; |
| } |
| |
| if (has_name) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, |
| G_MARKUP_ERROR_PARSE, |
| _("The attribute \"%s\" was found twice on the <%s> element"), |
| "name", element_name); |
| return FALSE; |
| } |
| |
| has_name = TRUE; |
| } |
| else if (strcmp (attribute_names[i], "id") == 0) |
| { |
| gchar *tmp; |
| |
| if (has_name) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, |
| G_MARKUP_ERROR_PARSE, |
| _("Both \"id\" and \"name\" were found on the <%s> element"), |
| element_name); |
| return FALSE; |
| } |
| |
| if (has_id) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, |
| G_MARKUP_ERROR_PARSE, |
| _("The attribute \"%s\" was found twice on the <%s> element"), |
| "id", element_name); |
| return FALSE; |
| } |
| |
| has_id = TRUE; |
| |
| /* Try parsing the integer */ |
| tmp = NULL; |
| errno = 0; |
| *id = g_ascii_strtoll (attribute_values[i], &tmp, 10); |
| |
| if (errno || tmp == attribute_values[i]) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, |
| _("<%s> element has invalid ID \"%s\""), attribute_values[i]); |
| return FALSE; |
| } |
| } |
| } |
| |
| if (!has_id && !has_name) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, |
| _("<%s> element has neither a \"name\" nor an \"id\" attribute"), element_name); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| 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, |
| gboolean allow_unknown_attrs, |
| 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 && !allow_unknown_attrs) |
| { |
| 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 GtkTextTag * |
| tag_exists (GMarkupParseContext *context, |
| const gchar *name, |
| gint id, |
| ParseInfo *info, |
| GError **error) |
| { |
| GtkTextTagTable *tag_table; |
| const gchar *real_name; |
| |
| tag_table = gtk_text_buffer_get_tag_table (info->buffer); |
| |
| if (info->create_tags) |
| { |
| /* If we have an anonymous tag, just return it directly */ |
| if (!name) |
| return g_hash_table_lookup (info->anonymous_tags, |
| GINT_TO_POINTER (id)); |
| |
| /* First, try the substitutions */ |
| real_name = g_hash_table_lookup (info->substitutions, name); |
| |
| if (real_name) |
| return gtk_text_tag_table_lookup (tag_table, real_name); |
| |
| /* Next, try the list of defined tags */ |
| if (g_hash_table_lookup (info->defined_tags, name) != NULL) |
| return gtk_text_tag_table_lookup (tag_table, name); |
| |
| set_error (error, context, |
| G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, |
| _("Tag \"%s\" has not been defined."), name); |
| |
| return NULL; |
| } |
| else |
| { |
| GtkTextTag *tag; |
| |
| if (!name) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, |
| _("Anonymous tag found and tags can not be created.")); |
| return NULL; |
| } |
| |
| tag = gtk_text_tag_table_lookup (tag_table, name); |
| |
| if (tag) |
| return tag; |
| |
| 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, |
| (const guint8 *) header->start, error)) |
| return NULL; |
| |
| pixbuf = gdk_pixbuf_from_pixdata (&pixdata, TRUE, error); |
| |
| 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, *priority; |
| gint id; |
| GtkTextTag *tag; |
| |
| 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, TRUE, error, |
| "priority", &priority, NULL)) |
| return; |
| |
| if (!check_id_or_name (context, element_name, attribute_names, attribute_values, |
| &id, &name, error)) |
| return; |
| |
| |
| tag = tag_exists (context, name, id, info, error); |
| |
| if (!tag) |
| return; |
| |
| info->tag_stack = g_slist_prepend (info->tag_stack, tag); |
| |
| push_state (info, STATE_APPLY_TAG); |
| } |
| else if (ELEMENT_IS ("pixbuf")) |
| { |
| int int_id; |
| GdkPixbuf *pixbuf; |
| TextSpan *span; |
| const gchar *pixbuf_id; |
| |
| if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, error, |
| "index", &pixbuf_id, NULL)) |
| return; |
| |
| int_id = atoi (pixbuf_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 = G_VALUE_INIT; |
| GParamSpec *pspec; |
| |
| g_assert (peek_state (info) == STATE_TAG); |
| |
| if (ELEMENT_IS ("attr")) |
| { |
| if (!locate_attributes (context, element_name, attribute_names, attribute_values, FALSE, 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 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) |
| { |
| GtkTextTagTable *tag_table; |
| gchar *name; |
| gint i; |
| |
| name = g_strdup (tag_name); |
| |
| if (!info->create_tags) |
| return name; |
| |
| i = 0; |
| tag_table = gtk_text_buffer_get_tag_table (info->buffer); |
| |
| while (gtk_text_tag_table_lookup (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 id; |
| 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, TRUE, error, |
| "priority", &priority, NULL)) |
| return; |
| |
| if (!check_id_or_name (context, element_name, attribute_names, attribute_values, |
| &id, &name, error)) |
| return; |
| |
| if (name) |
| { |
| 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; |
| } |
| } |
| |
| tmp = NULL; |
| errno = 0; |
| prio = g_ascii_strtoll (priority, &tmp, 10); |
| |
| if (errno || tmp == priority) |
| { |
| set_error (error, context, |
| G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE, |
| _("Tag \"%s\" has invalid priority \"%s\""), name, priority); |
| return; |
| } |
| |
| if (name) |
| { |
| tag_name = get_tag_name (info, name); |
| info->current_tag = gtk_text_tag_new (tag_name); |
| g_free (tag_name); |
| } |
| else |
| { |
| info->current_tag = gtk_text_tag_new (NULL); |
| info->current_tag_id = id; |
| } |
| |
| info->current_tag_prio = prio; |
| |
| 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 <%s> element has already been specified"), "tags"); |
| 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 <%s> element has already been specified"), "text"); |
| 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 (gtk_text_buffer_get_tag_table (info->buffer), |
| 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); |
| |
| if (info->current_tag->priv->name) |
| { |
| /* Add tag to defined tags hash */ |
| tmp = g_strdup (info->current_tag->priv->name); |
| g_hash_table_insert (info->defined_tags, |
| tmp, tmp); |
| } |
| else |
| { |
| g_hash_table_insert (info->anonymous_tags, |
| GINT_TO_POINTER (info->current_tag_id), |
| info->current_tag); |
| } |
| |
| 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 */ |
| 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 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 = g_slist_copy (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->anonymous_tags = g_hash_table_new_full (NULL, NULL, NULL, NULL); |
| 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) |
| { |
| g_free (span->text); |
| g_slist_free (span->tags); |
| g_free (span); |
| } |
| |
| static void |
| parse_info_free (ParseInfo *info) |
| { |
| GList *list; |
| |
| 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 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) |
| { |
| GtkTextTag *tag = tags->data; |
| |
| gtk_text_buffer_apply_tag (info->buffer, tag, |
| &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, strlen (id)) == 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 + 30 >= len) |
| goto error; |
| |
| if (strncmp (start + i, "GTKTEXTBUFFERCONTENTS-0001", 26) == 0 || |
| strncmp (start + i, "GTKTEXTBUFFERPIXBDATA-0001", 26) == 0) |
| { |
| section_len = read_int ((const guchar *) start + i + 26); |
| |
| if (i + 30 + section_len > len) |
| goto error; |
| |
| header = g_new0 (Header, 1); |
| header->id = start + i; |
| header->length = section_len; |
| header->start = start + i + 30; |
| |
| i += 30 + section_len; |
| |
| headers = g_list_prepend (headers, header); |
| } |
| else |
| break; |
| } |
| |
| return g_list_reverse (headers); |
| |
| error: |
| g_list_free_full (headers, g_free); |
| |
| g_set_error_literal (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 const 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 *register_buffer, |
| GtkTextBuffer *content_buffer, |
| GtkTextIter *iter, |
| const guint8 *text, |
| gsize length, |
| gboolean create_tags, |
| gpointer user_data, |
| GError **error) |
| { |
| GList *headers; |
| Header *header; |
| gboolean retval; |
| |
| headers = read_headers ((gchar *) text, length, error); |
| |
| if (!headers) |
| return FALSE; |
| |
| header = headers->data; |
| if (!header_is (header, "GTKTEXTBUFFERCONTENTS-0001")) |
| { |
| g_set_error_literal (error, |
| G_MARKUP_ERROR, |
| G_MARKUP_ERROR_PARSE, |
| _("Serialized data is malformed. First section isn't GTKTEXTBUFFERCONTENTS-0001")); |
| |
| retval = FALSE; |
| goto out; |
| } |
| |
| retval = deserialize_text (content_buffer, iter, |
| header->start, header->length, |
| create_tags, error, headers->next); |
| |
| out: |
| g_list_free_full (headers, g_free); |
| |
| return retval; |
| } |