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"