libfastjson/json_print.c

581 lines
17 KiB
C

/*
* Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
* Michael Clark <michael@metaparadigm.com>
* Copyright (c) 2009 Hewlett-Packard Development Company, L.P.
* Copyright (c) 2015 Rainer Gerhards
* Copyright (c) 2016 Copernica BV
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See COPYING for details.
*
*/
#include "config.h"
/* this is a work-around until we manage to fix configure.ac */
#pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#ifdef HAVE_STDARG_H
# include <stdarg.h>
#else /* !HAVE_STDARG_H */
# error Not enough var arg support!
#endif /* HAVE_STDARG_H */
#include "json_object.h"
#include "json_object_private.h"
#include "json_object_iterator.h"
#if !defined(HAVE_SNPRINTF)
# error You do not have snprintf on your system.
#endif /* HAVE_SNPRINTF */
#if !defined(HAVE_VASPRINTF)
/* CAW: compliant version of vasprintf */
/* Note: on OpenCSW, we have vasprintf() inside the headers, but not inside the lib.
* So we need to use a different name, else we get issues with redefinitions. We
* we solve this by using the macro below, which just renames the function BUT
* does not affect the (variadic) arguments.
* rgerhards, 2017-04-11
*/
#define vasprintf rs_vasprintf
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
static int rs_vasprintf(char **buf, const char *fmt, va_list ap)
{
int chars;
char *b;
static char _T_emptybuffer = '\0';
if(!buf) { return -1; }
/* CAW: RAWR! We have to hope to god here that vsnprintf doesn't overwrite
our buffer like on some 64bit sun systems.... but hey, its time to move on */
chars = vsnprintf(&_T_emptybuffer, 0, fmt, ap)+1;
if(chars < 0) { chars *= -1; } /* CAW: old glibc versions have this problem */
b = (char*)malloc(sizeof(char)*chars);
if(!b) { return -1; }
if((chars = vsprintf(b, fmt, ap)) < 0) {
free(b);
} else {
*buf = b;
}
return chars;
}
#pragma GCC diagnostic pop
#endif /* !HAVE_VASPRINTF */
/**
* Internal structure that we use for buffering the print output
*/
struct buffer {
char *buffer;
size_t size;
size_t filled;
fjson_write_fn *overflow;
void *ptr;
};
/**
* Internal method to flush the buffer
* @param buffer
* @return size_t
*/
static size_t buffer_flush(struct buffer *buffer)
{
// call the user-supplied overflow function
size_t result = buffer->overflow(buffer->ptr, buffer->buffer, buffer->filled);
// buffer is empty now
buffer->filled = 0;
// done
return result;
}
/**
* Internal method to append data to the buffer
* @param buffer
* @param data
* @param size
* @return size_t
*/
static size_t buffer_append(struct buffer *buffer, const char *data, size_t size)
{
// return value
size_t result = 0;
// is the data to big to fit in the buffer?
if (buffer->filled + size > buffer->size)
{
// flush current buffer
if (buffer->filled > 0) result += buffer_flush(buffer);
// does it still not fit? then we pass it to the callback immediately
if (size > buffer->size) return result + buffer->overflow(buffer->ptr, data, size);
}
// append to the buffer
memcpy(buffer->buffer + buffer->filled, data, size);
// update buffer size
buffer->filled += size;
// done
return result;
}
/**
* Internal method to printf() into the buffer
* @param buffer
* @param format
* @param ...
* @return size_t
*/
__attribute__((__format__(__printf__, 2, 3)))
static size_t buffer_printf(struct buffer *buffer, const char *format, ...)
{
// return value
size_t result = 0;
// variables used in this function
va_list arguments;
char *tmp;
int size;
// make sure we have sufficient room in our buffer
if (buffer->size - buffer->filled < 32) result += buffer_flush(buffer);
// initialize varargs
va_start(arguments, format);
// write to the buffer (note the extra char for the extra null that is written by vsnprintf())
size = vsnprintf(buffer->buffer + buffer->filled, buffer->size - buffer->filled - 1, format, arguments);
// clean up varargs (it is not possible to reuse the vararg arguments later on,
// the have to be reset and possible reinitialized later on)
va_end(arguments);
// was this all successful?
if (size >= 0 && size < (int)(buffer->size - buffer->filled))
{
// this was a major success
buffer->filled += size;
}
else if (size > 0 && size < (int)buffer->size)
{
// there was not enough room in the buffer, but it would have been enough if
// we would have been able to use the entire buffer, so we reset the buffer,
// and retry the whole procedure
result += buffer_flush(buffer);
// buffer is empty now, we can retry, start with the vararg initialization
va_start(arguments, format);
// format into the buffer, again
buffer->size += vsnprintf(buffer->buffer + buffer->filled,
buffer->size - buffer->filled - 1, format, arguments);
// clean up varargs
va_end(arguments);
}
else
{
// initialize varargs
va_start(arguments, format);
// our own buffer is not big enough to fit the text, we are going to use
// a dynamically allocated buffer using vasprintf(), init varargs first
va_start(arguments, format);
// use dynamically allocated vasprintf() call
size = vasprintf(&tmp, format, arguments);
// clean up varargs
va_end(arguments);
// was this a success?
if (size > 0) result += buffer_append(buffer, tmp, size);
// deallocate the memory
if (size >= 0) free(tmp);
}
// done
return result;
}
/* Forward declaration of the write function */
static size_t write(struct fjson_object *jso, int level, int flags, struct buffer *buffer);
/**
* helper for accessing the optimized string data component in fjson_object
* @param jso
* @return
*/
static const char *get_string_component(struct fjson_object *jso)
{
return (jso->o.c_string.len < LEN_DIRECT_STRING_DATA) ?
jso->o.c_string.str.data : jso->o.c_string.str.ptr;
}
/**
* string escaping
*
* String escaping is a surprisingly performance intense operation.
* I spent many hours in the profiler, and the root problem seems
* to be that there is no easy way to detect the character classes
* that need to be escaped, where the root cause is that these
* characters are spread all over the ascii table. I tried
* several approaches, including call tables, re-structuring
* the case condition, different types of if conditions and
* reordering the if conditions. What worked out best is this:
* The regular case is that a character must not be escaped. So
* we want to process that as fast as possible. In order to
* detect this as quickly as possible, we have a lookup table
* that tells us if a char needs escaping ("needsEscape", below).
* This table has a spot for each ascii code. Note that it uses
* chars, because anything larger causes worse cache operation
* and anything smaller requires bit indexing and masking
* operations, which are also comparatively costly. So plain
* chars work best. What we then do is a single lookup into the
* table to detect if we need to escape a character. If we need,
* we go into the depth of actual escape detection. But if we
* do NOT need to escape, we just quickly advance the index
* and are done with that char. Note that it may look like the
* extra table lookup costs performance, but right the contrary
* is the case. We get amore than 30% performance increase due
* to it (compared to the latest version of the code that did not
* do the lookups).
* rgerhards@adiscon.com, 2015-11-18
* using now external char_needsEscape array. -- rgerhards, 2016-11-30
*/
extern const char char_needsEscape[256];
/**
* Function to escape a string
* @param str the string to be escaped
* @param buffer the internal buffer to write to
* @return size_t number of bytes written
*/
static size_t escape(const char *str, struct buffer *buffer)
{
size_t result = 0;
const char *start_offset = str;
while(1) { /* broken below on 0-byte */
if(char_needsEscape[*((unsigned char*)str)]) {
if(*str == '\0') break;
if(str != start_offset) result += buffer_append(buffer, start_offset, str - start_offset);
switch(*str) {
case '\b': result += buffer_append(buffer, "\\b", 2); break;
case '\n': result += buffer_append(buffer, "\\n", 2); break;
case '\r': result += buffer_append(buffer, "\\r", 2); break;
case '\t': result += buffer_append(buffer, "\\t", 2); break;
case '\f': result += buffer_append(buffer, "\\f", 2); break;
case '"': result += buffer_append(buffer, "\\\"", 2); break;
case '\\': result += buffer_append(buffer, "\\\\", 2); break;
case '/': result += buffer_append(buffer, "\\/", 2); break;
default:
result += buffer_printf(buffer, "\\u00%c%c",
fjson_hex_chars[*str >> 4], fjson_hex_chars[*str & 0xf]);
break;
}
start_offset = ++str;
} else
++str;
}
if(str != start_offset) result += buffer_append(buffer, start_offset, str - start_offset);
return result;
}
/* add indentation */
static size_t indent(int level, int flags, struct buffer *buffer)
{
// result variable, and loop counter
size_t result = 0;
int i;
// skip if pretty-printing is not needed
if (!(flags & FJSON_TO_STRING_PRETTY)) return 0;
// iterate to add the spaces
for (i = 0; i < level; ++i)
{
// write a tab or two spaces
if (flags & FJSON_TO_STRING_PRETTY_TAB) result += buffer_append(buffer, "\t", 1);
else result += buffer_append(buffer, " ", 2);
}
// done
return result;
}
/* write a json object */
static size_t write_object(struct fjson_object* jso, int level, int flags, struct buffer *buffer)
{
int had_children = 0;
size_t result = 0;
result += buffer_append(buffer, "{" /*}*/, 1);
if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1);
struct fjson_object_iterator it = fjson_object_iter_begin(jso);
struct fjson_object_iterator itEnd = fjson_object_iter_end(jso);
while (!fjson_object_iter_equal(&it, &itEnd)) {
if (had_children)
{
result += buffer_append(buffer, ",", 1);
if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1);
}
had_children = 1;
if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, " ", 1);
result += indent(level+1, flags, buffer);
result += buffer_append(buffer, "\"", 1);
result += escape(fjson_object_iter_peek_name(&it), buffer);
if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, "\": ", 3);
else result += buffer_append(buffer, "\":", 2);
result += write(fjson_object_iter_peek_value(&it), level+1, flags, buffer);
fjson_object_iter_next(&it);
}
if (flags & FJSON_TO_STRING_PRETTY)
{
if (had_children) result += buffer_append(buffer, "\n", 1);
result += indent(level, flags, buffer);
}
if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, /*{*/ " }", 2);
else result += buffer_append(buffer, /*{*/ "}", 1);
return result;
}
/* write a json boolean */
static size_t write_boolean(struct fjson_object* jso, struct buffer *buffer)
{
if (jso->o.c_boolean) return buffer_append(buffer, "true", 4);
else return buffer_append(buffer, "false", 5);
}
/* write a json int */
static size_t write_int(struct fjson_object* jso, struct buffer *buffer)
{
// printf into the buffer
return buffer_printf(buffer, "%" PRId64, jso->o.c_int64);
}
/* write a json floating point */
static size_t write_double(struct fjson_object* jso, int flags, struct buffer *buffer)
{
// return value for the function
size_t result = 0;
// helper functions to fix the output
char *buf, *p, *q;
// needed for modf()
double dummy;
// if the original value is set, we reuse that
if (jso->o.c_double.source) return buffer_append(buffer, jso->o.c_double.source, strlen(jso->o.c_double.source));
/* Although JSON RFC does not support
* NaN or Infinity as numeric values
* ECMA 262 section 9.8.1 defines
* how to handle these cases as strings
*/
if(isnan(jso->o.c_double.value)) return buffer_append(buffer, "NaN", 3);
if(isinf(jso->o.c_double.value)) return buffer_printf(buffer, jso->o.c_double.value > 0 ? "Infinity" : "-Infinity");
// store the beginning of the buffer (this is where buffer_printf() will most likely write)
buf = buffer->buffer + buffer->filled;
// write to the buffer
result = buffer_printf(buffer, (modf(jso->o.c_double.value, &dummy)==0)?"%.17g.0":"%.17g", jso->o.c_double.value);
// if the buffer got flushed
if (buffer->buffer + buffer->filled < buf) buf = buffer->buffer;
// if localization stuff caused "," to be generated instead of "."
// @todo is there not a nicer way to work around that???
p = strchr(buf, ',');
if (p) {
*p = '.';
} else {
p = strchr(buf, '.');
}
// remove trailing zero's
if (p && (flags & FJSON_TO_STRING_NOZERO)) {
/* last useful digit, always keep 1 zero */
p++;
for (q=p ; *q ; q++) {
if (*q!='0') p=q;
}
/* drop trailing zeroes */
buffer->filled = p - buffer->buffer;
}
// done
return result;
}
/* write a json string */
static size_t write_string(struct fjson_object* jso, struct buffer *buffer)
{
return buffer_append(buffer, "\"", 1) + escape(get_string_component(jso), buffer) + buffer_append(buffer, "\"", 1);
}
/* write a json array */
static size_t write_array(struct fjson_object* jso, int level, int flags, struct buffer *buffer)
{
int had_children = 0;
int ii;
size_t result = 0;
result += buffer_append(buffer, "[", 1);
if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1);
for(ii=0; ii < fjson_object_array_length(jso); ii++)
{
if (had_children)
{
result += buffer_append(buffer, ",", 1);
if (flags & FJSON_TO_STRING_PRETTY) result += buffer_append(buffer, "\n", 1);
}
had_children = 1;
if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, " ", 1);
result += indent(level + 1, flags, buffer);
result += write(fjson_object_array_get_idx(jso, ii), level+1, flags, buffer);
}
if (flags & FJSON_TO_STRING_PRETTY)
{
if (had_children) result += buffer_append(buffer, "\n", 1);
result += indent(level, flags, buffer);
}
if (flags & FJSON_TO_STRING_SPACED) result += buffer_append(buffer, " ]", 2);
else result += buffer_append(buffer, "]", 1);
return result;
}
/* write a json value */
static size_t write(struct fjson_object *jso, int level, int flags, struct buffer *buffer)
{
// if object is not set
if (!jso) return buffer_append(buffer, "null", 4);
// check type
switch(jso->o_type) {
case fjson_type_null: return buffer_append(buffer, "null", 4);
case fjson_type_boolean: return write_boolean(jso, buffer);
case fjson_type_double: return write_double(jso, flags, buffer);
case fjson_type_int: return write_int(jso, buffer);
case fjson_type_object: return write_object(jso, level, flags, buffer);
case fjson_type_array: return write_array(jso, level, flags, buffer);
case fjson_type_string: return write_string(jso, buffer);
default: return 0;
}
}
/* wrapper around fwrite() that has the same signature as fjson_write_fn */
static size_t fwrite_wrapper(void *ptr, const char *buffer, size_t size)
{
return fwrite(buffer, 1, size, ptr);
}
/* dummy output function that does not output, but is used to calculate the size */
static size_t calculate(void __attribute__((unused)) *ptr, const char __attribute__((unused)) *buffer, size_t size)
{
return size;
}
/* extended dump to which the helper buffer can be passed */
size_t fjson_object_dump_buffered(struct fjson_object *jso, int flags, char *temp,
size_t size, fjson_write_fn *func, void *ptr)
{
// construct a buffer
struct buffer object;
// initialize the properties
object.buffer = temp;
object.size = size;
object.filled = 0;
object.overflow = func;
object.ptr = ptr;
// write the value
size_t result = write(jso, 0, flags, &object);
// ready if buffer is now empty
if (object.size == 0) return result;
// flush the buffer
return result + buffer_flush(&object);
}
/* extended dump function to string */
size_t fjson_object_dump_ext(struct fjson_object *jso, int flags, fjson_write_fn *func, void *ptr)
{
// create a local 1k buffer on the stack
char buffer[1024];
// pass on to the other function
return fjson_object_dump_buffered(jso, flags, buffer, 1024, func, ptr);
}
/* more simple write function */
size_t fjson_object_dump(struct fjson_object *jso, fjson_write_fn *func, void *ptr)
{
// write the value
return fjson_object_dump_ext(jso, FJSON_TO_STRING_SPACED, func, ptr);
}
/* extended function to calculate the size */
size_t fjson_object_size_ext(struct fjson_object *jso, int flags)
{
// write the value with a dummy function (this is a simple implementation that
// can later be optimized in a pure size-calculating function)
return fjson_object_dump_ext(jso, flags, &calculate, NULL);
}
/* function to calculate the size */
size_t fjson_object_size(struct fjson_object *jso)
{
// write the value with a dummy function (this is a simple implementation that
// can later be optimized in a pure size-calculating function)
return fjson_object_dump(jso, &calculate, NULL);
}
/* write to a file* */
size_t fjson_object_write(struct fjson_object *obj, FILE *fp)
{
return fjson_object_dump_ext(obj, FJSON_TO_STRING_SPACED, fwrite_wrapper, fp);
}
/* write to a file with custom output flags */
size_t fjson_object_write_ext(struct fjson_object *obj, int flags, FILE *fp)
{
return fjson_object_dump_ext(obj, flags, fwrite_wrapper, fp);
}