]>
git.wh0rd.org - fontconfig.git/blob - src/fcformat.c
4a850ee0944df487d95a77417b1615baba70f744
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 * 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 * - allow indexing in +, -, ? filtering?
73 * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
77 #define FCMATCH_FORMAT "%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
78 #define FCLIST_FORMAT "%{?file{%{file}: }}%{-file{%{=unparse}}}"
79 #define PKGKIT_FORMAT "%{[]family{font(%{family|downcase|delete( )})\n}}%{[]lang{font(:lang=%{lang|downcase|translate(_,-)})\n}}"
83 message (const char *fmt
, ...)
87 fprintf (stderr
, "Fontconfig: Pattern format error: ");
88 vfprintf (stderr
, fmt
, args
);
89 fprintf (stderr
, ".\n");
94 typedef struct _FcFormatContext
96 const FcChar8
*format_orig
;
97 const FcChar8
*format
;
100 FcBool word_allocated
;
104 FcFormatContextInit (FcFormatContext
*c
,
105 const FcChar8
*format
,
109 c
->format_orig
= c
->format
= format
;
110 c
->format_len
= strlen ((const char *) format
);
112 if (c
->format_len
< scratch_len
)
115 c
->word_allocated
= FcFalse
;
119 c
->word
= malloc (c
->format_len
+ 1);
120 c
->word_allocated
= FcTrue
;
123 return c
->word
!= NULL
;
127 FcFormatContextDone (FcFormatContext
*c
)
129 if (c
&& c
->word_allocated
)
136 consume_char (FcFormatContext
*c
,
139 if (*c
->format
!= term
)
147 expect_char (FcFormatContext
*c
,
150 FcBool res
= consume_char (c
, term
);
153 if (c
->format
== c
->format_orig
+ c
->format_len
)
154 message ("format ended while expecting '%c'",
157 message ("expected '%c' at %d",
158 term
, c
->format
- c
->format_orig
+ 1);
164 FcCharIsPunct (const FcChar8 c
)
183 static char escaped_char(const char ch
)
186 case 'a': return '\a';
187 case 'b': return '\b';
188 case 'f': return '\f';
189 case 'n': return '\n';
190 case 'r': return '\r';
191 case 't': return '\t';
192 case 'v': return '\v';
198 read_word (FcFormatContext
*c
)
206 if (*c
->format
== '\\')
210 *p
++ = escaped_char (*c
->format
++);
213 else if (FcCharIsPunct (*c
->format
))
222 message ("expected identifier at %d",
223 c
->format
- c
->format_orig
+ 1);
231 read_chars (FcFormatContext
*c
,
238 while (*c
->format
&& *c
->format
!= '}' && *c
->format
!= term
)
240 if (*c
->format
== '\\')
244 *p
++ = escaped_char (*c
->format
++);
254 message ("expected character data at %d",
255 c
->format
- c
->format_orig
+ 1);
263 FcPatternFormatToBuf (FcPattern
*pat
,
264 const FcChar8
*format
,
268 interpret_builtin (FcFormatContext
*c
,
275 if (!expect_char (c
, '=') ||
279 /* try simple builtins first */
281 #define BUILTIN(name, func) \
282 else if (0 == strcmp ((const char *) c->word, name))\
283 do { new_str = func (pat); ret = FcTrue; } while (0)
284 BUILTIN ("unparse", FcNameUnparse
);
285 /* BUILTIN ("verbose", FcPatternPrint); XXX */
294 FcStrBufString (buf
, new_str
);
302 /* now try our custom formats */
304 #define BUILTIN(name, format) \
305 else if (0 == strcmp ((const char *) c->word, name))\
306 ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
307 BUILTIN ("fcmatch", FCMATCH_FORMAT
);
308 BUILTIN ("fclist", FCLIST_FORMAT
);
309 BUILTIN ("pkgkit", PKGKIT_FORMAT
);
315 message ("unknown builtin \"%s\"",
322 interpret_expr (FcFormatContext
*c
,
328 interpret_subexpr (FcFormatContext
*c
,
332 return expect_char (c
, '{') &&
333 interpret_expr (c
, pat
, buf
, '}') &&
334 expect_char (c
, '}');
338 maybe_interpret_subexpr (FcFormatContext
*c
,
342 return (*c
->format
== '{') ?
343 interpret_subexpr (c
, pat
, buf
) :
348 skip_subexpr (FcFormatContext
*c
);
351 skip_percent (FcFormatContext
*c
)
355 if (!expect_char (c
, '%'))
358 /* skip an optional width specifier */
359 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
361 if (!expect_char (c
, '{'))
364 while(*c
->format
&& *c
->format
!= '}')
369 c
->format
++; /* skip over '\\' */
374 if (!skip_subexpr (c
))
381 return expect_char (c
, '}');
385 skip_expr (FcFormatContext
*c
)
387 while(*c
->format
&& *c
->format
!= '}')
392 c
->format
++; /* skip over '\\' */
397 if (!skip_percent (c
))
408 skip_subexpr (FcFormatContext
*c
)
410 return expect_char (c
, '{') &&
412 expect_char (c
, '}');
416 maybe_skip_subexpr (FcFormatContext
*c
)
418 return (*c
->format
== '{') ?
424 interpret_filter_in (FcFormatContext
*c
,
431 if (!expect_char (c
, '+'))
434 os
= FcObjectSetCreate ();
441 if (!read_word (c
) ||
442 !FcObjectSetAdd (os
, (const char *) c
->word
))
444 FcObjectSetDestroy (os
);
448 while (consume_char (c
, ','));
450 subpat
= FcPatternFilter (pat
, os
);
451 FcObjectSetDestroy (os
);
454 !interpret_subexpr (c
, subpat
, buf
))
457 FcPatternDestroy (subpat
);
462 interpret_filter_out (FcFormatContext
*c
,
468 if (!expect_char (c
, '-'))
471 subpat
= FcPatternDuplicate (pat
);
479 FcPatternDestroy (subpat
);
483 FcPatternDel (subpat
, (const char *) c
->word
);
485 while (consume_char (c
, ','));
487 if (!interpret_subexpr (c
, subpat
, buf
))
490 FcPatternDestroy (subpat
);
495 interpret_cond (FcFormatContext
*c
,
501 if (!expect_char (c
, '?'))
511 negate
= consume_char (c
, '!');
519 FcPatternGet (pat
, (const char *) c
->word
, 0, &v
)));
521 while (consume_char (c
, ','));
525 if (!interpret_subexpr (c
, pat
, buf
) ||
526 !maybe_skip_subexpr (c
))
531 if (!skip_subexpr (c
) ||
532 !maybe_interpret_subexpr (c
, pat
, buf
))
540 interpret_count (FcFormatContext
*c
,
546 FcChar8 buf_static
[64];
548 if (!expect_char (c
, '#'))
555 e
= FcPatternObjectFindElt (pat
,
556 FcObjectFromName ((const char *) c
->word
));
561 for (l
= FcPatternEltValues(e
);
567 snprintf ((char *) buf_static
, sizeof (buf_static
), "%d", count
);
568 FcStrBufString (buf
, buf_static
);
574 interpret_enumerate (FcFormatContext
*c
,
580 const FcChar8
*format_save
;
583 FcStrList
*lang_strs
;
585 if (!expect_char (c
, '[') ||
586 !expect_char (c
, ']'))
589 os
= FcObjectSetCreate ();
597 if (!read_word (c
) ||
598 !FcObjectSetAdd (os
, (const char *) c
->word
))
600 FcObjectSetDestroy (os
);
604 while (consume_char (c
, ','));
606 /* If we have one element and it's of type FcLangSet, we want
607 * to enumerate the languages in it. */
609 if (os
->nobject
== 1)
613 FcPatternGetLangSet (pat
, os
->objects
[0], 0, &langset
))
616 if (!(ss
= FcLangSetGetLangs (langset
)) ||
617 !(lang_strs
= FcStrListCreate (ss
)))
622 subpat
= FcPatternDuplicate (pat
);
626 format_save
= c
->format
;
638 FcPatternDel (subpat
, os
->objects
[0]);
639 if ((lang
= FcStrListNext (lang_strs
)))
642 FcPatternAddString (subpat
, os
->objects
[0], lang
);
648 for (i
= 0; i
< os
->nobject
; i
++)
652 /* XXX this can be optimized by accessing valuelist linked lists
653 * directly and remembering where we were. Most (all) value lists
654 * in normal uses are pretty short though (language tags are
655 * stored as a LangSet, not separate values.). */
656 FcPatternDel (subpat
, os
->objects
[i
]);
658 FcPatternGet (pat
, os
->objects
[i
], idx
, &v
))
661 FcPatternAdd (subpat
, os
->objects
[i
], v
, FcFalse
);
669 c
->format
= format_save
;
670 ret
= interpret_subexpr (c
, subpat
, buf
);
678 if (c
->format
== format_save
)
682 FcPatternDestroy (subpat
);
685 FcStrListDone (lang_strs
);
686 FcObjectSetDestroy (os
);
692 interpret_simple (FcFormatContext
*c
,
697 FcBool add_colon
= FcFalse
;
698 FcBool add_elt_name
= FcFalse
;
700 FcChar8
*else_string
;
702 if (consume_char (c
, ':'))
709 if (consume_char (c
, '['))
711 idx
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
714 message ("expected non-negative number at %d",
715 c
->format
-1 - c
->format_orig
+ 1);
718 if (!expect_char (c
, ']'))
722 if (consume_char (c
, '='))
723 add_elt_name
= FcTrue
;
727 if (consume_char (c
, ':'))
730 /* divert the c->word for now */
732 c
->word
= c
->word
+ strlen ((const char *) c
->word
) + 1;
733 /* for now we just support 'default value' */
734 if (!expect_char (c
, '-') ||
735 !read_chars (c
, '|'))
740 else_string
= c
->word
;
744 e
= FcPatternObjectFindElt (pat
,
745 FcObjectFromName ((const char *) c
->word
));
746 if (e
|| else_string
)
748 FcValueListPtr l
= NULL
;
751 FcStrBufChar (buf
, ':');
754 FcStrBufString (buf
, c
->word
);
755 FcStrBufChar (buf
, '=');
759 l
= FcPatternEltValues(e
);
765 l
= FcValueListNext(l
);
770 if (!FcNameUnparseValue (buf
, &l
->value
, '\0'))
777 FcNameUnparseValueList (buf
, l
, '\0');
783 FcStrBufString (buf
, else_string
);
791 cescape (FcFormatContext
*c
,
795 /* XXX escape \n etc? */
803 FcStrBufChar (buf
, '\\');
806 FcStrBufChar (buf
, *str
++);
812 shescape (FcFormatContext
*c
,
816 FcStrBufChar (buf
, '\'');
820 FcStrBufString (buf
, (const FcChar8
*) "'\\''");
822 FcStrBufChar (buf
, *str
);
825 FcStrBufChar (buf
, '\'');
830 xmlescape (FcFormatContext
*c
,
834 /* XXX escape \n etc? */
840 case '&': FcStrBufString (buf
, (const FcChar8
*) "&"); break;
841 case '<': FcStrBufString (buf
, (const FcChar8
*) "<"); break;
842 case '>': FcStrBufString (buf
, (const FcChar8
*) ">"); break;
843 default: FcStrBufChar (buf
, *str
); break;
851 delete_chars (FcFormatContext
*c
,
855 /* XXX not UTF-8 aware */
857 if (!expect_char (c
, '(') ||
858 !read_chars (c
, ')') ||
859 !expect_char (c
, ')'))
866 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
869 FcStrBufData (buf
, str
, p
- str
);
874 FcStrBufString (buf
, str
);
884 escape_chars (FcFormatContext
*c
,
888 /* XXX not UTF-8 aware */
890 if (!expect_char (c
, '(') ||
891 !read_chars (c
, ')') ||
892 !expect_char (c
, ')'))
899 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) c
->word
);
902 FcStrBufData (buf
, str
, p
- str
);
903 FcStrBufChar (buf
, c
->word
[0]);
904 FcStrBufChar (buf
, *p
);
909 FcStrBufString (buf
, str
);
919 translate_chars (FcFormatContext
*c
,
923 char *from
, *to
, repeat
;
924 int from_len
, to_len
;
926 /* XXX not UTF-8 aware */
928 if (!expect_char (c
, '(') ||
929 !read_chars (c
, ',') ||
930 !expect_char (c
, ','))
933 from
= (char *) c
->word
;
934 from_len
= strlen (from
);
935 to
= from
+ from_len
+ 1;
937 /* hack: we temporarily divert c->word */
938 c
->word
= (FcChar8
*) to
;
939 if (!read_chars (c
, ')'))
941 c
->word
= (FcChar8
*) from
;
944 c
->word
= (FcChar8
*) from
;
946 to_len
= strlen (to
);
947 repeat
= to
[to_len
- 1];
949 if (!expect_char (c
, ')'))
956 p
= (FcChar8
*) strpbrk ((const char *) str
, (const char *) from
);
960 FcStrBufData (buf
, str
, p
- str
);
961 i
= strchr (from
, *p
) - from
;
962 FcStrBufChar (buf
, i
< to_len
? to
[i
] : repeat
);
967 FcStrBufString (buf
, str
);
977 interpret_convert (FcFormatContext
*c
,
984 FcChar8 buf_static
[8192];
987 if (!expect_char (c
, '|') ||
991 /* prepare the buffer */
992 FcStrBufChar (buf
, '\0');
995 str
= buf
->buf
+ start
;
998 /* try simple converters first */
1000 #define CONVERTER(name, func) \
1001 else if (0 == strcmp ((const char *) c->word, name))\
1002 do { new_str = func (str); ret = FcTrue; } while (0)
1003 CONVERTER ("downcase", FcStrDowncase
);
1004 CONVERTER ("basename", FcStrBasename
);
1005 CONVERTER ("dirname", FcStrDirname
);
1014 FcStrBufString (buf
, new_str
);
1022 FcStrBufInit (&new_buf
, buf_static
, sizeof (buf_static
));
1024 /* now try our custom converters */
1026 #define CONVERTER(name, func) \
1027 else if (0 == strcmp ((const char *) c->word, name))\
1028 ret = func (c, str, &new_buf)
1029 CONVERTER ("cescape", cescape
);
1030 CONVERTER ("shescape", shescape
);
1031 CONVERTER ("xmlescape", xmlescape
);
1032 CONVERTER ("delete", delete_chars
);
1033 CONVERTER ("escape", escape_chars
);
1034 CONVERTER ("translate", translate_chars
);
1041 FcStrBufChar (&new_buf
, '\0');
1042 FcStrBufString (buf
, new_buf
.buf
);
1045 message ("unknown converter \"%s\"",
1048 FcStrBufDestroy (&new_buf
);
1054 maybe_interpret_converts (FcFormatContext
*c
,
1058 while (*c
->format
== '|')
1059 if (!interpret_convert (c
, buf
, start
))
1066 align_to_width (FcStrBuf
*buf
,
1075 len
= buf
->len
- start
;
1079 while (len
++ < -width
)
1080 FcStrBufChar (buf
, ' ');
1082 else if (len
< width
)
1087 while (len
++ < width
)
1088 FcStrBufChar (buf
, ' ');
1092 memmove (buf
->buf
+ buf
->len
- len
,
1093 buf
->buf
+ buf
->len
- width
,
1095 memset (buf
->buf
+ buf
->len
- width
,
1100 return !buf
->failed
;
1103 interpret_percent (FcFormatContext
*c
,
1110 if (!expect_char (c
, '%'))
1113 if (consume_char (c
, '%')) /* "%%" */
1115 FcStrBufChar (buf
, '%');
1119 /* parse an optional width specifier */
1120 width
= strtol ((const char *) c
->format
, (char **) &c
->format
, 10);
1122 if (!expect_char (c
, '{'))
1127 switch (*c
->format
) {
1128 case '=': ret
= interpret_builtin (c
, pat
, buf
); break;
1129 case '{': ret
= interpret_subexpr (c
, pat
, buf
); break;
1130 case '+': ret
= interpret_filter_in (c
, pat
, buf
); break;
1131 case '-': ret
= interpret_filter_out (c
, pat
, buf
); break;
1132 case '?': ret
= interpret_cond (c
, pat
, buf
); break;
1133 case '#': ret
= interpret_count (c
, pat
, buf
); break;
1134 case '[': ret
= interpret_enumerate (c
, pat
, buf
); break;
1135 default: ret
= interpret_simple (c
, pat
, buf
); break;
1139 maybe_interpret_converts (c
, buf
, start
) &&
1140 align_to_width (buf
, start
, width
) &&
1141 expect_char (c
, '}');
1145 interpret_expr (FcFormatContext
*c
,
1150 while (*c
->format
&& *c
->format
!= term
)
1155 c
->format
++; /* skip over '\\' */
1157 FcStrBufChar (buf
, escaped_char (*c
->format
++));
1160 if (!interpret_percent (c
, pat
, buf
))
1164 FcStrBufChar (buf
, *c
->format
++);
1170 FcPatternFormatToBuf (FcPattern
*pat
,
1171 const FcChar8
*format
,
1175 FcChar8 word_static
[1024];
1178 if (!FcFormatContextInit (&c
, format
, word_static
, sizeof (word_static
)))
1181 ret
= interpret_expr (&c
, pat
, buf
, '\0');
1183 FcFormatContextDone (&c
);
1189 FcPatternFormat (FcPattern
*pat
,
1190 const FcChar8
*format
)
1193 FcChar8 buf_static
[8192 - 1024];
1196 FcStrBufInit (&buf
, buf_static
, sizeof (buf_static
));
1198 ret
= FcPatternFormatToBuf (pat
, format
, &buf
);
1201 return FcStrBufDone (&buf
);
1204 FcStrBufDestroy (&buf
);
1209 #define __fcformat__
1210 #include "fcaliastail.h"