mirror of
https://github.com/Unidata/netcdf-c.git
synced 2024-12-21 08:39:46 +08:00
11fe00ea05
Filter support has three goals: 1. Use the existing HDF5 filter implementations, 2. Allow filter metadata to be stored in the NumCodecs metadata format used by Zarr, 3. Allow filters to be used even when HDF5 is disabled Detailed usage directions are define in docs/filters.md. For now, the existing filter API is left in place. So filters are defined using ''nc_def_var_filter'' using the HDF5 style where the id and parameters are unsigned integers. This is a big change since filters affect many parts of the code. In the following, the terms "compressor" and "filter" and "codec" are generally used synonomously. ### Filter-Related Changes: * In order to support dynamic loading of shared filter libraries, a new library was added in the libncpoco directory; it helps to isolate dynamic loading across multiple platforms. * Provide a json parsing library for use by plugins; this is created by merging libdispatch/ncjson.c with include/ncjson.h. * Add a new _Codecs attribute to allow clients to see what codecs are being used; let ncdump -s print it out. * Provide special headers to help support compilation of HDF5 filters when HDF5 is not enabled: netcdf_filter_hdf5_build.h and netcdf_filter_build.h. * Add a number of new test to test the new nczarr filters. * Let ncgen parse _Codecs attribute, although it is ignored. ### Plugin directory changes: * Add support for the Blosc compressor; this is essential because it is the most common compressor used in Zarr datasets. This also necessitated adding a CMake FindBlosc.cmake file * Add NCZarr support for the big-four filters provided by HDF5: shuffle, fletcher32, deflate (zlib), and szip * Add a Codec defaulter (see docs/filters.md) for the big four filters. * Make plugins work with windows by properly adding __declspec declaration. ### Misc. Non-Filter Changes * Replace most uses of USE_NETCDF4 (deprecated) with USE_HDF5. * Improve support for caching * More fixes for path conversion code * Fix misc. memory leaks * Add new utility -- ncdump/ncpathcvt -- that does more or less the same thing as cygpath. * Add a number of new test to test the non-filter fixes. * Update the parsers * Convert most instances of '#ifdef _MSC_VER' to '#ifdef _WIN32'
1144 lines
29 KiB
C
1144 lines
29 KiB
C
/* Copyright 2018, UCAR/Unidata.
|
|
See the COPYRIGHT file for more information.
|
|
*/
|
|
|
|
#ifndef NETCDF_JSON_H
|
|
#define NETCDF_JSON_H 1
|
|
|
|
#ifndef DLLEXPORT
|
|
#ifdef _WIN32
|
|
#define DLLEXPORT __declspec(dllexport)
|
|
#else
|
|
#define DLLEXPORT
|
|
#endif
|
|
#endif
|
|
|
|
/**************************************************/
|
|
/* 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
|
|
|
|
/* No flags are currently defined, but the argument is a placeholder */
|
|
|
|
/* 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 {
|
|
int 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 0 if ok else -1 */
|
|
|
|
#if defined(__cplusplus)
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* Parse a JSON string */
|
|
DLLEXPORT int NCJparse(const char* text, unsigned flags, NCjson** jsonp);
|
|
|
|
/* Reclaim a JSON tree */
|
|
DLLEXPORT extern void NCJreclaim(NCjson* json);
|
|
|
|
/* Create a new JSON node of a given sort */
|
|
DLLEXPORT extern int NCJnew(int sort, NCjson** objectp);
|
|
|
|
/* Create new json object with given string content */
|
|
DLLEXPORT extern int NCJnewstring(int sort, const char* value, NCjson** jsonp);
|
|
|
|
/* Create new json object with given counted string content */
|
|
DLLEXPORT extern int NCJnewstringn(int sort, size_t len, const char* value, NCjson** jsonp);
|
|
|
|
/* Get dict key value by name */
|
|
DLLEXPORT extern int NCJdictget(const NCjson* dict, const char* key, NCjson** valuep);
|
|
|
|
/* Convert one json sort to value of another type; don't use union so we can know when to reclaim sval */
|
|
DLLEXPORT extern int NCJcvt(const NCjson* value, int outsort, struct NCJconst* output);
|
|
|
|
#ifndef NETCDF_JSON_H
|
|
|
|
/* Insert an atomic value to an array or dict object. */
|
|
DLLEXPORT int NCJaddstring(NCjson* json, int sort, const char* s);
|
|
|
|
/* Append value to an array or dict object. */
|
|
DLLEXPORT extern int NCJappend(NCjson* object, NCjson* value);
|
|
|
|
/* Insert key-value pair into a dict object. key will be copied */
|
|
DLLEXPORT extern int NCJinsert(NCjson* object, char* key, NCjson* value);
|
|
|
|
/* Unparser to convert NCjson object to text in buffer */
|
|
DLLEXPORT extern int NCJunparse(const NCjson* json, unsigned flags, char** textp);
|
|
|
|
/* Deep clone a json object */
|
|
DLLEXPORT extern int NCJclone(const NCjson* json, NCjson** clonep);
|
|
|
|
/* dump NCjson* object to output file */
|
|
DLLEXPORT extern void NCJdump(const NCjson* json, unsigned flags, FILE*);
|
|
#endif
|
|
|
|
#if defined(__cplusplus)
|
|
}
|
|
#endif
|
|
|
|
/* Getters */
|
|
#define NCJsort(x) ((x)->sort)
|
|
#define NCJstring(x) ((x)->string)
|
|
#define NCJlength(x) ((x)==NULL ? 0 : (x)->list.len)
|
|
#define NCJcontents(x) ((x)->list.contents)
|
|
#define NCJith(x,i) ((x)->list.contents[i])
|
|
|
|
/* Setters */
|
|
#define NCJsetsort(x,s) (x)->sort=(s)
|
|
#define NCJsetstring(x,y) (x)->string=(y)
|
|
#define NCJsetcontents(x,c) (x)->list.contents=(c)
|
|
#define NCJsetlength(x,l) (x)->list.len=(l)
|
|
|
|
/* Misc */
|
|
#define NCJisatomic(j) ((j)->sort != NCJ_ARRAY && (j)->sort != NCJ_DICT && (j)->sort != NCJ_NULL && (j)->sort != NCJ_UNDEF)
|
|
|
|
/**************************************************/
|
|
|
|
#endif /*NETCDF_JSON_H*/
|
|
|
|
/* Copyright 2018, UCAR/Unidata.
|
|
See the COPYRIGHT file for more information.
|
|
*/
|
|
|
|
/*
|
|
TODO: make utf8 safe
|
|
*/
|
|
|
|
#define NCJSON_INTERNAL
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
|
|
#undef NCJDEBUG
|
|
#ifdef NCJDEBUG
|
|
static int ncjbreakpoint(int err) {return err;}
|
|
#define NCJTHROW(err) ((err)==NCJ_ERR?ncjbreakpoint(err):(err))
|
|
#else
|
|
#define NCJTHROW(err) (err)
|
|
#endif
|
|
|
|
/**************************************************/
|
|
#define NCJ_OK 0
|
|
#define NCJ_ERR (-1)
|
|
|
|
#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_$+-."
|
|
|
|
/**************************************************/
|
|
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 */
|
|
} NCJparser;
|
|
|
|
typedef struct NCJbuf {
|
|
int len; /* |text|; does not include nul terminator */
|
|
char* text; /* NULL || nul terminated */
|
|
} NCJbuf;
|
|
|
|
/**************************************************/
|
|
|
|
#ifdef _WIN32
|
|
#define strdup _strdup
|
|
#define strcasecmp _stricmp
|
|
#else
|
|
#include <strings.h>
|
|
#endif
|
|
|
|
#ifndef nullfree
|
|
#define nullfree(x) {if(x)free(x);}
|
|
#endif
|
|
#ifndef nulldup
|
|
#define nulldup(x) ((x)?strdup(x):(x))
|
|
#endif
|
|
|
|
#ifdef NCJDEBUG
|
|
static char* tokenname(int token);
|
|
#endif
|
|
|
|
/**************************************************/
|
|
/* 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 int listappend(struct NCjlist* list, NCjson* element);
|
|
|
|
#ifndef NETCDF_JSON_H
|
|
static int NCJcloneArray(const NCjson* array, NCjson** clonep);
|
|
static int NCJcloneDict(const NCjson* dict, NCjson** clonep);
|
|
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, const char c);
|
|
#endif
|
|
|
|
/**************************************************/
|
|
|
|
int
|
|
NCJparse(const char* text, unsigned flags, NCjson** jsonp)
|
|
{
|
|
int stat = NCJ_OK;
|
|
size_t len;
|
|
NCJparser* parser = NULL;
|
|
NCjson* json = NULL;
|
|
|
|
/* Need at least 1 character of input */
|
|
if(text == NULL || text[0] == '\0')
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if(jsonp == NULL) goto done;
|
|
parser = calloc(1,sizeof(NCJparser));
|
|
if(parser == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
len = strlen(text);
|
|
parser->text = (char*)malloc(len+1+1);
|
|
if(parser->text == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
strcpy(parser->text,text);
|
|
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
|
|
if((stat=NCJparseR(parser,&json))==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 c;
|
|
int token = NCJ_UNDEF;
|
|
char* start;
|
|
size_t count;
|
|
|
|
while(token == 0) { /* avoid need to goto when retrying */
|
|
c = *parser->pos;
|
|
if(c == '\0') {
|
|
token = NCJ_EOF;
|
|
} else if(c <= ' ' || c == '\177') {
|
|
parser->pos++;
|
|
continue; /* ignore whitespace */
|
|
} 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 if not whitespace */
|
|
parser->pos--;
|
|
count = ((parser->pos) - start);
|
|
if(NCJyytext(parser,start,count)) goto done;
|
|
/* Discriminate the word string to get the proper sort */
|
|
if(testbool(parser->yytext) == NCJ_OK)
|
|
token = NCJ_BOOLEAN;
|
|
/* do int test first since double subsumes int */
|
|
else if(testint(parser->yytext) == NCJ_OK)
|
|
token = NCJ_INT;
|
|
else if(testdouble(parser->yytext) == NCJ_OK)
|
|
token = NCJ_DOUBLE;
|
|
else if(testnull(parser->yytext) == NCJ_OK)
|
|
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 = ((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
|
|
} /*for(;;)*/
|
|
done:
|
|
if(parser->status == NCJ_ERR)
|
|
token = NCJ_UNDEF;
|
|
return token;
|
|
}
|
|
|
|
static int
|
|
testnull(const char* word)
|
|
{
|
|
if(strcasecmp(word,NCJ_TAG_NULL)==0)
|
|
return NCJTHROW(NCJ_OK);
|
|
return NCJTHROW(NCJ_ERR);
|
|
}
|
|
|
|
static int
|
|
testbool(const char* word)
|
|
{
|
|
if(strcasecmp(word,NCJ_TAG_TRUE)==0
|
|
|| strcasecmp(word,NCJ_TAG_FALSE)==0)
|
|
return NCJTHROW(NCJ_OK);
|
|
return NCJTHROW(NCJ_ERR);
|
|
}
|
|
|
|
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 NCJTHROW((ncvt == 1 && strlen(word)==count ? NCJ_OK : NCJ_ERR));
|
|
}
|
|
|
|
static int
|
|
testdouble(const char* word)
|
|
{
|
|
int ncvt;
|
|
double d;
|
|
int count = 0;
|
|
/* Check for Nan and Infinity */
|
|
if(strcasecmp("nan",word)==0) return NCJTHROW(NCJ_OK);
|
|
if(strcasecmp("infinity",word)==0) return NCJTHROW(NCJ_OK);
|
|
if(strcasecmp("-infinity",word)==0) return NCJTHROW(NCJ_OK);
|
|
/* Allow the XXXf versions as well */
|
|
if(strcasecmp("nanf",word)==0) return NCJTHROW(NCJ_OK);
|
|
if(strcasecmp("infinityf",word)==0) return NCJTHROW(NCJ_OK);
|
|
if(strcasecmp("-infinityf",word)==0) return NCJTHROW(NCJ_OK);
|
|
/* Try to convert to number */
|
|
ncvt = sscanf(word,"%lg%n",&d,&count);
|
|
return NCJTHROW((ncvt == 1 && strlen(word)==count ? NCJ_OK : NCJ_ERR));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/**************************************************/
|
|
|
|
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)
|
|
{
|
|
int i;
|
|
for(i=0;i<array->len;i++) {
|
|
NCJreclaim(array->contents[i]);
|
|
}
|
|
nullfree(array->contents);
|
|
array->contents = NULL;
|
|
}
|
|
|
|
static void
|
|
NCJreclaimDict(struct NCjlist* dict)
|
|
{
|
|
NCJreclaimArray(dict);
|
|
}
|
|
|
|
/**************************************************/
|
|
/* Build Functions */
|
|
|
|
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);
|
|
}
|
|
|
|
int
|
|
NCJnewstring(int sort, const char* value, NCjson** jsonp)
|
|
{
|
|
return NCJTHROW(NCJnewstringn(sort,strlen(value),value,jsonp));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int
|
|
NCJdictget(const NCjson* dict, const char* key, NCjson** valuep)
|
|
{
|
|
int i,stat = NCJ_OK;
|
|
|
|
if(dict == NULL || dict->sort != NCJ_DICT)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if(valuep) {*valuep = NULL;}
|
|
for(i=0;i<NCJlength(dict);i+=2) {
|
|
NCjson* jkey = NCJith(dict,i);
|
|
if(jkey->string != NULL && strcmp(jkey->string,key)==0) {
|
|
if(valuep) {*valuep = NCJith(dict,i+1); break;}
|
|
}
|
|
}
|
|
|
|
done:
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/* 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;
|
|
int 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: c = c; break;
|
|
case NCJ_ESCAPE: c = c; break;
|
|
default: c = c; break;/* technically not Json conformant */
|
|
}
|
|
}
|
|
*q++ = c;
|
|
}
|
|
*q = '\0';
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
#ifdef NCJDEBUG
|
|
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
|
|
|
|
/* Convert a JSON value to an equivalent value of a specified sort */
|
|
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,"%lldd",&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;
|
|
NCjson** newcontents = NULL;
|
|
|
|
assert(list->len == 0 || list->contents != NULL);
|
|
if(json == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if(list->len == 0) {
|
|
nullfree(list->contents);
|
|
list->contents = (NCjson**)calloc(2,sizeof(NCjson*));
|
|
if(list->contents == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
list->contents[0] = json;
|
|
list->len++;
|
|
} else {
|
|
if((newcontents = (NCjson**)calloc((2*list->len)+1,sizeof(NCjson*)))==NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
memcpy(newcontents,list->contents,list->len*sizeof(NCjson*));
|
|
newcontents[list->len] = json;
|
|
list->len++;
|
|
free(list->contents);
|
|
list->contents = newcontents; newcontents = NULL;
|
|
}
|
|
|
|
done:
|
|
nullfree(newcontents);
|
|
return NCJTHROW(stat);
|
|
}
|
|
|
|
/**************************************************/
|
|
|
|
#ifndef NETCDF_JSON_H
|
|
|
|
int
|
|
NCJclone(const NCjson* json, NCjson** clonep)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* clone = 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;
|
|
if((NCJstring(clone) = strdup(NCJstring(json))) == 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 i, stat=NCJ_OK;
|
|
NCjson* clone = NULL;
|
|
if((stat=NCJnew(NCJ_ARRAY,&clone))==NCJ_ERR) goto done;
|
|
for(i=0;i<NCJlength(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 i, stat=NCJ_OK;
|
|
NCjson* clone = NULL;
|
|
if((stat=NCJnew(NCJ_DICT,&clone))==NCJ_ERR) goto done;
|
|
for(i=0;i<NCJlength(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);
|
|
}
|
|
|
|
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 */
|
|
int
|
|
NCJinsert(NCjson* object, char* key, NCjson* jvalue)
|
|
{
|
|
int stat = NCJ_OK;
|
|
NCjson* jkey = NULL;
|
|
if(object == NULL || object->sort != NCJ_DICT || key == NULL || jvalue == NULL)
|
|
{stat = NCJTHROW(NCJ_ERR); goto done;}
|
|
if((stat = NCJnewstring(NCJ_STRING,key,&jkey))==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);
|
|
}
|
|
|
|
/* Append value to an array or dict object. */
|
|
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);
|
|
}
|
|
|
|
/**************************************************/
|
|
/* Unparser to convert NCjson object to text in buffer */
|
|
|
|
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;
|
|
int 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);
|
|
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;
|
|
int 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,c);
|
|
}
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
static int
|
|
bytesappendquoted(NCJbuf* buf, const char* s)
|
|
{
|
|
bytesappend(buf,"\"");
|
|
escape(s,buf);
|
|
bytesappend(buf,"\"");
|
|
return NCJTHROW(NCJ_OK);
|
|
}
|
|
|
|
void
|
|
NCJdump(const NCjson* json, unsigned flags, FILE* out)
|
|
{
|
|
char* text = NULL;
|
|
(void)NCJunparse(json,0,&text);
|
|
if(out == NULL) out = stderr;
|
|
fprintf(out,"%s\n",text);
|
|
fflush(out);
|
|
nullfree(text);
|
|
}
|
|
|
|
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);
|
|
}
|
|
#endif /*!NETCDF_JSON_H*/
|