From 18f65eb8b0e9979f69a32fdbd079afc6401abeb5 Mon Sep 17 00:00:00 2001 From: Lew Rossman Date: Sun, 17 Mar 2019 19:54:51 -0400 Subject: [PATCH] Replace fixed-sized comment strings with dynamic strings --- include/epanet2.bas | 9 ++- include/epanet2.def | 2 + include/epanet2.h | 6 +- include/epanet2.vb | 9 ++- include/epanet2_2.h | 22 +++++- include/epanet2_enums.h | 15 +++- src/epanet.c | 57 +++++++++++--- src/epanet2.c | 12 ++- src/funcs.h | 6 +- src/inpfile.c | 60 +++++++------- src/input1.c | 4 +- src/input2.c | 29 +++++-- src/input3.c | 39 +++++---- src/project.c | 149 ++++++++++++++++++++++++++++++++++- src/types.h | 27 ++++--- tests/test_comments.cpp | 131 ++++++++++++++++++++++++++++++ win_build/WinSDK/epanet2.def | 2 + 17 files changed, 494 insertions(+), 85 deletions(-) create mode 100644 tests/test_comments.cpp diff --git a/include/epanet2.bas b/include/epanet2.bas index 30c5d8d..f0ba641 100644 --- a/include/epanet2.bas +++ b/include/epanet2.bas @@ -5,7 +5,7 @@ Attribute VB_Name = "Module1" 'Declarations of functions in the EPANET PROGRAMMERs TOOLKIT '(EPANET2.DLL) -'Last updated on 02/28/2019 +'Last updated on 03/17/2019 ' These are codes used by the DLL functions Public Const EN_ELEVATION = 0 ' Node parameters @@ -83,6 +83,13 @@ Public Const EN_MAXHEADERROR = 2 Public Const EN_MAXFLOWCHANGE = 3 Public Const EN_MASSBALANCE = 4 +Public Const EN_NODE = 0 ' Component types +Public Const EN_LINK = 1 +Public Const EN_TIMEPAT = 2 +Public Const EN_CURVE = 3 +Public Const EN_CONTROL = 4 +Public Const EN_RULE = 5 + Public Const EN_NODECOUNT = 0 ' Component counts Public Const EN_TANKCOUNT = 1 Public Const EN_LINKCOUNT = 2 diff --git a/include/epanet2.def b/include/epanet2.def index f6b48bd..f9374ef 100644 --- a/include/epanet2.def +++ b/include/epanet2.def @@ -21,6 +21,7 @@ EXPORTS ENepanet = _ENepanet@16 ENgetaveragepatternvalue = _ENgetaveragepatternvalue@8 ENgetbasedemand = _ENgetbasedemand@12 + ENgetcomment = _ENgetcomment@12 ENgetcontrol = _ENgetcontrol@24 ENgetcoord = _ENgetcoord@12 ENgetcount = _ENgetcount@8 @@ -80,6 +81,7 @@ EXPORTS ENsavehydfile = _ENsavehydfile@4 ENsaveinpfile = _ENsaveinpfile@4 ENsetbasedemand = _ENsetbasedemand@12 + ENsetcomment = _ENsetcomment@12 ENsetcontrol = _ENsetcontrol@24 ENsetcoord = _ENsetcoord@20 ENsetcurve = _ENsetcurve@16 diff --git a/include/epanet2.h b/include/epanet2.h index 9f02fe5..38448da 100644 --- a/include/epanet2.h +++ b/include/epanet2.h @@ -7,7 +7,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 02/28/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ @@ -77,6 +77,10 @@ extern "C" { int DLLEXPORT ENsettitle(char *line1, char *line2, char *line3); + int DLLEXPORT ENgetcomment(int object, int index, char *comment); + + int DLLEXPORT ENsetcomment(int object, int index, char *comment); + int DLLEXPORT ENgetcount(int object, int *count); int DLLEXPORT ENsaveinpfile(const char *filename); diff --git a/include/epanet2.vb b/include/epanet2.vb index f3613b7..b4345eb 100644 --- a/include/epanet2.vb +++ b/include/epanet2.vb @@ -4,7 +4,7 @@ 'Declarations of functions in the EPANET PROGRAMMERs TOOLKIT '(EPANET2.DLL) for use with VB.Net. -'Last updated on 02/28/2019 +'Last updated on 03/17/2019 Imports System.Runtime.InteropServices Imports System.Text @@ -88,6 +88,13 @@ Public Const EN_MAXHEADERROR = 2 Public Const EN_MAXFLOWCHANGE = 3 Public Const EN_MASSBALANCE = 4 +Public Const EN_NODE = 0 ' Component types +Public Const EN_LINK = 1 +Public Const EN_TIMEPAT = 2 +Public Const EN_CURVE = 3 +Public Const EN_CONTROL = 4 +Public Const EN_RULE = 5 + Public Const EN_NODECOUNT = 0 'Component counts Public Const EN_TANKCOUNT = 1 Public Const EN_LINKCOUNT = 2 diff --git a/include/epanet2_2.h b/include/epanet2_2.h index 3e6fbff..c7774d3 100644 --- a/include/epanet2_2.h +++ b/include/epanet2_2.h @@ -11,7 +11,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 02/08/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ @@ -146,6 +146,26 @@ typedef struct Project *EN_Project; */ int DLLEXPORT EN_settitle(EN_Project ph, char *line1, char *line2, char *line3); + /** + @brief Retrieves a descriptive comment assigned to a Node, Link, Pattern or Curve. + @param ph an EPANET project handle. + @param object a type of object (either EN_NODE, EN_LINK, EN_TIMEPAT or EN_CURVE) + @param index the object's index starting from 1 + @param[out] comment the comment string assigned to the object + @return an error code + */ + int DLLEXPORT EN_getcomment(EN_Project ph, int object, int index, char *comment); + + /** + @brief Assigns a descriptive comment to a Node, Link, Pattern or Curve. + @param ph an EPANET project handle. + @param object a type of object (either EN_NODE, EN_LINK, EN_TIMEPAT or EN_CURVE) + @param index the object's index starting from 1 + @param[out] comment the comment string assigned to the object + @return an error code + */ + int DLLEXPORT EN_setcomment(EN_Project ph, int object, int index, char *comment); + /** @brief Retrieves the number of objects of a given type in a project. @param ph an EPANET project handle. diff --git a/include/epanet2_enums.h b/include/epanet2_enums.h index a445846..558d670 100644 --- a/include/epanet2_enums.h +++ b/include/epanet2_enums.h @@ -9,7 +9,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 01/14/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ @@ -128,6 +128,19 @@ typedef enum { EN_MASSBALANCE = 4 //!< Cumulative water quality mass balance ratio } EN_AnalysisStatistic; +/// Types of network objects +/** +A network model is composed of these types of objects. +*/ +typedef enum { + EN_NODE = 0, //!< Nodes + EN_LINK = 1, //!< Links + EN_TIMEPAT = 2, //!< Time patterns + EN_CURVE = 3, //!< Data curves + EN_CONTROL = 4, //!< Simple controls + EN_RULE = 5 //!< Control rules +} EN_ObjectType; + /// Types of objects to count /** These options tell @ref EN_getcount which type of object to count. diff --git a/src/epanet.c b/src/epanet.c index e0abd48..29462bc 100644 --- a/src/epanet.c +++ b/src/epanet.c @@ -7,7 +7,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 03/08/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ @@ -275,6 +275,32 @@ int DLLEXPORT EN_settitle(EN_Project p, char *line1, char *line2, char *line3) return 0; } +int DLLEXPORT EN_getcomment(EN_Project p, int object, int index, char *comment) +/*---------------------------------------------------------------- +** Input: object = a type of object (see EN_ObjectType) +** index = the object's index +** Output: comment = the object's descriptive comment +** Returns: error code +** Purpose: Retrieves an object's descriptive comment +**---------------------------------------------------------------- +*/ +{ + return getcomment(&p->network, object, index, comment); +} + +int DLLEXPORT EN_setcomment(EN_Project p, int object, int index, char *comment) +/*---------------------------------------------------------------- +** Input: object = a type of object (see EN_ObjectType) +** index = the object's index +** comment = a descriptive comment to assign +** Returns: error code +** Purpose: Assigns a descriptive comment to an object +**---------------------------------------------------------------- +*/ +{ + return setcomment(&p->network, object, index, comment); +} + int DLLEXPORT EN_getcount(EN_Project p, int object, int *count) /*---------------------------------------------------------------- ** Input: object = type of object to count (see EN_CountType) @@ -1208,7 +1234,7 @@ int DLLEXPORT EN_setoption(EN_Project p, int option, double value) if (demand->Pat == tmpPat) { demand->Pat = pat; - strcpy(demand->Name, ""); + demand->Name = xstrcpy(&demand->Name, "", MAXMSG); } } } @@ -1668,7 +1694,7 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType) demand = (struct Sdemand *)malloc(sizeof(struct Sdemand)); demand->Base = 0.0; demand->Pat = hyd->DefPat; // Use default pattern - strcpy(demand->Name, ""); + demand->Name = NULL; demand->next = NULL; node->D = demand; @@ -1743,7 +1769,7 @@ int DLLEXPORT EN_addnode(EN_Project p, char *id, int nodeType) node->Rpt = 0; node->X = MISSING; node->Y = MISSING; - strcpy(node->Comment, ""); + node->Comment = NULL; // Insert new node into hash table hashtable_insert(net->NodeHashTable, node->ID, nIdx); @@ -1769,7 +1795,6 @@ int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) int i, nodeType, tankindex; Snode *node; Pdemand demand, nextdemand; - Psource source; // Cannot modify network structure while solvers are active if (!p->Openflag) return 102; @@ -1801,16 +1826,17 @@ int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) // Remove node from its hash table hashtable_delete(net->NodeHashTable, node->ID); - // Free memory allocated to node's demands & WQ source + // Free memory allocated to node's demands, WQ source & comment demand = node->D; while (demand != NULL) { nextdemand = demand->next; + free(demand->Name); free(demand); demand = nextdemand; } - source = node->S; - if (source != NULL) free(source); + free(node->S); + free(node->Comment); // Shift position of higher entries in Node & Coord arrays down one for (i = index; i <= net->Nnodes - 1; i++) @@ -1823,6 +1849,7 @@ int DLLEXPORT EN_deletenode(EN_Project p, int index, int actionCode) // Remove references to demands & source in last (inactive) Node array entry net->Node[net->Nnodes].D = NULL; net->Node[net->Nnodes].S = NULL; + net->Node[net->Nnodes].Comment = NULL; // If deleted node is a tank, remove it from the Tank array if (nodeType != EN_JUNCTION) @@ -2522,7 +2549,6 @@ int DLLEXPORT EN_settankdata(EN_Project p, int index, double elev, return 0; } - int DLLEXPORT EN_getcoord(EN_Project p, int index, double *x, double *y) /*---------------------------------------------------------------- ** Input: index = node index @@ -2752,7 +2778,7 @@ int DLLEXPORT EN_setdemandname(EN_Project p, int nodeIndex, int demandIndex, for (d = p->network.Node[nodeIndex].D; n < demandIndex && d->next != NULL; d = d->next) n++; if (n != demandIndex) return 253; - strncpy(d->Name, demandName, MAXMSG); + d->Name = xstrcpy(&d->Name, demandName, MAXMSG); return 0; } @@ -2944,7 +2970,7 @@ int DLLEXPORT EN_addlink(EN_Project p, char *id, int linkType, link->R = 0; link->Rc = 0; link->Rpt = 0; - strcpy(link->Comment, ""); + link->Comment = NULL; hashtable_insert(net->LinkHashTable, link->ID, n); return 0; @@ -2993,6 +3019,10 @@ int DLLEXPORT EN_deletelink(EN_Project p, int index, int actionCode) // Remove link from its hash table hashtable_delete(net->LinkHashTable, link->ID); + // Remove link's comment + free(net->Link[index].Comment); + net->Link[net->Nlinks].Comment = NULL; + // Shift position of higher entries in Link array down one for (i = index; i <= net->Nlinks - 1; i++) { @@ -3864,6 +3894,7 @@ int DLLEXPORT EN_addpattern(EN_Project p, char *id) // Assign properties to the new pattern pat = &net->Pattern[n]; strcpy(pat->ID, id); + pat->Comment = NULL; pat->Length = 1; pat->F = (double *)calloc(1, sizeof(double)); if (pat->F == NULL) err = 1; @@ -3924,6 +3955,7 @@ int DLLEXPORT EN_deletepattern(EN_Project p, int index) // Free the pattern's factor array FREE(net->Pattern[index].F); + FREE(net->Pattern[index].Comment); // Shift the entries in the network's Pattern array for (i = index; i < net->Npats; i++) net->Pattern[i] = net->Pattern[i+1]; @@ -4141,6 +4173,7 @@ int DLLEXPORT EN_addcurve(EN_Project p, char *id) // Set the properties of the new curve curve = &net->Curve[n]; strcpy(curve->ID, id); + curve->Comment = NULL; curve->Npts = 1; curve->Type = GENERIC_CURVE; curve->X = (double *)calloc(1, sizeof(double)); @@ -4194,6 +4227,7 @@ int DLLEXPORT EN_deletecurve(EN_Project p, int index) // Free the curve's data arrays FREE(net->Curve[index].X); FREE(net->Curve[index].Y); + FREE(net->Curve[index].Comment); // Shift the entries in the network's Curve array for (i = index; i < net->Ncurves; i++) net->Curve[i] = net->Curve[i + 1]; @@ -5092,7 +5126,6 @@ int DLLEXPORT EN_getelseaction(EN_Project p, int ruleIndex, int actionIndex, **---------------------------------------------------------------- */ { - Saction *actions; Saction *action; diff --git a/src/epanet2.c b/src/epanet2.c index 003d888..935d6c4 100644 --- a/src/epanet2.c +++ b/src/epanet2.c @@ -7,7 +7,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 02/08/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ #ifndef __APPLE__ @@ -112,6 +112,16 @@ int DLLEXPORT ENsettitle(char *line1, char *line2, char *line3) return EN_settitle(_defaultProject, line1, line2, line3) ; } +int DLLEXPORT ENgetcomment(int object, int index, char *comment) +{ + return EN_getcomment(_defaultProject, object, index, comment); +} + +int DLLEXPORT ENsetcomment(int object, int index, char *comment) +{ + return EN_setcomment(_defaultProject, object, index, comment); +} + int DLLEXPORT ENgetcount(int object, int *count) { return EN_getcount(_defaultProject, object, count); diff --git a/src/funcs.h b/src/funcs.h index 154dcfc..6fffeb3 100755 --- a/src/funcs.h +++ b/src/funcs.h @@ -7,7 +7,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 02/08/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ #ifndef FUNCS_H @@ -39,7 +39,11 @@ int findpump(Network *, int); void adjustpatterns(Network *, int); void adjustcurves(Network *, int); +int getcomment(Network *, int, int, char *); +int setcomment(Network *, int, int, const char *); + char *getTmpName(char *); +char *xstrcpy(char **, const char *, const size_t n); int strcomp(const char *, const char *); double interp(int, double [], double [], double); char *geterrmsg(int, char *); diff --git a/src/inpfile.c b/src/inpfile.c index 8ce02dc..45b5aab 100644 --- a/src/inpfile.c +++ b/src/inpfile.c @@ -7,7 +7,7 @@ Description: saves network data to an EPANET formatted text file Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE -Last Updated: 03/09/2019 +Last Updated: 03/17/2019 ****************************************************************************** */ @@ -84,7 +84,7 @@ void saveauxdata(Project *pr, FILE *f) case _LABELS: case _BACKDROP: case _TAGS: - fprintf(f, "%s", line); + fprintf(f, "\n%s", line); } } } @@ -161,8 +161,8 @@ int saveinpfile(Project *pr, const char *fname) for (i = 1; i <= net->Njuncs; i++) { node = &net->Node[i]; - fprintf(f, "\n %-31s %12.4f ;%s", node->ID, node->El * pr->Ucf[ELEV], - node->Comment); + fprintf(f, "\n %-31s %12.4f", node->ID, node->El * pr->Ucf[ELEV]); + if (node->Comment) fprintf(f, " ;%s", node->Comment); } // Write [RESERVOIRS] section @@ -175,9 +175,10 @@ int saveinpfile(Project *pr, const char *fname) { node = &net->Node[tank->Node]; sprintf(s, " %-31s %12.4f", node->ID, node->El * pr->Ucf[ELEV]); - if ((j = tank->Pat) > 0) sprintf(s1, " %-31s", net->Pattern[j].ID); - else strcpy(s1, ""); - fprintf(f, "\n%s %s ;%s", s, s1, node->Comment); + if ((j = tank->Pat) > 0) sprintf(s1, " %s", net->Pattern[j].ID); + else strcpy(s1, " "); + fprintf(f, "\n%s %-31s", s, s1); + if (node->Comment) fprintf(f, " ;%s", node->Comment); } } @@ -197,9 +198,10 @@ int saveinpfile(Project *pr, const char *fname) (tank->Hmax - node->El) * pr->Ucf[ELEV], sqrt(4.0 * tank->A / PI) * pr->Ucf[ELEV], tank->Vmin * SQR(pr->Ucf[ELEV]) * pr->Ucf[ELEV]); - if ((j = tank->Vcurve) > 0) sprintf(s1, "%-31s", net->Curve[j].ID); - else strcpy(s1, ""); - fprintf(f, "\n%s %s ;%s", s, s1, node->Comment); + if ((j = tank->Vcurve) > 0) sprintf(s1, "%s", net->Curve[j].ID); + else strcpy(s1, " "); + fprintf(f, "\n%s %-31s", s, s1); + if (node->Comment) fprintf(f, " ;%s", node->Comment); } } @@ -216,17 +218,15 @@ int saveinpfile(Project *pr, const char *fname) if (hyd->Formflag == DW) kc = kc * pr->Ucf[ELEV] * 1000.0; km = link->Km * SQR(d) * SQR(d) / 0.02517; - sprintf(s, " %-31s %-31s %-31s %12.4f %12.4f", link->ID, - net->Node[link->N1].ID, net->Node[link->N2].ID, - link->Len * pr->Ucf[LENGTH], d * pr->Ucf[DIAM]); - - if (hyd->Formflag == DW) sprintf(s1, "%12.4f %12.4f", kc, km); - else sprintf(s1, "%12.4f %12.4f", kc, km); + sprintf(s, " %-31s %-31s %-31s %12.4f %12.4f %12.4f %12.4f", + link->ID, net->Node[link->N1].ID, net->Node[link->N2].ID, + link->Len * pr->Ucf[LENGTH], d * pr->Ucf[DIAM], kc, km); if (link->Type == CVPIPE) sprintf(s2, "CV"); else if (link->Status == CLOSED) sprintf(s2, "CLOSED"); - else strcpy(s2, ""); - fprintf(f, "\n%s %s %s ;%s", s, s1, s2, link->Comment); + else strcpy(s2, " "); + fprintf(f, "\n%s %-6s", s, s2); + if (link->Comment) fprintf(f, " ;%s", link->Comment); } } @@ -264,7 +264,7 @@ int saveinpfile(Project *pr, const char *fname) // Optional speed pattern if ((j = pump->Upat) > 0) { - sprintf(s1, " PATTERN %s", net->Pattern[j].ID); + sprintf(s1, " PATTERN %s", net->Pattern[j].ID); strcat(s, s1); } @@ -275,7 +275,9 @@ int saveinpfile(Project *pr, const char *fname) strcat(s, s1); } - fprintf(f, "\n%s ;%s", s, link->Comment); + fprintf(f, "\n%s", s); + if (link->Comment) fprintf(f, " ;%s", link->Comment); + } // Write [VALVES] section @@ -316,7 +318,8 @@ int saveinpfile(Project *pr, const char *fname) sprintf(s1, "%-31s %12.4f", net->Curve[j].ID, km); } else sprintf(s1, "%12.4f %12.4f", kc, km); - fprintf(f, "\n%s %s ;%s", s, s1, link->Comment); + fprintf(f, "\n%s %s", s, s1); + if (link->Comment) fprintf(f, " ;%s", link->Comment); } // Write [DEMANDS] section @@ -329,9 +332,10 @@ int saveinpfile(Project *pr, const char *fname) for (demand = node->D; demand != NULL; demand = demand->next) { sprintf(s, " %-31s %14.6f", node->ID, ucf * demand->Base); - if ((j = demand->Pat) > 0) sprintf(s1, " %s", net->Pattern[j].ID); - else strcpy(s1, ""); - fprintf(f, "\n%s %s ;%s", s, s1, demand->Name); + if ((j = demand->Pat) > 0) sprintf(s1, " %-31s", net->Pattern[j].ID); + else strcpy(s1, " "); + fprintf(f, "\n%s %-31s", s, s1); + if (demand->Name) fprintf(f, " ;%s", demand->Name); } } @@ -392,6 +396,7 @@ int saveinpfile(Project *pr, const char *fname) fprintf(f, s_PATTERNS); for (i = 1; i <= net->Npats; i++) { + if (net->Pattern[i].Comment) fprintf(f, "\n;%s", net->Pattern[i].Comment); for (j = 0; j < net->Pattern[i].Length; j++) { if (j % 6 == 0) fprintf(f, "\n %-31s", net->Pattern[i].ID); @@ -404,11 +409,11 @@ int saveinpfile(Project *pr, const char *fname) fprintf(f, s_CURVES); for (i = 1; i <= net->Ncurves; i++) { + if (net->Curve[i].Comment) fprintf(f, "\n;%s", net->Curve[i].Comment); for (j = 0; j < net->Curve[i].Npts; j++) { curve = &net->Curve[i]; - fprintf(f, "\n %-31s %12.4f %12.4f", curve->ID, curve->X[j], - curve->Y[j]); + fprintf(f, "\n %-31s %12.4f %12.4f", curve->ID, curve->X[j], curve->Y[j]); } } @@ -776,7 +781,6 @@ int saveinpfile(Project *pr, const char *fname) } else fprintf(f, "\n %-20sNO",field->Name); } - fprintf(f, "\n\n"); // Write [COORDINATES] section fprintf(f, "\n\n"); @@ -787,9 +791,9 @@ int saveinpfile(Project *pr, const char *fname) if (node->X == MISSING || node->Y == MISSING) continue; fprintf(f, "\n %-31s %14.6f %14.6f", node->ID, node->X, node->Y); } - fprintf(f, "\n\n"); // Save auxilary data to new input file + fprintf(f, "\n"); saveauxdata(pr, f); // Close the new input file diff --git a/src/input1.c b/src/input1.c index 11cbce4..fe606d9 100644 --- a/src/input1.c +++ b/src/input1.c @@ -7,7 +7,7 @@ Description: retrieves network data from an EPANET input file Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE -Last Updated: 12/15/2018 +Last Updated: 03/17/2019 ****************************************************************************** */ @@ -334,7 +334,7 @@ void adjustdata(Project *pr) if (demand->Pat == 0) { demand->Pat = hyd->DefPat; - strcpy(demand->Name, ""); + xstrcpy(&demand->Name, "", MAXMSG); } } } diff --git a/src/input2.c b/src/input2.c index b63a9a9..2820e35 100644 --- a/src/input2.c +++ b/src/input2.c @@ -7,7 +7,7 @@ Description: reads and interprets network data from an EPANET input file Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE -Last Updated: 01/01/2019 +Last Updated: 03/17/2019 ****************************************************************************** */ @@ -166,6 +166,7 @@ int readdata(Project *pr) net->Npats = parser->MaxPats; parser->PrevPat = NULL; parser->PrevCurve = NULL; + parser->LineComment[0] = '\0'; sect = -1; errsum = 0; @@ -175,19 +176,31 @@ int readdata(Project *pr) { // Make copy of line and scan for tokens strcpy(wline, line); - parser->Ntokens = gettokens(wline, parser->Tok, MAXTOKS, - parser->Comment); + parser->Ntokens = gettokens(wline, parser->Tok, MAXTOKS, parser->Comment); - // Skip blank lines and comments + // Skip blank lines and those filled with a comment parser->ErrTok = -1; - if (parser->Ntokens == 0) continue; - if (*parser->Tok[0] == ';') continue; + if (parser->Ntokens == 0) + { + // Store full line comment for Patterns and Curves + if (sect == _PATTERNS || sect == _CURVES) + { + strncpy(parser->LineComment, parser->Comment, MAXMSG); + } + continue; + } + + // Apply full line comment for Patterns and Curves + if (sect == _PATTERNS || sect == _CURVES) + { + strcpy(parser->Comment, parser->LineComment); + } + parser->LineComment[0] = '\0'; // Check if max. line length exceeded if (strlen(line) >= MAXLINE) { - sprintf(pr->Msg, "%s section: %s", geterrmsg(214, pr->Msg), - SectTxt[sect]); + sprintf(pr->Msg, "%s section: %s", geterrmsg(214, pr->Msg), SectTxt[sect]); writeline(pr, pr->Msg); writeline(pr, line); errsum++; diff --git a/src/input3.c b/src/input3.c index ef5211c..fb35da2 100644 --- a/src/input3.c +++ b/src/input3.c @@ -7,7 +7,7 @@ Description: parses network data from a line of an EPANET input file Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE -Last Updated: 01/01/2019 +Last Updated: 03/17/2019 ****************************************************************************** */ @@ -115,15 +115,14 @@ int juncdata(Project *pr) node->Ke = 0.0; node->Rpt = 0; node->Type = JUNCTION; - strcpy(node->Comment, parser->Comment); - + node->Comment = xstrcpy(&node->Comment, parser->Comment, MAXMSG); // create a demand record, even if no demand is specified here. demand = (struct Sdemand *) malloc(sizeof(struct Sdemand)); if (demand == NULL) return 101; demand->Base = y; demand->Pat = p; - strncpy(demand->Name, "", MAXMSG); + demand->Name = NULL; demand->next = NULL; node->D = demand; hyd->NodeDemand[njuncs] = y; @@ -224,7 +223,7 @@ int tankdata(Project *pr) node->S = NULL; node->Ke = 0.0; node->Type = (diam == 0) ? RESERVOIR : TANK; - strcpy(node->Comment, parser->Comment); + node->Comment = xstrcpy(&node->Comment, parser->Comment, MAXMSG); tank->Node = i; tank->H0 = initlevel; tank->Hmin = minlevel; @@ -329,7 +328,7 @@ int pipedata(Project *pr) link->Type = type; link->Status = status; link->Rpt = 0; - strcpy(link->Comment, parser->Comment); + link->Comment = xstrcpy(&link->Comment, parser->Comment, MAXMSG); return 0; } @@ -392,7 +391,7 @@ int pumpdata(Project *pr) link->Type = PUMP; link->Status = OPEN; link->Rpt = 0; - strcpy(link->Comment, parser->Comment); + link->Comment = xstrcpy(&link->Comment, parser->Comment, MAXMSG); pump->Link = net->Nlinks; pump->Ptype = NOCURVE; // NOCURVE is a placeholder pump->Hcurve = 0; @@ -535,7 +534,7 @@ int valvedata(Project *pr) link->Type = type; link->Status = status; link->Rpt = 0; - strcpy(link->Comment, parser->Comment); + link->Comment = xstrcpy(&link->Comment, parser->Comment, MAXMSG); net->Valve[net->Nvalves].Link = net->Nlinks; return 0; } @@ -559,6 +558,7 @@ int patterndata(Project *pr) double x; SFloatlist *f; STmplist *p; + Spattern *pattern; n = parser->Ntokens - 1; if (n < 1) return 201; @@ -566,8 +566,13 @@ int patterndata(Project *pr) // Check for a new pattern if (parser->PrevPat != NULL && strcmp(parser->Tok[0], parser->PrevPat->ID) == 0) p = parser->PrevPat; - else p = getlistitem(parser->Tok[0], parser->Patlist); - if (p == NULL) return setError(parser, 0, 205); + else + { + p = getlistitem(parser->Tok[0], parser->Patlist); + if (p == NULL) return setError(parser, 0, 205); + pattern = &(net->Pattern[p->i]); + pattern->Comment = xstrcpy(&pattern->Comment, parser->Comment, MAXMSG); + } // Add parsed multipliers to the pattern for (i = 1; i <= n; i++) @@ -606,13 +611,19 @@ int curvedata(Project *pr) double x, y; SFloatlist *fx, *fy; STmplist *c; + Scurve *curve; // Check for valid curve ID if (parser->Ntokens < 3) return 201; if (parser->PrevCurve != NULL && strcmp(parser->Tok[0], parser->PrevCurve->ID) == 0) c = parser->PrevCurve; - else c = getlistitem(parser->Tok[0], parser->Curvelist); - if (c == NULL) return setError(parser, 0, 206); + else + { + c = getlistitem(parser->Tok[0], parser->Curvelist); + if (c == NULL) return setError(parser, 0, 206); + curve = &(net->Curve[c->i]); + curve->Comment = xstrcpy(&curve->Comment, parser->Comment, MAXMSG); + } // Check for valid data if (!getfloat(parser->Tok[1], &x)) return setError(parser, 1, 202); @@ -731,7 +742,7 @@ int demanddata(Project *pr) // with what is specified in this section demand->Base = y; demand->Pat = p; - strncpy(demand->Name, parser->Comment, MAXMSG); + demand->Name = xstrcpy(&demand->Name, parser->Comment, MAXMSG); hyd->NodeDemand[j] = MISSING; // marker - next iteration will append a new category. } @@ -744,7 +755,7 @@ int demanddata(Project *pr) if (demand == NULL) return 101; demand->Base = y; demand->Pat = p; - strncpy(demand->Name, parser->Comment, MAXMSG); + demand->Name = xstrcpy(&demand->Name, parser->Comment, MAXMSG); demand->next = NULL; cur_demand->next = demand; } diff --git a/src/project.c b/src/project.c index aceffa8..fd4cb8d 100644 --- a/src/project.c +++ b/src/project.c @@ -7,7 +7,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 03/05/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ @@ -370,6 +370,7 @@ int allocdata(Project *pr) { pr->network.Pattern[n].Length = 0; pr->network.Pattern[n].F = NULL; + pr->network.Pattern[n].Comment = NULL; } for (n = 0; n <= pr->parser.MaxCurves; n++) { @@ -377,10 +378,17 @@ int allocdata(Project *pr) pr->network.Curve[n].Type = GENERIC_CURVE; pr->network.Curve[n].X = NULL; pr->network.Curve[n].Y = NULL; + pr->network.Curve[n].Comment = NULL; } for (n = 0; n <= pr->parser.MaxNodes; n++) { pr->network.Node[n].D = NULL; // node demand + pr->network.Node[n].S = NULL; // node source + pr->network.Node[n].Comment = NULL; + } + for (n = 0; n <= pr->parser.MaxLinks; n++) + { + pr->network.Link[n].Comment = NULL; } } @@ -459,18 +467,29 @@ void freedata(Project *pr) while (demand != NULL) { nextdemand = demand->next; + free(demand->Name); free(demand); demand = nextdemand; } // Free memory used for WQ source data source = pr->network.Node[j].S; - if (source != NULL) free(source); + free(source); + free(pr->network.Node[j].Comment); } free(pr->network.Node); } - // Free memory for other network objects + // Free memory for link data + if (pr->network.Link != NULL) + { + for (j = 0; j <= pr->parser.MaxLinks; j++) + { + free(pr->network.Link[j].Comment); + } + } free(pr->network.Link); + + // Free memory for other network objects free(pr->network.Tank); free(pr->network.Pump); free(pr->network.Valve); @@ -479,7 +498,11 @@ void freedata(Project *pr) // Free memory for time patterns if (pr->network.Pattern != NULL) { - for (j = 0; j <= pr->parser.MaxPats; j++) free(pr->network.Pattern[j].F); + for (j = 0; j <= pr->parser.MaxPats; j++) + { + free(pr->network.Pattern[j].F); + free(pr->network.Pattern[j].Comment); + } free(pr->network.Pattern); } @@ -490,6 +513,7 @@ void freedata(Project *pr) { free(pr->network.Curve[j].X); free(pr->network.Curve[j].Y); + free(pr->network.Curve[j].Comment); } free(pr->network.Curve); } @@ -878,6 +902,87 @@ void adjustcurves(Network *network, int index) } } +int getcomment(Network *network, int object, int index, char *comment) +//---------------------------------------------------------------- +// Input: object = a type of network object +// index = index of the specified object +// comment = the object's comment string +// Output: error code +// Purpose: gets the comment string assigned to an object. +//---------------------------------------------------------------- +{ + char *currentcomment; + + // Get pointer to specified object's comment + switch (object) + { + case NODE: + if (index < 1 || index > network->Nnodes) return 251; + currentcomment = network->Node[index].Comment; + break; + case LINK: + if (index < 1 || index > network->Nlinks) return 251; + currentcomment = network->Link[index].Comment; + break; + case TIMEPAT: + if (index < 1 || index > network->Npats) return 251; + currentcomment = network->Pattern[index].Comment; + break; + case CURVE: + if (index < 1 || index > network->Ncurves) return 251; + currentcomment = network->Curve[index].Comment; + break; + default: + strcpy(comment, ""); + return 251; + } + + // Copy the object's comment to the returned string + if (currentcomment) strcpy(comment, currentcomment); + else comment[0] = '\0'; + return 0; +} + +int setcomment(Network *network, int object, int index, const char *newcomment) +//---------------------------------------------------------------- +// Input: object = a type of network object +// index = index of the specified object +// newcomment = new comment string +// Output: error code +// Purpose: sets the comment string of an object. +//---------------------------------------------------------------- +{ + char *comment; + + switch (object) + { + case NODE: + if (index < 1 || index > network->Nnodes) return 251; + comment = network->Node[index].Comment; + network->Node[index].Comment = xstrcpy(&comment, newcomment, MAXMSG); + return 0; + + case LINK: + if (index < 1 || index > network->Nlinks) return 251; + comment = network->Link[index].Comment; + network->Link[index].Comment = xstrcpy(&comment, newcomment, MAXMSG); + return 0; + + case TIMEPAT: + if (index < 1 || index > network->Npats) return 251; + comment = network->Pattern[index].Comment; + network->Pattern[index].Comment = xstrcpy(&comment, newcomment, MAXMSG); + return 0; + + case CURVE: + if (index < 1 || index > network->Ncurves) return 251; + comment = network->Curve[index].Comment; + network->Curve[index].Comment = xstrcpy(&comment, newcomment, MAXMSG); + return 0; + + default: return 251; + } +} char *getTmpName(char *fname) //---------------------------------------------------------------- @@ -911,6 +1016,42 @@ char *getTmpName(char *fname) return fname; } +char *xstrcpy(char **s1, const char *s2, const size_t n) +//---------------------------------------------------------------- +// Input: s1 = destination string +// s2 = source string +// n = maximum size of strings +// Output: none +// Purpose: like strcpy except for dynamic strings. +//---------------------------------------------------------------- +{ + size_t n1 = 0, n2; + + // Source string is empty -- free destination string + if (s2 == NULL || strlen(s2) == 0) + { + free(*s1); + return NULL; + } + + // Source string not empty -- overwrite destination string + else + { + // See if size of destination string needs to grow + if (*s1) n1 = strlen(*s1); + if ((n2 = strlen(s2)) > n) n2 = n; + if (n2 > n1) + { + free(*s1); + *s1 = (char *)malloc((n2 + 1) * sizeof(char)); + } + + // Copy the new comment string into the existing one + if (*s1) strcpy(*s1, s2); + return *s1; + } +} + int strcomp(const char *s1, const char *s2) /*--------------------------------------------------------------- ** Input: s1 = character string diff --git a/src/types.h b/src/types.h index 027bb61..4329414 100755 --- a/src/types.h +++ b/src/types.h @@ -7,7 +7,7 @@ Authors: see AUTHORS Copyright: see AUTHORS License: see LICENSE - Last Updated: 01/01/2019 + Last Updated: 03/17/2019 ****************************************************************************** */ @@ -118,11 +118,15 @@ typedef int INT4; ---------------------------------------------- Enumerated Data Types ---------------------------------------------- - */ +*/ typedef enum { NODE, - LINK + LINK, + TIMEPAT, + CURVE, + CONTROL, + RULE } ObjectType; typedef enum { @@ -333,6 +337,7 @@ typedef struct Tmplist STmplist; // Pointer to temporary list of objects typedef struct // Time Pattern Object { char ID[MAXID+1]; // pattern ID + char *Comment; // pattern comment int Length; // pattern length double *F; // pattern factors } Spattern; @@ -340,6 +345,7 @@ typedef struct // Time Pattern Object typedef struct // Curve Object { char ID[MAXID+1]; // curve ID + char *Comment; // curve comment CurveType Type; // curve type int Npts; // number of points double *X; // x-values @@ -350,7 +356,7 @@ struct Sdemand // Demand List Item { double Base; // baseline demand int Pat; // pattern index - char Name[MAXMSG+1]; // demand category name + char *Name; // demand category name struct Sdemand *next; // next demand list item }; typedef struct Sdemand *Pdemand; // Pointer to demand list @@ -386,7 +392,7 @@ typedef struct // Node Object double Ke; // emitter coeff. int Rpt; // reporting flag NodeType Type; // node type - char Comment[MAXMSG+1]; // node comment + char *Comment; // node comment } Snode; typedef struct // Link Object @@ -406,7 +412,7 @@ typedef struct // Link Object LinkType Type; // link type StatusType Status; // initial status int Rpt; // reporting flag - char Comment[MAXMSG+1]; // link Comment + char *Comment; // link comment } Slink; typedef struct // Tank Object @@ -544,10 +550,11 @@ typedef struct { FILE *InFile; // Input file handle char - DefPatID[MAXID+1], // Default demand pattern ID - InpFname[MAXFNAME+1], // Input file name - *Tok[MAXTOKS], // Array of token strings - Comment[MAXMSG+1]; // Comment text + DefPatID[MAXID + 1], // Default demand pattern ID + InpFname[MAXFNAME + 1], // Input file name + *Tok[MAXTOKS], // Array of token strings + Comment[MAXMSG + 1], // Comment text + LineComment[MAXMSG + 1]; // Full line comment int MaxNodes, // Node count from input file */ diff --git a/tests/test_comments.cpp b/tests/test_comments.cpp new file mode 100644 index 0000000..404b5ce --- /dev/null +++ b/tests/test_comments.cpp @@ -0,0 +1,131 @@ +// Test of EPANET's Comment Handling Functions +// +// This is a test of the API functions EN_getcomment and EN_setcomment +// +#define _CRT_SECURE_NO_DEPRECATE + +//#define NO_BOOST + +#ifndef NO_BOOST +#define BOOST_TEST_MODULE "toolkit" +#include +#endif + +#include +#include +#include "epanet2_2.h" + +#define DATA_PATH_INP "./net1.inp" +#define DATA_PATH_RPT "./test.rpt" +#define DATA_PATH_OUT "./test.out" +#define DATA_PATH_TMP "./tmp.inp" + +#ifdef NO_BOOST +#define BOOST_REQUIRE(x) (((x)) ? cout << "\nPassed at line " << __LINE__ : cout << "\nFailed at line " << __LINE__ ) +#endif + +using namespace std; + +int checkComments(EN_Project ph) +{ + int index; + char comment[EN_MAXMSG + 1]; + EN_getnodeindex(ph, (char *)"11", &index); + EN_getcomment(ph, EN_NODE, index, comment); + if (strcmp(comment, (char *)"J11") != 0) return 0; + + EN_getnodeindex(ph, (char *)"23", &index); + EN_getcomment(ph, EN_NODE, index, comment); + if (strcmp(comment, (char *)"Junc23") != 0) return 0; + + EN_getlinkindex(ph, (char *)"11", &index); + EN_getcomment(ph, EN_LINK, index, comment); + if (strcmp(comment, (char *)"P11") != 0) return 0; + + EN_getlinkindex(ph, (char *)"9", &index); + EN_getcomment(ph, EN_LINK, index, comment); + if (strcmp(comment, (char *)"Pump9") != 0) return 0; + + EN_getpatternindex(ph, (char *)"1", &index); + EN_getcomment(ph, EN_TIMEPAT, index, comment); + if (strcmp(comment, (char *)"Time Pattern 1") != 0) return 0; + + EN_getcurveindex(ph, (char *)"1", &index); + EN_getcomment(ph, EN_CURVE, index, comment); + if (strcmp(comment, (char *)"Curve 1") != 0) return 0; + return 1; +} + +#ifndef NO_BOOST +BOOST_AUTO_TEST_SUITE (test_toolkit) +BOOST_AUTO_TEST_CASE(test_setlinktype) +{ +#else +int main(int argc, char *argv[]) +{ +#endif + + int error = 0; + int index; + char comment[EN_MAXMSG+1]; + + // Create & load a project + EN_Project ph = NULL; + EN_createproject(&ph); + std::string path_inp = string(DATA_PATH_INP); + std::string path_rpt = string(DATA_PATH_RPT); + std::string path_out = string(DATA_PATH_OUT); + error = EN_open(ph, path_inp.c_str(), path_rpt.c_str(), ""); + BOOST_REQUIRE(error == 0); + + // Add comments to selected objects + EN_getnodeindex(ph, (char *)"11", &index); + EN_setcomment(ph, EN_NODE, index, (char *)"J11"); + EN_getnodeindex(ph, (char *)"23", &index); + EN_setcomment(ph, EN_NODE, index, (char *)"Junc23"); + EN_getlinkindex(ph, (char *)"11", &index); + + EN_setcomment(ph, EN_LINK, index, (char *)"P11"); + EN_getlinkindex(ph, (char *)"9", &index); + EN_setcomment(ph, EN_LINK, index, (char *)"Pump9"); + + EN_getpatternindex(ph, (char *)"1", &index); + EN_setcomment(ph, EN_TIMEPAT, index, (char *)"Time Pattern 1"); + + EN_getcurveindex(ph, (char *)"1", &index); + EN_setcomment(ph, EN_CURVE, index, (char *)"Curve 1"); + + // Retrieve comments and test their values + BOOST_REQUIRE(checkComments(ph) == 1); + + // Replace short comment with longer one and vice versa + EN_getnodeindex(ph, (char *)"11", &index); + EN_setcomment(ph, EN_NODE, index, (char *)"Junction11"); + EN_getcomment(ph, EN_NODE, index, comment); + BOOST_REQUIRE(strcmp(comment, (char *)"Junction11") == 0); + EN_setcomment(ph, EN_NODE, index, (char *)"J11"); + EN_getcomment(ph, EN_NODE, index, comment); + BOOST_REQUIRE(strcmp(comment, (char *)"J11") == 0); + + // Save & re-open project + string path_tmp = string(DATA_PATH_TMP); + EN_saveinpfile(ph, path_tmp.c_str()); + EN_close(ph); + error = EN_open(ph, path_tmp.c_str(), path_rpt.c_str(), ""); + BOOST_REQUIRE(error == 0); + + // Check that comments were saved & read correctly + BOOST_REQUIRE(checkComments(ph) == 1); + remove(path_tmp.c_str()); + + // Close project + EN_close(ph); + EN_deleteproject(&ph); + +#ifdef NO_BOOST + return 0; +#endif +} +#ifndef NO_BOOST +BOOST_AUTO_TEST_SUITE_END() +#endif \ No newline at end of file diff --git a/win_build/WinSDK/epanet2.def b/win_build/WinSDK/epanet2.def index f6b48bd..f9374ef 100644 --- a/win_build/WinSDK/epanet2.def +++ b/win_build/WinSDK/epanet2.def @@ -21,6 +21,7 @@ EXPORTS ENepanet = _ENepanet@16 ENgetaveragepatternvalue = _ENgetaveragepatternvalue@8 ENgetbasedemand = _ENgetbasedemand@12 + ENgetcomment = _ENgetcomment@12 ENgetcontrol = _ENgetcontrol@24 ENgetcoord = _ENgetcoord@12 ENgetcount = _ENgetcount@8 @@ -80,6 +81,7 @@ EXPORTS ENsavehydfile = _ENsavehydfile@4 ENsaveinpfile = _ENsaveinpfile@4 ENsetbasedemand = _ENsetbasedemand@12 + ENsetcomment = _ENsetcomment@12 ENsetcontrol = _ENsetcontrol@24 ENsetcoord = _ENsetcoord@20 ENsetcurve = _ENsetcurve@16