summaryrefslogtreecommitdiffstats
path: root/app/widgets/gimptextbuffer-serialize.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/widgets/gimptextbuffer-serialize.c662
1 files changed, 662 insertions, 0 deletions
diff --git a/app/widgets/gimptextbuffer-serialize.c b/app/widgets/gimptextbuffer-serialize.c
new file mode 100644
index 0000000..61ca3fe
--- /dev/null
+++ b/app/widgets/gimptextbuffer-serialize.c
@@ -0,0 +1,662 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * GimpTextBuffer-serialize
+ * Copyright (C) 2010 Michael Natterer <mitch@gimp.org>
+ *
+ * inspired by
+ * gtktextbufferserialize.c
+ * Copyright (C) 2004 Nokia Corporation.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include "widgets-types.h"
+
+#include "gimptextbuffer.h"
+#include "gimptextbuffer-serialize.h"
+
+#include "gimp-intl.h"
+
+
+/* serialize */
+
+static gboolean
+open_tag (GimpTextBuffer *buffer,
+ GString *string,
+ GtkTextTag *tag)
+{
+ const gchar *name;
+ const gchar *attribute;
+ gchar *attribute_value;
+
+ name = gimp_text_buffer_tag_to_name (buffer, tag,
+ &attribute,
+ &attribute_value);
+
+ if (name)
+ {
+ if (attribute && attribute_value)
+ {
+ gchar *escaped = g_markup_escape_text (attribute_value, -1);
+
+ g_string_append_printf (string, "<%s %s=\"%s\">",
+ name, attribute, escaped);
+
+ g_free (escaped);
+ g_free (attribute_value);
+ }
+ else
+ {
+ g_string_append_printf (string, "<%s>", name);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+close_tag (GimpTextBuffer *buffer,
+ GString *string,
+ GtkTextTag *tag)
+{
+ const gchar *name = gimp_text_buffer_tag_to_name (buffer, tag, NULL, NULL);
+
+ if (name)
+ {
+ g_string_append_printf (string, "</%s>", name);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+guint8 *
+gimp_text_buffer_serialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ const GtkTextIter *start,
+ const GtkTextIter *end,
+ gsize *length,
+ gpointer user_data)
+{
+ GString *string;
+ GtkTextIter iter, old_iter;
+ GSList *tag_list;
+ GSList *active_tags;
+
+ string = g_string_new ("<markup>");
+
+ iter = *start;
+ tag_list = NULL;
+ active_tags = NULL;
+
+ do
+ {
+ GSList *tmp;
+ gchar *tmp_text, *escaped_text;
+
+ active_tags = NULL;
+ tag_list = gtk_text_iter_get_tags (&iter);
+
+ /* Handle added tags */
+ for (tmp = tag_list; tmp; tmp = tmp->next)
+ {
+ GtkTextTag *tag = tmp->data;
+
+ open_tag (GIMP_TEXT_BUFFER (register_buffer), string, tag);
+
+ active_tags = g_slist_prepend (active_tags, tag);
+ }
+
+ g_slist_free (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)
+ {
+ /* pixbuf? can't happen! */
+ }
+ 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, end) > 0)
+ iter = *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 (string, escaped_text);
+ g_free (escaped_text);
+
+ /* Close any open tags */
+ for (tmp = active_tags; tmp; tmp = tmp->next)
+ close_tag (GIMP_TEXT_BUFFER (register_buffer), string, tmp->data);
+
+ g_slist_free (active_tags);
+ }
+ while (! gtk_text_iter_equal (&iter, end));
+
+ g_string_append (string, "</markup>");
+
+ *length = string->len;
+
+ return (guint8 *) g_string_free (string, FALSE);
+}
+
+
+/* deserialize */
+
+typedef enum
+{
+ STATE_START,
+ STATE_MARKUP,
+ STATE_TAG,
+ STATE_UNKNOWN
+} ParseState;
+
+typedef struct
+{
+ GSList *states;
+ GtkTextBuffer *register_buffer;
+ GtkTextBuffer *content_buffer;
+ GSList *tag_stack;
+ GList *spans;
+} ParseInfo;
+
+typedef struct
+{
+ gchar *text;
+ GSList *tags;
+} TextSpan;
+
+static void set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...) G_GNUC_PRINTF (5, 6);
+
+static void
+set_error (GError **err,
+ GMarkupParseContext *context,
+ int error_domain,
+ int error_code,
+ const char *format,
+ ...)
+{
+ gint line, ch;
+ va_list args;
+ gchar *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);
+}
+
+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 void
+parse_tag_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ ParseInfo *info,
+ GError **error)
+{
+ GtkTextTag *tag;
+ const gchar *attribute_name = NULL;
+ const gchar *attribute_value = NULL;
+
+ gimp_assert (peek_state (info) == STATE_MARKUP ||
+ peek_state (info) == STATE_TAG ||
+ peek_state (info) == STATE_UNKNOWN);
+
+ if (attribute_names)
+ attribute_name = attribute_names[0];
+
+ if (attribute_values)
+ attribute_value = attribute_values[0];
+
+ tag = gimp_text_buffer_name_to_tag (GIMP_TEXT_BUFFER (info->register_buffer),
+ element_name,
+ attribute_name, attribute_value);
+
+ if (tag)
+ {
+ info->tag_stack = g_slist_prepend (info->tag_stack, tag);
+
+ push_state (info, STATE_TAG);
+ }
+ else
+ {
+ push_state (info, STATE_UNKNOWN);
+ }
+}
+
+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 (! strcmp (element_name, "markup"))
+ {
+ if (! check_no_attributes (context, element_name,
+ attribute_names, attribute_values,
+ error))
+ return;
+
+ push_state (info, STATE_MARKUP);
+ break;
+ }
+ else
+ {
+ set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
+ _("Outermost element in text must be <markup> not <%s>"),
+ element_name);
+ }
+ break;
+
+ case STATE_MARKUP:
+ case STATE_TAG:
+ case STATE_UNKNOWN:
+ parse_tag_element (context, element_name,
+ attribute_names, attribute_values,
+ info, error);
+ break;
+
+ default:
+ gimp_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseInfo *info = user_data;
+
+ switch (peek_state (info))
+ {
+ case STATE_UNKNOWN:
+ pop_state (info);
+ gimp_assert (peek_state (info) == STATE_UNKNOWN ||
+ peek_state (info) == STATE_TAG ||
+ peek_state (info) == STATE_MARKUP);
+ break;
+
+ case STATE_TAG:
+ pop_state (info);
+ gimp_assert (peek_state (info) == STATE_UNKNOWN ||
+ peek_state (info) == STATE_TAG ||
+ peek_state (info) == STATE_MARKUP);
+
+ /* Pop tag */
+ info->tag_stack = g_slist_delete_link (info->tag_stack,
+ info->tag_stack);
+ break;
+
+ case STATE_MARKUP:
+ pop_state (info);
+ gimp_assert (peek_state (info) == STATE_START);
+
+ info->spans = g_list_reverse (info->spans);
+ break;
+
+ default:
+ gimp_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+all_whitespace (const char *text,
+ gint text_len)
+{
+ const char *p = text;
+ const char *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_MARKUP &&
+ peek_state (info) != STATE_TAG &&
+ peek_state (info) != STATE_UNKNOWN)
+ return;
+
+ switch (peek_state (info))
+ {
+ case STATE_START:
+ gimp_assert_not_reached (); /* gmarkup shouldn't do this */
+ break;
+
+ case STATE_MARKUP:
+ case STATE_TAG:
+ case STATE_UNKNOWN:
+ 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:
+ gimp_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+parse_info_init (ParseInfo *info,
+ GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer)
+{
+ info->states = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
+ info->tag_stack = NULL;
+ info->spans = NULL;
+ info->register_buffer = register_buffer;
+ info->content_buffer = content_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)
+{
+ g_slist_free (info->tag_stack);
+ g_slist_free (info->states);
+
+ g_list_free_full (info->spans, (GDestroyNotify) text_span_free);
+}
+
+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->content_buffer,
+ "deserialize-insert-point",
+ &start_iter, TRUE);
+
+ for (tmp = info->spans; tmp; tmp = tmp->next)
+ {
+ TextSpan *span = tmp->data;
+
+ if (span->text)
+ gtk_text_buffer_insert (info->content_buffer, iter, span->text, -1);
+
+ gtk_text_buffer_get_iter_at_mark (info->content_buffer, &start_iter, mark);
+
+ /* Apply tags */
+ for (tags = span->tags; tags; tags = tags->next)
+ {
+ GtkTextTag *tag = tags->data;
+
+ gtk_text_buffer_apply_tag (info->content_buffer, tag,
+ &start_iter, iter);
+ }
+
+ gtk_text_buffer_move_mark (info->content_buffer, mark, iter);
+ }
+
+ gtk_text_buffer_delete_mark (info->content_buffer, mark);
+}
+
+gboolean
+gimp_text_buffer_deserialize (GtkTextBuffer *register_buffer,
+ GtkTextBuffer *content_buffer,
+ GtkTextIter *iter,
+ const guint8 *text,
+ gsize length,
+ gboolean create_tags,
+ gpointer user_data,
+ GError **error)
+{
+ GMarkupParseContext *context;
+ ParseInfo info;
+ gboolean retval = FALSE;
+
+ static const GMarkupParser markup_parser =
+ {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+ };
+
+ parse_info_init (&info, register_buffer, content_buffer);
+
+ context = g_markup_parse_context_new (&markup_parser, 0, &info, NULL);
+
+ if (! g_markup_parse_context_parse (context,
+ (const gchar *) text,
+ length,
+ error))
+ goto out;
+
+ if (! g_markup_parse_context_end_parse (context, error))
+ goto out;
+
+ retval = TRUE;
+
+ insert_text (&info, iter);
+
+ out:
+ parse_info_free (&info);
+
+ g_markup_parse_context_free (context);
+
+ return retval;
+}
+
+void
+gimp_text_buffer_pre_serialize (GimpTextBuffer *buffer,
+ GtkTextBuffer *content)
+{
+ GtkTextIter iter;
+
+ g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer));
+ g_return_if_fail (GTK_IS_TEXT_BUFFER (content));
+
+ gtk_text_buffer_get_start_iter (content, &iter);
+
+ do
+ {
+ GSList *tags = gtk_text_iter_get_tags (&iter);
+ GSList *list;
+
+ for (list = tags; list; list = g_slist_next (list))
+ {
+ GtkTextTag *tag = list->data;
+
+ if (g_list_find (buffer->kerning_tags, tag))
+ {
+ GtkTextIter end;
+
+ gtk_text_buffer_insert_with_tags (content, &iter,
+ WORD_JOINER, -1,
+ tag, NULL);
+
+ end = iter;
+ gtk_text_iter_forward_char (&end);
+
+ gtk_text_buffer_remove_tag (content, tag, &iter, &end);
+ break;
+ }
+ }
+
+ g_slist_free (tags);
+ }
+ while (gtk_text_iter_forward_char (&iter));
+}
+
+void
+gimp_text_buffer_post_deserialize (GimpTextBuffer *buffer,
+ GtkTextBuffer *content)
+{
+ GtkTextIter iter;
+
+ g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer));
+ g_return_if_fail (GTK_IS_TEXT_BUFFER (content));
+
+ gtk_text_buffer_get_start_iter (content, &iter);
+
+ do
+ {
+ GSList *tags = gtk_text_iter_get_tags (&iter);
+ GSList *list;
+
+ for (list = tags; list; list = g_slist_next (list))
+ {
+ GtkTextTag *tag = list->data;
+
+ if (g_list_find (buffer->kerning_tags, tag))
+ {
+ GtkTextIter end;
+
+ gtk_text_iter_forward_char (&iter);
+ gtk_text_buffer_backspace (content, &iter, FALSE, TRUE);
+
+ end = iter;
+ gtk_text_iter_forward_char (&end);
+
+ gtk_text_buffer_apply_tag (content, tag, &iter, &end);
+ break;
+ }
+ }
+
+ g_slist_free (tags);
+ }
+ while (gtk_text_iter_forward_char (&iter));
+}