]> git.wh0rd.org - fontconfig.git/blob - src/fcformat.c
[fcformat] Add value-count syntax
[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 FcBool
128 read_word (FcFormatContext *c)
129 {
130 FcChar8 *p;
131
132 p = c->word;
133
134 while (*c->format)
135 {
136 if (*c->format == '\\')
137 {
138 c->format++;
139 if (*c->format)
140 c->format++;
141 continue;
142 }
143 else if (FcCharIsPunct (*c->format))
144 break;
145
146 *p++ = *c->format++;
147 }
148 *p = '\0';
149
150 if (p == c->word)
151 {
152 message ("expected element name at %d",
153 c->format - c->format_orig + 1);
154 return FcFalse;
155 }
156
157 return FcTrue;
158 }
159
160 static FcBool
161 interpret_expr (FcFormatContext *c,
162 FcPattern *pat,
163 FcStrBuf *buf,
164 FcChar8 term);
165
166 static FcBool
167 interpret_subexpr (FcFormatContext *c,
168 FcPattern *pat,
169 FcStrBuf *buf)
170 {
171 return expect_char (c, '{') &&
172 interpret_expr (c, pat, buf, '}') &&
173 expect_char (c, '}');
174 }
175
176 static FcBool
177 maybe_interpret_subexpr (FcFormatContext *c,
178 FcPattern *pat,
179 FcStrBuf *buf)
180 {
181 return (*c->format == '{') ?
182 interpret_subexpr (c, pat, buf) :
183 FcTrue;
184 }
185
186 static FcBool
187 skip_subexpr (FcFormatContext *c);
188
189 static FcBool
190 skip_percent (FcFormatContext *c)
191 {
192 int width;
193
194 if (!expect_char (c, '%'))
195 return FcFalse;
196
197 /* skip an optional width specifier */
198 width = strtol ((const char *) c->format, (char **) &c->format, 10);
199
200 if (!expect_char (c, '{'))
201 return FcFalse;
202
203 while(*c->format && *c->format != '}')
204 {
205 switch (*c->format)
206 {
207 case '\\':
208 c->format++; /* skip over '\\' */
209 if (*c->format)
210 c->format++;
211 continue;
212 case '{':
213 if (!skip_subexpr (c))
214 return FcFalse;
215 continue;
216 }
217 c->format++;
218 }
219
220 return expect_char (c, '}');
221 }
222
223 static FcBool
224 skip_expr (FcFormatContext *c)
225 {
226 while(*c->format && *c->format != '}')
227 {
228 switch (*c->format)
229 {
230 case '\\':
231 c->format++; /* skip over '\\' */
232 if (*c->format)
233 c->format++;
234 continue;
235 case '%':
236 if (!skip_percent (c))
237 return FcFalse;
238 continue;
239 }
240 c->format++;
241 }
242
243 return FcTrue;
244 }
245
246 static FcBool
247 skip_subexpr (FcFormatContext *c)
248 {
249 return expect_char (c, '{') &&
250 skip_expr (c) &&
251 expect_char (c, '}');
252 }
253
254 static FcBool
255 maybe_skip_subexpr (FcFormatContext *c)
256 {
257 return (*c->format == '{') ?
258 skip_subexpr (c) :
259 FcTrue;
260 }
261
262 static FcBool
263 interpret_filter (FcFormatContext *c,
264 FcPattern *pat,
265 FcStrBuf *buf)
266 {
267 FcObjectSet *os;
268 FcPattern *subpat;
269
270 if (!expect_char (c, '+'))
271 return FcFalse;
272
273 os = FcObjectSetCreate ();
274 if (!os)
275 return FcFalse;
276
277 do
278 {
279 if (!read_word (c) ||
280 !FcObjectSetAdd (os, (const char *) c->word))
281 {
282 FcObjectSetDestroy (os);
283 return FcFalse;
284 }
285 }
286 while (consume_char (c, ','));
287
288 subpat = FcPatternFilter (pat, os);
289 FcObjectSetDestroy (os);
290
291 if (!subpat ||
292 !interpret_subexpr (c, subpat, buf))
293 return FcFalse;
294
295 FcPatternDestroy (subpat);
296 return FcTrue;
297 }
298
299 static FcBool
300 interpret_delete (FcFormatContext *c,
301 FcPattern *pat,
302 FcStrBuf *buf)
303 {
304 FcPattern *subpat;
305
306 if (!expect_char (c, '-'))
307 return FcFalse;
308
309 subpat = FcPatternDuplicate (pat);
310 if (!subpat)
311 return FcFalse;
312
313 do
314 {
315 if (!read_word (c))
316 {
317 FcPatternDestroy (subpat);
318 return FcFalse;
319 }
320
321 FcPatternDel (subpat, (const char *) c->word);
322 }
323 while (consume_char (c, ','));
324
325 if (!interpret_subexpr (c, subpat, buf))
326 return FcFalse;
327
328 FcPatternDestroy (subpat);
329 return FcTrue;
330 }
331
332 static FcBool
333 interpret_cond (FcFormatContext *c,
334 FcPattern *pat,
335 FcStrBuf *buf)
336 {
337 FcBool pass;
338
339 if (!expect_char (c, '?'))
340 return FcFalse;
341
342 pass = FcTrue;
343
344 do
345 {
346 FcBool negate;
347 FcValue v;
348
349 negate = consume_char (c, '!');
350
351 if (!read_word (c))
352 return FcFalse;
353
354 pass = pass &&
355 (negate ^
356 (FcResultMatch == FcPatternGet (pat,
357 (const char *) c->word,
358 0, &v)));
359 }
360 while (consume_char (c, ','));
361
362 if (pass)
363 {
364 if (!interpret_subexpr (c, pat, buf) ||
365 !maybe_skip_subexpr (c))
366 return FcFalse;
367 }
368 else
369 {
370 if (!skip_subexpr (c) ||
371 !maybe_interpret_subexpr (c, pat, buf))
372 return FcFalse;
373 }
374
375 return FcTrue;
376 }
377
378 static FcBool
379 interpret_count (FcFormatContext *c,
380 FcPattern *pat,
381 FcStrBuf *buf)
382 {
383 int count;
384 FcPatternElt *e;
385 FcChar8 buf_static[64];
386
387 if (!expect_char (c, '#'))
388 return FcFalse;
389
390 if (!read_word (c))
391 return FcFalse;
392
393 count = 0;
394 e = FcPatternObjectFindElt (pat,
395 FcObjectFromName ((const char *) c->word));
396 if (e)
397 {
398 FcValueListPtr l;
399 count++;
400 for (l = FcPatternEltValues(e);
401 l->next;
402 l = l->next)
403 count++;
404 }
405
406 snprintf (buf_static, sizeof (buf_static), "%d", count);
407 FcStrBufString (buf, buf_static);
408
409 return FcTrue;
410 }
411
412 static FcBool
413 interpret_simple (FcFormatContext *c,
414 FcPattern *pat,
415 FcStrBuf *buf)
416 {
417 FcPatternElt *e;
418 FcBool add_colon = FcFalse;
419 FcBool add_elt_name = FcFalse;
420
421 if (consume_char (c, ':'))
422 add_colon = FcTrue;
423
424 if (!read_word (c))
425 return FcFalse;
426
427 if (consume_char (c, '='))
428 add_elt_name = FcTrue;
429
430 e = FcPatternObjectFindElt (pat,
431 FcObjectFromName ((const char *) c->word));
432 if (e)
433 {
434 FcValueListPtr l;
435
436 if (add_colon)
437 FcStrBufChar (buf, ':');
438 if (add_elt_name)
439 {
440 FcStrBufString (buf, c->word);
441 FcStrBufChar (buf, '=');
442 }
443
444 l = FcPatternEltValues(e);
445 FcNameUnparseValueList (buf, l, '\0');
446 }
447
448 return FcTrue;
449 }
450
451 static FcChar8 *
452 cescape (const FcChar8 *str)
453 {
454 FcStrBuf buf;
455 FcChar8 buf_static[8192];
456
457 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
458 while(*str)
459 {
460 switch (*str)
461 {
462 case '\\':
463 case '"':
464 FcStrBufChar (&buf, '\\');
465 break;
466 }
467 FcStrBufChar (&buf, *str++);
468 }
469 return FcStrBufDone (&buf);
470 }
471
472 static FcChar8 *
473 shescape (const FcChar8 *str)
474 {
475 FcStrBuf buf;
476 FcChar8 buf_static[8192];
477
478 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
479 FcStrBufChar (&buf, '\'');
480 while(*str)
481 {
482 if (*str == '\'')
483 FcStrBufString (&buf, (const FcChar8 *) "'\\''");
484 else
485 FcStrBufChar (&buf, *str);
486 str++;
487 }
488 FcStrBufChar (&buf, '\'');
489 return FcStrBufDone (&buf);
490 }
491
492 static FcChar8 *
493 xmlescape (const FcChar8 *str)
494 {
495 FcStrBuf buf;
496 FcChar8 buf_static[8192];
497
498 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
499 while(*str)
500 {
501 switch (*str)
502 {
503 case '&': FcStrBufString (&buf, (const FcChar8 *) "&amp;"); break;
504 case '<': FcStrBufString (&buf, (const FcChar8 *) "&lt;"); break;
505 case '>': FcStrBufString (&buf, (const FcChar8 *) "&gt;"); break;
506 default: FcStrBufChar (&buf, *str); break;
507 }
508 str++;
509 }
510 return FcStrBufDone (&buf);
511 }
512
513 static FcChar8 *
514 convert (FcFormatContext *c,
515 const FcChar8 *str)
516 {
517 if (!read_word (c))
518 return NULL;
519 #define CONVERTER(name, func) \
520 else if (0 == strcmp ((const char *) c->word, name))\
521 return func (str)
522 CONVERTER ("downcase", FcStrDowncase);
523 CONVERTER ("basename", FcStrBasename);
524 CONVERTER ("dirname", FcStrDirname);
525 CONVERTER ("cescape", cescape);
526 CONVERTER ("shescape", shescape);
527 CONVERTER ("xmlescape", xmlescape);
528
529 message ("unknown converter \"%s\"",
530 c->word);
531 return NULL;
532 }
533
534 static FcBool
535 maybe_interpret_converts (FcFormatContext *c,
536 FcStrBuf *buf,
537 int start)
538 {
539 while (consume_char (c, '|'))
540 {
541 const FcChar8 *str;
542 FcChar8 *new_str;
543
544 /* nul-terminate the buffer */
545 FcStrBufChar (buf, '\0');
546 if (buf->failed)
547 return FcFalse;
548 str = buf->buf + start;
549
550 if (!(new_str = convert (c, str)))
551 return FcFalse;
552
553 /* replace in the buffer */
554 buf->len = start;
555 FcStrBufString (buf, new_str);
556 free (new_str);
557 }
558
559 return FcTrue;
560 }
561
562 static FcBool
563 align_to_width (FcStrBuf *buf,
564 int start,
565 int width)
566 {
567 int len;
568
569 if (buf->failed)
570 return FcFalse;
571
572 len = buf->len - start;
573 if (len < -width)
574 {
575 /* left align */
576 while (len++ < -width)
577 FcStrBufChar (buf, ' ');
578 }
579 else if (len < width)
580 {
581 int old_len;
582 old_len = len;
583 /* right align */
584 while (len++ < width)
585 FcStrBufChar (buf, ' ');
586 if (buf->failed)
587 return FcFalse;
588 len = old_len;
589 memmove (buf->buf + buf->len - len,
590 buf->buf + buf->len - width,
591 len);
592 memset (buf->buf + buf->len - width,
593 ' ',
594 width - len);
595 }
596
597 return !buf->failed;
598 }
599 static FcBool
600 interpret_percent (FcFormatContext *c,
601 FcPattern *pat,
602 FcStrBuf *buf)
603 {
604 int width, start;
605 FcBool ret;
606
607 if (!expect_char (c, '%'))
608 return FcFalse;
609
610 if (consume_char (c, '%')) /* "%%" */
611 {
612 FcStrBufChar (buf, '%');
613 return FcTrue;
614 }
615
616 /* parse an optional width specifier */
617 width = strtol ((const char *) c->format, (char **) &c->format, 10);
618
619 if (!expect_char (c, '{'))
620 return FcFalse;
621
622 start = buf->len;
623
624 switch (*c->format) {
625 case '{': ret = interpret_subexpr (c, pat, buf); break;
626 case '+': ret = interpret_filter (c, pat, buf); break;
627 case '-': ret = interpret_delete (c, pat, buf); break;
628 case '?': ret = interpret_cond (c, pat, buf); break;
629 case '#': ret = interpret_count (c, pat, buf); break;
630 default: ret = interpret_simple (c, pat, buf); break;
631 }
632
633 return ret &&
634 maybe_interpret_converts (c, buf, start) &&
635 align_to_width (buf, start, width) &&
636 expect_char (c, '}');
637 }
638
639 static char escaped_char(const char ch)
640 {
641 switch (ch) {
642 case 'a': return '\a';
643 case 'b': return '\b';
644 case 'f': return '\f';
645 case 'n': return '\n';
646 case 'r': return '\r';
647 case 't': return '\t';
648 case 'v': return '\v';
649 default: return ch;
650 }
651 }
652
653 static FcBool
654 interpret_expr (FcFormatContext *c,
655 FcPattern *pat,
656 FcStrBuf *buf,
657 FcChar8 term)
658 {
659 while (*c->format && *c->format != term)
660 {
661 switch (*c->format)
662 {
663 case '\\':
664 c->format++; /* skip over '\\' */
665 if (*c->format)
666 FcStrBufChar (buf, escaped_char (*c->format++));
667 continue;
668 case '%':
669 if (!interpret_percent (c, pat, buf))
670 return FcFalse;
671 continue;
672 }
673 FcStrBufChar (buf, *c->format++);
674 }
675 return FcTrue;
676 }
677
678 FcChar8 *
679 FcPatternFormat (FcPattern *pat, const FcChar8 *format)
680 {
681 FcStrBuf buf;
682 FcChar8 buf_static[8192];
683 FcFormatContext c;
684 FcBool ret;
685
686 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
687 if (!FcFormatContextInit (&c, format))
688 return NULL;
689
690 ret = interpret_expr (&c, pat, &buf, '\0');
691
692 FcFormatContextDone (&c);
693 if (ret)
694 return FcStrBufDone (&buf);
695 else
696 {
697 FcStrBufDestroy (&buf);
698 return NULL;
699 }
700 }
701
702 #define __fcformat__
703 #include "fcaliastail.h"
704 #undef __fcformat__