//
//  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 ;
}