/**************************************************************************** * This file is part of the project Gwenhywfar. * Gwenhywfar (c) by 2023 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 #endif #include "./vars_p.h" #include "./vars_dbread.h" #include "aqhome/data/path.h" #include #include #include #include /* ------------------------------------------------------------------------------------------------ * 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 (%d)", rv); 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 (%d)", rv); 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 (%d)", rv); 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 (dataTypeAQH_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"