Upload tizen 2.0 beta source
[external/pango1.0.git] / pango / pango-markup.c
1 /* Pango
2  * pango-markup.c: Parse markup into attributed text
3  *
4  * Copyright (C) 2000 Red Hat Software
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #include "config.h"
23 #include <string.h>
24 #include <stdlib.h>
25 #include <errno.h>
26
27 #include "pango-attributes.h"
28 #include "pango-font.h"
29 #include "pango-enum-types.h"
30 #include "pango-impl-utils.h"
31
32 /* FIXME */
33 #define _(x) x
34
35 /* CSS size levels */
36 typedef enum
37 {
38   XXSmall = -3,
39   XSmall = -2,
40   Small = -1,
41   Medium = 0,
42   Large = 1,
43   XLarge = 2,
44   XXLarge = 3
45 } SizeLevel;
46
47 typedef struct _MarkupData MarkupData;
48
49 struct _MarkupData
50 {
51   PangoAttrList *attr_list;
52   GString *text;
53   GSList *tag_stack;
54   gsize index;
55   GSList *to_apply;
56   gunichar accel_marker;
57   gunichar accel_char;
58 };
59
60 typedef struct _OpenTag OpenTag;
61
62 struct _OpenTag
63 {
64   GSList *attrs;
65   gsize start_index;
66   /* Current total scale level; reset whenever
67    * an absolute size is set.
68    * Each "larger" ups it 1, each "smaller" decrements it 1
69    */
70   gint scale_level;
71   /* Our impact on scale_level, so we know whether we
72    * need to create an attribute ourselves on close
73    */
74   gint scale_level_delta;
75   /* Base scale factor currently in effect
76    * or size that this tag
77    * forces, or parent's scale factor or size.
78    */
79   double base_scale_factor;
80   int base_font_size;
81   guint has_base_font_size : 1;
82 };
83
84 typedef gboolean (*TagParseFunc) (MarkupData            *md,
85                                   OpenTag               *tag,
86                                   const gchar          **names,
87                                   const gchar          **values,
88                                   GMarkupParseContext   *context,
89                                   GError               **error);
90
91 static gboolean b_parse_func        (MarkupData           *md,
92                                      OpenTag              *tag,
93                                      const gchar         **names,
94                                      const gchar         **values,
95                                      GMarkupParseContext  *context,
96                                      GError              **error);
97 static gboolean big_parse_func      (MarkupData           *md,
98                                      OpenTag              *tag,
99                                      const gchar         **names,
100                                      const gchar         **values,
101                                      GMarkupParseContext  *context,
102                                      GError              **error);
103 static gboolean span_parse_func     (MarkupData           *md,
104                                      OpenTag              *tag,
105                                      const gchar         **names,
106                                      const gchar         **values,
107                                      GMarkupParseContext  *context,
108                                      GError              **error);
109 static gboolean i_parse_func        (MarkupData           *md,
110                                      OpenTag              *tag,
111                                      const gchar         **names,
112                                      const gchar         **values,
113                                      GMarkupParseContext  *context,
114                                      GError              **error);
115 static gboolean markup_parse_func   (MarkupData           *md,
116                                      OpenTag              *tag,
117                                      const gchar         **names,
118                                      const gchar         **values,
119                                      GMarkupParseContext  *context,
120                                      GError              **error);
121 static gboolean s_parse_func        (MarkupData           *md,
122                                      OpenTag              *tag,
123                                      const gchar         **names,
124                                      const gchar         **values,
125                                      GMarkupParseContext  *context,
126                                      GError              **error);
127 static gboolean sub_parse_func      (MarkupData           *md,
128                                      OpenTag              *tag,
129                                      const gchar         **names,
130                                      const gchar         **values,
131                                      GMarkupParseContext  *context,
132                                      GError              **error);
133 static gboolean sup_parse_func      (MarkupData           *md,
134                                      OpenTag              *tag,
135                                      const gchar         **names,
136                                      const gchar         **values,
137                                      GMarkupParseContext  *context,
138                                      GError              **error);
139 static gboolean small_parse_func    (MarkupData           *md,
140                                      OpenTag              *tag,
141                                      const gchar         **names,
142                                      const gchar         **values,
143                                      GMarkupParseContext  *context,
144                                      GError              **error);
145 static gboolean tt_parse_func       (MarkupData           *md,
146                                      OpenTag              *tag,
147                                      const gchar         **names,
148                                      const gchar         **values,
149                                      GMarkupParseContext  *context,
150                                      GError              **error);
151 static gboolean u_parse_func        (MarkupData           *md,
152                                      OpenTag              *tag,
153                                      const gchar         **names,
154                                      const gchar         **values,
155                                      GMarkupParseContext  *context,
156                                      GError              **error);
157
158 static double
159 scale_factor (int scale_level, double base)
160 {
161   double factor = base;
162   int i;
163
164   /* 1.2 is the CSS scale factor between sizes */
165
166   if (scale_level > 0)
167     {
168       i = 0;
169       while (i < scale_level)
170         {
171           factor *= 1.2;
172
173           ++i;
174         }
175     }
176   else if (scale_level < 0)
177     {
178       i = scale_level;
179       while (i < 0)
180         {
181           factor /= 1.2;
182
183           ++i;
184         }
185     }
186
187   return factor;
188 }
189
190 static void
191 open_tag_free (OpenTag *ot)
192 {
193   g_slist_foreach (ot->attrs, (GFunc) pango_attribute_destroy, NULL);
194   g_slist_free (ot->attrs);
195   g_slice_free (OpenTag, ot);
196 }
197
198 static void
199 open_tag_set_absolute_font_size (OpenTag *ot,
200                                  int      font_size)
201 {
202   ot->base_font_size = font_size;
203   ot->has_base_font_size = TRUE;
204   ot->scale_level = 0;
205   ot->scale_level_delta = 0;
206 }
207
208 static void
209 open_tag_set_absolute_font_scale (OpenTag *ot,
210                                   double   scale)
211 {
212   ot->base_scale_factor = scale;
213   ot->has_base_font_size = FALSE;
214   ot->scale_level = 0;
215   ot->scale_level_delta = 0;
216 }
217
218 static OpenTag*
219 markup_data_open_tag (MarkupData   *md)
220 {
221   OpenTag *ot;
222   OpenTag *parent = NULL;
223
224   if (md->attr_list == NULL)
225     return NULL;
226
227   if (md->tag_stack)
228     parent = md->tag_stack->data;
229
230   ot = g_slice_new (OpenTag);
231   ot->attrs = NULL;
232   ot->start_index = md->index;
233   ot->scale_level_delta = 0;
234
235   if (parent == NULL)
236     {
237       ot->base_scale_factor = 1.0;
238       ot->base_font_size = 0;
239       ot->has_base_font_size = FALSE;
240       ot->scale_level = 0;
241     }
242   else
243     {
244       ot->base_scale_factor = parent->base_scale_factor;
245       ot->base_font_size = parent->base_font_size;
246       ot->has_base_font_size = parent->has_base_font_size;
247       ot->scale_level = parent->scale_level;
248     }
249
250   md->tag_stack = g_slist_prepend (md->tag_stack, ot);
251
252   return ot;
253 }
254
255 static void
256 markup_data_close_tag (MarkupData *md)
257 {
258   OpenTag *ot;
259   GSList *tmp_list;
260
261   if (md->attr_list == NULL)
262     return;
263
264   /* pop the stack */
265   ot = md->tag_stack->data;
266   md->tag_stack = g_slist_delete_link (md->tag_stack,
267                                        md->tag_stack);
268
269   /* Adjust end indexes, and push each attr onto the front of the
270    * to_apply list. This means that outermost tags are on the front of
271    * that list; if we apply the list in order, then the innermost
272    * tags will "win" which is correct.
273    */
274   tmp_list = ot->attrs;
275   while (tmp_list != NULL)
276     {
277       PangoAttribute *a = tmp_list->data;
278
279       a->start_index = ot->start_index;
280       a->end_index = md->index;
281
282       md->to_apply = g_slist_prepend (md->to_apply, a);
283
284       tmp_list = g_slist_next (tmp_list);
285     }
286
287   if (ot->scale_level_delta != 0)
288     {
289       /* We affected relative font size; create an appropriate
290        * attribute and reverse our effects on the current level
291        */
292       PangoAttribute *a;
293
294       if (ot->has_base_font_size)
295         {
296           /* Create a font using the absolute point size
297            * as the base size to be scaled from
298            */
299           a = pango_attr_size_new (scale_factor (ot->scale_level,
300                                                  1.0) *
301                                    ot->base_font_size);
302         }
303       else
304         {
305           /* Create a font using the current scale factor
306            * as the base size to be scaled from
307            */
308           a = pango_attr_scale_new (scale_factor (ot->scale_level,
309                                                   ot->base_scale_factor));
310         }
311
312       a->start_index = ot->start_index;
313       a->end_index = md->index;
314
315       md->to_apply = g_slist_prepend (md->to_apply, a);
316     }
317
318   g_slist_free (ot->attrs);
319   g_slice_free (OpenTag, ot);
320 }
321
322 static void
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   TagParseFunc parse_func = NULL;
331   OpenTag *ot;
332
333   switch (*element_name)
334     {
335     case 'b':
336       if (strcmp ("b", element_name) == 0)
337         parse_func = b_parse_func;
338       else if (strcmp ("big", element_name) == 0)
339         parse_func = big_parse_func;
340       break;
341
342     case 'i':
343       if (strcmp ("i", element_name) == 0)
344         parse_func = i_parse_func;
345       break;
346
347     case 'm':
348       if (strcmp ("markup", element_name) == 0)
349         parse_func = markup_parse_func;
350       break;
351
352     case 's':
353       if (strcmp ("span", element_name) == 0)
354         parse_func = span_parse_func;
355       else if (strcmp ("s", element_name) == 0)
356         parse_func = s_parse_func;
357       else if (strcmp ("sub", element_name) == 0)
358         parse_func = sub_parse_func;
359       else if (strcmp ("sup", element_name) == 0)
360         parse_func = sup_parse_func;
361       else if (strcmp ("small", element_name) == 0)
362         parse_func = small_parse_func;
363       break;
364
365     case 't':
366       if (strcmp ("tt", element_name) == 0)
367         parse_func = tt_parse_func;
368       break;
369
370     case 'u':
371       if (strcmp ("u", element_name) == 0)
372         parse_func = u_parse_func;
373       break;
374     }
375
376   if (parse_func == NULL)
377     {
378       gint line_number, char_number;
379
380       g_markup_parse_context_get_position (context,
381                                            &line_number, &char_number);
382
383       g_set_error (error,
384                    G_MARKUP_ERROR,
385                    G_MARKUP_ERROR_UNKNOWN_ELEMENT,
386                    _("Unknown tag '%s' on line %d char %d"),
387                    element_name,
388                    line_number, char_number);
389
390       return;
391     }
392
393   ot = markup_data_open_tag (user_data);
394
395   /* note ot may be NULL if the user didn't want the attribute list */
396
397   if (!(*parse_func) (user_data, ot,
398                       attribute_names, attribute_values,
399                       context, error))
400     {
401       /* there's nothing to do; we return an error, and end up
402        * freeing ot off the tag stack later.
403        */
404     }
405 }
406
407 static void
408 end_element_handler    (GMarkupParseContext *context G_GNUC_UNUSED,
409                         const gchar         *element_name G_GNUC_UNUSED,
410                         gpointer             user_data,
411                         GError             **error G_GNUC_UNUSED)
412 {
413   markup_data_close_tag (user_data);
414 }
415
416 static void
417 text_handler           (GMarkupParseContext *context G_GNUC_UNUSED,
418                         const gchar         *text,
419                         gsize                text_len,
420                         gpointer             user_data,
421                         GError             **error G_GNUC_UNUSED)
422 {
423   MarkupData *md = user_data;
424
425   if (md->accel_marker == 0)
426     {
427       /* Just append all the text */
428
429       md->index += text_len;
430
431       g_string_append_len (md->text, text, text_len);
432     }
433   else
434     {
435       /* Parse the accelerator */
436       const gchar *p;
437       const gchar *end;
438       const gchar *range_start;
439       const gchar *range_end;
440       gssize uline_index = -1;
441       gsize uline_len = 0;      /* Quiet GCC */
442
443       range_end = NULL;
444       range_start = text;
445       p = text;
446       end = text + text_len;
447
448       while (p != end)
449         {
450           gunichar c;
451
452           c = g_utf8_get_char (p);
453
454           if (range_end)
455             {
456               if (c == md->accel_marker)
457                 {
458                   /* escaped accel marker; move range_end
459                    * past the accel marker that came before,
460                    * append the whole thing
461                    */
462                   range_end = g_utf8_next_char (range_end);
463                   g_string_append_len (md->text,
464                                        range_start,
465                                        range_end - range_start);
466                   md->index += range_end - range_start;
467
468                   /* set next range_start, skipping accel marker */
469                   range_start = g_utf8_next_char (p);
470                 }
471               else
472                 {
473                   /* Don't append the accel marker (leave range_end
474                    * alone); set the accel char to c; record location for
475                    * underline attribute
476                    */
477                   if (md->accel_char == 0)
478                     md->accel_char = c;
479
480                   g_string_append_len (md->text,
481                                        range_start,
482                                        range_end - range_start);
483                   md->index += range_end - range_start;
484
485                   /* The underline should go underneath the char
486                    * we're setting as the next range_start
487                    */
488                   uline_index = md->index;
489                   uline_len = g_utf8_next_char (p) - p;
490
491                   /* set next range_start to include this char */
492                   range_start = p;
493                 }
494
495               /* reset range_end */
496               range_end = NULL;
497             }
498           else if (c == md->accel_marker)
499             {
500               range_end = p;
501             }
502
503           p = g_utf8_next_char (p);
504         }
505
506       if (range_end)
507         {
508           g_string_append_len (md->text,
509                                range_start,
510                                range_end - range_start);
511           md->index += range_end - range_start;
512         }
513       else
514         {
515           g_string_append_len (md->text,
516                                range_start,
517                                end - range_start);
518           md->index += end - range_start;
519         }
520
521       if (md->attr_list != NULL && uline_index >= 0)
522         {
523           /* Add the underline indicating the accelerator */
524           PangoAttribute *attr;
525
526           attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW);
527
528           attr->start_index = uline_index;
529           attr->end_index = uline_index + uline_len;
530
531           pango_attr_list_change (md->attr_list, attr);
532         }
533     }
534 }
535
536 static gboolean
537 xml_isspace (char c)
538 {
539   return c == ' ' || c == '\t' || c == '\n' || c == '\r';
540 }
541
542 static const GMarkupParser pango_markup_parser = {
543   start_element_handler,
544   end_element_handler,
545   text_handler,
546   NULL,
547   NULL
548 };
549
550 /**
551  * pango_parse_markup:
552  * @markup_text: markup to parse (see <link linkend="PangoMarkupFormat">markup format</link>)
553  * @length: length of @markup_text, or -1 if nul-terminated
554  * @accel_marker: character that precedes an accelerator, or 0 for none
555  * @attr_list: address of return location for a #PangoAttrList, or %NULL
556  * @text: address of return location for text with tags stripped, or %NULL
557  * @accel_char: address of return location for accelerator char, or %NULL
558  * @error: address of return location for errors, or %NULL
559  *
560  * Parses marked-up text (see
561  * <link linkend="PangoMarkupFormat">markup format</link>) to create
562  * a plain-text string and an attribute list.
563  *
564  * If @accel_marker is nonzero, the given character will mark the
565  * character following it as an accelerator. For example, @accel_marker
566  * might be an ampersand or underscore. All characters marked
567  * as an accelerator will receive a %PANGO_UNDERLINE_LOW attribute,
568  * and the first character so marked will be returned in @accel_char.
569  * Two @accel_marker characters following each other produce a single
570  * literal @accel_marker character.
571  *
572  * If any error happens, none of the output arguments are touched except
573  * for @error.
574  *
575  * Return value: %FALSE if @error is set, otherwise %TRUE
576  **/
577 gboolean
578 pango_parse_markup (const char                 *markup_text,
579                     int                         length,
580                     gunichar                    accel_marker,
581                     PangoAttrList             **attr_list,
582                     char                      **text,
583                     gunichar                   *accel_char,
584                     GError                    **error)
585 {
586   GMarkupParseContext *context = NULL;
587   MarkupData *md = NULL;
588   gboolean needs_root = TRUE;
589   GSList *tmp_list;
590   const char *p;
591   const char *end;
592
593   g_return_val_if_fail (markup_text != NULL, FALSE);
594
595   md = g_slice_new (MarkupData);
596
597   /* Don't bother creating these if they weren't requested;
598    * might be useful e.g. if you just want to validate
599    * some markup.
600    */
601   if (attr_list)
602     md->attr_list = pango_attr_list_new ();
603   else
604     md->attr_list = NULL;
605
606   md->text = g_string_new (NULL);
607
608   if (accel_char)
609     *accel_char = 0;
610
611   md->accel_marker = accel_marker;
612   md->accel_char = 0;
613
614   md->index = 0;
615   md->tag_stack = NULL;
616   md->to_apply = NULL;
617
618   context = g_markup_parse_context_new (&pango_markup_parser,
619                                         0, md, NULL);
620
621   if (length < 0)
622     length = strlen (markup_text);
623
624   p = markup_text;
625   end = markup_text + length;
626   while (p != end && xml_isspace (*p))
627     ++p;
628
629   if (end - p >= 8 && strncmp (p, "<markup>", 8) == 0)
630      needs_root = FALSE;
631
632   if (needs_root)
633     if (!g_markup_parse_context_parse (context,
634                                        "<markup>",
635                                        -1,
636                                        error))
637       goto error;
638
639
640   if (!g_markup_parse_context_parse (context,
641                                      markup_text,
642                                      length,
643                                      error))
644     goto error;
645
646   if (needs_root)
647     if (!g_markup_parse_context_parse (context,
648                                        "</markup>",
649                                        -1,
650                                        error))
651       goto error;
652
653   if (!g_markup_parse_context_end_parse (context, error))
654     goto error;
655
656   g_markup_parse_context_free (context);
657
658   if (md->attr_list)
659     {
660       /* The apply list has the most-recently-closed tags first;
661        * we want to apply the least-recently-closed tag last.
662        */
663       tmp_list = md->to_apply;
664       while (tmp_list != NULL)
665         {
666           PangoAttribute *attr = tmp_list->data;
667
668           /* Innermost tags before outermost */
669           pango_attr_list_insert (md->attr_list, attr);
670
671           tmp_list = g_slist_next (tmp_list);
672         }
673       g_slist_free (md->to_apply);
674       md->to_apply = NULL;
675     }
676
677   if (attr_list)
678     *attr_list = md->attr_list;
679
680   if (text)
681     *text = g_string_free (md->text, FALSE);
682   else
683     g_string_free (md->text, TRUE);
684
685   if (accel_char)
686     *accel_char = md->accel_char;
687
688   g_assert (md->tag_stack == NULL);
689
690   g_slice_free (MarkupData, md);
691
692   return TRUE;
693
694  error:
695   g_slist_foreach (md->tag_stack, (GFunc) open_tag_free, NULL);
696   g_slist_free (md->tag_stack);
697   g_slist_foreach (md->to_apply, (GFunc) pango_attribute_destroy, NULL);
698   g_slist_free (md->to_apply);
699   g_string_free (md->text, TRUE);
700
701   if (md->attr_list)
702     pango_attr_list_unref (md->attr_list);
703
704   g_slice_free (MarkupData, md);
705
706   if (context)
707     g_markup_parse_context_free (context);
708
709   return FALSE;
710 }
711
712 static void
713 set_bad_attribute (GError             **error,
714                    GMarkupParseContext *context,
715                    const char          *element_name,
716                    const char          *attribute_name)
717 {
718   gint line_number, char_number;
719
720   g_markup_parse_context_get_position (context,
721                                        &line_number, &char_number);
722
723   g_set_error (error,
724                G_MARKUP_ERROR,
725                G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
726                _("Tag '%s' does not support attribute '%s' on line %d char %d"),
727                element_name,
728                attribute_name,
729                line_number, char_number);
730 }
731
732 static void
733 add_attribute (OpenTag        *ot,
734                PangoAttribute *attr)
735 {
736   if (ot == NULL)
737     pango_attribute_destroy (attr);
738   else
739     ot->attrs = g_slist_prepend (ot->attrs, attr);
740 }
741
742 #define CHECK_NO_ATTRS(elem) G_STMT_START {                    \
743          if (*names != NULL) {                                 \
744            set_bad_attribute (error, context, (elem), *names); \
745            return FALSE;                                       \
746          } }G_STMT_END
747
748 static gboolean
749 b_parse_func        (MarkupData            *md G_GNUC_UNUSED,
750                      OpenTag               *tag,
751                      const gchar          **names,
752                      const gchar          **values G_GNUC_UNUSED,
753                      GMarkupParseContext   *context,
754                      GError               **error)
755 {
756   CHECK_NO_ATTRS("b");
757   add_attribute (tag, pango_attr_weight_new (PANGO_WEIGHT_BOLD));
758   return TRUE;
759 }
760
761 static gboolean
762 big_parse_func      (MarkupData            *md G_GNUC_UNUSED,
763                      OpenTag               *tag,
764                      const gchar          **names,
765                      const gchar          **values G_GNUC_UNUSED,
766                      GMarkupParseContext   *context,
767                      GError               **error)
768 {
769   CHECK_NO_ATTRS("big");
770
771   /* Grow text one level */
772   if (tag)
773     {
774       tag->scale_level_delta += 1;
775       tag->scale_level += 1;
776     }
777
778   return TRUE;
779 }
780
781 static gboolean
782 parse_absolute_size (OpenTag               *tag,
783                      const char            *size)
784 {
785   SizeLevel level = Medium;
786   double factor;
787
788   if (strcmp (size, "xx-small") == 0)
789     level = XXSmall;
790   else if (strcmp (size, "x-small") == 0)
791     level = XSmall;
792   else if (strcmp (size, "small") == 0)
793     level = Small;
794   else if (strcmp (size, "medium") == 0)
795     level = Medium;
796   else if (strcmp (size, "large") == 0)
797     level = Large;
798   else if (strcmp (size, "x-large") == 0)
799     level = XLarge;
800   else if (strcmp (size, "xx-large") == 0)
801     level = XXLarge;
802   else
803     return FALSE;
804
805   /* This is "absolute" in that it's relative to the base font,
806    * but not to sizes created by any other tags
807    */
808   factor = scale_factor (level, 1.0);
809   add_attribute (tag, pango_attr_scale_new (factor));
810   if (tag)
811     open_tag_set_absolute_font_scale (tag, factor);
812
813   return TRUE;
814 }
815
816 /* a string compare func that ignores '-' vs '_' differences */
817 static gint
818 attr_strcmp (gconstpointer pa,
819              gconstpointer pb)
820 {
821   const char *a = pa;
822   const char *b = pb;
823
824   int ca;
825   int cb;
826
827   while (*a && *b)
828     {
829       ca = *a++;
830       cb = *b++;
831
832       if (ca == cb)
833         continue;
834
835       ca = ca == '_' ? '-' : ca;
836       cb = cb == '_' ? '-' : cb;
837
838       if (ca != cb)
839         return cb - ca;
840     }
841
842   ca = *a;
843   cb = *b;
844
845   return cb - ca;
846 }
847
848 static gboolean
849 span_parse_int (const char *attr_name,
850                 const char *attr_val,
851                 int *val,
852                 int line_number,
853                 GError **error)
854 {
855   const char *end = attr_val;
856
857   if (!pango_scan_int (&end, val) || *end != '\0')
858     {
859       g_set_error (error,
860                    G_MARKUP_ERROR,
861                    G_MARKUP_ERROR_INVALID_CONTENT,
862                    _("Value of '%s' attribute on <span> tag "
863                      "on line %d could not be parsed; "
864                      "should be an integer, not '%s'"),
865                    attr_name, line_number, attr_val);
866       return FALSE;
867     }
868
869   return TRUE;
870 }
871
872 static gboolean
873 span_parse_boolean (const char *attr_name,
874                     const char *attr_val,
875                     gboolean *val,
876                     int line_number,
877                     GError **error)
878 {
879   if (strcmp (attr_val, "true") == 0 ||
880       strcmp (attr_val, "yes") == 0 ||
881       strcmp (attr_val, "t") == 0 ||
882       strcmp (attr_val, "y") == 0)
883     *val = TRUE;
884   else if (strcmp (attr_val, "false") == 0 ||
885            strcmp (attr_val, "no") == 0 ||
886            strcmp (attr_val, "f") == 0 ||
887            strcmp (attr_val, "n") == 0)
888     *val = FALSE;
889   else
890     {
891       g_set_error (error,
892                    G_MARKUP_ERROR,
893                    G_MARKUP_ERROR_INVALID_CONTENT,
894                    _("Value of '%s' attribute on <span> tag "
895                      "line %d should have one of "
896                      "'true/yes/t/y' or 'false/no/f/n': '%s' is not valid"),
897                    attr_name, line_number, attr_val);
898       return FALSE;
899     }
900
901   return TRUE;
902 }
903
904 static gboolean
905 span_parse_color (const char *attr_name,
906                   const char *attr_val,
907                   PangoColor *color,
908                   int line_number,
909                   GError **error)
910 {
911   if (!pango_color_parse (color, attr_val))
912     {
913       g_set_error (error,
914                    G_MARKUP_ERROR,
915                    G_MARKUP_ERROR_INVALID_CONTENT,
916                    _("Value of '%s' attribute on <span> tag "
917                      "on line %d could not be parsed; "
918                      "should be a color specification, not '%s'"),
919                    attr_name, line_number, attr_val);
920       return FALSE;
921     }
922
923   return TRUE;
924 }
925
926 static gboolean
927 span_parse_enum (const char *attr_name,
928                  const char *attr_val,
929                  GType type,
930                  int *val,
931                  int line_number,
932                  GError **error)
933 {
934   char *possible_values = NULL;
935
936   if (!pango_parse_enum (type, attr_val, val, FALSE, &possible_values))
937     {
938       g_set_error (error,
939                    G_MARKUP_ERROR,
940                    G_MARKUP_ERROR_INVALID_CONTENT,
941                    _("'%s' is not a valid value for the '%s' "
942                      "attribute on <span> tag, line %d; valid "
943                      "values are %s"),
944                    attr_val, attr_name, line_number, possible_values);
945       g_free (possible_values);
946       return FALSE;
947     }
948
949   return TRUE;
950 }
951
952 static gboolean
953 span_parse_func     (MarkupData            *md G_GNUC_UNUSED,
954                      OpenTag               *tag,
955                      const gchar          **names,
956                      const gchar          **values,
957                      GMarkupParseContext   *context,
958                      GError               **error)
959 {
960   int line_number, char_number;
961   int i;
962
963   const char *family = NULL;
964   const char *size = NULL;
965   const char *style = NULL;
966   const char *weight = NULL;
967   const char *variant = NULL;
968   const char *stretch = NULL;
969   const char *desc = NULL;
970   const char *foreground = NULL;
971   const char *background = NULL;
972   const char *underline = NULL;
973   const char *underline_color = NULL;
974   const char *strikethrough = NULL;
975   const char *strikethrough_color = NULL;
976   const char *rise = NULL;
977   const char *letter_spacing = NULL;
978   const char *lang = NULL;
979   const char *fallback = NULL;
980   const char *gravity = NULL;
981   const char *gravity_hint = NULL;
982
983   g_markup_parse_context_get_position (context,
984                                        &line_number, &char_number);
985
986 #define CHECK_DUPLICATE(var) G_STMT_START{                              \
987           if ((var) != NULL) {                                          \
988             g_set_error (error, G_MARKUP_ERROR,                         \
989                          G_MARKUP_ERROR_INVALID_CONTENT,                \
990                          _("Attribute '%s' occurs twice on <span> tag " \
991                            "on line %d char %d, may only occur once"),  \
992                          names[i], line_number, char_number);           \
993             return FALSE;                                               \
994           }}G_STMT_END
995 #define CHECK_ATTRIBUTE2(var, name) \
996         if (attr_strcmp (names[i], (name)) == 0) { \
997           CHECK_DUPLICATE (var); \
998           (var) = values[i]; \
999           found = TRUE; \
1000           break; \
1001         }
1002 #define CHECK_ATTRIBUTE(var) CHECK_ATTRIBUTE2 (var, G_STRINGIFY (var))
1003
1004   i = 0;
1005   while (names[i])
1006     {
1007       gboolean found = FALSE;
1008
1009       switch (names[i][0]) {
1010       case 'f':
1011         CHECK_ATTRIBUTE (fallback);
1012         CHECK_ATTRIBUTE2(desc, "font");
1013         CHECK_ATTRIBUTE2(desc, "font_desc");
1014         CHECK_ATTRIBUTE2(family, "face");
1015
1016         CHECK_ATTRIBUTE2(family, "font_family");
1017         CHECK_ATTRIBUTE2(size, "font_size");
1018         CHECK_ATTRIBUTE2(stretch, "font_stretch");
1019         CHECK_ATTRIBUTE2(style, "font_style");
1020         CHECK_ATTRIBUTE2(variant, "font_variant");
1021         CHECK_ATTRIBUTE2(weight, "font_weight");
1022
1023         CHECK_ATTRIBUTE (foreground);
1024         CHECK_ATTRIBUTE2 (foreground, "fgcolor");
1025         break;
1026       case 's':
1027         CHECK_ATTRIBUTE (size);
1028         CHECK_ATTRIBUTE (stretch);
1029         CHECK_ATTRIBUTE (strikethrough);
1030         CHECK_ATTRIBUTE (strikethrough_color);
1031         CHECK_ATTRIBUTE (style);
1032         break;
1033       case 'g':
1034         CHECK_ATTRIBUTE (gravity);
1035         CHECK_ATTRIBUTE (gravity_hint);
1036         break;
1037       case 'l':
1038         CHECK_ATTRIBUTE (lang);
1039         CHECK_ATTRIBUTE (letter_spacing);
1040         break;
1041       case 'u':
1042         CHECK_ATTRIBUTE (underline);
1043         CHECK_ATTRIBUTE (underline_color);
1044         break;
1045       default:
1046         CHECK_ATTRIBUTE (background);
1047         CHECK_ATTRIBUTE2 (background, "bgcolor");
1048         CHECK_ATTRIBUTE2(foreground, "color");
1049         CHECK_ATTRIBUTE (rise);
1050         CHECK_ATTRIBUTE (variant);
1051         CHECK_ATTRIBUTE (weight);
1052         break;
1053       }
1054
1055       if (!found)
1056         {
1057           g_set_error (error, G_MARKUP_ERROR,
1058                        G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
1059                        _("Attribute '%s' is not allowed on the <span> tag "
1060                          "on line %d char %d"),
1061                        names[i], line_number, char_number);
1062           return FALSE;
1063         }
1064
1065       ++i;
1066     }
1067
1068   /* Parse desc first, then modify it with other font-related attributes. */
1069   if (G_UNLIKELY (desc))
1070     {
1071       PangoFontDescription *parsed;
1072
1073       parsed = pango_font_description_from_string (desc);
1074       if (parsed)
1075         {
1076           add_attribute (tag, pango_attr_font_desc_new (parsed));
1077           if (tag)
1078             open_tag_set_absolute_font_size (tag, pango_font_description_get_size (parsed));
1079           pango_font_description_free (parsed);
1080         }
1081     }
1082
1083   if (G_UNLIKELY (family))
1084     {
1085       add_attribute (tag, pango_attr_family_new (family));
1086     }
1087
1088   if (G_UNLIKELY (size))
1089     {
1090       if (g_ascii_isdigit (*size))
1091         {
1092           const char *end;
1093           gint n;
1094
1095 /* cap size from the top at an arbitrary 2048 */
1096 #define MAX_SIZE (2048 * PANGO_SCALE)
1097
1098           if ((end = size, !pango_scan_int (&end, &n)) || *end != '\0' || n < 0 || n > MAX_SIZE)
1099             {
1100               g_set_error (error,
1101                            G_MARKUP_ERROR,
1102                            G_MARKUP_ERROR_INVALID_CONTENT,
1103                            _("Value of 'size' attribute on <span> tag on line %d "
1104                              "could not be parsed; should be an integer less than %d, or a "
1105                              "string such as 'small', not '%s'"),
1106                            line_number, MAX_SIZE+1, size);
1107               goto error;
1108             }
1109
1110           add_attribute (tag, pango_attr_size_new (n));
1111           if (tag)
1112             open_tag_set_absolute_font_size (tag, n);
1113         }
1114       else if (strcmp (size, "smaller") == 0)
1115         {
1116           if (tag)
1117             {
1118               tag->scale_level_delta -= 1;
1119               tag->scale_level -= 1;
1120             }
1121         }
1122       else if (strcmp (size, "larger") == 0)
1123         {
1124           if (tag)
1125             {
1126               tag->scale_level_delta += 1;
1127               tag->scale_level += 1;
1128             }
1129         }
1130       else if (parse_absolute_size (tag, size))
1131         ; /* nothing */
1132       else
1133         {
1134           g_set_error (error,
1135                        G_MARKUP_ERROR,
1136                        G_MARKUP_ERROR_INVALID_CONTENT,
1137                        _("Value of 'size' attribute on <span> tag on line %d "
1138                          "could not be parsed; should be an integer, or a "
1139                          "string such as 'small', not '%s'"),
1140                        line_number, size);
1141           goto error;
1142         }
1143     }
1144
1145   if (G_UNLIKELY (style))
1146     {
1147       PangoStyle pango_style;
1148
1149       if (pango_parse_style (style, &pango_style, FALSE))
1150         add_attribute (tag, pango_attr_style_new (pango_style));
1151       else
1152         {
1153           g_set_error (error,
1154                        G_MARKUP_ERROR,
1155                        G_MARKUP_ERROR_INVALID_CONTENT,
1156                        _("'%s' is not a valid value for the 'style' attribute "
1157                          "on <span> tag, line %d; valid values are "
1158                          "'normal', 'oblique', 'italic'"),
1159                        style, line_number);
1160           goto error;
1161         }
1162     }
1163
1164   if (G_UNLIKELY (weight))
1165     {
1166       PangoWeight pango_weight;
1167
1168       if (pango_parse_weight (weight, &pango_weight, FALSE))
1169         add_attribute (tag,
1170                        pango_attr_weight_new (pango_weight));
1171       else
1172         {
1173           g_set_error (error,
1174                        G_MARKUP_ERROR,
1175                        G_MARKUP_ERROR_INVALID_CONTENT,
1176                        _("'%s' is not a valid value for the 'weight' "
1177                          "attribute on <span> tag, line %d; valid "
1178                          "values are for example 'light', 'ultrabold' or a number"),
1179                        weight, line_number);
1180           goto error;
1181         }
1182     }
1183
1184   if (G_UNLIKELY (variant))
1185     {
1186       PangoVariant pango_variant;
1187
1188       if (pango_parse_variant (variant, &pango_variant, FALSE))
1189         add_attribute (tag, pango_attr_variant_new (pango_variant));
1190       else
1191         {
1192           g_set_error (error,
1193                        G_MARKUP_ERROR,
1194                        G_MARKUP_ERROR_INVALID_CONTENT,
1195                        _("'%s' is not a valid value for the 'variant' "
1196                          "attribute on <span> tag, line %d; valid values are "
1197                          "'normal', 'smallcaps'"),
1198                        variant, line_number);
1199           goto error;
1200         }
1201     }
1202
1203   if (G_UNLIKELY (stretch))
1204     {
1205       PangoStretch pango_stretch;
1206
1207       if (pango_parse_stretch (stretch, &pango_stretch, FALSE))
1208         add_attribute (tag, pango_attr_stretch_new (pango_stretch));
1209       else
1210         {
1211           g_set_error (error,
1212                        G_MARKUP_ERROR,
1213                        G_MARKUP_ERROR_INVALID_CONTENT,
1214                        _("'%s' is not a valid value for the 'stretch' "
1215                          "attribute on <span> tag, line %d; valid "
1216                          "values are for example 'condensed', "
1217                          "'ultraexpanded', 'normal'"),
1218                        stretch, line_number);
1219           goto error;
1220         }
1221     }
1222
1223   if (G_UNLIKELY (foreground))
1224     {
1225       PangoColor color;
1226
1227       if (!span_parse_color ("foreground", foreground, &color, line_number, error))
1228         goto error;
1229
1230       add_attribute (tag, pango_attr_foreground_new (color.red, color.green, color.blue));
1231     }
1232
1233   if (G_UNLIKELY (background))
1234     {
1235       PangoColor color;
1236
1237       if (!span_parse_color ("background", background, &color, line_number, error))
1238         goto error;
1239
1240       add_attribute (tag, pango_attr_background_new (color.red, color.green, color.blue));
1241     }
1242
1243   if (G_UNLIKELY (underline))
1244     {
1245       PangoUnderline ul = PANGO_UNDERLINE_NONE;
1246
1247       if (!span_parse_enum ("underline", underline, PANGO_TYPE_UNDERLINE, (int*)(void*)&ul, line_number, error))
1248         goto error;
1249
1250       add_attribute (tag, pango_attr_underline_new (ul));
1251     }
1252
1253   if (G_UNLIKELY (underline_color))
1254     {
1255       PangoColor color;
1256
1257       if (!span_parse_color ("underline_color", underline_color, &color, line_number, error))
1258         goto error;
1259
1260       add_attribute (tag, pango_attr_underline_color_new (color.red, color.green, color.blue));
1261     }
1262
1263   if (G_UNLIKELY (gravity))
1264     {
1265       PangoGravity gr = PANGO_GRAVITY_SOUTH;
1266
1267       if (!span_parse_enum ("gravity", gravity, PANGO_TYPE_GRAVITY, (int*)(void*)&gr, line_number, error))
1268         goto error;
1269
1270       add_attribute (tag, pango_attr_gravity_new (gr));
1271     }
1272
1273   if (G_UNLIKELY (gravity_hint))
1274     {
1275       PangoGravityHint hint = PANGO_GRAVITY_HINT_NATURAL;
1276
1277       if (!span_parse_enum ("gravity_hint", gravity_hint, PANGO_TYPE_GRAVITY_HINT, (int*)(void*)&hint, line_number, error))
1278         goto error;
1279
1280       add_attribute (tag, pango_attr_gravity_hint_new (hint));
1281     }
1282
1283   if (G_UNLIKELY (strikethrough))
1284     {
1285       gboolean b = FALSE;
1286
1287       if (!span_parse_boolean ("strikethrough", strikethrough, &b, line_number, error))
1288         goto error;
1289
1290       add_attribute (tag, pango_attr_strikethrough_new (b));
1291     }
1292
1293   if (G_UNLIKELY (strikethrough_color))
1294     {
1295       PangoColor color;
1296
1297       if (!span_parse_color ("strikethrough_color", strikethrough_color, &color, line_number, error))
1298         goto error;
1299
1300       add_attribute (tag, pango_attr_strikethrough_color_new (color.red, color.green, color.blue));
1301     }
1302
1303   if (G_UNLIKELY (fallback))
1304     {
1305       gboolean b = FALSE;
1306
1307       if (!span_parse_boolean ("fallback", fallback, &b, line_number, error))
1308         goto error;
1309
1310       add_attribute (tag, pango_attr_fallback_new (b));
1311     }
1312
1313   if (G_UNLIKELY (rise))
1314     {
1315       gint n = 0;
1316
1317       if (!span_parse_int ("rise", rise, &n, line_number, error))
1318         goto error;
1319
1320       add_attribute (tag, pango_attr_rise_new (n));
1321     }
1322
1323   if (G_UNLIKELY (letter_spacing))
1324     {
1325       gint n = 0;
1326
1327       if (!span_parse_int ("letter_spacing", letter_spacing, &n, line_number, error))
1328         goto error;
1329
1330       add_attribute (tag, pango_attr_letter_spacing_new (n));
1331     }
1332
1333   if (G_UNLIKELY (lang))
1334     {
1335       add_attribute (tag,
1336                      pango_attr_language_new (pango_language_from_string (lang)));
1337     }
1338
1339   return TRUE;
1340
1341  error:
1342
1343   return FALSE;
1344 }
1345
1346 static gboolean
1347 i_parse_func        (MarkupData            *md G_GNUC_UNUSED,
1348                      OpenTag               *tag,
1349                      const gchar          **names,
1350                      const gchar          **values G_GNUC_UNUSED,
1351                      GMarkupParseContext   *context,
1352                      GError               **error)
1353 {
1354   CHECK_NO_ATTRS("i");
1355   add_attribute (tag, pango_attr_style_new (PANGO_STYLE_ITALIC));
1356
1357   return TRUE;
1358 }
1359
1360 static gboolean
1361 markup_parse_func (MarkupData            *md G_GNUC_UNUSED,
1362                    OpenTag               *tag G_GNUC_UNUSED,
1363                    const gchar          **names G_GNUC_UNUSED,
1364                    const gchar          **values G_GNUC_UNUSED,
1365                    GMarkupParseContext   *context G_GNUC_UNUSED,
1366                    GError               **error G_GNUC_UNUSED)
1367 {
1368   /* We don't do anything with this tag at the moment. */
1369
1370   return TRUE;
1371 }
1372
1373 static gboolean
1374 s_parse_func        (MarkupData            *md G_GNUC_UNUSED,
1375                      OpenTag               *tag,
1376                      const gchar          **names,
1377                      const gchar          **values G_GNUC_UNUSED,
1378                      GMarkupParseContext   *context,
1379                      GError               **error)
1380 {
1381   CHECK_NO_ATTRS("s");
1382   add_attribute (tag, pango_attr_strikethrough_new (TRUE));
1383
1384   return TRUE;
1385 }
1386
1387 #define SUPERSUB_RISE 5000
1388
1389 static gboolean
1390 sub_parse_func      (MarkupData            *md G_GNUC_UNUSED,
1391                      OpenTag               *tag,
1392                      const gchar          **names,
1393                      const gchar          **values G_GNUC_UNUSED,
1394                      GMarkupParseContext   *context,
1395                      GError               **error)
1396 {
1397   CHECK_NO_ATTRS("sub");
1398
1399   /* Shrink font, and set a negative rise */
1400   if (tag)
1401     {
1402       tag->scale_level_delta -= 1;
1403       tag->scale_level -= 1;
1404     }
1405
1406   add_attribute (tag, pango_attr_rise_new (-SUPERSUB_RISE));
1407
1408   return TRUE;
1409 }
1410
1411 static gboolean
1412 sup_parse_func      (MarkupData            *md G_GNUC_UNUSED,
1413                      OpenTag               *tag,
1414                      const gchar          **names,
1415                      const gchar          **values G_GNUC_UNUSED,
1416                      GMarkupParseContext   *context,
1417                      GError               **error)
1418 {
1419   CHECK_NO_ATTRS("sup");
1420
1421   /* Shrink font, and set a positive rise */
1422   if (tag)
1423     {
1424       tag->scale_level_delta -= 1;
1425       tag->scale_level -= 1;
1426     }
1427
1428   add_attribute (tag, pango_attr_rise_new (SUPERSUB_RISE));
1429
1430   return TRUE;
1431 }
1432
1433 static gboolean
1434 small_parse_func    (MarkupData            *md G_GNUC_UNUSED,
1435                      OpenTag               *tag,
1436                      const gchar          **names,
1437                      const gchar          **values G_GNUC_UNUSED,
1438                      GMarkupParseContext   *context,
1439                      GError               **error)
1440 {
1441   CHECK_NO_ATTRS("small");
1442
1443   /* Shrink text one level */
1444   if (tag)
1445     {
1446       tag->scale_level_delta -= 1;
1447       tag->scale_level -= 1;
1448     }
1449
1450   return TRUE;
1451 }
1452
1453 static gboolean
1454 tt_parse_func       (MarkupData            *md G_GNUC_UNUSED,
1455                      OpenTag               *tag,
1456                      const gchar          **names,
1457                      const gchar          **values G_GNUC_UNUSED,
1458                      GMarkupParseContext   *context,
1459                      GError               **error)
1460 {
1461   CHECK_NO_ATTRS("tt");
1462
1463   add_attribute (tag, pango_attr_family_new ("Monospace"));
1464
1465   return TRUE;
1466 }
1467
1468 static gboolean
1469 u_parse_func        (MarkupData            *md G_GNUC_UNUSED,
1470                      OpenTag               *tag,
1471                      const gchar          **names,
1472                      const gchar          **values G_GNUC_UNUSED,
1473                      GMarkupParseContext   *context,
1474                      GError               **error)
1475 {
1476   CHECK_NO_ATTRS("u");
1477   add_attribute (tag, pango_attr_underline_new (PANGO_UNDERLINE_SINGLE));
1478
1479   return TRUE;
1480 }