switch from internal jReadWrite to external
This commit is contained in:
parent
9358549667
commit
fb0fc0a30a
|
|
@ -13,13 +13,10 @@ idf_component_register(
|
|||
"src/MQTT.c"
|
||||
"src/MQTTSysHandler.c"
|
||||
"src/OTA.c"
|
||||
"extlibs/jRead.c"
|
||||
"extlibs/jWrite.c"
|
||||
|
||||
INCLUDE_DIRS "."
|
||||
"include"
|
||||
"src"
|
||||
"extlibs"
|
||||
|
||||
REQUIRES nvs_flash
|
||||
libespfs
|
||||
|
|
@ -30,4 +27,5 @@ idf_component_register(
|
|||
esp_modem
|
||||
esp_https_ota
|
||||
app_update
|
||||
jReadWrite
|
||||
)
|
||||
784
extlibs/jRead.c
784
extlibs/jRead.c
|
|
@ -1,784 +0,0 @@
|
|||
// jRead.cpp
|
||||
// Version 1v6
|
||||
//
|
||||
// jRead - an in-place JSON element reader
|
||||
// =======================================
|
||||
//
|
||||
// Instead of parsing JSON into some structure, this maintains the input JSON as unaltered text
|
||||
// and allows queries to be made on it directly.
|
||||
//
|
||||
// e.g. with the simple JSON:
|
||||
// {
|
||||
// "astring":"This is a string",
|
||||
// "anumber":42,
|
||||
// "myarray":[ "one", 2, {"description":"element 3"}, null ],
|
||||
// "yesno":true,
|
||||
// "HowMany":"1234",
|
||||
// "foo":null
|
||||
// }
|
||||
//
|
||||
// calling:
|
||||
// jRead( json, "{'myarray'[0", &jElem );
|
||||
//
|
||||
// would return:
|
||||
// jElem.dataType= JREAD_STRING;
|
||||
// jElem.elements= 1
|
||||
// jElem.bytelen= 3
|
||||
// jElem.pValue -> "one"
|
||||
//
|
||||
// or you could call the helper functions:
|
||||
// jRead_string( json, "{'astring'", destString, MAXLEN );
|
||||
// jRead_int( json, "{'anumber'", &myint );
|
||||
// jRead_string( json, "{'myarray'[3", destString, MAXLEN );
|
||||
// etc.
|
||||
//
|
||||
// Note that the helper functions do type coersion and always return a value
|
||||
// (on error an empty string is returned or value of zero etc.)
|
||||
//
|
||||
// The query string simply defines the route to the required data item
|
||||
// as an arbitary list of object or array specifiers:
|
||||
// object element= "{'keyname'"
|
||||
// array element= "[INDEX"
|
||||
//
|
||||
// The jRead() function fills a jReadElement structure to describe the located element
|
||||
// this can be used to locate any element, not just terminal values
|
||||
// e.g.
|
||||
// jRead( json, "{'myarray'", &jElem );
|
||||
//
|
||||
// in this case jElem would contain:
|
||||
// jElem.dataType= JSON_ARRAY
|
||||
// jElem.elements= 4
|
||||
// jElem.bytelen= 46
|
||||
// jElem.pValue -> [ "one", 2, {"descripton":"element 3"}, null ] ...
|
||||
//
|
||||
// allowing jRead to be called again on the array:
|
||||
// e.g.
|
||||
// jRead( jElem.pValue, "[3", &jElem ); // get 4th element - the null value
|
||||
//
|
||||
// .oO! see main.c runExamples() for a whole bunch of examples !Oo.
|
||||
// -------------------------------------------------------
|
||||
//
|
||||
// Note that jRead never modifies the source JSON and does not allocate any memory.
|
||||
// i.e. elements are returned as pointer and length into the source text.
|
||||
//
|
||||
// Functions
|
||||
// =========
|
||||
// Main JSON reader:
|
||||
// int jRead( char * JsonSource, char *query, jReadElement &pResult );
|
||||
//
|
||||
// Extended function using query parameters for indexing:
|
||||
// int jRead( char * JsonSource, char *query, jReadElement &pResult, int *queryParams );
|
||||
//
|
||||
// Function to step thru JSON arrays instead of indexing:
|
||||
// char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult );
|
||||
//
|
||||
// Optional Helper functions:
|
||||
// long jRead_long( char *pJson, char *pQuery );
|
||||
// int jRead_int( char *pJson, char *pQuery );
|
||||
// double jRead_double( char *pJson, char *pQuery );
|
||||
// int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen );
|
||||
//
|
||||
// Optional String output Functions
|
||||
// char * jReadTypeToString( int dataType ); // string describes dataType
|
||||
// char * jReadErrorToString( int error ); // string descibes error code
|
||||
//
|
||||
// *NEW* in 1v2
|
||||
// - "{NUMBER" returns the "key" value at that index within an object
|
||||
// - jReadParam() adds queryParams which can be used as indexes into arrays (or into
|
||||
// objects to return key values) by specifying '*' in the query string
|
||||
// e.g. jReadParam( pJson, "[*", &result, &index )
|
||||
// *NEW in 1v4
|
||||
// - fixed a couple of error return values
|
||||
// - added #define JREAD_DOUBLE_QUOTE_IN_QUERY
|
||||
// *NEW* in 1v5 (11mar2015)
|
||||
// - fixed null ptr if '[*' used when null param passed
|
||||
// *NEW* in 1v6 (24sep2016)
|
||||
// - fixed handling of empty arrays and objects
|
||||
//
|
||||
// TonyWilk, 24sep2016
|
||||
// mail at tonywilk . co .uk
|
||||
//
|
||||
// License: "Free as in You Owe Me a Beer"
|
||||
// - actually, since some people really worry about licenses, you are free to apply
|
||||
// whatever licence you want.
|
||||
//
|
||||
// Note: jRead_atol() and jRead_atof() are modified from original routines
|
||||
// fast_atol() and fast_atof() 09-May-2009 Tom Van Baak (tvb) www.LeapSecond.com
|
||||
//
|
||||
// You may want to replace the use of jRead_atol() and jRead_atof() in helper functions
|
||||
// of your own. Especially note that my atof does not handle exponents.
|
||||
//
|
||||
//
|
||||
#include <jRead.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
// By default we use single quote in query strings so it's a lot easier
|
||||
// to type in code i.e. "{'key'" instead of "{\"key\""
|
||||
//
|
||||
#ifdef JREAD_DOUBLE_QUOTE_IN_QUERY
|
||||
#define QUERY_QUOTE '\"'
|
||||
#else
|
||||
#define QUERY_QUOTE '\''
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------
|
||||
// Internal Functions
|
||||
|
||||
char * jReadSkipWhitespace( char *sp );
|
||||
char * jReadFindTok( char *sp, int *tokType );
|
||||
char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote );
|
||||
int jReadTextLen( char *pJson );
|
||||
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 );
|
||||
char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex );
|
||||
char * jReadCountArray( char *pJson, struct jReadElement *pResult );
|
||||
char * jRead_atoi( char *p, unsigned int *result );
|
||||
char * jRead_atol( char *p, long *result );
|
||||
char * jRead_atof( char *p, double *result);
|
||||
|
||||
//=======================================================
|
||||
|
||||
char *jReadSkipWhitespace( char *sp )
|
||||
{
|
||||
while( (*sp != '\0') && (*sp <= ' ') )
|
||||
sp++;
|
||||
return sp;
|
||||
};
|
||||
|
||||
|
||||
// Find start of a token
|
||||
// - returns pointer to start of next token or element
|
||||
// returns type via tokType
|
||||
//
|
||||
char *jReadFindTok( char *sp, int *tokType )
|
||||
{
|
||||
char c;
|
||||
sp= jReadSkipWhitespace(sp);
|
||||
c= *sp;
|
||||
if( c == '\0' ) *tokType= JREAD_EOL;
|
||||
else if((c == '"') || (c == QUERY_QUOTE))*tokType= JREAD_STRING;
|
||||
else if((c >= '0') && (c <= '9')) *tokType= JREAD_NUMBER;
|
||||
else if( c == '-') *tokType= JREAD_NUMBER;
|
||||
else if( c == '{') *tokType= JREAD_OBJECT;
|
||||
else if( c == '[') *tokType= JREAD_ARRAY;
|
||||
else if( c == '}') *tokType= JREAD_EOBJECT;
|
||||
else if( c == ']') *tokType= JREAD_EARRAY;
|
||||
else if((c == 't') || (c == 'f')) *tokType= JREAD_BOOL;
|
||||
else if( c == 'n') *tokType= JREAD_NULL;
|
||||
else if( c == ':') *tokType= JREAD_COLON;
|
||||
else if( c == ',') *tokType= JREAD_COMMA;
|
||||
else if( c == '*') *tokType= JREAD_QPARAM;
|
||||
else *tokType= JREAD_ERROR;
|
||||
return sp;
|
||||
};
|
||||
|
||||
// jReadGetString
|
||||
// - assumes next element is "string" which may include "\" sequences
|
||||
// - returns pointer to -------------^
|
||||
// - pElem contains result ( JREAD_STRING, length, pointer to string)
|
||||
// - pass quote = '"' for Json, quote = '\'' for query scanning
|
||||
//
|
||||
// returns: pointer into pJson after the string (char after the " terminator)
|
||||
// pElem contains pointer and length of string (or dataType=JREAD_ERROR)
|
||||
//
|
||||
char * jReadGetString( char *pJson, struct jReadElement *pElem, char quote )
|
||||
{
|
||||
short skipch;
|
||||
pElem->dataType= JREAD_ERROR;
|
||||
pElem->elements= 1;
|
||||
pElem->bytelen= 0;
|
||||
pJson= jReadSkipWhitespace( pJson );
|
||||
if( *pJson == quote )
|
||||
{
|
||||
pJson++;
|
||||
pElem->pValue= pJson; // -> start of actual string
|
||||
pElem->bytelen=0;
|
||||
skipch= 0;
|
||||
while( *pJson != '\0' )
|
||||
{
|
||||
if( skipch )
|
||||
skipch= 0;
|
||||
else if( *pJson == '\\' ) // "\" sequence
|
||||
skipch= 1;
|
||||
else if( *pJson == quote )
|
||||
{
|
||||
pElem->dataType= JREAD_STRING;
|
||||
pJson++;
|
||||
break;
|
||||
}
|
||||
pElem->bytelen++;
|
||||
pJson++;
|
||||
};
|
||||
};
|
||||
return pJson;
|
||||
};
|
||||
|
||||
// jReadTextLen
|
||||
// - used to identify length of element text
|
||||
// - returns no. of chars from pJson upto a terminator
|
||||
// - terminators: ' ' , } ]
|
||||
//
|
||||
int jReadTextLen( char *pJson )
|
||||
{
|
||||
int len= 0;
|
||||
while( (*pJson > ' ' ) && // any ctrl char incl '\0'
|
||||
(*pJson != ',' ) &&
|
||||
(*pJson != '}' ) &&
|
||||
(*pJson != ']' ) )
|
||||
{
|
||||
len++;
|
||||
pJson++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// compare two json elements
|
||||
// returns: 0 if they are identical strings, else 1
|
||||
//
|
||||
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 )
|
||||
{
|
||||
int i;
|
||||
if( (j1->dataType != JREAD_STRING) ||
|
||||
(j2->dataType != JREAD_STRING) ||
|
||||
(j1->bytelen != j2->bytelen ) )
|
||||
return 1;
|
||||
|
||||
for( i=0; i< j1->bytelen; i++ )
|
||||
if( ((char *)(j1->pValue))[i] != ((char *)(j2->pValue))[i] )
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// read unsigned int from string
|
||||
char * jRead_atoi( char *p, unsigned int *result )
|
||||
{
|
||||
unsigned int x = 0;
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
x = (x*10) + (*p - '0');
|
||||
++p;
|
||||
}
|
||||
*result= x;
|
||||
return p;
|
||||
}
|
||||
|
||||
// read long int from string
|
||||
//
|
||||
char * jRead_atol( char *p, long *result )
|
||||
{
|
||||
long x = 0;
|
||||
int neg = 0;
|
||||
if (*p == '-') {
|
||||
neg = 1;
|
||||
++p;
|
||||
}
|
||||
while (*p >= '0' && *p <= '9') {
|
||||
x = (x*10) + (*p - '0');
|
||||
++p;
|
||||
}
|
||||
if (neg) {
|
||||
x = -x;
|
||||
}
|
||||
*result= x;
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
#define valid_digit(c) ((c) >= '0' && (c) <= '9')
|
||||
|
||||
// read double from string
|
||||
// *CAUTION* does not handle exponents
|
||||
//
|
||||
//
|
||||
char * jRead_atof( char *p, double *result)
|
||||
{
|
||||
double sign, value;
|
||||
|
||||
// Get sign, if any.
|
||||
sign = 1.0;
|
||||
if (*p == '-') {
|
||||
sign = -1.0;
|
||||
p += 1;
|
||||
|
||||
} else if (*p == '+') {
|
||||
p += 1;
|
||||
}
|
||||
|
||||
// Get digits before decimal point or exponent, if any.
|
||||
for (value = 0.0; valid_digit(*p); p += 1) {
|
||||
value = value * 10.0 + (*p - '0');
|
||||
}
|
||||
|
||||
// Get digits after decimal point, if any.
|
||||
if (*p == '.') {
|
||||
double pow10 = 10.0;
|
||||
p += 1;
|
||||
while (valid_digit(*p)) {
|
||||
value += (*p - '0') / pow10;
|
||||
pow10 *= 10.0;
|
||||
p += 1;
|
||||
}
|
||||
}
|
||||
*result= sign * value;
|
||||
return p;
|
||||
}
|
||||
|
||||
// read element into destination buffer and add '\0' terminator
|
||||
// - always copies element irrespective of dataType (unless it's an error)
|
||||
// - destBuffer is always '\0'-terminated (even on zero lenght returns)
|
||||
// - returns pointer to destBuffer
|
||||
//
|
||||
char *jRead_strcpy( char *destBuffer, int destLength, struct jReadElement *pElement )
|
||||
{
|
||||
int i;
|
||||
int len= pElement->bytelen;
|
||||
char *pdest= destBuffer;
|
||||
char *psrc= (char *)pElement->pValue;
|
||||
if( pElement->error == 0 )
|
||||
{
|
||||
if( len >= destLength )
|
||||
len= destLength;
|
||||
for( i=0; i<destLength; i++ )
|
||||
*pdest++= *psrc++;
|
||||
}
|
||||
*pdest= '\0';
|
||||
return destBuffer;
|
||||
}
|
||||
|
||||
// jReadCountObject
|
||||
// - used when query ends at an object, we want to return the object length
|
||||
// - on entry pJson -> "{... "
|
||||
// - used to skip unwanted values which are objects
|
||||
// - keyIndex normally passed as -1 unless we're looking for the nth "key" value
|
||||
// in which case keyIndex is the index of the key we want
|
||||
//
|
||||
char * jReadCountObject( char *pJson, struct jReadElement *pResult, int keyIndex )
|
||||
{
|
||||
struct jReadElement jElement;
|
||||
int jTok;
|
||||
char *sp;
|
||||
pResult->dataType= JREAD_OBJECT;
|
||||
pResult->error= 0;
|
||||
pResult->elements= 0;
|
||||
pResult->pValue= pJson;
|
||||
sp= jReadFindTok( pJson+1, &jTok ); // check for empty object
|
||||
if( jTok == JREAD_EOBJECT )
|
||||
{
|
||||
pJson= sp+1;
|
||||
}else
|
||||
{
|
||||
while( 1 )
|
||||
{
|
||||
pJson= jReadGetString( ++pJson, &jElement, '\"' );
|
||||
if( jElement.dataType != JREAD_STRING )
|
||||
{
|
||||
pResult->error= 3; // Expected "key"
|
||||
break;
|
||||
}
|
||||
if( pResult->elements == keyIndex ) // if passed keyIndex
|
||||
{
|
||||
*pResult= jElement; // we return "key" at this index
|
||||
pResult->dataType= JREAD_KEY;
|
||||
return pJson;
|
||||
}
|
||||
pJson= jReadFindTok( pJson, &jTok );
|
||||
if( jTok != JREAD_COLON )
|
||||
{
|
||||
pResult->error= 4; // Expected ":"
|
||||
break;
|
||||
}
|
||||
pJson= jRead( ++pJson, "", &jElement );
|
||||
if( pResult->error )
|
||||
break;
|
||||
pJson= jReadFindTok( pJson, &jTok );
|
||||
pResult->elements++;
|
||||
if( jTok == JREAD_EOBJECT )
|
||||
{
|
||||
pJson++;
|
||||
break;
|
||||
}
|
||||
if( jTok != JREAD_COMMA )
|
||||
{
|
||||
pResult->error= 6; // Expected "," in object
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( keyIndex >= 0 )
|
||||
{
|
||||
// we wanted a "key" value - that we didn't find
|
||||
pResult->dataType= JREAD_ERROR;
|
||||
pResult->error= 11; // Object key not found (bad index)
|
||||
}else{
|
||||
pResult->bytelen= pJson - (char *)pResult->pValue;
|
||||
}
|
||||
return pJson;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// jReadCountArray
|
||||
// - used when query ends at an array, we want to return the array length
|
||||
// - on entry pJson -> "[... "
|
||||
// - used to skip unwanted values which are arrays
|
||||
//
|
||||
char * jReadCountArray( char *pJson, struct jReadElement *pResult )
|
||||
{
|
||||
struct jReadElement jElement;
|
||||
int jTok;
|
||||
char *sp;
|
||||
pResult->dataType= JREAD_ARRAY;
|
||||
pResult->error= 0;
|
||||
pResult->elements= 0;
|
||||
pResult->pValue= pJson;
|
||||
sp= jReadFindTok( pJson+1, &jTok ); // check for empty array
|
||||
if( jTok == JREAD_EARRAY )
|
||||
{
|
||||
pJson= sp+1;
|
||||
}else
|
||||
{
|
||||
while( 1 )
|
||||
{
|
||||
pJson= jRead( ++pJson, "", &jElement ); // array value
|
||||
if( pResult->error )
|
||||
break;
|
||||
pJson= jReadFindTok( pJson, &jTok ); // , or ]
|
||||
pResult->elements++;
|
||||
if( jTok == JREAD_EARRAY )
|
||||
{
|
||||
pJson++;
|
||||
break;
|
||||
}
|
||||
if( jTok != JREAD_COMMA )
|
||||
{
|
||||
pResult->error= 9; // Expected "," in array
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pResult->bytelen= pJson - (char *)pResult->pValue;
|
||||
return pJson;
|
||||
}
|
||||
|
||||
// jReadArrayStep()
|
||||
// - reads one value from an array
|
||||
// - assumes pJsonArray points at the start of an array or array element
|
||||
//
|
||||
char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult )
|
||||
{
|
||||
int jTok;
|
||||
|
||||
pJsonArray= jReadFindTok( pJsonArray, &jTok );
|
||||
switch( jTok )
|
||||
{
|
||||
case JREAD_ARRAY: // start of array
|
||||
case JREAD_COMMA: // element separator
|
||||
return jRead( ++pJsonArray, "", pResult );
|
||||
|
||||
case JREAD_EARRAY: // end of array
|
||||
pResult->error= 13; // End of array found
|
||||
break;
|
||||
default: // some other error
|
||||
pResult->error= 9; // expected comma in array
|
||||
break;
|
||||
}
|
||||
pResult->dataType= JREAD_ERROR;
|
||||
return pJsonArray;
|
||||
}
|
||||
|
||||
|
||||
// jRead
|
||||
// - reads a complete JSON <value>
|
||||
// - matches pQuery against pJson, results in pResult
|
||||
// returns: pointer into pJson
|
||||
//
|
||||
// Note: is recursive
|
||||
//
|
||||
char * jRead( char *pJson, char *pQuery, struct jReadElement *pResult )
|
||||
{
|
||||
return jReadParam( pJson, pQuery, pResult, NULL );
|
||||
}
|
||||
|
||||
char * jReadParam( char *pJson, char *pQuery, struct jReadElement *pResult, int *queryParams )
|
||||
{
|
||||
int qTok, jTok, bytelen;
|
||||
unsigned int index, count;
|
||||
struct jReadElement qElement, jElement;
|
||||
|
||||
pJson= jReadFindTok( pJson, &jTok );
|
||||
pQuery= jReadFindTok( pQuery, &qTok );
|
||||
|
||||
pResult->dataType= jTok;
|
||||
pResult->bytelen= pResult->elements= pResult->error= 0;
|
||||
pResult->pValue= pJson;
|
||||
|
||||
if( (qTok != JREAD_EOL) && (qTok != jTok) )
|
||||
{
|
||||
pResult->error= 1; // JSON does not match Query
|
||||
return pJson;
|
||||
}
|
||||
|
||||
switch( jTok )
|
||||
{
|
||||
case JREAD_ERROR: // general error, eof etc.
|
||||
pResult->error= 2; // Error reading JSON value
|
||||
break;
|
||||
|
||||
case JREAD_OBJECT: // "{"
|
||||
if( qTok == JREAD_EOL )
|
||||
return jReadCountObject( pJson, pResult, -1 ); // return length of object
|
||||
|
||||
pQuery= jReadFindTok( ++pQuery, &qTok ); // "('key'...", "{NUMBER", "{*" or EOL
|
||||
if( qTok != JREAD_STRING )
|
||||
{
|
||||
index= 0;
|
||||
switch( qTok )
|
||||
{
|
||||
case JREAD_NUMBER:
|
||||
pQuery= jRead_atoi( (char *)pQuery, &index ); // index value
|
||||
break;
|
||||
case JREAD_QPARAM:
|
||||
pQuery++;
|
||||
index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter
|
||||
break;
|
||||
default:
|
||||
pResult->error= 12; // Bad Object key
|
||||
return pJson;
|
||||
}
|
||||
return jReadCountObject( pJson, pResult, index );
|
||||
}
|
||||
|
||||
pQuery= jReadGetString( pQuery, &qElement, QUERY_QUOTE ); // qElement = query 'key'
|
||||
//
|
||||
// read <key> : <value> , ... }
|
||||
// loop 'til key matched
|
||||
//
|
||||
while( 1 )
|
||||
{
|
||||
pJson= jReadGetString( ++pJson, &jElement, '\"' );
|
||||
if( jElement.dataType != JREAD_STRING )
|
||||
{
|
||||
pResult->error= 3; // Expected "key"
|
||||
break;
|
||||
}
|
||||
pJson= jReadFindTok( pJson, &jTok );
|
||||
if( jTok != JREAD_COLON )
|
||||
{
|
||||
pResult->error= 4; // Expected ":"
|
||||
break;
|
||||
}
|
||||
// compare object keys
|
||||
if( jReadStrcmp( &qElement, &jElement ) == 0 )
|
||||
{
|
||||
// found object key
|
||||
return jReadParam( ++pJson, pQuery, pResult, queryParams );
|
||||
}
|
||||
// no key match... skip this value
|
||||
pJson= jRead( ++pJson, "", pResult );
|
||||
pJson= jReadFindTok( pJson, &jTok );
|
||||
if( jTok == JREAD_EOBJECT )
|
||||
{
|
||||
pResult->error= 5; // Object key not found
|
||||
break;
|
||||
}
|
||||
if( jTok != JREAD_COMMA )
|
||||
{
|
||||
pResult->error= 6; // Expected "," in object
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case JREAD_ARRAY: // "[NUMBER" or "[*"
|
||||
//
|
||||
// read index, skip values 'til index
|
||||
//
|
||||
if( qTok == JREAD_EOL )
|
||||
return jReadCountArray( pJson, pResult ); // return length of object
|
||||
|
||||
index= 0;
|
||||
pQuery= jReadFindTok( ++pQuery, &qTok ); // "[NUMBER" or "[*"
|
||||
if( qTok == JREAD_NUMBER )
|
||||
{
|
||||
pQuery= jRead_atoi( pQuery, &index ); // get array index
|
||||
}else if( qTok == JREAD_QPARAM )
|
||||
{
|
||||
pQuery++;
|
||||
index= (queryParams != NULL) ? *queryParams++ : 0; // substitute parameter
|
||||
}
|
||||
|
||||
count=0;
|
||||
while( 1 )
|
||||
{
|
||||
if( count == index )
|
||||
return jReadParam( ++pJson, pQuery, pResult, queryParams ); // return value at index
|
||||
// not this index... skip this value
|
||||
pJson= jRead( ++pJson, "", &jElement );
|
||||
if( pResult->error )
|
||||
break;
|
||||
count++;
|
||||
pJson= jReadFindTok( pJson, &jTok ); // , or ]
|
||||
if( jTok == JREAD_EARRAY )
|
||||
{
|
||||
pResult->error= 10; // Array element not found (bad index)
|
||||
break;
|
||||
}
|
||||
if( jTok != JREAD_COMMA )
|
||||
{
|
||||
pResult->error= 9; // Expected "," in array
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case JREAD_STRING: // "string"
|
||||
pJson= jReadGetString( pJson, pResult, '\"' );
|
||||
break;
|
||||
case JREAD_NUMBER: // number (may be -ve) int or float
|
||||
case JREAD_BOOL: // true or false
|
||||
case JREAD_NULL: // null
|
||||
bytelen= jReadTextLen( pJson );
|
||||
pResult->dataType= jTok;
|
||||
pResult->bytelen= bytelen;
|
||||
pResult->pValue= pJson;
|
||||
pResult->elements= 1;
|
||||
pJson += bytelen;
|
||||
break;
|
||||
default:
|
||||
pResult->error= 8; // unexpected character (in pResult->dataType)
|
||||
}
|
||||
// We get here on a 'terminal value'
|
||||
// - make sure the query string is empty also
|
||||
pQuery= jReadFindTok( pQuery, &qTok );
|
||||
if( !pResult->error && (qTok != JREAD_EOL) )
|
||||
pResult->error= 7; // terminal value found before end of query
|
||||
if( pResult->error )
|
||||
{
|
||||
pResult->dataType= JREAD_ERROR;
|
||||
pResult->elements= pResult->bytelen= 0;
|
||||
pResult->pValue= pJson; // return pointer into JSON at error point
|
||||
}
|
||||
return pJson;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Optional helper functions
|
||||
// - simple routines to extract values from JSON
|
||||
// - does coercion of types where possible
|
||||
// - always returns a value (e.g. 0 or "" on error)
|
||||
//
|
||||
// Note: by default, pass NULL for queryParams
|
||||
// unless you are using '*' in the query for indexing
|
||||
//
|
||||
|
||||
// jRead_long
|
||||
// - reads signed long value from JSON
|
||||
// - returns number from NUMBER or STRING elements (if possible)
|
||||
// returns 1 or 0 from BOOL elements
|
||||
// otherwise returns 0
|
||||
//
|
||||
long jRead_long( char *pJson, char *pQuery, int *queryParams )
|
||||
{
|
||||
struct jReadElement elem;
|
||||
long result;
|
||||
jReadParam( pJson, pQuery, &elem, queryParams );
|
||||
if( (elem.dataType == JREAD_ERROR) || (elem.dataType == JREAD_NULL))
|
||||
return 0;
|
||||
if( elem.dataType == JREAD_BOOL )
|
||||
return *((char *)elem.pValue)=='t' ? 1 : 0;
|
||||
|
||||
jRead_atol( (char *)elem.pValue, &result );
|
||||
return result;
|
||||
}
|
||||
|
||||
int jRead_int( char *pJson, char *pQuery, int *queryParams )
|
||||
{
|
||||
return (int)jRead_long( pJson, pQuery, queryParams );
|
||||
}
|
||||
|
||||
// jRead_double
|
||||
// - returns double from JSON
|
||||
// - returns number from NUMBER or STRING elements
|
||||
// otherwise returns 0.0
|
||||
//
|
||||
double jRead_double( char *pJson, char *pQuery, int *queryParams )
|
||||
{
|
||||
struct jReadElement elem;
|
||||
double result;
|
||||
jReadParam( pJson, pQuery, &elem, queryParams );
|
||||
if( elem.dataType == JREAD_ERROR )
|
||||
return 0.0;
|
||||
jRead_atof( (char *)elem.pValue, &result );
|
||||
return result;
|
||||
}
|
||||
|
||||
// jRead_string
|
||||
// Copy string to pDest and '\0'-terminate it (upto destlen total bytes)
|
||||
// returns: character length of string (excluding '\0' terminator)
|
||||
//
|
||||
// Note: any element can be returned as a string
|
||||
//
|
||||
int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams )
|
||||
{
|
||||
struct jReadElement elem;
|
||||
int i;
|
||||
|
||||
*pDest= '\0';
|
||||
jReadParam( pJson, pQuery, &elem, queryParams );
|
||||
if( elem.dataType == JREAD_ERROR )
|
||||
return 0;
|
||||
|
||||
for( i=0; (i<elem.bytelen) && (i<destlen-1); i++ )
|
||||
*pDest++ = ((char *)elem.pValue)[i];
|
||||
*pDest= '\0';
|
||||
return elem.bytelen;
|
||||
}
|
||||
|
||||
//-------------------------------------------------
|
||||
// Optional String output Functions
|
||||
//
|
||||
char *jReadTypeStrings[]={
|
||||
"Error", // 0
|
||||
"Object", // 1
|
||||
"Array", // 2
|
||||
"String", // 3
|
||||
"Number", // 4
|
||||
"Bool", // 5
|
||||
"null", // 6
|
||||
"Object key", // 7
|
||||
"colon", // 8
|
||||
"eol", // 9
|
||||
"comma", // 10
|
||||
"}", // 11
|
||||
"]", // 12
|
||||
"* parameter" // 13
|
||||
};
|
||||
|
||||
char *jReadTypeToString( int dataType )
|
||||
{
|
||||
return jReadTypeStrings[ dataType ];
|
||||
};
|
||||
|
||||
char * jReadErrorStrings[]={
|
||||
"Ok", // 0
|
||||
"JSON does not match Query", // 1
|
||||
"Error reading JSON value", // 2
|
||||
"Expected \"key\"", // 3
|
||||
"Expected ':'", // 4
|
||||
"Object key not found", // 5
|
||||
"Expected ',' in object", // 6
|
||||
"Terminal value found before end of query", // 7
|
||||
"Unexpected character", // 8
|
||||
"Expected ',' in array", // 9
|
||||
"Array element not found (bad index)", // 10
|
||||
"Object key not found (bad index)", // 11
|
||||
"Bad object key", // 12
|
||||
"End of array found", // 13
|
||||
"End of object found" // 14
|
||||
};
|
||||
char * jReadErrorToString( int error )
|
||||
{
|
||||
if( (error >=0 ) && (error <= 14))
|
||||
return jReadErrorStrings[ error ];
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// end of jRead.c
|
||||
128
extlibs/jRead.h
128
extlibs/jRead.h
|
|
@ -1,128 +0,0 @@
|
|||
// jRead.h
|
||||
//
|
||||
// see jRead.c for more information
|
||||
//
|
||||
|
||||
// uncomment this if you really want to use double quotes in query strings instead of '
|
||||
//#define JREAD_DOUBLE_QUOTE_IN_QUERY
|
||||
|
||||
//
|
||||
// return dataTypes:
|
||||
#define JREAD_ERROR 0 // general error, eof etc.
|
||||
#define JREAD_OBJECT 1 // "{"
|
||||
#define JREAD_ARRAY 2 // "["
|
||||
#define JREAD_STRING 3 // "string"
|
||||
#define JREAD_NUMBER 4 // number (may be -ve) int or float
|
||||
#define JREAD_BOOL 5 // true or false
|
||||
#define JREAD_NULL 6 // null
|
||||
#define JREAD_KEY 7 // object "key"
|
||||
// internal values:
|
||||
#define JREAD_COLON 8 // ":"
|
||||
#define JREAD_EOL 9 // end of input string (ptr at '\0')
|
||||
#define JREAD_COMMA 10 // ","
|
||||
#define JREAD_EOBJECT 11 // "}"
|
||||
#define JREAD_EARRAY 12 // "]"
|
||||
#define JREAD_QPARAM 13 // "*" query string parameter
|
||||
|
||||
//------------------------------------------------------
|
||||
// jReadElement
|
||||
// - structure to return JSON elements
|
||||
// - error=0 for valid returns
|
||||
//
|
||||
// *NOTES*
|
||||
// the returned pValue pointer points into the passed JSON
|
||||
// string returns are not '\0' terminated.
|
||||
// bytelen specifies the length of the returned data pointed to by pValue
|
||||
//
|
||||
struct jReadElement{
|
||||
int dataType; // one of JREAD_...
|
||||
int elements; // number of elements (e.g. elements in array or object)
|
||||
int bytelen; // byte length of element (e.g. length of string, array text "[ ... ]" etc.)
|
||||
void * pValue; // pointer to value string in JSON text
|
||||
int error; // error value if dataType == JREAD_ERROR
|
||||
};
|
||||
|
||||
//------------------------------------------------------
|
||||
// The JSON reader function
|
||||
//
|
||||
// - reads a '\0'-terminated JSON text string from pJson
|
||||
// - traverses the JSON according to the pQuery string
|
||||
// - returns the result value in pResult
|
||||
//
|
||||
// returns: pointer into pJson after the queried value
|
||||
//
|
||||
// e.g.
|
||||
// With JSON like: "{ ..., "key":"value", ... }"
|
||||
//
|
||||
// jRead( pJson, "{'key'", &result );
|
||||
// returns with:
|
||||
// result.dataType= JREAD_STRING, result.pValue->'value', result.bytelen=5
|
||||
//
|
||||
char * jRead( char *pJson, char *pQuery, struct jReadElement *pResult );
|
||||
|
||||
// version of jRead which allows one or more queryParam integers to be substituted
|
||||
// for array or object indexes marked by a '*' in the query
|
||||
//
|
||||
// e.g. jReadParam( pJson, "[*", &resultElement, &arrayIndex );
|
||||
//
|
||||
// *!* CAUTION *!*
|
||||
// You can supply an array of integers which are indexed for each '*' in pQuery
|
||||
// however, horrid things will happen if you don't supply enough parameters
|
||||
//
|
||||
char * jReadParam( char *pJson, char *pQuery, struct jReadElement *pResult, int *queryParams );
|
||||
|
||||
// Array Stepping function
|
||||
// - assumes pJsonArray is JSON source of an array "[ ... ]"
|
||||
// - returns next element of the array in pResult
|
||||
// - returns pointer to end of element, to be passed to next call of jReadArrayStep()
|
||||
// - if end of array is encountered, pResult->error = 13 "End of array found"
|
||||
//
|
||||
// e.g.
|
||||
// With JSON like: "{ ... "arrayInObject":[ elem1,elem2,... ], ... }"
|
||||
//
|
||||
// pJson= jRead( pJson, "{'arrayInObject'", &theArray );
|
||||
// if( theArray.dataType == JREAD_ARRAY )
|
||||
// {
|
||||
// char *pArray= (char *)theArray.pValue;
|
||||
// jReadElement arrayElement;
|
||||
// int index;
|
||||
// for( index=0; index < theArray.elements; index++ )
|
||||
// {
|
||||
// pArray= jReadArrayStep( pArray, &arrayElement );
|
||||
// ...
|
||||
//
|
||||
// Note: this significantly speeds up traversing arrays.
|
||||
//
|
||||
char *jReadArrayStep( char *pJsonArray, struct jReadElement *pResult );
|
||||
|
||||
|
||||
#define EXPORT_OPTIONAL_FUNCTIONS
|
||||
#ifdef EXPORT_OPTIONAL_FUNCTIONS
|
||||
|
||||
//------------------------------------------------------
|
||||
// Optional Helper Functions
|
||||
//
|
||||
long jRead_long( char *pJson, char *pQuery, int *queryParams );
|
||||
int jRead_int( char *pJson, char *pQuery, int *queryParams );
|
||||
double jRead_double( char *pJson, char *pQuery, int *queryParams );
|
||||
int jRead_string( char *pJson, char *pQuery, char *pDest, int destlen, int *queryParams );
|
||||
|
||||
//------------------------------------------------------
|
||||
// Optional String output Functions
|
||||
//
|
||||
char *jReadTypeToString( int dataType ); // string describes dataType
|
||||
char * jReadErrorToString( int error ); // string descibes error code
|
||||
|
||||
//------------------------------------------------------
|
||||
// Other jRead utilities which may be useful...
|
||||
//
|
||||
char * jRead_atoi( char *p, unsigned int *result ); // string to unsigned int
|
||||
char * jRead_atol( char *p, long *result ); // string to signed long
|
||||
char * jRead_atof( char *p, double *result); // string to double (does not do exponents)
|
||||
int jReadStrcmp( struct jReadElement *j1, struct jReadElement *j2 ); // compare STRING elements
|
||||
|
||||
// copy element to '\0'-terminated buffer
|
||||
char * jRead_strcpy( char *destBuffer, int destLength, struct jReadElement *pElement );
|
||||
|
||||
#endif
|
||||
// end of jRead.h
|
||||
561
extlibs/jWrite.c
561
extlibs/jWrite.c
|
|
@ -1,561 +0,0 @@
|
|||
//
|
||||
// jWrite.c version 1v2
|
||||
//
|
||||
// A *really* simple JSON writer in C
|
||||
//
|
||||
// see: jWrite.h for info
|
||||
//
|
||||
// TonyWilk, Mar 2015
|
||||
//
|
||||
#define _CRT_SECURE_NO_WARNINGS // stop complaining about deprecated functions
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h> // memset()
|
||||
|
||||
#include <jWrite.h>
|
||||
|
||||
#include <stdint.h> // definintion of uint32_t, int32_t
|
||||
//typedef unsigned int uint32_t;
|
||||
//typedef int int32_t;
|
||||
|
||||
|
||||
// the jWrite functions take the above jWriteControl structure pointer
|
||||
// to maintain state while writing a JSON string.
|
||||
//
|
||||
// You can opt to use a single global instance of a jWriteControl structure
|
||||
// which simplifies the function parameters or to supply your own structure
|
||||
//
|
||||
#ifdef JW_GLOBAL_CONTROL_STRUCT
|
||||
struct jWriteControl g_jWriteControl; // global control struct
|
||||
#define JWC_DECL // function parameter decl is empty
|
||||
#define JWC_DECL0
|
||||
#define JWC(x) g_jWriteControl.x // functions access global
|
||||
#define JWC_PARAM // pointer to struct is empty
|
||||
#define JWC_PARAM0
|
||||
#else
|
||||
#define JWC_DECL struct jWriteControl *jwc, // function parameter is ptr to control struct
|
||||
#define JWC_DECL0 struct jWriteControl *jwc // function parameter, no params
|
||||
#define JWC(x) jwc->x // functions use pointer
|
||||
#define JWC_PARAM jwc, // pointer to stuct
|
||||
#define JWC_PARAM0 jwc // pointer to stuct, no params
|
||||
#endif
|
||||
|
||||
//------------------------------------------
|
||||
// Internal functions
|
||||
//
|
||||
void jwPutch( JWC_DECL char c );
|
||||
void jwPutstr( JWC_DECL char *str );
|
||||
void jwPutraw( JWC_DECL char *str );
|
||||
void modp_itoa10(int32_t value, char* str);
|
||||
void modp_dtoa2(double value, char* str, int prec);
|
||||
void jwPretty( JWC_DECL0 );
|
||||
enum jwNodeType jwPop( JWC_DECL0 );
|
||||
void jwPush( JWC_DECL enum jwNodeType nodeType );
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// jwOpen
|
||||
// - open writing of JSON starting with rootType = JW_OBJECT or JW_ARRAY
|
||||
// - initialise with user string buffer of length buflen
|
||||
// - isPretty=JW_PRETTY adds \n and spaces to prettify output (else JW_COMPACT)
|
||||
//
|
||||
void jwOpen( JWC_DECL char *buffer, unsigned int buflen,
|
||||
enum jwNodeType rootType, int isPretty )
|
||||
{
|
||||
memset( buffer, 0, buflen ); // zap the whole destination buffer
|
||||
JWC(buffer)= buffer;
|
||||
JWC(buflen)= buflen;
|
||||
JWC(bufp)= buffer;
|
||||
JWC(nodeStack)[0].nodeType= rootType;
|
||||
JWC(nodeStack)[0].elementNo= 0;
|
||||
JWC(stackpos)=0;
|
||||
JWC(error)= JWRITE_OK;
|
||||
JWC(callNo)= 1;
|
||||
JWC(isPretty)= isPretty;
|
||||
jwPutch( JWC_PARAM (rootType==JW_OBJECT) ? '{' : '[' );
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
// jwClose
|
||||
// - closes the root JSON object started by jwOpen()
|
||||
// - returns error code
|
||||
//
|
||||
int jwClose( JWC_DECL0 )
|
||||
{
|
||||
if( JWC(error) == JWRITE_OK )
|
||||
{
|
||||
if( JWC(stackpos) == 0 )
|
||||
{
|
||||
enum jwNodeType node= JWC(nodeStack)[0].nodeType;
|
||||
if( JWC(isPretty) )
|
||||
jwPutch( JWC_PARAM '\n' );
|
||||
jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']');
|
||||
}else{
|
||||
JWC(error)= JWRITE_NEST_ERROR; // nesting error, not all objects closed when jwClose() called
|
||||
}
|
||||
}
|
||||
return JWC(error);
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
// End the current array/object
|
||||
//
|
||||
int jwEnd( JWC_DECL0 )
|
||||
{
|
||||
if( JWC(error) == JWRITE_OK )
|
||||
{
|
||||
enum jwNodeType node;
|
||||
int lastElemNo= JWC(nodeStack)[JWC(stackpos)].elementNo;
|
||||
node= jwPop( JWC_PARAM0 );
|
||||
if( lastElemNo > 0 )
|
||||
jwPretty( JWC_PARAM0 );
|
||||
jwPutch( JWC_PARAM (node == JW_OBJECT) ? '}' : ']');
|
||||
}
|
||||
return JWC(error);
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// jwErrorPos
|
||||
// - Returns position of error: the nth call to a jWrite function
|
||||
//
|
||||
int jwErrorPos( JWC_DECL0 )
|
||||
{
|
||||
return JWC(callNo);
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// Object insert functions
|
||||
//
|
||||
int _jwObj( JWC_DECL char *key );
|
||||
|
||||
// put raw string to object (i.e. contents of rawtext without quotes)
|
||||
//
|
||||
void jwObj_raw( JWC_DECL char *key, char *rawtext )
|
||||
{
|
||||
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
|
||||
jwPutraw( JWC_PARAM rawtext);
|
||||
}
|
||||
|
||||
// put "quoted" string to object
|
||||
//
|
||||
void jwObj_string( JWC_DECL char *key, char *value )
|
||||
{
|
||||
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
|
||||
jwPutstr( JWC_PARAM value );
|
||||
}
|
||||
|
||||
void jwObj_int( JWC_DECL char *key, int value )
|
||||
{
|
||||
modp_itoa10( value, JWC(tmpbuf) );
|
||||
jwObj_raw( JWC_PARAM key, JWC(tmpbuf) );
|
||||
}
|
||||
|
||||
void jwObj_double( JWC_DECL char *key, double value )
|
||||
{
|
||||
modp_dtoa2( value, JWC(tmpbuf), 6 );
|
||||
jwObj_raw( JWC_PARAM key, JWC(tmpbuf) );
|
||||
}
|
||||
|
||||
void jwObj_bool( JWC_DECL char *key, int oneOrZero )
|
||||
{
|
||||
jwObj_raw( JWC_PARAM key, (oneOrZero) ? "true" : "false" );
|
||||
}
|
||||
|
||||
void jwObj_null( JWC_DECL char *key )
|
||||
{
|
||||
jwObj_raw( JWC_PARAM key, "null" );
|
||||
}
|
||||
|
||||
// put Object in Object
|
||||
//
|
||||
void jwObj_object( JWC_DECL char *key )
|
||||
{
|
||||
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
|
||||
{
|
||||
jwPutch( JWC_PARAM '{' );
|
||||
jwPush( JWC_PARAM JW_OBJECT );
|
||||
}
|
||||
}
|
||||
|
||||
// put Array in Object
|
||||
//
|
||||
void jwObj_array( JWC_DECL char *key )
|
||||
{
|
||||
if(_jwObj( JWC_PARAM key ) == JWRITE_OK)
|
||||
{
|
||||
jwPutch( JWC_PARAM '[' );
|
||||
jwPush( JWC_PARAM JW_ARRAY );
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
// Array insert functions
|
||||
//
|
||||
int _jwArr( JWC_DECL0 );
|
||||
|
||||
// put raw string to array (i.e. contents of rawtext without quotes)
|
||||
//
|
||||
void jwArr_raw( JWC_DECL char *rawtext )
|
||||
{
|
||||
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
|
||||
jwPutraw( JWC_PARAM rawtext);
|
||||
}
|
||||
|
||||
// put "quoted" string to array
|
||||
//
|
||||
void jwArr_string( JWC_DECL char *value )
|
||||
{
|
||||
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
|
||||
jwPutstr( JWC_PARAM value );
|
||||
}
|
||||
|
||||
void jwArr_int( JWC_DECL int value )
|
||||
{
|
||||
modp_itoa10( value, JWC(tmpbuf) );
|
||||
jwArr_raw( JWC_PARAM JWC(tmpbuf) );
|
||||
}
|
||||
|
||||
void jwArr_double( JWC_DECL double value )
|
||||
{
|
||||
modp_dtoa2( value, JWC(tmpbuf), 6 );
|
||||
jwArr_raw( JWC_PARAM JWC(tmpbuf) );
|
||||
}
|
||||
|
||||
void jwArr_bool( JWC_DECL int oneOrZero )
|
||||
{
|
||||
jwArr_raw( JWC_PARAM (oneOrZero) ? "true" : "false" );
|
||||
}
|
||||
|
||||
void jwArr_null( JWC_DECL0 )
|
||||
{
|
||||
jwArr_raw( JWC_PARAM "null" );
|
||||
}
|
||||
|
||||
void jwArr_object( JWC_DECL0 )
|
||||
{
|
||||
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
|
||||
{
|
||||
jwPutch( JWC_PARAM '{' );
|
||||
jwPush( JWC_PARAM JW_OBJECT );
|
||||
}
|
||||
}
|
||||
|
||||
void jwArr_array( JWC_DECL0 )
|
||||
{
|
||||
if(_jwArr( JWC_PARAM0 ) == JWRITE_OK)
|
||||
{
|
||||
jwPutch( JWC_PARAM '[' );
|
||||
jwPush( JWC_PARAM JW_ARRAY );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// jwErrorToString
|
||||
// - returns string describing error code
|
||||
//
|
||||
char *jwErrorToString( int err )
|
||||
{
|
||||
switch( err )
|
||||
{
|
||||
case JWRITE_OK: return "OK";
|
||||
case JWRITE_BUF_FULL: return "output buffer full";
|
||||
case JWRITE_NOT_ARRAY: return "tried to write Array value into Object";
|
||||
case JWRITE_NOT_OBJECT: return "tried to write Object key/value into Array";
|
||||
case JWRITE_STACK_FULL: return "array/object nesting > JWRITE_STACK_DEPTH";
|
||||
case JWRITE_STACK_EMPTY:return "stack underflow error (too many 'end's)";
|
||||
case JWRITE_NEST_ERROR: return "nesting error, not all objects closed when jwClose() called";
|
||||
}
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
// Internal functions
|
||||
//
|
||||
void jwPretty( JWC_DECL0 )
|
||||
{
|
||||
int i;
|
||||
if( JWC(isPretty) )
|
||||
{
|
||||
jwPutch( JWC_PARAM '\n' );
|
||||
for( i=0; i<JWC(stackpos)+1; i++ )
|
||||
jwPutraw( JWC_PARAM " " );
|
||||
}
|
||||
}
|
||||
|
||||
// Push / Pop node stack
|
||||
//
|
||||
void jwPush( JWC_DECL enum jwNodeType nodeType )
|
||||
{
|
||||
if( (JWC(stackpos)+1) >= JWRITE_STACK_DEPTH )
|
||||
JWC(error)= JWRITE_STACK_FULL; // array/object nesting > JWRITE_STACK_DEPTH
|
||||
else
|
||||
{
|
||||
JWC(nodeStack[++JWC(stackpos)]).nodeType= nodeType;
|
||||
JWC(nodeStack[JWC(stackpos)]).elementNo= 0;
|
||||
}
|
||||
}
|
||||
|
||||
enum jwNodeType jwPop( JWC_DECL0 )
|
||||
{
|
||||
enum jwNodeType retval= JWC(nodeStack[JWC(stackpos)]).nodeType;
|
||||
if( JWC(stackpos) == 0 )
|
||||
JWC(error)= JWRITE_STACK_EMPTY; // stack underflow error (too many 'end's)
|
||||
else
|
||||
JWC(stackpos)--;
|
||||
return retval;
|
||||
}
|
||||
|
||||
void jwPutch( JWC_DECL char c )
|
||||
{
|
||||
if( (unsigned int)(JWC(bufp) - JWC(buffer)) >= JWC(buflen) )
|
||||
{
|
||||
JWC(error)= JWRITE_BUF_FULL;
|
||||
}else{
|
||||
*JWC(bufp)++ = c;
|
||||
}
|
||||
}
|
||||
|
||||
// put string enclosed in quotes
|
||||
//
|
||||
void jwPutstr( JWC_DECL char *str )
|
||||
{
|
||||
jwPutch( JWC_PARAM '\"' );
|
||||
while( *str != '\0' )
|
||||
jwPutch( JWC_PARAM *str++ );
|
||||
jwPutch( JWC_PARAM '\"' );
|
||||
}
|
||||
|
||||
// put raw string
|
||||
//
|
||||
void jwPutraw( JWC_DECL char *str )
|
||||
{
|
||||
while( *str != '\0' )
|
||||
jwPutch( JWC_PARAM *str++ );
|
||||
}
|
||||
|
||||
|
||||
// *common Object function*
|
||||
// - checks error
|
||||
// - checks current node is OBJECT
|
||||
// - adds comma if reqd
|
||||
// - adds "key" :
|
||||
//
|
||||
int _jwObj( JWC_DECL char *key )
|
||||
{
|
||||
if(JWC(error) == JWRITE_OK)
|
||||
{
|
||||
JWC(callNo)++;
|
||||
if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_OBJECT )
|
||||
JWC(error)= JWRITE_NOT_OBJECT; // tried to write Object key/value into Array
|
||||
else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 )
|
||||
jwPutch( JWC_PARAM ',' );
|
||||
jwPretty( JWC_PARAM0 );
|
||||
jwPutstr( JWC_PARAM key );
|
||||
jwPutch( JWC_PARAM ':' );
|
||||
if( JWC(isPretty) )
|
||||
jwPutch( JWC_PARAM ' ' );
|
||||
}
|
||||
return JWC(error);
|
||||
}
|
||||
|
||||
// *common Array function*
|
||||
// - checks error
|
||||
// - checks current node is ARRAY
|
||||
// - adds comma if reqd
|
||||
//
|
||||
int _jwArr( JWC_DECL0 )
|
||||
{
|
||||
if(JWC(error) == JWRITE_OK)
|
||||
{
|
||||
JWC(callNo)++;
|
||||
if( JWC(nodeStack)[JWC(stackpos)].nodeType != JW_ARRAY )
|
||||
JWC(error)= JWRITE_NOT_ARRAY; // tried to write array value into Object
|
||||
else if( JWC(nodeStack)[JWC(stackpos)].elementNo++ > 0 )
|
||||
jwPutch( JWC_PARAM ',' );
|
||||
jwPretty( JWC_PARAM0 );
|
||||
}
|
||||
return JWC(error);
|
||||
}
|
||||
|
||||
//=================================================================
|
||||
//
|
||||
// modp value-to-string functions
|
||||
// - modified for C89
|
||||
//
|
||||
// We use these functions as they are a lot faster than sprintf()
|
||||
//
|
||||
// Origin of these routines:
|
||||
/*
|
||||
* <pre>
|
||||
* Copyright © 2007, Nick Galbreath -- nickg [at] modp [dot] com
|
||||
* All rights reserved.
|
||||
* http://code.google.com/p/stringencoders/
|
||||
* Released under the bsd license.
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
static void strreverse(char* begin, char* end)
|
||||
{
|
||||
char aux;
|
||||
while (end > begin)
|
||||
aux = *end, *end-- = *begin, *begin++ = aux;
|
||||
}
|
||||
|
||||
/** \brief convert an signed integer to char buffer
|
||||
*
|
||||
* \param[in] value
|
||||
* \param[out] buf the output buffer. Should be 16 chars or more.
|
||||
*/
|
||||
void modp_itoa10(int32_t value, char* str)
|
||||
{
|
||||
char* wstr=str;
|
||||
// Take care of sign
|
||||
unsigned int uvalue = (value < 0) ? -value : value;
|
||||
// Conversion. Number is reversed.
|
||||
do *wstr++ = (char)(48 + (uvalue % 10)); while(uvalue /= 10);
|
||||
if (value < 0) *wstr++ = '-';
|
||||
*wstr='\0';
|
||||
|
||||
// Reverse string
|
||||
strreverse(str,wstr-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Powers of 10
|
||||
* 10^0 to 10^9
|
||||
*/
|
||||
static const double pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000,
|
||||
10000000, 100000000, 1000000000};
|
||||
|
||||
/** \brief convert a floating point number to char buffer with a
|
||||
* variable-precision format, and no trailing zeros
|
||||
*
|
||||
* This is similar to "%.[0-9]f" in the printf style, except it will
|
||||
* NOT include trailing zeros after the decimal point. This type
|
||||
* of format oddly does not exists with printf.
|
||||
*
|
||||
* If the input value is greater than 1<<31, then the output format
|
||||
* will be switched exponential format.
|
||||
*
|
||||
* \param[in] value
|
||||
* \param[out] buf The allocated output buffer. Should be 32 chars or more.
|
||||
* \param[in] precision Number of digits to the right of the decimal point.
|
||||
* Can only be 0-9.
|
||||
*/
|
||||
void modp_dtoa2(double value, char* str, int prec)
|
||||
{
|
||||
/* if input is larger than thres_max, revert to exponential */
|
||||
const double thres_max = (double)(0x7FFFFFFF);
|
||||
int count;
|
||||
double diff = 0.0;
|
||||
char* wstr = str;
|
||||
int neg= 0;
|
||||
int whole;
|
||||
double tmp;
|
||||
uint32_t frac;
|
||||
|
||||
/* Hacky test for NaN
|
||||
* under -fast-math this won't work, but then you also won't
|
||||
* have correct nan values anyways. The alternative is
|
||||
* to link with libmath (bad) or hack IEEE double bits (bad)
|
||||
*/
|
||||
if (! (value == value)) {
|
||||
str[0] = 'n'; str[1] = 'a'; str[2] = 'n'; str[3] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
if (prec < 0) {
|
||||
prec = 0;
|
||||
} else if (prec > 9) {
|
||||
/* precision of >= 10 can lead to overflow errors */
|
||||
prec = 9;
|
||||
}
|
||||
|
||||
/* we'll work in positive values and deal with the
|
||||
negative sign issue later */
|
||||
if (value < 0) {
|
||||
neg = 1;
|
||||
value = -value;
|
||||
}
|
||||
|
||||
|
||||
whole = (int) value;
|
||||
tmp = (value - whole) * pow10[prec];
|
||||
frac = (uint32_t)(tmp);
|
||||
diff = tmp - frac;
|
||||
|
||||
if (diff > 0.5) {
|
||||
++frac;
|
||||
/* handle rollover, e.g. case 0.99 with prec 1 is 1.0 */
|
||||
if (frac >= pow10[prec]) {
|
||||
frac = 0;
|
||||
++whole;
|
||||
}
|
||||
} else if (diff == 0.5 && ((frac == 0) || (frac & 1))) {
|
||||
/* if halfway, round up if odd, OR
|
||||
if last digit is 0. That last part is strange */
|
||||
++frac;
|
||||
}
|
||||
|
||||
/* for very large numbers switch back to native sprintf for exponentials.
|
||||
anyone want to write code to replace this? */
|
||||
/*
|
||||
normal printf behavior is to print EVERY whole number digit
|
||||
which can be 100s of characters overflowing your buffers == bad
|
||||
*/
|
||||
if (value > thres_max) {
|
||||
sprintf(str, "%e", neg ? -value : value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prec == 0) {
|
||||
diff = value - whole;
|
||||
if (diff > 0.5) {
|
||||
/* greater than 0.5, round up, e.g. 1.6 -> 2 */
|
||||
++whole;
|
||||
} else if (diff == 0.5 && (whole & 1)) {
|
||||
/* exactly 0.5 and ODD, then round up */
|
||||
/* 1.5 -> 2, but 2.5 -> 2 */
|
||||
++whole;
|
||||
}
|
||||
|
||||
//vvvvvvvvvvvvvvvvvvv Diff from modp_dto2
|
||||
} else if (frac) {
|
||||
count = prec;
|
||||
// now do fractional part, as an unsigned number
|
||||
// we know it is not 0 but we can have leading zeros, these
|
||||
// should be removed
|
||||
while (!(frac % 10)) {
|
||||
--count;
|
||||
frac /= 10;
|
||||
}
|
||||
//^^^^^^^^^^^^^^^^^^^ Diff from modp_dto2
|
||||
|
||||
// now do fractional part, as an unsigned number
|
||||
do {
|
||||
--count;
|
||||
*wstr++ = (char)(48 + (frac % 10));
|
||||
} while (frac /= 10);
|
||||
// add extra 0s
|
||||
while (count-- > 0) *wstr++ = '0';
|
||||
// add decimal
|
||||
*wstr++ = '.';
|
||||
}
|
||||
|
||||
// do whole part
|
||||
// Take care of sign
|
||||
// Conversion. Number is reversed.
|
||||
do *wstr++ = (char)(48 + (whole % 10)); while (whole /= 10);
|
||||
if (neg) {
|
||||
*wstr++ = '-';
|
||||
}
|
||||
*wstr='\0';
|
||||
strreverse(str, wstr-1);
|
||||
}
|
||||
//=================================================================
|
||||
|
||||
/* end of jWrite.c */
|
||||
217
extlibs/jWrite.h
217
extlibs/jWrite.h
|
|
@ -1,217 +0,0 @@
|
|||
//
|
||||
// jWrite.h
|
||||
//
|
||||
// A *really* simple JSON writer in C (C89)
|
||||
// - a collection of functions to generate JSON semi-automatically
|
||||
//
|
||||
// The idea is to simplify writing native C values into a JSON string and
|
||||
// to provide some error trapping to ensure that the result is valid JSON.
|
||||
//
|
||||
// Example:
|
||||
// jwOpen( buffer, buflen, JW_OBJECT, JW_PRETTY ); // open root node as object
|
||||
// jwObj_string( "key", "value" );
|
||||
// jwObj_int( "int", 1 );
|
||||
// jwObj_array( "anArray");
|
||||
// jwArr_int( 0 );
|
||||
// jwArr_int( 1 );
|
||||
// jwArr_int( 2 );
|
||||
// jwEnd();
|
||||
// err= jwClose(); // close root object
|
||||
//
|
||||
// results in:
|
||||
//
|
||||
// {
|
||||
// "key": "value",
|
||||
// "int": 1,
|
||||
// "anArray": [
|
||||
// 0,
|
||||
// 1,
|
||||
// 2
|
||||
// ]
|
||||
// }
|
||||
//
|
||||
// Note that jWrite handles string quoting and getting commas in the right place.
|
||||
// If the sequence of calls is incorrect
|
||||
// e.g.
|
||||
// jwOpen( buffer, buflen, JW_OBJECT, 1 );
|
||||
// jwObj_string( "key", "value" );
|
||||
// jwArr_int( 0 );
|
||||
// ...
|
||||
//
|
||||
// then the error code returned from jwClose() would indicate that you attempted to
|
||||
// put an array element into an object (instead of a key:value pair)
|
||||
// To locate the error, the supplied buffer has the JSON created upto the error point
|
||||
// and a call to jwErrorPos() would return the function call at which the error occurred
|
||||
// - in this case 3, the 3rd function call "jwArr_int(0)" is not correct at this point.
|
||||
//
|
||||
// The root JSON type can be JW_OBJECT or JW_ARRAY.
|
||||
//
|
||||
// For more information on each function, see the prototypes below.
|
||||
//
|
||||
//
|
||||
// GLOBAL vs. Application-Supplied Control Structure
|
||||
// -------------------------------------------------
|
||||
// jWrite requires a jWriteControl structure to save the internal state.
|
||||
// For many applications it is much simpler for this to be a global variable as
|
||||
// used by the above examples.
|
||||
//
|
||||
// To use multiple instances of jWrite, an application has to supply unique instances
|
||||
// of jWriteControl structures.
|
||||
//
|
||||
// This feature is enabled by commenting out the definition of JW_GLOBAL_CONTROL_STRUCT
|
||||
//
|
||||
// All the jWrite functions then take an additional parameter: a ptr to the structure
|
||||
// e.g.
|
||||
// struct jWriteControl jwc;
|
||||
//
|
||||
// jwOpen( &jwc, buffer, buflen, JW_OBJECT, 1 );
|
||||
// jwObj_string( &jwc, "key", "value" );
|
||||
// jwObj_int( &jwc, "int", 1 );
|
||||
// jwObj_array( &jwc, "anArray");
|
||||
// jwArr_int( &jwc, 0 );
|
||||
// jwArr_int( &jwc, 1 );
|
||||
// jwArr_int( &jwc, 2 );
|
||||
// jwEnd( &jwc );
|
||||
// err= jwClose( &jwc );
|
||||
//
|
||||
// - which is more flexible, but a pain to type in !
|
||||
//
|
||||
// TonyWilk, Mar 2015
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef JWRITE_H_
|
||||
#define JWRITE_H_
|
||||
|
||||
|
||||
#define JW_GLOBAL_CONTROL_STRUCT // <--- comment this out to use applic-supplied jWriteControl
|
||||
#define JWRITE_STACK_DEPTH 32 // max nesting depth of objects/arrays
|
||||
|
||||
#define JW_COMPACT 0 // output string control for jwOpen()
|
||||
#define JW_PRETTY 1 // pretty adds \n and indentation
|
||||
|
||||
enum jwNodeType{
|
||||
JW_OBJECT= 1,
|
||||
JW_ARRAY
|
||||
};
|
||||
|
||||
struct jwNodeStack{
|
||||
enum jwNodeType nodeType;
|
||||
int elementNo;
|
||||
};
|
||||
|
||||
struct jWriteControl{
|
||||
char *buffer; // pointer to application's buffer
|
||||
unsigned int buflen; // length of buffer
|
||||
char *bufp; // current write position in buffer
|
||||
char tmpbuf[32]; // local buffer for int/double convertions
|
||||
int error; // error code
|
||||
int callNo; // API call on which error occurred
|
||||
struct jwNodeStack nodeStack[JWRITE_STACK_DEPTH]; // stack of array/object nodes
|
||||
int stackpos;
|
||||
int isPretty; // 1= pretty output (inserts \n and spaces)
|
||||
};
|
||||
|
||||
// Error Codes
|
||||
// -----------
|
||||
#define JWRITE_OK 0
|
||||
#define JWRITE_BUF_FULL 1 // output buffer full
|
||||
#define JWRITE_NOT_ARRAY 2 // tried to write Array value into Object
|
||||
#define JWRITE_NOT_OBJECT 3 // tried to write Object key/value into Array
|
||||
#define JWRITE_STACK_FULL 4 // array/object nesting > JWRITE_STACK_DEPTH
|
||||
#define JWRITE_STACK_EMPTY 5 // stack underflow error (too many 'end's)
|
||||
#define JWRITE_NEST_ERROR 6 // nesting error, not all objects closed when jwClose() called
|
||||
|
||||
|
||||
// API functions
|
||||
// -------------
|
||||
|
||||
// Returns '\0'-termianted string describing the error (as returned by jwClose())
|
||||
//
|
||||
char *jwErrorToString( int err );
|
||||
|
||||
|
||||
#ifdef JW_GLOBAL_CONTROL_STRUCT /* USING GLOBAL g_jWriteControl */
|
||||
|
||||
// jwOpen
|
||||
// - initialises jWrite with the application supplied 'buffer' of length 'buflen'
|
||||
// in operation, the buffer will always contain a valid '\0'-terminated string
|
||||
// - jWrite will not overrun the buffer (it returns an "output buffer full" error)
|
||||
// - rootType is the base JSON type: JW_OBJECT or JW_ARRAY
|
||||
// - isPretty controls 'prettifying' the output: JW_PRETTY or JW_COMPACT
|
||||
void jwOpen( char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty );
|
||||
|
||||
// jwClose
|
||||
// - closes the element opened by jwOpen()
|
||||
// - returns error code (0 = JWRITE_OK)
|
||||
// - after an error, all following jWrite calls are skipped internally
|
||||
// so the error code is for the first error detected
|
||||
int jwClose( );
|
||||
|
||||
// jwErrorPos
|
||||
// - if jwClose returned an error, this function returns the number of the jWrite function call
|
||||
// which caused that error.
|
||||
int jwErrorPos( );
|
||||
|
||||
// Object insertion functions
|
||||
// - used to insert "key":"value" pairs into an object
|
||||
//
|
||||
void jwObj_string( char *key, char *value );
|
||||
void jwObj_int( char *key, int value );
|
||||
void jwObj_double( char *key, double value );
|
||||
void jwObj_bool( char *key, int oneOrZero );
|
||||
void jwObj_null( char *key );
|
||||
void jwObj_object( char *key );
|
||||
void jwObj_array( char *key );
|
||||
|
||||
// Array insertion functions
|
||||
// - used to insert "value" elements into an array
|
||||
//
|
||||
void jwArr_string( char *value );
|
||||
void jwArr_int( int value );
|
||||
void jwArr_double( double value );
|
||||
void jwArr_bool( int oneOrZero );
|
||||
void jwArr_null( );
|
||||
void jwArr_object( );
|
||||
void jwArr_array( );
|
||||
|
||||
// jwEnd
|
||||
// - defines the end of an Object or Array definition
|
||||
int jwEnd( );
|
||||
|
||||
|
||||
// these 'raw' routines write the JSON value as the contents of rawtext
|
||||
// i.e. enclosing quotes are not added
|
||||
// - use if your app. supplies its own value->string functions
|
||||
//
|
||||
void jwObj_raw( char *key, char *rawtext );
|
||||
void jwArr_raw( char *rawtext );
|
||||
|
||||
#else /* JW_GLOBAL_CONTROL_STRUCT not defined */
|
||||
// Same API functions with app-supplied control struct option
|
||||
//
|
||||
void jwOpen( struct jWriteControl *jwc, char *buffer, unsigned int buflen, enum jwNodeType rootType, int isPretty );
|
||||
int jwClose( struct jWriteControl *jwc );
|
||||
int jwErrorPos( struct jWriteControl *jwc );
|
||||
void jwObj_string( struct jWriteControl *jwc, char *key, char *value );
|
||||
void jwObj_int( struct jWriteControl *jwc, char *key, int value );
|
||||
void jwObj_double( struct jWriteControl *jwc, char *key, double value );
|
||||
void jwObj_bool( struct jWriteControl *jwc, char *key, int oneOrZero );
|
||||
void jwObj_null( struct jWriteControl *jwc, char *key );
|
||||
void jwObj_object( struct jWriteControl *jwc, char *key );
|
||||
void jwObj_array( struct jWriteControl *jwc, char *key );
|
||||
void jwArr_string( struct jWriteControl *jwc, char *value );
|
||||
void jwArr_int( struct jWriteControl *jwc, int value );
|
||||
void jwArr_double( struct jWriteControl *jwc, double value );
|
||||
void jwArr_bool( struct jWriteControl *jwc, int oneOrZero );
|
||||
void jwArr_null( struct jWriteControl *jwc );
|
||||
void jwArr_object( struct jWriteControl *jwc );
|
||||
void jwArr_array( struct jWriteControl *jwc );
|
||||
int jwEnd( struct jWriteControl *jwc );
|
||||
void jwObj_raw( struct jWriteControl *jwc, char *key, char *rawtext );
|
||||
void jwArr_raw( struct jWriteControl *jwc, char *rawtext );
|
||||
|
||||
#endif /* JW_GLOBAL_CONTROL_STRUCT */
|
||||
|
||||
#endif /*JWRITE_H_ */
|
||||
/* end of jWrite.h */
|
||||
Loading…
Reference in New Issue
Block a user