/*H*************************************************************************************/
/*!
\File jsonformat.c
\Description
This module formats simple JSON, in a linear fashion, using a character buffer
that the client provides.
\Copyright
Copyright (c) Electronic Arts 2012.
\Notes
\verbatim
When JsonInit() is called, a 24-bytes header is added at the beginning of the
client-provided buffer. The header is an ascii string that looks like this:
where:
AAAAAAAA is the offset field
BBBBBBBB is the buffer size field
CC is the indent field
DD is the flag field
Those first 24 bytes are replaced by whitespaces when JsonFinish() is called.
Required buffer capacity for json = ??? length(JSON) + 1.
That means if length(buffer) <= length(JSON), buffer-overrun occurs.
\endverbatim
\Version 12/11/2012 (jbrookes) First Version, based on XmlFormat
*/
/*************************************************************************************H*/
/*** Include files *********************************************************************/
#include
#include
#include
#include "DirtySDK/platform.h"
#include "DirtySDK/dirtysock/dirtylib.h"
#include "DirtySDK/util/jsonformat.h"
/*** Defines ***************************************************************************/
#define JSON_HEADER_LEN (22)
#define JSON_MAX_TAG_LENGTH (128)
#define JSON_MAX_FLOAT_LENGTH (128)
#define JSON_MAX_DATE_LENGTH (32)
/*** Type Definitions ******************************************************************/
/*** Variables *************************************************************************/
static const uint8_t _Json_Hex2AsciiTable[16] = "0123456789abcdef";
// ascii->hex conversion table for 7bit ASCII characters
static const uint8_t _Json_Ascii2HexTable[128] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
/*! 7bit ASCII characters that should be encoded ([0..31], ", ?, \)
characters that are represented with a 1 are escaped in long form
(u00xy) whereas characters represented with a 2 are escaped with
a single backslash */
static const uint8_t _Json_EncodeStringTable[128] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, // 00-0F
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10-1F
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20-2F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, // 30-3F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40-4F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 50-5F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60-6F
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, // 70-7F
};
static int32_t _iLastIndent; //$$TODO - clean this up (put it in table?)
/*** Private Functions *****************************************************************/
/*F*************************************************************************************/
/*!
\Function _JsonGet8
\Description
Get 8bit value from given offset in buffer
\Input *pJson - pointer to start of buffer
\Input iOffset - offset within buffer to get value from
\Output
int32_t - value
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonGet8(const char *pJson, int32_t iOffset)
{
return((_Json_Ascii2HexTable[(int32_t)pJson[iOffset+0]] << 4) |
_Json_Ascii2HexTable[(int32_t)pJson[iOffset+1]]);
}
/*F*************************************************************************************/
/*!
\Function _JsonSet8
\Description
Save 8bit value in buffer
\Input *pJson - pointer to start of buffer
\Input iOffset - offset to save at
\Input iValue - value to save
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static void _JsonSet8(char *pJson, int32_t iOffset, int32_t iValue)
{
pJson[iOffset+0] = _Json_Hex2AsciiTable[(iValue >> 4) & 15];
pJson[iOffset+1] = _Json_Hex2AsciiTable[(iValue >> 0) & 15];
}
/*F*************************************************************************************/
/*!
\Function _JsonGet32
\Description
Get 32bit value from given offset in buffer
\Input *pJson - pointer to start of buffer
\Input iOffset - offset within buffer to get value from
\Output
int32_t - value
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonGet32(char *pJson, int32_t iOffset)
{
return((_Json_Ascii2HexTable[(int32_t)pJson[iOffset+0]] << 28) |
(_Json_Ascii2HexTable[(int32_t)pJson[iOffset+1]] << 24) |
(_Json_Ascii2HexTable[(int32_t)pJson[iOffset+2]] << 20) |
(_Json_Ascii2HexTable[(int32_t)pJson[iOffset+3]] << 16) |
(_Json_Ascii2HexTable[(int32_t)pJson[iOffset+4]] << 12) |
(_Json_Ascii2HexTable[(int32_t)pJson[iOffset+5]] << 8) |
(_Json_Ascii2HexTable[(int32_t)pJson[iOffset+6]] << 4) |
_Json_Ascii2HexTable[(int32_t)pJson[iOffset+7]]);
}
/*F*************************************************************************************/
/*!
\Function _JsonSet32
\Description
Save 32-bit value at given offset in buffer
\Input *pJson - pointer to start of buffer
\Input iOffset - offset into buffer to save
\Input iValue - value to save into buffer
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static void _JsonSet32(char *pJson, int32_t iOffset, int32_t iValue)
{
pJson[iOffset+0] = _Json_Hex2AsciiTable[(iValue>>28)&15];
pJson[iOffset+1] = _Json_Hex2AsciiTable[(iValue>>24)&15];
pJson[iOffset+2] = _Json_Hex2AsciiTable[(iValue>>20)&15];
pJson[iOffset+3] = _Json_Hex2AsciiTable[(iValue>>16)&15];
pJson[iOffset+4] = _Json_Hex2AsciiTable[(iValue>>12)&15];
pJson[iOffset+5] = _Json_Hex2AsciiTable[(iValue>>8)&15];
pJson[iOffset+6] = _Json_Hex2AsciiTable[(iValue>>4)&15];
pJson[iOffset+7] = _Json_Hex2AsciiTable[(iValue>>0)&15];
}
/*F*************************************************************************************/
/*!
\Function _JsonGetOffset
\Description
Get offset from buffer
\Input *pJson - pointer to start of buffer
\Output
int32_t - offset
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonGetOffset(char *pJson)
{
return(_JsonGet32(pJson, 1));
}
/*F*************************************************************************************/
/*!
\Function _JsonGetLength
\Description
Get length from buffer
\Input *pJson - pointer to start of buffer
\Output
int32_t - length
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonGetLength(char *pJson)
{
return(_JsonGet32(pJson, 9));
}
/*F*************************************************************************************/
/*!
\Function _JsonGetIndent
\Description
Get indent level from buffer
\Input *pJson - pointer to start of buffer
\Output
int32_t - indent
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonGetIndent(char *pJson)
{
return(_JsonGet8(pJson, 17));
}
/*F*************************************************************************************/
/*!
\Function _JsonGetFlags
\Description
Get flags from buffer
\Input *pJson - pointer to start of buffer
\Output
int32_t - flags
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonGetFlags(const char *pJson)
{
return(_JsonGet8(pJson, 19));
}
/*F*************************************************************************************/
/*!
\Function _JsonSetOffset
\Description
Save offset in buffer
\Input *pJson - pointer to start of buffer
\Input iOffset - offset to save in buffer
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static void _JsonSetOffset(char *pJson, int32_t iOffset)
{
_JsonSet32(pJson, 1, iOffset);
}
/*F*************************************************************************************/
/*!
\Function _JsonSetLength
\Description
Save length in buffer
\Input *pJson - pointer to start of buffer
\Input iLength - length to save in buffer
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static void _JsonSetLength(char *pJson, int32_t iLength)
{
_JsonSet32(pJson, 9, iLength);
}
/*F*************************************************************************************/
/*!
\Function _JsonSetIndent
\Description
Save indent level in buffer
\Input *pJson - pointer to start of buffer
\Input iIndent - indent level to save
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static void _JsonSetIndent(char *pJson, int32_t iIndent)
{
_JsonSet8(pJson, 17, iIndent);
}
/*F*************************************************************************************/
/*!
\Function _JsonSetFlags
\Description
Save flags in buffer
\Input *pJson - pointer to start of buffer
\Input iFlags - flags
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static void _JsonSetFlags(char *pJson, int32_t iFlags)
{
_JsonSet8(pJson, 19, iFlags);
}
/*F*************************************************************************************/
/*!
\Function _JsonValidHeader
\Description
Check whether buffer is initialized with a valid header.
\Input *pBuffer - buffer to which JSON will be written
\Output
int32_t - returns 1 if there is a valid header, 0 otherwise
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonValidHeader(const char *pBuffer)
{
return((pBuffer[0] != '{') ? 0 : 1);
}
/*F*************************************************************************************/
/*!
\Function _JsonOpenTag
\Description
Check whether there is an open tag in the buffer.
\Input *pBuffer - buffer to which JSON will be written
\Input iOffset - current offset within the buffer
\Output
int32_t - returns 1 if an open tag exists in the buffer, 0 otherwise
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonOpenTag(const char *pBuffer, int32_t iOffset)
{
int32_t iBackPos;
int32_t iBeginCount = 0;
int32_t iEndCount = 0;
for (iBackPos = (iOffset-1); iBackPos >= JSON_HEADER_LEN; iBackPos--)
{
// Count all closing tags (ended tag with no children will be counted as starting and closing tag)
if (pBuffer[iBackPos] == '}')
{
iEndCount++;
iBackPos--;
}
// Count all starting tags
else if (pBuffer[iBackPos] == '{')
{
iBeginCount++;
// If there are more open tags than close tags, return true
if (iBeginCount > iEndCount)
{
return(1);
}
}
}
return(0);
}
/*F*************************************************************************************/
/*!
\Function _JsonUpdateHeader
\Description
Update the JSON header to reflect the number of characters written. If the buffer
was full, this function will update the header to indicate the full buffer and will
return the JSON_ERR_FULL error code.
\Input *pBuffer - buffer to which json will be written
\Input iNumChars - number of characters written, -1 for buffer filled
\Input iLength - length of the buffer
\Input iOffset - current offset within the buffer
\Input iIndent - current indent level
\Output
int32_t - returns JSON_ERR_FULL if buffer was filled, JSON_ERR_NONE otherwise
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonUpdateHeader(char *pBuffer, int32_t iNumChars, int32_t iLength, int32_t iOffset, int32_t iIndent)
{
iOffset += iNumChars;
// leave room for null character
iLength -= 1;
if ((iNumChars < 0) || (iOffset > iLength))
{
pBuffer[iLength] = '\0';
iOffset = iLength;
_JsonSetOffset(pBuffer, iOffset);
return(JSON_ERR_FULL);
}
else
{
_iLastIndent = _JsonGetIndent(pBuffer);
_JsonSetOffset(pBuffer, iOffset);
_JsonSetIndent(pBuffer, iIndent);
return(JSON_ERR_NONE);
}
}
/*F****************************************************************************/
/*!
\Function _JsonIndent
\Description
Add the appropriate level of whitespace to indent the current tag if
the whitespace flag is enabled for the buffer.
\Input *pBuffer - buffer to which JSON will be written
\Input iLength - length of the buffer
\Input iOffset - current offset within the buffer
\Input iIndent - current indent level
\Input iFlags - flags
\Output
int32_t - negative=buffer overrun, zero/positive=number of characters written
\Version 12/11/2012 (jbrookes)
*/
/****************************************************************************F*/
static int32_t _JsonIndent(char *pBuffer, int32_t iLength, int32_t iOffset, int32_t iIndent, int32_t iFlags)
{
int32_t iNumChars = -1;
#if DIRTYCODE_LOGGING
if (iIndent < 0)
{
NetPrintf(("jsonformat: warning -- invalid indent level (%d) in _JsonIndent\n", iIndent));
}
#endif
if ((iFlags & JSON_FL_WHITESPACE) == 0)
{
iNumChars = 0;
}
else if (iIndent <= 0)
{
if ((iLength - iOffset) > 1)
{
iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\n");
}
}
else
{
if ((iLength - iOffset) > ((iIndent * 2) + 1))
{
iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\n%*c", iIndent * 2, ' ');
}
}
return(iNumChars);
}
#if 0 //UNUSED?
/*F*************************************************************************************/
/*!
\Function _JsonFormatInsert
\Description
Insert a preformatted item
\Input *pBuffer - the JSON buffer
\Input *pValue - raw string to insert (must be correctly preformatted)
\Output
int32_t - return code.
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonFormatInsert(char *pBuffer, const char *pValue)
{
int32_t iWidth, iCount;
int32_t iOffset, iLength, iIndent, iFlags;
// make sure there is a header and extract fields
if (!_JsonValidHeader(pBuffer))
return(JSON_ERR_UNINIT);
iOffset = _JsonGetOffset(pBuffer);
iLength = _JsonGetLength(pBuffer);
iIndent = _JsonGetIndent(pBuffer);
iFlags = _JsonGetFlags(pBuffer);
// there must be an open tag in the buffer
if (!_JsonOpenTag(pBuffer, iOffset))
{
return(JSON_ERR_NOT_OPEN);
}
// check if there's enough room for the insertion to complete successfully
iWidth = (int32_t)strlen(pValue);
// figure out indent size
iWidth += ((pValue[0] == '{') && (iFlags & JSON_FL_WHITESPACE)) ? (1 + iIndent * 2) : 0;
// we must be able to insert completely
if ((iLength - iOffset) <= iWidth)
{
return(JSON_ERR_FULL);
}
// buffer is good, now start to determine the insertion type
// 1. value, handle indent if necessary
if (pValue[0] == '{')
{
if (iFlags & JSON_FL_WHITESPACE)
{
pBuffer[iOffset++] = '\n';
for (iCount = 0; iCount < iIndent; ++iCount)
{
pBuffer[iOffset++] = ' ';
pBuffer[iOffset++] = ' ';
}
}
}
// 2. attr="value">
else if (strchr(pValue, '"') != NULL)
{
// the tag must be open for accepting attributes (no children and no element text set)
if (!_JsonOpenForAttr(pBuffer, iOffset))
{
return(JSON_ERR_ATTR_POSITION);
}
// replace the close tag '>' to ' ' for input (pValue[] must look like: attr="value">)
pBuffer[iOffset - 1] = ' ';
}
// for all insertion types, insert the string
for (iCount = 0; pValue[iCount] != 0; ++iCount)
{
pBuffer[iOffset++] = pValue[iCount];
}
pBuffer[iOffset] = 0;
// update offset and return
_JsonSetOffset(pBuffer, iOffset);
return(JSON_ERR_NONE);
}
#endif
/*F*************************************************************************************/
/*!
\Function _JsonEncodeString
\Description
Encode a string as needed for reserved characters
\Input *pBuffer - the JSON buffer
\Input iLength - number of eight-bit characters available in buffer
\Input *_pSrc - source string to add to buffer
\Output
int32_t - negative=not enough buffer, zero/positive=encoded length.
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonEncodeString(char *pBuffer, int32_t iLength, const char *_pSrc)
{
char * const pBackup = pBuffer;
const uint8_t *pSource;
int32_t iRemain, iEncodeType;
if (_pSrc == NULL)
{
_pSrc = "null";
}
pSource = (const uint8_t *)_pSrc;
// encode the string
for (iRemain = iLength; (iRemain > 1) && (*pSource != '\0'); ++pSource)
{
if ((*pSource < 128) && ((iEncodeType = _Json_EncodeStringTable[*pSource]) != 0))
{
if ((iEncodeType == 2) && (iRemain > 6))
{
// hex encode all out of range values
*pBuffer++ = '\\';
*pBuffer++ = 'u';
*pBuffer++ = '0';
*pBuffer++ = '0';
*pBuffer++ = _Json_Hex2AsciiTable[(*pSource)>>4];
*pBuffer++ = _Json_Hex2AsciiTable[(*pSource)&0xF];
iRemain -= 6;
}
else if ((iEncodeType == 1) && (iRemain > 1))
{
*pBuffer++ = '\\';
*pBuffer++ = *pSource;
}
else // buffer overrun
{
break;
}
}
else
{
// normal encoding
*pBuffer++ = *pSource;
--iRemain;
}
}
// make sure space for terminator
if (iRemain > 0)
{
*pBuffer = 0;
}
// if character(s) remains, return negative to indicate buffer-overrun.
if (*pSource != '\0')
{
// if buffer has been changed, reset the change(s)
if (pBackup != pBuffer)
{
*pBackup = '\0';
}
return(-1);
}
// return encoded size
return(iLength-iRemain);
}
/*F*************************************************************************************/
/*!
\Function _JsonAddStr
\Description
Adds a string element
\Input *pBuffer - JSON buffer
\Input *pElemName - name of the element (may be null)
\Input *pValue - value of the element
\Input bQuote - TRUE if value should be quoted
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
static int32_t _JsonAddStr(char *pBuffer, const char *pElemName, const char *pValue, uint8_t bQuote)
{
int32_t iOffset, iLength, iNumChars, iIndent, iFlags, iTempLength;
if (!_JsonValidHeader(pBuffer))
{
return(JSON_ERR_UNINIT);
}
iOffset = _JsonGetOffset(pBuffer);
iLength = _JsonGetLength(pBuffer);
iIndent = _JsonGetIndent(pBuffer);
iFlags = _JsonGetFlags(pBuffer);
// there must be an open tag in the buffer
if (_JsonOpenTag(pBuffer, iOffset))
{
iNumChars = 0;
if (iIndent <= _iLastIndent)
{
if ((iTempLength = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, ",")) <= 0)
{
pBuffer[iOffset] = '\0'; // reset all changes
return(JSON_ERR_FULL);
}
iNumChars = iTempLength;
}
if ((iTempLength = _JsonIndent(pBuffer+iNumChars, iLength-iNumChars, iOffset, iIndent, iFlags)) < 0)
{
return(JSON_ERR_FULL);
}
iNumChars += iTempLength;
// add element name
if (pElemName != NULL)
{
if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "\"%s\":", pElemName)) <= 0)
{
pBuffer[iOffset] = '\0'; // reset all changes
return(JSON_ERR_FULL);
}
iNumChars += iTempLength;
}
// add leading quote
if (bQuote)
{
if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "\"")) <= 0)
{
pBuffer[iOffset] = '\0'; // reset all changes
return(JSON_ERR_FULL);
}
iNumChars += iTempLength;
}
// add element value
if ((iTempLength = _JsonEncodeString(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, pValue)) < 0)
{
pBuffer[iOffset] = '\0'; // reset all changes
return(JSON_ERR_FULL);
}
iNumChars += iTempLength;
// add trailing quote
if (bQuote)
{
if ((iTempLength = ds_snzprintf(pBuffer+iOffset+iNumChars, iLength-iOffset-iNumChars, "\"")) <= 0)
{
pBuffer[iOffset] = '\0'; // reset all changes
return(JSON_ERR_FULL);
}
iNumChars += iTempLength;
}
// all done successfully
return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent));
}
else
{
return(JSON_ERR_NOT_OPEN);
}
}
/*** Public Functions ******************************************************************/
/*F*************************************************************************************/
/*!
\Function JsonInit
\Description
Initialize the Json Buffer. This MUST be called prior to any other calls.
Client can allocate the buffer on stack, but should not access it directly.
(see JsonData()).
\Input *pBuffer - buffer to which JSON will be written.
\Input iBufLen – size of buffer passed in.
\Input uFlags - encoding flags (JSONFORMAT_FL_xxx)
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
void JsonInit(char *pBuffer, int32_t iBufLen, unsigned char uFlags)
{
int32_t iOffset;
ds_memclr(pBuffer, iBufLen);
if (iBufLen > JSON_HEADER_LEN)
{
iOffset = ds_snzprintf(pBuffer, iBufLen - 1, "{00000000000000000000}{");
_JsonSetOffset(pBuffer, iOffset);
_JsonSetLength(pBuffer, iBufLen);
_JsonSetFlags(pBuffer, uFlags);
_JsonSetIndent(pBuffer, 1);
_iLastIndent = 0;
}
}
/*F*************************************************************************************/
/*!
\Function JsonBufSizeIncrease
\Description
Notify the jsonformat API that the buffer size was increased.
\Input *pBuffer - buffer to which JSON is being written.
\Input iNewBufLen – new size for that buffer
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
void JsonBufSizeIncrease(char *pBuffer, int32_t iNewBufLen)
{
_JsonSetLength(pBuffer, iNewBufLen);
}
/*F*************************************************************************************/
/*!
\Function JsonFinish
\Description
Signal completion of output to this buffer. Clear the JSON API hidden
data.
\Input *pBuffer – buffer to which JSON was written.
\Output char* - pointer to real JSON data (skipping past header)
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
char *JsonFinish(char *pBuffer)
{
if (_JsonValidHeader(pBuffer))
{
int32_t iOffset = _JsonGetOffset(pBuffer);
int32_t iBufLen = _JsonGetLength(pBuffer);
int32_t iFlags = _JsonGetFlags(pBuffer);
// add final close tag
if (iFlags == JSON_FL_WHITESPACE)
{
ds_snzprintf(pBuffer + iOffset, iBufLen - iOffset, "\n}");
}
else
{
ds_snzprintf(pBuffer + iOffset, iBufLen - iOffset, "}");
}
// remove header
ds_memset(pBuffer, ' ', JSON_HEADER_LEN);
pBuffer += JSON_HEADER_LEN;
}
return(pBuffer);
}
/*F*************************************************************************************/
/*!
\Function JsonObjectStart
\Description
Start a JSON object, to which you can add other objects or elements. Must be
ended with JsonObjectEnd.
\Input *pBuffer - JSON buffer
\Input *pName - name of the object (may be null)
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonObjectStart(char *pBuffer, const char *pName)
{
int32_t iOffset, iLength, iNumChars, iIndent, iFlags;
if (!_JsonValidHeader(pBuffer))
{
return(JSON_ERR_UNINIT);
}
iOffset = _JsonGetOffset(pBuffer);
iLength = _JsonGetLength(pBuffer);
iIndent = _JsonGetIndent(pBuffer);
iFlags = _JsonGetFlags(pBuffer);
if (iIndent <= _iLastIndent)
{
if ((iNumChars = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, ",")) <= 0)
{
pBuffer[iOffset] = '\0'; // reset all changes
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
}
if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
if (pName != NULL)
{
if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\"%s\":", pName)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
}
if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent++, iFlags)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "{")) < 0)
{
return(JSON_ERR_FULL);
}
return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent));
}
/*F*************************************************************************************/
/*!
\Function JsonObjectEnd
\Description
End the current object -- must have an outstanding open tag.
\Input *pBuffer - JSON buffer.
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonObjectEnd(char *pBuffer)
{
int32_t iOffset, iLength, iNumChars;
int32_t iIndent, iFlags;
if (!_JsonValidHeader(pBuffer))
{
return(JSON_ERR_UNINIT);
}
iOffset = _JsonGetOffset(pBuffer);
iLength = _JsonGetLength(pBuffer);
iFlags = _JsonGetFlags(pBuffer);
iIndent = _JsonGetIndent(pBuffer) - 1;
if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
iNumChars = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, "}");
return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent));
}
/*F*************************************************************************************/
/*!
\Function JsonArrayStart
\Description
Start a JSON array, to which you can add other objects or elements. Must be
ended with JsonArrayEnd.
\Input *pBuffer - JSON buffer
\Input *pName - name of the array (may be null)
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonArrayStart(char *pBuffer, const char *pName)
{
int32_t iOffset, iLength, iNumChars, iIndent, iFlags;
if (!_JsonValidHeader(pBuffer))
{
return(JSON_ERR_UNINIT);
}
iOffset = _JsonGetOffset(pBuffer);
iLength = _JsonGetLength(pBuffer);
iIndent = _JsonGetIndent(pBuffer);
iFlags = _JsonGetFlags(pBuffer);
iNumChars = 0;
if (iIndent <= _iLastIndent)
{
int32_t iTempLength;
if ((iTempLength = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, ",")) <= 0)
{
pBuffer[iOffset] = '\0'; // reset all changes
return(JSON_ERR_FULL);
}
iNumChars = iTempLength;
}
iOffset += iNumChars;
if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
if (pName != NULL)
{
if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "\"%s\":", pName)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
}
if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent++, iFlags)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
if ((iNumChars = ds_snzprintf(pBuffer + iOffset, iLength - iOffset, "[")) < 0)
{
return(JSON_ERR_FULL);
}
return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent));
}
/*F*************************************************************************************/
/*!
\Function JsonArrayEnd
\Description
End the current array -- must have an outstanding open array
\Input *pBuffer - JSON buffer
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonArrayEnd(char *pBuffer)
{
int32_t iOffset, iLength, iNumChars;
int32_t iIndent, iFlags;
if (!_JsonValidHeader(pBuffer))
{
return(JSON_ERR_UNINIT);
}
iOffset = _JsonGetOffset(pBuffer);
iLength = _JsonGetLength(pBuffer);
iFlags = _JsonGetFlags(pBuffer);
iIndent = _JsonGetIndent(pBuffer) - 1;
if ((iNumChars = _JsonIndent(pBuffer, iLength, iOffset, iIndent, iFlags)) < 0)
{
return(JSON_ERR_FULL);
}
iOffset += iNumChars;
iNumChars = ds_snzprintf(pBuffer+iOffset, iLength-iOffset, "]");
return(_JsonUpdateHeader(pBuffer, iNumChars, iLength, iOffset, iIndent));
}
/*F*************************************************************************************/
/*!
\Function JsonAddStr
\Description
Adds a string element
\Input *pBuffer - JSON buffer
\Input *pElemName - name of the element (may be null)
\Input *pValue - value of the element
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonAddStr(char *pBuffer, const char *pElemName, const char *pValue)
{
return(_JsonAddStr(pBuffer, pElemName, pValue, TRUE));
}
/*F*************************************************************************************/
/*!
\Function JsonAddInt
\Description
Add an integer element
\Input *pBuffer - JSON buffer
\Input *pElemName - name of the element
\Input iValue - integer value of the element
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonAddInt(char *pBuffer, const char *pElemName, int64_t iValue)
{
char strInt[64];
ds_snzprintf(strInt, sizeof(strInt), "%lld", iValue);
return(_JsonAddStr(pBuffer, pElemName, strInt, FALSE));
}
/*F*************************************************************************************/
/*!
\Function JsonAddNum
\Description
Add a complete, contained decimal element. Builds start and end tag.
Nothing can be appended to this tag. Format it using formatSpec.
\Input *pBuffer - JSON buffer
\Input *pElemName - name of the element
\Input *pFormatSpec - format spec for formatting the float (see printf)
\Input fValue - float value of the element
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonAddNum(char *pBuffer, const char *pElemName, const char *pFormatSpec, float fValue)
{
char strFloat[JSON_MAX_FLOAT_LENGTH];
if (ds_snzprintf(strFloat, sizeof(strFloat), pFormatSpec, fValue) <= 0)
{
return(JSON_ERR_INVALID_PARAM);
}
return(_JsonAddStr(pBuffer, pElemName, strFloat, FALSE));
}
/*F*************************************************************************************/
/*!
\Function JsonAddDate
\Description
Add a date (will be encoded as a string; use JsonAddInteger for integer encoding)
\Input *pBuffer - JSON buffer
\Input *pElemName - name of the element
\Input uEpochDate - date/time since epoch
\Output
int32_t - JSON_ERR_*
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
int32_t JsonAddDate(char *pBuffer, const char *pElemName, uint32_t uEpochDate)
{
struct tm *pTime, Time;
char strDate[JSON_MAX_DATE_LENGTH];
if ((pTime = ds_secstotime(&Time, uEpochDate)) == NULL)
{
return(JSON_ERR_INVALID_PARAM);
}
ds_timetostr(pTime, TIMETOSTRING_CONVERSION_ISO_8601, FALSE, strDate, sizeof(strDate));
return(_JsonAddStr(pBuffer, pElemName, strDate, FALSE));
}
/*F*************************************************************************************/
/*!
\Function JsonFormatVPrintf
\Description
Takes a format string and variable parameter list and formats into syntax
correct JSON. The "v" (variable args) version can be called by other
functions that use "..." in the their prototype.
\Input *pJsonBuff - JSON output buffer
\Input iBufLen - length of Json output buffer (negative=append to existing buffer)
\Input *pFormat - printf style format string
\Input pFmtArgs - variable argument list (use va_start / va_end to get)
\Output
char * - pointer to formatted buffer
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
char *JsonFormatVPrintf(char *pJsonBuff, int32_t iBufLen, const char *pFormat, va_list pFmtArgs)
{
#if 0
int32_t iToken;
int32_t iIndex;
char strName[65];
const char *pName;
const char *pParse;
unsigned char uFlags = 0;
/* table of allowed name characters. see http://www.w3.org/TR/xml/#charsets
for information on name characters allowed by the XML 1.0 specification */
static char _ConvName[128] =
" " " "
" -. " "0123456789: "
" ABCDEFGHIJKLMNO" "PQRSTUVWXYZ _"
" abcdefghijklmno" "pqrstuvwxyz ";
// determine if whitespace indenting is desired
if (*pFormat == ' ')
{
uFlags |= JSON_FL_WHITESPACE;
++pFormat;
}
// start the formatting
if (iBufLen >= 0)
JsonInit(pJsonBuff, iBufLen, uFlags);
// parse the format string
for (pParse = pFormat; *pParse != 0; ++pParse)
{
// look for tag open
if (pParse[0] == '{')
{
for (iIndex = 0; (iIndex < (signed)(sizeof(strName) - 1)) && (pParse[iIndex+1] > 0) && (pParse[iIndex+1] < 127); ++iIndex)
{
if (_ConvName[(int32_t)pParse[iIndex+1]] <= ' ')
break;
strName[iIndex] = pParse[iIndex+1];
}
strName[iIndex] = 0;
if (iIndex > 0)
{
JsonObjStart(pJsonBuff, strName);
}
}
// parse name for assignments
if (pParse[0] == '=')
{
// find start of name
for (pName = pParse; (pName != pFormat) && (pName[-1] > 0) && (pName[-1] < 127); --pName)
{
if (_ConvName[pName[-1]&127] <= ' ')
break;
}
// copy and format name in private buffer
for (iIndex = 0; (iIndex < (signed)(sizeof(strName) - 1)) && (pName+iIndex != pParse); ++iIndex)
{
strName[iIndex] = pName[iIndex];
}
strName[iIndex] = 0;
}
// grab next 3 characters as a token
iToken = ' ';
iToken = (iToken << 8) | pParse[0];
iToken = (iToken << 8) | pParse[1];
iToken = (iToken << 8) | pParse[2];
// handle format tokens
if (pParse[0] == '}')
{
JsonObjEnd(pJsonBuff);
}
else if (iToken == ' >%s')
{
const char *pString = va_arg(pFmtArgs, const char *);
JsonElemSetString(pJsonBuff, pString);
}
else if (iToken == ' >%d')
{
int32_t iInteger = va_arg(pFmtArgs, int32_t);
JsonElemSetInt(pJsonBuff, iInteger);
}
else if (iToken == ' >%e')
{
uint32_t uEpoch = va_arg(pFmtArgs, uint32_t);
if (uEpoch == 0)
uEpoch = (uint32_t)ds_timeinsecs();
JsonElemSetDate(pJsonBuff, uEpoch);
}
else if (iToken == ' >%a')
{
uint32_t uAddr = va_arg(pFmtArgs, uint32_t);
JsonElemSetAddr(pJsonBuff, uAddr);
}
else if (strName[0] == 0)
{
}
else if (iToken == ' =%s')
{
const char *pString = va_arg(pFmtArgs, const char *);
JsonAttrSetString(pJsonBuff, strName, pString);
}
else if (iToken == ' =%d')
{
int32_t iInteger = va_arg(pFmtArgs, int32_t);
JsonAttrSetInt(pJsonBuff, strName, iInteger);
}
else if (iToken == ' =%e')
{
uint32_t uEpoch = va_arg(pFmtArgs, uint32_t);
if (uEpoch == 0)
uEpoch = (uint32_t)ds_timeinsecs();
JsonAttrSetDate(pJsonBuff, strName, uEpoch);
}
else if (iToken == ' =%a')
{
uint32_t uAddr = va_arg(pFmtArgs, uint32_t);
JsonAttrSetAddr(pJsonBuff, strName, uAddr);
}
}
// finish up and return data pointer
if (iBufLen >= 0)
pJsonBuff = JsonFinish(pJsonBuff);
return(pJsonBuff);
#else
return(NULL);
#endif
}
/*F*************************************************************************************/
/*!
\Function JsonFormatPrintf
\Description
Takes a format string and variable parameter list and formats into syntax
correct JSON.
\Input *pJsonBuff - JSON output buffer
\Input iBufLen - length of Json output buffer (negative=append to existing buffer)
\Input *pFormat - printf style format string
\Output
char * - pointer to formatted buffer
\Version 12/11/2012 (jbrookes)
*/
/*************************************************************************************F*/
char *JsonFormatPrintf(char *pJsonBuff, int32_t iBufLen, const char *pFormat, ...)
{
char *pResult;
va_list pFmtArgs;
// setup varargs and call real function
va_start(pFmtArgs, pFormat);
pResult = JsonFormatVPrintf(pJsonBuff, iBufLen, pFormat, pFmtArgs);
va_end(pFmtArgs);
// return pointer to start of valid JSON data (skips leading spaces)
return(pResult);
}