1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * GimpTextBuffer-serialize
5  * Copyright (C) 2010  Michael Natterer <mitch@gimp.org>
6  *
7  * inspired by
8  * gtktextbufferserialize.c
9  * Copyright (C) 2004  Nokia Corporation.
10  *
11  * This program is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
23  */
24 
25 #include "config.h"
26 
27 #include <string.h>
28 
29 #include <gtk/gtk.h>
30 
31 #include "widgets-types.h"
32 
33 #include "gimptextbuffer.h"
34 #include "gimptextbuffer-serialize.h"
35 
36 #include "gimp-intl.h"
37 
38 
39 /*  serialize  */
40 
41 static gboolean
open_tag(GimpTextBuffer * buffer,GString * string,GtkTextTag * tag)42 open_tag (GimpTextBuffer *buffer,
43           GString        *string,
44           GtkTextTag     *tag)
45 {
46   const gchar *name;
47   const gchar *attribute;
48   gchar       *attribute_value;
49 
50   name = gimp_text_buffer_tag_to_name (buffer, tag,
51                                        &attribute,
52                                        &attribute_value);
53 
54   if (name)
55     {
56       if (attribute && attribute_value)
57         {
58           gchar *escaped = g_markup_escape_text (attribute_value, -1);
59 
60           g_string_append_printf (string, "<%s %s=\"%s\">",
61                                   name, attribute, escaped);
62 
63           g_free (escaped);
64           g_free (attribute_value);
65         }
66       else
67         {
68           g_string_append_printf (string, "<%s>", name);
69         }
70 
71       return TRUE;
72     }
73 
74   return FALSE;
75 }
76 
77 static gboolean
close_tag(GimpTextBuffer * buffer,GString * string,GtkTextTag * tag)78 close_tag (GimpTextBuffer *buffer,
79            GString        *string,
80            GtkTextTag     *tag)
81 {
82   const gchar *name = gimp_text_buffer_tag_to_name (buffer, tag, NULL, NULL);
83 
84   if (name)
85     {
86       g_string_append_printf (string, "</%s>", name);
87 
88       return TRUE;
89     }
90 
91   return FALSE;
92 }
93 
94 guint8 *
gimp_text_buffer_serialize(GtkTextBuffer * register_buffer,GtkTextBuffer * content_buffer,const GtkTextIter * start,const GtkTextIter * end,gsize * length,gpointer user_data)95 gimp_text_buffer_serialize (GtkTextBuffer     *register_buffer,
96                             GtkTextBuffer     *content_buffer,
97                             const GtkTextIter *start,
98                             const GtkTextIter *end,
99                             gsize             *length,
100                             gpointer           user_data)
101 {
102   GString     *string;
103   GtkTextIter  iter, old_iter;
104   GSList      *tag_list;
105   GSList      *active_tags;
106 
107   string = g_string_new ("<markup>");
108 
109   iter        = *start;
110   tag_list    = NULL;
111   active_tags = NULL;
112 
113   do
114     {
115       GSList *tmp;
116       gchar *tmp_text, *escaped_text;
117 
118       active_tags = NULL;
119       tag_list = gtk_text_iter_get_tags (&iter);
120 
121       /* Handle added tags */
122       for (tmp = tag_list; tmp; tmp = tmp->next)
123         {
124           GtkTextTag *tag = tmp->data;
125 
126           open_tag (GIMP_TEXT_BUFFER (register_buffer), string, tag);
127 
128           active_tags = g_slist_prepend (active_tags, tag);
129         }
130 
131       g_slist_free (tag_list);
132       old_iter = iter;
133 
134       /* Now try to go to either the next tag toggle, or if a pixbuf appears */
135       while (TRUE)
136         {
137           gunichar ch = gtk_text_iter_get_char (&iter);
138 
139           if (ch == 0xFFFC)
140             {
141               /* pixbuf? can't happen! */
142             }
143           else if (ch == 0)
144             {
145               break;
146             }
147           else
148             {
149               gtk_text_iter_forward_char (&iter);
150             }
151 
152           if (gtk_text_iter_toggles_tag (&iter, NULL))
153             break;
154         }
155 
156       /* We might have moved too far */
157       if (gtk_text_iter_compare (&iter, end) > 0)
158         iter = *end;
159 
160       /* Append the text */
161       tmp_text = gtk_text_iter_get_slice (&old_iter, &iter);
162       escaped_text = g_markup_escape_text (tmp_text, -1);
163       g_free (tmp_text);
164 
165       g_string_append (string, escaped_text);
166       g_free (escaped_text);
167 
168       /* Close any open tags */
169       for (tmp = active_tags; tmp; tmp = tmp->next)
170         close_tag (GIMP_TEXT_BUFFER (register_buffer), string, tmp->data);
171 
172       g_slist_free (active_tags);
173     }
174   while (! gtk_text_iter_equal (&iter, end));
175 
176   g_string_append (string, "</markup>");
177 
178   *length = string->len;
179 
180   return (guint8 *) g_string_free (string, FALSE);
181 }
182 
183 
184 /*  deserialize  */
185 
186 typedef enum
187 {
188   STATE_START,
189   STATE_MARKUP,
190   STATE_TAG,
191   STATE_UNKNOWN
192 } ParseState;
193 
194 typedef struct
195 {
196   GSList        *states;
197   GtkTextBuffer *register_buffer;
198   GtkTextBuffer *content_buffer;
199   GSList        *tag_stack;
200   GList         *spans;
201 } ParseInfo;
202 
203 typedef struct
204 {
205   gchar  *text;
206   GSList *tags;
207 } TextSpan;
208 
209 static void set_error (GError              **err,
210                        GMarkupParseContext  *context,
211                        int                   error_domain,
212                        int                   error_code,
213                        const char           *format,
214                        ...) G_GNUC_PRINTF (5, 6);
215 
216 static void
set_error(GError ** err,GMarkupParseContext * context,int error_domain,int error_code,const char * format,...)217 set_error (GError              **err,
218            GMarkupParseContext  *context,
219            int                   error_domain,
220            int                   error_code,
221            const char           *format,
222            ...)
223 {
224   gint     line, ch;
225   va_list  args;
226   gchar   *str;
227 
228   g_markup_parse_context_get_position (context, &line, &ch);
229 
230   va_start (args, format);
231   str = g_strdup_vprintf (format, args);
232   va_end (args);
233 
234   g_set_error (err, error_domain, error_code,
235                ("Line %d character %d: %s"),
236                line, ch, str);
237 
238   g_free (str);
239 }
240 
241 static void
push_state(ParseInfo * info,ParseState state)242 push_state (ParseInfo  *info,
243             ParseState  state)
244 {
245   info->states = g_slist_prepend (info->states, GINT_TO_POINTER (state));
246 }
247 
248 static void
pop_state(ParseInfo * info)249 pop_state (ParseInfo *info)
250 {
251   g_return_if_fail (info->states != NULL);
252 
253   info->states = g_slist_remove (info->states, info->states->data);
254 }
255 
256 static ParseState
peek_state(ParseInfo * info)257 peek_state (ParseInfo *info)
258 {
259   g_return_val_if_fail (info->states != NULL, STATE_START);
260 
261   return GPOINTER_TO_INT (info->states->data);
262 }
263 
264 static gboolean
check_no_attributes(GMarkupParseContext * context,const char * element_name,const char ** attribute_names,const char ** attribute_values,GError ** error)265 check_no_attributes (GMarkupParseContext  *context,
266                      const char           *element_name,
267                      const char          **attribute_names,
268                      const char          **attribute_values,
269                      GError              **error)
270 {
271   if (attribute_names[0] != NULL)
272     {
273       set_error (error, context,
274                  G_MARKUP_ERROR,
275                  G_MARKUP_ERROR_PARSE,
276                  _("Attribute \"%s\" is invalid on <%s> element in this context"),
277                  attribute_names[0], element_name);
278       return FALSE;
279     }
280 
281   return TRUE;
282 }
283 
284 static void
parse_tag_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,ParseInfo * info,GError ** error)285 parse_tag_element (GMarkupParseContext  *context,
286                    const gchar          *element_name,
287                    const gchar         **attribute_names,
288                    const gchar         **attribute_values,
289                    ParseInfo            *info,
290                    GError              **error)
291 {
292   GtkTextTag  *tag;
293   const gchar *attribute_name  = NULL;
294   const gchar *attribute_value = NULL;
295 
296   gimp_assert (peek_state (info) == STATE_MARKUP ||
297                peek_state (info) == STATE_TAG    ||
298                peek_state (info) == STATE_UNKNOWN);
299 
300   if (attribute_names)
301     attribute_name = attribute_names[0];
302 
303   if (attribute_values)
304     attribute_value = attribute_values[0];
305 
306   tag = gimp_text_buffer_name_to_tag (GIMP_TEXT_BUFFER (info->register_buffer),
307                                       element_name,
308                                       attribute_name, attribute_value);
309 
310   if (tag)
311     {
312       info->tag_stack = g_slist_prepend (info->tag_stack, tag);
313 
314       push_state (info, STATE_TAG);
315     }
316   else
317     {
318       push_state (info, STATE_UNKNOWN);
319     }
320 }
321 
322 static void
start_element_handler(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)323 start_element_handler (GMarkupParseContext  *context,
324                        const gchar          *element_name,
325                        const gchar         **attribute_names,
326                        const gchar         **attribute_values,
327                        gpointer              user_data,
328                        GError              **error)
329 {
330   ParseInfo *info = user_data;
331 
332   switch (peek_state (info))
333     {
334     case STATE_START:
335       if (! strcmp (element_name, "markup"))
336         {
337           if (! check_no_attributes (context, element_name,
338                                      attribute_names, attribute_values,
339                                      error))
340             return;
341 
342           push_state (info, STATE_MARKUP);
343           break;
344         }
345       else
346         {
347           set_error (error, context, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
348                      _("Outermost element in text must be <markup> not <%s>"),
349                      element_name);
350         }
351       break;
352 
353     case STATE_MARKUP:
354     case STATE_TAG:
355     case STATE_UNKNOWN:
356       parse_tag_element (context, element_name,
357                          attribute_names, attribute_values,
358                          info, error);
359       break;
360 
361     default:
362       gimp_assert_not_reached ();
363       break;
364     }
365 }
366 
367 static void
end_element_handler(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)368 end_element_handler (GMarkupParseContext  *context,
369                      const gchar          *element_name,
370                      gpointer              user_data,
371                      GError              **error)
372 {
373   ParseInfo *info = user_data;
374 
375   switch (peek_state (info))
376     {
377     case STATE_UNKNOWN:
378       pop_state (info);
379       gimp_assert (peek_state (info) == STATE_UNKNOWN ||
380                    peek_state (info) == STATE_TAG     ||
381                    peek_state (info) == STATE_MARKUP);
382       break;
383 
384     case STATE_TAG:
385       pop_state (info);
386       gimp_assert (peek_state (info) == STATE_UNKNOWN ||
387                    peek_state (info) == STATE_TAG     ||
388                    peek_state (info) == STATE_MARKUP);
389 
390       /* Pop tag */
391       info->tag_stack = g_slist_delete_link (info->tag_stack,
392                                              info->tag_stack);
393       break;
394 
395     case STATE_MARKUP:
396       pop_state (info);
397       gimp_assert (peek_state (info) == STATE_START);
398 
399       info->spans = g_list_reverse (info->spans);
400       break;
401 
402     default:
403       gimp_assert_not_reached ();
404       break;
405     }
406 }
407 
408 static gboolean
all_whitespace(const char * text,gint text_len)409 all_whitespace (const char *text,
410                 gint        text_len)
411 {
412   const char *p   = text;
413   const char *end = text + text_len;
414 
415   while (p != end)
416     {
417       if (! g_ascii_isspace (*p))
418         return FALSE;
419 
420       p = g_utf8_next_char (p);
421     }
422 
423   return TRUE;
424 }
425 
426 static void
text_handler(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)427 text_handler (GMarkupParseContext  *context,
428               const gchar          *text,
429               gsize                 text_len,
430               gpointer              user_data,
431               GError              **error)
432 {
433   ParseInfo *info = user_data;
434   TextSpan  *span;
435 
436   if (all_whitespace (text, text_len)   &&
437       peek_state (info) != STATE_MARKUP &&
438       peek_state (info) != STATE_TAG    &&
439       peek_state (info) != STATE_UNKNOWN)
440     return;
441 
442   switch (peek_state (info))
443     {
444     case STATE_START:
445       gimp_assert_not_reached (); /* gmarkup shouldn't do this */
446       break;
447 
448     case STATE_MARKUP:
449     case STATE_TAG:
450     case STATE_UNKNOWN:
451       if (text_len == 0)
452         return;
453 
454       span = g_new0 (TextSpan, 1);
455       span->text = g_strndup (text, text_len);
456       span->tags = g_slist_copy (info->tag_stack);
457 
458       info->spans = g_list_prepend (info->spans, span);
459       break;
460 
461     default:
462       gimp_assert_not_reached ();
463       break;
464     }
465 }
466 
467 static void
parse_info_init(ParseInfo * info,GtkTextBuffer * register_buffer,GtkTextBuffer * content_buffer)468 parse_info_init (ParseInfo     *info,
469                  GtkTextBuffer *register_buffer,
470                  GtkTextBuffer *content_buffer)
471 {
472   info->states          = g_slist_prepend (NULL, GINT_TO_POINTER (STATE_START));
473   info->tag_stack       = NULL;
474   info->spans           = NULL;
475   info->register_buffer = register_buffer;
476   info->content_buffer  = content_buffer;
477 }
478 
479 static void
text_span_free(TextSpan * span)480 text_span_free (TextSpan *span)
481 {
482   g_free (span->text);
483   g_slist_free (span->tags);
484   g_free (span);
485 }
486 
487 static void
parse_info_free(ParseInfo * info)488 parse_info_free (ParseInfo *info)
489 {
490   g_slist_free (info->tag_stack);
491   g_slist_free (info->states);
492 
493   g_list_free_full (info->spans, (GDestroyNotify) text_span_free);
494 }
495 
496 static void
insert_text(ParseInfo * info,GtkTextIter * iter)497 insert_text (ParseInfo   *info,
498              GtkTextIter *iter)
499 {
500   GtkTextIter  start_iter;
501   GtkTextMark *mark;
502   GList       *tmp;
503   GSList      *tags;
504 
505   start_iter = *iter;
506 
507   mark = gtk_text_buffer_create_mark (info->content_buffer,
508                                       "deserialize-insert-point",
509                                       &start_iter, TRUE);
510 
511   for (tmp = info->spans; tmp; tmp = tmp->next)
512     {
513       TextSpan *span = tmp->data;
514 
515       if (span->text)
516         gtk_text_buffer_insert (info->content_buffer, iter, span->text, -1);
517 
518       gtk_text_buffer_get_iter_at_mark (info->content_buffer, &start_iter, mark);
519 
520       /* Apply tags */
521       for (tags = span->tags; tags; tags = tags->next)
522         {
523           GtkTextTag *tag = tags->data;
524 
525           gtk_text_buffer_apply_tag (info->content_buffer, tag,
526                                      &start_iter, iter);
527         }
528 
529       gtk_text_buffer_move_mark (info->content_buffer, mark, iter);
530     }
531 
532   gtk_text_buffer_delete_mark (info->content_buffer, mark);
533 }
534 
535 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)536 gimp_text_buffer_deserialize (GtkTextBuffer *register_buffer,
537                               GtkTextBuffer *content_buffer,
538                               GtkTextIter   *iter,
539                               const guint8  *text,
540                               gsize          length,
541                               gboolean       create_tags,
542                               gpointer       user_data,
543                               GError       **error)
544 {
545   GMarkupParseContext *context;
546   ParseInfo            info;
547   gboolean             retval = FALSE;
548 
549   static const GMarkupParser markup_parser =
550   {
551     start_element_handler,
552     end_element_handler,
553     text_handler,
554     NULL,
555     NULL
556   };
557 
558   parse_info_init (&info, register_buffer, content_buffer);
559 
560   context = g_markup_parse_context_new (&markup_parser, 0, &info, NULL);
561 
562   if (! g_markup_parse_context_parse (context,
563                                       (const gchar *) text,
564                                       length,
565                                       error))
566     goto out;
567 
568   if (! g_markup_parse_context_end_parse (context, error))
569     goto out;
570 
571   retval = TRUE;
572 
573   insert_text (&info, iter);
574 
575  out:
576   parse_info_free (&info);
577 
578   g_markup_parse_context_free (context);
579 
580   return retval;
581 }
582 
583 void
gimp_text_buffer_pre_serialize(GimpTextBuffer * buffer,GtkTextBuffer * content)584 gimp_text_buffer_pre_serialize (GimpTextBuffer *buffer,
585                                 GtkTextBuffer  *content)
586 {
587   GtkTextIter iter;
588 
589   g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer));
590   g_return_if_fail (GTK_IS_TEXT_BUFFER (content));
591 
592   gtk_text_buffer_get_start_iter (content, &iter);
593 
594   do
595     {
596       GSList *tags = gtk_text_iter_get_tags (&iter);
597       GSList *list;
598 
599       for (list = tags; list; list = g_slist_next (list))
600         {
601           GtkTextTag *tag = list->data;
602 
603           if (g_list_find (buffer->kerning_tags, tag))
604             {
605               GtkTextIter end;
606 
607               gtk_text_buffer_insert_with_tags (content, &iter,
608                                                 WORD_JOINER, -1,
609                                                 tag, NULL);
610 
611               end = iter;
612               gtk_text_iter_forward_char (&end);
613 
614               gtk_text_buffer_remove_tag (content, tag, &iter, &end);
615               break;
616             }
617         }
618 
619       g_slist_free (tags);
620     }
621   while (gtk_text_iter_forward_char (&iter));
622 }
623 
624 void
gimp_text_buffer_post_deserialize(GimpTextBuffer * buffer,GtkTextBuffer * content)625 gimp_text_buffer_post_deserialize (GimpTextBuffer *buffer,
626                                    GtkTextBuffer  *content)
627 {
628   GtkTextIter iter;
629 
630   g_return_if_fail (GIMP_IS_TEXT_BUFFER (buffer));
631   g_return_if_fail (GTK_IS_TEXT_BUFFER (content));
632 
633   gtk_text_buffer_get_start_iter (content, &iter);
634 
635   do
636     {
637       GSList *tags = gtk_text_iter_get_tags (&iter);
638       GSList *list;
639 
640       for (list = tags; list; list = g_slist_next (list))
641         {
642           GtkTextTag *tag = list->data;
643 
644           if (g_list_find (buffer->kerning_tags, tag))
645             {
646               GtkTextIter end;
647 
648               gtk_text_iter_forward_char (&iter);
649               gtk_text_buffer_backspace (content, &iter, FALSE, TRUE);
650 
651               end = iter;
652               gtk_text_iter_forward_char (&end);
653 
654               gtk_text_buffer_apply_tag (content, tag, &iter, &end);
655               break;
656             }
657         }
658 
659       g_slist_free (tags);
660     }
661   while (gtk_text_iter_forward_char (&iter));
662 }
663