]> git.wh0rd.org - fontconfig.git/blob - src/fcformat.c
[fcformat] Implement 'cescape', 'shescape', and 'xmlescape' converters
[fontconfig.git] / src / fcformat.c
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
31 /*
32 * Some ideas for future syntax extensions:
33 *
34 * - number of values for element using '%{#elt}'
35 * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
36 * - allow indexing simple tags using '%{elt[idx]}'
37 * - conditional/filtering/deletion on binding (using '(w)'/'(s)' notation)
38 */
39
40 static void
41 message (const char *fmt, ...)
42 {
43 va_list args;
44 va_start (args, fmt);
45 fprintf (stderr, "Fontconfig: Pattern format error: ");
46 vfprintf (stderr, fmt, args);
47 fprintf (stderr, ".\n");
48 va_end (args);
49 }
50
51
52 typedef struct _FcFormatContext
53 {
54 const FcChar8 *format_orig;
55 const FcChar8 *format;
56 int format_len;
57 FcChar8 *word;
58 } FcFormatContext;
59
60 static FcBool
61 FcFormatContextInit (FcFormatContext *c,
62 const FcChar8 *format)
63 {
64 c->format_orig = c->format = format;
65 c->format_len = strlen ((const char *) format);
66 c->word = malloc (c->format_len + 1);
67
68 return c->word != NULL;
69 }
70
71 static void
72 FcFormatContextDone (FcFormatContext *c)
73 {
74 if (c)
75 {
76 free (c->word);
77 }
78 }
79
80 static FcBool
81 consume_char (FcFormatContext *c,
82 FcChar8 term)
83 {
84 if (*c->format != term)
85 return FcFalse;
86
87 c->format++;
88 return FcTrue;
89 }
90
91 static FcBool
92 expect_char (FcFormatContext *c,
93 FcChar8 term)
94 {
95 FcBool res = consume_char (c, term);
96 if (!res)
97 {
98 if (c->format == c->format_orig + c->format_len)
99 message ("format ended while expecting '%c'",
100 term);
101 else
102 message ("expected '%c' at %d",
103 term, c->format - c->format_orig + 1);
104 }
105 return res;
106 }
107
108 static FcBool
109 FcCharIsPunct (const FcChar8 c)
110 {
111 if (c < '0')
112 return FcTrue;
113 if (c <= '9')
114 return FcFalse;
115 if (c < 'A')
116 return FcTrue;
117 if (c <= 'Z')
118 return FcFalse;
119 if (c < 'a')
120 return FcTrue;
121 if (c <= 'z')
122 return FcFalse;
123 if (c <= '~')
124 return FcTrue;
125 return FcFalse;
126 }
127
128 static FcBool
129 read_word (FcFormatContext *c)
130 {
131 FcChar8 *p;
132
133 p = c->word;
134
135 while (*c->format)
136 {
137 if (*c->format == '\\')
138 {
139 c->format++;
140 if (*c->format)
141 c->format++;
142 continue;
143 }
144 else if (FcCharIsPunct (*c->format))
145 break;
146
147 *p++ = *c->format++;
148 }
149 *p = '\0';
150
151 if (p == c->word)
152 {
153 message ("expected element name at %d",
154 c->format - c->format_orig + 1);
155 return FcFalse;
156 }
157
158 return FcTrue;
159 }
160
161 static FcBool
162 interpret_expr (FcFormatContext *c,
163 FcPattern *pat,
164 FcStrBuf *buf,
165 FcChar8 term);
166
167 static FcBool
168 interpret_subexpr (FcFormatContext *c,
169 FcPattern *pat,
170 FcStrBuf *buf)
171 {
172 return expect_char (c, '{') &&
173 interpret_expr (c, pat, buf, '}') &&
174 expect_char (c, '}');
175 }
176
177 static FcBool
178 maybe_interpret_subexpr (FcFormatContext *c,
179 FcPattern *pat,
180 FcStrBuf *buf)
181 {
182 return (*c->format == '{') ?
183 interpret_subexpr (c, pat, buf) :
184 FcTrue;
185 }
186
187 static FcBool
188 skip_subexpr (FcFormatContext *c);
189
190 static FcBool
191 skip_percent (FcFormatContext *c)
192 {
193 int width;
194
195 if (!expect_char (c, '%'))
196 return FcFalse;
197
198 /* skip an optional width specifier */
199 width = strtol ((const char *) c->format, (char **) &c->format, 10);
200
201 if (!expect_char (c, '{'))
202 return FcFalse;
203
204 while(*c->format && *c->format != '}')
205 {
206 switch (*c->format)
207 {
208 case '\\':
209 c->format++; /* skip over '\\' */
210 if (*c->format)
211 c->format++;
212 continue;
213 case '{':
214 if (!skip_subexpr (c))
215 return FcFalse;
216 continue;
217 }
218 c->format++;
219 }
220
221 return expect_char (c, '}');
222 }
223
224 static FcBool
225 skip_expr (FcFormatContext *c)
226 {
227 while(*c->format && *c->format != '}')
228 {
229 switch (*c->format)
230 {
231 case '\\':
232 c->format++; /* skip over '\\' */
233 if (*c->format)
234 c->format++;
235 continue;
236 case '%':
237 if (!skip_percent (c))
238 return FcFalse;
239 continue;
240 }
241 c->format++;
242 }
243
244 return FcTrue;
245 }
246
247 static FcBool
248 skip_subexpr (FcFormatContext *c)
249 {
250 return expect_char (c, '{') &&
251 skip_expr (c) &&
252 expect_char (c, '}');
253 }
254
255 static FcBool
256 maybe_skip_subexpr (FcFormatContext *c)
257 {
258 return (*c->format == '{') ?
259 skip_subexpr (c) :
260 FcTrue;
261 }
262
263 static FcBool
264 interpret_simple (FcFormatContext *c,
265 FcPattern *pat,
266 FcStrBuf *buf)
267 {
268 FcPatternElt *e;
269 FcBool add_colon = FcFalse;
270 FcBool add_elt_name = FcFalse;
271
272 if (consume_char (c, ':'))
273 add_colon = FcTrue;
274
275 if (!read_word (c))
276 return FcFalse;
277
278 if (consume_char (c, '='))
279 add_elt_name = FcTrue;
280
281 e = FcPatternObjectFindElt (pat,
282 FcObjectFromName ((const char *) c->word));
283 if (e)
284 {
285 FcValueListPtr l;
286
287 if (add_colon)
288 FcStrBufChar (buf, ':');
289 if (add_elt_name)
290 {
291 FcStrBufString (buf, c->word);
292 FcStrBufChar (buf, '=');
293 }
294
295 l = FcPatternEltValues(e);
296 FcNameUnparseValueList (buf, l, '\0');
297 }
298
299 return FcTrue;
300 }
301
302 static FcBool
303 interpret_filter (FcFormatContext *c,
304 FcPattern *pat,
305 FcStrBuf *buf)
306 {
307 FcObjectSet *os;
308 FcPattern *subpat;
309
310 if (!expect_char (c, '+'))
311 return FcFalse;
312
313 os = FcObjectSetCreate ();
314 if (!os)
315 return FcFalse;
316
317 do
318 {
319 if (!read_word (c) ||
320 !FcObjectSetAdd (os, (const char *) c->word))
321 {
322 FcObjectSetDestroy (os);
323 return FcFalse;
324 }
325 }
326 while (consume_char (c, ','));
327
328 subpat = FcPatternFilter (pat, os);
329 FcObjectSetDestroy (os);
330
331 if (!subpat ||
332 !interpret_subexpr (c, subpat, buf))
333 return FcFalse;
334
335 FcPatternDestroy (subpat);
336 return FcTrue;
337 }
338
339 static FcBool
340 interpret_delete (FcFormatContext *c,
341 FcPattern *pat,
342 FcStrBuf *buf)
343 {
344 FcPattern *subpat;
345
346 if (!expect_char (c, '-'))
347 return FcFalse;
348
349 subpat = FcPatternDuplicate (pat);
350 if (!subpat)
351 return FcFalse;
352
353 do
354 {
355 if (!read_word (c))
356 {
357 FcPatternDestroy (subpat);
358 return FcFalse;
359 }
360
361 FcPatternDel (subpat, (const char *) c->word);
362 }
363 while (consume_char (c, ','));
364
365 if (!interpret_subexpr (c, subpat, buf))
366 return FcFalse;
367
368 FcPatternDestroy (subpat);
369 return FcTrue;
370 }
371
372 static FcBool
373 interpret_cond (FcFormatContext *c,
374 FcPattern *pat,
375 FcStrBuf *buf)
376 {
377 FcBool pass;
378
379 if (!expect_char (c, '?'))
380 return FcFalse;
381
382 pass = FcTrue;
383
384 do
385 {
386 FcBool negate;
387 FcValue v;
388
389 negate = consume_char (c, '!');
390
391 if (!read_word (c))
392 return FcFalse;
393
394 pass = pass &&
395 (negate ^
396 (FcResultMatch == FcPatternGet (pat,
397 (const char *) c->word,
398 0, &v)));
399 }
400 while (consume_char (c, ','));
401
402 if (pass)
403 {
404 if (!interpret_subexpr (c, pat, buf) ||
405 !maybe_skip_subexpr (c))
406 return FcFalse;
407 }
408 else
409 {
410 if (!skip_subexpr (c) ||
411 !maybe_interpret_subexpr (c, pat, buf))
412 return FcFalse;
413 }
414
415 return FcTrue;
416 }
417
418 static FcChar8 *
419 cescape (const FcChar8 *str)
420 {
421 FcStrBuf buf;
422 FcChar8 buf_static[8192];
423
424 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
425 while(*str)
426 {
427 switch (*str)
428 {
429 case '\\':
430 case '"':
431 FcStrBufChar (&buf, '\\');
432 break;
433 }
434 FcStrBufChar (&buf, *str++);
435 }
436 return FcStrBufDone (&buf);
437 }
438
439 static FcChar8 *
440 shescape (const FcChar8 *str)
441 {
442 FcStrBuf buf;
443 FcChar8 buf_static[8192];
444
445 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
446 FcStrBufChar (&buf, '\'');
447 while(*str)
448 {
449 if (*str == '\'')
450 FcStrBufString (&buf, (const FcChar8 *) "'\\''");
451 else
452 FcStrBufChar (&buf, *str);
453 str++;
454 }
455 FcStrBufChar (&buf, '\'');
456 return FcStrBufDone (&buf);
457 }
458
459 static FcChar8 *
460 xmlescape (const FcChar8 *str)
461 {
462 FcStrBuf buf;
463 FcChar8 buf_static[8192];
464
465 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
466 while(*str)
467 {
468 switch (*str)
469 {
470 case '&': FcStrBufString (&buf, (const FcChar8 *) "&amp;"); break;
471 case '<': FcStrBufString (&buf, (const FcChar8 *) "&lt;"); break;
472 case '>': FcStrBufString (&buf, (const FcChar8 *) "&gt;"); break;
473 default: FcStrBufChar (&buf, *str); break;
474 }
475 str++;
476 }
477 return FcStrBufDone (&buf);
478 }
479
480 static FcChar8 *
481 convert (FcFormatContext *c,
482 const FcChar8 *str)
483 {
484 if (!read_word (c))
485 return NULL;
486 #define CONVERTER(name, func) \
487 else if (0 == strcmp ((const char *) c->word, name))\
488 return func (str)
489 CONVERTER ("downcase", FcStrDowncase);
490 CONVERTER ("basename", FcStrBasename);
491 CONVERTER ("dirname", FcStrDirname);
492 CONVERTER ("cescape", cescape);
493 CONVERTER ("shescape", shescape);
494 CONVERTER ("xmlescape", xmlescape);
495
496 message ("unknown converter \"%s\"",
497 c->word);
498 return NULL;
499 }
500
501 static FcBool
502 maybe_interpret_converts (FcFormatContext *c,
503 FcStrBuf *buf,
504 int start)
505 {
506 while (consume_char (c, '|'))
507 {
508 const FcChar8 *str;
509 FcChar8 *new_str;
510
511 /* nul-terminate the buffer */
512 FcStrBufChar (buf, '\0');
513 if (buf->failed)
514 return FcFalse;
515 str = buf->buf + start;
516
517 if (!(new_str = convert (c, str)))
518 return FcFalse;
519
520 /* replace in the buffer */
521 buf->len = start;
522 FcStrBufString (buf, new_str);
523 free (new_str);
524 }
525
526 return FcTrue;
527 }
528
529 static FcBool
530 align_to_width (FcStrBuf *buf,
531 int start,
532 int width)
533 {
534 int len;
535
536 if (buf->failed)
537 return FcFalse;
538
539 len = buf->len - start;
540 if (len < -width)
541 {
542 /* left align */
543 while (len++ < -width)
544 FcStrBufChar (buf, ' ');
545 }
546 else if (len < width)
547 {
548 int old_len;
549 old_len = len;
550 /* right align */
551 while (len++ < width)
552 FcStrBufChar (buf, ' ');
553 if (buf->failed)
554 return FcFalse;
555 len = old_len;
556 memmove (buf->buf + buf->len - len,
557 buf->buf + buf->len - width,
558 len);
559 memset (buf->buf + buf->len - width,
560 ' ',
561 width - len);
562 }
563
564 return !buf->failed;
565 }
566 static FcBool
567 interpret_percent (FcFormatContext *c,
568 FcPattern *pat,
569 FcStrBuf *buf)
570 {
571 int width, start;
572 FcBool ret;
573
574 if (!expect_char (c, '%'))
575 return FcFalse;
576
577 if (consume_char (c, '%')) /* "%%" */
578 {
579 FcStrBufChar (buf, '%');
580 return FcTrue;
581 }
582
583 /* parse an optional width specifier */
584 width = strtol ((const char *) c->format, (char **) &c->format, 10);
585
586 if (!expect_char (c, '{'))
587 return FcFalse;
588
589 start = buf->len;
590
591 switch (*c->format) {
592 case '{': ret = interpret_subexpr (c, pat, buf); break;
593 case '+': ret = interpret_filter (c, pat, buf); break;
594 case '-': ret = interpret_delete (c, pat, buf); break;
595 case '?': ret = interpret_cond (c, pat, buf); break;
596 default: ret = interpret_simple (c, pat, buf); break;
597 }
598
599 return ret &&
600 maybe_interpret_converts (c, buf, start) &&
601 align_to_width (buf, start, width) &&
602 expect_char (c, '}');
603 }
604
605 static char escaped_char(const char ch)
606 {
607 switch (ch) {
608 case 'a': return '\a';
609 case 'b': return '\b';
610 case 'f': return '\f';
611 case 'n': return '\n';
612 case 'r': return '\r';
613 case 't': return '\t';
614 case 'v': return '\v';
615 default: return ch;
616 }
617 }
618
619 static FcBool
620 interpret_expr (FcFormatContext *c,
621 FcPattern *pat,
622 FcStrBuf *buf,
623 FcChar8 term)
624 {
625 while (*c->format && *c->format != term)
626 {
627 switch (*c->format)
628 {
629 case '\\':
630 c->format++; /* skip over '\\' */
631 if (*c->format)
632 FcStrBufChar (buf, escaped_char (*c->format++));
633 continue;
634 case '%':
635 if (!interpret_percent (c, pat, buf))
636 return FcFalse;
637 continue;
638 }
639 FcStrBufChar (buf, *c->format++);
640 }
641 return FcTrue;
642 }
643
644 FcChar8 *
645 FcPatternFormat (FcPattern *pat, const FcChar8 *format)
646 {
647 FcStrBuf buf;
648 FcChar8 buf_static[8192];
649 FcFormatContext c;
650 FcBool ret;
651
652 FcStrBufInit (&buf, buf_static, sizeof (buf_static));
653 if (!FcFormatContextInit (&c, format))
654 return NULL;
655
656 ret = interpret_expr (&c, pat, &buf, '\0');
657
658 FcFormatContextDone (&c);
659 if (ret)
660 return FcStrBufDone (&buf);
661 else
662 {
663 FcStrBufDestroy (&buf);
664 return NULL;
665 }
666 }
667
668 #define __fcformat__
669 #include "fcaliastail.h"
670 #undef __fcformat__