//
// File: hdsGraph.cpp
//
// Legal Notice: This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
// The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
// Software Foundation, either version 3 of the License, or any later version.
//
// The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <dirent.h>
#include <netdb.h>
#include <signal.h>
#include "hzErrcode.h"
//#include "hzFsTbl.h"
#include "hzDissemino.h"
using namespace std ;
/*
** Graphics Entity Constructors/Destructors
*/
#define FCG_NULL 0x00 // Graphic not assigned to a command
#define FCG_START 0x01 // Graphic is a START stadium
#define FCG_STEP 0x02 // Graphic is a START stadium
#define FCG_IF 0x03 // Graphic is an IF hexagon
#define FCG_ELSEIF 0x04 // Graphic is an ELSE-IF hexagon
#define FCG_SWITCH 0x05 // Graphic is an DOWHILE loop hexagon
#define FCG_CASE 0x06 // Graphic is an DOWHILE loop hexagon
#define FCG_DEFAULT 0x07 // Graphic is an DOWHILE loop hexagon
#define FCG_FOR 0x08 // Graphic is an FOR loop hexagon
#define FCG_WHILE 0x09 // Graphic is an WHILE loop hexagon
#define FCG_DOWHILE 0x0A // Graphic is an DOWHILE loop hexagon
#define FCG_GOTO 0x0B // Graphic is a GOTO stadium
#define FCG_BREAK 0x0C // Graphic is a GOTO stadium
#define FCG_CONTINUE 0x0D // Graphic is a GOTO stadium
#define FCG_RETURN 0x0E // Graphic is a GOTO stadium
#define FCG_EXIT 0x0F // Graphic is a GOTO stadium
global hzString _hzGlobal_str_TRUE = "True" ;
global hzString _hzGlobal_str_FALSE = "False" ;
global hzString _hzGlobal_str_START = "START" ;
hdsGraphic::hdsGraphic (void)
{
m_eShape = HDSGRAPH_NULL ;
m_ColorFill = 0x00ffffff ;
m_ColorLine = 0 ;
m_Thick = 1 ;
m_Width = m_Height = 0 ;
m_Lft = m_Rht = m_Top = m_Bot = 0 ;
m_Rad = 0 ;
m_Id = 0 ;
}
hdsDiagram::hdsDiagram (hdsApp* pApp)
{
InitVE(pApp) ;
m_pApp = pApp ;
m_ColorFill = 0x00ffffff ;
m_ColorLine = 0 ;
m_Width = m_Height = 0 ;
}
hdsFlowchart::hdsFlowchart (hdsApp* pApp)
{
InitVE(pApp) ;
m_pApp = pApp ;
m_pShapes = 0 ; // Final list of graphic in flowchart
m_ColorTerm = 0x0000ffff ; // Cyan
m_ColorTest = 0x00ffffe0 ; // Light yellow
m_ColorProc = 0x0066ff00 ; // Light green
m_ColorLine = 0x00000000 ; // Black
m_Height = m_Width = 0 ; // Caclulated from incident graphic objects
m_nShapes = m_nConnects = 0 ; // No of graphic objects and connectors
m_nBoundary = 0 ; // Width of boundary. Default 0
m_nWidthConn = 1 ; // Connector line width
}
/*
** hdsText - please note, not a VE
*/
hdsText::hdsText (void)
{
Clear() ;
}
void hdsText::Clear (void)
{
m_Text = (char*) 0 ;
m_Font = (char*) 0 ;
m_Color = 0 ;
m_posV = m_posH = 0 ;
}
// hdsLine hdsLine::operator= (const hdsLine& op)
// {
// m_Text = op.m_Text ;
// m_Font = op.m_Font ;
// m_Color = op.m_Color ;
// m_posY = op.m_posY ;
// m_posV = op.m_posV ;
// }
hdsText hdsText::operator= (const hdsText& op)
{
m_Text = op.m_Text ;
m_Font = op.m_Font ;
m_Color = op.m_Color ;
m_posH = op.m_posH ;
m_posV = op.m_posV ;
return *this ;
}
void hdsText::Init (const hzString& text, const hzString& font, uint32_t color, int16_t posV, int16_t posH, int32_t align)
{
m_Text = text ;
m_Font = font ;
m_Color = color & 0x00ffffff ;
m_posH = posH ;
m_posV = posV ;
m_Color |= align == 2 ? 0x02000000 : align == 1 ? 0x01000000 : 0 ;
}
/*
** hdsChartPie
*/
hdsChartPie::hdsChartPie (hdsApp* pApp)
{
InitVE(pApp) ;
m_Total = 0.0 ;
m_Height = m_Width = 0 ;
m_ccX = m_ccY = m_Rad = m_IdxX = m_IdxY = m_nSlices = 0 ;
}
hdsChartPie::~hdsChartPie (void)
{
hzList<_pie*>::Iter pi ; // Pie chart component iterator
_pie* ptr ; // Pie chart component
for (pi = m_Parts ; pi.Valid() ; pi++)
{
ptr = pi.Element() ;
delete ptr ;
}
}
/*
** hdsChartBar
*/
hdsChartBar::hdsChartBar (hdsApp* pApp)
{
InitVE(pApp) ;
m_nMinY = m_nMaxY = m_nMinX = m_nMaxX = 0 ;
m_origX = m_origY = 0 ;
}
hdsChartBar::~hdsChartBar (void)
{
hzList<_rset*>::Iter ri ; // Line chart dataset iterator
_rset* ptr ; // Line chart dataset
for (ri = m_Sets ; ri.Valid() ; ri++)
{
ptr = ri.Element() ;
delete ptr ;
}
}
/*
** hdsChartStd
*/
hdsChartStd::hdsChartStd (hdsApp* pApp)
{
InitVE(pApp) ;
m_nMinY = m_nMaxY = m_nMinX = m_nMaxX = 0 ;
m_origX = m_origY = 0 ;
}
hdsChartStd::~hdsChartStd (void)
{
hzList<_rset*>::Iter ri ; // Line chart dataset iterator
_rset* ptr ; // Line chart dataset
for (ri = m_Sets ; ri.Valid() ; ri++)
{
ptr = ri.Element() ;
delete ptr ;
}
}
/*
** Graphics Config Functions
*/
hzEcode hdsApp::_readText (hdsText& tx, hzXmlNode* pN)
{
// Read a HTML canvas text entity
//
// Arguments: 1) tx The current text entity
// 2) pN Current XML node
//
// Returns: E_SYNTAX If there is a syntax error
// E_OK If the text entity was set OK
_hzfunc("hdsApp::_readText") ;
hzAttrset ai ; // Attribute iterator
hzString text ; // Text content
hzString font ; // Font
uint32_t color ; // Color
int16_t posH ; // H position
int16_t posV ; // Y position
int32_t align ; // Align - Left, center or right
hzEcode rc = E_OK ; // Return code
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("text")) Fatal("File %s Line %d: Incorrect node (%s) supplied. Must be <text>\n", *pN->Filename(), pN->Line(), pN->txtName()) ;
tx.Clear() ;
for (ai = pN ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("str")) text = ai.Value() ;
else if (ai.NameEQ("font")) font = ai.Value() ;
else if (ai.NameEQ("ypos")) posH = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("xpos")) posV = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("color")) IsHexnum(color, ai.Value()) ;
else if (ai.NameEQ("align"))
{
if (ai.ValEQ("left")) align = 0 ;
else if (ai.ValEQ("center")) align = 1 ;
else if (ai.ValEQ("right")) align = 2 ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <text> Bad alignment (%s)\n", *pN->Filename(), pN->Line(), ai.Value()) ; break ; }
}
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <text> Bad param (%s=%s)\n", *pN->Filename(), pN->Line(), ai.Name(), ai.Value()) ; break ; }
}
if (posH == -1) { rc=E_SYNTAX ; m_pLog->Log("File %s Line %d: <text> No ypos supplied\n", *pN->Filename(), pN->Line()) ; }
if (posV == -1) { rc=E_SYNTAX ; m_pLog->Log("File %s Line %d: <text> No xpos supplied\n", *pN->Filename(), pN->Line()) ; }
if (!text) { rc=E_SYNTAX ; m_pLog->Log("File %s Line %d: <text> No string supplied\n", *pN->Filename(), pN->Line()) ; }
if (rc == E_OK)
tx.Init(text, font, color, posH, posV, align) ;
return rc ;
}
hdsVE* hdsApp::_readChartPie (hzXmlNode* pN)
{
// Read in configs for a pie chart. The current XML node is assumed to be at an <xchartPie> tag
//
// Argument: pN The current XML node
//
// Returns: Pointer to the new pie chart as a visible entity
// NULL If the any configuration errors occur
_hzfunc("hdsApp::_readChartPie") ;
hzArray<hdsChartPie::_pie> ar ; // Temp array of slices
hdsChartPie::_pie slice ; // Current slice
hdsChartPie* pPie ; // The chart
hdsText TX ; // Text item
hzAttrset ai ; // Attribute iterator
hdsVE* thisVE ; // Visible entity (pie chart)
hzXmlNode* pN1 ; // Subtag probe
hzString cfg ; // Config source file
hzString color ; // Color (hex string)
hzString typ ; // Basetype
uint32_t ln ; // Current line
uint32_t bErr = 0 ; // Error
hzEcode rc ; // Return code
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("xchartPie")) Fatal("File %s Line %d: Incorrect node (%s) supplied. Must be <xchartPie>\n", *pN->Filename(), pN->Line(), pN->txtName()) ;
thisVE = pPie = new hdsChartPie(this) ;
cfg = pN->Filename() ;
thisVE->m_Line = pN->Line() ;
thisVE->m_Indent = pN->Level() ;
// All <xchartPie> tags have attrs of id, type, header and footer and bgcolor
for (ai = pN ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("id")) pPie->m_Id = ai.Value() ;
else if (ai.NameEQ("font")) pPie->m_Font = ai.Value() ;
else if (ai.NameEQ("dtype")) typ = ai.Value() ;
else if (ai.NameEQ("header")) pPie->m_Header = ai.Value() ;
else if (ai.NameEQ("footer")) pPie->m_Footer = ai.Value() ;
else if (ai.NameEQ("bgcolor")) IsHexnum(pPie->m_BgColor, ai.Value()) ;
else if (ai.NameEQ("linecolor")) IsHexnum(pPie->m_ColorLine, ai.Value()) ;
else if (ai.NameEQ("fillcolor")) IsHexnum(pPie->m_ColorFill, ai.Value()) ;
else if (ai.NameEQ("rad")) pPie->m_Rad = ai.Value() ? atoi(ai.Value()) : -1 ;
else
{ bErr=1 ; m_pLog->Log("File %s Line %d: <xchartPie> Bad param (%s=%s)\n", *cfg, pN->Line(), ai.Name(), ai.Value()) ; }
}
// Check values
if (!pPie->m_Id) { bErr=1 ; m_pLog->Log("File %s Line %d: <xchart> Chart id not supplied\n", *cfg, pN->Line()) ; }
if (!pPie->m_Font) { bErr=1; m_pLog->Log("File %s Line %d: <params> No font\n", *cfg, ln) ; }
if (!pPie->m_Rad) { bErr=1; m_pLog->Log("File %s Line %d: <params> No radius\n", *cfg, ln) ; }
// Get other params. All types have headers and footer but different graphic and other configs
for (pN1 = pN->GetFirstChild() ; !bErr && pN1 ; pN1 = pN1->Sibling())
{
ln = pN1->Line() ;
if (pN1->NameEQ("text"))
{
rc = _readText(TX, pN1) ;
pPie->m_Texts.Add(TX) ;
}
else if (pN1->NameEQ("part"))
{
slice.header.Clear() ;
slice.color = 0 ;
slice.m_nValue = 0.0 ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("name")) slice.header = ai.Value() ;
else if (ai.NameEQ("color")) IsHexnum(slice.color, ai.Value()) ;
else if (ai.NameEQ("value")) slice.m_nValue = ai.Value() ? atof(ai.Value()) : 0 ;
else
{ bErr=1 ; m_pLog->Log("File %s Line %d: <part> Bad param (%s=%s)\n", *cfg, pN1->Line(), ai.Name(), ai.Value()) ; }
}
if (!slice.header) { bErr=1; m_pLog->Log("File %s Line %d: <part> No name\n", *cfg, ln) ; }
if (!slice.color) { bErr=1; m_pLog->Log("File %s Line %d: <part> Bad color\n", *cfg, ln) ; }
if (!slice.m_nValue) { bErr=1; m_pLog->Log("File %s Line %d: <part> Bad value\n", *cfg, ln) ; }
pPie->m_Total += slice.m_nValue ;
ar.Add(slice) ;
}
else
{ bErr=1 ; m_pLog->Log("File %s Line %d: <xchartPie> Bad subtag <%s>\n", *cfg, ln, pN1->txtName()) ; }
}
if (bErr)
{ delete pPie ; return 0 ; }
// Add the slices to the pie
pPie->m_pSlices = new hdsChartPie::_pie [ar.Count()] ;
for (pPie->m_nSlices = 0 ; pPie->m_nSlices < ar.Count() ; pPie->m_nSlices++)
{
pPie->m_pSlices[pPie->m_nSlices] = ar[pPie->m_nSlices] ;
}
// Calculate dimensions. The step size in pixels and numbers of steps specified for each axis, gives the height and width of the graph itself. To this the following entities
// are added:-
//
// - The graph header and footer (if any), will each add one line to the height
// - The v-axis heading adds 15 pixels to the height
// - The v-axis markers add to the width, but this depends on their maximum text size
// - The h-axis markers add 15 pixels to the height
// - The chart index (multiple data sets only), will either add one text line to the height or the width of the longest data set title
ln = pPie->m_Header ? 3 : 2 ;
pPie->m_ccY = (ln * 20) + pPie->m_Rad ;
pPie->m_ccX = 80 + pPie->m_Rad ;
ln += pPie->m_Footer ? 1 : 0 ;
pPie->m_Height = (ln * 20) + (pPie->m_Rad * 2) ;
pPie->m_Height += 100 ;
pPie->m_Width = pPie->m_ccX + pPie->m_Rad + 200 ;
pPie->m_IdxX = pPie->m_ccX - pPie->m_Rad ;
pPie->m_IdxY = pPie->m_ccY + pPie->m_Rad + 40 ;
m_pLog->Log("Declared xchartPie parmas\n") ;
return thisVE ;
}
hdsVE* hdsApp::_readChartBar (hzXmlNode* pN)
{
// Category: HtmlGeneration
//
// Read in parameters for displaying a standard (bar or line) chart (graph)
//
// Argument: pN The current XML node
//
// Returns: Pointer to the new standard chart as a visible entity
// NULL If the any configuration errors occur
_hzfunc("hdsApp::_readChartBar") ;
static hzString dfltFont = "10px Arial" ; // Default font settings
hdsChartBar* pChart ; // This chart
hdsChartBar::_rset* pSet ; // Dataset pointer
hdsText TX ; // Text item
chIter zi ; // For reading comma separated values
hzChain tmpChain ; // For processing explicit datasets
hzChain W ; // For processing dataset values
hzAttrset ai ; // Attribute iterator
//hzNumPair np ; // For graph values
hzXmlNode* pN1 ; // Subtag probe
hdsVE* thisVE ; // Visible entity/active entity
double stepSize ; // Axis step size
double val ; // For processing dataset values
hzString strStart ; // Axis start value
hzString strIndex ; // Index (either horizontal, vertical or not specified)
hzString tmp ; // Temp string (for comma separated values)
hzString cfg ; // Config source file
hzString strDatatype ; // V/H axis datatype
hzString min ; // V/H axis min value
hzString max ; // V/H axis max value
hzString mark ; // V/H axis line marker
hzString count ; // V/H axis value marker
uint32_t ln ; // Line in file
hdbBasetype eType ; // Basetype
hzEcode rc = E_OK ; // Return code
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("xchartBar")) Fatal("File %s Line %d: Incorrect node (%s) supplied. Must be <xchartBar>\n", *pN->Filename(), pN->Line(), pN->txtName()) ;
pSet = 0 ;
thisVE = pChart = new hdsChartBar(this) ;
cfg = pN->Filename() ;
thisVE->m_Line = ln = pN->Line() ;
thisVE->m_Indent = pN->Level() ;
// All <xchartStd> tags have attrs of id, type
for (ai = pN ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("id")) pChart->m_Id = ai.Value() ;
else if (ai.NameEQ("header")) pChart->m_Header = ai.Value() ;
else if (ai.NameEQ("footer")) pChart->m_Footer = ai.Value() ;
else if (ai.NameEQ("index")) strIndex = ai.Value() ;
else if (ai.NameEQ("bgcolor")) IsHexnum(pChart->m_BgColor, ai.Value()) ;
else if (ai.NameEQ("fgcolor")) IsHexnum(pChart->m_FgColor, ai.Value()) ;
else if (ai.NameEQ("linecolor")) IsHexnum(pChart->m_ColorLine, ai.Value()) ;
else if (ai.NameEQ("fillcolor")) IsHexnum(pChart->m_ColorFill, ai.Value()) ;
else if (ai.NameEQ("height")) pChart->m_Height = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("width")) pChart->m_Width = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("font")) pChart->m_Font = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartBar> Bad param (%s=%s)\n", *cfg, pN->Line(), ai.Name(), ai.Value()) ; }
}
if (!pChart->m_Id) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartBar> No canvas id supplied\n", *cfg, ln) ; }
if (!pChart->m_Height) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartBar> No canvas height\n", *cfg, ln) ; }
if (!pChart->m_Width) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartBar> No canvas width\n", *cfg, ln) ; }
if (!pChart->m_Font)
pChart->m_Font = dfltFont ;
m_pLog->Log("Declaring xchartBar (%s)\n", *pChart->m_Id) ;
// Get other params. All types have headers and footer but different graphic and other configs
for (pN1 = pN->GetFirstChild() ; rc == E_OK && pN1 ; pN1 = pN1->Sibling())
{
ln = pN1->Line() ;
if (pN1->NameEQ("axisY"))
{
strDatatype.Clear() ;
min.Clear() ;
max.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("header")) pChart->m_HdrY = ai.Value() ;
else if (ai.NameEQ("datatype")) strDatatype = ai.Value() ;
else if (ai.NameEQ("start")) strStart = ai.Value() ;
else if (ai.NameEQ("step")) stepSize = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("noSteps")) pChart->m_nSlotsY = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("slotPx")) pChart->m_nPxSlotY = ai.Value() ? atoi(ai.Value()) : 0 ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> Bad param (%s=%s)\n", *cfg, ln, ai.Name(), ai.Value()) ; }
}
if (!pChart->m_HdrY) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No x-axis header\n", *cfg, ln) ; }
if (!strDatatype) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No datatype set\n", *cfg, ln) ; }
if (!strStart) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No min value\n", *cfg, ln) ; }
if (!stepSize) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No max value\n", *cfg, ln) ; }
if (!pChart->m_nSlotsY) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No number of slots\n", *cfg, ln) ; }
if (!pChart->m_nPxSlotY) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No pixels per slot\n", *cfg, ln) ; }
if (rc != E_OK)
break ;
eType = Str2Basetype(strDatatype) ;
if (eType == BASETYPE_UNDEF)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> Illegal datatype (%s)\n", *cfg, ln, *strDatatype) ; break ; }
pChart->m_nMinY = atof(*strStart) ;
pChart->m_nMaxY = pChart->m_nMinY + (pChart->m_nSlotsY * stepSize) ;
m_pLog->Log("Declared xchartStd x-axis range of (%f - %f) over %u pixels with steps of %u\n",
*_fn, pChart->m_nMinY, pChart->m_nMaxY, pChart->m_nPxSlotY * pChart->m_nSlotsY, pChart->m_nPxSlotY) ;
}
else if (pN1->NameEQ("axisX"))
{
m_pLog->Log("Doing xchartStd y-axis\n") ;
strDatatype.Clear() ;
min.Clear() ;
max.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("header")) pChart->m_HdrX = ai.Value() ;
else if (ai.NameEQ("datatype")) strDatatype = ai.Value() ;
else if (ai.NameEQ("start")) strStart = ai.Value() ;
else if (ai.NameEQ("step")) stepSize = ai.Value() ? atof(ai.Value()) : 0 ;
else if (ai.NameEQ("noSteps")) pChart->m_nSlotsX = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("slotPx")) pChart->m_nPxSlotX = ai.Value() ? atoi(ai.Value()) : 0 ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> Bad param (%s=%s)\n", *cfg, ln, ai.Name(), ai.Value()) ; }
}
if (!pChart->m_HdrX) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No x-axis header\n", *cfg, ln) ; }
if (!strDatatype) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No datatype set\n", *cfg, ln) ; }
if (!strStart) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No min value\n", *cfg, ln) ; }
if (!stepSize) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No max value\n", *cfg, ln) ; }
if (!pChart->m_nSlotsX) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No number of slots\n", *cfg, ln) ; }
if (!pChart->m_nPxSlotX) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No pixels per slot\n", *cfg, ln) ; }
if (rc != E_OK)
break ;
eType = Str2Basetype(strDatatype) ;
if (eType == BASETYPE_UNDEF)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> Illegal datatype (%s)\n", *cfg, ln, *strDatatype) ; break ; }
pChart->m_nMinX = atof(*strStart) ;
pChart->m_nMaxX = pChart->m_nMinX + (pChart->m_nSlotsX * stepSize) ;
m_pLog->Log("Declared xchartStd axisX range of (%f - %f) over %u pixels with steps of %u\n",
*_fn, pChart->m_nMinX, pChart->m_nMaxX, pChart->m_nPxSlotX * pChart->m_nSlotsX, pChart->m_nPxSlotX) ;
for (val = pChart->m_nMinX ; val <= pChart->m_nMaxX ; val += stepSize)
pChart->m_hVals.Add(val) ;
}
else if (pN1->NameEQ("dataset"))
{
// The <dataset> tag names the dataset and sets a color for the trace. The values are provided as a simple list of x-values. There need not be any
// correlation between the number of y,x value pairs and the number of y-axis markers. There must be at least two y,x value pairs. The value pairs
// will be of the form {y,x}
m_pLog->Log("Doing <dataset>\n") ;
pSet = new hdsChartBar::_rset() ;
pChart->m_Sets.Add(pSet) ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("color")) IsHexnum(pSet->color, ai.Value()) ;
else if (ai.NameEQ("name")) pSet->header = ai.Value() ;
else if (ai.NameEQ("data")) tmpChain = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <dataset> Bad param (%s=%s)\n", *cfg, pN1->Line(), ai.Name(), ai.Value()) ; }
}
if (!tmpChain.Size())
tmpChain = pN1->m_fixContent ;
for (zi = tmpChain ; !zi.eof() ; zi++)
{
for (; !zi.eof() && *zi <= CHAR_SPACE ; zi++) ;
for (; !zi.eof() && (IsDigit(*zi) || *zi == '.') ; zi++)
W.AddByte(*zi) ;
tmp = W ;
W.Clear() ;
val = atof(*tmp) ;
pSet->m_vVals.Add(val) ;
if (zi.eof())
break ;
if (*zi == CHAR_COMMA)
continue ;
rc = E_SYNTAX ;
m_pLog->Log("File %s Line %d: <dataset> Expected a comma (got %c)\n", *cfg, ln, *zi) ;
break ;
}
m_pLog->Log("Done <dataset> have now %d values\n", pSet->m_vVals.Count()) ;
}
else
{
rc = E_SYNTAX ;
m_pLog->Log("File %s Line %d: <xchartStd> Bad subtag %s\n", *cfg, pN1->Line(), pN1->txtName()) ;
}
}
// Calculate dimensions. The step size in pixels and numbers of steps specified for each axis, gives the height and width of the graph itself. To this the following entities
// are added:-
//
// - The graph header and footer (if any), will each add one line to the height
// - The v-axis heading adds 15 pixels to the height
// - The v-axis markers add to the width, but this depends on their maximum text size
// - The h-axis markers add 15 pixels to the height
// - The chart index (multiple data sets only), will either add one text line to the height or the width of the longest data set title
if (rc != E_OK)
{ delete pChart ; return 0 ; }
ln = pChart->m_Header ? 3 : 2 ;
pChart->m_origY = (ln * 20) + ((pChart->m_nSlotsY+1) * pChart->m_nPxSlotY) ;
pChart->m_origX = 80 ;
ln += pChart->m_Footer ? 1 : 0 ;
pChart->m_Height = (ln * 20) + (pChart->m_nSlotsY * pChart->m_nPxSlotY) ;
pChart->m_Height += 100 ;
pChart->m_Width = pChart->m_nSlotsX * pChart->m_nPxSlotX ;
pChart->m_Width += 120 ;
m_pLog->Log("Declared xchart parmas\n") ;
return thisVE ;
}
hdsVE* hdsApp::_readChartStd (hzXmlNode* pN)
{
// Category: HtmlGeneration
//
// Read in parameters for displaying a standard (bar or line) chart (graph)
//
// Argument: pN The current XML node
//
// Returns: Pointer to the new standard chart as a visible entity
// NULL If the any configuration errors occur
_hzfunc("hdsApp::_readChartStd") ;
static hzString dfltFont = "10px Arial" ; // Default font settings
hdsChartStd* pChart ; // This chart
hdsChartStd::_rset* pSet ; // Dataset pointer
hdsText TX ; // Text item
chIter zi ; // For reading comma separated values
hzChain tmpChain ; // For processing explicit datasets
hzChain W ; // For processing dataset values
hzAttrset ai ; // Attribute iterator
hzXmlNode* pN1 ; // Subtag probe
hdsVE* thisVE ; // Visible entity/active entity
double stepSize ; // Axis step size
double val ; // Working value
hzString strStart ; // Axis start value
hzString strIndex ; // Index (either horizontal, vertical or not specified)
hzString tmp ; // Temp string (for comma separated values)
hzString cfg ; // Config source file
hzString strDatatype ; // V/H axis datatype
hzString min ; // V/H axis min value
hzString max ; // V/H axis max value
hzString mark ; // V/H axis line marker
hzString count ; // V/H axis value marker
uint32_t ln ; // Line in file
hdbBasetype eType ; // Basetype
hzEcode rc = E_OK ; // Return code
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("xchartStd")) Fatal("File %s Line %d: Incorrect node (%s) supplied. Must be <xchartStd>\n", *pN->Filename(), pN->Line(), pN->txtName()) ;
pSet = 0 ;
thisVE = pChart = new hdsChartStd(this) ;
cfg = pN->Filename() ;
thisVE->m_Line = ln = pN->Line() ;
thisVE->m_Indent = pN->Level() ;
// All <xchartStd> tags have attrs of id, type
for (ai = pN ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("id")) pChart->m_Id = ai.Value() ;
else if (ai.NameEQ("header")) pChart->m_Header = ai.Value() ;
else if (ai.NameEQ("footer")) pChart->m_Footer = ai.Value() ;
else if (ai.NameEQ("index")) strIndex = ai.Value() ;
else if (ai.NameEQ("bgcolor")) IsHexnum(pChart->m_BgColor, ai.Value()) ;
else if (ai.NameEQ("fgcolor")) IsHexnum(pChart->m_FgColor, ai.Value()) ;
else if (ai.NameEQ("linecolor")) IsHexnum(pChart->m_ColorLine, ai.Value()) ;
else if (ai.NameEQ("fillcolor")) IsHexnum(pChart->m_ColorFill, ai.Value()) ;
else if (ai.NameEQ("height")) pChart->m_Height = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("width")) pChart->m_Width = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("font")) pChart->m_Font = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartStd> Bad param (%s=%s)\n", *cfg, pN->Line(), ai.Name(), ai.Value()) ; }
}
if (!pChart->m_Id) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartStd> No canvas id supplied\n", *cfg, ln) ; }
if (!pChart->m_Height) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartStd> No canvas height\n", *cfg, ln) ; }
if (!pChart->m_Width) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xchartStd> No canvas width\n", *cfg, ln) ; }
if (!pChart->m_Font)
pChart->m_Font = dfltFont ;
m_pLog->Log("Declaring xchartStd (%s)\n", *pChart->m_Id) ;
// Get other params. All types have headers and footer but different graphic and other configs
for (pN1 = pN->GetFirstChild() ; rc == E_OK && pN1 ; pN1 = pN1->Sibling())
{
ln = pN1->Line() ;
if (pN1->NameEQ("axisY"))
{
// Vertical axis parameters
strDatatype.Clear() ;
min.Clear() ;
max.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("header")) pChart->m_HdrY = ai.Value() ;
else if (ai.NameEQ("datatype")) strDatatype = ai.Value() ;
else if (ai.NameEQ("start")) strStart = ai.Value() ;
else if (ai.NameEQ("step")) stepSize = ai.Value() ? atof(ai.Value()) : 0 ;
else if (ai.NameEQ("noSteps")) pChart->m_nSlotsY = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("slotPx")) pChart->m_nPxSlotY = ai.Value() ? atoi(ai.Value()) : 0 ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> Bad param (%s=%s)\n", *cfg, ln, ai.Name(), ai.Value()) ; }
}
if (!pChart->m_HdrY) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No header\n", *cfg, ln) ; }
if (!strDatatype) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No datatype set\n", *cfg, ln) ; }
if (!strStart) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No origin value\n", *cfg, ln) ; }
if (!stepSize) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No step size\n", *cfg, ln) ; }
if (!pChart->m_nSlotsY) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No number of slots\n", *cfg, ln) ; }
if (!pChart->m_nPxSlotY) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> No pixels per slot\n", *cfg, ln) ; }
if (rc != E_OK)
break ;
eType = Str2Basetype(strDatatype) ;
if (eType == BASETYPE_UNDEF)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisY> Illegal datatype (%s)\n", *cfg, ln, *strDatatype) ; break ; }
pChart->m_nMinY = atof(*strStart) ;
pChart->m_nMaxY = pChart->m_nMinY + (pChart->m_nSlotsY * stepSize) ;
m_pLog->Log("Declared xchartStd x-axis range of (%f - %f) over %u pixels with steps of %u\n",
*_fn, pChart->m_nMinY, pChart->m_nMaxY, pChart->m_nPxSlotY * pChart->m_nSlotsY, pChart->m_nPxSlotY) ;
}
else if (pN1->NameEQ("axisX"))
{
// Horizontal axis parameters
strDatatype.Clear() ;
min.Clear() ;
max.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("header")) pChart->m_HdrX = ai.Value() ;
else if (ai.NameEQ("datatype")) strDatatype = ai.Value() ;
else if (ai.NameEQ("start")) strStart = ai.Value() ;
else if (ai.NameEQ("step")) stepSize = ai.Value() ? atof(ai.Value()) : 0 ;
else if (ai.NameEQ("noSteps")) pChart->m_nSlotsX = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("slotPx")) pChart->m_nPxSlotX = ai.Value() ? atoi(ai.Value()) : 0 ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> Bad param (%s=%s)\n", *cfg, ln, ai.Name(), ai.Value()) ; }
}
if (!pChart->m_HdrX) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No header\n", *cfg, ln) ; }
if (!strDatatype) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No datatype set\n", *cfg, ln) ; }
if (!strStart) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No origin value\n", *cfg, ln) ; }
if (!stepSize) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No step size\n", *cfg, ln) ; }
if (!pChart->m_nSlotsX) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No number of slots\n", *cfg, ln) ; }
if (!pChart->m_nPxSlotX) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> No pixels per slot\n", *cfg, ln) ; }
if (rc != E_OK)
break ;
eType = Str2Basetype(strDatatype) ;
if (eType == BASETYPE_UNDEF)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <axisX> Illegal datatype (%s)\n", *cfg, ln, *strDatatype) ; break ; }
pChart->m_nMinX = atof(*strStart) ;
pChart->m_nMaxX = pChart->m_nMinX + (pChart->m_nSlotsX * stepSize) ;
m_pLog->Log("Declared xchartStd axisX range of (%f - %f) over %u pixels with steps of %u\n",
*_fn, pChart->m_nMinX, pChart->m_nMaxX, pChart->m_nPxSlotX * pChart->m_nSlotsX, pChart->m_nPxSlotX) ;
for (val = pChart->m_nMinX ; val <= pChart->m_nMaxX ; val += stepSize)
pChart->m_hVals.Add(val) ;
}
else if (pN1->NameEQ("dataset"))
{
// The <dataset> tag names the dataset and sets a color for the trace. The values are provided as a simple list of x-values. There need not be any
// correlation between the number of y,x value pairs and the number of y-axis markers. There must be at least two y,x value pairs. The value pairs
// will be of the form {y,x}
m_pLog->Log("Doing <dataset>\n") ;
pSet = new hdsChartStd::_rset() ;
pChart->m_Sets.Add(pSet) ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("color")) IsHexnum(pSet->color, ai.Value()) ;
else if (ai.NameEQ("name")) pSet->header = ai.Value() ;
else if (ai.NameEQ("data")) tmpChain = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <dataset> Bad param (%s=%s)\n", *cfg, pN1->Line(), ai.Name(), ai.Value()) ; }
}
if (!tmpChain.Size())
tmpChain = pN1->m_fixContent ;
for (zi = tmpChain ; !zi.eof() ; zi++)
{
for (; !zi.eof() && *zi <= CHAR_SPACE ; zi++) ;
for (; !zi.eof() && (IsDigit(*zi) || *zi == '.') ; zi++)
W.AddByte(*zi) ;
tmp = W ;
W.Clear() ;
val = atof(*tmp) ;
pSet->m_vVals.Add(val) ;
if (zi.eof())
break ;
if (*zi == CHAR_COMMA)
continue ;
rc = E_SYNTAX ;
m_pLog->Log("File %s Line %d: <dataset> Expected a comma (got %c)\n", *cfg, ln, *zi) ;
break ;
}
m_pLog->Log("Done <dataset> have now %d values\n", pSet->m_vVals.Count()) ;
}
else
{
rc = E_SYNTAX ;
m_pLog->Log("File %s Line %d: <xchartStd> Bad subtag %s\n", *cfg, pN1->Line(), pN1->txtName()) ;
}
}
if (rc != E_OK)
{ delete pChart ; return 0 ; }
// Calculate dimensions: The header adds 1 line, as does the footer, the Y-axis heading and the X-axis markers. If an index applies and is horizonal, yet another line will be
// needed.
ln = pChart->m_Header ? 3 : 2 ;
pChart->m_origY = (ln * 20) + ((pChart->m_nSlotsY+1) * pChart->m_nPxSlotY) ;
pChart->m_origX = 80 ;
ln += pChart->m_Footer ? 1 : 0 ;
pChart->m_Height = (ln * 20) + (pChart->m_nSlotsY * pChart->m_nPxSlotY) ;
pChart->m_Height += 100 ;
pChart->m_Width = pChart->m_nPxSlotX * (pChart->m_nSlotsX + 1) ;
pChart->m_Width += 120 ;
m_pLog->Log("Declared xchart parmas\n") ;
return thisVE ;
}
#if 0
hzEcode hdsApp::_readShapes (hzXmlNode* pN, hdsDiagram* pDiag)
{
// Read in a single diagram component
//
// Arguments: 1) pN The current XML node expected to be a <shapes> tag
// 2) pDiag The current diagram
//
// Returns: E_SYNTAX If there is a syntax error
// E_OK If no errors occured
_hzfunc("hdsApp::_readShape") ;
hzMapS<hzString,hdsGraphic*> tmp ; // Map of graphics (for connectors)
hdsGraphic* pShape = 0 ; // Diagram component (new)
hdsGraphic* pShape_x = 0 ; // Diagram component (previously existing)
hzAttrset ai ; // Attribute iterator
hzXmlNode* pN1 ; // Subtag probe
hzString cfg ; // Config source file
hzString name ; // Node name
hzString from ; // For connector from (shape id)
hzString to ; // For connector to (shape id)
uint32_t dfltFillColor = -1 ; // Default fill color
uint32_t ln ; // Line number
uint32_t flag ; // Flags to control which attributes apply to which shape types
uint32_t maxH = 0 ; // Highest y-coord
uint32_t maxV = 0 ; // Highest x-coord
//uint32_t bErr = 0 ; // Error if set
hzEcode rc = E_OK ; // Return code
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("shapes")) Fatal("Incorrect node (%s) supplied. Must be <shapes>\n", pN->txtName()) ;
if (!pDiag) Fatal("No diagram supplied\n") ;
cfg = pN->Filename() ;
// Process params here
for (ai = pN ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("fillcolor"))
IsHexnum(dfltFillColor, ai.Value()) ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> Bad param (%s=%s)\n", *cfg, ln, pN->txtName(), ai.Name(), ai.Value()) ; }
}
// Process child nodes (shapes)
for (pN1 = pN->GetFirstChild() ; rc == E_OK && pN1 ; pN1 = pN1->Sibling())
{
ln = pN1->Line() ;
name = pN1->txtName() ;
pShape = new hdsGraphic() ;
pDiag->m_Shapes.Add(pShape) ;
if (name == "line") { flag = 0x04FF ; pShape->m_eShape = HDSGRAPH_LINE ; }
else if (name == "diamond") { flag = 0x04FF ; pShape->m_eShape = HDSGRAPH_DIAMOND ; }
else if (name == "arrow") { flag = 0x0CFF ; pShape->m_eShape = HDSGRAPH_ARROW ; }
else if (name == "rect") { flag = 0x04FF ; pShape->m_eShape = HDSGRAPH_RECT ; }
else if (name == "rrect") { flag = 0x04FF ; pShape->m_eShape = HDSGRAPH_RRECT ; }
else if (name == "stadium") { flag = 0x04FF ; pShape->m_eShape = HDSGRAPH_STADIUM ; }
else if (name == "circle") { flag = 0x01FF ; pShape->m_eShape = HDSGRAPH_CIRCLE ; }
else if (name == "gateAND") { flag = 0x04F1 ; pShape->m_eShape = HDSGRAPH_LGATE_AND ; }
else if (name == "gateOR") { flag = 0x04F1 ; pShape->m_eShape = HDSGRAPH_LGATE_OR ; }
else if (name == "gateNOT") { flag = 0x04F1 ; pShape->m_eShape = HDSGRAPH_LGATE_NOT ; }
else if (name == "gateNAND") { flag = 0x04F1 ; pShape->m_eShape = HDSGRAPH_LGATE_NAND ; }
else if (name == "gateNOR") { flag = 0x04FD ; pShape->m_eShape = HDSGRAPH_LGATE_NOR ; }
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xdiagram> Bad subtag <%s>\n", *cfg, ln, *name) ; }
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (flag & 0x0001 && ai.NameEQ("id")) pShape->m_Uid = ai.Value() ;
else if (flag & 0x0002 && ai.NameEQ("text")) pShape->m_Text = ai.Value() ;
//else if (flag & 0x0004 && ai.NameEQ("txH")) pShape->text_y = ai.Value() ? atoi(ai.Value()) : -1 ;
//else if (flag & 0x0008 && ai.NameEQ("txV")) pShape->text_x = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x0010 && ai.NameEQ("top")) pShape->m_Rect.m_Top = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x0020 && ai.NameEQ("bot")) pShape->m_Rect.m_Bot = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x0040 && ai.NameEQ("lft")) pShape->m_Rect.m_Lft = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x0080 && ai.NameEQ("rht")) pShape->m_Rect.m_Rht = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x0100 && ai.NameEQ("rad")) pShape->m_Rad = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x0200 && ai.NameEQ("width")) pShape->m_Width = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x0400 && ai.NameEQ("thick")) pShape->m_Thick = ai.Value() ? atoi(ai.Value()) : -1 ;
//else if (flag & 0x0800 && ai.NameEQ("head")) pShape->head = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (flag & 0x1000 && ai.NameEQ("from")) from = ai.Value() ;
else if (flag & 0x2000 && ai.NameEQ("to")) to = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> Bad param (%s=%s)\n", *cfg, ln, *name, ai.Name(), ai.Value()) ; }
}
if (pShape->m_eShape != HDSGRAPH_LINE && !pShape->m_Uid)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> No shape id supplied\n", *cfg, ln, *name) ; }
else
{
if (tmp.Exists(pShape->m_Uid))
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> Duplicate shape id (%s)\n", *cfg, ln, *name, *pShape->m_Uid) ; }
else
tmp.Insert(pShape->m_Uid, pShape) ;
}
/*
if (pShape->type == HDSGRAPH_CONNECT)
{
if (!from)
{ rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> No from shape id supplied\n", *cfg, ln, *name) ; }
if (!to)
{ rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> No from shape id supplied\n", *cfg, ln, *name) ; }
pShape_x = tmp[pShape->uid] ;
if (!pShape_x)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> Cannot locate shape %s\n", *cfg, ln, *name, *pShape->uid) ; }
else
pShape->from = pShape_x->nid ;
pShape_x = tmp[pShape->uid] ;
if (!pShape_x)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> Cannot locate shape %s\n", *cfg, ln, *name, *pShape->uid) ; }
else
pShape->to = pShape_x->nid ;
from = (char*) 0 ;
to = (char*) 0 ;
continue ;
}
*/
if (pShape->m_Rect.m_Lft == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> No Lft coord supplied\n", *cfg, ln, *name, *pShape->m_Uid) ; }
if (pShape->m_Rect.m_Top == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> No Top coord supplied\n", *cfg, ln, *name, *pShape->m_Uid) ; }
if (pShape->m_Rect.m_Rht == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> No Rht coord supplied\n", *cfg, ln, *name, *pShape->m_Uid) ; }
if (pShape->m_Rect.m_Bot == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> No Bot coord supplied\n", *cfg, ln, *name, *pShape->m_Uid) ; }
if (pShape->m_Rect.m_Top > pShape->m_Rect.m_Bot)
{ rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> Top/Bot inversion\n", *cfg, ln, *name, *pShape->m_Uid) ; }
if (pShape->m_Rect.m_Lft > pShape->m_Rect.m_Rht)
{ rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%s> Lft/Rht reversal\n", *cfg, ln, *name, *pShape->m_Uid) ; }
if (rc != E_OK)
break ;
// Set canvas limits
if (pShape->m_Rect.m_Bot > maxV) maxV = pShape->m_Rect.m_Bot ;
if (pShape->m_Rect.m_Rht > maxH) maxH = pShape->m_Rect.m_Rht ;
// Set width and height
pShape->m_Width = pShape->m_Rect.m_Rht - pShape->m_Rect.m_Lft ;
pShape->m_Height = pShape->m_Rect.m_Bot - pShape->m_Rect.m_Top ;
// Set connection points
//pShape->northH = pShape->coords.m_Lft + (pShape->width/2) ;
//pShape->northV = pShape->coords.m_Top ;
//pShape->eastH = pShape->coords.m_Rht ;
//pShape->eastV = pShape->coords.m_Top + (pShape->height/2) ;
//pShape->southH = pShape->coords.m_Lft + (pShape->width/2) ;
//pShape->southV = pShape->coords.m_Bot ;
//pShape->westH = pShape->coords.m_Lft ;
//pShape->westV = pShape->coords.m_Top + (pShape->height/2) ;
if (pShape->m_eShape == HDSGRAPH_LINE)
{
// By convention, lines are drawn from y1/x1 (top left) to y2/x2 (bottom right)
}
if (pShape->m_eShape == HDSGRAPH_STADIUM)
{
pShape->m_Rad = (pShape->m_Rect.m_Bot - pShape->m_Rect.m_Top)/2 ;
//pShape->text_y = pShape->coords.m_Lft + pShape->rad ;
//pShape->text_x = pShape->coords.m_Top + pShape->rad ;
}
if (pShape->m_ColorFill == 0)
{
if (dfltFillColor == 0)
pShape->m_ColorFill = 0 ;
else
pShape->m_ColorFill = dfltFillColor ;
}
}
return rc ;
}
#endif
hdsVE* hdsApp::_readDiagram (hzXmlNode* pN)
{
// Read in diagram components.
//
// <xdiagram id="mydiagram" header="flowchart">
// <stadium top="20" bot="70" lft="50" rht="150" text="Start"/>
// <arrow from="70,100" to"120,100" width="2" head="10" color="0"/>
// <diamond top="120" bot="220" lft="50" rht="150" text="Are we ready?"/>
// <arrow from="170,150" to"70,100" width="2" head="10" color="0" text="N"/>
// <arrow from="220,100" to"270,100" width="2" head="10" color="0" text="Y"/>
// <stadium top="270" bot="320" lft="50" rht="150" text="Go!"/>
// </xdiagram>
//
// Argument: pN The current XML node expected to be a <xdiagram> tag
//
// Returns: Pointer to diagram visual entity
_hzfunc("hdsApp::_readDiagram") ;
hzSet<uint16_t> tmp ; // Set of graphic object ids already found
hdsText TX ; // Diagram Text component
hdsLine LN ; // Diagram Line component
hdsGraphic gObj ; // Current grapgic object
//hdsConnector con ; // Current connector
hzAttrset ai ; // Attribute iterator
hzXmlNode* pN1 ; // Subtag probe
hdsDiagram* pDiag ; // This diagram
hdsVE* thisVE ; // Visible entity/active entity
hzString cfg ; // Config source file
hzString strXid ; // Text representation of graphic object id
uint32_t ln ; // Line number
uint32_t maxH = 0 ; // Highest y-coord
uint32_t maxV = 0 ; // Highest x-coord
hzEcode rc = E_OK ; // Return code
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("xdiagram")) Fatal("File %s Line %d: Incorrect node (%s) supplied. Must be <xdiagram>\n", *pN->Filename(), pN->Line(), pN->txtName()) ;
thisVE = pDiag = new hdsDiagram(this) ;
cfg = pN->Filename() ;
thisVE->m_Line = ln = pN->Line() ;
thisVE->m_Indent = pN->Level() ;
// Get diagram params
for (ai = pN ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("id")) pDiag->m_Id = ai.Value() ;
else if (ai.NameEQ("bgcolor")) IsHexnum(pDiag->m_BgColor, ai.Value()) ;
else if (ai.NameEQ("fgcolor")) IsHexnum(pDiag->m_FgColor, ai.Value()) ;
else if (ai.NameEQ("linecolor")) IsHexnum(pDiag->m_ColorLine, ai.Value()) ;
else if (ai.NameEQ("fillcolor")) IsHexnum(pDiag->m_ColorFill, ai.Value()) ;
else if (ai.NameEQ("height")) pDiag->m_Height = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("width")) pDiag->m_Width = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("font")) pDiag->m_Font = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xdiagram> Bad param (%s=%s)\n", *cfg, ln, ai.Name(), ai.Value()) ; }
}
// Check values
if (!pDiag->m_Id) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xdiagram> No canvas id\n", *cfg, ln) ; }
if (!pDiag->m_Height) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xdiagram> No canvas height\n", *cfg, ln) ; }
if (!pDiag->m_Width) { rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xdiagram> No canvas width\n", *cfg, ln) ; }
if (rc != E_OK)
{ delete pDiag ; return 0 ; }
// Get components
for (pN1 = pN->GetFirstChild() ; rc == E_OK && pN1 ; pN1 = pN1->Sibling())
{
ln = pN1->Line() ;
if (pN1->NameEQ("line"))
{
LN.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("color")) IsHexnum(LN.m_Color, ai.Value()) ;
else if (ai.NameEQ("y1")) LN.m_startH = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("x1")) LN.m_startV = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("y2")) LN.m_finalH = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("x2")) LN.m_finalV = ai.Value() ? atoi(ai.Value()) : -1 ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <line> Bad param (%s=%s)\n", *cfg, ln, ai.Name(), ai.Value()) ; }
}
pDiag->m_Lines.Add(LN) ;
continue ;
}
/*
if (pN1->NameEQ("connector"))
{
rc = _readConnector(con, pN1) ;
if (rc == E_OK)
pDiag->m_Connectors.Add(con) ;
continue ;
}
*/
if (pN1->NameEQ("text"))
{
rc = _readText(TX, pN1) ;
if (rc == E_OK)
pDiag->m_Texts.Add(TX) ;
continue ;
}
if (pN1->NameEQ("diamond")) gObj.m_eShape = HDSGRAPH_DIAMOND ;
else if (pN1->NameEQ("arrow")) gObj.m_eShape = HDSGRAPH_ARROW ;
else if (pN1->NameEQ("rect")) gObj.m_eShape = HDSGRAPH_RECT ;
else if (pN1->NameEQ("rrect")) gObj.m_eShape = HDSGRAPH_RRECT ;
else if (pN1->NameEQ("stadium")) gObj.m_eShape = HDSGRAPH_STADIUM ;
else if (pN1->NameEQ("circle")) gObj.m_eShape = HDSGRAPH_CIRCLE ;
else if (pN1->NameEQ("gateAND")) gObj.m_eShape = HDSGRAPH_LGATE_AND ;
else if (pN1->NameEQ("gateOR")) gObj.m_eShape = HDSGRAPH_LGATE_OR ;
else if (pN1->NameEQ("gateNOT")) gObj.m_eShape = HDSGRAPH_LGATE_NOT ;
else if (pN1->NameEQ("gateNAND")) gObj.m_eShape = HDSGRAPH_LGATE_NAND ;
else if (pN1->NameEQ("gateNOR")) gObj.m_eShape = HDSGRAPH_LGATE_NOR ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xdiagram> Bad subtag <%s>\n", *pN1->Filename(), pN1->Line(), pN1->txtName()) ; }
strXid.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("id")) strXid = ai.Value() ;
else if (ai.NameEQ("text")) gObj.m_Text = ai.Value() ;
else if (ai.NameEQ("font")) gObj.m_Font = ai.Value() ;
else if (ai.NameEQ("top")) gObj.m_Top = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("bot")) gObj.m_Bot = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("lft")) gObj.m_Lft = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("rht")) gObj.m_Rht = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("rad")) gObj.m_Rad = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("width")) gObj.m_Width = ai.Value() ? atoi(ai.Value()) : -1 ;
else if (ai.NameEQ("thick")) gObj.m_Thick = ai.Value() ? atoi(ai.Value()) : -1 ;
//else if (ai.NameEQ("from")) from = ai.Value() ;
//else if (ai.NameEQ("to")) to = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> Bad param (%s=%s)\n", *pN1->Filename(), pN1->Line(), pN1->txtName(), ai.Name(), ai.Value()) ; }
}
// Check graphic object id is set and is not a duplicate
if (!strXid)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> No shape id supplied\n", *pN1->Filename(), pN1->Line(), pN1->txtName()) ; }
else
{
gObj.m_Id = atoi(*strXid) ;
if (tmp.Exists(gObj.m_Id))
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <%s> Duplicate shape id (%u)\n", *cfg, ln, pN1->txtName(), gObj.m_Id) ; }
else
tmp.Insert(gObj.m_Id) ;
}
// Check other parameters
if (gObj.m_Lft == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%u> No Lft coord supplied\n", *cfg, ln, pN1->txtName(), gObj.m_Id) ; }
if (gObj.m_Top == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%u> No Top coord supplied\n", *cfg, ln, pN1->txtName(), gObj.m_Id) ; }
if (gObj.m_Rht == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%u> No Rht coord supplied\n", *cfg, ln, pN1->txtName(), gObj.m_Id) ; }
if (gObj.m_Bot == -1) { rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%u> No Bot coord supplied\n", *cfg, ln, pN1->txtName(), gObj.m_Id) ; }
if (gObj.m_Top > gObj.m_Bot)
{ rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%u> Top/Bot inversion\n", *cfg, ln, pN1->txtName(), gObj.m_Id) ; }
if (gObj.m_Lft > gObj.m_Rht)
{ rc = E_SYNTAX; m_pLog->Log("File %s Line %d: <%u> Lft/Rht reversal\n", *cfg, ln, pN1->txtName(), gObj.m_Id) ; }
if (rc != E_OK)
break ;
// Set canvas limits
if (gObj.m_Bot > maxV) maxV = gObj.m_Bot ;
if (gObj.m_Rht > maxH) maxH = gObj.m_Rht ;
// Set width and height
gObj.m_Width = gObj.m_Rht - gObj.m_Lft ;
gObj.m_Height = gObj.m_Bot - gObj.m_Top ;
// Add text
if (gObj.m_Text)
{
//TX.Init(Text, Font, color, posH, posV, align) ;
TX.Init(gObj.m_Text, gObj.m_Font, 0, gObj.m_Lft, gObj.m_Top, 1) ;
pDiag->m_Texts.Add(TX) ;
}
// Set connection points
if (gObj.m_eShape == HDSGRAPH_STADIUM)
{
gObj.m_Rad = (gObj.m_Bot - gObj.m_Top)/2 ;
}
if (gObj.m_ColorFill == 0)
gObj.m_ColorFill = pDiag->m_ColorFill ;
pDiag->m_Shapes.Add(gObj) ;
//if (pN1->NameEQ("shapes")) rc = _readShapes(pN1, pDiag) ;
}
// Calculate dimensions
if (rc != E_OK)
{ delete pDiag ; return 0 ; }
m_pLog->Log("Declared xchart parmas\n") ;
return thisVE ;
}
/*
** Graphic Spatial Calculations
*/
class hzPauli
{
// Represents a diagram component which in all cases, is deemed to occupy a rectangle with vertical and horizontal sides, within the diagram space.
//
// The basic idea is that diagram components (graphics and lines), should never intersect. In order to determine if a proposed rectangle would intersect with an existing one,
// the rectangles (hzPauli instances) are held in a map. This requires hzPauli to support the less than and greater than operators. For this purpose the horizontal X-axis is
// considered 'superior' to the vertical Y-axis.
public:
uint32_t m_X ;
uint32_t m_Y ;
hzPauli (void) { m_X = m_Y = 0 ; }
bool operator< (const hzPauli& op)
{
if (m_X > op.m_X) return false ;
if (m_X < op.m_X) return true ;
return (m_Y < op.m_Y) ;
}
bool operator> (const hzPauli& op)
{
if (m_X < op.m_X) return false ;
if (m_X > op.m_X) return true ;
return (m_Y > op.m_Y) ;
}
} ;
class hzSpace2D
{
hzSet<hzPauli> m_Shapes ; // Diagram components
} ;
/*
** Flowcharts
*/
enum _fc_type
{
// Statement type
STMT_NULL, // No command
// Applicable at the file level
STMT_USING, // Tells lookup functions to add a namespace to the search
STMT_NAMESPACE, // Declares a namespace and makes it current (all things from now belong to it)
STMT_TYPEDEF, // Typedef (typedef existing_type new_type)
STMT_CLASS_DCL, // Class/Forward declaration of a class or struct
STMT_CLASS_DEF, // Class definition
STMT_CTMPL_DEF, // Class template definition
STMT_UNION_DCL, // Forward declaration of a union
STMT_UNION_DEF, // Union definition
STMT_ENUM_DCL, // Forward declaration of an enum
STMT_ENUM_DEF, // Enum definition
STMT_FUNC_DCL, // Function prototype
STMT_FUNC_DEF, // Function definition
STMT_FTMPL_DEF, // Function template definition
// Variable declarations
STMT_VARDCL_FNPTR, // Function ptr declaration: Of the form [keywords] typlex (*var_name)(type_args) ;
STMT_VARDCL_FNASS, // Function ptr declation and assignement: Of the form [keywords] typlex (*var_name)(type_args) = function_addr ;
STMT_VARDCL_STD, // Variable declarations: Of the form [keywords] typlex var_name ;
STMT_VARDCL_ASSIG, // Variable declaration and assignment: Of the form [keywords] typlex var_name = exp ;
STMT_VARDCL_ARRAY, // Variable array declarations: Of the form [keywords] typlex var_name[noElements] ;
STMT_VARDCL_ARASS, // Variable array declaration and assignment: Of the form [keywords] typlex var_name[noElements] = { values } ;
STMT_VARDCL_CONS, // Variable declarations: Of the form [keywords] class/typlex var_name(value_args) ;
// Branches
STMT_BRANCH_IF, // If: Of the form if (condition) then statement/statement block
STMT_BRANCH_ELSE, // Else: An 'else' then statement/statement block. The 'else' must follow an if or an else
STMT_BRANCH_ELSEIF, // Else: An 'else' then statement/statement block. The 'else' must follow an if or an else
STMT_BRANCH_FOR, // For: Of the form for (statements ; condition ; statements) followed by statement/statement block
STMT_BRANCH_DOWHILE, // Do while: Of the form do statement block while (condition)
STMT_BRANCH_WHILE, // While: Of the form while (condition) then statement/statement block
STMT_BRANCH_SWITCH, // Switches: Of the form switch (value) then block of switch cases
STMT_BRANCH_CASE, // Switch cases: Of the form case value: then series of statements
STMT_BRANCH_DEFAULT, // Switch defaul case: Of the form case value: then series of statements
// Operations
STMT_BLOC, // Start of a block of non-branching operations
STMT_STEP, // A single non-branching operation
STMT_VAR_INCA, // Variable increments: Of the form var++
STMT_VAR_INCB, // Variable increments: Of the form ++var
STMT_VAR_DECA, // Variable decrements: Of the form var--
STMT_VAR_DECB, // Variable decrements: Of the form --var
STMT_VAR_ASSIGN, // Variable assignments: Of the form var = evalutable expression
STMT_VAR_MATH, // Variable operations: Of the form var +=, -=, *= or /= etc followed by an evalutable expresion
STMT_FUNC_CALL, // Function calls: Of the form funcname(args)
STMT_DELETE, // Delete variable: Of the form delete varname ;
// Jumps and exits
STMT_CONTINUE, // To top of for/while loop
STMT_BREAK, // Break out of loop or case block
STMT_LABEL, // Goto
STMT_GOTO, // Label
STMT_RETURN, // Return from function
STMT_EXIT // Terminate execution
} ;
class _fc_item
{
// The _fc_item struct is a temporary support class for the _readFlowItem function. The purpose is to give hdsGraphic extra members during the parsing of flowchart XML, needed
// to assign links between the flowchart items (graphics).
//
// Flowchart items are either:-
//
// START Stadia No incoming connector; One outgoing connector from the South point. The START item is singular.
// RETURN Stadia One incoming connector, No outgoing connectors. Must be at least 1 RETURN, can be more.
// LABEL Stadia One or more incoming connectors (all to the same, Northmost point)
// TEST Hexagon One or more incoming connectors (all to the same, Northmost point), Two outgoing connectors: True; False.
// ACTION Rectangle One or more incoming connectors (all to the same, Northmost point); One outgoing connector, either from the South or East point.
//
// Notes:-
// Hexagons are used as they are more space efficient than diamonds, actions or labels.
// Actions can be a single step or a contiguous set of steps.
// Connectors are not specified in the <xFlowchart> tag, but are inferred from the list of flowchart items and generated automatically.
public:
hzString m_Text ; // Compiled text content
hdsShape m_eShape ; // Either a stadium, rectangle or hexagon
_fc_type m_eSType ; // Type of instruction
uint16_t m_nLevel ; // Code level
uint16_t m_Row ; // Incremented on next step
uint16_t m_Col ; // Incremented on branch
uint16_t m_nLines ; // Number of lines in text
uint16_t m_actH ; // Actual height based on shape and text
uint16_t m_actW ; // Actual width based on shape and text
uint16_t m_effH ; // Effective height based all shapes in a linked row
uint16_t m_Top ; // Actual top of image space
uint16_t m_Bot ; // Actual bottom of image space
uint16_t m_Lft ; // Actual left of image space
uint16_t m_Rht ; // Actual right of image space
uint16_t m_effW ; // Effective width based all shapes in a linked column
uint16_t m_Xid ; // Statement id
uint16_t m_LinkCtrl ; // Start of current control loop if applicable
//uint16_t m_LinkSwitch ; // Start of current switch if applicable
uint16_t m_LinkDown ; // Next item in line of execution
uint16_t m_LinkTrue ; // First item of execution branch (on conditiion=true)
uint16_t m_Resv ; // Reserved
_fc_item (void)
{
//m_LinkCtrl = m_LinkSwitch = m_LinkDown = m_LinkTrue = 0 ;
m_LinkCtrl = m_LinkDown = m_LinkTrue = 0 ;
m_eShape = HDSGRAPH_NULL ;
m_eSType = STMT_NULL ;
m_Xid = m_Row = m_Col = m_nLevel = m_actH = m_actW = m_effH = m_effW = m_Resv = m_Top = m_Bot = m_Lft = m_Rht = 0 ;
}
} ;
struct _fc_arg
{
// Container for graphic array, connector array and log channel
hzMapS <uint32_t,_fc_item*> m_mapItems ; // Map of flowchart items ordered by the IDs of the XML nodes that create them.
hzSet <hzString> m_setLabels ; // Set of labels found
hzArray <_fc_item*> m_arrItems ; // All flowchart statements (by order of incidence)
hzLogger* pLog ; // Logger
const char* file ; // Filename
bool m_bErr ; // Error state
_fc_arg ()
{
pLog = 0 ;
file = 0 ;
m_bErr = false ;
}
~_fc_arg ()
{
uint32_t n ;
threadLog("Clearing base\n") ;
for (n = 0 ; n < m_arrItems.Count() ; n++)
{
delete m_arrItems[n] ;
}
threadLog("based cleared\n") ;
}
} ;
static hzEcode _createFCItems (_fc_arg& a, hzXmlNode* pN)
{
// Start with the <xflowchart> tag and move through all subnodes. There are two passes, in the first pass the objectives are:-
//
// 1) Check that tags are legal, but not if they are in the correct context. For example, elseif is legal, but only as part of an if/elseif/else series.
// 2) Create an item for every subnode that necessitate a flowchart shape.
//
// In the second pass the objectives are:-
//
// 1) Check that labels exist for goto commands
// 2) Check that any elseif or else statements are part of an if/elseif/else series
// 3) Check that case statements are part of a switch statement and that no case statements follow a default
// 4) ...
//
// Arguments: 1) a Temporary flowchart container
// 2) pN Initial XML node
//
// Returns: E_FORMAT Error in syntax
// E_OK Operation successful
_hzfunc(__func__) ;
const hzDocXml* pHostDoc ; // Host document
hzXmlNode* pN1 ; // Current XML node
hzXmlNode* pN2 ; // Secondary XML node
hzAttrset ai ; // Attribute iterator
_fc_item* pItem ; // Temporary flowchart item
hzChain T ; // For text aggregation
hzString st ; // Statement object
hzString obj ; // Statement object
hzString typ ; // Statement type
uint32_t nId ; // Start node id
uint16_t nLevel ; // Level of <xflowchart> tag in document. All subnodes of <xflowchart> must be above this level
hzEcode rc = E_OK ; // Return code
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("xflowchart")) Fatal("File %s Line %d: Incorrect node (%s) supplied. Must be <xflowchart>\n", *pN->Filename(), pN->Line(), pN->txtName()) ;
pHostDoc = pN->GetHostDoc() ;
nId = pN->GetUid() ;
nLevel = pN->Level() ;
for (nId++, pN1 = pHostDoc->GetNode(nId) ; rc == E_OK && pN1 ; nId++, pN1 = pHostDoc->GetNode(nId))
{
if (pN1->Level() <= nLevel)
break ;
// <break>, <continue>, <else> and <default> don't produce items
if (pN1->NameEQ("break")) continue ;
if (pN1->NameEQ("continue")) continue ;
if (pN1->NameEQ("else")) continue ;
if (pN1->NameEQ("default")) continue ;
// All the below tags do produce an item
if (!a.m_mapItems.Exists(pN1->GetUid()))
{
pItem = new _fc_item() ;
pItem->m_nLevel = pN1->Level() - nLevel ;
a.m_mapItems.Insert(pN1->GetUid(), pItem) ;
a.pLog->Log("Inserted item: level %d uid %u line %d <%s>\n", pItem->m_nLevel, pN1->GetUid(), pN1->Line(), pN1->txtName()) ;
}
// Get the tag attributes (if any)
st.Clear() ;
typ.Clear() ;
obj.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("stmt")) st = ai.Value() ;
else if (ai.NameEQ("type")) typ = ai.Value() ;
else if (ai.NameEQ("obj")) obj = ai.Value() ;
else
{ rc = E_FORMAT ; a.pLog->Log("File %s Line %d: <%s> Bad param (%s=%s)\n", a.file, pN1->Line(), pN1->txtName(), ai.Name(), ai.Value()) ; }
}
if (rc != E_OK)
break ;
if (pN1->NameEQ("label")) { pItem->m_eSType = STMT_LABEL ; pItem->m_eShape = HDSGRAPH_STADIUM ; pItem->m_Text = obj ; a.m_setLabels.Insert(obj) ; continue ; }
if (pN1->NameEQ("goto")) { pItem->m_eSType = STMT_GOTO ; pItem->m_eShape = HDSGRAPH_STADIUM ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("return")) { pItem->m_eSType = STMT_RETURN ; pItem->m_eShape = HDSGRAPH_STADIUM ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("exit")) { pItem->m_eSType = STMT_EXIT ; pItem->m_eShape = HDSGRAPH_STADIUM ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("bloc"))
{
// Read in an action block - a set of one or more statements that are merged into a single action box rectangle. <bloc> can only have subtags of <step>.
pN2 = pN1->GetFirstChild() ;
if (!pN2)
{
// Dead block so ignore and continue
continue ;
}
pItem->m_eSType = STMT_BLOC ;
pItem->m_eShape = HDSGRAPH_RECT ;
// Recurse to handle CASE statements
T.Clear() ;
for (; !a.m_bErr && pN2 ; pN2 = pN2->Sibling())
{
if (!pN2->NameEQ("step"))
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <bloc> Illegal subtag <%s>, only <step> allowed\n", a.file, pN2->Line(), pN2->txtName()) ; break ; }
obj.Clear() ;
for (ai = pN2 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("stmt")) st = ai.Value() ;
else if (ai.NameEQ("type")) typ = ai.Value() ;
else if (ai.NameEQ("obj")) obj = ai.Value() ;
else
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <step> Bad param (%s=%s)\n", a.file, pN2->Line(), ai.Name(), ai.Value()) ; }
}
if (a.m_bErr)
break ;
if (!obj)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <step> No object\n", a.file, pN2->Line()) ; break ; }
if (T.Size())
T.AddByte(CHAR_NL) ;
T << obj ;
}
continue ;
}
if (pN1->NameEQ("step")) { pItem->m_eSType = STMT_STEP ; pItem->m_eShape = HDSGRAPH_RECT ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("switch")) { pItem->m_eSType = STMT_BRANCH_SWITCH ; pItem->m_eShape = HDSGRAPH_RECT ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("case")) { pItem->m_eSType = STMT_BRANCH_CASE ; pItem->m_eShape = HDSGRAPH_HEXAGON ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("if")) { pItem->m_eSType = STMT_BRANCH_IF ; pItem->m_eShape = HDSGRAPH_HEXAGON ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("elseif")) { pItem->m_eSType = STMT_BRANCH_ELSEIF ; pItem->m_eShape = HDSGRAPH_HEXAGON ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("for")) { pItem->m_eSType = STMT_BRANCH_FOR ; pItem->m_eShape = HDSGRAPH_HEXAGON ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("while")) { pItem->m_eSType = STMT_BRANCH_WHILE ; pItem->m_eShape = HDSGRAPH_HEXAGON ; pItem->m_Text = obj ; continue ; }
if (pN1->NameEQ("dowhile")) { pItem->m_eSType = STMT_BRANCH_DOWHILE ; pItem->m_eShape = HDSGRAPH_HEXAGON ; pItem->m_Text = obj ; continue ; }
else
{
// Illegal tag or tag position
rc = E_FORMAT ;
a.pLog->Log("Illegal tag\n") ;
break ;
}
// Place item(s) in m_arrItems
}
return rc ;
}
static _fc_item* _readFCItem (_fc_arg& a, hzXmlNode* pN, uint32_t lnkControl, uint32_t lnkBeyond, uint32_t nLevel)
{
// Recursively read the flowchart XML as an intial pass. This builds an array of flowchart items, in the same order and at the same level as the progenitor XML tags. The items
// are assigned ids and shapes, and some connectors. The branching follows the ...
//
// Arguments: 1) a Container of the statements array and logger
// 2) pN The current XML node
// 3) lnkControl Loop control item (if applicable)
// 4) lnkBeyond Item beyond the current loop, switch or if/elseif/else series
// 5) nLevel Code level
//
// Returns: Pointer to first item created by this call
//
// Notes:
_hzfunc("_readFCItem") ;
hzXmlNode* pN1 ; // Subtag probe
hzXmlNode* pN2 ; // Subtag probe
_fc_item* pFirst ; // First flowchart item issued by this call
_fc_item* pLast ; // Flowchart item
_fc_item* pItem ; // Flowchart item
_fc_item* pCase ; // Case statement
_fc_item* pBranch ; // Case statement
hzAttrset ai ; // Attribute iterator
hzChain T ; // For text aggregation
hzString st ; // Statement object
hzString obj ; // Statement object
hzString typ ; // Statement type
bool bDefault ; // Set when a <default> tag is encountered (ensures it is singular)
pLast = a.m_arrItems[a.m_arrItems.Count() - 1] ;
pFirst = pItem = 0 ;
// Process tags
for (pN1 = pN->GetFirstChild() ; !a.m_bErr && pN1 ; pN1 = pN1->Sibling())
{
a.pLog->Log("File %s Line %d: Processing <%s> tag at level %d ctrl %u beyond %u\n", a.file, pN1->Line(), pN1->txtName(), nLevel, lnkControl, lnkBeyond) ;
if (pItem)
pLast = pItem ;
if (pN1->NameEQ("break"))
{
if (!lnkControl)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <step> break outside control loop\n", a.file, pN1->Line()) ; break ; }
// No item for break. Link to the item beyond the current control loop, switch or if/elseif/else series
if (pLast)
pLast->m_LinkDown = lnkBeyond ;
continue ;
}
if (pN1->NameEQ("continue"))
{
if (!lnkControl)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <step> continue outside control loop\n", a.file, pN1->Line()) ; break ; }
// No item for continue. The link down is back to the loop control item
if (pLast)
pLast->m_LinkDown = lnkControl ;
continue ;
}
// The following tags produce an item
pItem = a.m_mapItems[pN1->GetUid()] ;
if (!pItem)
{ a.pLog->Log("ERROR: No item found for tag %s file %s line %d\n", pN1->txtName(), a.file, pN1->Line()) ; a.m_bErr = 1 ; break ; }
if (!pFirst)
pFirst = pItem ;
pItem->m_Xid = a.m_arrItems.Count() ;
pItem->m_Col = pItem->m_nLevel = nLevel ;
if (pLast)
{
pLast->m_LinkDown = pItem->m_Xid ;
threadLog("Set link past %d down %d\n", pLast->m_Xid, pLast->m_LinkDown) ;
}
if (lnkBeyond)
{
//pItem->m_LinkDown = pControl->m_LinkDown ;
//threadLog("Set link item %d control %d down %d\n", pItem->m_Xid, pControl->m_Xid, pControl->m_LinkDown) ;
threadLog("Set link item %u control %u down %u\n", pItem->m_Xid, lnkControl, lnkBeyond) ;
}
a.m_arrItems.Add(pItem) ;
// The following tags may have an object attrubute
obj.Clear() ;
for (ai = pN1 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("stmt")) st = ai.Value() ;
else if (ai.NameEQ("type")) typ = ai.Value() ;
else if (ai.NameEQ("obj")) obj = ai.Value() ;
else
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <%s> Bad param (%s=%s)\n", a.file, pN1->Line(), pN1->txtName(), ai.Name(), ai.Value()) ; }
}
if (a.m_bErr)
break ;
if (pN1->NameEQ("label"))
{
// This will generate a stadium but in the line of execution, and with a link in and a link out. The label will generally appear after any goto that refers to it, thus
// at the point of the goto, the item id of the label is unknown.
if (!obj)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <%s> No object\n", a.file, pN1->Line()) ; break ; }
pItem->m_eShape = HDSGRAPH_STADIUM ;
pItem->m_eSType = STMT_LABEL ;
pItem->m_Text = obj ;
continue ;
}
if (pN1->NameEQ("goto"))
{
// Link from current shape to the label. It may be the label is not yet so this is recorded as a note
if (!obj)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <%s> No object\n", a.file, pN1->Line()) ; break ; }
pItem->m_eShape = HDSGRAPH_STADIUM ;
pItem->m_eSType = STMT_GOTO ;
pItem->m_Text = obj ;
continue ;
}
if (pN1->NameEQ("return") || pN1->NameEQ("exit"))
{
if (pN1->NameEQ("return"))
pItem->m_eSType = STMT_RETURN ;
else
pItem->m_eSType = STMT_EXIT ;
pItem->m_eShape = HDSGRAPH_STADIUM ;
pItem->m_Text = obj ;
continue ;
}
// Action steps or blocks of steps
if (pN1->NameEQ("bloc"))
{
// Read in an action block - a set of one or more statements that are merged into a single action box rectangle. <bloc> can only have subtags of <step>.
pN2 = pN1->GetFirstChild() ;
if (!pN2)
{
// Dead block so ignore and continue
continue ;
}
pItem->m_eSType = STMT_BLOC ;
pItem->m_eShape = HDSGRAPH_RECT ;
// Recurse to handle CASE statements
T.Clear() ;
for (; !a.m_bErr && pN2 ; pN2 = pN2->Sibling())
{
if (!pN2->NameEQ("step"))
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <bloc> Illegal subtag <%s>, only <step> allowed\n", a.file, pN2->Line(), pN2->txtName()) ; break ; }
obj.Clear() ;
for (ai = pN2 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("stmt")) st = ai.Value() ;
else if (ai.NameEQ("type")) typ = ai.Value() ;
else if (ai.NameEQ("obj")) obj = ai.Value() ;
else
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <step> Bad param (%s=%s)\n", a.file, pN2->Line(), ai.Name(), ai.Value()) ; }
}
if (a.m_bErr)
break ;
if (!obj)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <step> No object\n", a.file, pN2->Line()) ; break ; }
if (T.Size())
T.AddByte(CHAR_NL) ;
T << obj ;
}
pItem->m_Text = T ;
continue ;
}
if (pN1->NameEQ("step"))
{
pItem->m_eSType = STMT_STEP ;
pItem->m_eShape = HDSGRAPH_RECT ;
pItem->m_Text = obj ;
continue ;
}
/*
** Switch handler
*/
if (pN1->NameEQ("switch"))
{
// Expect CASE and DEFAULT to be at next level
pN2 = pN1->GetFirstChild() ;
if (!pN2)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <switch> has no subtags\n", a.file, pN2->Line()) ; break ; }
// Check that at next level, all subnodes are <case> except for the last which may be <default>
for (bDefault = false ; !a.m_bErr && pN2 ; pN2 = pN2->Sibling())
{
if (pN2->NameEQ("case"))
continue ;
if (pN2->NameEQ("default"))
{
if (bDefault)
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <default> Duplicate default\n", a.file, pN2->Line()) ; break ; }
bDefault = true ;
continue ;
}
a.m_bErr = true ;
a.pLog->Log("File %s Line %d: <%s> Illegal <switch> subtag\n", a.file, pN2->Line(), pN2->txtName()) ;
break ;
}
// Commit the default
pItem->m_eSType = STMT_BRANCH_SWITCH ;
pItem->m_eShape = HDSGRAPH_RECT ;
pItem->m_Text = obj ;
//currSwitch = pItem ;
// Handle CASE and DEFAULT
for (pN2 = pN1->GetFirstChild() ; !a.m_bErr && pN2 ; pN2 = pN2->Sibling())
{
obj.Clear() ;
for (ai = pN2 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("stmt")) st = ai.Value() ;
else if (ai.NameEQ("type")) typ = ai.Value() ;
else if (ai.NameEQ("obj")) obj = ai.Value() ;
else
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <%s> Bad param (%s=%s)\n", a.file, pN2->Line(), pN2->txtName(), ai.Name(), ai.Value()) ; }
}
if (a.m_bErr)
break ;
pCase = a.m_mapItems[pN2->GetUid()] ;
if (!pCase)
{ a.pLog->Log("ERROR: No item found for tag %s file %s line %d\n", pN1->txtName(), a.file, pN1->Line()) ; a.m_bErr = 1 ; break ; }
//pCase = new _fc_item() ;
a.m_arrItems.Add(pCase) ;
pCase->m_Xid = a.m_arrItems.Count() ;
pCase->m_Col = pCase->m_nLevel = nLevel ;
//pCase->m_LinkCtrl = pControl ? pControl->m_Xid : 0 ;
pCase->m_eShape = HDSGRAPH_HEXAGON ;
if (pN2->NameEQ("case"))
pCase->m_eSType = STMT_BRANCH_CASE ;
else
pCase->m_eSType = STMT_BRANCH_DEFAULT ;
pCase->m_Text = obj ;
_readFCItem(a, pN2, lnkControl, lnkBeyond, nLevel+1) ;
//pCase->m_LinkSwitch = a.m_arrItems.Count() ;
}
continue ;
}
/*
** Handle IF, ELSEIF and ELSE
*/
if (pN1->NameEQ("if"))
{
// Note that an IF can optionally be followed by one or more ELSE-IF and/or an ELSE. For the benefit of actions, it is necessary to know where the series ends since a
// connector will be needed from the action to the step beyond the IF series. This will not necessarily be within the set of steps being processed in this call.
pItem->m_eSType = STMT_BRANCH_IF ;
pItem->m_eShape = HDSGRAPH_HEXAGON ;
pItem->m_Text = obj ;
// Recurse to handle TRUE action
pBranch = _readFCItem(a, pN1, lnkControl, lnkBeyond, nLevel+1) ;
if (pBranch)
pItem->m_LinkTrue = pBranch->m_Xid ;
pItem->m_LinkDown = a.m_arrItems.Count() ;
threadLog("Set IF link src %d true %d down %d\n", pItem->m_Xid, pItem->m_LinkTrue, pItem->m_LinkDown) ;
// Check for ELSE-IF and ELSE
pN2 = pN1->Sibling() ;
if (!pN2)
continue ;
for (; !a.m_bErr && pN2 && pN2->NameEQ("elseif") ; pN2 = pN2->Sibling())
{
pLast = pItem ;
pItem = a.m_mapItems[pN2->GetUid()] ;
if (!pItem)
{ a.pLog->Log("ERROR: No item found for tag %s file %s line %d\n", pN1->txtName(), a.file, pN1->Line()) ; a.m_bErr = 1 ; break ; }
//pItem = new _fc_item() ;
a.m_arrItems.Add(pItem) ;
pItem->m_Xid = a.m_arrItems.Count() ;
pItem->m_Col = pItem->m_nLevel = nLevel ;
//pItem->m_LinkCtrl = pControl ? pControl->m_Xid : 0 ;
pItem->m_eSType = STMT_BRANCH_ELSEIF ;
pItem->m_eShape = HDSGRAPH_HEXAGON ;
obj.Clear() ;
for (ai = pN2 ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("stmt")) st = ai.Value() ;
else if (ai.NameEQ("type")) typ = ai.Value() ;
else if (ai.NameEQ("obj")) obj = ai.Value() ;
else
{ a.m_bErr = true ; a.pLog->Log("File %s Line %d: <%s> Bad param (%s=%s)\n", a.file, pN2->Line(), pN2->txtName(), ai.Name(), ai.Value()) ; }
}
if (a.m_bErr)
break ;
pBranch = _readFCItem(a, pN2, lnkControl, lnkBeyond, nLevel+1) ;
if (pBranch)
pItem->m_LinkTrue = pBranch->m_Xid ;
pItem->m_LinkDown = a.m_arrItems.Count() ;
threadLog("Set ELSEIF link src %d true %d down %d\n", pItem->m_Xid, pItem->m_LinkTrue, pItem->m_LinkDown) ;
pN1 = pN2 ;
}
if (!a.m_bErr && pN2 && pN2->NameEQ("else"))
{
// Recurse to handle CASE statements
pBranch = _readFCItem(a, pN2, lnkControl, lnkBeyond, nLevel) ;
if (pBranch)
pItem->m_LinkDown = pBranch->m_Xid ;
pItem->m_LinkDown = a.m_arrItems.Count() ;
threadLog("Set ELSE link src %d true %d down %d\n", pItem->m_Xid, pItem->m_LinkTrue, pItem->m_LinkDown) ;
pN1 = pN2 ;
}
continue ;
}
/*
** Handle FOR, WHILE and DOWHILE loops
*/
if (pN1->NameEQ("for") || pN1->NameEQ("while"))
{
// Put the condition test hexagon before the loop body (branch)
if (pN1->NameEQ("for"))
pItem->m_eSType = STMT_BRANCH_FOR ;
else
pItem->m_eSType = STMT_BRANCH_WHILE ;
pItem->m_eShape = HDSGRAPH_HEXAGON ;
pItem->m_Text = obj ;
pN2 = pN1->Sibling() ;
if (pN2)
{
}
_readFCItem(a, pN1, pItem->m_Xid, lnkBeyond, nLevel+1) ;
continue ;
}
if (pN1->NameEQ("dowhile"))
{
// Place loop body before the condition test hexagon. This means the loop body is not at a higher level, i.e. it is a continuation of the execution line. The item ids
// are intended to be in order of appearence in the flowchart diagram. So if the dowhile tag is the Nth tag in the flowchart, the first item in the do-while loop will
// have an id of N and the condition test hexagon will have an id of N+X where X is the number of items in the do-while loop. The true link of the condition test item
// is then set to N.
pItem->m_eSType = STMT_BRANCH_DOWHILE ;
pItem->m_eShape = HDSGRAPH_HEXAGON ;
pItem->m_Text = obj ;
_readFCItem(a, pN1, pItem->m_Xid, lnkBeyond, nLevel) ;
continue ;
}
}
return pFirst ;
}
static void _calcFCDims (_fc_arg& a, uint32_t& maxX, uint32_t& maxY)
{
// Support function for _readFlowchart. Align midpoints of flowchart items and calculate total height and width.
//
// The actual width and height of a flowchart item is determined by the item text content. The actual rectangle the item will occupy will be larger as there must be separating
// space, and of a margin sufficient to accomodate objective connectors. The horizontal and vertical midpoints of the items are aligned so that as many connectors as possible
// are single vertical or horizontal lines. It is accepted that some connectors will comprise multiple lines, such as those resuming the main line of execution after a branch,
// and those returning to the top of a loop.
//
// First the actual width and height of every item is determined, then the effective height and width of the items is calculated, then finally the actual top, bottom, left and
// right coords are assigned. The effective width of items is that of the widest item in the applicable vertically descending series (line of execution), plus a space margin.
// The effective height of items is that of the tallest item in a horizontal series (an execution branch).
//
// Arguments: a The flowchart paramenter container
// maxX The total width of the flowchart (set by this function)
// maxY The total height of the flowchart (set by this function)
//
// Returns: None
_hzfunc("_calcFCDims") ;
_fc_item* pItem ; // Current flowchart item
_fc_item* pNext ; // Current flowchart item
const char* i ; // Text iterator
uint32_t maxH ; // Max height found in a horizontal series
uint32_t maxW ; // Max width found in a vertical series
uint32_t nRow ; // Must be in same row
uint32_t nCol ; // Must be in same column
uint32_t start ; // Item iterator
uint32_t end ; // Count of items
uint32_t n ; // Item iterator
uint32_t count ; // Number in either a horizontal or vertical series
uint32_t h_sofar ; // Height so far
uint32_t h_next ; // Height of next row
uint32_t h_mid ; // Height midpoint
uint32_t w_mid ; // Height midpoint
uint32_t lenLine ; // Length of this line
uint32_t lenMax ; // Longest line encountered
end = a.m_arrItems.Count() - 1 ;
/*
** Calculate actual height and width based on text content.
*/
for (n = 0 ; n <= end ; n++)
{
pItem = a.m_arrItems[n] ;
lenMax = 10 ;
pItem->m_nLines = 1 ;
if (pItem->m_Text)
{
for (lenLine = 0, i = *pItem->m_Text ; *i ; i++)
{
if (*i == CHAR_NL)
{
pItem->m_nLines++ ;
if (lenLine > lenMax)
lenMax = lenLine ;
lenLine = 0 ;
continue ;
}
lenLine++ ;
}
if (lenLine > lenMax)
lenMax = lenLine ;
}
pItem->m_actH = 16 * (pItem->m_nLines+1) ;
pItem->m_actW = 8 * lenMax ;
if (pItem->m_eShape == HDSGRAPH_HEXAGON)
pItem->m_actW += (pItem->m_actH/2) ;
}
/*
** Set height, top and bottom
*/
nCol = nRow = 0 ;
for (start = 1 ; start <= end ; start++)
{
pItem = a.m_arrItems[start] ;
if (pItem->m_Col > nCol)
pItem->m_Row = nRow ;
else
pItem->m_Row = ++nRow ;
nCol = pItem->m_Col ;
}
nCol = nRow = 0 ;
for (start = 0 ; start <= end ; start++)
{
pItem = a.m_arrItems[start] ;
if (pItem->m_effW == 0)
{
// Set the column - and then scan all items at the column or above, terminating either at the end or upon ecountering an item of lower colum
pItem->m_effW = pItem->m_actW ;
nCol = pItem->m_Col ;
maxW = pItem->m_actW ;
count = 0 ;
for (n = start + 1 ; n < end ; n++)
{
pNext = a.m_arrItems[n] ;
if (pNext->m_Col < nCol)
break ;
if (pNext->m_Col > nCol)
continue ;
if (pNext->m_actW > maxW)
maxW = pNext->m_actW ;
pNext->m_effW = pNext->m_actW ;
count++ ;
}
if (count)
{
// Revisit the items and set the m_effW to maxW
for (n = start ; n < end ; n++)
{
pNext = a.m_arrItems[n] ;
if (pNext->m_Col < nCol)
break ;
if (pNext->m_Col > nCol)
continue ;
pItem->m_effW = maxW ;
}
}
}
}
nCol = nRow = 0 ;
for (start = 0 ; start <= end ; start++)
{
pItem = a.m_arrItems[start] ;
if (pItem->m_effH == 0)
{
// If we are on a horizontal series, establish max height for the series
maxH = pItem->m_actH ;
count = 0 ;
nRow = pItem->m_Row ;
for (n = start + 1 ; n < end ; n++)
{
pNext = a.m_arrItems[n] ;
if (pNext->m_Row > nRow)
break ;
if (pNext->m_actH > maxH)
maxH = pNext->m_actH ;
count++ ;
}
if (!count)
pItem->m_effH = pItem->m_actH ;
else
{
// Horizontal series established. Set effective height for whole series
for (n = start ; count ; count--, n++)
{
pItem = a.m_arrItems[n] ;
pItem->m_effH = maxH ;
}
}
}
}
h_sofar = 30 ;
h_next = 0 ;
nRow = 0 ;
for (start = 0 ; start <= end ; start++)
{
pItem = a.m_arrItems[start] ;
if (nRow < pItem->m_Row)
{
nRow++ ;
h_sofar += h_next ;
h_sofar += 30 ;
}
h_mid = h_sofar + (pItem->m_effH/2) ;
pItem->m_Top = h_mid - (pItem->m_actH/2) ;
pItem->m_Bot = h_mid + (pItem->m_actH/2) ;
h_next = pItem->m_effH ;
}
/*
** Set width, left and right
*/
uint16_t cols[16] ;
memset(cols, 0, 32) ;
for (nCol = start = 0 ; start <= end ; start++)
{
pItem = a.m_arrItems[start] ;
nCol = pItem->m_Col ;
if (nCol > 0)
cols[nCol] = cols[nCol-1] + pItem->m_effW ;
else
cols[nCol] = pItem->m_effW ;
w_mid = ((nCol+1)*30) + cols[nCol] - (pItem->m_effW/2) ;
pItem->m_Lft = w_mid - (pItem->m_actW/2) ;
pItem->m_Rht = w_mid + (pItem->m_actW/2) ;
}
// Set height and width of flowchart
maxX = maxY = 0 ;
for (start = 0 ; start <= end ; start++)
{
pItem = a.m_arrItems[start] ;
threadLog("doing item %d of %d: %s Row %02d Col %02d L %d W %03d/%03d H %03d/%03d top %03d bot %03d lft %03d rht %03d %s\n",
start,
end,
pItem->m_eShape == HDSGRAPH_STADIUM ? "STA" : pItem->m_eShape == HDSGRAPH_HEXAGON ? "HEX" : pItem->m_eShape == HDSGRAPH_RECT ? "REC" : "NUL",
pItem->m_Row,
pItem->m_Col,
pItem->m_nLevel,
pItem->m_actW,
pItem->m_effW,
pItem->m_actH,
pItem->m_effH,
pItem->m_Top,
pItem->m_Bot,
pItem->m_Lft,
pItem->m_Rht,
*pItem->m_Text) ;
if (pItem->m_Bot > maxY)
maxY = pItem->m_Bot ;
if (pItem->m_Rht > maxX)
maxX = pItem->m_Rht ;
}
maxY += 60 ;
maxX += 60 ;
}
hdsVE* hdsApp::_readFlowchart (hzXmlNode* pN)
{
// Read in flowchart components.
//
// Argument: pN The current XML node expected to be a <xdiagram> tag
//
// Returns: Pointer to diagram visual entity
_hzfunc("hdsApp::_readFlowchart") ;
_fc_arg base ; // All flowchart graphics, connectors and other info
_fc_item* pItem ; // Current flowchart item
_fc_item* pCtrl ; // Current control flowchart item
hdsText TX ; // Diagram Text component
hzAttrset ai ; // Attribute iterator
hzNumPair np ; // Row height entry
hdsGraphic fcG ; // Flowchart start stadium
hdsConnector* pConn ; // Flowchart connector
hdsFlowchart* pFlow ; // This flowchart
hdsGraphic* pG ; // Current flowchart component
hdsVE* thisVE ; // Visible entity/active entity
hzString cfg ; // Config source file
uint32_t n ; // Graphic iterator
uint32_t Xmax ; // Max implied width
uint32_t Ymax ; // Max implied width
hzEcode rc = E_OK ; // Return code
// Check node
if (!pN) Fatal("No node supplied\n") ;
if (!pN->NameEQ("xflowchart")) Fatal("File %s Line %d: Incorrect node (%s) supplied. Must be <xflowchart>\n", *pN->Filename(), pN->Line(), pN->txtName()) ;
// Create flowchart visible entity
thisVE = pFlow = new hdsFlowchart(this) ;
cfg = pN->Filename() ;
thisVE->m_Line = pN->Line() ;
thisVE->m_Indent = pN->Level() ;
pFlow->m_BgColor = 0xc0c0ff ;
pFlow->m_ColorLine = 0 ;
pFlow->m_VSpace = pFlow->m_HSpace = 50 ;
pFlow->m_Font = "10px Arial" ;
if (!pN->GetFirstChild())
return pFlow ;
// Get diagram params
for (ai = pN ; ai.Valid() ; ai.Advance())
{
if (ai.NameEQ("id")) pFlow->m_Id = ai.Value() ;
else if (ai.NameEQ("header")) pFlow->m_Header = ai.Value() ;
else if (ai.NameEQ("footer")) pFlow->m_Footer = ai.Value() ;
else if (ai.NameEQ("bgcolor")) IsHexnum(pFlow->m_BgColor, ai.Value()) ;
else if (ai.NameEQ("fgcolor")) IsHexnum(pFlow->m_FgColor, ai.Value()) ;
else if (ai.NameEQ("linecolor")) IsHexnum(pFlow->m_ColorLine, ai.Value()) ;
else if (ai.NameEQ("termcolor")) IsHexnum(pFlow->m_ColorTerm, ai.Value()) ;
else if (ai.NameEQ("testcolor")) IsHexnum(pFlow->m_ColorTest, ai.Value()) ;
else if (ai.NameEQ("proccolor")) IsHexnum(pFlow->m_ColorProc, ai.Value()) ;
else if (ai.NameEQ("vspace")) pFlow->m_VSpace = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("hspace")) pFlow->m_HSpace = ai.Value() ? atoi(ai.Value()) : 0 ;
else if (ai.NameEQ("font")) pFlow->m_Font = ai.Value() ;
else
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xflowchart> Bad param (%s=%s)\n", *cfg, pN->Line(), ai.Name(), ai.Value()) ; }
}
// Check values
if (!pFlow->m_Id)
{ rc = E_SYNTAX ; m_pLog->Log("File %s Line %d: <xflowchart> No id\n", *cfg, pN->Line()) ; }
if (rc != E_OK)
return pFlow ;
threadLog("Doing flowchart %s\n", *pFlow->m_Id) ;
// Init flowchart base params
base.pLog = m_pLog ;
base.file = pN->Filename() ;
base.m_bErr = false ;
// Add the START stadium
pItem = new _fc_item() ;
base.m_arrItems.Add(pItem) ;
pItem->m_eShape = HDSGRAPH_STADIUM ;
pItem->m_eSType = STMT_LABEL ;
pItem->m_Xid = 0 ;
pItem->m_Col = pItem->m_nLevel = 0 ;
pItem->m_Row = 0 ;
pItem->m_Text = "START" ;
// Recursive read of flowchart items
_createFCItems(base, pN) ;
_readFCItem(base, pN, 0, 0, 0) ;
// Reject flowcharts with less than 3 items
if (base.m_arrItems.Count() < 3)
return pFlow ;
// Calculate dimensions of flowchart items and whole flowchart
_calcFCDims(base, Xmax, Ymax) ;
// Count up number of graphics and connectors and assign graphic ids to the items
pFlow->m_nShapes = base.m_arrItems.Count() ;
pFlow->m_pShapes = new hdsGraphic[pFlow->m_nShapes] ;
threadLog("Copying %d shapes\n", base.m_arrItems.Count()) ;
for (n = 0 ; n < base.m_arrItems.Count() ; n++)
{
pItem = base.m_arrItems[n] ;
if (pItem->m_Xid != n)
threadLog("Warning - case A item id %d, n %d\n", pItem->m_Xid, n) ;
pG = pFlow->m_pShapes + n ;
pG->m_eShape = pItem->m_eShape ;
pG->m_Text = pItem->m_Text ;
pG->m_Top = pItem->m_Top ;
pG->m_Bot = pItem->m_Bot ;
pG->m_Lft = pItem->m_Lft ;
pG->m_Rht = pItem->m_Rht ;
pG->m_Width = pG->m_Rht - pG->m_Lft ;
pG->m_Height = pG->m_Bot - pG->m_Top ;
pG->m_Id = n ;
pG->m_ColorLine = pFlow->m_ColorLine ;
pG->m_Font = pFlow->m_Font ;
switch (pG->m_eShape)
{
case HDSGRAPH_STADIUM: pG->m_ColorFill = pFlow->m_ColorTerm ; break ;
case HDSGRAPH_RECT: pG->m_ColorFill = pFlow->m_ColorProc ; break ;
case HDSGRAPH_HEXAGON: pG->m_ColorFill = pFlow->m_ColorTest ; break ;
default:
threadLog("NULL GRAPHIC (%d)\n", n) ;
pG->m_eShape = HDSGRAPH_HEXAGON ;
pG->m_ColorLine = 0xff0000 ;
break ;
}
}
threadLog("DONE Copying shapes\n") ;
// Assign connectors
pFlow->m_nConnects = 0 ;
for (n = 0 ; n < base.m_arrItems.Count() ; n++)
{
pItem = base.m_arrItems[n] ;
if (pItem->m_Xid != n)
threadLog("Warning - case B item id %d, n %d\n", pItem->m_Xid, n) ;
if (pItem->m_LinkCtrl >= base.m_arrItems.Count()) threadLog("ERROR: Link %u has ctrl of %u\n", pItem->m_Xid, pItem->m_LinkCtrl) ;
if (pItem->m_LinkDown >= base.m_arrItems.Count()) threadLog("ERROR: Link %u has down of %u\n", pItem->m_Xid, pItem->m_LinkDown) ;
if (pItem->m_LinkTrue >= base.m_arrItems.Count()) threadLog("ERROR: Link %u has true of %u\n", pItem->m_Xid, pItem->m_LinkTrue) ;
if (pItem->m_LinkDown || pItem->m_LinkCtrl)
pFlow->m_nConnects++ ;
if (pItem->m_LinkTrue)
pFlow->m_nConnects++ ;
}
pFlow->m_pConn = new hdsConnector[pFlow->m_nConnects] ;
for (n = 0 ; n < base.m_arrItems.Count() ; n++)
{
pItem = base.m_arrItems[n] ;
if (!pItem)
threadLog("No item %d\n", n) ;
if (pItem->m_Xid != n)
threadLog("Warning - case C item id %d, n %d\n", pItem->m_Xid, n) ;
pConn = pFlow->m_pConn ;
pConn->m_Origin = pItem->m_Xid ;
if (!pItem->m_LinkDown)
{
if (pItem->m_LinkCtrl)
{
pCtrl = base.m_arrItems[pItem->m_LinkCtrl] ;
if (pCtrl)
pItem->m_LinkDown = pCtrl->m_LinkDown ;
else
threadLog("Error - NULL CONTROL %d\n", pItem->m_Xid) ;
}
}
if (pItem->m_LinkDown)
{
pConn->m_Target = pItem->m_LinkDown ;
pConn++ ;
}
else
{
threadLog("ERROR item %d no link down\n", pItem->m_Xid) ;
}
if (pItem->m_LinkTrue)
{
if (pItem->m_LinkTrue >= base.m_arrItems.Count())
threadLog("Error - link true exceeds range (%d)\n", pItem->m_Xid) ;
else
{
pConn->m_Origin = pItem->m_Xid ;
pConn->m_Target = pItem->m_LinkTrue ;
pConn++ ;
}
}
else
{
if (pItem->m_eSType == STMT_BRANCH_IF)
threadLog("ERROR IF without a link TRUE\n") ;
if (pItem->m_eSType == STMT_BRANCH_ELSEIF)
threadLog("ERROR ELSEIF without a link TRUE\n") ;
}
}
pFlow->m_Width = Xmax ;
pFlow->m_Height = Ymax ;
m_pLog->Log("Declared xflowchart parmas X %d Y %d\n", Xmax, Ymax) ;
return thisVE ;
}