]> git.wh0rd.org - fontconfig.git/blob - src/fcformat.c
[fcformat] Start adding builtins
[fontconfig.git] / src / fcformat.c
1 /*
2 * Copyright © 2008,2009 Red Hat, Inc.
3 *
4 * Red Hat Author(s): Behdad Esfahbod
5 *
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.
15 *
16 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
17 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
18 * EVENT SHALL KEITH PACKARD 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.
23 */
24
25 #include "fcint.h"
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdarg.h>
29
30
31 /*
32 * Some ideas for future syntax extensions:
33 *
34 * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
35 * - allow indexing simple tags using '%{elt[idx]}'
36 * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
37 */
38
39 static void
40 message (const char *fmt, ...)
41 {
42 va_list args;
43 va_start (args, fmt);
44 fprintf (stderr, "Fontconfig: Pattern format error: ");
45 vfprintf (stderr, fmt, args);
46 fprintf (stderr, ".\n");
47 va_end (args);
48 }
49
50
51 typedef struct _FcFormatContext
52 {
53 const FcChar8 *format_orig;
54 const FcChar8 *format;
55 int format_len;
56 FcChar8 *word;
57 } FcFormatContext;
58
59 static FcBool
60 FcFormatContextInit (FcFormatContext *c,
61 const FcChar8 *format)
62 {
63 c->format_orig = c->format = format;
64 c->format_len = strlen ((const char *) format);
65 c->word = malloc (c->format_len + 1);
66
67 return c->word != NULL;
68 }
69
70 static void
71 FcFormatContextDone (FcFormatContext *c)
72 {
73 if (c)
74 {
75 free (c->word);
76 }
77 }
78
79 static FcBool
80 consume_char (FcFormatContext *c,
81 FcChar8 term)
82 {
83 if (*c->format != term)
84 return FcFalse;
85
86 c->format++;
87 return FcTrue;
88 }
89
90 static FcBool
91 expect_char (FcFormatContext *c,
92 FcChar8 term)
93 {
94 FcBool res = consume_char (c, term);
95 if (!res)
96 {
97 if (c->format == c->format_orig + c->format_len)
98 message ("format ended while expecting '%c'",
99 term);
100 else
101 message ("expected '%c' at %d",
102 term, c->format - c->format_orig + 1);
103 }
104 return res;
105 }
106
107 static FcBool
108 FcCharIsPunct (const FcChar8 c)
109 {
110 if (c < '0')
111 return FcTrue;
112 if (c <= '9')
113 return FcFalse;
114 if (c < 'A')
115 return FcTrue;
116 if (c <= 'Z')
117 return FcFalse;
118 if (c < 'a')
119 return FcTrue;
120 if (c <= 'z')
121 return FcFalse;
122 if (c <= '~')
123 return FcTrue;
124 return FcFalse;
125 }
126
127 static char escaped_char(const char ch)
128 {
129 switch (ch) {
130 case 'a': return '\a';
131 case 'b': return '\b';
132 case 'f': return '\f';
133 case 'n': return '\n';
134 case 'r': return '\r';
135 case 't': return '\t';
136 case 'v': return '\v';
137 default: return ch;
138 }
139 }
140
141 static FcBool
142 read_word (FcFormatContext *c)
143 {
144 FcChar8 *p;
145
146 p = c->word;
147
148 while (*c->format)
149 {
150 if (*c->format == '\\')
151 {
152 c->format++;
153 if (*c->format)
154 *p++ = escaped_char (*c->format++);
155 continue;
156 }
157 else if (FcCharIsPunct (*c->format))
158 break;
159
160 *p++ = *c->format++;
161 }
162 *p = '\0';
163
164 if (p == c->word)
165 {
166 message ("expected element name at %d",
167 c->format - c->format_orig + 1);
168 return FcFalse;
169 }
170
171 return FcTrue;
172 }
173
174 static FcBool
175 read_chars (FcFormatContext *c,
176 FcChar8 term)
177 {
178 FcChar8 *p;
179
180 p = c->word;
181
182 while (*c->format && *c->format != '}' && *c->format != term)
183 {
184 if (*c->format == '\\')
185 {
186 c->format++;
187 if (*c->format)
188 *p++ = escaped_char (*c->format++);
189 continue;
190 }
191
192 *p++ = *c->format++;
193 }
194 *p = '\0';
195
196 if (p == c->word)
197 {
198 message ("expected character data at %d",
199 c->format - c->format_orig + 1);
200 return FcFalse;
201 }
202
203 return FcTrue;
204 }
205
206 static FcBool
207 interpret_builtin (FcFormatContext *c,
208 FcPattern *pat,
209 FcStrBuf *buf)
210 {
211 if (!expect_char (c, '='))
212 return FcFalse;
213
214 if (!read_word (c))
215 return FcFalse;
216 #define BUILTIN(name, func) \
217 else if (0 == strcmp ((const char *) c->word, name))\
218 return func (c, pat, buf)
219 BUILTIN ("unparse", FcNameUnparse);
220 BUILTIN ("verbose", FcPatternPrint);
221 BUILTIN2 ("fcmatch", FcStrDirname);
222 BUILTIN2 ("fclist", FcStrDirname);
223 BUILTIN2 ("pkgkit", FcStrDirname);
224
225 message ("unknown builtin \"%s\"",
226 c->word);
227 return FcFalse;
228 }
229
230 static FcBool
231 interpret_expr (FcFormatContext *c,
232 FcPattern *pat,
233 FcStrBuf *buf,
234 FcChar8 term);
235
236 static FcBool
237 interpret_subexpr (FcFormatContext *c,
238 FcPattern *pat,
239 FcStrBuf *buf)
240 {
241 return expect_char (c, '{') &&
242 interpret_expr (c, pat, buf, '}') &&
243 expect_char (c, '}');
244 }
245
246 static FcBool
247 maybe_interpret_subexpr (FcFormatContext *c,
248 FcPattern *pat,
249 FcStrBuf *buf)
250 {
251 return (*c->format == '{') ?
252 interpret_subexpr (c, pat, buf) :
253 FcTrue;
254 }
255
256 static FcBool
257 skip_subexpr (FcFormatContext *c);
258
259 static FcBool
260 skip_percent (FcFormatContext *c)
261 {
262 int width;
263
264 if (!expect_char (c, '%'))
265 return FcFalse;
266
267 /* skip an optional width specifier */
268 width = strtol ((const char *) c->format, (char **) &c->format, 10);
269
270 if (!expect_char (c, '{'))
271 return FcFalse;
272
273 while(*c->format && *c->format != '}')
274 {
275 switch (*c->format)
276 {
277 case '\\':
278 c->format++; /* skip over '\\' */
279 if (*c->format)
280 c->format++;
281 continue;
282 case '{':
283 if (!skip_subexpr (c))
284 return FcFalse;
285 continue;
286 }
287 c->format++;
288 }
289
290 return expect_char (c, '}');
291 }
292
293 static FcBool
294 skip_expr (FcFormatContext *c)
295 {
296 while(*c->format && *c->format != '}')
297 {
298 switch (*c->format)
299 {
300 case '\\':
301 c->format++; /* skip over '\\' */
302 if (*c->format)
303 c->format++;
304 continue;
305 case '%':
306 if (!skip_percent (c))
307 return FcFalse;
308 continue;
309 }
310 c->format++;
311 }
312
313 return FcTrue;
314 }
315
316 static FcBool
317 skip_subexpr (FcFormatContext *c)
318 {
319 return expect_char (c, '{') &&
320 skip_expr (c) &&
321 expect_char (c, '}');
322 }
323
324 static FcBool
325 maybe_skip_subexpr (FcFormatContext *c)
326 {
327 return (*c->format == '{') ?
328 skip_subexpr (c) :
329 FcTrue;
330 }
331
332 static FcBool
333 interpret_filter (FcFormatContext *c,
334 FcPattern *pat,
335 FcStrBuf *buf)
336 {
337 FcObjectSet *os;
338 FcPattern *subpat;
339
340 if (!expect_char (c, '+'))
341 return FcFalse;
342
343 os = FcObjectSetCreate ();
344 if (!os)
345 return FcFalse;
346
347 do
348 {
349 if (!read_word (c) ||
350 !FcObjectSetAdd (os, (const char *) c->word))
351 {
352 FcObjectSetDestroy (os);
353 return FcFalse;
354 }
355 }
356 while (consume_char (c, ','));
357
358 subpat = FcPatternFilter (pat, os);
359 FcObjectSetDestroy (os);
360
361 if (!subpat ||
362 !interpret_subexpr (c, subpat, buf))
363 return FcFalse;
364
365 FcPatternDestroy (subpat);
366 return FcTrue;
367 }
368
369 static FcBool
370 interpret_delete (FcFormatContext *c,
371 FcPattern *pat,
372 FcStrBuf *buf)
373 {
374 FcPattern *subpat;
375
376 if (!expect_char (c, '-'))
377 return FcFalse;
378
379 subpat = FcPatternDuplicate (pat);
380 if (!subpat)
381 return FcFalse;
382
383 do
384 {
385 if (!read_word (c))
386 {
387 FcPatternDestroy (subpat);
388 return FcFalse;
389 }
390
391 FcPatternDel (subpat, (const char *) c->word);
392 }
393 while (consume_char (c, ','));
394
395 if (!interpret_subexpr (c, subpat, buf))
396 return FcFalse;
397
398 FcPatternDestroy (subpat);
399 return FcTrue;
400 }
401
402 static FcBool
403 interpret_cond (FcFormatContext *c,
404 FcPattern *pat,
405 FcStrBuf *buf)
406 {
407 FcBool pass;
408
409 if (!expect_char (c, '?'))
410 return FcFalse;
411
412 pass = FcTrue;
413
414 do
415 {
416 FcBool negate;
417 FcValue v;
418
419 negate = consume_char (c, '!');
420
421 if (!read_word (c))
422 return FcFalse;
423
424 pass = pass &&
425 (negate ^
426 (FcResultMatch == FcPatternGet (pat,
427 (const char *) c->word,
428 0, &v)));
429 }
430 while (consume_char (c, ','));
431
432 if (pass)
433 {
434 if (!interpret_subexpr (c, pat, buf) ||
435 !maybe_skip_subexpr (c))
436 return FcFalse;
437 }
438 else
439 {
440 if (!skip_subexpr (c) ||
441 !maybe_interpret_subexpr (c, pat, buf))
442 return FcFalse;
443 }
444
445 return FcTrue;
446 }
447
448 static FcBool
449 interpret_count (FcFormatContext *c,
450 FcPattern *pat,
451 FcStrBuf *buf)
452 {
453 int count;
454 FcPatternElt *e;
455 FcChar8 buf_static[64];
456
457 if (!expect_char (c, '#'))
458 return FcFalse;
459
460 if (!read_word (c))
461 return FcFalse;
462
463 count = 0;
464 e = FcPatternObjectFindElt (pat,
465 FcObjectFromName ((const char *) c->word));
466 if (e)
467 {
468 FcValueListPtr l;
469 count++;
470 for (l = FcPatternEltValues(e);
471 l->next;
472 l = l->next)
473 count++;
474 }
475
476 snprintf ((char *) buf_static, sizeof (buf_static), "%d", count);
477 FcStrBufString (buf, buf_static);
478
479 return FcTrue;
480 }
481
482 static FcBool
483 interpret_simple (FcFormatContext *c,
484 FcPattern *pat,
485 FcStrBuf *buf)
486 {
487 FcPatternElt *e;
488 FcBool add_colon = FcFalse;
489 FcBool add_elt_name = FcFalse;
490
491 if (consume_char (c, ':'))
492 add_colon = FcTrue;
493
494 if (!read_word (c))
495 return FcFalse;
496
497 if (consume_char (c, '='))
498 add_elt_name = FcTrue;
499
500 e = FcPatternObjectFindElt (pat,
501 FcObjectFromName ((const char *) c->word));
502 if (e)
503 {
504 FcValueListPtr l;
505
506 if (add_colon)
507 FcStrBufChar (buf, ':');
508 if (add_elt_name)
509 {
510 FcStrBufString (buf, c->word);
511 FcStrBufChar (buf, '=');
512 }
513
514 l = FcPatternEltValues(e);
515 FcNameUnparseValueList (buf, l, '\0');
516 }
517
518 return FcTrue;
519 }
520
521 static FcChar8 *
522 cescape (const FcChar8 *str)
523 {
524 FcStrBuf buf;
525 FcChar8 buf_static[8192];
526
527 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
528 while(*str)
529 {
530 switch (*str)
531 {
532 case '\\':
533 case '"':
534 FcStrBufChar (&buf, '\\');
535 break;
536 }
537 FcStrBufChar (&buf, *str++);
538 }
539 return FcStrBufDone (&buf);
540 }
541
542 static FcChar8 *
543 shescape (const FcChar8 *str)
544 {
545 FcStrBuf buf;
546 FcChar8 buf_static[8192];
547
548 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
549 FcStrBufChar (&buf, '\'');
550 while(*str)
551 {
552 if (*str == '\'')
553 FcStrBufString (&buf, (const FcChar8 *) "'\\''");
554 else
555 FcStrBufChar (&buf, *str);
556 str++;
557 }
558 FcStrBufChar (&buf, '\'');
559 return FcStrBufDone (&buf);
560 }
561
562 static FcChar8 *
563 xmlescape (const FcChar8 *str)
564 {
565 FcStrBuf buf;
566 FcChar8 buf_static[8192];
567
568 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
569 while(*str)
570 {
571 switch (*str)
572 {
573 case '&': FcStrBufString (&buf, (const FcChar8 *) "&amp;"); break;
574 case '<': FcStrBufString (&buf, (const FcChar8 *) "&lt;"); break;
575 case '>': FcStrBufString (&buf, (const FcChar8 *) "&gt;"); break;
576 default: FcStrBufChar (&buf, *str); break;
577 }
578 str++;
579 }
580 return FcStrBufDone (&buf);
581 }
582
583 static FcChar8 *
584 delete_chars (FcFormatContext *c,
585 const FcChar8 *str)
586 {
587 FcStrBuf buf;
588 FcChar8 buf_static[8192];
589
590 /* XXX not UTF-8 aware */
591
592 if (!expect_char (c, '(') ||
593 !read_chars (c, ')') ||
594 !expect_char (c, ')'))
595 return NULL;
596
597 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
598 while(*str)
599 {
600 FcChar8 *p;
601
602 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
603 if (p)
604 {
605 FcStrBufData (&buf, str, p - str);
606 str = p + 1;
607 }
608 else
609 {
610 FcStrBufString (&buf, str);
611 break;
612 }
613
614 }
615 return FcStrBufDone (&buf);
616 }
617
618 static FcChar8 *
619 escape_chars (FcFormatContext *c,
620 const FcChar8 *str)
621 {
622 FcStrBuf buf;
623 FcChar8 buf_static[8192];
624
625 /* XXX not UTF-8 aware */
626
627 if (!expect_char (c, '(') ||
628 !read_chars (c, ')') ||
629 !expect_char (c, ')'))
630 return NULL;
631
632 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
633 while(*str)
634 {
635 FcChar8 *p;
636
637 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
638 if (p)
639 {
640 FcStrBufData (&buf, str, p - str);
641 FcStrBufChar (&buf, c->word[0]);
642 FcStrBufChar (&buf, *p);
643 str = p + 1;
644 }
645 else
646 {
647 FcStrBufString (&buf, str);
648 break;
649 }
650
651 }
652 return FcStrBufDone (&buf);
653 }
654
655 static FcChar8 *
656 translate_chars (FcFormatContext *c,
657 const FcChar8 *str)
658 {
659 FcStrBuf buf;
660 FcChar8 buf_static[8192];
661 char *from, *to, repeat;
662 int from_len, to_len;
663
664 /* XXX not UTF-8 aware */
665
666 if (!expect_char (c, '(') ||
667 !read_chars (c, ',') ||
668 !expect_char (c, ','))
669 return NULL;
670
671 from = (char *) c->word;
672 from_len = strlen (from);
673 to = from + from_len + 1;
674
675 /* hack: we temporarily diverge c->word */
676 c->word = (FcChar8 *) to;
677 if (!read_chars (c, ')'))
678 {
679 c->word = (FcChar8 *) from;
680 return FcFalse;
681 }
682 c->word = (FcChar8 *) from;
683
684 to_len = strlen (to);
685 repeat = to[to_len - 1];
686
687 if (!expect_char (c, ')'))
688 return FcFalse;
689
690 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
691 while(*str)
692 {
693 FcChar8 *p;
694
695 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) from);
696 if (p)
697 {
698 int i;
699 FcStrBufData (&buf, str, p - str);
700 i = strchr (from, *p) - from;
701 FcStrBufChar (&buf, i < to_len ? to[i] : repeat);
702 str = p + 1;
703 }
704 else
705 {
706 FcStrBufString (&buf, str);
707 break;
708 }
709
710 }
711 return FcStrBufDone (&buf);
712 }
713
714 static FcChar8 *
715 convert (FcFormatContext *c,
716 const FcChar8 *str)
717 {
718 if (!read_word (c))
719 return NULL;
720 #define CONVERTER(name, func) \
721 else if (0 == strcmp ((const char *) c->word, name))\
722 return func (str)
723 #define CONVERTER2(name, func) \
724 else if (0 == strcmp ((const char *) c->word, name))\
725 return func (c, str)
726 CONVERTER ("downcase", FcStrDowncase);
727 CONVERTER ("basename", FcStrBasename);
728 CONVERTER ("dirname", FcStrDirname);
729 CONVERTER ("cescape", cescape);
730 CONVERTER ("shescape", shescape);
731 CONVERTER ("xmlescape", xmlescape);
732 CONVERTER2 ("delete", delete_chars);
733 CONVERTER2 ("escape", escape_chars);
734 CONVERTER2 ("translate", translate_chars);
735
736 message ("unknown converter \"%s\"",
737 c->word);
738 return NULL;
739 }
740
741 static FcBool
742 maybe_interpret_converts (FcFormatContext *c,
743 FcStrBuf *buf,
744 int start)
745 {
746 while (consume_char (c, '|'))
747 {
748 const FcChar8 *str;
749 FcChar8 *new_str;
750
751 /* nul-terminate the buffer */
752 FcStrBufChar (buf, '\0');
753 if (buf->failed)
754 return FcFalse;
755 str = buf->buf + start;
756
757 if (!(new_str = convert (c, str)))
758 return FcFalse;
759
760 /* replace in the buffer */
761 buf->len = start;
762 FcStrBufString (buf, new_str);
763 free (new_str);
764 }
765
766 return FcTrue;
767 }
768
769 static FcBool
770 align_to_width (FcStrBuf *buf,
771 int start,
772 int width)
773 {
774 int len;
775
776 if (buf->failed)
777 return FcFalse;
778
779 len = buf->len - start;
780 if (len < -width)
781 {
782 /* left align */
783 while (len++ < -width)
784 FcStrBufChar (buf, ' ');
785 }
786 else if (len < width)
787 {
788 int old_len;
789 old_len = len;
790 /* right align */
791 while (len++ < width)
792 FcStrBufChar (buf, ' ');
793 if (buf->failed)
794 return FcFalse;
795 len = old_len;
796 memmove (buf->buf + buf->len - len,
797 buf->buf + buf->len - width,
798 len);
799 memset (buf->buf + buf->len - width,
800 ' ',
801 width - len);
802 }
803
804 return !buf->failed;
805 }
806 static FcBool
807 interpret_percent (FcFormatContext *c,
808 FcPattern *pat,
809 FcStrBuf *buf)
810 {
811 int width, start;
812 FcBool ret;
813
814 if (!expect_char (c, '%'))
815 return FcFalse;
816
817 if (consume_char (c, '%')) /* "%%" */
818 {
819 FcStrBufChar (buf, '%');
820 return FcTrue;
821 }
822
823 /* parse an optional width specifier */
824 width = strtol ((const char *) c->format, (char **) &c->format, 10);
825
826 if (!expect_char (c, '{'))
827 return FcFalse;
828
829 start = buf->len;
830
831 switch (*c->format) {
832 case '=': ret = interpret_builtin (c, pat, buf); break;
833 case '{': ret = interpret_subexpr (c, pat, buf); break;
834 case '+': ret = interpret_filter (c, pat, buf); break;
835 case '-': ret = interpret_delete (c, pat, buf); break;
836 case '?': ret = interpret_cond (c, pat, buf); break;
837 case '#': ret = interpret_count (c, pat, buf); break;
838 default: ret = interpret_simple (c, pat, buf); break;
839 }
840
841 return ret &&
842 maybe_interpret_converts (c, buf, start) &&
843 align_to_width (buf, start, width) &&
844 expect_char (c, '}');
845 }
846
847 static FcBool
848 interpret_expr (FcFormatContext *c,
849 FcPattern *pat,
850 FcStrBuf *buf,
851 FcChar8 term)
852 {
853 while (*c->format && *c->format != term)
854 {
855 switch (*c->format)
856 {
857 case '\\':
858 c->format++; /* skip over '\\' */
859 if (*c->format)
860 FcStrBufChar (buf, escaped_char (*c->format++));
861 continue;
862 case '%':
863 if (!interpret_percent (c, pat, buf))
864 return FcFalse;
865 continue;
866 }
867 FcStrBufChar (buf, *c->format++);
868 }
869 return FcTrue;
870 }
871
872 FcChar8 *
873 FcPatternFormat (FcPattern *pat, const FcChar8 *format)
874 {
875 FcStrBuf buf;
876 FcChar8 buf_static[8192];
877 FcFormatContext c;
878 FcBool ret;
879
880 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
881 if (!FcFormatContextInit (&c, format))
882 return NULL;
883
884 ret = interpret_expr (&c, pat, &buf, '\0');
885
886 FcFormatContextDone (&c);
887 if (ret)
888 return FcStrBufDone (&buf);
889 else
890 {
891 FcStrBufDestroy (&buf);
892 return NULL;
893 }
894 }
895
896 #define __fcformat__
897 #include "fcaliastail.h"
898 #undef __fcformat__