/* $Id: kHtmlPC.cpp,v 1.3 2000-02-18 12:42:07 bird Exp $ */ /* * kHtmlPC - Special-purpose HTML/SQL preprocessor. * * Copyright (c) 1999 knut st. osmundsen * */ /******************************************************************************* * Defined Constants * *******************************************************************************/ #define ErrorDescCase(errorcode) case errorcode: return formatMessage(#errorcode) #define TAG_PARAMETER_LIST \ const kTag &tag, \ kLIFO &lifoFile, \ kLIFO &lifoUsed, \ kLIFO &lifoVar, \ kLIFO2 &lifoSql, \ kFileEntry * &pCurFile, \ int &i, \ FILE *phLog #define TAG_PARAMETERS tag, lifoFile, lifoUsed, lifoVar, lifoSql, pCurFile, i, phLog #define TAG_PARAMETER_LIST_UNREF \ int f = phLog || i || pCurFile || &lifoFile || \ &lifoUsed || &lifoVar || &lifoSql || &tag; \ f = f #ifdef __EMX__ #define max(a,b) (((a) > (b)) ? (a) : (b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) #define _System #endif #define TRUE 1 #define FALSE 0 #define CCHMAXPATH 260 /******************************************************************************* * Structures and Typedefs * *******************************************************************************/ typedef unsigned long BOOL; typedef struct _POINTL { long x; long y; } POINTL, *PPOINTL; /******************************************************************************* * Header Files * *******************************************************************************/ #include #include #include #include #include #ifdef __EMX__ #include #undef _NAN static double _NAN = (0.0/0.0); #else #include #endif #include /* gifdraw */ extern "C" { #include "gd/gd.h" #include "gd/gdfontg.h" #include "gd/gdfontl.h" #include "gd/gdfonts.h" /* Y wrappers; LB = origo at Left Botton corner */ #define gdImageLineLB(im,x1,y1,x2,y2,cl) gdImageLine(im,x1,gdImageSY(im)-(y1),x2,gdImageSY(im)-(y2),cl) #define gdImageStringLB(im,f,x,y,s,cl) gdImageString(im,f,x,gdImageSY(im)-(y),s,cl) #define gdImageStringUpLB(im,f,x,y,s,cl) gdImageStringUp(im,f,x,gdImageSY(im)-(y),s,cl) } #include "kLIFO.h" #include "kList.h" #include "kHtmlPC.h" #include "db.h" /*@Struct*********************************************************************** * Structures and Typedefs * *******************************************************************************/ typedef struct _GraphCallBackParameters { kGraph *pGraph; kGraphDataSet *pDataSet; kGraphData *pData; kGraph::enmType enmTypeCd; long lSeqNbr; char *pszLegend; char *pszColor; } GRAPHCALLBACKPARAM, *PGRAPHCALLBACKPARAM; /******************************************************************************* * Global Variables * *******************************************************************************/ static FILE *phLog = NULL; /******************************************************************************* * Internal Functions * *******************************************************************************/ extern "C" void handler(int sig); static void syntax(void); static void openLog(void); static void closeLog(void); static unsigned long processFile(const char *pszFilenamem, const POPTIONS pOptions); static unsigned long tagEndkSql(TAG_PARAMETER_LIST); static unsigned long tagkSql(TAG_PARAMETER_LIST); static unsigned long tagkTemplate(TAG_PARAMETER_LIST, FILE * &phFile, const POPTIONS pOptions); static unsigned long tagkInclude(TAG_PARAMETER_LIST); static unsigned long tagkGraph(TAG_PARAMETER_LIST, const POPTIONS pOptions); static unsigned long tagkDefine(TAG_PARAMETER_LIST); static unsigned long tagkUndef(TAG_PARAMETER_LIST); static unsigned long tagkIf(TAG_PARAMETER_LIST); static unsigned long tagkElse(TAG_PARAMETER_LIST); static unsigned long tagkEndif(TAG_PARAMETER_LIST); static BOOL rebuildFileLIFO(kLIFO &lifoFile, kLIFO &lifoUsed, const kFileEntry *pFileEntryTop); static char *dupeString(const char *psz); #if 0 inline char upcase(char ch); static char *stristr(const char *pszStr, const char *pszSubStr); static char *trim(char *psz); static char *ltrim(char *psz); static const char *ltrim(const char *psz); static char *ltrimL(char *psz); #endif static const char *ltrimL(const char *psz); static long _System dbFetchCallBack(const char *pszValue, const char *pszFieldName, void *pvUser); long _System dbDataSetCallBack(const char *pszValue, const char *pszFieldName, void *pvUser); long _System dbGraphCallBack(const char *pszValue, const char *pszFieldName, void *pvUser); /** * Main function. * @returns Number of errors * @param argc Argument count. * @param argv Argument array. */ int main(int argc, char **argv) { int argi; BOOL fFatal = FALSE; unsigned long ulRc = 0; char *pszHost = "localhost"; char *pszDatabase = "Odin32"; char *pszUser = "root"; char *pszPasswd = ""; char szPathName[CCHMAXPATH]; OPTIONS options = {"."}; /* signal handler */ if (SIG_ERR == signal(SIGBREAK, handler) || SIG_ERR == signal(SIGTERM, handler) || SIG_ERR == signal(SIGINT, handler) ) fprintf(stderr, "Error installing signalhandler..."); /************************************************************************** * parse arguments. * options: -h or -? help * -b: Basepath for output files. * -d: Database name * -p: Password * -u: Userid * -h: Hostname/IP-address **************************************************************************/ if (argc == 1) syntax(); argi = 1; while (argi < argc && !fFatal) { if(argv[argi][0] == '-' || argv[argi][0] == '/') { switch (argv[argi][1]) { case 'b': case 'B': printf("%s\n", argv[argi]); if (argv[argi][2] == ':') { if (_fullpath(&szPathName[0], &argv[argi][3], sizeof(szPathName)) != NULL) options.pszBaseDir = &szPathName[0]; else { fprintf(stderr, "error: basepath '%s' don't exists\n", &argv[argi][3]); fFatal = TRUE; } } else { fprintf(stderr, "error: option '-b:' requires a directory name.\n"); fFatal = TRUE; } break; case 'd': case 'D': if (argv[argi][2] == ':') pszDatabase = &argv[argi][3]; else { fprintf(stderr, "error: option '-d:' requires database name.\n"); fFatal = TRUE; } break; case 'h': case 'H': if (argv[argi][2] == ':') { pszHost = &argv[argi][3]; break; } case '?': syntax(); return 0; case 'p': case 'P': if (argv[argi][2] == ':') pszPasswd = &argv[argi][3]; else { fprintf(stderr, "error: option '-p:' requires password.\n"); fFatal = TRUE; } break; case 'u': case 'U': if (argv[argi][2] == ':') pszUser = &argv[argi][3]; else { fprintf(stderr, "error: option '-u:' requires userid.\n"); fFatal = TRUE; } break; /* options.fSomeOption = argv[argi][2] != '-'; break; */ default: fprintf(stderr, "incorrect parameter. (argi=%d, argv[argi]=%s)\n", argi, argv[argi]); fFatal = TRUE; break; } } else { if (phLog == NULL) openLog(); if (dbConnect(pszHost, pszUser, pszPasswd, pszDatabase)) { ulRc += processFile(argv[argi], &options); dbDisconnect(); } else fprintf(phLog, "Error connecting to database.\n"); } argi++; } /* close the log */ closeLog(); /* warn if error during processing. */ if (!fFatal) fprintf(stderr, "kHTMLPreCompiler compleated with %ld error%s and %ld warning%s.\n", ulRc & 0x0000ffffUL, (ulRc & 0x0000ffffUL) != 1 ? "s" : "", ulRc >> 16, (ulRc >> 16) != 1 ? "s" : "" ); return (int)(ulRc & 0x0000ffff); } /** * signal handler.... * @param sig * @remark Needs to flush files before termination. (debugging purpose) */ void handler(int sig) { fprintf(stderr, "\n\t!signal %d!\n", sig); flushall(); dbDisconnect(); exit(-1); } /** * Display syntax. */ static void syntax(void) { printf("\n" "kHtmlPC v%01d.%02d - General-purpose HTML precompiler.\n" "-------------------------------------------------\n" "syntax: kHtmlPc.exe [-h|-?] [options] [file1 [file2 [..]]\n" "\n" " -h or -? Syntax help. (this)\n" " -h: Database server hostname. default: localhost\n" " -u: Username on the server. default: root\n" " -p: Password. default: \n" " -d: Database to use. default: Odin32\n" "\n" "\n" "Copyright (c) 1999 knut st. osmundsen (knut.stange.osmundsen@pmsc.no)", VER_MAJOR, VER_MINOR ); } /** * Opens log file. Currently this we don't use a logfile, stderr is our logfile. */ static void openLog(void) { #if 0 if (phLog == NULL) { phLog = fopen("APIImport.Log", "w"); if (phLog == NULL) { fprintf(stderr,"error occured while opening log file - will use stderr instead.\n"); phLog = stderr; } } #else phLog = stderr; #endif } /** * Closes the log. */ static void closeLog(void) { if (phLog != stderr && phLog != NULL) fclose(phLog); } /** * Preprocesses a file. * @returns high word Number of warnings. * low word Number of errors; * @param pszFilename Pointer to filename. * @param pOption Pointer to the option struct. * @remark Big function! */ static unsigned long processFile(const char *pszFilename, const POPTIONS pOptions) { unsigned long ulRc = 0; unsigned long ulRc2; FILE *phFile = NULL; /* Current output file. */ kLIFO lifoFile, lifoUsed; kLIFO lifoVar; /* tags not implemented yet... */ kLIFO2 lifoSql; kFileEntry *pCurFile; const kVariableEntry *pVariable = NULL; const char *psz; char szVariable[81]; /* open initial file */ try { pCurFile = new kFileEntry(pszFilename); } catch (int errorcode) { fprintf(phLog, "error opening initial file. errorcode = %d\n", errorcode); return 0x00000001; } /* loop on pCurFile */ while (pCurFile != NULL) { /* loop on curFile->pszCurrent */ while (pCurFile->pszCurrent != NULL && *pCurFile->pszCurrent != '\0') { /*********/ /* !kTag */ /*********/ if (pCurFile->pszCurrent[0] == '<' && pCurFile->pszCurrent[1] == '!') { /* find end, replace variables and switch tag type. */ char szTag[1024]; BOOL fQuote = FALSE; int i = 0, j = 0; /* copy tag and insert variables */ while ((fQuote || pCurFile->pszCurrent[i] != '>') && pCurFile->pszCurrent[i] != '\0' && j < (int)sizeof(szTag)) { fQuote = fQuote ? pCurFile->pszCurrent[i] != '"' : pCurFile->pszCurrent[i] == '"'; /* variable? */ if (pCurFile->pszCurrent[i] == '$' && pCurFile->pszCurrent[i+1] == '(') { psz = pCurFile->pszCurrent + i + 2; while (*psz != ')' && *psz != '\0' && (psz - &pCurFile->pszCurrent[i]) < (int)(sizeof(szVariable)-1)) psz++; if (*psz == ')') { strncpy(&szVariable[0], pCurFile->pszCurrent + i + 2, psz - &pCurFile->pszCurrent[i] - 2); szVariable[psz - &pCurFile->pszCurrent[i] - 2] = '\0'; pVariable = lifoSql.findSub(&szVariable[0]); if (pVariable == NULL) pVariable = lifoVar.find(&szVariable[0]); if (pVariable != NULL) { if(j + strlen(pVariable->getValue()) < sizeof(szTag)) { /* copy variable value */ strcpy(&szTag[j], pVariable->getValue()); j += strlen(&szTag[j]) - 1; i += psz - &pCurFile->pszCurrent[i]; } else { /* warning - complain, skip and continue */ fprintf(phLog, "%s(%ld) : warning: Variable '%s' out of space, increase the size of szTag.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, &szVariable[0]); szTag[j] = pCurFile->pszCurrent[i]; } } else { /* warning - complain, skip and continue */ fprintf(phLog, "%s(%ld) : warning: Variable '%s' not defined.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, &szVariable[0]); szTag[j] = pCurFile->pszCurrent[i]; } } else { /* error - complain, skip and continue. */ fprintf(phLog, "%s(%ld) : error: Missing left parenthese on variable expression\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); szTag[j] = pCurFile->pszCurrent[i]; } } else szTag[j] = pCurFile->pszCurrent[i]; /* next */ i++; j++; } /* copy ok? */ if (j < (int)sizeof(szTag) && pCurFile->pszCurrent[i] == '>') { szTag[j++] = '>'; szTag[j] = '\0'; i++; try { /* try create tag object */ kTag tag(&szTag[0]); /* check for warning */ if (tag.queryWarning() != NULL) fprintf(phLog, "%s(%ld) : warning: tag warning - %s.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, tag.queryWarning()); /**************/ /**************/ /* tag switch */ /**************/ /**************/ ulRc2 = 0; if (tag.isTag("!kSql")) /* !kSql */ ulRc2 = tagkSql(TAG_PARAMETERS); else if (tag.isTag("!/kSql")) /* !/kSql */ ulRc2 = tagEndkSql(TAG_PARAMETERS); else if (tag.isTag("!kTemplate")) /* !kTemplate */ ulRc2 = tagkTemplate(TAG_PARAMETERS, phFile, pOptions); else if (tag.isTag("!kInclude")) /* !kInclude */ ulRc2 = tagkInclude(TAG_PARAMETERS); else if (tag.isTag("!kGraph")) /* !kGraph */ ulRc2 = tagkGraph(TAG_PARAMETERS, pOptions); else if (tag.isTag("!kDefine")) /* !kDefine */ ulRc2 = tagkDefine(TAG_PARAMETERS); else if (tag.isTag("!kUndef")) /* !kUndef */ ulRc2 = tagkUndef(TAG_PARAMETERS); else if (tag.isTag("!kIf")) /* !kIf */ ulRc2 = tagkIf(TAG_PARAMETERS); else if (tag.isTag("!kElse")) /* !kElse */ ulRc2 = tagkElse(TAG_PARAMETERS); else if (tag.isTag("!kEndif")) /* !kEndif */ ulRc2 = tagkEndif(TAG_PARAMETERS); else { if (phFile != NULL) fputc(*pCurFile->pszCurrent, phFile); /* if this proove to be very slow, we'll have to buffer writes better. */ pCurFile->pszCurrent++; } /* if error occurred - skip and continue. */ if ((ulRc2 & 0x0000ffff) != 0) { if (phFile != NULL) fputc(*pCurFile->pszCurrent, phFile); /* if this proove to be very slow, we'll have to buffer writes better. */ pCurFile->pszCurrent++; } ulRc += ulRc2; } catch (kError::enmErrors enmErrorCd) { fprintf(phLog, "%s(%ld) : error: tag error - %s.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, kError::queryDescription(enmErrorCd)); if (phFile != NULL) fputc(*pCurFile->pszCurrent, phFile); /* if this proove to be very slow, we'll have to buffer writes better. */ pCurFile->pszCurrent++; ulRc += 0x00000001; } } else { fprintf(phLog, "%s(%ld) : error: error occurred extracting tag.\n\tPossible reasons: unbalanced quotes, missing '>',...\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); if (phFile != NULL) fputc(*pCurFile->pszCurrent, phFile); /* if this proove to be very slow, we'll have to buffer writes better. */ pCurFile->pszCurrent++; ulRc += 0x00000001; } } /*************/ /*************/ /* VARIABLE */ /*************/ /*************/ else if (pCurFile->pszCurrent[0] == '$' && pCurFile->pszCurrent[1] == '(') { psz = pCurFile->pszCurrent + 2; while (*psz != ')' && *psz != '\0' && (psz - pCurFile->pszCurrent) < (int)(sizeof(szVariable)-1)) psz++; if (*psz == ')') { strncpy(&szVariable[0], pCurFile->pszCurrent+2, psz - pCurFile->pszCurrent-2); szVariable[psz - pCurFile->pszCurrent - 2] = '\0'; pVariable = lifoSql.findSub(&szVariable[0]); if (pVariable == NULL) pVariable = lifoVar.find(&szVariable[0]); if (pVariable != NULL) { /* write variable */ if (phFile != NULL) fwrite(pVariable->getValue(), strlen(pVariable->getValue()), 1, phFile); } else { /* warning - complain, skip and continue */ fprintf(phLog, "%s(%ld) : warning: Variable '%s' not defined.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, &szVariable[0]); if (phFile != NULL) fwrite(pCurFile->pszCurrent, psz - pCurFile->pszCurrent + 1, 1, phFile); ulRc += 0x00010000; } pCurFile->pszCurrent = psz + 1; } else { /* error - complain, skip and continue. */ fprintf(phLog, "%s(%ld) : error: Missing left parenthese on variable expression\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); if (phFile != NULL) fputc(*pCurFile->pszCurrent, phFile); /* if this proove to be very slow, we'll have to buffer writes better. */ pCurFile->pszCurrent++; ulRc += 0x00000001; } } else { /* next */ if (phFile != NULL) fputc(*pCurFile->pszCurrent, phFile); /* if this proove to be very slow, we'll have to buffer writes better. */ pCurFile->pszCurrent++; } } /* resume processing of parent file */ kFileEntry *pFE = lifoFile.pop(); if (pFE != NULL) { assert(pFE == pCurFile->queryParent()); pFE->pszCurrent = pCurFile->queryParentPointer(); } lifoUsed.push(pCurFile); pCurFile = pFE; } /* close file */ if (phFile != NULL) fclose(phFile); return ulRc; } /** * Tag function - kSql. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkSql(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; try { lifoSql.push(new kSqlEntry(tag, &pCurFile->pszCurrent[i], pCurFile)); pCurFile->pszCurrent += i; } catch (kError::enmErrors enmErrorCd) { fprintf(phLog, "%s(%ld) : error: kSql tag - %s (errorcode=%d)\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, kError::queryDescription(enmErrorCd), enmErrorCd); /* fake entry */ if (enmErrorCd != kError::error_invalid_tag) lifoSql.push(new kSqlEntry()); TAG_PARAMETER_LIST_UNREF; } return ulRc; } /** * Tag function - /kSql. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagEndkSql(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; kSqlEntry *pCurSql; pCurSql = lifoSql.pop(); if (pCurSql != NULL) { if (!pCurSql->eof()) { if (pCurSql->fetch()) { if (pCurSql->queryFileEntry() != NULL) { /* three cases: current file, lifoFile or lifoUsed */ if (pCurSql->queryFileEntry() == pCurFile) { /* 1. current file */ pCurFile->pszCurrent = pCurSql->queryBackTrackPos(); } else if (lifoFile.exists(pCurSql->queryFileEntry())) { /* 2. lifoFile */ lifoFile.popPush(pCurSql->queryFileEntry(), lifoUsed); pCurFile = lifoFile.pop(); if (pCurFile != NULL) pCurFile->pszCurrent = pCurSql->queryBackTrackPos(); else { fprintf(phLog, "fatal internal error(%s, %ld, %s, %d): kSqlEntry - pCurFile == NULL\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, __FILE__, __LINE__); return (unsigned long)~0; } } else if (lifoUsed.exists(pCurSql->queryFileEntry())) { /* 3. lifoUsed */ lifoFile.popPush(NULL, lifoUsed); assert(lifoFile.isEmpty()); if (rebuildFileLIFO(lifoFile, lifoUsed, pCurSql->queryFileEntry())) { pCurFile = lifoFile.pop(); assert(pCurFile == pCurSql->queryFileEntry()); pCurFile->pszCurrent = pCurSql->queryBackTrackPos(); } else { fprintf(phLog, "fatal internal error(%s, %ld, %s, %d): rebuildLIFO failed\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, __FILE__, __LINE__); return (unsigned long)~0; } } else { fprintf(phLog, "internal error(%s, %ld, %s, %d): pFileEntry == NULL\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, __FILE__, __LINE__); pCurSql = lifoSql.pop(); /* neutralize the push below */ } lifoSql.push(pCurSql); pCurSql = NULL; } else { fprintf(phLog, "internal error(%s, %ld, %s, %d): kSqlEntry - pFileEntry == NULL\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, __FILE__, __LINE__); pCurFile->pszCurrent += i; } } else { fprintf(phLog, "%s(%ld) : error: sql error, %s\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, pCurSql->querySqlLastError()); pCurFile->pszCurrent += i; } } /* finished processing this tag? then delete it! */ if (pCurSql != NULL) { delete pCurSql; pCurFile->pszCurrent += i; } } else { fprintf(phLog, "%s(%ld) : error: unexpected '/kSql' tag.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); pCurFile->pszCurrent += i; TAG_PARAMETER_LIST_UNREF; } return ulRc; } /** * Tag function - kTemplate. * @returns low word: number of errors * high word: number of warnings * @param phFile Reference to current output file. * @param pOptions Pointer to the options struct. * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkTemplate(TAG_PARAMETER_LIST, FILE * &phFile, const POPTIONS pOptions) { unsigned long ulRc = 0; const char *pszFilename; /* verify parameters */ if (tag.getParameterCount() > 1 && tag.getParameterCount() == 0) { fprintf(phLog, "%s(%ld) : warning: kTemplate - incorrect number of parameters.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); ulRc += 0x00010000; } pszFilename = tag.queryParameter("filename"); if (pszFilename != NULL) { char szFullFileName[CCHMAXPATH]; if (phFile != NULL) fclose(phFile); if (strlen(pszFilename) + strlen(pOptions->pszBaseDir) + 1 + 1 < sizeof(szFullFileName)) { sprintf(&szFullFileName[0], "%s\\%s", pOptions->pszBaseDir, pszFilename); phFile = fopen(&szFullFileName[0], "wb"); if (phFile != NULL) fprintf(phLog, "%s(%ld) : info: new output file, '%s'.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, &szFullFileName[0]); else { fprintf(phLog, "%s(%ld) : error: kTemplate - error opening output file '%s'.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, &szFullFileName[0]); ulRc += 0x00000001; } } else { fprintf(phLog, "%s(%ld) : error: kTemplate - filename and base dir is too long! '%s\\%s'.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, pOptions->pszBaseDir, pszFilename); ulRc += 0x00000001; } } else { fprintf(phLog, "%s(%ld) : error: kTemplate - filename is missing.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); ulRc += 0x00000001; TAG_PARAMETER_LIST_UNREF; } pCurFile->pszCurrent += i; return ulRc; } /** * Tag function - kInclude. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkInclude(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; try { lifoFile.push(pCurFile); pCurFile = new kFileEntry(tag, pCurFile->pszCurrent+i, pCurFile); } catch (kError::enmErrors enmErrorCd) { fprintf(phLog, "%s(%ld) : error: kInclude - %s\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, kError::queryDescription(enmErrorCd)); pCurFile = lifoFile.pop(); ulRc = 0x00000001; TAG_PARAMETER_LIST_UNREF; } return ulRc; } /** * Tag function - kGraph. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkGraph(TAG_PARAMETER_LIST, const POPTIONS pOptions) { unsigned long ulRc = 0; /** * Tag description: * [subtype=] * data="sql x,y,color,legend from...." * data="sql x,y,color,legend from...." * data="sql x,y,color,legend from...." * ... * > * [] = optional */ try { kGraph graph(tag, pOptions->pszBaseDir); graph.showWarnings(phLog, pCurFile); graph.save(); } catch (kError::enmErrors emnErrorCd) { fprintf(phLog, "%s(%ld) : error: kGraph - %s.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, kError::queryDescription(emnErrorCd)); ulRc = 0x00000001; TAG_PARAMETER_LIST_UNREF; } pCurFile->pszCurrent += i; return ulRc; } /** * Tag function - kDefine. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkDefine(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; fprintf(phLog, "%s(%ld) : error: kDefine - This tag is not implemented yet.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); ulRc = 0x00000001; TAG_PARAMETER_LIST_UNREF; return ulRc; } /** * Tag function - kUndef. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkUndef(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; fprintf(phLog, "%s(%ld) : error: kUndef - This tag is not implemented yet.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); ulRc = 0x00000001; TAG_PARAMETER_LIST_UNREF; return ulRc; } /** * Tag function - kIf. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkIf(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; fprintf(phLog, "%s(%ld) : error: kIf - This tag is not implemented yet.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); ulRc = 0x00000001; TAG_PARAMETER_LIST_UNREF; return ulRc; } /** * Tag function - kElse. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkElse(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; fprintf(phLog, "%s(%ld) : error: kElse - This tag is not implemented yet.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); ulRc = 0x00000001; TAG_PARAMETER_LIST_UNREF; return ulRc; } /** * Tag function - kEndif. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagkEndif(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; fprintf(phLog, "%s(%ld) : error: kEndif - This tag is not implemented yet.\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1); ulRc = 0x00000001; TAG_PARAMETER_LIST_UNREF; return ulRc; } #if 0 /** * Tag function - k. * @returns low word: number of errors * high word: number of warnings * @remark See TAG_PARAMETER_LIST for parameters. * Use TAG_PARAMETERS when calling this function. */ static unsigned long tagk(TAG_PARAMETER_LIST) { unsigned long ulRc = 0; TAG_PARAMETER_LIST_UNREF; return ulRc; } #endif /** * Rebuild a fileentry based on a given file entry. * @returns success indicator. TRUE / FALSE. * @param lifoFile Target. * @param lifoUsed Source. * @param pFileEntryTop New to file entry. * @remark Recusive. */ static BOOL rebuildFileLIFO(kLIFO &lifoFile, kLIFO &lifoUsed, const kFileEntry *pFileEntryTop) { /* base case */ if (pFileEntryTop == NULL) return TRUE; /* general case */ pFileEntryTop = lifoUsed.get(pFileEntryTop); if (pFileEntryTop != NULL && rebuildFileLIFO(lifoFile, lifoUsed, pFileEntryTop->queryParent())) lifoFile.push((kFileEntry*)pFileEntryTop); else return FALSE; return TRUE; } /** * Duplicates a string. * @returns Pointer to stringcopy. Remeber to delete this! * @param psz Pointer to string to duplicate. */ static char *dupeString(const char *psz) { char *pszDupe; if (psz == NULL) return NULL; pszDupe = new char[strlen(psz)+1]; return strcpy(pszDupe, psz); } #if 0 // not used /** * Upcases a char. * @returns Upper case of the char given in ch. * @param ch Char to capitalize. */ inline char upcase(char ch) { return ch >= 'a' && ch <= 'z' ? (char)(ch - ('a' - 'A')) : ch; } /** * Searches for a substring in a string. * @returns Pointer to start of substring when found, NULL when not found. * @param pszStr String to be searched. * @param pszSubStr String to be searched. * @remark Depends on the upcase function. */ static char *stristr(const char *pszStr, const char *pszSubStr) { int cchSubStr = strlen(pszSubStr); int i = 0; while (*pszStr != '\0' && i < cchSubStr) { i = 0; while (i < cchSubStr && pszStr[i] != '\0' && (upcase(pszStr[i]) == upcase(pszSubStr[i]))) i++; pszStr++; } return (char*)(*pszStr != '\0' ? pszStr - 1 : NULL); } /** * Trims a string, that is removing blank spaces at start and end. * @returns Pointer to first non-blank char. * @param psz Pointer to string. * @result Blank at end of string is removed. ('\0' is moved to the left.) */ static char *trim(char *psz) { int i; if (psz == NULL) return NULL; while (*psz == ' ') psz++; i = strlen(psz) - 1; while (i >= 0 && psz[i] == ' ') i--; psz[i+1] = '\0'; return psz; } /** * Trims left side of a string; that is removing blank spaces at start. * @returns Pointer to first non-blank char. * @param psz Pointer to string. * @remark non-const edtion. */ static char *ltrim(char *psz) { while (*psz == ' ') psz++; return psz; } /** * Trims left side of a string; that is removing blank spaces. * @returns Pointer to first non-blank char. * @param psz Pointer to string. * @remark const edtion. */ static const char *ltrim(const char *psz) { while (*psz == ' ') psz++; return psz; } /** * Trims left side of a string; that is removing blank spaces, new lines and tabs. * @returns Pointer to first non-blank char. * @param psz Pointer to string. * @remark non-const edtion. */ static char *ltrimL(char *psz) { while (*psz == ' ' || *psz == '\t' || *psz == '\r' || *psz == '\n') psz++; return psz; } #endif /** * Trims left side of a string; that is removing blank spaces, new lines and tabs. * @returns Pointer to first non-blank char. * @param psz Pointer to string. * @remark const edtion. */ static const char *ltrimL(const char *psz) { while (*psz == ' ' || *psz == '\t' || *psz == '\r' || *psz == '\n') psz++; return psz; } /** * Formats a raw message to a readable message. * @returns Pointer to a static data field containing the formatted message. * @param pszMsg message to format. */ const char *kError::formatMessage(const char *pszMsg) { static char szData[256]; int i = 0; /* skip first word */ while (*pszMsg != '_' && *pszMsg != '\0') pszMsg++; if (pszMsg != '\0') { pszMsg++; do { if (*pszMsg == '_') szData[i++] = ' '; else szData[i++] = *pszMsg; } while (*pszMsg++ != '\0'); strcat(&szData[0], "."); } else szData[0] = '\0'; return &szData[0]; } /** * Give a description of an error code. * @returns "Read only"/const string. Never NULL. * @param emnError Error code. */ const char *kError::queryDescription(kError::enmErrors enmError) { switch (enmError) { case error_sql_failed: return kSqlEntry::querySqlLastError(); ErrorDescCase(no_error ); ErrorDescCase(error_unexpected_end_of_string ); ErrorDescCase(error_unexpected_eot ); ErrorDescCase(error_invalid_tag_start_char ); ErrorDescCase(error_invalid_tagname ); ErrorDescCase(error_invalid_tag ); ErrorDescCase(error_no_filename ); ErrorDescCase(error_unexpected_eof ); ErrorDescCase(error_unbalanced_quotes ); ErrorDescCase(error_eot_not_found ); ErrorDescCase(error_opening_file ); ErrorDescCase(error_determing_file_size ); ErrorDescCase(error_new_failed ); ErrorDescCase(error_reading_file ); ErrorDescCase(error_incorrect_number_of_parameters ); ErrorDescCase(error_missing_sql ); ErrorDescCase(error_invalid_sql_tag ); ErrorDescCase(error_opening_output_file ); ErrorDescCase(error_graph_to_small ); ErrorDescCase(error_graph_type_must_be_specified_before_data ); ErrorDescCase(error_data_param_is_missing_value ); ErrorDescCase(error_type_param_is_missing_value ); ErrorDescCase(error_invalid_type ); ErrorDescCase(error_filename_param_is_missing_value ); ErrorDescCase(error_width_cx_param_is_missing_value ); ErrorDescCase(error_height_cy_param_is_missing_value ); ErrorDescCase(error_graph_type_is_missing ); ErrorDescCase(error_graph_subtype_is_invalid ); ErrorDescCase(error_filename_is_missing ); ErrorDescCase(error_no_data ); ErrorDescCase(error_invalid_dimentions ); ErrorDescCase(error_data_param_is_missing_sql_statement ); ErrorDescCase(error_xstart_param_is_missing_value ); ErrorDescCase(error_xend_param_is_missing_value ); ErrorDescCase(error_ystart_param_is_missing_value ); ErrorDescCase(error_yend_param_is_missing_value ); ErrorDescCase(warning_illegal_string_char ); ErrorDescCase(warning_too_many_parameters ); ErrorDescCase(warning_unexpected_end_of_string ); ErrorDescCase(warning_unexpected_eot ); ErrorDescCase(warning_failed_to_open_background_image ); ErrorDescCase(warning_failed_to_load_background_image ); ErrorDescCase(warning_invalid_sub_type ); ErrorDescCase(warning_title_param_is_missing_value ); ErrorDescCase(warning_titlex_param_is_missing_value ); ErrorDescCase(warning_titley_param_is_missing_value ); ErrorDescCase(warning_invalid_parameter ); ErrorDescCase(warning_legend_is_not_implemented_yet ); ErrorDescCase(warning_failed_to_convert_date ); ErrorDescCase(warning_invalid_color_value ); ErrorDescCase(warning_to_many_fields_in_data_sql ); ErrorDescCase(warning_pie_not_implemented_yet ); ErrorDescCase(warning_negative_values_are_not_supported_yet ); ErrorDescCase(warning_background_param_is_missing_value ); ErrorDescCase(warning_backgroundcolor_is_invalid ); ErrorDescCase(warning_backgroundcolor_param_is_missing_value ); ErrorDescCase(warning_foregroundcolor_is_invalid ); ErrorDescCase(warning_foregroundcolor_param_is_missing_value ); ErrorDescCase(warning_axiscolor_is_invalid ); ErrorDescCase(warning_axiscolor_param_is_missing_value ); } return "unknown error"; } /** * Query for a warning. * @returns Const pointer to warning description. NULL if no warning. */ const char *kTag::queryWarning(void) { return enmWarning != kError::no_error ? kError::queryDescription(enmWarning) : NULL; } /** * Anayses an tag: get tagname and parameters (if any). * @param pszTag Pointer to tag buffer. * @sketch * @remark Throws kError::emnErrors on error. */ void kTag::analyseTag(const char *pszTag) throw(kError::enmErrors) { char *psz = &szTag[0]; /* skip blanks at start */ while (*pszTag == ' ') pszTag++; /* tagname */ if (*pszTag++ != '<') throw(kError::error_invalid_tag_start_char); pszTag = copyTag(psz, pszTag); pszTagname = psz; psz += strlen(psz) + 1; /* parameters */ if (strncmp(pszTagname, "!--", 3) != 0) /* ignore comments */ { try { pszTag = ltrimL(pszTag); while (*pszTag != '>' && *pszTag != '\0' && cParameters < TAG_MAX_PARAMETERS) { /* parametername */ pszTag = copyParameterName(psz, pszTag); apszParameters[cParameters] = psz; psz += strlen(psz) + 1; /* parametervalue */ apszValues[cParameters] = NULL; pszTag = ltrimL(pszTag); if (*pszTag == '=') { pszTag = ltrimL(pszTag + 1); if (*pszTag != '\"') pszTag = copyParameterValue1(psz, pszTag); else pszTag = copyParameterValue2(psz, pszTag); apszValues[cParameters] = psz; psz += strlen(psz) + 1; } /* parameter finished - next */ cParameters++; pszTag = ltrimL(pszTag); } /* check for warnings */ if (*pszTag != '>' && cParameters >= TAG_MAX_PARAMETERS) throw(kError::warning_too_many_parameters); if (*pszTag != '>') throw(kError::warning_unexpected_eot); } catch (kError::enmErrors enmErrorCd) { /* no fatal error --> warning */ enmWarning = enmErrorCd; } } } /** * Copy a tagname. * @returns Pointer to first char after the tag. (pszFrom) * @param pszTo Pointer to target. * @param pszFrom Pointer to value string. * @remark Throws kError::emnErrors on error, fatal for this object. */ const char *kTag::copyTag(char *pszTo, const char *pszFrom) throw(kError::enmErrors) { const char *psz = pszTo; /* checks for errors */ if (*pszFrom == '>') throw(kError::error_unexpected_eot); /* skip to start of tagname */ while (*pszFrom == ' ') pszFrom++; /* copy string */ while ( (*pszFrom >= 'a' && *pszFrom <= 'z') || (*pszFrom >= 'A' && *pszFrom <= 'Z') || (*pszFrom >= '0' && *pszFrom <= '9') || *pszFrom == '/' || *pszFrom == '!' || *pszFrom == '-' // and ) *pszTo++ = *pszFrom++; *pszTo = '\0'; /* checks for errors */ if (*pszFrom == '\0') throw(kError::error_unexpected_eot); if (*pszFrom != '>' && *pszFrom != ' ' && strncmp("!--", psz, 3) != 0) // throw(kError::error_invalid_tagname); return pszFrom; } /** * Copy a * @returns Pointer to first char after the tag. (pszFrom) * @param pszTo Pointer to target. * @param pszFrom Pointer to value string. * @remark Throws kError::emnErrors on error, fatal for this object. */ const char *kTag::copyParameterName(char *pszTo, const char *pszFrom) throw(kError::enmErrors) { /* checks for errors */ if (*pszFrom == '>') throw(kError::warning_unexpected_eot); /* skip to start of parameter name */ while (*pszFrom == ' ') pszFrom++; /* copy string */ while ( (*pszFrom >= 'a' && *pszFrom <= 'z') || (*pszFrom >= 'A' && *pszFrom <= 'Z') || (*pszFrom >= '0' && *pszFrom <= '9') ) *pszTo++ = *pszFrom++; *pszTo = '\0'; /* checks for errors */ if (*pszFrom == '\0') throw(kError::warning_unexpected_eot); return pszFrom; } /** * Copy a simple parameter value. A simple parameter value is not enclosed in "". * @returns Pointer to first char after the value. (pszFrom) * @param pszTo Pointer to target. * @param pszFrom Pointer to value string. * @remark Throws kError::emnErrors on warning, do not continue analysis. */ const char *kTag::copyParameterValue1(char *pszTo, const char *pszFrom) throw(kError::enmErrors) { /* skip to start of parameter value */ while (*pszFrom == ' ') pszFrom++; /* copy string */ while ( (*pszFrom >= 'a' && *pszFrom <= 'z') || (*pszFrom >= 'A' && *pszFrom <= 'Z') || (*pszFrom >= '0' && *pszFrom <= '9') || *pszFrom == '.' || *pszFrom == ',' ) *pszTo++ = *pszFrom++; *pszTo = '\0'; /* check for warnings */ if (*pszFrom == '\0') throw(kError::warning_unexpected_eot); if (*pszFrom != ' ' && *pszFrom != '>' && *pszFrom != '\n' && *pszFrom != '\r') throw(kError::warning_illegal_string_char); return pszFrom; } /** * Copy an enclosed parameter value. Enclosed in "". * @returns Pointer to first char after the value. (pszFrom) * @param pszTo Pointer to target. * @param pszFrom Pointer to value string. * @remark Throws kError::emnErrors on warning, do not continue analysis. */ const char *kTag::copyParameterValue2(char *pszTo, const char *pszFrom) throw(kError::enmErrors) { /* skip to start " */ while (*pszFrom != '\"' && *pszFrom != '>' && *pszFrom != '\0') pszFrom++; if (*pszFrom != '\"') throw(kError::error_unexpected_end_of_string); pszFrom++; /* copy string */ while (*pszFrom != '\"' /*&& *pszFrom != '>'*/ && *pszFrom != '\0') *pszTo++ = *pszFrom++; *pszTo = '\0'; /* check for warnings */ if (*pszFrom != '\"') throw(kError::warning_unexpected_end_of_string); return pszFrom + 1; } /** * Is this the correct tag? * @returns TRUE pszTagname matches this->pszTagname, else FALSE. * @param pszTagname Name to match. */ BOOL kTag::isTag(const char *pszTagname) const { if (pszTagname == NULL) return FALSE; return stricmp(pszTagname, this->pszTagname) == 0; } /** * Constructor. * @param pszTag Pointer to tag to analyse. * @remark Throws kError::emnErrors on warning, do not continue analysis. */ kTag::kTag(const char *pszTag) throw(kError::enmErrors) : pszTagname(NULL), cParameters(0), enmWarning(kError::no_error) { analyseTag(pszTag); } /** * Destructor. */ kTag::~kTag(void) { } /** * Gets the tag name. * @returns Const pointer to tag name. Not NULL. */ const char *kTag::getTagname(void) const { return pszTagname; } /** * Gets the number of parameters found for this tag. * @returns Number of parameters. -1 on error (should never occur!). */ unsigned long kTag::getParameterCount(void) const { return cParameters; } /** * Seek for the value of a given parameter. * @returns "Readonly"/const pointer to value. NULL if no value or parameter not found. * @param pszParameter Pointer to parameter name. * @remark Parameters are case insensitive! */ const char *kTag::queryParameter(const char *pszParameter) const { unsigned int i = 0; while (i < cParameters && stricmp(apszParameters[i], pszParameter) != 0) i++; return i < cParameters ? apszValues[i]: NULL; } /** * Checks if a parameter exists. * @returns TRUE: exists, FALSE: does not exist. * @param pszParameter Pointer to parameter name. * @remark Parameters are case insensitive! */ BOOL kTag::queryExists(const char *pszParameter) const { unsigned int i = 0; while (i < cParameters && stricmp(apszParameters[i], pszParameter) != 0) i++; return i < cParameters; } /** * Gets the parameter name for the given parameter number. * @returns Pointer to "readonly"/const parametername. NULL if outof range. * @param ulOrdinal Parameter number. 0-based. */ const char *kTag::queryParameterName(unsigned long ulOrdinal) const { return ulOrdinal < cParameters ? apszParameters[ulOrdinal] : NULL; } /** * Gets the value for the given parameter number. * @returns Pointer to "readonly"/const parameter value. NULL if outof range or no value. * @param ulOrdinal Parameter number. 0-based. * @remark Return value NULL doesn't have to be an error, the parameter may not have a value! */ const char *kTag::queryParameterValue(unsigned long ulOrdinal) const { return ulOrdinal < cParameters ? apszValues[ulOrdinal] : NULL; } /** * Opens and reads a file into memory. * @param pszFilename Pointer to input filename. * @remark throws error code (enum). */ void kFileEntry::openAndReadFile(const char *pszFilename) throw (kError::enmErrors) { FILE *phFile; phFile = fopen(pszFilename, "rb"); if (phFile == NULL) throw(kError::error_opening_file); if (fseek(phFile, 0, SEEK_END) == 0 && (cbFile = ftell(phFile)) != 0 && fseek(phFile, 0, SEEK_SET) == 0 ) { pszFile = new char[cbFile+1]; if (pszFile != NULL) { memset(pszFile, 0, (size_t)(cbFile+1)); if (fread(pszFile, cbFile, 1, phFile) != 1) { fclose(phFile); throw(kError::error_reading_file); } fclose(phFile); } else { fclose(phFile); throw(kError::error_new_failed); } } else { fclose(phFile); throw(kError::error_determing_file_size); } } /** * Creates a file entry object. * @param pszFilename Pointer input filename. * @sketch Open file. * Find file length. * Read file into memory. * @remark throws error code enum. */ kFileEntry::kFileEntry(const char *pszFilename) throw (kError::enmErrors) : pParent(NULL), pszParent(NULL) { openAndReadFile(pszFilename); strcpy(&szFilename[0], pszFilename); pszCurrent = pszFile; } /** * Create a file entry object with parent from a include statement. * @parma tag Reference to tag object. * @param pszParent Current pointer in parent file. * @param pParent Pointer to current file entry. * @sketch Open and read file into memory. * Copy filename. * Set current pointer. * @remark throws error code enum. * TODO - use not fully implemented. */ kFileEntry::kFileEntry(const kTag &tag, const char *pszParent, const kFileEntry *pParent) throw (kError::enmErrors) : pParent(pParent), pszParent(pszParent) { const char *pszFilename; /* verify tag */ if (stricmp(tag.getTagname(), "!kInclude")) throw(kError::error_invalid_tag); /* verify parameters */ if (tag.getParameterCount() != 1) throw(kError::error_incorrect_number_of_parameters); /* get filename */ pszFilename = tag.queryParameter("filename"); if (pszFilename == NULL) throw(kError::error_no_filename); /* copy filename */ strcpy(&szFilename[0], pszFilename); /* open and read file */ openAndReadFile(&szFilename[0]); pszCurrent = pszFile; } /** * Destroys the file entry object. */ kFileEntry::~kFileEntry() { delete pszFile; pszCurrent = NULL; } /** * Get linenumber. * @returns linenumber, -1 on error * @remark 0 based. */ long kFileEntry::getLineNumber(void) const { long cLines = 0; char *psz = pszFile; while (psz < pszCurrent && *psz != '\0') { if (*psz == '\n') cLines++; psz++; } return cLines; } /** * Creates a variable entry object. */ kVariableEntry::kVariableEntry(const char *pszName, const char *pszValue) { this->pszName = dupeString(pszName); this->pszValue = dupeString(pszValue); } /** * Destroys the variable entry object. */ kVariableEntry::~kVariableEntry() { if (pszName != NULL) delete pszName; if (pszValue != NULL) delete pszValue; } /** * Executes the sql query present in pszSql. * @remark Throws error code. */ void kSqlEntry::dbExecuteQuery(void) throw (kError::enmErrors) { pres = ::dbExecuteQuery(pszSql); if (pres != NULL) { cRows = dbQueryResultRows(pres); dbFetch(); } else throw(kError::error_sql_failed); } /** * Fetch next row of results. * @returns success indicator. TRUE / FASLE. * @result lifoVariables are destroyed. */ BOOL kSqlEntry::dbFetch(void) { BOOL fRc = FALSE; lifoVariables.destroy(); if (cRows != 0) { cRows--; fRc = ::dbFetch(pres, dbFetchCallBack, (void*)&lifoVariables); } return fRc; } /** * Callback function used when receiving data from a dbFetch call. * This function creates a new kVariableEntry from the input, and pushes it * onto the variable lifo. * @returns 0 success, -1 error. * @param pszValue Field value. Used as variable value. * @param pszFieldName Field name. Used as variable name. * @param pvUser Pointer to user defined data, here a variable lifo. */ static long _System dbFetchCallBack(const char *pszValue, const char *pszFieldName, void *pvUser) { kLIFO *plifoVar = (kLIFO *) pvUser; kVariableEntry *pVarEntry; if (pszFieldName == NULL) return -1; if (pszValue == NULL) pszValue = "-NULL-"; pVarEntry = new kVariableEntry(pszFieldName, pszValue); if (pVarEntry != NULL) plifoVar->push(pVarEntry); else return -1; return 0; } /** * Frees the database result. * @remark Called during destruction of object. */ void kSqlEntry::dbFreeResult(void) { if (pres != NULL) { ::dbFreeResult(pres); pres = NULL; } } /** * Creates a dummy sql entry. */ kSqlEntry::kSqlEntry() : pszSql(NULL), pres(NULL), cRows(0), pFileEntry(pFileEntry), pszCurrent(pszCurrent) { } /** * Interpret the tag and creates an sql entry object from it. * @param pszCurrent Current pointer into the file entry object. * @param pFileEntry Pointer to current file entry object. * @remark throws error code. */ kSqlEntry::kSqlEntry(const kTag &tag, const char *pszCurrent, const kFileEntry *pFileEntry) throw (kError::enmErrors) : pszSql(NULL), pres(NULL), cRows(0), pFileEntry(pFileEntry), pszCurrent(pszCurrent) { const char *pszSql; /* verify tag */ if (!tag.isTag("!kSql")) throw(kError::error_invalid_tag); if (!tag.queryExists("sql")) throw(kError::error_missing_sql); if (tag.getParameterCount() > 1) throw(kError::error_incorrect_number_of_parameters); /* get sql */ pszSql = tag.queryParameter("sql"); if (pszSql == NULL || strlen(pszSql) == 0) throw(kError::error_invalid_sql_tag); this->pszSql = dupeString(pszSql); if (this->pszSql == NULL) throw(kError::error_new_failed); /* execute sql */ dbExecuteQuery(); } /** * Destructor. */ kSqlEntry::~kSqlEntry() { if (pszSql != NULL) delete pszSql; dbFreeResult(); } /** * Equal operator. Comapres this object with a key string. * @returns TRUE if equal, FALSE if not equal. * @param psz Pointer to key string. */ BOOL kSqlEntry::operator ==(const char *psz) const { return lifoVariables.find(psz) != NULL; } /** * Fetches the next set of data. * @returns Success indicator. TRUE / FALSE. */ BOOL kSqlEntry::fetch(void) { return dbFetch(); } /** * Finds a kVariableEntry matching a given key. * @returns Const pointer to variable node or NULL (if not found). * @param pszKey Key to match. */ const kVariableEntry *kSqlEntry::find(const char *pszKey) const { return lifoVariables.find(pszKey); } /** * Checks if a node exists. * @returns TRUE - node exists, FALSE - node does not exist. * @param pVariableEntry Pointer to node/entry. */ BOOL kSqlEntry::exists(const kVariableEntry *pVariableEntry) const { return lifoVariables.exists(pVariableEntry); } /** * Gets last sql error. * @returns Pointer to readonly string describing the error. */ const char *kSqlEntry::querySqlLastError(void) { return dbGetLastErrorDesc(); } /** * Finds an item in the sublist of the nodes.. Matching a given key. * @returns Pointer to subnode if found. * @param pszKey Key to match. * @remark uses the const EntryEntry *find(const char *pszKey) const; function. */ template const kEntryEntry *kLIFO2::findSub(const char *pszKey) const { const kEntry *pE = pTop; const kEntryEntry *pEE = NULL; while (pE != NULL && (pEE = pE->find(pszKey)) == NULL) pE = (const kEntry*)pE->getNext(); return pEE; } /** * Checks if an item exists. * @returns TRUE - exists, FALSE - doesn't exists * @param pEntryEntry Pointer to sub not which is to exists. * @remark uses the BOOL exist(const char *pszKey) const; function. */ template BOOL kLIFO2::existsSub(const kEntryEntry *pEntryEntry) const { kEntry *pE = pTop; BOOL f= FALSE; while (pE != NULL && !(f = pE->exists(pEntryEntry))) pE = (kEntry *)pE->getNext(); return f; } /** * BOOL operator. */ BOOL kGraphData::operator==(const kGraphData &entry) const { return rdX == entry.rdX; } /** * BOOL operator. */ BOOL kGraphData::operator!=(const kGraphData &entry) const { return rdX != entry.rdX; } /** * BOOL operator. */ BOOL kGraphData::operator< (const kGraphData &entry) const { return rdX < entry.rdX; } /** * BOOL operator. */ BOOL kGraphData::operator<=(const kGraphData &entry) const { return rdX <= entry.rdX; } /** * BOOL operator. */ BOOL kGraphData::operator> (const kGraphData &entry) const { return rdX > entry.rdX; } /** * BOOL operator. */ BOOL kGraphData::operator>=(const kGraphData &entry) const { return rdX >= entry.rdX; } /** * Adds a warning to the list of warnings. * @param enmErrorCd Warning code. */ void kGraphDataSet::addWarning(kError::enmErrors enmErrorCd) { if (plistWarnings != NULL) plistWarnings->insert(new kWarningEntry(enmErrorCd)); else assert(!"internal error"); } /** * Constructs an empty dataset. * @remark Throws kError::enmErrors */ kGraphDataSet::kGraphDataSet() throw(kError::enmErrors) : rdCount(_NAN), ulColor(0xffffffffUL), clrColor(-1), pszLegend(NULL), fXDate(FALSE), plistWarnings(NULL) { } /** * Constructs a dataset from a sql statement and a given graph type. * @param pszSql Sql-statement which gives us the data. * @param enmTypeCd This is the integer value of the graph type. * @param plistWarnings Pointer to warning list. * @remark Throws kError::enmErrors */ kGraphDataSet::kGraphDataSet(const char *pszSql, int enmTypeCd, kSortedList *plistWarnings) throw(kError::enmErrors) : rdCount(_NAN), ulColor(0xffffffffUL), clrColor(-1), pszLegend(NULL), fXDate(FALSE), plistWarnings(plistWarnings) { void *pres; if (pszSql == NULL || strlen(pszSql) == 0) throw(kError::error_data_param_is_missing_sql_statement); if (enmTypeCd == kGraph::unknown) throw(kError::error_graph_type_must_be_specified_before_data); switch (enmTypeCd) { case kGraph::lines: { pres = ::dbExecuteQuery(pszSql); if (pres != NULL) { GRAPHCALLBACKPARAM Param = {NULL, this, NULL, (kGraph::enmType)enmTypeCd, 0}; while (::dbFetch(pres, dbDataSetCallBack, (void*)&Param)) Param.lSeqNbr = 0; } else throw(kError::error_sql_failed); break; } case kGraph::pie: addWarning(kError::warning_pie_not_implemented_yet); default: break; } } /** * Callback function used when receiving data. * @returns 0 success, -1 error. * @param pszValue Field value. Used as variable value. * @param pszFieldName Field name. Used as variable name. * @param pvUser Pointer to user defined data, here a variable lifo. */ long _System dbDataSetCallBack(const char *pszValue, const char *pszFieldName, void *pvUser) { PGRAPHCALLBACKPARAM pParam = (PGRAPHCALLBACKPARAM)pvUser; assert(pParam != NULL); switch (pParam->enmTypeCd) { case kGraph::lines: { switch (pParam->lSeqNbr) { case 0: pParam->pData = new kGraphData; /* date test */ if (strlen(pszValue) == (4+1+2+1+2) && pszValue[4] == '-' && pszValue[7] == '-') { strcpy(&pParam->pData->szXDate[0], pszValue); pParam->pData->rdX = kGraph::dateToDbl(&pParam->pData->szXDate[0]); pParam->pDataSet->fXDate = TRUE; } else { pParam->pData->szXDate[0] = '\0'; pParam->pData->rdX = atol(pszValue); } pParam->pDataSet->listData.insert(pParam->pData); break; case 1: pParam->pData->rdY = atol(pszValue); break; case 2: if (pszFieldName == NULL || stricmp(pszFieldName, "legend") != 0) pParam->pDataSet->setColor(pszValue); else pParam->pDataSet->setLegend(pszValue); break; case 3: if (pszFieldName == NULL || stricmp(pszFieldName, "color") != 0) pParam->pDataSet->setLegend(pszValue); else pParam->pDataSet->setColor(pszValue); break; default: pParam->pDataSet->addWarning(kError::warning_to_many_fields_in_data_sql); } break; } case kGraph::pie: default: break; } pParam->lSeqNbr++; return 0; } /** * Destructor. */ kGraphDataSet::~kGraphDataSet(void) { if (pszLegend != NULL) delete pszLegend; } /** * Find max X value in set. * @returns max X value. 0.0 if empty set. */ double kGraphDataSet::maxX(void) const { double rdRet = _NAN; kGraphData *p = listData.getFirst(); while (p != NULL) { if (rdRet < p->rdX || rdRet != rdRet) rdRet = p->rdX; p = (kGraphData*)p->getNext(); } return rdRet == rdRet ? rdRet : 0.0; } /** * Find min X value in set. * @returns min X value. 0.0 if empty set. */ double kGraphDataSet::minX(void) const { double rdRet = _NAN; kGraphData *p = listData.getFirst(); while (p != NULL) { if (rdRet > p->rdX || rdRet != rdRet) rdRet = p->rdX; p = (kGraphData*)p->getNext(); } return rdRet == rdRet ? rdRet : 0.0; } /** * Find max Y value in set. * @returns max Y value. 0.0 if empty set. */ double kGraphDataSet::maxY(void) const { double rdRet = _NAN; kGraphData *p = listData.getFirst(); while (p != NULL) { if (rdRet < p->rdY || rdRet != rdRet) rdRet = p->rdY; p = (kGraphData*)p->getNext(); } return rdRet == rdRet ? rdRet : 0.0; } /** * Find min Y value in set. * @returns min Y value. 0.0 if empty set. */ double kGraphDataSet::minY(void) const { double rdRet = _NAN; kGraphData *p = listData.getFirst(); while (p != NULL) { if (rdRet > p->rdY || rdRet != rdRet) rdRet = p->rdY; p = (kGraphData*)p->getNext(); } return rdRet == rdRet ? rdRet : 0.0; } /** * Sets the ulColor data member. This * @param pszColor Pointer to color string. That is "#RRGGBB". */ void kGraphDataSet::setColor(const char *pszColor) { if (ulColor != 0xffffffffUL) return; if (*pszColor == '#') { ulColor = 0; for (int i = 1; i < 7; i++) { ulColor = ulColor << 4; if (pszColor[i] >= '0' && pszColor[i] <= '9') ulColor |= pszColor[i] - '0'; else if (pszColor[i] >= 'A' && pszColor[i] <= 'F') ulColor |= pszColor[i] - 'A' + 0xa; else if (pszColor[i] >= 'a' && pszColor[i] <= 'f') ulColor |= pszColor[i] - 'a' + 0xa; else addWarning(kError::warning_invalid_color_value); } } else addWarning(kError::warning_invalid_color_value); } /** * Allocates color and set the clrColor datamember. * @param pGraph Pointer to gifdraw image structure. */ void kGraphDataSet::setColor(gdImagePtr pGraph) { if (clrColor == -1) clrColor = gdImageColorAllocate(pGraph, (int)(0xFF & (ulColor >> 16)), (int)(0xFF & (ulColor >> 8)), (int)(0xFF & ulColor)); } /** * Sets the legend string. * @param pszLegend Legend. */ void kGraphDataSet::setLegend(const char *pszLegend) { if (this->pszLegend == NULL) this->pszLegend = dupeString(pszLegend); } /** * Analyses the tag and executes SQL statements. * @param tag Tag. * @param pszBaseDir Base output directory. * @remark Throws kError::enmErrors */ void kGraph::analyseTag(const kTag &tag, const char *pszBaseDir) throw(kError::enmErrors) { int cArgs, i; const char *pszName; const char *pszValue; /*const char *psz; long l; unsigned long ul;*/ /* tagname */ if (stricmp(tag.getTagname(), "!kGraph") != 0) throw(kError::error_invalid_tagname); /* parameters */ i = 0; cArgs = (int)tag.getParameterCount(); while (i < cArgs) { pszName = tag.queryParameterName(i); pszValue = tag.queryParameterValue(i); if (stricmp(pszName, "data") == 0) { if ((int)enmTypeCd == unknown) throw(kError::error_graph_type_must_be_specified_before_data); if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_data_param_is_missing_value); #if 0 listDataSets.insert(new kGraphDataSet(pszValue, enmTypeCd, &listWarnings)); #else fetchData(pszValue); #endif } else if (stricmp(pszName, "type") == 0) { if (pszValue == NULL) throw(kError::error_type_param_is_missing_value); if (stricmp(pszValue, "lines") == 0) enmTypeCd = lines; else if (stricmp(pszValue, "pie") == 0) enmTypeCd = pie; else throw(kError::error_invalid_type); } else if (stricmp(pszName, "subtype") == 0) { if (pszValue != NULL && stricmp(pszValue, "normal") == 0) enmSubTypeCd = normal; else addWarning(kError::warning_invalid_sub_type); } else if (stricmp(pszName, "filename") == 0) { if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_filename_param_is_missing_value); pszFilename = new char[strlen(pszValue) + 2 + strlen(pszBaseDir)]; if (pszFilename == NULL) throw(kError::error_filename_param_is_missing_value); sprintf(pszFilename, "%s\\%s", pszBaseDir, pszValue); } else if (stricmp(pszName, "title") == 0) { if (pszValue != NULL) pszTitle = dupeString(pszValue); else addWarning(kError::warning_title_param_is_missing_value); } else if (stricmp(pszName, "titlex") == 0) { if (pszValue != NULL) pszTitleX = dupeString(pszValue); else addWarning(kError::warning_titlex_param_is_missing_value); } else if (stricmp(pszName, "titley") == 0) { if (pszValue != NULL) pszTitleY = dupeString(pszValue); else addWarning(kError::warning_titley_param_is_missing_value); } else if (stricmp(pszName, "legend") == 0) { fLegend = pszValue == NULL || stricmp(pszValue, "yes") == 0; } else if (stricmp(pszName, "width") == 0 || stricmp(pszName, "cx") == 0) { if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_width_cx_param_is_missing_value); cX = atol(pszValue); if (cX < 100) throw(kError::error_graph_to_small); } else if (stricmp(pszName, "height") == 0 || stricmp(pszName, "cy") == 0) { if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_height_cy_param_is_missing_value); cY = atol(pszValue); if (cY < 100) throw(kError::error_graph_to_small); } else if (stricmp(pszName, "startx") == 0 || stricmp(pszName, "minx") == 0) { if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_xstart_param_is_missing_value); if (isDate(pszValue)) rdStartX = dateToDbl(pszValue); else rdStartX = (double)atol(pszValue); /* TODO: real value not supported */ } else if (stricmp(pszName, "endx") == 0 || stricmp(pszName, "maxx") == 0) { if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_xend_param_is_missing_value); if (isDate(pszValue)) rdEndX = dateToDbl(pszValue); else rdEndX = (double)atol(pszValue); /* TODO: real value not supported */ } else if (stricmp(pszName, "starty") == 0 || stricmp(pszName, "miny") == 0) { if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_ystart_param_is_missing_value); rdStartY = (double)atol(pszValue); /* TODO: real value not supported */ } else if (stricmp(pszName, "endy") == 0 || stricmp(pszName, "maxy") == 0) { if (pszValue == NULL || strlen(pszValue) == 0) throw(kError::error_yend_param_is_missing_value); rdEndY = (double)atol(pszValue); /* TODO: real value not supported */ } else if (stricmp(pszName, "background") == 0) { if (pszValue != NULL) { if (pszValue[0] == '#') ulBGColor = readColor(pszValue); else pszBackground = dupeString(pszValue); } else addWarning(kError::warning_background_param_is_missing_value); } else if (stricmp(pszName, "backgroundcolor") == 0) { if (pszValue != NULL) { if (pszValue[0] == '#') ulBGColor = readColor(pszValue); else addWarning(kError::warning_backgroundcolor_is_invalid); } else addWarning(kError::warning_backgroundcolor_param_is_missing_value); } else if (stricmp(pszName, "foregroundcolor") == 0) { if (pszValue != NULL) { if (pszValue[0] == '#') ulFGColor = readColor(pszValue); else addWarning(kError::warning_foregroundcolor_is_invalid); } else addWarning(kError::warning_foregroundcolor_param_is_missing_value); } else if (stricmp(pszName, "axiscolor") == 0) { if (pszValue != NULL) { if (pszValue[0] == '#') ulAxisColor = readColor(pszValue); else addWarning(kError::warning_axiscolor_is_invalid); } else addWarning(kError::warning_axiscolor_param_is_missing_value); } else addWarning(kError::warning_invalid_parameter); /* next */ i++; } /* check that required parameters are present */ if ((int)enmTypeCd == unknown) throw(kError::error_graph_type_is_missing); if ((int)enmSubTypeCd != normal) throw(kError::error_graph_subtype_is_invalid); if (pszFilename == NULL) throw(kError::error_filename_is_missing); if (listDataSets.getFirst() == NULL) throw(kError::error_no_data); if (cX < 50 || cY < 40) throw(kError::error_invalid_dimentions); #if 0 /* Check for date X values */ fXDate = listDataSets.getFirst()->getXDate(); #endif } /** * Fetches data for multiple data sets. * @param pszSql Pointer to sql statement. */ void kGraph::fetchData(const char *pszSql) throw(kError::enmErrors) { void *pres; switch (enmTypeCd) { case kGraph::lines: { pres = ::dbExecuteQuery(pszSql); if (pres != NULL) { kGraphDataSet *pDataSet = NULL; GRAPHCALLBACKPARAM Param = {this, NULL, NULL, enmTypeCd, 0, NULL, NULL}; while (::dbFetch(pres, dbGraphCallBack, (void*)&Param)) { /* find correct dataset: legend present - allways; else first time only.*/ if (Param.pszLegend != NULL || pDataSet == NULL) pDataSet = findOrCreateDataSet(Param.pszLegend); assert(pDataSet != NULL); pDataSet->setLegend(Param.pszLegend); pDataSet->setColor(Param.pszColor); pDataSet->listData.insert(Param.pData); /* next */ Param.lSeqNbr = 0; Param.pData = NULL; if (Param.pszColor != NULL) { delete Param.pszColor; Param.pszColor = NULL; } if (Param.pszLegend != NULL) { delete Param.pszLegend; Param.pszLegend = NULL; } } if (Param.pData != NULL) delete Param.pData; if (Param.pszColor != NULL) delete Param.pszColor; if (Param.pszLegend != NULL) delete Param.pszLegend; } else throw(kError::error_sql_failed); break; } case kGraph::pie: addWarning(kError::warning_pie_not_implemented_yet); default: break; } } /** * Callback function used when receiving data. * @returns 0 success, -1 error. * @param pszValue Field value. Used as variable value. * @param pszFieldName Field name. Used as variable name. * @param pvUser Pointer to user defined data, here a variable lifo. */ long _System dbGraphCallBack(const char *pszValue, const char *pszFieldName, void *pvUser) { PGRAPHCALLBACKPARAM pParam = (PGRAPHCALLBACKPARAM)pvUser; assert(pParam != NULL); switch (pParam->enmTypeCd) { case kGraph::lines: { switch (pParam->lSeqNbr) { case 0: pParam->pData = new kGraphData; /* date test */ if (strlen(pszValue) == (4+1+2+1+2) && pszValue[4] == '-' && pszValue[7] == '-') { strcpy(&pParam->pData->szXDate[0], pszValue); pParam->pData->rdX = kGraph::dateToDbl(&pParam->pData->szXDate[0]); pParam->pGraph->fXDate = TRUE; } else { pParam->pData->szXDate[0] = '\0'; pParam->pData->rdX = atol(pszValue); } break; case 1: pParam->pData->rdY = atol(pszValue); break; case 2: if (pszFieldName == NULL || stricmp(pszFieldName, "legend") != 0) pParam->pszColor = dupeString(pszValue); else pParam->pszLegend = dupeString(pszValue); break; case 3: if (pszFieldName == NULL || stricmp(pszFieldName, "color") != 0) pParam->pszLegend = dupeString(pszValue); else pParam->pszColor = dupeString(pszValue); break; default: pParam->pGraph->addWarning(kError::warning_to_many_fields_in_data_sql); } break; } case kGraph::pie: default: break; } pParam->lSeqNbr++; return 0; } /** * Finds or creates an dataset for the given legend (id). * @returns Pointer to DataSet. (NULL only when new failes.) * @param pszLegend DataSet identifier. NULL is allowed. */ kGraphDataSet *kGraph::findOrCreateDataSet(const char *pszLegend) throw(kError::enmErrors) { kGraphDataSet *p; if (pszLegend != NULL) { p = listDataSets.getFirst(); while (p != NULL && *p != pszLegend) p = (kGraphDataSet *)p->getNext(); } else p = NULL; if (p == NULL) listDataSets.insert(p = new kGraphDataSet()); return p; } /** * Draw the base structure of the graph. */ void kGraph::createBaseGraph(void) throw(kError::enmErrors) { POINTL ptl; char szMax[32]; pGraph = gdImageCreate(cX, cY); /* background? */ if (pszBackground != NULL) { gdImagePtr pBackground; FILE *phFile; phFile = fopen(pszBackground, "rb"); if (phFile) { pBackground = gdImageCreateFromGif(phFile); if (pBackground != NULL) { gdImageCopyResized(pGraph, pBackground, 0, 0, //destination low left corner 0, 0, //source low left corner cX, cY, //destination width and height gdImageSX(pBackground), //source width gdImageSY(pBackground) //source height ); gdImageDestroy(pBackground); } else addWarning(kError::warning_failed_to_load_background_image); fclose(phFile); } else addWarning(kError::warning_failed_to_open_background_image); } /* calc base coordinates */ ptlOrigo.x = max(2 + (fXDate ? 5 : 0)*6, (long)(cX*0.05)); _itoa((int)maxY(), &szMax[0], 10); ptlOrigo.y = max((long)(2 + strlen(&szMax[0])*6), (long)(cY*0.05)); if (pszTitleX) ptlOrigo.y += 16; if (pszTitleY) ptlOrigo.x += 16; ptlYEnd.x = ptlOrigo.x; ptlYEnd.y = cY - ptlOrigo.y - (pszTitle ? max(20, (long)(cY*0.10)) : 0); ptlXEnd.x = cX - ptlOrigo.x; ptlXEnd.y = ptlOrigo.y; if (fLegend) { addWarning(kError::warning_legend_is_not_implemented_yet); ptlXEnd.x -= (long)(cX * 0.20); } /* allocate default colors */ setColors(); /* draw axsis */ if (pszTitleX) gdImageStringLB(pGraph, gdFontGiant, (int)(ptlOrigo.x + (ptlXEnd.x - ptlOrigo.x)/2.0 - strlen(pszTitleX)*9/2.0), max(2, (int)(cY*0.01))+ 16, pszTitleX, clrForeground); if (pszTitleY) gdImageStringUpLB(pGraph, gdFontGiant, max(2, (int)(cX*0.01)), (int)(ptlOrigo.y + (ptlYEnd.y - ptlOrigo.y)/2.0 - strlen(pszTitleY)*9/2.0), pszTitleY, clrForeground); if (pszTitle) { ptl.x = (long)(cX/2.0 - strlen(pszTitle)*9/2.0); ptl.y = cY - max(5, (int)(cY*0.02)); gdImageStringLB(pGraph, gdFontGiant, (int)ptl.x, (int)ptl.y, pszTitle, clrForeground); gdImageLineLB(pGraph, (int)ptl.x - 3, (int)ptl.y - 18, (int)ptl.x + strlen(pszTitle)*9 + 5, (int)ptl.y - 18, clrForeground); } gdImageLineLB(pGraph, (int)ptlOrigo.x, (int)ptlOrigo.y-2, (int)ptlYEnd.x, (int)ptlYEnd.y+3, clrAxis); gdImageLineLB(pGraph, (int)ptlYEnd.x+2, (int)ptlYEnd.y, (int)ptlYEnd.x, (int)ptlYEnd.y+3, clrAxis); gdImageLineLB(pGraph, (int)ptlYEnd.x-2, (int)ptlYEnd.y, (int)ptlYEnd.x, (int)ptlYEnd.y+3, clrAxis); gdImageLineLB(pGraph, (int)ptlOrigo.x-2, (int)ptlOrigo.y, (int)ptlXEnd.x+3, (int)ptlXEnd.y, clrAxis); gdImageLineLB(pGraph, (int)ptlXEnd.x, (int)ptlXEnd.y+2, (int)ptlXEnd.x+3, (int)ptlXEnd.y, clrAxis); gdImageLineLB(pGraph, (int)ptlXEnd.x, (int)ptlXEnd.y-2, (int)ptlXEnd.x+3, (int)ptlXEnd.y, clrAxis); /* scale - x */ char szData[20]; double rdMin, rdMax, rd, rdD; int cTags; rdMax = maxX(); rdMin = minX(); if (fXDate) { if (!kGraph::dblToDate(rdMin, &szData[0])) addWarning(kError::warning_failed_to_convert_date); } else sprintf(&szData[0], "%1.*f", rdMax > 10 ? 0 : 1, rdMin); gdImageStringLB(pGraph, gdFontSmall, (int)(ptlOrigo.x - strlen(&szData[0])*6/2.0), (int)(ptlOrigo.y - 3), &szData[0], clrForeground); if (fXDate) { if (!kGraph::dblToDate(rdMax, &szData[0])) addWarning(kError::warning_failed_to_convert_date); } else sprintf(&szData[0], "%1.*f", rdMax > 10 ? 0 : 1, rdMax); gdImageStringLB(pGraph, gdFontSmall, (int)(ptlXEnd.x - strlen(&szData[0])*6/2.0), (int)(ptlOrigo.y - 3), &szData[0], clrForeground); rd = strlen(&szData[0])*6 + 20; //length of on scale tag cTags = (int)((ptlXEnd.x - ptlOrigo.x) / rd); //max number of tags if (cTags > 0) { rdD = rdMax - rdMin; if (rd > 10) rdD = (int)(rdD/cTags); else rdD = ((int)(rdD/cTags*10))/10; rd = rdMin + rdD; while (rdD > 0.0 && (rd + rdD) <= rdMax) { int x = (int)(ptlOrigo.x + (ptlXEnd.x - ptlOrigo.x)/(rdMax-rdMin)*(rd - rdMin)); if (fXDate) { if (!kGraph::dblToDate(rd, &szData[0])) addWarning(kError::warning_failed_to_convert_date); } else sprintf(&szData[0], "%1.*f", rdMax > 10 ? 0 : 1, rd); gdImageStringLB(pGraph, gdFontSmall, x - (int)(strlen(&szData[0])*6/2.0), (int)(ptlOrigo.y - 3), &szData[0], clrForeground); gdImageLineLB(pGraph, x, (int)(ptlOrigo.y - 1), x, (int)(ptlOrigo.y + 1), clrAxis); /* next */ rd += rdD; } } /* scale - y */ rdMax = maxY(); rdMin = minY(); sprintf(&szData[0], "%1.*f", rdMax > 10 ? 0 : 1, rdMin); gdImageStringLB(pGraph, gdFontSmall, (int)(ptlOrigo.x - 3 - strlen(&szData[0])*6), (int)(ptlOrigo.y + 8/2 + 2), &szData[0], clrForeground); sprintf(&szData[0], "%1.*f", rdMax > 10 ? 0 : 1, rdMax); gdImageStringLB(pGraph, gdFontSmall, (int)(ptlOrigo.x - 3 - strlen(&szData[0])*6), (int)(ptlYEnd.y + 8/2), &szData[0], clrForeground); rd = 14 + 10; //length of on scale tag cTags = (int)((ptlYEnd.y - ptlOrigo.y) / rd); //max number of tags if (cTags > 0) { rdD = rdMax - rdMin; if (rd > 10) rdD = (int)(rdD/cTags); else rdD = ((int)(rdD/cTags*10))/10; rd = rdMin + rdD; while (rdD > 0.0 && (rd + rdD) <= rdMax) { int y = (int)(ptlOrigo.y + (ptlYEnd.y - ptlOrigo.y)/(rdMax-rdMin)*(rd - rdMin)); sprintf(&szData[0], "%1.*f", rdMax > 10 ? 0 : 1, rd); gdImageStringLB(pGraph, gdFontSmall, (int)(ptlOrigo.x - 3 - strlen(&szData[0])*6), y + 8/2, &szData[0], clrForeground); gdImageLineLB(pGraph, (int)(ptlOrigo.x - 1), y, (int)(ptlOrigo.x + 1), y, clrAxis); /* next */ rd += rdD; } } } /** * Draw all lines. */ void kGraph::drawLines(void) throw(kError::enmErrors) { kGraphDataSet *pDataSet; assert((int)enmTypeCd == (int)lines); pDataSet = listDataSets.getFirst(); while (pDataSet != NULL) { pDataSet->setColor(pGraph); drawLine(pDataSet); /* next */ pDataSet = (kGraphDataSet *)pDataSet->getNext(); } } /** * Draws a line for the given data set. * @param pDataSet Pointer to the dataset for the line. * @remark not implemented. */ void kGraph::drawLine(const kGraphDataSet *pDataSet) throw(kError::enmErrors) { BOOL fFirstTime = TRUE; int xPrev = 0; /* don't have to initialized, but it removes gcc warning (release) */ int yPrev = 0; /* don't have to initialized, but it removes gcc warning (release) */ double rdMinX = minX(); double rdMinY = minY(); double rdDX = maxX() - rdMinX; double rdDY = maxY() - rdMinY; kGraphData *p = pDataSet->listData.getFirst(); if (rdDX != 0.0 && rdDY != 0.0) { while (p != NULL) { int x, y; x = (int)(ptlOrigo.x + ((ptlXEnd.x - ptlOrigo.x)/rdDX * (p->rdX - rdMinX))); y = (int)(ptlOrigo.y + ((ptlYEnd.y - ptlOrigo.y)/rdDY * (p->rdY - rdMinY))); if (fFirstTime) { xPrev = x; yPrev = y; fFirstTime = FALSE; } gdImageLineLB(pGraph, xPrev, yPrev, x, y, pDataSet->getColor()); /* next */ p = (kGraphData*)p->getNext(); xPrev = x; yPrev = y; } } } /** * Draws the legend. * @remark not implemented. */ void kGraph::drawLegend(void) throw(kError::enmErrors) { if (fLegend == FALSE) return; /* TODO */ } /** * Converts an ISO date to days. * @returns Returns days after Christ, year 0. * @param pszDate Pointer to ISO date string. * @remark pszDate should be an ISO date, if not an exception may occur. * Use isDate to check it is an ISO date. */ double kGraph::dateToDbl(const char *pszDate) { double rdRet = dbDateToDaysAfterChrist(pszDate); return rdRet != -1 ? rdRet : _NAN; } /** * Converts days to an ISO date. * @returns Success indicator. TRUE / FALSE. * @param rdDate Date in double, (really days from year 0). * @param pszDate Pointer to result buffer. Will hold ISO date string on successful return. */ BOOL kGraph::dblToDate(double rdDate, char *pszDate) { if (rdDate != rdDate) return FALSE; return dbDaysAfterChristToDate((signed long)rdDate, pszDate); } /** * Checks if the given text string is an ISO date. * @returns TRUE - if date, else FALSE. * @param pszDate Possible ISO date string. */ BOOL kGraph::isDate(const char *pszDate) { return strlen(pszDate) == 10 && pszDate[4] == '-' && pszDate[7] == '-'; } /** * frees all storage. * @remark Called only at destruction. (That is destructor or when contruction failes.) */ void kGraph::destroy(void) { if (pGraph != NULL) gdImageDestroy(pGraph); if (pszFilename != NULL) delete pszFilename; if (pszTitle != NULL) delete pszTitle; if (pszTitleX != NULL) delete pszTitleX; if (pszTitleY != NULL) delete pszTitleY; if (pszBackground != NULL) delete pszBackground; } /** * Adds a warning to the list of warnings. * @param enmErrorCd Warning code. */ void kGraph::addWarning(kError::enmErrors enmErrorCd) { listWarnings.insert(new kWarningEntry(enmErrorCd)); } /** * Finds the max X value in all data sets. * @returns max X value. 0.0 if no sets */ double kGraph::maxX(void) { if (rdMaxX != rdMaxX) { /* first time */ if (rdEndX != rdEndX) { /* not spesified by user */ kGraphDataSet *p = listDataSets.getFirst(); while (p != NULL) { double rd = p->maxX(); if (rdMaxX < rd || rdMaxX != rdMaxX) rdMaxX = rd; p = (kGraphDataSet*)p->getNext(); } rdMaxX = rdMaxX == rdMaxX ? rdMaxX : 0.0; } else /* spesified by user */ rdMaxX = rdEndX; } return rdMaxX; } /** * Finds the min X value in all data sets. * @returns min X value. 0.0 if no sets * @remark Addjusts value if close to 0. */ double kGraph::minX(void) { if (rdMinX != rdMinX) { /* first time */ if (rdStartX != rdStartX) { /* not spesified by user */ kGraphDataSet *p = listDataSets.getFirst(); while (p != NULL) { double rd = p->minX(); if (rdMinX > rd || rdMinX != rdMinX) rdMinX = rd; p = (kGraphDataSet*)p->getNext(); } /* addjustment */ if (rdMinX != rdMinX) rdMinX = fXDate ? maxX() - 1.0 : 0.0; else if (rdMinX >= 0.0) rdMinX = fXDate ? rdMinX : 0.0; else { /* TODO negative values are not supported yet. */ addWarning(kError::warning_negative_values_are_not_supported_yet); rdMinX = 0.0; } } else /* spesified by user */ rdMinX = rdStartX; } return rdMinX; } /** * Finds the max Y value in all data sets. * @returns max Y value. 0.0 if no sets */ double kGraph::maxY(void) { if (rdMaxY != rdMaxY) { /* first time */ if (rdEndY != rdEndY) { /* not spesified by user */ kGraphDataSet *p = listDataSets.getFirst(); while (p != NULL) { double rd = p->maxY(); if (rdMaxY < rd || rdMaxY != rdMaxY) rdMaxY = rd; p = (kGraphDataSet*)p->getNext(); } rdMaxY = rdMaxY == rdMaxY ? rdMaxY : 0.0; } else /* spesified by user */ rdMaxY = rdEndY; } return rdMaxY; } /** * Finds the min Y value in all data sets. * @returns min Y value. 0.0 if no sets * @remark Addjusts value if close to 0. */ double kGraph::minY(void) { if (rdMinY != rdMinY) { /* first time */ if (rdStartY != rdStartY) { /* not spesified by user */ kGraphDataSet *p = listDataSets.getFirst(); while (p != NULL) { double rd = p->minY(); if (rdMinY > rd || rdMinY != rdMinY) rdMinY = rd; p = (kGraphDataSet*)p->getNext(); } /* addjustment */ if (rdMinY != rdMinY) rdMinY = 0.0; else if (rdMinY >= 0.0) rdMinY = 0.0; else { /* TODO negative values are not supported yet. */ addWarning(kError::warning_negative_values_are_not_supported_yet); rdMinY = 0.0; } } else /* spesified by user */ rdMinY = rdStartY; } return rdMinY; } /** * Reads a color parameter. * @returns 24Bit RGB color value. * @param pszColor Pointer to color string. That is "#RRGGBB". */ unsigned long kGraph::readColor(const char *pszColor) { unsigned long ulColor = 0xffffff; if (*pszColor == '#') { ulColor = 0; for (int i = 1; i < 7; i++) { ulColor = ulColor << 4; if (pszColor[i] >= '0' && pszColor[i] <= '9') ulColor |= pszColor[i] - '0'; else if (pszColor[i] >= 'A' && pszColor[i] <= 'F') ulColor |= pszColor[i] - 'A' + 0xa; else if (pszColor[i] >= 'a' && pszColor[i] <= 'f') ulColor |= pszColor[i] - 'a' + 0xa; else addWarning(kError::warning_invalid_color_value); } } else addWarning(kError::warning_invalid_color_value); return ulColor; } /** * Allocates a color in the image. * @returns GifDraw Color. * @param ulColor 24Bit RGB color value. */ int kGraph::setColor(unsigned long ulColor) { return gdImageColorAllocate(pGraph, (int)(0xFF & (ulColor >> 16)), (int)(0xFF & (ulColor >> 8)), (int)(0xFF & ulColor)); } /** * Allocates/sets default colors. */ void kGraph::setColors(void) { clrBackground = setColor(ulBGColor != ~0UL ? ulBGColor : 0x00000000UL); /* vitally important that this is done first! */ clrForeground = setColor(ulFGColor != ~0UL ? ulFGColor : 0x0000FF00UL); clrAxis = setColor(ulAxisColor != ~0UL ? ulAxisColor : 0x00808080UL); } /** * Constructor. * @remark Throws kError::enmError on error. */ kGraph::kGraph(const kTag &tag, const char *pszBaseDir) throw(kError::enmErrors) : pGraph(NULL), enmTypeCd(unknown), enmSubTypeCd(normal), pszFilename(NULL), pszTitle(NULL), pszTitleX(NULL), pszTitleY(NULL), pszBackground(NULL), fLegend(FALSE), cX(~0L), cY(~0L), rdStartX(_NAN), rdEndX(_NAN), rdStartY(_NAN), rdEndY(_NAN), fXDate(FALSE), ulBGColor(~0UL), ulFGColor(~0UL), ulAxisColor(~0UL), rdMaxX(_NAN), rdMinX(_NAN), rdMaxY(_NAN), rdMinY(_NAN) { try { analyseTag(tag, pszBaseDir); createBaseGraph(); if (fLegend) drawLegend(); drawLines(); } catch (kError::enmErrors enmErrorCd) { destroy(); throw(enmErrorCd); } } /** * Destructor. * @remark calls destroy(). */ kGraph::~kGraph(void) { destroy(); } /** * Saves the graph to the filename specified in the tag. * 2 * @returns Errorcode. */ kError::enmErrors kGraph::save(void) { FILE *phFileOut; assert(pGraph != NULL); assert(pszFilename != NULL); phFileOut = fopen(pszFilename, "wb"); if (phFileOut != NULL) { gdImageGif(pGraph, phFileOut); fclose(phFileOut); } else return kError::error_opening_output_file; return kError::no_error; } /** * Prints warnings. * @returns Number of warnings. * @param phLog Pointer to log file handle. */ unsigned long kGraph::showWarnings(FILE *phLog, const kFileEntry *pCurFile) { unsigned long cWarnings = 0; kWarningEntry *p = listWarnings.getFirst(); while (p != NULL) { cWarnings++; fprintf(phLog, "%s(%ld) : warning: kGraph - %s\n", pCurFile->getFilename(), pCurFile->getLineNumber()+1, kError::queryDescription(p->getWarningCode())); p = (kWarningEntry*)p->getNext(); } return cWarnings; } /* include template code */ #include "kLIFO.cpp" #include "kList.cpp" #ifdef __EMX__ template class kLIFO; #endif