1535 lines
40 KiB
C
1535 lines
40 KiB
C
/* Copyright 2018, UCAR/Unidata.
|
|
See the COPYRIGHT file for more information.
|
|
*/
|
|
|
|
#ifndef NETCDF_JSON_H
|
|
#define NETCDF_JSON_H 1
|
|
|
|
#ifndef OPTEXPORT
|
|
#ifdef NETCDF_JSON_H
|
|
#define OPTEXPORT static
|
|
#else /*!NETCDF_JSON_H*/
|
|
#ifdef _WIN32
|
|
#define OPTEXPORT __declspec(dllexport)
|
|
#else /*!WIN32*/
|
|
#define OPTEXPORT extern
|
|
#endif /*WIN32*/
|
|
#endif /*NETCDF_JSON_H*/
|
|
#endif /*OPTEXPORT*/
|
|
|
|
/**************************************************/
|
|
|
|
/* Return codes */
|
|
#define NCJ_OK 0 /* must equal NC_NOERR in netcdf.h */
|
|
#define NCJ_ERR (-1) /* must equal NC_ERROR in netcdf.h */
|
|
|
|
/* Json object sorts (note use of term sort rather than e.g. type or discriminant) */
|
|
#define NCJ_UNDEF 0
|
|
#define NCJ_STRING 1
|
|
#define NCJ_INT 2
|
|
#define NCJ_DOUBLE 3
|
|
#define NCJ_BOOLEAN 4
|
|
#define NCJ_DICT 5
|
|
#define NCJ_ARRAY 6
|
|
#define NCJ_NULL 7
|
|
|
|
#define NCJ_NSORTS 8
|
|
|
|
/* Dump/text/unparse flags */
|
|
#define NCJFLAG_NONE 0
|
|
#define NCJFLAG_INDENTED 1
|
|
|
|
/* Define a struct to store primitive values as unquoted
|
|
strings. The sort will provide more info. Do not bother with
|
|
a union since the amount of saved space is minimal.
|
|
*/
|
|
|
|
typedef struct NCjson {
|
|
int sort; /* of this object */
|
|
char* string; /* sort != DICT|ARRAY */
|
|
struct NCjlist {
|
|
size_t alloc;
|
|
size_t len;
|
|
struct NCjson** contents;
|
|
} list; /* sort == DICT|ARRAY */
|
|
} NCjson;
|
|
|
|
/* Structure to hold result of convertinf one json sort to value of another type;
|
|
don't use union so we can know when to reclaim sval
|
|
*/
|
|
struct NCJconst {int bval; long long ival; double dval; char* sval;};
|
|
|
|
/**************************************************/
|
|
/* Extended API */
|
|
|
|
/* Return NCJ_OK if ok else NCJ_ERR */
|
|
|
|
#if defined(__cplusplus)
|
|
extern "C" {
|
|
#endif /*__cplusplus*/
|
|
|
|
/* Parse a string to NCjson*/
|
|
OPTEXPORT int NCJparse(const char* text, unsigned flags, NCjson** jsonp);
|
|
|
|
/* Parse a counted string to NCjson*/
|
|
OPTEXPORT int NCJparsen(size_t len, const char* text, unsigned flags, NCjson** jsonp);
|
|
|
|
/* Reclaim a JSON tree */
|
|
OPTEXPORT void NCJreclaim(NCjson* json);
|
|
|
|
/* Create a new JSON node of a given sort */
|
|
OPTEXPORT int NCJnew(int sort, NCjson** objectp);
|
|
|
|
/* Create new json object with given string content */
|
|
OPTEXPORT int NCJnewstring(int sort, const char* value, NCjson** jsonp);
|
|
|
|
/* Create new json object with given counted string content */
|
|
OPTEXPORT int NCJnewstringn(int sort, size_t len, const char* value, NCjson** jsonp);
|
|
|
|
/* Get dict key value by name */
|
|
OPTEXPORT int NCJdictget(const NCjson* dict, const char* key, const NCjson** valuep);
|
|
|
|
/* Functional version of NCJdictget */
|
|
OPTEXPORT NCjson* NCJdictlookup(const NCjson* dict, const char* key);
|
|
|
|
/* Convert one json sort to value of another type; don't use union so we can know when to reclaim sval */
|
|
OPTEXPORT int NCJcvt(const NCjson* value, int outsort, struct NCJconst* output);
|
|
|
|
/* Append an atomic value to an array or dict object. */
|
|
OPTEXPORT int NCJaddstring(NCjson* json, int sort, const char* s);
|
|
|
|
/* Append value to an array or dict object. */
|
|
OPTEXPORT int NCJappend(NCjson* object, NCjson* value);
|
|
|
|
/* Append string value to an array or dict object. */
|
|
OPTEXPORT int NCJappendstring(NCjson* object, int sort, const char* s);
|
|
|
|
/* Append int value to an array or dict object. */
|
|
OPTEXPORT int NCJappendint(NCjson* object, long long n);
|
|
|
|
/* Insert (string)key-(NCjson*)value pair into a dict object. key will be copied; jvalue will not */
|
|
OPTEXPORT int NCJinsert(NCjson* object, const char* key, NCjson* jvalue);
|
|
|
|
/* Insert key-value pair into a dict object. key and value will be copied */
|
|
OPTEXPORT int NCJinsertstring(NCjson* object, const char* key, const char* value);
|
|
|
|
/* Overwrite key-value pair in a dict object. Act like NCJinsert if key not found */
|
|
OPTEXPORT int NCJoverwrite(NCjson* object, const char* key, NCjson* value);
|
|
|
|
/* Insert key-value pair into a dict object. key and value will be copied */
|
|
OPTEXPORT int NCJinsertint(NCjson* object, const char* key, long long n);
|
|
|
|
/* Unparser to convert NCjson object to text in buffer */
|
|
OPTEXPORT int NCJunparse(const NCjson* json, unsigned flags, char** textp);
|
|
|
|
/* Deep clone a json object */
|
|
OPTEXPORT int NCJclone(const NCjson* json, NCjson** clonep);
|
|
|
|
#ifndef NETCDF_JSON_H
|
|
|
|
/* dump NCjson* object to output file */
|
|
OPTEXPORT void NCJdump(const NCjson* json, unsigned flags, FILE*);
|
|
|
|
/* convert NCjson* object to output string */
|
|
OPTEXPORT const char* NCJtotext(const NCjson* json, unsigned flags);
|
|
|
|
/* Sort a dictionary by key */
|
|
OPTEXPORT void NCJdictsort(NCjson* jdict);
|
|
|
|
#endif /*NETCDF_JSON_H*/
|
|
|
|
#if defined(__cplusplus)
|
|
}
|
|
#endif /*__cplusplus*/
|
|
|
|
/* Getters */
|
|
#define NCJsort(x) ((x)==NULL?NCJ_UNDEF:(x)->sort)
|
|
#define NCJstring(x) ((x)==NULL?(char*)(x):(x)->string)
|
|
#define NCJarraylength(x) ((x)==NULL ? 0 : (x)->list.len)
|
|
#define NCJdictlength(x) ((x)==NULL ? 0 : ((x)->list.len) / 2)
|
|
#define NCJcontents(x) ((x)==NULL?(struct NCjson**)(x):(x)->list.contents)
|
|
#define NCJith(x,i) ((x)->list.contents[i])
|
|
#define NCJdictkey(x,i) ((x)->list.contents[(i)*2])
|
|
#define NCJdictvalue(x,i) ((x)->list.contents[((i)*2)+1])
|
|
|
|
/* Setters */
|
|
#define NCJsetsort(x,s) (x)->sort=(s)
|
|
#define NCJsetstring(x,y) (x)->string=(y)
|
|
#define NCJsetcontents(x,c) (x)->list.contents=(c)
|
|
#define NCJsetarraylength(x,l) (x)->list.len=(l)
|
|
#define NCJsetdictlength(x,l) (x)->list.len=((l)*2)
|
|
|
|
/* Misc */
|
|
#define NCJisatomic(j) ((j)->sort != NCJ_ARRAY && (j)->sort != NCJ_DICT && (j)->sort != NCJ_NULL && (j)->sort != NCJ_UNDEF)
|
|
|
|
/**************************************************/
|
|
/* Error detection helper */
|
|
#undef NCJDEBUG
|
|
#ifdef NCJDEBUG
|
|
static int
|
|
NCJBREAKPOINT(int err)
|
|
{
|
|
(void)NCJBREAKPOINT;
|
|
return err;
|
|
}
|
|
#else
|
|
#define NCJBREAKPOINT(err) (err)
|
|
#endif /*NCJDEBUG*/
|
|
#define NCJcheck(expr) do{if((expr) < 0) {stat = NCJBREAKPOINT(NCJ_ERR); goto done;}}while(0)
|
|
|
|
/**************************************************/
|
|
|
|
#endif /*!NETCDF_JSON_H*/ /* Leave the ! as a tag for sed */
|
|
#ifdef NETCDF_JSON_H
|
|
/* Copyright 2018, UCAR/Unidata.
|
|
See the COPYRIGHT file for more information.
|
|
*/
|
|
|
|
/*
|
|
TODO: make utf8 safe
|
|
*/
|
|
|
|
/*
|
|
WARNING:
|
|
If you modify this file,
|
|
then you need to go to
|
|
the include/ directory
|
|
and do the command:
|
|
make netcdf_json.h
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif /*HAVE_CONFIG_H*/
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#undef NCJCATCH
|
|
#undef NCJDEBUG
|
|
#undef NCJTRACE
|
|
|
|
#ifdef NCJCATCH
|
|
/* Warning: do not evaluate err more than once */
|
|
#define NCJTHROW(err) ncjbreakpoint(err)
|
|
static int ncjbreakpoint(int err) {return err;}
|
|
#else /*!NCJCATCH*/
|
|
#define NCJTHROW(err) (err)
|
|
#endif /*NCJCATCH*/
|
|
|
|
/**************************************************/
|
|
|
|
#define NCJ_EOF -2
|
|
|
|
#define NCJ_LBRACKET '['
|
|
#define NCJ_RBRACKET ']'
|
|
#define NCJ_LBRACE '{'
|
|
#define NCJ_RBRACE '}'
|
|
#define NCJ_COLON ':'
|
|
#define NCJ_COMMA ','
|
|
#define NCJ_QUOTE '"'
|
|
#define NCJ_ESCAPE '\\'
|
|
#define NCJ_TAG_TRUE "true"
|
|
#define NCJ_TAG_FALSE "false"
|
|
#define NCJ_TAG_NULL "null"
|
|
|
|
/* JSON_WORD Subsumes Number also */
|
|
#define JSON_WORD "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$+-."
|
|
|
|
#define NCJ_DEFAULTALLOC 16
|
|
|
|
/**************************************************/
|
|
typedef struct NCJparser {
|
|
char* text;
|
|
char* pos;
|
|
size_t yylen; /* |yytext| */
|
|
char* yytext; /* string or word */
|
|
long long num;
|
|
int tf;
|
|
int status; /* NCJ_ERR|NCJ_OK */
|
|
unsigned flags;
|
|
# define NCJ_TRACE 1
|
|
} NCJparser;
|
|
|
|
/* This is used only by the unparser */
|
|
typedef struct NCJbuf {
|
|
size_t len; /* |text|; does not include nul terminator */
|
|
char* text; /* NULL || nul terminated */
|
|
} NCJbuf;
|
|
|
|
/**************************************************/
|
|
|
|
#if defined(_WIN32) && !defined(__MINGW32__)
|
|
#define strdup _strdup
|
|
#define strcasecmp _stricmp
|
|
#else /*!WIN32 || __MINGW32*/
|
|
#include <strings.h>
|
|
#endif /*defined(_WIN32) && !defined(__MINGW32__)*/
|
|
|
|
#ifndef nullfree
|
|
#define nullfree(x) {if(x)free(x);}
|
|
#endif /*nullfree*/
|
|
#ifndef nulldup
|
|
#define nulldup(x) ((x)?strdup(x):(x))
|
|
#endif /*nulldup*/
|
|
|
|
#if defined NCJDEBUG || defined NCJTRACE
|
|
static char* tokenname(int token);
|
|
#endif /*defined NCJDEBUG || defined NCJTRACE*/
|
|
|
|
/**************************************************/
|
|
/* Forward */
|
|
static int NCJparseR(NCJparser* parser, NCjson**);
|
|
static int NCJparseArray(NCJparser* parser, struct NCjlist* array);
|
|
static int NCJparseDict(NCJparser* parser, struct NCjlist* dict);
|
|
static int testbool(const char* word);
|
|
static int testint(const char* word);
|
|
static int testdouble(const char* word);
|
|
static int testnull(const char* word);
|
|
static int NCJlex(NCJparser* parser);
|
|
static int NCJyytext(NCJparser*, char* start, size_t pdlen);
|
|
static void NCJreclaimArray(struct NCjlist*);
|
|
static void NCJreclaimDict(struct NCjlist*);
|
|
static int NCJunescape(NCJparser* parser);
|
|
static char unescape1(char c);
|
|
|
|
static int listappend(struct NCjlist* list, NCjson* element);
|
|
static int listsetalloc(struct NCjlist* list, size_t sz);
|
|
static int listlookup(const struct NCjlist* list, const char* key, size_t* indexp);
|
|
|
|
static int NCJcloneArray(const NCjson* array, NCjson** clonep);
|
|
static int NCJcloneDict(const NCjson* dict, NCjson** clonep);
|
|
|
|
/* These are used only by the unparser */
|
|
static int NCJunparseR(const NCjson* json, NCJbuf* buf, unsigned flags);
|
|
static int bytesappendquoted(NCJbuf* buf, const char* s);
|
|
static int bytesappend(NCJbuf* buf, const char* s);
|
|
static int bytesappendc(NCJbuf* bufp, char c);
|
|
|
|
/* Hide everything for plugins */
|
|
#ifdef NETCDF_JSON_H
|
|
#define OPTSTATIC static
|
|
#else /*!NETCDF_JSON_H*/
|
|
#define OPTSTATIC
|
|
#endif /*NETCDF_JSON_H*/
|
|
|
|
/* List legal nan and infinity names (lower case); keep in strcasecmp sorted order */
|
|
static const char* NANINF[] = {"-infinity","infinity","-infinityf","infinityf","nan","nanf"};
|
|
static const size_t NNANINF = 6;
|
|
|
|
/**************************************************/
|
|
|
|
OPTSTATIC int
|
|
NCJparse(const char* text, unsigned flags, NCjson** jsonp)
|
|
{
|
|
size_t textlen = strlen(text);
|
|
return NCJparsen(textlen,text,flags,jsonp);
|
|
}
|
|
|
|
OPTSTATIC int
|
|
NCJparsen(size_t len, const char* text, unsigned flags, NCjson** jsonp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCJparser* parser = NULL;
|
|
NCjson* json = NULL;
|
|
|
|
parser = calloc(1,sizeof(NCJparser));
|
|
if(parser == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
parser->flags = flags;
|
|
parser->text = (char*)malloc(len+1+1);
|
|
if(parser->text == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
memcpy(parser->text,text,len);
|
|
/* trim trailing whitespace */
|
|
if(len > 0) {
|
|
char* p;
|
|
for(p=parser->text+(len-1);p >= parser->text;p--) {
|
|
if(*p > ' ') break;
|
|
}
|
|
len = (size_t)((p - parser->text) + 1);
|
|
}
|
|
if(len == 0)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
parser->text[len] = '\0';
|
|
parser->text[len+1] = '\0';
|
|
parser->pos = &parser->text[0];
|
|
parser->status = NCJ_OK;
|
|
#ifdef NCJDEBUG
|
|
fprintf(stderr,"json: |%s|\n",parser->text);
|
|
#endif /*NCJDEBUG*/
|
|
if((stat=NCJparseR(parser,&json))==NCJ_ERR) goto done;
|
|
/* Must consume all of the input */
|
|
if(parser->pos != (parser->text+len)) {stat = NCJ_ERR; goto done;}
|
|
*jsonp = json;
|
|
json = NULL;
|
|
|
|
done:
|
|
if(parser != NULL) {
|
|
nullfree(parser->text);
|
|
nullfree(parser->yytext);
|
|
free(parser);
|
|
}
|
|
(void)NCJreclaim(json);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/*
|
|
Simple recursive descent
|
|
intertwined with dict and list parsers.
|
|
|
|
Invariants:
|
|
1. The json argument is provided by caller and filled in by NCJparseR.
|
|
2. Each call pushed back last unconsumed token
|
|
*/
|
|
|
|
static int
|
|
NCJparseR(NCJparser* parser, NCjson** jsonp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
int token = NCJ_UNDEF;
|
|
NCjson* json = NULL;
|
|
|
|
if(jsonp == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if((token = NCJlex(parser)) == NCJ_UNDEF)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
switch (token) {
|
|
case NCJ_EOF:
|
|
break;
|
|
case NCJ_NULL:
|
|
if((stat = NCJnew(NCJ_NULL,&json))==NCJ_ERR) goto done;
|
|
break;
|
|
case NCJ_BOOLEAN:
|
|
if((stat = NCJnew(NCJ_BOOLEAN,&json))==NCJ_ERR) goto done;
|
|
json->string = strdup(parser->yytext);
|
|
break;
|
|
case NCJ_INT:
|
|
if((stat = NCJnew(NCJ_INT,&json))==NCJ_ERR) goto done;
|
|
json->string = strdup(parser->yytext);
|
|
break;
|
|
case NCJ_DOUBLE:
|
|
if((stat = NCJnew(NCJ_DOUBLE,&json))==NCJ_ERR) goto done;
|
|
json->string = strdup(parser->yytext);
|
|
break;
|
|
case NCJ_STRING:
|
|
if((stat = NCJnew(NCJ_STRING,&json))==NCJ_ERR) goto done;
|
|
json->string = strdup(parser->yytext);
|
|
break;
|
|
case NCJ_LBRACE:
|
|
if((stat = NCJnew(NCJ_DICT,&json))==NCJ_ERR) goto done;
|
|
if((stat = NCJparseDict(parser, &json->list))==NCJ_ERR) goto done;
|
|
break;
|
|
case NCJ_LBRACKET:
|
|
if((stat = NCJnew(NCJ_ARRAY,&json))==NCJ_ERR) goto done;
|
|
if((stat = NCJparseArray(parser, &json->list))==NCJ_ERR) goto done;
|
|
break;
|
|
case NCJ_RBRACE: /* We hit end of the dict we are parsing */
|
|
parser->pos--; /* pushback so NCJparseArray will catch */
|
|
json = NULL;
|
|
break;
|
|
case NCJ_RBRACKET:
|
|
parser->pos--; /* pushback so NCJparseDict will catch */
|
|
json = NULL;
|
|
break;
|
|
default:
|
|
stat = NCJTHROW(NCJ_ERR);
|
|
break;
|
|
}
|
|
if(jsonp && json) {*jsonp = json; json = NULL;}
|
|
|
|
done:
|
|
NCJreclaim(json);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
static int
|
|
NCJparseArray(NCJparser* parser, struct NCjlist* arrayp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
int token = NCJ_UNDEF;
|
|
NCjson* element = NULL;
|
|
int stop = 0;
|
|
|
|
/* [ ^e1,e2, ...en] */
|
|
|
|
while(!stop) {
|
|
/* Recurse to get the value ei (might be null) */
|
|
if((stat = NCJparseR(parser,&element))==NCJ_ERR) goto done;
|
|
token = NCJlex(parser); /* Get next token */
|
|
/* Next token should be comma or rbracket */
|
|
switch(token) {
|
|
case NCJ_RBRACKET:
|
|
if(element != NULL) listappend(arrayp,element);
|
|
element = NULL;
|
|
stop = 1;
|
|
break;
|
|
case NCJ_COMMA:
|
|
/* Append the ei to the list */
|
|
if(element == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;} /* error */
|
|
listappend(arrayp,element);
|
|
element = NULL;
|
|
break;
|
|
case NCJ_EOF:
|
|
case NCJ_UNDEF:
|
|
default:
|
|
stat = NCJTHROW(NCJ_ERR);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
if(element != NULL)
|
|
NCJreclaim(element);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
static int
|
|
NCJparseDict(NCJparser* parser, struct NCjlist* dictp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
int token = NCJ_UNDEF;
|
|
NCjson* value = NULL;
|
|
NCjson* key = NULL;
|
|
int stop = 0;
|
|
|
|
/* { ^k1:v1,k2:v2, ...kn:vn] */
|
|
|
|
while(!stop) {
|
|
/* Get the key, which must be a word of some sort */
|
|
token = NCJlex(parser);
|
|
switch(token) {
|
|
case NCJ_STRING:
|
|
case NCJ_BOOLEAN:
|
|
case NCJ_INT: case NCJ_DOUBLE: {
|
|
if((stat=NCJnewstring(token,parser->yytext,&key))==NCJ_ERR) goto done;
|
|
} break;
|
|
case NCJ_RBRACE: /* End of containing Dict */
|
|
stop = 1;
|
|
continue; /* leave loop */
|
|
case NCJ_EOF: case NCJ_UNDEF:
|
|
default:
|
|
stat = NCJTHROW(NCJ_ERR);
|
|
goto done;
|
|
}
|
|
/* Next token must be colon*/
|
|
switch((token = NCJlex(parser))) {
|
|
case NCJ_COLON: break;
|
|
case NCJ_UNDEF: case NCJ_EOF:
|
|
default: stat = NCJTHROW(NCJ_ERR); goto done;
|
|
}
|
|
/* Get the value */
|
|
if((stat = NCJparseR(parser,&value))==NCJ_ERR) goto done;
|
|
/* Next token must be comma or RBRACE */
|
|
switch((token = NCJlex(parser))) {
|
|
case NCJ_RBRACE:
|
|
stop = 1;
|
|
/* fall thru */
|
|
case NCJ_COMMA:
|
|
/* Insert key value into dict: key first, then value */
|
|
listappend(dictp,key);
|
|
key = NULL;
|
|
listappend(dictp,value);
|
|
value = NULL;
|
|
break;
|
|
case NCJ_EOF:
|
|
case NCJ_UNDEF:
|
|
default:
|
|
stat = NCJTHROW(NCJ_ERR);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
if(key != NULL)
|
|
NCJreclaim(key);
|
|
if(value != NULL)
|
|
NCJreclaim(value);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
static int
|
|
NCJlex(NCJparser* parser)
|
|
{
|
|
int token = NCJ_UNDEF;
|
|
char* start;
|
|
size_t count;
|
|
|
|
while(token == 0) { /* avoid need to goto when retrying */
|
|
char c = *parser->pos;
|
|
if(c == '\0') {
|
|
token = NCJ_EOF;
|
|
} else if(c <= ' ' || c == '\177') {/* ignore whitespace */
|
|
parser->pos++;
|
|
continue;
|
|
} else if(c == NCJ_ESCAPE) {
|
|
parser->pos++;
|
|
c = *parser->pos;
|
|
*parser->pos = (char)unescape1(c);
|
|
continue;
|
|
} else if(strchr(JSON_WORD, c) != NULL) {
|
|
start = parser->pos;
|
|
for(;;) {
|
|
c = *parser->pos++;
|
|
if(c == '\0' || strchr(JSON_WORD,c) == NULL) break; /* end of word */
|
|
}
|
|
/* Pushback c */
|
|
parser->pos--;
|
|
count = (size_t)((parser->pos) - start);
|
|
if(NCJyytext(parser,start,count)) goto done;
|
|
/* Discriminate the word string to get the proper sort */
|
|
if(testbool(parser->yytext))
|
|
token = NCJ_BOOLEAN;
|
|
/* do int test first since double subsumes int */
|
|
else if(testint(parser->yytext))
|
|
token = NCJ_INT;
|
|
else if(testdouble(parser->yytext))
|
|
token = NCJ_DOUBLE;
|
|
else if(testnull(parser->yytext))
|
|
token = NCJ_NULL;
|
|
else
|
|
token = NCJ_STRING;
|
|
} else if(c == NCJ_QUOTE) {
|
|
parser->pos++;
|
|
start = parser->pos;
|
|
for(;;) {
|
|
c = *parser->pos++;
|
|
if(c == NCJ_ESCAPE) parser->pos++;
|
|
else if(c == NCJ_QUOTE || c == '\0') break;
|
|
}
|
|
if(c == '\0') {
|
|
parser->status = NCJ_ERR;
|
|
token = NCJ_UNDEF;
|
|
goto done;
|
|
}
|
|
count = (size_t)((parser->pos) - start) - 1; /* -1 for trailing quote */
|
|
if(NCJyytext(parser,start,count)==NCJ_ERR) goto done;
|
|
if(NCJunescape(parser)==NCJ_ERR) goto done;
|
|
token = NCJ_STRING;
|
|
} else { /* single char token */
|
|
if(NCJyytext(parser,parser->pos,1)==NCJ_ERR) goto done;
|
|
token = *parser->pos++;
|
|
}
|
|
#ifdef NCJDEBUG
|
|
fprintf(stderr,"%s(%d): |%s|\n",tokenname(token),token,parser->yytext);
|
|
#endif /*NCJDEBUG*/
|
|
} /*for(;;)*/
|
|
done:
|
|
if(parser->status == NCJ_ERR)
|
|
token = NCJ_UNDEF;
|
|
#ifdef NCJTRACE
|
|
if(parser->flags & NCJ_TRACE) {
|
|
const char* txt = NULL;
|
|
switch(token) {
|
|
case NCJ_STRING: case NCJ_INT: case NCJ_DOUBLE: case NCJ_BOOLEAN: txt = parser->yytext; break;
|
|
default: break;
|
|
}
|
|
fprintf(stderr,">>>> token=%s:'%s'\n",tokenname(token),(txt?txt:""));
|
|
}
|
|
#endif /*NCJTRACE*/
|
|
return token;
|
|
}
|
|
|
|
static int
|
|
testnull(const char* word)
|
|
{
|
|
if(strcasecmp(word,NCJ_TAG_NULL)==0) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
testbool(const char* word)
|
|
{
|
|
if(strcasecmp(word,NCJ_TAG_TRUE)==0
|
|
|| strcasecmp(word,NCJ_TAG_FALSE)==0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
testint(const char* word)
|
|
{
|
|
int ncvt;
|
|
long long i;
|
|
int count = 0;
|
|
/* Try to convert to number */
|
|
ncvt = sscanf(word,"%lld%n",&i,&count);
|
|
return (ncvt == 1 && strlen(word)==((size_t)count) ? 1 : 0);
|
|
}
|
|
|
|
static int
|
|
nancmp(const void* keyp, const void* membpp)
|
|
{
|
|
int cmp;
|
|
const char* key = (const char*)keyp;
|
|
const char** membp = (const char**)membpp;
|
|
cmp = strcasecmp(key,*membp);
|
|
return cmp;
|
|
}
|
|
|
|
static int
|
|
testdouble(const char* word)
|
|
{
|
|
int ncvt;
|
|
double d;
|
|
int count = 0;
|
|
void* pos = NULL;
|
|
|
|
/* Check for Nan and Infinity */
|
|
pos = bsearch(word, NANINF, NNANINF, sizeof(char*), nancmp);
|
|
if(pos != NULL) return 1;
|
|
/* Try to convert to number */
|
|
ncvt = sscanf(word,"%lg%n",&d,&count);
|
|
return (ncvt == 1 && strlen(word)==((size_t)count) ? 1 : 0);
|
|
}
|
|
|
|
static int
|
|
NCJyytext(NCJparser* parser, char* start, size_t pdlen)
|
|
{
|
|
size_t len = (size_t)pdlen;
|
|
if(parser->yytext == NULL) {
|
|
parser->yytext = (char*)malloc(len+1);
|
|
parser->yylen = len;
|
|
} else if(parser->yylen <= len) {
|
|
parser->yytext = (char*) realloc(parser->yytext,len+1);
|
|
parser->yylen = len;
|
|
}
|
|
if(parser->yytext == NULL) return NCJTHROW(NCJ_ERR);
|
|
memcpy(parser->yytext,start,len);
|
|
parser->yytext[len] = '\0';
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
/**************************************************/
|
|
|
|
OPTSTATIC void
|
|
NCJreclaim(NCjson* json)
|
|
{
|
|
if(json == NULL) return;
|
|
switch(json->sort) {
|
|
case NCJ_INT:
|
|
case NCJ_DOUBLE:
|
|
case NCJ_BOOLEAN:
|
|
case NCJ_STRING:
|
|
nullfree(json->string);
|
|
break;
|
|
case NCJ_DICT:
|
|
NCJreclaimDict(&json->list);
|
|
break;
|
|
case NCJ_ARRAY:
|
|
NCJreclaimArray(&json->list);
|
|
break;
|
|
default: break; /* nothing to reclaim */
|
|
}
|
|
free(json);
|
|
}
|
|
|
|
static void
|
|
NCJreclaimArray(struct NCjlist* array)
|
|
{
|
|
size_t i;
|
|
for(i=0;i<array->len;i++) {
|
|
NCJreclaim(array->contents[i]);
|
|
}
|
|
nullfree(array->contents);
|
|
array->contents = NULL;
|
|
array->len = 0;
|
|
}
|
|
|
|
static void
|
|
NCJreclaimDict(struct NCjlist* dict)
|
|
{
|
|
NCJreclaimArray(dict);
|
|
}
|
|
|
|
/**************************************************/
|
|
/* Build Functions */
|
|
|
|
OPTSTATIC int
|
|
NCJnew(int sort, NCjson** objectp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* object = NULL;
|
|
|
|
if((object = (NCjson*)calloc(1,sizeof(NCjson))) == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
NCJsetsort(object,sort);
|
|
switch (sort) {
|
|
case NCJ_INT:
|
|
case NCJ_DOUBLE:
|
|
case NCJ_BOOLEAN:
|
|
case NCJ_STRING:
|
|
case NCJ_NULL:
|
|
break;
|
|
case NCJ_DICT:
|
|
case NCJ_ARRAY:
|
|
break;
|
|
default:
|
|
stat = NCJTHROW(NCJ_ERR);
|
|
goto done;
|
|
}
|
|
if(objectp) {*objectp = object; object = NULL;}
|
|
|
|
done:
|
|
if(stat) NCJreclaim(object);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
OPTSTATIC int
|
|
NCJnewstring(int sort, const char* value, NCjson** jsonp)
|
|
{
|
|
return NCJTHROW(NCJnewstringn(sort,strlen(value),value,jsonp));
|
|
}
|
|
|
|
OPTSTATIC int
|
|
NCJnewstringn(int sort, size_t len, const char* value, NCjson** jsonp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* json = NULL;
|
|
|
|
if(jsonp) *jsonp = NULL;
|
|
if(value == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if((stat = NCJnew(sort,&json))==NCJ_ERR) goto done;
|
|
if((json->string = (char*)malloc(len+1))==NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
memcpy(json->string,value,len);
|
|
json->string[len] = '\0';
|
|
if(jsonp) *jsonp = json;
|
|
json = NULL; /* avoid memory errors */
|
|
done:
|
|
NCJreclaim(json);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
OPTSTATIC int
|
|
NCJdictget(const NCjson* dict, const char* key, const NCjson** jvaluep)
|
|
{
|
|
int stat = NCJ_OK;
|
|
size_t i;
|
|
|
|
if(dict == NULL || dict->sort != NCJ_DICT)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if(jvaluep) {*jvaluep = NULL;}
|
|
for(i=0;i<NCJdictlength(dict);i++) {
|
|
NCjson* jkey = NCJdictkey(dict,i);
|
|
NCjson* jvalue = NCJdictvalue(dict,i);
|
|
if(jkey->string != NULL && strcmp(jkey->string,key)==0) {
|
|
if(jvaluep) {*jvaluep = jvalue;}
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Functional version of NCJdictget */
|
|
OPTSTATIC NCjson*
|
|
NCJdictlookup(const NCjson* dict, const char* key)
|
|
{
|
|
int stat;
|
|
const NCjson* jvalue = NULL;
|
|
stat = NCJdictget(dict,key,&jvalue);
|
|
if(stat != NCJ_OK) jvalue = NULL;
|
|
return jvalue;
|
|
}
|
|
|
|
/* Unescape the text in parser->yytext; can
|
|
do in place because unescaped string will
|
|
always be shorter */
|
|
static int
|
|
NCJunescape(NCJparser* parser)
|
|
{
|
|
char* p = parser->yytext;
|
|
char* q = p;
|
|
char c;
|
|
for(;(c=*p++);) {
|
|
if(c == NCJ_ESCAPE) {
|
|
c = *p++;
|
|
switch (c) {
|
|
case 'b': c = '\b'; break;
|
|
case 'f': c = '\f'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\t'; break;
|
|
case NCJ_QUOTE: break;
|
|
case NCJ_ESCAPE: break;
|
|
default: break;/* technically not Json conformant */
|
|
}
|
|
}
|
|
*q++ = (char)c;
|
|
}
|
|
*q = '\0';
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
/* Unescape a single character */
|
|
static char
|
|
unescape1(char c)
|
|
{
|
|
switch (c) {
|
|
case 'b': c = '\b'; break;
|
|
case 'f': c = '\f'; break;
|
|
case 'n': c = '\n'; break;
|
|
case 'r': c = '\r'; break;
|
|
case 't': c = '\t'; break;
|
|
default: break;/* technically not Json conformant */
|
|
}
|
|
return c;
|
|
}
|
|
|
|
#if defined NCJDEBUG || defined NCJTRACE
|
|
static char*
|
|
tokenname(int token)
|
|
{
|
|
switch (token) {
|
|
case NCJ_STRING: return ("NCJ_STRING");
|
|
case NCJ_INT: return ("NCJ_INT");
|
|
case NCJ_DOUBLE: return ("NCJ_DOUBLE");
|
|
case NCJ_BOOLEAN: return ("NCJ_BOOLEAN");
|
|
case NCJ_DICT: return ("NCJ_DICT");
|
|
case NCJ_ARRAY: return ("NCJ_ARRAY");
|
|
case NCJ_NULL: return ("NCJ_NULL");
|
|
default:
|
|
if(token > ' ' && token <= 127) {
|
|
static char s[4];
|
|
s[0] = '\'';
|
|
s[1] = (char)token;
|
|
s[2] = '\'';
|
|
s[3] = '\0';
|
|
return (s);
|
|
} else
|
|
break;
|
|
}
|
|
return ("NCJ_UNDEF");
|
|
}
|
|
#endif /*defined NCJDEBUG || defined NCJTRACE*/
|
|
|
|
/* Convert a JSON value to an equivalent value of a specified sort */
|
|
OPTSTATIC int
|
|
NCJcvt(const NCjson* jvalue, int outsort, struct NCJconst* output)
|
|
{
|
|
int stat = NCJ_OK;
|
|
|
|
if(output == NULL) goto done;
|
|
|
|
#undef CASE
|
|
#define CASE(t1,t2) ((t1)<<4 | (t2)) /* the shift constant must be larger than log2(NCJ_NSORTS) */
|
|
switch (CASE(jvalue->sort,outsort)) {
|
|
|
|
case CASE(NCJ_BOOLEAN,NCJ_BOOLEAN):
|
|
if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->bval = 0; else output->bval = 1;
|
|
break;
|
|
case CASE(NCJ_BOOLEAN,NCJ_INT):
|
|
if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->ival = 0; else output->ival = 1;
|
|
break;
|
|
case CASE(NCJ_BOOLEAN,NCJ_DOUBLE):
|
|
if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->dval = 0.0; else output->dval = 1.0;
|
|
break;
|
|
case CASE(NCJ_BOOLEAN,NCJ_STRING):
|
|
output->sval = nulldup(jvalue->string);
|
|
break;
|
|
|
|
case CASE(NCJ_INT,NCJ_BOOLEAN):
|
|
sscanf(jvalue->string,"%lld",&output->ival);
|
|
output->bval = (output->ival?1:0);
|
|
break;
|
|
case CASE(NCJ_INT,NCJ_INT):
|
|
sscanf(jvalue->string,"%lld",&output->ival);
|
|
break;
|
|
case CASE(NCJ_INT,NCJ_DOUBLE):
|
|
sscanf(jvalue->string,"%lld",&output->ival);
|
|
output->dval = (double)output->ival;
|
|
break;
|
|
case CASE(NCJ_INT,NCJ_STRING):
|
|
output->sval = nulldup(jvalue->string);
|
|
break;
|
|
|
|
case CASE(NCJ_DOUBLE,NCJ_BOOLEAN):
|
|
sscanf(jvalue->string,"%lf",&output->dval);
|
|
output->bval = (output->dval == 0?0:1);
|
|
break;
|
|
case CASE(NCJ_DOUBLE,NCJ_INT):
|
|
sscanf(jvalue->string,"%lf",&output->dval);
|
|
output->ival = (long long)output->dval;
|
|
break;
|
|
case CASE(NCJ_DOUBLE,NCJ_DOUBLE):
|
|
sscanf(jvalue->string,"%lf",&output->dval);
|
|
break;
|
|
case CASE(NCJ_DOUBLE,NCJ_STRING):
|
|
output->sval = nulldup(jvalue->string);
|
|
break;
|
|
|
|
case CASE(NCJ_STRING,NCJ_BOOLEAN):
|
|
if(strcasecmp(jvalue->string,NCJ_TAG_FALSE)==0) output->bval = 0; else output->bval = 1;
|
|
break;
|
|
case CASE(NCJ_STRING,NCJ_INT):
|
|
sscanf(jvalue->string,"%lld",&output->ival);
|
|
break;
|
|
case CASE(NCJ_STRING,NCJ_DOUBLE):
|
|
sscanf(jvalue->string,"%lf",&output->dval);
|
|
break;
|
|
case CASE(NCJ_STRING,NCJ_STRING):
|
|
output->sval = nulldup(jvalue->string);
|
|
break;
|
|
|
|
default:
|
|
stat = NCJTHROW(NCJ_ERR);
|
|
break;
|
|
}
|
|
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
static int
|
|
listappend(struct NCjlist* list, NCjson* json)
|
|
{
|
|
int stat = NCJ_OK;
|
|
|
|
assert(list->len == 0 || list->contents != NULL);
|
|
if(json == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
/* Make space for two new elements; better than one, but still probably not optimal */
|
|
if((stat = listsetalloc(list,list->len + 2))<0) goto done;
|
|
/* Append the new item */
|
|
list->contents[list->len++] = json;
|
|
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Locate the index of a key in a list of (key,value) pairs.
|
|
@param list pointer to the list
|
|
@param key for which to search
|
|
@param indexp store index of match here
|
|
@return NCJ_OK if key found, NCJ_EOF if not found, NCJ_ERR if error
|
|
*/
|
|
static int
|
|
listlookup(const struct NCjlist* list, const char* key, size_t* indexp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
int i,len,match = -1;
|
|
|
|
if(list == NULL || key == NULL || strlen(key) == 0 || list->len %2 == 1)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
len = (int)list->len; /* => |list| < 2 billion or do */
|
|
for(i=0;i<len;i+=2) {
|
|
const NCjson* jkey = list->contents[i];
|
|
if(jkey != NULL && jkey->string != NULL && strcmp(jkey->string,key)==0) {match = i;break;}
|
|
}
|
|
if(match < 0) {stat = NCJ_EOF;} else {if(indexp) *indexp = (size_t)match;}
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Increase the space available to dict/array.
|
|
Even if alloc is zero, ensure that the object's list alloc is >= 1.
|
|
@param list pointer to the list
|
|
@param alloc increase allocation to this size
|
|
@return NCJ_ERR|NCJ_OK
|
|
*/
|
|
static int
|
|
listsetalloc(struct NCjlist* list, size_t alloc)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson** newcontents = NULL;
|
|
|
|
if(list == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
assert(list->alloc == 0 || list->contents != NULL);
|
|
if(alloc == 0) alloc = 1; /* Guarantee that the list->content is not NULL */
|
|
if(list->alloc >= alloc) goto done;
|
|
/* Since alloc > list->alloc > 0, we need to allocate space */
|
|
if((newcontents=(NCjson**)calloc(alloc,sizeof(NCjson*))) == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
list->alloc = alloc;
|
|
if(list->contents != NULL && list->len > 0) {
|
|
/* Preserve any existing contents */
|
|
memcpy((void*)newcontents,
|
|
(void*)list->contents,
|
|
sizeof(NCjson*)*list->len);
|
|
}
|
|
free(list->contents);
|
|
list->contents = newcontents; newcontents = NULL;
|
|
assert(list->alloc > 0 && list->contents != NULL);
|
|
done:
|
|
if(newcontents != NULL) free(newcontents);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/**************************************************/
|
|
|
|
OPTSTATIC int
|
|
NCJclone(const NCjson* json, NCjson** clonep)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* clone = NULL;
|
|
|
|
if(clonep) *clonep = NULL;
|
|
if(json == NULL) goto done;
|
|
switch(NCJsort(json)) {
|
|
case NCJ_INT:
|
|
case NCJ_DOUBLE:
|
|
case NCJ_BOOLEAN:
|
|
case NCJ_STRING:
|
|
if((stat=NCJnew(NCJsort(json),&clone))==NCJ_ERR) goto done;
|
|
NCJsetstring(clone,strdup(NCJstring(json)));
|
|
if(NCJstring(clone)==NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
break;
|
|
case NCJ_NULL:
|
|
if((stat=NCJnew(NCJsort(json),&clone))==NCJ_ERR) goto done;
|
|
break;
|
|
case NCJ_DICT:
|
|
if((stat=NCJcloneDict(json,&clone))==NCJ_ERR) goto done;
|
|
break;
|
|
case NCJ_ARRAY:
|
|
if((stat=NCJcloneArray(json,&clone))==NCJ_ERR) goto done;
|
|
break;
|
|
default: break; /* nothing to clone */
|
|
}
|
|
done:
|
|
if(stat == NCJ_OK && clonep) {*clonep = clone; clone = NULL;}
|
|
NCJreclaim(clone);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
static int
|
|
NCJcloneArray(const NCjson* array, NCjson** clonep)
|
|
{
|
|
int stat=NCJ_OK;
|
|
size_t i;
|
|
NCjson* clone = NULL;
|
|
if((stat=NCJnew(NCJ_ARRAY,&clone))==NCJ_ERR) goto done;
|
|
if((stat=listsetalloc(&clone->list,array->list.len))<0) goto done;
|
|
for(i=0;i<NCJarraylength(array);i++) {
|
|
NCjson* elem = NCJith(array,i);
|
|
NCjson* elemclone = NULL;
|
|
if((stat=NCJclone(elem,&elemclone))==NCJ_ERR) goto done;
|
|
NCJappend(clone,elemclone);
|
|
}
|
|
done:
|
|
if(stat == NCJ_OK && clonep) {*clonep = clone; clone = NULL;}
|
|
NCJreclaim(clone);
|
|
return stat;
|
|
}
|
|
|
|
static int
|
|
NCJcloneDict(const NCjson* dict, NCjson** clonep)
|
|
{
|
|
int stat=NCJ_OK;
|
|
size_t i;
|
|
NCjson* clone = NULL;
|
|
if((stat=NCJnew(NCJ_DICT,&clone))==NCJ_ERR) goto done;
|
|
if((stat=listsetalloc(&clone->list,dict->list.len))<0) goto done;
|
|
for(i=0;i<NCJarraylength(dict);i++) {
|
|
NCjson* elem = NCJith(dict,i);
|
|
NCjson* elemclone = NULL;
|
|
if((stat=NCJclone(elem,&elemclone))==NCJ_ERR) goto done;
|
|
NCJappend(clone,elemclone);
|
|
}
|
|
done:
|
|
if(stat == NCJ_OK && clonep) {*clonep = clone; clone = NULL;}
|
|
NCJreclaim(clone);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
OPTSTATIC int
|
|
NCJaddstring(NCjson* json, int sort, const char* s)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* jtmp = NULL;
|
|
|
|
if(NCJsort(json) != NCJ_DICT && NCJsort(json) != NCJ_ARRAY)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if((stat = NCJnewstring(sort, s, &jtmp))==NCJ_ERR) goto done;
|
|
if((stat = NCJappend(json,jtmp))==NCJ_ERR) goto done;
|
|
jtmp = NULL;
|
|
|
|
done:
|
|
NCJreclaim(jtmp);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Insert key-value pair into a dict object. key will be strdup'd, jvalue will be used without copying.
|
|
If key already exists, then it's value is overwritten
|
|
*/
|
|
OPTSTATIC int
|
|
NCJinsert(NCjson* jdict, const char* key, NCjson* jvalue)
|
|
{
|
|
int stat = NCJ_OK;
|
|
size_t i;
|
|
NCjson* jkey = NULL;
|
|
NCjson* jprev = NULL;
|
|
int found;
|
|
|
|
if(jdict == NULL
|
|
|| NCJsort(jdict) != NCJ_DICT
|
|
|| key == NULL
|
|
|| jvalue == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
for(found=(-1),i=0;i < NCJdictlength(jdict); i++) {
|
|
jkey = NCJdictkey(jdict,i);
|
|
if (jkey != NULL && strcmp(NCJstring(jkey), key) == 0) {
|
|
found = (int)i;
|
|
break;
|
|
}
|
|
}
|
|
if(found >= 0) {
|
|
jprev = NCJdictvalue(jdict,found);
|
|
// replace existing values for new key
|
|
NCJreclaim(jprev); // free old value
|
|
NCJdictvalue(jdict,found) = jvalue; jvalue = NULL;
|
|
jkey = NULL; /* avoid reclamation */
|
|
} else { /* not found */
|
|
if((stat=listsetalloc(&jdict->list,jdict->list.len+2))<0) goto done;
|
|
NCJcheck(NCJnewstring(NCJ_STRING, key, (NCjson**)&jkey));
|
|
NCJcheck(NCJappend(jdict,jkey)); jkey = NULL;
|
|
NCJcheck(NCJappend(jdict,jvalue)); jvalue = NULL;
|
|
}
|
|
done:
|
|
NCJreclaim(jkey);
|
|
NCJreclaim(jvalue);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Insert key-value pair into a dict object. key will be strdup'd */
|
|
OPTSTATIC int
|
|
NCJinsertstring(NCjson* object, const char* key, const char* value)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* jkey = NULL;
|
|
NCjson* jvalue = NULL;
|
|
if(key == NULL || value == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if((stat = NCJnewstring(NCJ_STRING,key,&jkey))==NCJ_ERR) goto done;
|
|
if((stat = NCJnewstring(NCJ_STRING,value,&jvalue))==NCJ_ERR) goto done;
|
|
if((stat = NCJappend(object,jkey))==NCJ_ERR) goto done;
|
|
if((stat = NCJappend(object,jvalue))==NCJ_ERR) goto done;
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Insert key-value pair into a dict object. key will be strdup'd */
|
|
OPTSTATIC int
|
|
NCJinsertint(NCjson* object, const char* key, long long value)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* jkey = NULL;
|
|
NCjson* jvalue = NULL;
|
|
char digits[64];
|
|
|
|
if(key == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if((stat = NCJnewstring(NCJ_STRING,key,&jkey))==NCJ_ERR) goto done;
|
|
snprintf(digits,sizeof(digits),"%lld",value);
|
|
if((stat = NCJnewstring(NCJ_INT,digits,&jvalue))==NCJ_ERR) goto done;
|
|
listsetalloc(&object->list,object->list.len + 2);
|
|
if((stat = NCJappend(object,jkey))==NCJ_ERR) goto done;
|
|
if((stat = NCJappend(object,jvalue))==NCJ_ERR) goto done;
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Append value to an array or dict object. */
|
|
OPTSTATIC int
|
|
NCJappend(NCjson* object, NCjson* value)
|
|
{
|
|
if(object == NULL || value == NULL)
|
|
return NCJTHROW(NCJ_ERR);
|
|
switch (object->sort) {
|
|
case NCJ_ARRAY:
|
|
case NCJ_DICT:
|
|
listappend(&object->list,value);
|
|
break;
|
|
default:
|
|
return NCJTHROW(NCJ_ERR);
|
|
}
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
/* Append string value to an array or dict object. */
|
|
OPTSTATIC int
|
|
NCJappendstring(NCjson* object, int sort, const char* s)
|
|
{
|
|
NCjson* js = NULL;
|
|
if(object == NULL || s == NULL)
|
|
return NCJTHROW(NCJ_ERR);
|
|
NCJnewstring(sort,s,&js);
|
|
switch (object->sort) {
|
|
case NCJ_ARRAY:
|
|
case NCJ_DICT:
|
|
listappend(&object->list,js);
|
|
break;
|
|
default:
|
|
return NCJTHROW(NCJ_ERR);
|
|
}
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
/* Append int value into an array/dict object. */
|
|
OPTSTATIC int
|
|
NCJappendint(NCjson* object, long long value)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* jvalue = NULL;
|
|
char digits[64];
|
|
|
|
snprintf(digits,sizeof(digits),"%lld",value);
|
|
NCJcheck(NCJnewstring(NCJ_INT,digits,&jvalue));
|
|
NCJcheck(NCJappend(object,jvalue)); jvalue = NULL;
|
|
done:
|
|
NCJreclaim(jvalue);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Overwrite key-value pair in a dict object.
|
|
If key does not exist, then act like NCJinsert().
|
|
*/
|
|
OPTSTATIC int
|
|
NCJoverwrite(NCjson* dict, const char* key, NCjson* jvalue)
|
|
{
|
|
int stat = NCJ_OK;
|
|
size_t index;
|
|
NCjson* jkey = NULL;
|
|
NCjson* oldvalue = NULL;
|
|
|
|
if(dict == NULL
|
|
|| dict->sort != NCJ_DICT
|
|
|| key == NULL
|
|
|| jvalue == NULL) {stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
/* See if key already exists */
|
|
switch(stat=listlookup(&dict->list,key,&index)) {
|
|
case NCJ_OK:
|
|
/* Overwrite value part */
|
|
oldvalue = dict->list.contents[index];
|
|
dict->list.contents[index] = jvalue;
|
|
NCJreclaim(oldvalue);
|
|
break;
|
|
case NCJ_EOF: /* Not found */
|
|
if((stat=listsetalloc(&dict->list,dict->list.len+2))<0) goto done;
|
|
if((stat = NCJappend(dict,jkey))==NCJ_ERR) goto done;
|
|
if((stat = NCJappend(dict,jvalue))==NCJ_ERR) goto done;
|
|
break;
|
|
case NCJ_ERR:
|
|
default: goto done;
|
|
}
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/**************************************************/
|
|
/* Unparser to convert NCjson object to text in buffer */
|
|
|
|
OPTSTATIC int
|
|
NCJunparse(const NCjson* json, unsigned flags, char** textp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCJbuf buf = {0,NULL};
|
|
if((stat = NCJunparseR(json,&buf,flags))==NCJ_ERR)
|
|
goto done;
|
|
if(textp) {*textp = buf.text; buf.text = NULL; buf.len = 0;}
|
|
done:
|
|
nullfree(buf.text);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
static int
|
|
NCJunparseR(const NCjson* json, NCJbuf* buf, unsigned flags)
|
|
{
|
|
int stat = NCJ_OK;
|
|
size_t i;
|
|
|
|
switch (NCJsort(json)) {
|
|
case NCJ_STRING:
|
|
bytesappendquoted(buf,json->string);
|
|
break;
|
|
case NCJ_INT:
|
|
case NCJ_DOUBLE:
|
|
case NCJ_BOOLEAN:
|
|
bytesappend(buf,json->string);
|
|
break;
|
|
case NCJ_DICT:
|
|
bytesappendc(buf,NCJ_LBRACE);
|
|
if(json->list.len > 0 && json->list.contents != NULL) {
|
|
int shortlist = 0;
|
|
for(i=0;!shortlist && i < json->list.len;i+=2) {
|
|
if(i > 0) {bytesappendc(buf,NCJ_COMMA);bytesappendc(buf,' ');};
|
|
NCJunparseR(json->list.contents[i],buf,flags); /* key */
|
|
bytesappendc(buf,NCJ_COLON);
|
|
bytesappendc(buf,' ');
|
|
/* Allow for the possibility of a short dict entry */
|
|
if(json->list.contents[i+1] == NULL) { /* short */
|
|
bytesappendc(buf,'?');
|
|
shortlist = 1;
|
|
} else {
|
|
NCJunparseR(json->list.contents[i+1],buf,flags);
|
|
}
|
|
}
|
|
}
|
|
bytesappendc(buf,NCJ_RBRACE);
|
|
break;
|
|
case NCJ_ARRAY:
|
|
bytesappendc(buf,NCJ_LBRACKET);
|
|
if(json->list.len > 0 && json->list.contents != NULL) {
|
|
for(i=0;i < json->list.len;i++) {
|
|
if(i > 0) bytesappendc(buf,NCJ_COMMA);
|
|
NCJunparseR(json->list.contents[i],buf,flags);
|
|
}
|
|
}
|
|
bytesappendc(buf,NCJ_RBRACKET);
|
|
break;
|
|
case NCJ_NULL:
|
|
bytesappend(buf,"null");
|
|
break;
|
|
default:
|
|
stat = NCJTHROW(NCJ_ERR); goto done;
|
|
}
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* Escape a string and append to buf */
|
|
static int
|
|
escape(const char* text, NCJbuf* buf)
|
|
{
|
|
const char* p = text;
|
|
char c;
|
|
for(;(c=*p++);) {
|
|
char replace = 0;
|
|
switch (c) {
|
|
case '\b': replace = 'b'; break;
|
|
case '\f': replace = 'f'; break;
|
|
case '\n': replace = 'n'; break;
|
|
case '\r': replace = 'r'; break;
|
|
case '\t': replace = 't'; break;
|
|
case NCJ_QUOTE: replace = '\"'; break;
|
|
case NCJ_ESCAPE: replace = '\\'; break;
|
|
default: break;
|
|
}
|
|
if(replace) {
|
|
bytesappendc(buf,NCJ_ESCAPE);
|
|
bytesappendc(buf,replace);
|
|
} else
|
|
bytesappendc(buf,(char)c);
|
|
}
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
static int
|
|
bytesappendquoted(NCJbuf* buf, const char* s)
|
|
{
|
|
bytesappend(buf,"\"");
|
|
escape(s,buf);
|
|
bytesappend(buf,"\"");
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
static int
|
|
bytesappend(NCJbuf* buf, const char* s)
|
|
{
|
|
int stat = NCJ_OK;
|
|
char* newtext = NULL;
|
|
if(buf == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if(s == NULL) s = "";
|
|
if(buf->len == 0) {
|
|
assert(buf->text == NULL);
|
|
buf->text = strdup(s);
|
|
if(buf->text == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
buf->len = strlen(s);
|
|
} else {
|
|
size_t slen = strlen(s);
|
|
size_t newlen = buf->len + slen + 1;
|
|
if((newtext = (char*)malloc(newlen))==NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
strcpy(newtext,buf->text);
|
|
strcat(newtext,s);
|
|
free(buf->text); buf->text = NULL;
|
|
buf->text = newtext; newtext = NULL;
|
|
buf->len = newlen;
|
|
}
|
|
|
|
done:
|
|
nullfree(newtext);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
static int
|
|
bytesappendc(NCJbuf* bufp, const char c)
|
|
{
|
|
char s[2];
|
|
s[0] = c;
|
|
s[1] = '\0';
|
|
return bytesappend(bufp,s);
|
|
}
|
|
|
|
OPTSTATIC const char*
|
|
NCJtotext(const NCjson* json, unsigned flags)
|
|
{
|
|
static char outtext[4096];
|
|
char* text = NULL;
|
|
NC_UNUSED(flags);
|
|
if(json == NULL) {strcpy(outtext,"<null>"); goto done;}
|
|
(void)NCJunparse(json,0,&text);
|
|
strncpy(outtext,text,sizeof(outtext));
|
|
nullfree(text);
|
|
done:
|
|
return outtext;
|
|
}
|
|
|
|
OPTSTATIC void
|
|
NCJdump(const NCjson* json, unsigned flags, FILE* out)
|
|
{
|
|
const char* text = NCJtotext(json,flags);
|
|
if(out == NULL) out = stderr;
|
|
fprintf(out,"%s\n",text);
|
|
fflush(out);
|
|
}
|
|
|
|
static int
|
|
pairsort(const void* a, const void* b)
|
|
{
|
|
const NCjson** j1 = (const NCjson**)a;
|
|
const NCjson** j2 = (const NCjson**)b;
|
|
return strcmp(NCJstring(*j1),NCJstring(*j2));
|
|
}
|
|
|
|
OPTSTATIC void
|
|
NCJdictsort(NCjson* jdict)
|
|
{
|
|
assert(NCJsort(jdict) == NCJ_DICT);
|
|
qsort((void*)NCJcontents(jdict),NCJdictlength(jdict),2*sizeof(NCjson*),pairsort);
|
|
}
|
|
|
|
/* Hack to avoid static unused warning */
|
|
static void
|
|
netcdf_supresswarnings(void)
|
|
{
|
|
void* ignore = NULL;
|
|
ignore = (void*)netcdf_supresswarnings;
|
|
ignore = (void*)NCJparse;
|
|
ignore = (void*)NCJparsen;
|
|
ignore = (void*)NCJreclaim;
|
|
ignore = (void*)NCJnew;
|
|
ignore = (void*)NCJnewstring;
|
|
ignore = (void*)NCJnewstringn;
|
|
ignore = (void*)NCJdictget;
|
|
ignore = (void*)NCJdictlookup;
|
|
ignore = (void*)NCJcvt;
|
|
ignore = (void*)NCJaddstring;
|
|
ignore = (void*)NCJappend;
|
|
ignore = (void*)NCJappendstring;
|
|
ignore = (void*)NCJappendint;
|
|
ignore = (void*)NCJinsert;
|
|
ignore = (void*)NCJinsertstring;
|
|
ignore = (void*)NCJinsertint;
|
|
ignore = (void*)NCJoverwrite;
|
|
ignore = (void*)NCJdictsort;
|
|
ignore = (void*)NCJdump;
|
|
(void)ignore;
|
|
}
|
|
#endif /*NETCDF_JSON_H*/
|