2
* Copyright © 2008,2009 Red Hat, Inc.
4
* Red Hat Author(s): Behdad Esfahbod
6
* Permission to use, copy, modify, distribute, and sell this software and its
7
* documentation for any purpose is hereby granted without fee, provided that
8
* the above copyright notice appear in all copies and that both that
9
* copyright notice and this permission notice appear in supporting
10
* documentation, and that the name of Keith Packard not be used in
11
* advertising or publicity pertaining to distribution of the software without
12
* specific, written prior permission. Keith Packard makes no
13
* representations about the suitability of this software for any purpose. It
14
* is provided "as is" without express or implied warranty.
16
* THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18
* EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
19
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
20
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
21
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22
* PERFORMANCE OF THIS SOFTWARE.
31
/* The language is documented in doc/fcformat.fncs
32
* These are the features implemented:
39
* default %{elt:-word}
42
* filter-out %{-elt1,elt2,elt3{expr}}
43
* filter-in %{+elt1,elt2,elt3{expr}}
44
* conditional %{?elt1,elt2,!elt3{}{}}
45
* enumerate %{[]elt1,elt2{expr}}
46
* langset langset enumeration using the same syntax
48
* convert %{elt|conv1|conv2|conv3}
51
* basename FcStrBasename
52
* dirname FcStrDirname
53
* downcase FcStrDowncase
59
* translate translate chars
62
* unparse FcNameUnparse
63
* fcmatch fc-match default
64
* fclist fc-list default
65
* pkgkit PackageKit package tag format
68
* Some ideas for future syntax extensions:
70
* - verbose builtin that is like FcPatternPrint
71
* - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
72
* - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
76
#define FCMATCH_FORMAT "%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
77
#define FCLIST_FORMAT "%{?file{%{file}: }}%{=unparse}"
78
#define PKGKIT_FORMAT "%{[]family{font(%{family|downcase|delete( )})\n}}%{[]lang{font(:lang=%{lang|downcase|translate(_,-)})\n}}"
82
message (const char *fmt, ...)
86
fprintf (stderr, "Fontconfig: Pattern format error: ");
87
vfprintf (stderr, fmt, args);
88
fprintf (stderr, ".\n");
93
typedef struct _FcFormatContext
95
const FcChar8 *format_orig;
96
const FcChar8 *format;
99
FcBool word_allocated;
103
FcFormatContextInit (FcFormatContext *c,
104
const FcChar8 *format,
108
c->format_orig = c->format = format;
109
c->format_len = strlen ((const char *) format);
111
if (c->format_len < scratch_len)
114
c->word_allocated = FcFalse;
118
c->word = malloc (c->format_len + 1);
119
c->word_allocated = FcTrue;
122
return c->word != NULL;
126
FcFormatContextDone (FcFormatContext *c)
128
if (c && c->word_allocated)
135
consume_char (FcFormatContext *c,
138
if (*c->format != term)
146
expect_char (FcFormatContext *c,
149
FcBool res = consume_char (c, term);
152
if (c->format == c->format_orig + c->format_len)
153
message ("format ended while expecting '%c'",
156
message ("expected '%c' at %d",
157
term, c->format - c->format_orig + 1);
163
FcCharIsPunct (const FcChar8 c)
182
static char escaped_char(const char ch)
185
case 'a': return '\a';
186
case 'b': return '\b';
187
case 'f': return '\f';
188
case 'n': return '\n';
189
case 'r': return '\r';
190
case 't': return '\t';
191
case 'v': return '\v';
197
read_word (FcFormatContext *c)
205
if (*c->format == '\\')
209
*p++ = escaped_char (*c->format++);
212
else if (FcCharIsPunct (*c->format))
221
message ("expected identifier at %d",
222
c->format - c->format_orig + 1);
230
read_chars (FcFormatContext *c,
237
while (*c->format && *c->format != '}' && *c->format != term)
239
if (*c->format == '\\')
243
*p++ = escaped_char (*c->format++);
253
message ("expected character data at %d",
254
c->format - c->format_orig + 1);
262
FcPatternFormatToBuf (FcPattern *pat,
263
const FcChar8 *format,
267
interpret_builtin (FcFormatContext *c,
274
if (!expect_char (c, '=') ||
278
/* try simple builtins first */
280
#define BUILTIN(name, func) \
281
else if (0 == strcmp ((const char *) c->word, name))\
282
do { new_str = func (pat); ret = FcTrue; } while (0)
283
BUILTIN ("unparse", FcNameUnparse);
284
/* BUILTIN ("verbose", FcPatternPrint); XXX */
293
FcStrBufString (buf, new_str);
301
/* now try our custom formats */
303
#define BUILTIN(name, format) \
304
else if (0 == strcmp ((const char *) c->word, name))\
305
ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
306
BUILTIN ("fcmatch", FCMATCH_FORMAT);
307
BUILTIN ("fclist", FCLIST_FORMAT);
308
BUILTIN ("pkgkit", PKGKIT_FORMAT);
314
message ("unknown builtin \"%s\"",
321
interpret_expr (FcFormatContext *c,
327
interpret_subexpr (FcFormatContext *c,
331
return expect_char (c, '{') &&
332
interpret_expr (c, pat, buf, '}') &&
333
expect_char (c, '}');
337
maybe_interpret_subexpr (FcFormatContext *c,
341
return (*c->format == '{') ?
342
interpret_subexpr (c, pat, buf) :
347
skip_subexpr (FcFormatContext *c);
350
skip_percent (FcFormatContext *c)
354
if (!expect_char (c, '%'))
357
/* skip an optional width specifier */
358
width = strtol ((const char *) c->format, (char **) &c->format, 10);
360
if (!expect_char (c, '{'))
363
while(*c->format && *c->format != '}')
368
c->format++; /* skip over '\\' */
373
if (!skip_subexpr (c))
380
return expect_char (c, '}');
384
skip_expr (FcFormatContext *c)
386
while(*c->format && *c->format != '}')
391
c->format++; /* skip over '\\' */
396
if (!skip_percent (c))
407
skip_subexpr (FcFormatContext *c)
409
return expect_char (c, '{') &&
411
expect_char (c, '}');
415
maybe_skip_subexpr (FcFormatContext *c)
417
return (*c->format == '{') ?
423
interpret_filter_in (FcFormatContext *c,
430
if (!expect_char (c, '+'))
433
os = FcObjectSetCreate ();
439
if (!read_word (c) ||
440
!FcObjectSetAdd (os, (const char *) c->word))
442
FcObjectSetDestroy (os);
446
while (consume_char (c, ','));
448
subpat = FcPatternFilter (pat, os);
449
FcObjectSetDestroy (os);
452
!interpret_subexpr (c, subpat, buf))
455
FcPatternDestroy (subpat);
460
interpret_filter_out (FcFormatContext *c,
466
if (!expect_char (c, '-'))
469
subpat = FcPatternDuplicate (pat);
477
FcPatternDestroy (subpat);
481
FcPatternDel (subpat, (const char *) c->word);
483
while (consume_char (c, ','));
485
if (!interpret_subexpr (c, subpat, buf))
488
FcPatternDestroy (subpat);
493
interpret_cond (FcFormatContext *c,
499
if (!expect_char (c, '?'))
509
negate = consume_char (c, '!');
517
FcPatternGet (pat, (const char *) c->word, 0, &v)));
519
while (consume_char (c, ','));
523
if (!interpret_subexpr (c, pat, buf) ||
524
!maybe_skip_subexpr (c))
529
if (!skip_subexpr (c) ||
530
!maybe_interpret_subexpr (c, pat, buf))
538
interpret_count (FcFormatContext *c,
544
FcChar8 buf_static[64];
546
if (!expect_char (c, '#'))
553
e = FcPatternObjectFindElt (pat,
554
FcObjectFromName ((const char *) c->word));
559
for (l = FcPatternEltValues(e);
565
snprintf ((char *) buf_static, sizeof (buf_static), "%d", count);
566
FcStrBufString (buf, buf_static);
572
interpret_enumerate (FcFormatContext *c,
578
const FcChar8 *format_save;
581
FcStrList *lang_strs;
583
if (!expect_char (c, '[') ||
584
!expect_char (c, ']'))
587
os = FcObjectSetCreate ();
595
if (!read_word (c) ||
596
!FcObjectSetAdd (os, (const char *) c->word))
598
FcObjectSetDestroy (os);
602
while (consume_char (c, ','));
604
/* If we have one element and it's of type FcLangSet, we want
605
* to enumerate the languages in it. */
607
if (os->nobject == 1)
611
FcPatternGetLangSet (pat, os->objects[0], idx, &langset))
614
if (!(ss = FcLangSetGetLangs (langset)) ||
615
!(lang_strs = FcStrListCreate (ss)))
620
subpat = FcPatternDuplicate (pat);
624
format_save = c->format;
636
FcPatternDel (subpat, os->objects[0]);
637
if ((lang = FcStrListNext (lang_strs)))
639
FcPatternAddString (subpat, os->objects[0], lang);
645
for (i = 0; i < os->nobject; i++)
649
/* XXX this can be optimized by accessing valuelist linked lists
650
* directly and remembering where we were. Most (all) value lists
651
* in normal uses are pretty short though (language tags are
652
* stored as a LangSet, not separate values.). */
653
FcPatternDel (subpat, os->objects[i]);
655
FcPatternGet (pat, os->objects[i], idx, &v))
657
FcPatternAdd (subpat, os->objects[i], v, FcFalse);
665
c->format = format_save;
666
ret = interpret_subexpr (c, subpat, buf);
674
if (c->format == format_save)
678
FcPatternDestroy (subpat);
681
FcStrListDone (lang_strs);
682
FcObjectSetDestroy (os);
688
interpret_simple (FcFormatContext *c,
693
FcBool add_colon = FcFalse;
694
FcBool add_elt_name = FcFalse;
696
FcChar8 *else_string;
698
if (consume_char (c, ':'))
705
if (consume_char (c, '['))
707
idx = strtol ((const char *) c->format, (char **) &c->format, 10);
710
message ("expected non-negative number at %d",
711
c->format-1 - c->format_orig + 1);
714
if (!expect_char (c, ']'))
718
if (consume_char (c, '='))
719
add_elt_name = FcTrue;
723
if (consume_char (c, ':'))
726
/* divert the c->word for now */
728
c->word = c->word + strlen ((const char *) c->word) + 1;
729
/* for now we just support 'default value' */
730
if (!expect_char (c, '-') ||
731
!read_chars (c, '\0'))
736
else_string = c->word;
740
e = FcPatternObjectFindElt (pat,
741
FcObjectFromName ((const char *) c->word));
742
if (e || else_string)
744
FcValueListPtr l = NULL;
747
FcStrBufChar (buf, ':');
750
FcStrBufString (buf, c->word);
751
FcStrBufChar (buf, '=');
755
l = FcPatternEltValues(e);
761
l = FcValueListNext(l);
766
if (!FcNameUnparseValue (buf, &l->value, '\0'))
773
FcNameUnparseValueList (buf, l, '\0');
779
FcStrBufString (buf, else_string);
787
cescape (FcFormatContext *c,
797
FcStrBufChar (buf, '\\');
800
FcStrBufChar (buf, *str++);
806
shescape (FcFormatContext *c,
810
FcStrBufChar (buf, '\'');
814
FcStrBufString (buf, (const FcChar8 *) "'\\''");
816
FcStrBufChar (buf, *str);
819
FcStrBufChar (buf, '\'');
824
xmlescape (FcFormatContext *c,
832
case '&': FcStrBufString (buf, (const FcChar8 *) "&"); break;
833
case '<': FcStrBufString (buf, (const FcChar8 *) "<"); break;
834
case '>': FcStrBufString (buf, (const FcChar8 *) ">"); break;
835
default: FcStrBufChar (buf, *str); break;
843
delete_chars (FcFormatContext *c,
847
/* XXX not UTF-8 aware */
849
if (!expect_char (c, '(') ||
850
!read_chars (c, ')') ||
851
!expect_char (c, ')'))
858
p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
861
FcStrBufData (buf, str, p - str);
866
FcStrBufString (buf, str);
876
escape_chars (FcFormatContext *c,
880
/* XXX not UTF-8 aware */
882
if (!expect_char (c, '(') ||
883
!read_chars (c, ')') ||
884
!expect_char (c, ')'))
891
p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
894
FcStrBufData (buf, str, p - str);
895
FcStrBufChar (buf, c->word[0]);
896
FcStrBufChar (buf, *p);
901
FcStrBufString (buf, str);
911
translate_chars (FcFormatContext *c,
915
char *from, *to, repeat;
916
int from_len, to_len;
918
/* XXX not UTF-8 aware */
920
if (!expect_char (c, '(') ||
921
!read_chars (c, ',') ||
922
!expect_char (c, ','))
925
from = (char *) c->word;
926
from_len = strlen (from);
927
to = from + from_len + 1;
929
/* hack: we temporarily divert c->word */
930
c->word = (FcChar8 *) to;
931
if (!read_chars (c, ')'))
933
c->word = (FcChar8 *) from;
936
c->word = (FcChar8 *) from;
938
to_len = strlen (to);
939
repeat = to[to_len - 1];
941
if (!expect_char (c, ')'))
948
p = (FcChar8 *) strpbrk ((const char *) str, (const char *) from);
952
FcStrBufData (buf, str, p - str);
953
i = strchr (from, *p) - from;
954
FcStrBufChar (buf, i < to_len ? to[i] : repeat);
959
FcStrBufString (buf, str);
969
interpret_convert (FcFormatContext *c,
976
FcChar8 buf_static[8192];
979
if (!expect_char (c, '|') ||
983
/* prepare the buffer */
984
FcStrBufChar (buf, '\0');
987
str = buf->buf + start;
990
/* try simple converters first */
992
#define CONVERTER(name, func) \
993
else if (0 == strcmp ((const char *) c->word, name))\
994
do { new_str = func (str); ret = FcTrue; } while (0)
995
CONVERTER ("downcase", FcStrDowncase);
996
CONVERTER ("basename", FcStrBasename);
997
CONVERTER ("dirname", FcStrDirname);
1006
FcStrBufString (buf, new_str);
1014
FcStrBufInit (&new_buf, buf_static, sizeof (buf_static));
1016
/* now try our custom converters */
1018
#define CONVERTER(name, func) \
1019
else if (0 == strcmp ((const char *) c->word, name))\
1020
ret = func (c, str, &new_buf)
1021
CONVERTER ("cescape", cescape);
1022
CONVERTER ("shescape", shescape);
1023
CONVERTER ("xmlescape", xmlescape);
1024
CONVERTER ("delete", delete_chars);
1025
CONVERTER ("escape", escape_chars);
1026
CONVERTER ("translate", translate_chars);
1033
FcStrBufChar (&new_buf, '\0');
1034
FcStrBufString (buf, new_buf.buf);
1037
message ("unknown converter \"%s\"",
1040
FcStrBufDestroy (&new_buf);
1046
maybe_interpret_converts (FcFormatContext *c,
1050
while (*c->format == '|')
1051
if (!interpret_convert (c, buf, start))
1058
align_to_width (FcStrBuf *buf,
1067
len = buf->len - start;
1071
while (len++ < -width)
1072
FcStrBufChar (buf, ' ');
1074
else if (len < width)
1079
while (len++ < width)
1080
FcStrBufChar (buf, ' ');
1084
memmove (buf->buf + buf->len - len,
1085
buf->buf + buf->len - width,
1087
memset (buf->buf + buf->len - width,
1092
return !buf->failed;
1095
interpret_percent (FcFormatContext *c,
1102
if (!expect_char (c, '%'))
1105
if (consume_char (c, '%')) /* "%%" */
1107
FcStrBufChar (buf, '%');
1111
/* parse an optional width specifier */
1112
width = strtol ((const char *) c->format, (char **) &c->format, 10);
1114
if (!expect_char (c, '{'))
1119
switch (*c->format) {
1120
case '=': ret = interpret_builtin (c, pat, buf); break;
1121
case '{': ret = interpret_subexpr (c, pat, buf); break;
1122
case '+': ret = interpret_filter_in (c, pat, buf); break;
1123
case '-': ret = interpret_filter_out (c, pat, buf); break;
1124
case '?': ret = interpret_cond (c, pat, buf); break;
1125
case '#': ret = interpret_count (c, pat, buf); break;
1126
case '[': ret = interpret_enumerate (c, pat, buf); break;
1127
default: ret = interpret_simple (c, pat, buf); break;
1131
maybe_interpret_converts (c, buf, start) &&
1132
align_to_width (buf, start, width) &&
1133
expect_char (c, '}');
1137
interpret_expr (FcFormatContext *c,
1142
while (*c->format && *c->format != term)
1147
c->format++; /* skip over '\\' */
1149
FcStrBufChar (buf, escaped_char (*c->format++));
1152
if (!interpret_percent (c, pat, buf))
1156
FcStrBufChar (buf, *c->format++);
1162
FcPatternFormatToBuf (FcPattern *pat,
1163
const FcChar8 *format,
1167
FcChar8 word_static[1024];
1170
if (!FcFormatContextInit (&c, format, word_static, sizeof (word_static)))
1173
ret = interpret_expr (&c, pat, buf, '\0');
1175
FcFormatContextDone (&c);
1181
FcPatternFormat (FcPattern *pat,
1182
const FcChar8 *format)
1185
FcChar8 buf_static[8192 - 1024];
1188
FcStrBufInit (&buf, buf_static, sizeof (buf_static));
1190
ret = FcPatternFormatToBuf (pat, format, &buf);
1193
return FcStrBufDone (&buf);
1196
FcStrBufDestroy (&buf);
1201
#define __fcformat__
1202
#include "fcaliastail.h"