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 the author(s) not be used in
11 * advertising or publicity pertaining to distribution of the software without
12 * specific, written prior permission. The authors make 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 * fccat fc-cat default
66 * pkgkit PackageKit package tag format
69 * Some ideas for future syntax extensions:
71 * - verbose builtin that is like FcPatternPrint
72 * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
73 * - allow indexing in +, -, ? filtering?
74 * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
78 #define FCCAT_FORMAT "\"%{file|basename|cescape}\" %{index} \"%{-file{%{=unparse|cescape}}}\""
79 #define FCMATCH_FORMAT "%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
80 #define FCLIST_FORMAT "%{?file{%{file}: }}%{-file{%{=unparse}}}"
81 #define PKGKIT_FORMAT "%{[]family{font(%{family|downcase|delete( )})\n}}%{[]lang{font(:lang=%{lang|downcase|translate(_,-)})\n}}"
85 message (const char *fmt, ...)
89 fprintf (stderr, "Fontconfig: Pattern format error: ");
90 vfprintf (stderr, fmt, args);
91 fprintf (stderr, ".\n");
96 typedef struct _FcFormatContext
98 const FcChar8 *format_orig;
99 const FcChar8 *format;
102 FcBool word_allocated;
106 FcFormatContextInit (FcFormatContext *c,
107 const FcChar8 *format,
111 c->format_orig = c->format = format;
112 c->format_len = strlen ((const char *) format);
114 if (c->format_len < scratch_len)
117 c->word_allocated = FcFalse;
121 c->word = malloc (c->format_len + 1);
122 c->word_allocated = FcTrue;
125 return c->word != NULL;
129 FcFormatContextDone (FcFormatContext *c)
131 if (c && c->word_allocated)
138 consume_char (FcFormatContext *c,
141 if (*c->format != term)
149 expect_char (FcFormatContext *c,
152 FcBool res = consume_char (c, term);
155 if (c->format == c->format_orig + c->format_len)
156 message ("format ended while expecting '%c'",
159 message ("expected '%c' at %d",
160 term, c->format - c->format_orig + 1);
166 FcCharIsPunct (const FcChar8 c)
185 static char escaped_char(const char ch)
188 case 'a': return '\a';
189 case 'b': return '\b';
190 case 'f': return '\f';
191 case 'n': return '\n';
192 case 'r': return '\r';
193 case 't': return '\t';
194 case 'v': return '\v';
200 read_word (FcFormatContext *c)
208 if (*c->format == '\\')
212 *p++ = escaped_char (*c->format++);
215 else if (FcCharIsPunct (*c->format))
224 message ("expected identifier at %d",
225 c->format - c->format_orig + 1);
233 read_chars (FcFormatContext *c,
240 while (*c->format && *c->format != '}' && *c->format != term)
242 if (*c->format == '\\')
246 *p++ = escaped_char (*c->format++);
256 message ("expected character data at %d",
257 c->format - c->format_orig + 1);
265 FcPatternFormatToBuf (FcPattern *pat,
266 const FcChar8 *format,
270 interpret_builtin (FcFormatContext *c,
277 if (!expect_char (c, '=') ||
281 /* try simple builtins first */
283 #define BUILTIN(name, func) \
284 else if (0 == strcmp ((const char *) c->word, name))\
285 do { new_str = func (pat); ret = FcTrue; } while (0)
286 BUILTIN ("unparse", FcNameUnparse);
287 /* BUILTIN ("verbose", FcPatternPrint); XXX */
296 FcStrBufString (buf, new_str);
304 /* now try our custom formats */
306 #define BUILTIN(name, format) \
307 else if (0 == strcmp ((const char *) c->word, name))\
308 ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
309 BUILTIN ("fccat", FCCAT_FORMAT);
310 BUILTIN ("fcmatch", FCMATCH_FORMAT);
311 BUILTIN ("fclist", FCLIST_FORMAT);
312 BUILTIN ("pkgkit", PKGKIT_FORMAT);
318 message ("unknown builtin \"%s\"",
325 interpret_expr (FcFormatContext *c,
331 interpret_subexpr (FcFormatContext *c,
335 return expect_char (c, '{') &&
336 interpret_expr (c, pat, buf, '}') &&
337 expect_char (c, '}');
341 maybe_interpret_subexpr (FcFormatContext *c,
345 return (*c->format == '{') ?
346 interpret_subexpr (c, pat, buf) :
351 skip_subexpr (FcFormatContext *c);
354 skip_percent (FcFormatContext *c)
356 if (!expect_char (c, '%'))
359 /* skip an optional width specifier */
360 if (strtol ((const char *) c->format, (char **) &c->format, 10))
363 if (!expect_char (c, '{'))
366 while(*c->format && *c->format != '}')
371 c->format++; /* skip over '\\' */
376 if (!skip_subexpr (c))
383 return expect_char (c, '}');
387 skip_expr (FcFormatContext *c)
389 while(*c->format && *c->format != '}')
394 c->format++; /* skip over '\\' */
399 if (!skip_percent (c))
410 skip_subexpr (FcFormatContext *c)
412 return expect_char (c, '{') &&
414 expect_char (c, '}');
418 maybe_skip_subexpr (FcFormatContext *c)
420 return (*c->format == '{') ?
426 interpret_filter_in (FcFormatContext *c,
433 if (!expect_char (c, '+'))
436 os = FcObjectSetCreate ();
443 if (!read_word (c) ||
444 !FcObjectSetAdd (os, (const char *) c->word))
446 FcObjectSetDestroy (os);
450 while (consume_char (c, ','));
452 subpat = FcPatternFilter (pat, os);
453 FcObjectSetDestroy (os);
456 !interpret_subexpr (c, subpat, buf))
459 FcPatternDestroy (subpat);
464 interpret_filter_out (FcFormatContext *c,
470 if (!expect_char (c, '-'))
473 subpat = FcPatternDuplicate (pat);
481 FcPatternDestroy (subpat);
485 FcPatternDel (subpat, (const char *) c->word);
487 while (consume_char (c, ','));
489 if (!interpret_subexpr (c, subpat, buf))
492 FcPatternDestroy (subpat);
497 interpret_cond (FcFormatContext *c,
503 if (!expect_char (c, '?'))
513 negate = consume_char (c, '!');
521 FcPatternGet (pat, (const char *) c->word, 0, &v)));
523 while (consume_char (c, ','));
527 if (!interpret_subexpr (c, pat, buf) ||
528 !maybe_skip_subexpr (c))
533 if (!skip_subexpr (c) ||
534 !maybe_interpret_subexpr (c, pat, buf))
542 interpret_count (FcFormatContext *c,
548 FcChar8 buf_static[64];
550 if (!expect_char (c, '#'))
557 e = FcPatternObjectFindElt (pat,
558 FcObjectFromName ((const char *) c->word));
563 for (l = FcPatternEltValues(e);
569 snprintf ((char *) buf_static, sizeof (buf_static), "%d", count);
570 FcStrBufString (buf, buf_static);
576 interpret_enumerate (FcFormatContext *c,
582 const FcChar8 *format_save;
585 FcStrList *lang_strs;
587 if (!expect_char (c, '[') ||
588 !expect_char (c, ']'))
591 os = FcObjectSetCreate ();
599 if (!read_word (c) ||
600 !FcObjectSetAdd (os, (const char *) c->word))
602 FcObjectSetDestroy (os);
606 while (consume_char (c, ','));
608 /* If we have one element and it's of type FcLangSet, we want
609 * to enumerate the languages in it. */
611 if (os->nobject == 1)
615 FcPatternGetLangSet (pat, os->objects[0], 0, &langset))
618 if (!(ss = FcLangSetGetLangs (langset)) ||
619 !(lang_strs = FcStrListCreate (ss)))
624 subpat = FcPatternDuplicate (pat);
628 format_save = c->format;
640 FcPatternDel (subpat, os->objects[0]);
641 if ((lang = FcStrListNext (lang_strs)))
644 FcPatternAddString (subpat, os->objects[0], lang);
650 for (i = 0; i < os->nobject; i++)
654 /* XXX this can be optimized by accessing valuelist linked lists
655 * directly and remembering where we were. Most (all) value lists
656 * in normal uses are pretty short though (language tags are
657 * stored as a LangSet, not separate values.). */
658 FcPatternDel (subpat, os->objects[i]);
660 FcPatternGet (pat, os->objects[i], idx, &v))
663 FcPatternAdd (subpat, os->objects[i], v, FcFalse);
671 c->format = format_save;
672 ret = interpret_subexpr (c, subpat, buf);
680 if (c->format == format_save)
684 FcPatternDestroy (subpat);
687 FcStrListDone (lang_strs);
688 FcObjectSetDestroy (os);
694 interpret_simple (FcFormatContext *c,
699 FcBool add_colon = FcFalse;
700 FcBool add_elt_name = FcFalse;
702 FcChar8 *else_string;
704 if (consume_char (c, ':'))
711 if (consume_char (c, '['))
713 idx = strtol ((const char *) c->format, (char **) &c->format, 10);
716 message ("expected non-negative number at %d",
717 c->format-1 - c->format_orig + 1);
720 if (!expect_char (c, ']'))
724 if (consume_char (c, '='))
725 add_elt_name = FcTrue;
729 if (consume_char (c, ':'))
732 /* divert the c->word for now */
734 c->word = c->word + strlen ((const char *) c->word) + 1;
735 /* for now we just support 'default value' */
736 if (!expect_char (c, '-') ||
737 !read_chars (c, '|'))
742 else_string = c->word;
746 e = FcPatternObjectFindElt (pat,
747 FcObjectFromName ((const char *) c->word));
748 if (e || else_string)
750 FcValueListPtr l = NULL;
753 FcStrBufChar (buf, ':');
756 FcStrBufString (buf, c->word);
757 FcStrBufChar (buf, '=');
761 l = FcPatternEltValues(e);
767 l = FcValueListNext(l);
772 if (!FcNameUnparseValue (buf, &l->value, '\0'))
779 FcNameUnparseValueList (buf, l, '\0');
785 FcStrBufString (buf, else_string);
793 cescape (FcFormatContext *c FC_UNUSED,
797 /* XXX escape \n etc? */
805 FcStrBufChar (buf, '\\');
808 FcStrBufChar (buf, *str++);
814 shescape (FcFormatContext *c FC_UNUSED,
818 FcStrBufChar (buf, '\'');
822 FcStrBufString (buf, (const FcChar8 *) "'\\''");
824 FcStrBufChar (buf, *str);
827 FcStrBufChar (buf, '\'');
832 xmlescape (FcFormatContext *c FC_UNUSED,
836 /* XXX escape \n etc? */
842 case '&': FcStrBufString (buf, (const FcChar8 *) "&"); break;
843 case '<': FcStrBufString (buf, (const FcChar8 *) "<"); break;
844 case '>': FcStrBufString (buf, (const FcChar8 *) ">"); break;
845 default: FcStrBufChar (buf, *str); break;
853 delete_chars (FcFormatContext *c,
857 /* XXX not UTF-8 aware */
859 if (!expect_char (c, '(') ||
860 !read_chars (c, ')') ||
861 !expect_char (c, ')'))
868 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
871 FcStrBufData (buf, str, p - str);
876 FcStrBufString (buf, str);
886 escape_chars (FcFormatContext *c,
890 /* XXX not UTF-8 aware */
892 if (!expect_char (c, '(') ||
893 !read_chars (c, ')') ||
894 !expect_char (c, ')'))
901 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
904 FcStrBufData (buf, str, p - str);
905 FcStrBufChar (buf, c->word[0]);
906 FcStrBufChar (buf, *p);
911 FcStrBufString (buf, str);
921 translate_chars (FcFormatContext *c,
925 char *from, *to, repeat;
926 int from_len, to_len;
928 /* XXX not UTF-8 aware */
930 if (!expect_char (c, '(') ||
931 !read_chars (c, ',') ||
932 !expect_char (c, ','))
935 from = (char *) c->word;
936 from_len = strlen (from);
937 to = from + from_len + 1;
939 /* hack: we temporarily divert c->word */
940 c->word = (FcChar8 *) to;
941 if (!read_chars (c, ')'))
943 c->word = (FcChar8 *) from;
946 c->word = (FcChar8 *) from;
948 to_len = strlen (to);
949 repeat = to[to_len - 1];
951 if (!expect_char (c, ')'))
958 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) from);
962 FcStrBufData (buf, str, p - str);
963 i = strchr (from, *p) - from;
964 FcStrBufChar (buf, i < to_len ? to[i] : repeat);
969 FcStrBufString (buf, str);
979 interpret_convert (FcFormatContext *c,
986 FcChar8 buf_static[8192];
989 if (!expect_char (c, '|') ||
993 /* prepare the buffer */
994 FcStrBufChar (buf, '\0');
997 str = buf->buf + start;
1000 /* try simple converters first */
1002 #define CONVERTER(name, func) \
1003 else if (0 == strcmp ((const char *) c->word, name))\
1004 do { new_str = func (str); ret = FcTrue; } while (0)
1005 CONVERTER ("downcase", FcStrDowncase);
1006 CONVERTER ("basename", FcStrBasename);
1007 CONVERTER ("dirname", FcStrDirname);
1016 FcStrBufString (buf, new_str);
1017 FcStrFree (new_str);
1024 FcStrBufInit (&new_buf, buf_static, sizeof (buf_static));
1026 /* now try our custom converters */
1028 #define CONVERTER(name, func) \
1029 else if (0 == strcmp ((const char *) c->word, name))\
1030 ret = func (c, str, &new_buf)
1031 CONVERTER ("cescape", cescape);
1032 CONVERTER ("shescape", shescape);
1033 CONVERTER ("xmlescape", xmlescape);
1034 CONVERTER ("delete", delete_chars);
1035 CONVERTER ("escape", escape_chars);
1036 CONVERTER ("translate", translate_chars);
1043 FcStrBufChar (&new_buf, '\0');
1044 FcStrBufString (buf, new_buf.buf);
1047 message ("unknown converter \"%s\"",
1050 FcStrBufDestroy (&new_buf);
1056 maybe_interpret_converts (FcFormatContext *c,
1060 while (*c->format == '|')
1061 if (!interpret_convert (c, buf, start))
1068 align_to_width (FcStrBuf *buf,
1077 len = buf->len - start;
1081 while (len++ < -width)
1082 FcStrBufChar (buf, ' ');
1084 else if (len < width)
1089 while (len++ < width)
1090 FcStrBufChar (buf, ' ');
1094 memmove (buf->buf + buf->len - len,
1095 buf->buf + buf->len - width,
1097 memset (buf->buf + buf->len - width,
1102 return !buf->failed;
1105 interpret_percent (FcFormatContext *c,
1112 if (!expect_char (c, '%'))
1115 if (consume_char (c, '%')) /* "%%" */
1117 FcStrBufChar (buf, '%');
1121 /* parse an optional width specifier */
1122 width = strtol ((const char *) c->format, (char **) &c->format, 10);
1124 if (!expect_char (c, '{'))
1129 switch (*c->format) {
1130 case '=': ret = interpret_builtin (c, pat, buf); break;
1131 case '{': ret = interpret_subexpr (c, pat, buf); break;
1132 case '+': ret = interpret_filter_in (c, pat, buf); break;
1133 case '-': ret = interpret_filter_out (c, pat, buf); break;
1134 case '?': ret = interpret_cond (c, pat, buf); break;
1135 case '#': ret = interpret_count (c, pat, buf); break;
1136 case '[': ret = interpret_enumerate (c, pat, buf); break;
1137 default: ret = interpret_simple (c, pat, buf); break;
1141 maybe_interpret_converts (c, buf, start) &&
1142 align_to_width (buf, start, width) &&
1143 expect_char (c, '}');
1147 interpret_expr (FcFormatContext *c,
1152 while (*c->format && *c->format != term)
1157 c->format++; /* skip over '\\' */
1159 FcStrBufChar (buf, escaped_char (*c->format++));
1162 if (!interpret_percent (c, pat, buf))
1166 FcStrBufChar (buf, *c->format++);
1172 FcPatternFormatToBuf (FcPattern *pat,
1173 const FcChar8 *format,
1177 FcChar8 word_static[1024];
1180 if (!FcFormatContextInit (&c, format, word_static, sizeof (word_static)))
1183 ret = interpret_expr (&c, pat, buf, '\0');
1185 FcFormatContextDone (&c);
1191 FcPatternFormat (FcPattern *pat,
1192 const FcChar8 *format)
1195 FcChar8 buf_static[8192 - 1024];
1196 FcPattern *alloced = NULL;
1200 alloced = pat = FcPatternCreate ();
1202 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
1204 ret = FcPatternFormatToBuf (pat, format, &buf);
1207 FcPatternDestroy (alloced);
1210 return FcStrBufDone (&buf);
1213 FcStrBufDestroy (&buf);
1218 #define __fcformat__
1219 #include "fcaliastail.h"