430 lines
8.1 KiB
C
430 lines
8.1 KiB
C
/*
|
|
Copyright (C) 2020 Taler Systems SA
|
|
|
|
Original license:
|
|
Author: José Bollo <jobol@nonadev.net>
|
|
Author: José Bollo <jose.bollo@iot.bzh>
|
|
|
|
https://gitlab.com/jobol/mustach
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#include "platform.h"
|
|
#include "mustach-jansson.h"
|
|
|
|
struct Context
|
|
{
|
|
/**
|
|
* Context object.
|
|
*/
|
|
json_t *cont;
|
|
|
|
/**
|
|
* Current object.
|
|
*/
|
|
json_t *obj;
|
|
|
|
/**
|
|
* Opaque object iterator.
|
|
*/
|
|
void *iter;
|
|
|
|
/**
|
|
* Current index when iterating over an array.
|
|
*/
|
|
unsigned int index;
|
|
|
|
/**
|
|
* Count when iterating over an array.
|
|
*/
|
|
unsigned int count;
|
|
|
|
bool is_objiter;
|
|
};
|
|
|
|
enum Bang
|
|
{
|
|
BANG_NONE,
|
|
BANG_I18N,
|
|
BANG_STRINGIFY,
|
|
BANG_AMOUNT_CURRENCY,
|
|
BANG_AMOUNT_DECIMAL,
|
|
};
|
|
|
|
struct JanssonClosure
|
|
{
|
|
json_t *root;
|
|
mustach_jansson_write_cb writecb;
|
|
int depth;
|
|
|
|
/**
|
|
* Did the last find(..) call result in an iterable?
|
|
*/
|
|
struct Context stack[MUSTACH_MAX_DEPTH];
|
|
|
|
/**
|
|
* The last object we found should be iterated over.
|
|
*/
|
|
bool found_iter;
|
|
|
|
/**
|
|
* Last bang we found.
|
|
*/
|
|
enum Bang found_bang;
|
|
|
|
/**
|
|
* Language for i18n lookups.
|
|
*/
|
|
const char *lang;
|
|
};
|
|
|
|
|
|
static json_t *
|
|
walk (json_t *obj, const char *path)
|
|
{
|
|
char *saveptr = NULL;
|
|
char *sp = GNUNET_strdup (path);
|
|
char *p = sp;
|
|
while (true)
|
|
{
|
|
char *tok = strtok_r (p, ".", &saveptr);
|
|
if (tok == NULL)
|
|
break;
|
|
obj = json_object_get (obj, tok);
|
|
if (obj == NULL)
|
|
break;
|
|
p = NULL;
|
|
}
|
|
GNUNET_free (sp);
|
|
return obj;
|
|
}
|
|
|
|
|
|
static json_t *
|
|
find (struct JanssonClosure *e, const char *name)
|
|
{
|
|
json_t *obj = NULL;
|
|
char *path = GNUNET_strdup (name);
|
|
char *bang;
|
|
|
|
bang = strchr (path, '!');
|
|
|
|
e->found_bang = BANG_NONE;
|
|
|
|
if (NULL != bang)
|
|
{
|
|
*bang = 0;
|
|
bang++;
|
|
|
|
if (0 == strcmp (bang, "i18n"))
|
|
e->found_bang = BANG_I18N;
|
|
else if (0 == strcmp (bang, "stringify"))
|
|
e->found_bang = BANG_STRINGIFY;
|
|
else if (0 == strcmp (bang, "amount_decimal"))
|
|
e->found_bang = BANG_AMOUNT_CURRENCY;
|
|
else if (0 == strcmp (bang, "amount_currency"))
|
|
e->found_bang = BANG_AMOUNT_DECIMAL;
|
|
}
|
|
|
|
if (BANG_I18N == e->found_bang && NULL != e->lang)
|
|
{
|
|
char *aug_path;
|
|
GNUNET_asprintf (&aug_path, "%s_i18n.%s", path, e->lang);
|
|
obj = walk (e->stack[e->depth].obj, aug_path);
|
|
GNUNET_free (aug_path);
|
|
}
|
|
|
|
if (NULL == obj)
|
|
{
|
|
obj = walk (e->stack[e->depth].obj, path);
|
|
}
|
|
|
|
GNUNET_free (path);
|
|
|
|
return obj;
|
|
}
|
|
|
|
|
|
static int
|
|
start (void *closure)
|
|
{
|
|
struct JanssonClosure *e = closure;
|
|
e->depth = 0;
|
|
e->stack[0].cont = NULL;
|
|
e->stack[0].obj = e->root;
|
|
e->stack[0].index = 0;
|
|
e->stack[0].count = 1;
|
|
e->lang = json_string_value (json_object_get (e->root, "$language"));
|
|
return MUSTACH_OK;
|
|
}
|
|
|
|
|
|
static int
|
|
emituw (void *closure, const char *buffer, size_t size, int escape, FILE *file)
|
|
{
|
|
struct JanssonClosure *e = closure;
|
|
if (! escape)
|
|
e->writecb (file, buffer, size);
|
|
else
|
|
do
|
|
{
|
|
switch (*buffer)
|
|
{
|
|
case '<':
|
|
e->writecb (file, "<", 4);
|
|
break;
|
|
case '>':
|
|
e->writecb (file, ">", 4);
|
|
break;
|
|
case '&':
|
|
e->writecb (file, "&", 5);
|
|
break;
|
|
default:
|
|
e->writecb (file, buffer, 1);
|
|
break;
|
|
}
|
|
buffer++;
|
|
}
|
|
while(--size);
|
|
return MUSTACH_OK;
|
|
}
|
|
|
|
|
|
static int
|
|
enter (void *closure, const char *name)
|
|
{
|
|
struct JanssonClosure *e = closure;
|
|
json_t *o = find (e, name);
|
|
if (++e->depth >= MUSTACH_MAX_DEPTH)
|
|
return MUSTACH_ERROR_TOO_DEEP;
|
|
|
|
if (json_is_object (o))
|
|
{
|
|
if (e->found_iter)
|
|
{
|
|
void *iter = json_object_iter (o);
|
|
if (NULL == iter)
|
|
{
|
|
e->depth--;
|
|
return 0;
|
|
}
|
|
e->stack[e->depth].is_objiter = 1;
|
|
e->stack[e->depth].iter = iter;
|
|
e->stack[e->depth].obj = json_object_iter_value (iter);
|
|
e->stack[e->depth].cont = o;
|
|
}
|
|
else
|
|
{
|
|
e->stack[e->depth].is_objiter = 0;
|
|
e->stack[e->depth].obj = o;
|
|
e->stack[e->depth].cont = o;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
if (json_is_array (o))
|
|
{
|
|
unsigned int size = json_array_size (o);
|
|
if (size == 0)
|
|
{
|
|
e->depth--;
|
|
return 0;
|
|
}
|
|
e->stack[e->depth].count = size;
|
|
e->stack[e->depth].cont = o;
|
|
e->stack[e->depth].obj = json_array_get (o, 0);
|
|
e->stack[e->depth].index = 0;
|
|
e->stack[e->depth].is_objiter = 0;
|
|
return 1;
|
|
}
|
|
|
|
e->depth--;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
next (void *closure)
|
|
{
|
|
struct JanssonClosure *e = closure;
|
|
struct Context *ctx;
|
|
if (e->depth <= 0)
|
|
return MUSTACH_ERROR_CLOSING;
|
|
ctx = &e->stack[e->depth];
|
|
if (ctx->is_objiter)
|
|
{
|
|
ctx->iter = json_object_iter_next (ctx->obj, ctx->iter);
|
|
if (NULL == ctx->iter)
|
|
return 0;
|
|
ctx->obj = json_object_iter_value (ctx->iter);
|
|
return 1;
|
|
}
|
|
ctx->index++;
|
|
if (ctx->index >= ctx->count)
|
|
return 0;
|
|
ctx->obj = json_array_get (ctx->cont, ctx->index);
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int
|
|
leave (void *closure)
|
|
{
|
|
struct JanssonClosure *e = closure;
|
|
if (e->depth <= 0)
|
|
return MUSTACH_ERROR_CLOSING;
|
|
e->depth--;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
freecb (void *v)
|
|
{
|
|
free (v);
|
|
}
|
|
|
|
|
|
static int
|
|
get (void *closure, const char *name, struct mustach_sbuf *sbuf)
|
|
{
|
|
struct JanssonClosure *e = closure;
|
|
json_t *obj;
|
|
|
|
if ( (0 == strcmp (name, "*") ) &&
|
|
(e->stack[e->depth].is_objiter) )
|
|
{
|
|
sbuf->value = json_object_iter_key (e->stack[e->depth].iter);
|
|
return MUSTACH_OK;
|
|
}
|
|
obj = find (e, name);
|
|
if (NULL != obj)
|
|
{
|
|
switch (e->found_bang)
|
|
{
|
|
case BANG_I18N:
|
|
case BANG_NONE:
|
|
{
|
|
const char *s = json_string_value (obj);
|
|
if (NULL != s)
|
|
{
|
|
sbuf->value = s;
|
|
return MUSTACH_OK;
|
|
}
|
|
}
|
|
break;
|
|
case BANG_STRINGIFY:
|
|
sbuf->value = json_dumps (obj, JSON_INDENT (2));
|
|
sbuf->freecb = freecb;
|
|
return MUSTACH_OK;
|
|
case BANG_AMOUNT_DECIMAL:
|
|
{
|
|
char *s;
|
|
char *c;
|
|
if (! json_is_string (obj))
|
|
break;
|
|
s = GNUNET_strdup (json_string_value (obj));
|
|
c = strchr (s, ':');
|
|
if (NULL != c)
|
|
*c = 0;
|
|
sbuf->value = s;
|
|
sbuf->freecb = freecb;
|
|
return MUSTACH_OK;
|
|
}
|
|
break;
|
|
case BANG_AMOUNT_CURRENCY:
|
|
{
|
|
const char *s;
|
|
if (! json_is_string (obj))
|
|
break;
|
|
s = json_string_value (obj);
|
|
s = strchr (s, ':');
|
|
if (NULL == s)
|
|
break;
|
|
sbuf->value = s + 1;
|
|
return MUSTACH_OK;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
sbuf->value = "";
|
|
return MUSTACH_OK;
|
|
}
|
|
|
|
|
|
static struct mustach_itf itf = {
|
|
.start = start,
|
|
.put = NULL,
|
|
.enter = enter,
|
|
.next = next,
|
|
.leave = leave,
|
|
.partial = NULL,
|
|
.get = get,
|
|
.emit = NULL,
|
|
.stop = NULL
|
|
};
|
|
|
|
static struct mustach_itf itfuw = {
|
|
.start = start,
|
|
.put = NULL,
|
|
.enter = enter,
|
|
.next = next,
|
|
.leave = leave,
|
|
.partial = NULL,
|
|
.get = get,
|
|
.emit = emituw,
|
|
.stop = NULL
|
|
};
|
|
|
|
int
|
|
fmustach_jansson (const char *template, json_t *root, FILE *file)
|
|
{
|
|
struct JanssonClosure e = { 0 };
|
|
e.root = root;
|
|
return fmustach (template, &itf, &e, file);
|
|
}
|
|
|
|
|
|
int
|
|
fdmustach_jansson (const char *template, json_t *root, int fd)
|
|
{
|
|
struct JanssonClosure e = { 0 };
|
|
e.root = root;
|
|
return fdmustach (template, &itf, &e, fd);
|
|
}
|
|
|
|
|
|
int
|
|
mustach_jansson (const char *template, json_t *root, char **result,
|
|
size_t *size)
|
|
{
|
|
struct JanssonClosure e = { 0 };
|
|
e.root = root;
|
|
e.writecb = NULL;
|
|
return mustach (template, &itf, &e, result, size);
|
|
}
|
|
|
|
|
|
int
|
|
umustach_jansson (const char *template, json_t *root, mustach_jansson_write_cb
|
|
writecb, void *closure)
|
|
{
|
|
struct JanssonClosure e = { 0 };
|
|
e.root = root;
|
|
e.writecb = writecb;
|
|
return fmustach (template, &itfuw, &e, closure);
|
|
}
|