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