]> git.wh0rd.org Git - 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__