/* grecs - Gray's Extensible Configuration System
Copyright (C) 2007-2016 Sergey Poznyakoff
Grecs is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
Grecs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with Grecs. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include "grecs.h"
#include "grecs/json.h"
static void
json_writez(struct json_format *fmt, char const *str)
{
size_t len = strlen(str);
fmt->write(fmt->data, str, len);
}
static void
json_writec(struct json_format *fmt, char c)
{
fmt->write(fmt->data, &c, 1);
}
static void
json_indent(struct json_format *fmt, size_t level)
{
level *= fmt->indent;
while (level--)
json_writec(fmt, ' ');
}
static void
json_format_delim(struct json_format *fmt, size_t level)
{
json_writec(fmt, ',');
if (fmt->indent) {
json_writec(fmt, '\n');
json_indent(fmt, level);
} else
json_writec(fmt, ' ');
}
static int
escape(char c, char *o)
{
static char transtab[] = "\\\\\"\"b\bf\fn\nr\rt\t";
char *p;
for (p = transtab; *p; p += 2) {
if (p[1] == c) {
*o = p[0];
return 0;
}
}
return -1;
}
static void
json_format_string(struct json_format *fmt, const char *s)
{
json_writec(fmt, '"');
for (; *s; s++) {
char c;
if (!escape(*s, &c)) {
json_writec(fmt, '\\');
json_writec(fmt, c);
} else
json_writec(fmt, *s);
}
json_writec(fmt, '"');
}
static void
json_format_number(struct json_format *fmt, double n)
{
char *buffer = NULL;
size_t size = 0;
if (fmt->precision == -1)
grecs_asprintf(&buffer, &size, "%e", n);
else
grecs_asprintf(&buffer, &size, "%.*f", fmt->precision, n);
json_writez(fmt, buffer);
free(buffer);
}
static int
collect_keypairs(void *sym, void *data)
{
struct json_pair *p = sym;
struct json_pair ***kp = data;
**kp = p;
++*kp;
return 0;
}
static void json_formatter(struct json_format *fmt, struct json_value *obj,
size_t level);
static int
keypair_cmp_name(const void *a, const void *b)
{
struct json_pair const * const *kpa = a;
struct json_pair const * const *kpb = b;
return strcmp((*kpa)->k, (*kpb)->k);
}
static void
json_format_obj(struct json_format *fmt, struct json_value *obj, size_t level)
{
size_t count, i;
struct json_pair **keypairs, **kp;
count = grecs_symtab_count(obj->v.o);
keypairs = grecs_calloc(count, sizeof(*keypairs));
kp = keypairs;
grecs_symtab_foreach(obj->v.o, collect_keypairs, &kp);
qsort(keypairs, count, sizeof(*keypairs), keypair_cmp_name);
json_writec(fmt, '{');
if (count) {
if (fmt->indent)
json_writec(fmt, '\n');
for (i = 0; i < count; i++) {
(i ? json_format_delim : json_indent)(fmt, level);
json_format_string(fmt, keypairs[i]->k);
json_writec(fmt, ':');
if (fmt->indent)
json_writec(fmt, ' ');
json_formatter(fmt, keypairs[i]->v, level);
}
if (fmt->indent) {
json_writec(fmt, '\n');
json_indent(fmt, level-1);
}
}
json_writec(fmt, '}');
grecs_free(keypairs);
}
static void
json_format_array(struct json_format *fmt, struct json_value *obj,
size_t level)
{
size_t i;
json_array_flatten(obj);
json_writec(fmt, '[');
if (obj->v.a->oc) {
if (fmt->indent)
json_writec(fmt, '\n');
for (i = 0; i < obj->v.a->oc; i++) {
(i ? json_format_delim : json_indent)(fmt, level);
json_formatter(fmt, obj->v.a->ov[i], level);
}
if (fmt->indent) {
json_writec(fmt, '\n');
json_indent(fmt, level-1);
}
}
json_writec(fmt, ']');
}
static void
json_formatter(struct json_format *fmt, struct json_value *obj, size_t level)
{
if (!obj) {
json_writez(fmt, "null");
return;
}
++level;
switch (obj->type) {
case json_null:
json_writez(fmt, "null");
break;
case json_bool:
json_writez(fmt, obj->v.b ? "true" : "false");
break;
case json_number:
json_format_number(fmt, obj->v.n);
break;
case json_string:
json_format_string(fmt, obj->v.s);
break;
case json_arr:
json_format_array(fmt, obj, level);
break;
case json_object:
json_format_obj(fmt, obj, level);
break;
}
}
void
json_format_value(struct json_value *obj, struct json_format *fmt)
{
json_formatter(fmt, obj, 0);
}