]> git.wh0rd.org - fontconfig.git/blame - src/fcformat.c
[fcformat] Add value-count syntax
[fontconfig.git] / src / fcformat.c
CommitLineData
0c93b91d 1/*
b6a23028 2 * Copyright © 2008,2009 Red Hat, Inc.
0c93b91d
BE
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
2017a5eb
BE
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
0c93b91d
BE
39static void
40message (const char *fmt, ...)
41{
42 va_list args;
43 va_start (args, fmt);
8c31a243 44 fprintf (stderr, "Fontconfig: Pattern format error: ");
0c93b91d 45 vfprintf (stderr, fmt, args);
d6506ff6 46 fprintf (stderr, ".\n");
0c93b91d
BE
47 va_end (args);
48}
49
50
27b3e2dd 51typedef struct _FcFormatContext
0c93b91d 52{
27b3e2dd
BE
53 const FcChar8 *format_orig;
54 const FcChar8 *format;
55 int format_len;
2017a5eb 56 FcChar8 *word;
27b3e2dd 57} FcFormatContext;
c493c3b7 58
8c31a243 59static FcBool
27b3e2dd
BE
60FcFormatContextInit (FcFormatContext *c,
61 const FcChar8 *format)
62{
63 c->format_orig = c->format = format;
64 c->format_len = strlen ((const char *) format);
2017a5eb 65 c->word = malloc (c->format_len + 1);
8c31a243 66
2017a5eb 67 return c->word != NULL;
27b3e2dd 68}
c493c3b7 69
27b3e2dd
BE
70static void
71FcFormatContextDone (FcFormatContext *c)
72{
73 if (c)
74 {
2017a5eb 75 free (c->word);
27b3e2dd
BE
76 }
77}
c493c3b7 78
27b3e2dd
BE
79static FcBool
80consume_char (FcFormatContext *c,
81 FcChar8 term)
82{
83 if (*c->format != term)
d6506ff6
BE
84 return FcFalse;
85
86 c->format++;
87 return FcTrue;
88}
89
90static FcBool
91expect_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
107static FcBool
108FcCharIsPunct (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
127static FcBool
2017a5eb 128read_word (FcFormatContext *c)
d6506ff6
BE
129{
130 FcChar8 *p;
131
2017a5eb 132 p = c->word;
d6506ff6
BE
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
2017a5eb 150 if (p == c->word)
0c93b91d 151 {
d6506ff6
BE
152 message ("expected element name at %d",
153 c->format - c->format_orig + 1);
27b3e2dd
BE
154 return FcFalse;
155 }
0c93b91d 156
27b3e2dd
BE
157 return FcTrue;
158}
0c93b91d 159
8c31a243 160static FcBool
7717b25f
BE
161interpret_expr (FcFormatContext *c,
162 FcPattern *pat,
163 FcStrBuf *buf,
164 FcChar8 term);
d6506ff6 165
8c31a243
BE
166static FcBool
167interpret_subexpr (FcFormatContext *c,
27b3e2dd
BE
168 FcPattern *pat,
169 FcStrBuf *buf)
170{
8c31a243 171 return expect_char (c, '{') &&
7717b25f 172 interpret_expr (c, pat, buf, '}') &&
8c31a243
BE
173 expect_char (c, '}');
174}
0c93b91d 175
7717b25f
BE
176static FcBool
177maybe_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
186static FcBool
187skip_subexpr (FcFormatContext *c);
188
189static FcBool
190skip_percent (FcFormatContext *c)
191{
2017a5eb
BE
192 int width;
193
7717b25f
BE
194 if (!expect_char (c, '%'))
195 return FcFalse;
196
197 /* skip an optional width specifier */
2017a5eb 198 width = strtol ((const char *) c->format, (char **) &c->format, 10);
7717b25f
BE
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
223static FcBool
224skip_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
246static FcBool
247skip_subexpr (FcFormatContext *c)
248{
249 return expect_char (c, '{') &&
250 skip_expr (c) &&
251 expect_char (c, '}');
252}
253
254static FcBool
255maybe_skip_subexpr (FcFormatContext *c)
256{
257 return (*c->format == '{') ?
258 skip_subexpr (c) :
259 FcTrue;
260}
261
8c31a243
BE
262static FcBool
263interpret_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 {
2017a5eb
BE
279 if (!read_word (c) ||
280 !FcObjectSetAdd (os, (const char *) c->word))
8c31a243
BE
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
299static FcBool
300interpret_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 {
2017a5eb 315 if (!read_word (c))
8c31a243
BE
316 {
317 FcPatternDestroy (subpat);
318 return FcFalse;
319 }
320
2017a5eb 321 FcPatternDel (subpat, (const char *) c->word);
8c31a243
BE
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
7717b25f 332static FcBool
2017a5eb
BE
333interpret_cond (FcFormatContext *c,
334 FcPattern *pat,
335 FcStrBuf *buf)
7717b25f
BE
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
2017a5eb 351 if (!read_word (c))
7717b25f
BE
352 return FcFalse;
353
354 pass = pass &&
355 (negate ^
2017a5eb
BE
356 (FcResultMatch == FcPatternGet (pat,
357 (const char *) c->word,
358 0, &v)));
7717b25f
BE
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
b6a23028
BE
378static FcBool
379interpret_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
412static FcBool
413interpret_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
ced38254
BE
451static FcChar8 *
452cescape (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
472static FcChar8 *
473shescape (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
492static FcChar8 *
493xmlescape (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
2017a5eb
BE
513static FcChar8 *
514convert (FcFormatContext *c,
515 const FcChar8 *str)
516{
517 if (!read_word (c))
518 return NULL;
ced38254
BE
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);
2017a5eb
BE
528
529 message ("unknown converter \"%s\"",
530 c->word);
531 return NULL;
532}
533
534static FcBool
535maybe_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
562static FcBool
563align_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}
8c31a243
BE
599static FcBool
600interpret_percent (FcFormatContext *c,
601 FcPattern *pat,
602 FcStrBuf *buf)
603{
2017a5eb
BE
604 int width, start;
605 FcBool ret;
8c31a243
BE
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
8c31a243
BE
619 if (!expect_char (c, '{'))
620 return FcFalse;
621
2017a5eb 622 start = buf->len;
7717b25f 623
2017a5eb
BE
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;
b6a23028 629 case '#': ret = interpret_count (c, pat, buf); break;
2017a5eb 630 default: ret = interpret_simple (c, pat, buf); break;
0c93b91d 631 }
c493c3b7 632
2017a5eb
BE
633 return ret &&
634 maybe_interpret_converts (c, buf, start) &&
635 align_to_width (buf, start, width) &&
636 expect_char (c, '}');
0c93b91d
BE
637}
638
639static 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
8c31a243 653static FcBool
7717b25f
BE
654interpret_expr (FcFormatContext *c,
655 FcPattern *pat,
656 FcStrBuf *buf,
657 FcChar8 term)
0c93b91d 658{
7717b25f 659 while (*c->format && *c->format != term)
0c93b91d 660 {
27b3e2dd 661 switch (*c->format)
0c93b91d
BE
662 {
663 case '\\':
27b3e2dd
BE
664 c->format++; /* skip over '\\' */
665 if (*c->format)
666 FcStrBufChar (buf, escaped_char (*c->format++));
0c93b91d
BE
667 continue;
668 case '%':
8c31a243
BE
669 if (!interpret_percent (c, pat, buf))
670 return FcFalse;
0c93b91d
BE
671 continue;
672 }
27b3e2dd 673 FcStrBufChar (buf, *c->format++);
0c93b91d 674 }
8c31a243 675 return FcTrue;
0c93b91d
BE
676}
677
678FcChar8 *
679FcPatternFormat (FcPattern *pat, const FcChar8 *format)
680{
ced38254
BE
681 FcStrBuf buf;
682 FcChar8 buf_static[8192];
27b3e2dd 683 FcFormatContext c;
ced38254 684 FcBool ret;
0c93b91d 685
ced38254 686 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
8c31a243
BE
687 if (!FcFormatContextInit (&c, format))
688 return NULL;
0c93b91d 689
7717b25f 690 ret = interpret_expr (&c, pat, &buf, '\0');
0c93b91d 691
27b3e2dd 692 FcFormatContextDone (&c);
8c31a243
BE
693 if (ret)
694 return FcStrBufDone (&buf);
695 else
696 {
697 FcStrBufDestroy (&buf);
698 return NULL;
699 }
0c93b91d
BE
700}
701
702#define __fcformat__
703#include "fcaliastail.h"
704#undef __fcformat__