]> git.wh0rd.org Git - 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__