445 lines
9.9 KiB
C
445 lines
9.9 KiB
C
/****************************************************************************
|
|
* This file is part of the project AqHome.
|
|
* AqHome (c) by 2024 Martin Preuss, all rights reserved.
|
|
*
|
|
* The license for this file can be found in the file COPYING which you
|
|
* should have received along with this file.
|
|
****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
|
|
|
|
#include "./vars_p.h"
|
|
#include "./vars_dbread.h"
|
|
#include "aqhome/data/path.h"
|
|
|
|
#include <gwenhywfar/misc.h>
|
|
#include <gwenhywfar/text.h>
|
|
#include <gwenhywfar/debug.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* definitions
|
|
* ------------------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
const uint32_t _textFlags=
|
|
GWEN_TEXT_FLAGS_DEL_LEADING_BLANKS |
|
|
GWEN_TEXT_FLAGS_DEL_TRAILING_BLANKS |
|
|
GWEN_TEXT_FLAGS_DEL_QUOTES;
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* forward declarations
|
|
* ------------------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
static const char *_readLine(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf);
|
|
static const char *_readGroupOrVar(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf);
|
|
static const char *_contAsTypedVar(AQH_VARS **pVars, const char *src, const char *tname, GWEN_BUFFER *wbuf);
|
|
static const char *_contAsVar(AQH_VARS **pVars, AQH_VARS_DATATYPE dataType, const char *src, const char *vname, GWEN_BUFFER *wbuf);
|
|
static const char *_readValue(AQH_VARS *vtVar, AQH_VARS_DATATYPE dataType, const char *s, GWEN_BUFFER *wbuf);
|
|
static AQH_VARS *_mkStringValue(const char *s);
|
|
static AQH_VARS *_mkIntValue(const char *s);
|
|
static AQH_VARS *_mkDoubleValue(const char *s);
|
|
static const char *_getWordToBuffer(const char *src, const char *delims, GWEN_BUFFER *buf, uint32_t flags);
|
|
static const char *_skipBlanks(const char *src, const char *delims);
|
|
static const char *_readEscapeSequence(const char *src, GWEN_BUFFER *wbuf);
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* implementations
|
|
* ------------------------------------------------------------------------------------------------
|
|
*/
|
|
|
|
|
|
AQH_VARS *AQH_Vars_ReadDbFormat(const char *src)
|
|
{
|
|
AQH_VARS *vtRoot;
|
|
AQH_VARS *vt;
|
|
GWEN_BUFFER *wbuf;
|
|
const char *s;
|
|
|
|
vtRoot=AQH_Vars_CreateGroup("root");
|
|
vt=vtRoot;
|
|
wbuf=GWEN_Buffer_new(0, 64, 0, 1);
|
|
s=src;
|
|
while(*s) {
|
|
GWEN_Buffer_Reset(wbuf);
|
|
s=_readLine(&vt, s, wbuf);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
GWEN_Buffer_free(wbuf);
|
|
AQH_Vars_free(vtRoot);
|
|
return NULL;
|
|
}
|
|
}
|
|
GWEN_Buffer_free(wbuf);
|
|
|
|
if (vt!=vtRoot) {
|
|
DBG_ERROR(AQH_LOGDOMAIN, "Incomplete groups read");
|
|
AQH_Vars_free(vtRoot);
|
|
return NULL;
|
|
}
|
|
|
|
return vtRoot;
|
|
}
|
|
|
|
|
|
|
|
const char *_readLine(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf)
|
|
{
|
|
int rv;
|
|
const char *s;
|
|
|
|
s=src;
|
|
while(*s && isblank(*s))
|
|
s++;
|
|
|
|
if (*s=='}') {
|
|
AQH_VARS *vt;
|
|
|
|
/* current group ends */
|
|
vt=AQH_Vars_Tree2_GetParent(*pVars);
|
|
*pVars=vt;
|
|
s++;
|
|
}
|
|
else if (*s=='#') {
|
|
/* rest of line is a comment */
|
|
s++;
|
|
while(*s && !(*s==10 || *s==13))
|
|
s++;
|
|
}
|
|
else if (*s==10 || *s==13) {
|
|
/* end of line, so empty line */
|
|
}
|
|
else {
|
|
s=_readGroupOrVar(pVars, s, wbuf);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* handle end of line */
|
|
while(*s && isblank(*s))
|
|
s++;
|
|
while(*s==10 || *s==13)
|
|
s++;
|
|
|
|
return s;
|
|
}
|
|
|
|
|
|
|
|
const char *_readGroupOrVar(AQH_VARS **pVars, const char *src, GWEN_BUFFER *wbuf)
|
|
{
|
|
const char *s;
|
|
int rv;
|
|
|
|
GWEN_Buffer_Reset(wbuf);
|
|
s=_getWordToBuffer(src, " ={#\t\r\n", wbuf, _textFlags);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
|
|
while(*s && isblank(*s))
|
|
s++;
|
|
|
|
if (*s=='{') {
|
|
AQH_VARS *vt;
|
|
|
|
/* read group */
|
|
vt=AQH_Vars_CreateGroup(GWEN_Buffer_GetStart(wbuf));
|
|
AQH_Vars_Tree2_AddChild(*pVars, vt);
|
|
*pVars=vt;
|
|
s++;
|
|
}
|
|
else if (*s=='=') {
|
|
/* read untyped var, assume string */
|
|
s=_contAsVar(pVars, AQH_Vars_DataType_ValueString, s, GWEN_Buffer_GetStart(wbuf), wbuf);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
}
|
|
else if (*s=='#' || *s=='\r' || *s=='\n') {
|
|
/* line logically or physically ends after first word, syntax error */
|
|
DBG_ERROR(AQH_LOGDOMAIN, "Unexpected end of line");
|
|
return NULL;
|
|
}
|
|
else {
|
|
s=_contAsTypedVar(pVars, s, GWEN_Buffer_GetStart(wbuf), wbuf);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
|
|
const char *_contAsTypedVar(AQH_VARS **pVars, const char *src, const char *tname, GWEN_BUFFER *wbuf)
|
|
{
|
|
AQH_VARS_DATATYPE dataType;
|
|
const char *s;
|
|
|
|
/* got type, convert to type */
|
|
dataType=AQH_Vars_DataTypeFromString(tname);
|
|
if (dataType<AQH_Vars_DataType_ValueString || dataType>AQH_Vars_DataType_ValueDouble) {
|
|
DBG_ERROR(AQH_LOGDOMAIN, "Invalid type \"%s\"", tname);
|
|
return NULL;
|
|
}
|
|
|
|
/* read name */
|
|
GWEN_Buffer_Reset(wbuf);
|
|
s=_getWordToBuffer(src, " \t={#\r\n", wbuf, _textFlags);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
|
|
while(*s && isblank(*s))
|
|
s++;
|
|
|
|
s=_contAsVar(pVars, dataType, s, GWEN_Buffer_GetStart(wbuf), wbuf);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
|
|
const char *_contAsVar(AQH_VARS **pVars, AQH_VARS_DATATYPE dataType, const char *src, const char *vname, GWEN_BUFFER *wbuf)
|
|
{
|
|
const char *s;
|
|
AQH_VARS *vtVar;
|
|
|
|
s=src;
|
|
if (*s!='=') {
|
|
DBG_ERROR(AQH_LOGDOMAIN, "Expected \"=\"");
|
|
return NULL;
|
|
}
|
|
s++;
|
|
|
|
vtVar=AQH_Vars_CreateVariable(vname);
|
|
AQH_Vars_Tree2_AddChild(*pVars, vtVar);
|
|
|
|
while(*s) {
|
|
GWEN_Buffer_Reset(wbuf);
|
|
s=_readValue(vtVar, dataType, s, wbuf);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
|
|
while(*s && isblank(*s))
|
|
s++;
|
|
if (*s==';') {
|
|
s++;
|
|
break;
|
|
}
|
|
if (*s!=',')
|
|
break;
|
|
s++;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
|
|
const char *_readValue(AQH_VARS *vtVar, AQH_VARS_DATATYPE dataType, const char *s, GWEN_BUFFER *wbuf)
|
|
{
|
|
AQH_VARS *vtValue;
|
|
|
|
s=_getWordToBuffer(s, " \t,;#\r\n", wbuf, _textFlags);
|
|
if (s==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
|
|
switch(dataType) {
|
|
case AQH_Vars_DataType_ValueString: vtValue=_mkStringValue(GWEN_Buffer_GetStart(wbuf)); break;
|
|
case AQH_Vars_DataType_ValueInt: vtValue=_mkIntValue(GWEN_Buffer_GetStart(wbuf)); break;
|
|
case AQH_Vars_DataType_ValueDouble: vtValue=_mkDoubleValue(GWEN_Buffer_GetStart(wbuf)); break;
|
|
default: vtValue=NULL; break;
|
|
}
|
|
if (vtValue==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
AQH_Vars_Tree2_AddChild(vtVar, vtValue);
|
|
return s;
|
|
}
|
|
|
|
|
|
// @TODO: unescape (write own GWEN_Text_GetWordToBuffer here)
|
|
AQH_VARS *_mkStringValue(const char *s)
|
|
{
|
|
return AQH_Vars_CreateStringValue(strdup(s));
|
|
}
|
|
|
|
|
|
|
|
AQH_VARS *_mkIntValue(const char *s)
|
|
{
|
|
int v;
|
|
|
|
if (1!=sscanf(s, "%i", &v)) {
|
|
DBG_ERROR(AQH_LOGDOMAIN, "Not an int value [%s]", s);
|
|
return NULL;
|
|
}
|
|
return AQH_Vars_CreateIntValue(v);
|
|
}
|
|
|
|
|
|
|
|
AQH_VARS *_mkDoubleValue(const char *s)
|
|
{
|
|
int rv;
|
|
double v;
|
|
|
|
rv=GWEN_Text_StringToDouble(s, &v);
|
|
if (rv<0) {
|
|
DBG_ERROR(AQH_LOGDOMAIN, "Not a double value [%s] (%d)", s, rv);
|
|
return NULL;
|
|
}
|
|
return AQH_Vars_CreateDoubleValue(v);
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *_getWordToBuffer(const char *src, const char *delims, GWEN_BUFFER *buf, uint32_t flags)
|
|
{
|
|
|
|
/* skip leading blanks, if wanted */
|
|
if (flags & GWEN_TEXT_FLAGS_DEL_LEADING_BLANKS)
|
|
src=_skipBlanks(src, delims);
|
|
|
|
if (*src=='"') {
|
|
/* inside brackets */
|
|
if (flags & GWEN_TEXT_FLAGS_DEL_QUOTES)
|
|
src++;
|
|
while(*src && strchr(delims, *src)==NULL) {
|
|
unsigned char x;
|
|
|
|
x=(unsigned char)*src;
|
|
if (x=='"') {
|
|
if (flags & GWEN_TEXT_FLAGS_DEL_QUOTES)
|
|
src++;
|
|
return src;
|
|
}
|
|
else {
|
|
if (x=='%') {
|
|
src=_readEscapeSequence(src, buf);
|
|
if (src==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
GWEN_Buffer_AppendByte(buf, x);
|
|
src++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
while(*src && strchr(delims, *src)==NULL) {
|
|
unsigned char x;
|
|
|
|
x=*src;
|
|
if (x=='%') {
|
|
src=_readEscapeSequence(src, buf);
|
|
if (src==NULL) {
|
|
DBG_INFO(AQH_LOGDOMAIN, "here");
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
GWEN_Buffer_AppendByte(buf, x);
|
|
src++;
|
|
}
|
|
if ((flags & GWEN_TEXT_FLAGS_DEL_MULTIPLE_BLANKS) && isblank(x))
|
|
src=_skipBlanks(src, delims);
|
|
}
|
|
}
|
|
|
|
return src;
|
|
}
|
|
|
|
|
|
|
|
const char *_skipBlanks(const char *src, const char *delims)
|
|
{
|
|
while (*src && isblank(*src)) {
|
|
if (strchr(delims, *src)) {
|
|
return src; /* empty buffer */
|
|
}
|
|
src++;
|
|
}
|
|
return src;
|
|
}
|
|
|
|
|
|
|
|
const char *_readEscapeSequence(const char *src, GWEN_BUFFER *wbuf)
|
|
{
|
|
if (src[1] && src[2]) {
|
|
unsigned char d1, d2;
|
|
unsigned char c;
|
|
|
|
src++; /* skip "%" */
|
|
|
|
if (!(*src) || !isxdigit((int)*src)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (no digits)");
|
|
return NULL;
|
|
}
|
|
d1=(unsigned char)(toupper(*src));
|
|
src++;
|
|
|
|
if (!(*src) || !isxdigit((int)*src)) {
|
|
DBG_ERROR(GWEN_LOGDOMAIN, "Incomplete escape sequence (only 1 digit)");
|
|
return NULL;
|
|
}
|
|
d2=(unsigned char)(toupper(*src));
|
|
src++;
|
|
|
|
d1-='0';
|
|
if (d1>9)
|
|
d1-=7;
|
|
|
|
d2-='0';
|
|
if (d2>9)
|
|
d2-=7;
|
|
c=((d1<<4) & 0xf0) | (d2 & 0x0f);
|
|
GWEN_Buffer_AppendByte(wbuf, c);
|
|
return src;
|
|
}
|
|
else {
|
|
DBG_ERROR(AQH_LOGDOMAIN, "Incomplete escape sequence");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
//if ((x>='A' && x<='Z') || (x>='a' && x<='z') || (x>='0' && x<='9') || strchr(_acceptableChars, x))
|
|
|
|
|
|
|
|
#include "./vars_dbread-t.c"
|
|
|