]> git.wh0rd.org - fontconfig.git/blobdiff - src/fcformat.c
Switch fc-match to use FcPatternFormat()
[fontconfig.git] / src / fcformat.c
index 898ee324151371476cfd8fba1e9d8efe42d7c99d..4a850ee0944df487d95a77417b1615baba70f744 100644 (file)
@@ -7,15 +7,15 @@
  * documentation for any purpose is hereby granted without fee, provided that
  * the above copyright notice appear in all copies and that both that
  * copyright notice and this permission notice appear in supporting
- * documentation, and that the name of Keith Packard not be used in
+ * documentation, and that the name of the author(s) not be used in
  * advertising or publicity pertaining to distribution of the software without
- * specific, written prior permission.  Keith Packard makes no
+ * specific, written prior permission.  The authors make no
  * representations about the suitability of this software for any purpose.  It
  * is provided "as is" without express or implied warranty.
  *
- * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
  * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
- * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+ * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
  * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
  * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 #include <stdarg.h>
 
 
-/*
+/* The language is documented in doc/fcformat.fncs
+ * These are the features implemented:
+ *
+ * simple      %{elt}
+ * width       %width{elt}
+ * index       %{elt[idx]}
+ * name=       %{elt=}
+ * :name=      %{:elt}
+ * default     %{elt:-word}
+ * count       %{#elt}
+ * subexpr     %{{expr}}
+ * filter-out  %{-elt1,elt2,elt3{expr}}
+ * filter-in   %{+elt1,elt2,elt3{expr}}
+ * conditional %{?elt1,elt2,!elt3{}{}}
+ * enumerate   %{[]elt1,elt2{expr}}
+ * langset     langset enumeration using the same syntax
+ * builtin     %{=blt}
+ * convert     %{elt|conv1|conv2|conv3}
+ *
+ * converters:
+ * basename    FcStrBasename
+ * dirname     FcStrDirname
+ * downcase    FcStrDowncase
+ * shescape
+ * cescape
+ * xmlescape
+ * delete      delete chars
+ * escape      escape chars
+ * translate   translate chars
+ *
+ * builtins:
+ * unparse     FcNameUnparse
+ * fcmatch     fc-match default
+ * fclist      fc-list default
+ * pkgkit      PackageKit package tag format
+ *
+ *
  * Some ideas for future syntax extensions:
  *
- * - array enumeration using '%{[]family,familylang{expr}|decorator}'
- * - langset enumeration using same syntax as array enumeration
- * - allow indexing simple tags using '%{elt[idx]}'
+ * - verbose builtin that is like FcPatternPrint
  * - allow indexing subexprs using '%{[idx]elt1,elt2{subexpr}}'
+ * - allow indexing in +, -, ? filtering?
  * - conditional/filtering/deletion on binding (using '(w)'/'(s)'/'(=)' notation)
  */
 
 
-/* fc-match needs '<unknown filename>', etc handling. */
-#define FCMATCH_FORMAT "%{file|basename}: \"%{family[0]}\" \"%{style[0]}\""
-#define FCLIST_FORMAT  "%{?file{%{file}: }}%{=unparse}"
+#define FCMATCH_FORMAT "%{file:-<unknown filename>|basename}: \"%{family[0]:-<unknown family>}\" \"%{style[0]:-<unknown style>}\""
+#define FCLIST_FORMAT  "%{?file{%{file}: }}%{-file{%{=unparse}}}"
+#define PKGKIT_FORMAT  "%{[]family{font(%{family|downcase|delete( )})\n}}%{[]lang{font(:lang=%{lang|downcase|translate(_,-)})\n}}"
 
 
 static void
@@ -247,7 +282,7 @@ interpret_builtin (FcFormatContext *c,
     else if (0 == strcmp ((const char *) c->word, name))\
        do { new_str = func (pat); ret = FcTrue; } while (0)
     BUILTIN ("unparse",  FcNameUnparse);
- /* BUILTIN ("verbose",  FcPatternPrint); */
+ /* BUILTIN ("verbose",  FcPatternPrint); XXX */
 #undef BUILTIN
     else
        ret = FcFalse;
@@ -271,6 +306,7 @@ interpret_builtin (FcFormatContext *c,
        ret = FcPatternFormatToBuf (pat, (const FcChar8 *) format, buf)
     BUILTIN ("fcmatch",  FCMATCH_FORMAT);
     BUILTIN ("fclist",   FCLIST_FORMAT);
+    BUILTIN ("pkgkit",   PKGKIT_FORMAT);
 #undef BUILTIN
     else
        ret = FcFalse;
@@ -385,9 +421,9 @@ maybe_skip_subexpr (FcFormatContext *c)
 }
 
 static FcBool
-interpret_filter (FcFormatContext *c,
-                 FcPattern       *pat,
-                 FcStrBuf        *buf)
+interpret_filter_in (FcFormatContext *c,
+                    FcPattern       *pat,
+                    FcStrBuf        *buf)
 {
     FcObjectSet  *os;
     FcPattern    *subpat;
@@ -401,6 +437,7 @@ interpret_filter (FcFormatContext *c,
 
     do
     {
+       /* XXX binding */
        if (!read_word (c) ||
            !FcObjectSetAdd (os, (const char *) c->word))
        {
@@ -422,9 +459,9 @@ interpret_filter (FcFormatContext *c,
 }
 
 static FcBool
-interpret_delete (FcFormatContext *c,
-                 FcPattern       *pat,
-                 FcStrBuf        *buf)
+interpret_filter_out (FcFormatContext *c,
+                     FcPattern       *pat,
+                     FcStrBuf        *buf)
 {
     FcPattern    *subpat;
 
@@ -478,9 +515,8 @@ interpret_cond (FcFormatContext *c,
 
        pass = pass &&
               (negate ^
-               (FcResultMatch == FcPatternGet (pat,
-                                               (const char *) c->word,
-                                               0, &v)));
+               (FcResultMatch ==
+                FcPatternGet (pat, (const char *) c->word, 0, &v)));
     }
     while (consume_char (c, ','));
 
@@ -534,6 +570,124 @@ interpret_count (FcFormatContext *c,
     return FcTrue;
 }
 
+static FcBool
+interpret_enumerate (FcFormatContext *c,
+                    FcPattern       *pat,
+                    FcStrBuf        *buf)
+{
+    FcObjectSet   *os;
+    FcPattern     *subpat;
+    const FcChar8 *format_save;
+    int            idx;
+    FcBool         ret, done;
+    FcStrList      *lang_strs;
+
+    if (!expect_char (c, '[') ||
+       !expect_char (c, ']'))
+       return FcFalse;
+
+    os = FcObjectSetCreate ();
+    if (!os)
+       return FcFalse;
+
+    ret = FcTrue;
+
+    do
+    {
+       if (!read_word (c) ||
+           !FcObjectSetAdd (os, (const char *) c->word))
+       {
+           FcObjectSetDestroy (os);
+           return FcFalse;
+       }
+    }
+    while (consume_char (c, ','));
+
+    /* If we have one element and it's of type FcLangSet, we want
+     * to enumerate the languages in it. */
+    lang_strs = NULL;
+    if (os->nobject == 1)
+    {
+       FcLangSet *langset;
+       if (FcResultMatch ==
+           FcPatternGetLangSet (pat, os->objects[0], 0, &langset))
+       {
+           FcStrSet *ss;
+           if (!(ss = FcLangSetGetLangs (langset)) ||
+               !(lang_strs = FcStrListCreate (ss)))
+               goto bail0;
+       }
+    }
+
+    subpat = FcPatternDuplicate (pat);
+    if (!subpat)
+       goto bail0;
+
+    format_save = c->format;
+    idx = 0;
+    do
+    {
+       int i;
+
+       done = FcTrue;
+
+       if (lang_strs)
+       {
+           FcChar8 *lang;
+
+           FcPatternDel (subpat, os->objects[0]);
+           if ((lang = FcStrListNext (lang_strs)))
+           {
+               /* XXX binding? */
+               FcPatternAddString (subpat, os->objects[0], lang);
+               done = FcFalse;
+           }
+       }
+       else
+       {
+           for (i = 0; i < os->nobject; i++)
+           {
+               FcValue v;
+
+               /* XXX this can be optimized by accessing valuelist linked lists
+                * directly and remembering where we were.  Most (all) value lists
+                * in normal uses are pretty short though (language tags are
+                * stored as a LangSet, not separate values.). */
+               FcPatternDel (subpat, os->objects[i]);
+               if (FcResultMatch ==
+                   FcPatternGet (pat, os->objects[i], idx, &v))
+               {
+                   /* XXX binding */
+                   FcPatternAdd (subpat, os->objects[i], v, FcFalse);
+                   done = FcFalse;
+               }
+           }
+       }
+
+       if (!done)
+       {
+           c->format = format_save;
+           ret = interpret_subexpr (c, subpat, buf);
+           if (!ret)
+               goto bail;
+       }
+
+       idx++;
+    } while (!done);
+
+    if (c->format == format_save)
+       skip_subexpr (c);
+
+bail:
+    FcPatternDestroy (subpat);
+bail0:
+    if (lang_strs)
+       FcStrListDone (lang_strs);
+    FcObjectSetDestroy (os);
+
+    return ret;
+}
+
 static FcBool
 interpret_simple (FcFormatContext *c,
                  FcPattern       *pat,
@@ -543,6 +697,7 @@ interpret_simple (FcFormatContext *c,
     FcBool        add_colon = FcFalse;
     FcBool        add_elt_name = FcFalse;
     int           idx;
+    FcChar8      *else_string;
 
     if (consume_char (c, ':'))
        add_colon = FcTrue;
@@ -560,17 +715,37 @@ interpret_simple (FcFormatContext *c,
                     c->format-1 - c->format_orig + 1);
            return FcFalse;
        }
-       expect_char (c, ']');
+       if (!expect_char (c, ']'))
+           return FcFalse;
     }
 
     if (consume_char (c, '='))
        add_elt_name = FcTrue;
 
+    /* modifiers */
+    else_string = NULL;
+    if (consume_char (c, ':'))
+    {
+       FcChar8 *orig;
+       /* divert the c->word for now */
+       orig = c->word;
+       c->word = c->word + strlen ((const char *) c->word) + 1;
+       /* for now we just support 'default value' */
+       if (!expect_char (c, '-') ||
+           !read_chars (c, '|'))
+       {
+           c->word = orig;
+           return FcFalse;
+       }
+       else_string = c->word;
+       c->word = orig;
+    }
+
     e = FcPatternObjectFindElt (pat,
                                FcObjectFromName ((const char *) c->word));
-    if (e)
+    if (e || else_string)
     {
-       FcValueListPtr l;
+       FcValueListPtr l = NULL;
 
        if (add_colon)
            FcStrBufChar (buf, ':');
@@ -580,7 +755,8 @@ interpret_simple (FcFormatContext *c,
            FcStrBufChar (buf, '=');
        }
 
-       l = FcPatternEltValues(e);
+       if (e)
+           l = FcPatternEltValues(e);
 
        if (idx != -1)
        {
@@ -596,14 +772,16 @@ interpret_simple (FcFormatContext *c,
            }
            else goto notfound;
         }
-       else
+       else if (l)
        {
            FcNameUnparseValueList (buf, l, '\0');
        }
-    }
-    else
-notfound:
-    {
+       else
+       {
+    notfound:
+           if (else_string)
+               FcStrBufString (buf, else_string);
+       }
     }
 
     return FcTrue;
@@ -614,6 +792,8 @@ cescape (FcFormatContext *c,
         const FcChar8   *str,
         FcStrBuf        *buf)
 {
+    /* XXX escape \n etc? */
+
     while(*str)
     {
        switch (*str)
@@ -651,6 +831,8 @@ xmlescape (FcFormatContext *c,
           const FcChar8   *str,
           FcStrBuf        *buf)
 {
+    /* XXX escape \n etc? */
+
     while(*str)
     {
        switch (*str)
@@ -752,7 +934,7 @@ translate_chars (FcFormatContext *c,
     from_len = strlen (from);
     to = from + from_len + 1;
 
-    /* hack: we temporarily diverge c->word */
+    /* hack: we temporarily divert c->word */
     c->word = (FcChar8 *) to;
     if (!read_chars (c, ')'))
     {
@@ -943,13 +1125,14 @@ interpret_percent (FcFormatContext *c,
     start = buf->len;
 
     switch (*c->format) {
-    case '=': ret = interpret_builtin (c, pat, buf); break;
-    case '{': ret = interpret_subexpr (c, pat, buf); break;
-    case '+': ret = interpret_filter  (c, pat, buf); break;
-    case '-': ret = interpret_delete  (c, pat, buf); break;
-    case '?': ret = interpret_cond    (c, pat, buf); break;
-    case '#': ret = interpret_count   (c, pat, buf); break;
-    default:  ret = interpret_simple  (c, pat, buf); break;
+    case '=': ret = interpret_builtin    (c, pat, buf); break;
+    case '{': ret = interpret_subexpr    (c, pat, buf); break;
+    case '+': ret = interpret_filter_in  (c, pat, buf); break;
+    case '-': ret = interpret_filter_out (c, pat, buf); break;
+    case '?': ret = interpret_cond       (c, pat, buf); break;
+    case '#': ret = interpret_count      (c, pat, buf); break;
+    case '[': ret = interpret_enumerate  (c, pat, buf); break;
+    default:  ret = interpret_simple     (c, pat, buf); break;
     }
 
     return ret &&