]> git.wh0rd.org - fontconfig.git/blob - src/fcformat.c
[fcformat] Implement 'delete', 'escape', and 'translate' filter functions
[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_expr (FcFormatContext *c,
208 FcPattern *pat,
209 FcStrBuf *buf,
210 FcChar8 term);
211
212 static FcBool
213 interpret_subexpr (FcFormatContext *c,
214 FcPattern *pat,
215 FcStrBuf *buf)
216 {
217 return expect_char (c, '{') &&
218 interpret_expr (c, pat, buf, '}') &&
219 expect_char (c, '}');
220 }
221
222 static FcBool
223 maybe_interpret_subexpr (FcFormatContext *c,
224 FcPattern *pat,
225 FcStrBuf *buf)
226 {
227 return (*c->format == '{') ?
228 interpret_subexpr (c, pat, buf) :
229 FcTrue;
230 }
231
232 static FcBool
233 skip_subexpr (FcFormatContext *c);
234
235 static FcBool
236 skip_percent (FcFormatContext *c)
237 {
238 int width;
239
240 if (!expect_char (c, '%'))
241 return FcFalse;
242
243 /* skip an optional width specifier */
244 width = strtol ((const char *) c->format, (char **) &c->format, 10);
245
246 if (!expect_char (c, '{'))
247 return FcFalse;
248
249 while(*c->format && *c->format != '}')
250 {
251 switch (*c->format)
252 {
253 case '\\':
254 c->format++; /* skip over '\\' */
255 if (*c->format)
256 c->format++;
257 continue;
258 case '{':
259 if (!skip_subexpr (c))
260 return FcFalse;
261 continue;
262 }
263 c->format++;
264 }
265
266 return expect_char (c, '}');
267 }
268
269 static FcBool
270 skip_expr (FcFormatContext *c)
271 {
272 while(*c->format && *c->format != '}')
273 {
274 switch (*c->format)
275 {
276 case '\\':
277 c->format++; /* skip over '\\' */
278 if (*c->format)
279 c->format++;
280 continue;
281 case '%':
282 if (!skip_percent (c))
283 return FcFalse;
284 continue;
285 }
286 c->format++;
287 }
288
289 return FcTrue;
290 }
291
292 static FcBool
293 skip_subexpr (FcFormatContext *c)
294 {
295 return expect_char (c, '{') &&
296 skip_expr (c) &&
297 expect_char (c, '}');
298 }
299
300 static FcBool
301 maybe_skip_subexpr (FcFormatContext *c)
302 {
303 return (*c->format == '{') ?
304 skip_subexpr (c) :
305 FcTrue;
306 }
307
308 static FcBool
309 interpret_filter (FcFormatContext *c,
310 FcPattern *pat,
311 FcStrBuf *buf)
312 {
313 FcObjectSet *os;
314 FcPattern *subpat;
315
316 if (!expect_char (c, '+'))
317 return FcFalse;
318
319 os = FcObjectSetCreate ();
320 if (!os)
321 return FcFalse;
322
323 do
324 {
325 if (!read_word (c) ||
326 !FcObjectSetAdd (os, (const char *) c->word))
327 {
328 FcObjectSetDestroy (os);
329 return FcFalse;
330 }
331 }
332 while (consume_char (c, ','));
333
334 subpat = FcPatternFilter (pat, os);
335 FcObjectSetDestroy (os);
336
337 if (!subpat ||
338 !interpret_subexpr (c, subpat, buf))
339 return FcFalse;
340
341 FcPatternDestroy (subpat);
342 return FcTrue;
343 }
344
345 static FcBool
346 interpret_delete (FcFormatContext *c,
347 FcPattern *pat,
348 FcStrBuf *buf)
349 {
350 FcPattern *subpat;
351
352 if (!expect_char (c, '-'))
353 return FcFalse;
354
355 subpat = FcPatternDuplicate (pat);
356 if (!subpat)
357 return FcFalse;
358
359 do
360 {
361 if (!read_word (c))
362 {
363 FcPatternDestroy (subpat);
364 return FcFalse;
365 }
366
367 FcPatternDel (subpat, (const char *) c->word);
368 }
369 while (consume_char (c, ','));
370
371 if (!interpret_subexpr (c, subpat, buf))
372 return FcFalse;
373
374 FcPatternDestroy (subpat);
375 return FcTrue;
376 }
377
378 static FcBool
379 interpret_cond (FcFormatContext *c,
380 FcPattern *pat,
381 FcStrBuf *buf)
382 {
383 FcBool pass;
384
385 if (!expect_char (c, '?'))
386 return FcFalse;
387
388 pass = FcTrue;
389
390 do
391 {
392 FcBool negate;
393 FcValue v;
394
395 negate = consume_char (c, '!');
396
397 if (!read_word (c))
398 return FcFalse;
399
400 pass = pass &&
401 (negate ^
402 (FcResultMatch == FcPatternGet (pat,
403 (const char *) c->word,
404 0, &v)));
405 }
406 while (consume_char (c, ','));
407
408 if (pass)
409 {
410 if (!interpret_subexpr (c, pat, buf) ||
411 !maybe_skip_subexpr (c))
412 return FcFalse;
413 }
414 else
415 {
416 if (!skip_subexpr (c) ||
417 !maybe_interpret_subexpr (c, pat, buf))
418 return FcFalse;
419 }
420
421 return FcTrue;
422 }
423
424 static FcBool
425 interpret_count (FcFormatContext *c,
426 FcPattern *pat,
427 FcStrBuf *buf)
428 {
429 int count;
430 FcPatternElt *e;
431 FcChar8 buf_static[64];
432
433 if (!expect_char (c, '#'))
434 return FcFalse;
435
436 if (!read_word (c))
437 return FcFalse;
438
439 count = 0;
440 e = FcPatternObjectFindElt (pat,
441 FcObjectFromName ((const char *) c->word));
442 if (e)
443 {
444 FcValueListPtr l;
445 count++;
446 for (l = FcPatternEltValues(e);
447 l->next;
448 l = l->next)
449 count++;
450 }
451
452 snprintf ((char *) buf_static, sizeof (buf_static), "%d", count);
453 FcStrBufString (buf, buf_static);
454
455 return FcTrue;
456 }
457
458 static FcBool
459 interpret_simple (FcFormatContext *c,
460 FcPattern *pat,
461 FcStrBuf *buf)
462 {
463 FcPatternElt *e;
464 FcBool add_colon = FcFalse;
465 FcBool add_elt_name = FcFalse;
466
467 if (consume_char (c, ':'))
468 add_colon = FcTrue;
469
470 if (!read_word (c))
471 return FcFalse;
472
473 if (consume_char (c, '='))
474 add_elt_name = FcTrue;
475
476 e = FcPatternObjectFindElt (pat,
477 FcObjectFromName ((const char *) c->word));
478 if (e)
479 {
480 FcValueListPtr l;
481
482 if (add_colon)
483 FcStrBufChar (buf, ':');
484 if (add_elt_name)
485 {
486 FcStrBufString (buf, c->word);
487 FcStrBufChar (buf, '=');
488 }
489
490 l = FcPatternEltValues(e);
491 FcNameUnparseValueList (buf, l, '\0');
492 }
493
494 return FcTrue;
495 }
496
497 static FcChar8 *
498 cescape (const FcChar8 *str)
499 {
500 FcStrBuf buf;
501 FcChar8 buf_static[8192];
502
503 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
504 while(*str)
505 {
506 switch (*str)
507 {
508 case '\\':
509 case '"':
510 FcStrBufChar (&buf, '\\');
511 break;
512 }
513 FcStrBufChar (&buf, *str++);
514 }
515 return FcStrBufDone (&buf);
516 }
517
518 static FcChar8 *
519 shescape (const FcChar8 *str)
520 {
521 FcStrBuf buf;
522 FcChar8 buf_static[8192];
523
524 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
525 FcStrBufChar (&buf, '\'');
526 while(*str)
527 {
528 if (*str == '\'')
529 FcStrBufString (&buf, (const FcChar8 *) "'\\''");
530 else
531 FcStrBufChar (&buf, *str);
532 str++;
533 }
534 FcStrBufChar (&buf, '\'');
535 return FcStrBufDone (&buf);
536 }
537
538 static FcChar8 *
539 xmlescape (const FcChar8 *str)
540 {
541 FcStrBuf buf;
542 FcChar8 buf_static[8192];
543
544 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
545 while(*str)
546 {
547 switch (*str)
548 {
549 case '&': FcStrBufString (&buf, (const FcChar8 *) "&amp;"); break;
550 case '<': FcStrBufString (&buf, (const FcChar8 *) "&lt;"); break;
551 case '>': FcStrBufString (&buf, (const FcChar8 *) "&gt;"); break;
552 default: FcStrBufChar (&buf, *str); break;
553 }
554 str++;
555 }
556 return FcStrBufDone (&buf);
557 }
558
559 static FcChar8 *
560 delete_chars (FcFormatContext *c,
561 const FcChar8 *str)
562 {
563 FcStrBuf buf;
564 FcChar8 buf_static[8192];
565
566 /* XXX not UTF-8 aware */
567
568 if (!expect_char (c, '(') ||
569 !read_chars (c, ')') ||
570 !expect_char (c, ')'))
571 return NULL;
572
573 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
574 while(*str)
575 {
576 FcChar8 *p;
577
578 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
579 if (p)
580 {
581 FcStrBufData (&buf, str, p - str);
582 str = p + 1;
583 }
584 else
585 {
586 FcStrBufString (&buf, str);
587 break;
588 }
589
590 }
591 return FcStrBufDone (&buf);
592 }
593
594 static FcChar8 *
595 escape_chars (FcFormatContext *c,
596 const FcChar8 *str)
597 {
598 FcStrBuf buf;
599 FcChar8 buf_static[8192];
600
601 /* XXX not UTF-8 aware */
602
603 if (!expect_char (c, '(') ||
604 !read_chars (c, ')') ||
605 !expect_char (c, ')'))
606 return NULL;
607
608 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
609 while(*str)
610 {
611 FcChar8 *p;
612
613 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) c->word);
614 if (p)
615 {
616 FcStrBufData (&buf, str, p - str);
617 FcStrBufChar (&buf, c->word[0]);
618 FcStrBufChar (&buf, *p);
619 str = p + 1;
620 }
621 else
622 {
623 FcStrBufString (&buf, str);
624 break;
625 }
626
627 }
628 return FcStrBufDone (&buf);
629 }
630
631 static FcChar8 *
632 translate_chars (FcFormatContext *c,
633 const FcChar8 *str)
634 {
635 FcStrBuf buf;
636 FcChar8 buf_static[8192];
637 char *from, *to, repeat;
638 int from_len, to_len;
639
640 /* XXX not UTF-8 aware */
641
642 if (!expect_char (c, '(') ||
643 !read_chars (c, ',') ||
644 !expect_char (c, ','))
645 return NULL;
646
647 from = (char *) c->word;
648 from_len = strlen (from);
649 to = from + from_len + 1;
650
651 /* hack: we temporarily diverge c->word */
652 c->word = (FcChar8 *) to;
653 if (!read_chars (c, ')'))
654 {
655 c->word = (FcChar8 *) from;
656 return FcFalse;
657 }
658 c->word = (FcChar8 *) from;
659
660 to_len = strlen (to);
661 repeat = to[to_len - 1];
662
663 if (!expect_char (c, ')'))
664 return FcFalse;
665
666 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
667 while(*str)
668 {
669 FcChar8 *p;
670
671 p = (FcChar8 *) strpbrk ((const char *) str, (const char *) from);
672 if (p)
673 {
674 int i;
675 FcStrBufData (&buf, str, p - str);
676 i = strchr (from, *p) - from;
677 FcStrBufChar (&buf, i < to_len ? to[i] : repeat);
678 str = p + 1;
679 }
680 else
681 {
682 FcStrBufString (&buf, str);
683 break;
684 }
685
686 }
687 return FcStrBufDone (&buf);
688 }
689
690 static FcChar8 *
691 convert (FcFormatContext *c,
692 const FcChar8 *str)
693 {
694 if (!read_word (c))
695 return NULL;
696 #define CONVERTER(name, func) \
697 else if (0 == strcmp ((const char *) c->word, name))\
698 return func (str)
699 #define CONVERTER2(name, func) \
700 else if (0 == strcmp ((const char *) c->word, name))\
701 return func (c, str)
702 CONVERTER ("downcase", FcStrDowncase);
703 CONVERTER ("basename", FcStrBasename);
704 CONVERTER ("dirname", FcStrDirname);
705 CONVERTER ("cescape", cescape);
706 CONVERTER ("shescape", shescape);
707 CONVERTER ("xmlescape", xmlescape);
708 CONVERTER2 ("delete", delete_chars);
709 CONVERTER2 ("escape", escape_chars);
710 CONVERTER2 ("translate", translate_chars);
711
712 message ("unknown converter \"%s\"",
713 c->word);
714 return NULL;
715 }
716
717 static FcBool
718 maybe_interpret_converts (FcFormatContext *c,
719 FcStrBuf *buf,
720 int start)
721 {
722 while (consume_char (c, '|'))
723 {
724 const FcChar8 *str;
725 FcChar8 *new_str;
726
727 /* nul-terminate the buffer */
728 FcStrBufChar (buf, '\0');
729 if (buf->failed)
730 return FcFalse;
731 str = buf->buf + start;
732
733 if (!(new_str = convert (c, str)))
734 return FcFalse;
735
736 /* replace in the buffer */
737 buf->len = start;
738 FcStrBufString (buf, new_str);
739 free (new_str);
740 }
741
742 return FcTrue;
743 }
744
745 static FcBool
746 align_to_width (FcStrBuf *buf,
747 int start,
748 int width)
749 {
750 int len;
751
752 if (buf->failed)
753 return FcFalse;
754
755 len = buf->len - start;
756 if (len < -width)
757 {
758 /* left align */
759 while (len++ < -width)
760 FcStrBufChar (buf, ' ');
761 }
762 else if (len < width)
763 {
764 int old_len;
765 old_len = len;
766 /* right align */
767 while (len++ < width)
768 FcStrBufChar (buf, ' ');
769 if (buf->failed)
770 return FcFalse;
771 len = old_len;
772 memmove (buf->buf + buf->len - len,
773 buf->buf + buf->len - width,
774 len);
775 memset (buf->buf + buf->len - width,
776 ' ',
777 width - len);
778 }
779
780 return !buf->failed;
781 }
782 static FcBool
783 interpret_percent (FcFormatContext *c,
784 FcPattern *pat,
785 FcStrBuf *buf)
786 {
787 int width, start;
788 FcBool ret;
789
790 if (!expect_char (c, '%'))
791 return FcFalse;
792
793 if (consume_char (c, '%')) /* "%%" */
794 {
795 FcStrBufChar (buf, '%');
796 return FcTrue;
797 }
798
799 /* parse an optional width specifier */
800 width = strtol ((const char *) c->format, (char **) &c->format, 10);
801
802 if (!expect_char (c, '{'))
803 return FcFalse;
804
805 start = buf->len;
806
807 switch (*c->format) {
808 case '{': ret = interpret_subexpr (c, pat, buf); break;
809 case '+': ret = interpret_filter (c, pat, buf); break;
810 case '-': ret = interpret_delete (c, pat, buf); break;
811 case '?': ret = interpret_cond (c, pat, buf); break;
812 case '#': ret = interpret_count (c, pat, buf); break;
813 default: ret = interpret_simple (c, pat, buf); break;
814 }
815
816 return ret &&
817 maybe_interpret_converts (c, buf, start) &&
818 align_to_width (buf, start, width) &&
819 expect_char (c, '}');
820 }
821
822 static FcBool
823 interpret_expr (FcFormatContext *c,
824 FcPattern *pat,
825 FcStrBuf *buf,
826 FcChar8 term)
827 {
828 while (*c->format && *c->format != term)
829 {
830 switch (*c->format)
831 {
832 case '\\':
833 c->format++; /* skip over '\\' */
834 if (*c->format)
835 FcStrBufChar (buf, escaped_char (*c->format++));
836 continue;
837 case '%':
838 if (!interpret_percent (c, pat, buf))
839 return FcFalse;
840 continue;
841 }
842 FcStrBufChar (buf, *c->format++);
843 }
844 return FcTrue;
845 }
846
847 FcChar8 *
848 FcPatternFormat (FcPattern *pat, const FcChar8 *format)
849 {
850 FcStrBuf buf;
851 FcChar8 buf_static[8192];
852 FcFormatContext c;
853 FcBool ret;
854
855 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
856 if (!FcFormatContextInit (&c, format))
857 return NULL;
858
859 ret = interpret_expr (&c, pat, &buf, '\0');
860
861 FcFormatContextDone (&c);
862 if (ret)
863 return FcStrBufDone (&buf);
864 else
865 {
866 FcStrBufDestroy (&buf);
867 return NULL;
868 }
869 }
870
871 #define __fcformat__
872 #include "fcaliastail.h"
873 #undef __fcformat__