]>
Commit | Line | Data |
---|---|---|
0c93b91d BE |
1 | /* |
2 | * Copyright © 2008 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 | ||
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 |
39 | static void |
40 | message (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 | 51 | typedef 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 | 59 | static FcBool |
27b3e2dd BE |
60 | FcFormatContextInit (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 |
70 | static void |
71 | FcFormatContextDone (FcFormatContext *c) | |
72 | { | |
73 | if (c) | |
74 | { | |
2017a5eb | 75 | free (c->word); |
27b3e2dd BE |
76 | } |
77 | } | |
c493c3b7 | 78 | |
27b3e2dd BE |
79 | static FcBool |
80 | consume_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 | ||
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 | |
2017a5eb | 128 | read_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 | 160 | static FcBool |
7717b25f BE |
161 | interpret_expr (FcFormatContext *c, |
162 | FcPattern *pat, | |
163 | FcStrBuf *buf, | |
164 | FcChar8 term); | |
d6506ff6 | 165 | |
8c31a243 BE |
166 | static FcBool |
167 | interpret_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 |
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 | { | |
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 | ||
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 | ||
8c31a243 | 262 | static FcBool |
2017a5eb BE |
263 | interpret_simple (FcFormatContext *c, |
264 | FcPattern *pat, | |
265 | FcStrBuf *buf) | |
8c31a243 BE |
266 | { |
267 | FcPatternElt *e; | |
d6506ff6 BE |
268 | FcBool add_colon = FcFalse; |
269 | FcBool add_elt_name = FcFalse; | |
270 | ||
8c31a243 | 271 | if (consume_char (c, ':')) |
d6506ff6 | 272 | add_colon = FcTrue; |
d6506ff6 | 273 | |
2017a5eb | 274 | if (!read_word (c)) |
8c31a243 | 275 | return FcFalse; |
d6506ff6 BE |
276 | |
277 | if (consume_char (c, '=')) | |
278 | add_elt_name = FcTrue; | |
279 | ||
280 | e = FcPatternObjectFindElt (pat, | |
2017a5eb | 281 | FcObjectFromName ((const char *) c->word)); |
27b3e2dd BE |
282 | if (e) |
283 | { | |
284 | FcValueListPtr l; | |
d6506ff6 BE |
285 | |
286 | if (add_colon) | |
287 | FcStrBufChar (buf, ':'); | |
288 | if (add_elt_name) | |
289 | { | |
2017a5eb | 290 | FcStrBufString (buf, c->word); |
d6506ff6 BE |
291 | FcStrBufChar (buf, '='); |
292 | } | |
293 | ||
27b3e2dd BE |
294 | l = FcPatternEltValues(e); |
295 | FcNameUnparseValueList (buf, l, '\0'); | |
c493c3b7 BE |
296 | } |
297 | ||
8c31a243 BE |
298 | return FcTrue; |
299 | } | |
300 | ||
301 | static FcBool | |
302 | interpret_filter (FcFormatContext *c, | |
303 | FcPattern *pat, | |
304 | FcStrBuf *buf) | |
305 | { | |
306 | FcObjectSet *os; | |
307 | FcPattern *subpat; | |
308 | ||
309 | if (!expect_char (c, '+')) | |
310 | return FcFalse; | |
311 | ||
312 | os = FcObjectSetCreate (); | |
313 | if (!os) | |
314 | return FcFalse; | |
315 | ||
316 | do | |
317 | { | |
2017a5eb BE |
318 | if (!read_word (c) || |
319 | !FcObjectSetAdd (os, (const char *) c->word)) | |
8c31a243 BE |
320 | { |
321 | FcObjectSetDestroy (os); | |
322 | return FcFalse; | |
323 | } | |
324 | } | |
325 | while (consume_char (c, ',')); | |
326 | ||
327 | subpat = FcPatternFilter (pat, os); | |
328 | FcObjectSetDestroy (os); | |
329 | ||
330 | if (!subpat || | |
331 | !interpret_subexpr (c, subpat, buf)) | |
332 | return FcFalse; | |
333 | ||
334 | FcPatternDestroy (subpat); | |
335 | return FcTrue; | |
336 | } | |
337 | ||
338 | static FcBool | |
339 | interpret_delete (FcFormatContext *c, | |
340 | FcPattern *pat, | |
341 | FcStrBuf *buf) | |
342 | { | |
343 | FcPattern *subpat; | |
344 | ||
345 | if (!expect_char (c, '-')) | |
346 | return FcFalse; | |
347 | ||
348 | subpat = FcPatternDuplicate (pat); | |
349 | if (!subpat) | |
350 | return FcFalse; | |
351 | ||
352 | do | |
353 | { | |
2017a5eb | 354 | if (!read_word (c)) |
8c31a243 BE |
355 | { |
356 | FcPatternDestroy (subpat); | |
357 | return FcFalse; | |
358 | } | |
359 | ||
2017a5eb | 360 | FcPatternDel (subpat, (const char *) c->word); |
8c31a243 BE |
361 | } |
362 | while (consume_char (c, ',')); | |
363 | ||
364 | if (!interpret_subexpr (c, subpat, buf)) | |
365 | return FcFalse; | |
366 | ||
367 | FcPatternDestroy (subpat); | |
368 | return FcTrue; | |
369 | } | |
370 | ||
7717b25f | 371 | static FcBool |
2017a5eb BE |
372 | interpret_cond (FcFormatContext *c, |
373 | FcPattern *pat, | |
374 | FcStrBuf *buf) | |
7717b25f BE |
375 | { |
376 | FcBool pass; | |
377 | ||
378 | if (!expect_char (c, '?')) | |
379 | return FcFalse; | |
380 | ||
381 | pass = FcTrue; | |
382 | ||
383 | do | |
384 | { | |
385 | FcBool negate; | |
386 | FcValue v; | |
387 | ||
388 | negate = consume_char (c, '!'); | |
389 | ||
2017a5eb | 390 | if (!read_word (c)) |
7717b25f BE |
391 | return FcFalse; |
392 | ||
393 | pass = pass && | |
394 | (negate ^ | |
2017a5eb BE |
395 | (FcResultMatch == FcPatternGet (pat, |
396 | (const char *) c->word, | |
397 | 0, &v))); | |
7717b25f BE |
398 | } |
399 | while (consume_char (c, ',')); | |
400 | ||
401 | if (pass) | |
402 | { | |
403 | if (!interpret_subexpr (c, pat, buf) || | |
404 | !maybe_skip_subexpr (c)) | |
405 | return FcFalse; | |
406 | } | |
407 | else | |
408 | { | |
409 | if (!skip_subexpr (c) || | |
410 | !maybe_interpret_subexpr (c, pat, buf)) | |
411 | return FcFalse; | |
412 | } | |
413 | ||
414 | return FcTrue; | |
415 | } | |
416 | ||
2017a5eb BE |
417 | static FcChar8 * |
418 | convert (FcFormatContext *c, | |
419 | const FcChar8 *str) | |
420 | { | |
421 | if (!read_word (c)) | |
422 | return NULL; | |
423 | else if (0 == strcmp ((const char *) c->word, "downcase")) | |
424 | return FcStrDowncase (str); | |
425 | else if (0 == strcmp ((const char *) c->word, "basename")) | |
426 | return FcStrBasename (str); | |
427 | else if (0 == strcmp ((const char *) c->word, "dirname")) | |
428 | return FcStrDirname (str); | |
429 | ||
430 | message ("unknown converter \"%s\"", | |
431 | c->word); | |
432 | return NULL; | |
433 | } | |
434 | ||
435 | static FcBool | |
436 | maybe_interpret_converts (FcFormatContext *c, | |
437 | FcStrBuf *buf, | |
438 | int start) | |
439 | { | |
440 | while (consume_char (c, '|')) | |
441 | { | |
442 | const FcChar8 *str; | |
443 | FcChar8 *new_str; | |
444 | ||
445 | /* nul-terminate the buffer */ | |
446 | FcStrBufChar (buf, '\0'); | |
447 | if (buf->failed) | |
448 | return FcFalse; | |
449 | str = buf->buf + start; | |
450 | ||
451 | if (!(new_str = convert (c, str))) | |
452 | return FcFalse; | |
453 | ||
454 | /* replace in the buffer */ | |
455 | buf->len = start; | |
456 | FcStrBufString (buf, new_str); | |
457 | free (new_str); | |
458 | } | |
459 | ||
460 | return FcTrue; | |
461 | } | |
462 | ||
463 | static FcBool | |
464 | align_to_width (FcStrBuf *buf, | |
465 | int start, | |
466 | int width) | |
467 | { | |
468 | int len; | |
469 | ||
470 | if (buf->failed) | |
471 | return FcFalse; | |
472 | ||
473 | len = buf->len - start; | |
474 | if (len < -width) | |
475 | { | |
476 | /* left align */ | |
477 | while (len++ < -width) | |
478 | FcStrBufChar (buf, ' '); | |
479 | } | |
480 | else if (len < width) | |
481 | { | |
482 | int old_len; | |
483 | old_len = len; | |
484 | /* right align */ | |
485 | while (len++ < width) | |
486 | FcStrBufChar (buf, ' '); | |
487 | if (buf->failed) | |
488 | return FcFalse; | |
489 | len = old_len; | |
490 | memmove (buf->buf + buf->len - len, | |
491 | buf->buf + buf->len - width, | |
492 | len); | |
493 | memset (buf->buf + buf->len - width, | |
494 | ' ', | |
495 | width - len); | |
496 | } | |
497 | ||
498 | return !buf->failed; | |
499 | } | |
8c31a243 BE |
500 | static FcBool |
501 | interpret_percent (FcFormatContext *c, | |
502 | FcPattern *pat, | |
503 | FcStrBuf *buf) | |
504 | { | |
2017a5eb BE |
505 | int width, start; |
506 | FcBool ret; | |
8c31a243 BE |
507 | |
508 | if (!expect_char (c, '%')) | |
509 | return FcFalse; | |
510 | ||
511 | if (consume_char (c, '%')) /* "%%" */ | |
512 | { | |
513 | FcStrBufChar (buf, '%'); | |
514 | return FcTrue; | |
515 | } | |
516 | ||
517 | /* parse an optional width specifier */ | |
518 | width = strtol ((const char *) c->format, (char **) &c->format, 10); | |
519 | ||
8c31a243 BE |
520 | if (!expect_char (c, '{')) |
521 | return FcFalse; | |
522 | ||
2017a5eb | 523 | start = buf->len; |
7717b25f | 524 | |
2017a5eb BE |
525 | switch (*c->format) { |
526 | case '{': ret = interpret_subexpr (c, pat, buf); break; | |
527 | case '+': ret = interpret_filter (c, pat, buf); break; | |
528 | case '-': ret = interpret_delete (c, pat, buf); break; | |
529 | case '?': ret = interpret_cond (c, pat, buf); break; | |
530 | default: ret = interpret_simple (c, pat, buf); break; | |
0c93b91d | 531 | } |
c493c3b7 | 532 | |
2017a5eb BE |
533 | return ret && |
534 | maybe_interpret_converts (c, buf, start) && | |
535 | align_to_width (buf, start, width) && | |
536 | expect_char (c, '}'); | |
0c93b91d BE |
537 | } |
538 | ||
539 | static char escaped_char(const char ch) | |
540 | { | |
541 | switch (ch) { | |
542 | case 'a': return '\a'; | |
543 | case 'b': return '\b'; | |
544 | case 'f': return '\f'; | |
545 | case 'n': return '\n'; | |
546 | case 'r': return '\r'; | |
547 | case 't': return '\t'; | |
548 | case 'v': return '\v'; | |
549 | default: return ch; | |
550 | } | |
551 | } | |
552 | ||
8c31a243 | 553 | static FcBool |
7717b25f BE |
554 | interpret_expr (FcFormatContext *c, |
555 | FcPattern *pat, | |
556 | FcStrBuf *buf, | |
557 | FcChar8 term) | |
0c93b91d | 558 | { |
7717b25f | 559 | while (*c->format && *c->format != term) |
0c93b91d | 560 | { |
27b3e2dd | 561 | switch (*c->format) |
0c93b91d BE |
562 | { |
563 | case '\\': | |
27b3e2dd BE |
564 | c->format++; /* skip over '\\' */ |
565 | if (*c->format) | |
566 | FcStrBufChar (buf, escaped_char (*c->format++)); | |
0c93b91d BE |
567 | continue; |
568 | case '%': | |
8c31a243 BE |
569 | if (!interpret_percent (c, pat, buf)) |
570 | return FcFalse; | |
0c93b91d BE |
571 | continue; |
572 | } | |
27b3e2dd | 573 | FcStrBufChar (buf, *c->format++); |
0c93b91d | 574 | } |
8c31a243 | 575 | return FcTrue; |
0c93b91d BE |
576 | } |
577 | ||
578 | FcChar8 * | |
579 | FcPatternFormat (FcPattern *pat, const FcChar8 *format) | |
580 | { | |
0c93b91d | 581 | FcStrBuf buf; |
27b3e2dd | 582 | FcFormatContext c; |
8c31a243 | 583 | FcBool ret; |
0c93b91d BE |
584 | |
585 | FcStrBufInit (&buf, 0, 0); | |
8c31a243 BE |
586 | if (!FcFormatContextInit (&c, format)) |
587 | return NULL; | |
0c93b91d | 588 | |
7717b25f | 589 | ret = interpret_expr (&c, pat, &buf, '\0'); |
8c31a243 BE |
590 | if (buf.failed) |
591 | ret = FcFalse; | |
0c93b91d | 592 | |
27b3e2dd | 593 | FcFormatContextDone (&c); |
8c31a243 BE |
594 | if (ret) |
595 | return FcStrBufDone (&buf); | |
596 | else | |
597 | { | |
598 | FcStrBufDestroy (&buf); | |
599 | return NULL; | |
600 | } | |
0c93b91d BE |
601 | } |
602 | ||
603 | #define __fcformat__ | |
604 | #include "fcaliastail.h" | |
605 | #undef __fcformat__ |