luci-base: add support for plural translations and contexts in Lua api
[project/luci.git] / modules / luci-base / src / template_lmo.c
index f7a118c9bbd41a4e8bde6b6dcca740a78787abc7..8634bc4bf36bd499ca532d323b084ceeb8730ac2 100644 (file)
@@ -17,6 +17,7 @@
  */
 
 #include "template_lmo.h"
+#include "plural_formula.h"
 
 /*
  * Hash function from http://www.azillionmonkeys.com/qed/hash.html
@@ -69,17 +70,51 @@ uint32_t sfh_hash(const char *data, int len)
        return hash;
 }
 
-uint32_t lmo_canon_hash(const char *str, int len)
+uint32_t lmo_canon_hash(const char *str, int len,
+                        const char *ctx, int ctxlen, int plural)
 {
        char res[4096];
-       char *ptr, prev;
+       char *ptr, *end, prev;
        int off;
 
-       if (!str || len >= sizeof(res))
+       if (!str)
                return 0;
 
-       for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
+       ptr = res;
+       end = res + sizeof(res);
+
+       if (ctx)
+       {
+               for (prev = ' ', off = 0; off < ctxlen; prev = *ctx, off++, ctx++)
+               {
+                       if (ptr >= end)
+                               return 0;
+
+                       if (isspace(*ctx))
+                       {
+                               if (!isspace(prev))
+                                       *ptr++ = ' ';
+                       }
+                       else
+                       {
+                               *ptr++ = *ctx;
+                       }
+               }
+
+               if ((ptr > res) && isspace(*(ptr-1)))
+                       ptr--;
+
+               if (ptr >= end)
+                       return 0;
+
+               *ptr++ = '\1';
+       }
+
+       for (prev = ' ', off = 0; off < len; prev = *str, off++, str++)
        {
+               if (ptr >= end)
+                       return 0;
+
                if (isspace(*str))
                {
                        if (!isspace(prev))
@@ -94,6 +129,14 @@ uint32_t lmo_canon_hash(const char *str, int len)
        if ((ptr > res) && isspace(*(ptr-1)))
                ptr--;
 
+       if (plural > -1)
+       {
+               if (plural >= 100 || ptr + 3 >= end)
+                       return 0;
+
+               ptr += snprintf(ptr, 3, "\2%d", plural);
+       }
+
        return sfh_hash(res, ptr - res);
 }
 
@@ -277,7 +320,193 @@ static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
        return NULL;
 }
 
+void *pluralParseAlloc(void *(*)(size_t));
+void pluralParse(void *, int, int, void *);
+void pluralParseFree(void *, void (*)(void *));
+
+static int lmo_eval_plural(const char *expr, int len, int val)
+{
+       struct { int num; int res; } s = { .num = val, .res = -1 };
+       const char *p = NULL;
+       void *pParser = NULL;
+       int t, n;
+       char c;
+
+       while (len > 7) {
+               if (*expr == 'p') {
+                       if (!strncmp(expr, "plural=", 7)) {
+                               p = expr + 7;
+                               len -= 7;
+                               break;
+                       }
+               }
+
+               expr++;
+               len--;
+       }
+
+       if (!p)
+               goto out;
+
+       pParser = pluralParseAlloc(malloc);
+
+       if (!pParser)
+               goto out;
+
+       while (len-- > 0) {
+               c = *p++;
+               t = -1;
+               n = 0;
+
+               switch (c) {
+               case ' ':
+               case '\t':
+                       continue;
+
+               case '0': case '1': case '2': case '3': case '4':
+               case '5': case '6': case '7': case '8': case '9':
+                       t = T_NUM;
+                       n = c - '0';
+
+                       while (*p >= '0' && *p <= '9') {
+                               n *= 10;
+                               n += *p - '0';
+                               p++;
+                       }
+
+                       break;
+
+               case '=':
+                       if (*p == '=') {
+                               t = T_EQ;
+                               p++;
+                       }
+
+                       break;
+
+               case '!':
+                       if (*p == '=') {
+                               t = T_NE;
+                               p++;
+                       }
+                       else {
+                               t = T_NOT;
+                       }
+
+                       break;
+
+               case '&':
+                       if (*p == '&') {
+                               t = T_AND;
+                               p++;
+                       }
+
+                       break;
+
+               case '|':
+                       if (*p == '|') {
+                               t = T_OR;
+                               p++;
+                       }
+
+                       break;
+
+               case '<':
+                       if (*p == '=') {
+                               t = T_LE;
+                               p++;
+                       }
+                       else {
+                               t = T_LT;
+                       }
+
+                       break;
+
+               case '>':
+                       if (*p == '=') {
+                               t = T_GE;
+                               p++;
+                       }
+                       else {
+                               t = T_GT;
+                       }
+
+                       break;
+
+               case '*':
+                       t = T_MUL;
+                       break;
+
+               case '/':
+                       t = T_DIV;
+                       break;
+
+               case '%':
+                       t = T_MOD;
+                       break;
+
+               case '+':
+                       t = T_ADD;
+                       break;
+
+               case '-':
+                       t = T_SUB;
+                       break;
+
+               case 'n':
+                       t = T_N;
+                       break;
+
+               case '?':
+                       t = T_QMARK;
+                       break;
+
+               case ':':
+                       t = T_COLON;
+                       break;
+
+               case '(':
+                       t = T_LPAREN;
+                       break;
+
+               case ')':
+                       t = T_RPAREN;
+                       break;
+
+               case ';':
+               case '\n':
+               case '\0':
+                       t = 0;
+                       break;
+               }
+
+               /* syntax error */
+               if (t < 0)
+                       goto out;
+
+               pluralParse(pParser, t, n, &s);
+
+               /* eof */
+               if (t == 0)
+                       break;
+       }
+
+       pluralParse(pParser, 0, 0, &s);
+
+out:
+       pluralParseFree(pParser, free);
+
+       return s.res;
+}
+
 int lmo_translate(const char *key, int keylen, char **out, int *outlen)
+{
+       return lmo_translate_ctxt(key, keylen, NULL, 0, out, outlen);
+}
+
+int lmo_translate_ctxt(const char *key, int keylen,
+                       const char *ctx, int ctxlen,
+                       char **out, int *outlen)
 {
        uint32_t hash;
        lmo_entry_t *e;
@@ -286,7 +515,62 @@ int lmo_translate(const char *key, int keylen, char **out, int *outlen)
        if (!key || !_lmo_active_catalog)
                return -2;
 
-       hash = lmo_canon_hash(key, keylen);
+       hash = lmo_canon_hash(key, keylen, ctx, ctxlen, -1);
+
+       if (hash > 0)
+       {
+               for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
+               {
+                       if ((e = lmo_find_entry(ar, hash)) != NULL)
+                       {
+                               *out = ar->mmap + ntohl(e->offset);
+                               *outlen = ntohl(e->length);
+                               return 0;
+                       }
+               }
+       }
+
+       return -1;
+}
+
+int lmo_translate_plural(int n, const char *skey, int skeylen,
+                                const char *pkey, int pkeylen,
+                                char **out, int *outlen)
+{
+       return lmo_translate_plural_ctxt(n, skey, skeylen, pkey, pkeylen,
+                                        NULL, 0, out, outlen);
+}
+
+int lmo_translate_plural_ctxt(int n, const char *skey, int skeylen,
+                                     const char *pkey, int pkeylen,
+                                     const char *ctx, int ctxlen,
+                                     char **out, int *outlen)
+{
+       int pid = -1;
+       uint32_t hash;
+       lmo_entry_t *e;
+       lmo_archive_t *ar;
+       const char *plural_formula;
+
+       if (!skey || !pkey || !_lmo_active_catalog)
+               return -2;
+
+       for (ar = _lmo_active_catalog->archives; ar; ar = ar->next) {
+               e = lmo_find_entry(ar, 0);
+
+               if (e != NULL) {
+                       pid = lmo_eval_plural(ar->mmap + ntohl(e->offset), ntohl(e->length), n);
+                       break;
+               }
+       }
+
+       if (pid == -1)
+               pid = (n != 1);
+
+       hash = lmo_canon_hash(skey, skeylen, ctx, ctxlen, pid);
+
+       if (hash == 0)
+               return -1;
 
        for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
        {
@@ -298,7 +582,18 @@ int lmo_translate(const char *key, int keylen, char **out, int *outlen)
                }
        }
 
-       return -1;
+       if (n != 1)
+       {
+               *out = (char *)pkey;
+               *outlen = pkeylen;
+       }
+       else
+       {
+               *out = (char *)skey;
+               *outlen = skeylen;
+       }
+
+       return 0;
 }
 
 void lmo_iterate(lmo_iterate_cb_t cb, void *priv)