//
//  File:   hdsGenerate.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 <fstream>
#include <cmath>
#include <unistd.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include "hzDissemino.h"
using namespace std ;
/*
**  Prototypes
*/
const char* Exec2Txt    (Exectype eType) ;
/*
**  Shorthand for generating HTML
*/
//  Google script
static  hzString    s_Recaptcha = "<script src=\"https://www.google.com/recaptcha/api.js\" async defer></script>\n" ;
//  Standard meta tags for generated pages
static  hzString    s_std_metas =
    "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n"
    "<meta http-equiv=\"Expires\" content=\"-1\"/>\n"
    "<meta http-equiv=\"Cache-Control\" content=\"no-cache\"/>\n"
    "<meta http-equiv=\"Cache-Control\" content=\"no-store\"/>\n"
    "<meta http-equiv=\"Pragma\" content=\"no-cache\"/>\n"
    "<meta http-equiv=\"Pragma\" content=\"no-store\"/>\n" ;
/*
**  Variables
*/
static  hzString    tagInput = "input" ;        //  String for HTML tag input
static  hzString    tagImg = "img" ;            //  String for HTML tag img
static  hzString    tagBr = "br" ;              //  String for HTML tag br
extern  hzString    _dsmScript_ckEmail ;        //  Javascript to validate email address
extern  hzString    _dsmScript_ckUrl ;          //  Javascript to validate URL
extern  hzString    _dsmScript_ckExists ;       //  Javascript to run AJAX check for value already exists
extern  hzString    _dsmScript_tog ;            //  Javascript to toggle nav-tree items
extern  hzString    _dsmScript_loadArticle ;    //  Javascript to load aricles upon selection from nav-tree
extern  hzString    _dsmScript_navtree ;        //  Javascript to display nav-tree
extern  hzString    _dsmScript_gwp ;            //  Javascript to obtain window params
extern  hzString    _hzGlobal_str_TRUE ;        //  For flowchart decision boxes
extern  hzString    _hzGlobal_str_FALSE ;       //  For flowchart decision boxes
extern  hzString    _hzGlobal_str_START ;       //  For flowchart START stadium
/*
**  Pull down menu driver
*/
void    hdsNavbar::Generate (hzChain& Z, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Aggregates into the supplied chain, the HTML nessesary to display the navbar
    //
    //  Arguments:  1)  Z       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsNavbar::Generate") ;
    hzList<hzString>::Iter  si ;    //  Subjects iterator
    hzPair          p ;             //  NV pair representing page url and page title
    hdsInfo*        pInfo = 0 ;     //  Session
    //hdsSubject*       pSubj ;         //  Subject
    hzString        subject ;       //  Subject
    hdsResource*    pPage ;         //  Page within subject
    uint32_t        x ;             //  Subhect iterator
    uint32_t        y ;             //  Page iterator (within subject)
    uint32_t        nLo ;           //  First page of a given subject
    uint32_t        nHi ;           //  Last page of a given subject
    if (pE)
        pInfo = (hdsInfo*) pE->Session() ;
    if (m_JS)
    {
        Z << "<script language=\"javascript\" src=\"/jsc/navbarItems.js\"></script>\n" ;
        Z << "<script language=\"javascript\" src=\"/jsc/navbarMenu.js\"></script>\n" ;
        return ;
    }
    Z <<
    "\n<table cellspacing='0' cellpadding='0' width='90%' background-color='#000000' border='0'>\n"
    "<tr>\n"
    "\t<td width='1'> </td><td height='20' valign='center'>\n" ;
    //  Prepare headings
#if 0
    for (x = 0 ; x < m_pApp->m_vecPgSubjects.Count() ; x++)
    {
        pSubj = m_pApp->m_vecPgSubjects[x] ;
        if (!pSubj->pglist.Count())
            continue ;
        if (pSubj->pglist.Count() <= 1)
            Z.Printf("\t<span id='Pdm%d' class='measure'><a href='%s' onmouseover='hideLast()' class='top'>", x, *pSubj->first) ;
        else
            Z.Printf("\t<span id='Pdm%d' class='measure'><a href=\"#\" class='top' onmouseover='doSub(%d)' onclick='return false'>", x, x) ;
        Z.Printf("%s    </a></span>\n", *pSubj->subject) ;
    }
#endif
    for (si = m_pApp->m_lstPgSubjects, x = 0 ; si.Valid() && x < m_pApp->m_lstPgSubjects.Count() ; si++, x++)
    {
        subject = si.Element() ;
        if (m_pApp->m_lstPgSubjects.Count() == 1)
            Z.Printf("\t<span id='Pdm%d' class='measure'><a href='%s' onmouseover='hideLast()' class='top'>", x, *subject) ;
        else
            Z.Printf("\t<span id='Pdm%d' class='measure'><a href=\"#\" class='top' onmouseover='doSub(%d)' onclick='return false'>", x, x) ;
        Z.Printf("%s    </a></span>\n", *subject) ;
    }
    Z <<
    "\t</td>\n"
    "</tr>\n"
    "</table>\n" ;
    //  Prepare lists (sub-menus)
#if 0
    for (x = 0 ; x < m_pApp->m_vecPgSubjects.Count() ; x++)
    {
        Z.Printf("<div id='Sub%d' style='visibility:hidden;position:absolute;width:relative;' onmouseover='IEBum(0,%d)' onmouseout='IEBum(1,%d)'>\n",
            x, x, x) ;
        Z << "\t<table border='0' background-color='#000000' cellspacing=0 cellpadding=0 width='200'>\n" ; 
        pSubj = m_pApp->m_vecPgSubjects[x] ;
        if (!pSubj->pglist.Count())
            continue ;
        for (y = 0 ; y < pSubj->pglist.Count() ; y++)
        {
            pPage = pSubj->pglist[y] ;
            if (pPage->m_resAccess == ACCESS_PUBLIC
                    || (pPage->m_resAccess == ACCESS_NOBODY && (!pInfo || !(pInfo->m_Access & ACCESS_MASK)))
                    || (pInfo && (pInfo->m_Access & ACCESS_ADMIN || (pInfo->m_Access & ACCESS_MASK) == pPage->m_resAccess)))
                Z.Printf("\t<tr><td height='13' valign='center'> <a href='%s' id='link' class='top' onmouseover='IEBum(0,%d)'>%s</a></td></tr>\n",
                    *pPage->m_Url, x, *pPage->m_Title) ;
        }
        Z << "\t</table>\n</div>\n" ;
    }
#endif
    for (si = m_pApp->m_lstPgSubjects, x = 0 ; si.Valid() && x < m_pApp->m_lstPgSubjects.Count() ; si++, x++)
    {
        subject = si.Element() ;
        Z.Printf("<div id='Sub%d' style='visibility:hidden;position:absolute;width:relative;' onmouseover='IEBum(0,%d)' onmouseout='IEBum(1,%d)'>\n",
            x, x, x) ;
        Z << "\t<table border='0' background-color='#000000' cellspacing=0 cellpadding=0 width='200'>\n" ; 
        nLo = m_pApp->m_mapSubj2Res.First(subject) ;
        if (nLo < 0)
            continue ;
        nHi = m_pApp->m_mapSubj2Res.Last(subject) ;
        for (y = nLo ; y <= nHi ; y++)
        {
            pPage = m_pApp->m_mapSubj2Res.GetObj(y) ;
            if (pPage->m_resAccess == ACCESS_PUBLIC
                    || (pPage->m_resAccess == ACCESS_NOBODY && (!pInfo || !(pInfo->m_Access & ACCESS_MASK)))
                    || (pInfo && (pInfo->m_Access & ACCESS_ADMIN || (pInfo->m_Access & ACCESS_MASK) == pPage->m_resAccess)))
                Z.Printf("\t<tr><td height='13' valign='center'> <a href='%s' id='link' class='top' onmouseover='IEBum(0,%d)'>%s</a></td></tr>\n",
                    *pPage->m_Url, x, *pPage->m_Title) ;
        }
        Z << "\t</table>\n</div>\n" ;
    }
}
/*
**  Error message
*/
void    hdsApp::_doHead (hzChain& Z, const char* cpPage)
{
    //  Formulate a standard HTML header (the <head> tag) and agregate it to the supplied chain. The title is set to the supplied value and the header
    //  contains a link to the application's stylesheet.
    //
    //  Arguments:  1)  Z       The target chain
    //              2)  cpPage  The page title
    //
    //  Returns:    None
    _hzfunc("hdsApp::_doHead") ;
    Z <<
    "<!DOCTYPE html>\n"
    "<html>\n"
    "<head>\n"
    "<title>" << cpPage << "</title>\n"
    << s_std_metas <<
    "<link rel=\"stylesheet\" href=\"" << m_namCSS << "\">\n"
    "</head>\n\n" ;
    //"<link rel=\"icon\" href=\"data:;base64,iVBORw0KGgo=\">\n"
}
void    hdsApp::_doHeadR    (hzChain& Z, const char* cpPage, const char* cpUrl, int nDelay)
{
    //  Formulate a standard HTML header as with _doHead() but with the addition of a <meta> refresh tag to direct the browser to another URL after a
    //  stated time interval.
    //
    //  Arguments:  1)  Z       The target chain
    //              2)  cpPage  The page title
    //              3)  cpUrl   The redirection RL
    //              4)  nDelay  The delay in seconds
    //
    //  Returns:    None
    _hzfunc("hdsApp::_doHeadR") ;
    Z <<
    "<!DOCTYPE html>\n"
    "<html>\n"
    "<head>\n"
    "<title>" << cpPage << "</title>\n" ;
    if (!nDelay)
        nDelay = 10 ;
    if (cpUrl)
        Z.Printf("<meta http-equiv=\"Refresh\" content=\"%d; url='%s'\">\n", nDelay, cpUrl) ;
    else
        Z.Printf("<meta http-equiv=\"Refresh\" content=\"%d\">\n", nDelay) ;
    Z << s_std_metas <<
    "<link rel=\"stylesheet\" href=\"" << m_namCSS << "\">\n"
    "</head>\n\n" ;
}
//  FnGrp:  hdsApp::SendErrorPage
//
//  Generate HTML to display a system error report. This will name the function in which the error occured and the error message
//
//  Returns:    None 
//
//  Func:   hdsApp::_xerror (hzHttpEvent*, HttpRC, const char*, const char* ...)
//  Func:   hdsApp::_xerror (hzHttpEvent*, HttpRC, const char*, hzChain&)
void    hdsApp::SendErrorPage   (hzHttpEvent* pE, HttpRC rv, const char* func, const char* va_alist ...)
{
    //  Arguments:  1)  pE          The current HTTP event
    //              2)  rv          The required HTML return code
    //              3)  func        The function name
    //              4)  va_alist    The error message either as a varargs string
    //
    //  Returns:    None
    _hzfunc("hdsApp::SendErrorPage(1)") ;
    va_list         ap1 ;       //  Variable argument list
    hzChain         C ;         //  Output chain
    hzChain         E ;         //  Error chain
    if (!pE)
    {
        hzerr(E_ARGUMENT, "No HTTP Event") ;
        return ;
    }
    va_start(ap1, va_alist) ;
    E._vainto(va_alist, ap1) ;
    _doHead(C, "Error") ;
    C << "<body marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" topmargin=\"0\">\n\n" ;
    C << "<center><h2>Oops!</h2></center>\n" ;
    C << "<table width=\"300\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"fld\">\n" ;
    C << "<tr height=\"100\"><td>Function " << func << " Has produced the following error<td></tr>\n" ;
    C << "<tr height=\"350\"><td>" << E << "</td></tr>\n" ;
    C.Printf("<tr height=\"150\"><td><input type=\"button\" value=\"Go Back\" onclick=\"window.location.href='%s'\"></td></tr>\n", *pE->Referer()) ;
    C << "</table>\n\n</body>\n</html>\n" ;
    pE->SendRawChain(rv, HMTYPE_TXT_HTML, C, 0, false) ;
}
void    hdsApp::SendErrorPage   (hzHttpEvent* pE, HttpRC rv, const char* func, hzChain& error)
{
    //  Arguments:  1)  pE      The current HTTP event
    //              2)  rv      The required HTML return code
    //              3)  func    The function name
    //              4)  error   The error message as a preformulated chain
    //
    //  Returns:    None
    _hzfunc("hdsApp::SendErrorPage(1)") ;
    hzChain          C ;        //  Output chain
    if (!pE)
    {
        hzerr(E_ARGUMENT, "No HTTP Event") ;
        return ;
    }
    _doHead(C, "Error") ;
    C << "<body marginwidth=\"0\" marginheight=\"0\" leftmargin=\"0\" topmargin=\"0\">\n\n" ;
    C << "<center><h2>Oops!</h2></center>\n" ;
    C << "<table width=\"300\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"fld\">\n" ;
    C << "<tr height=\"100\"><td>Function " << func << " Has produced the following error<td></tr>\n" ;
    C << "<tr height=\"350\"><td>" << error << "</td></tr>\n" ;
    C.Printf("<tr height=\"150\"><td><input type=\"button\" value=\"Go Back\" onclick=\"window.location.href='%s'\"></td></tr>\n", *pE->Referer()) ;
    C << "</table>\n\n</body>\n</html>\n" ;
    pE->SendRawChain(rv, HMTYPE_TXT_HTML, C, 0, false) ;
}
/*
**  Web Component display functions
*/
void    hdsButton::Generate (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Aggregate to the supplied chain, HTML to manifest a button.
    //
    //  The button can be a link (GET request) OR a form action (POST request). In the link case, button member m_Linkto will give the location. In the form action case, there will
    //  need to be submission URL which is obtained from the form reference (given in HTTP event variable m_pContextForm).
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsButton::Generate") ;
    hdsFormref* pFR ;   //  Form reference
    hzString    url ;   //  URL to use (if form action)
    uint32_t    x ;     //  Indent counter
    if (m_Line != nLine)
    {
        C.AddByte(CHAR_NL) ;
        for (x = m_Indent ; x ; x--)
            C.AddByte(CHAR_TAB) ;
        nLine = m_Line ;
    }
    if (pE->m_pContextForm)
    {
        pFR = (hdsFormref*) pE->m_pContextForm ;
        x = m_pApp->m_FormRef2Url.First(pFR) ;
        if (x >= 0)
            url = m_pApp->m_FormRef2Url.GetObj(x + m_Resv - 1) ;
    }
    if (m_Formname)
    {
        if (url)
            C.Printf("<input type=\"submit\" formaction=\"%s\" value=\"%s\">", *url, *m_strContent) ;
        else
            C.Printf("<input type=\"submit\" value=\"%s\">", *m_strContent) ;
    }
    else
    {
        if (pE && m_Linkto[0] == CHAR_PERCENT)
            C.Printf("<input type=\"button\" value=\"%s\" onclick=\"window.location.href='%s'\">", *m_strContent, *m_pApp->ConvertText(m_Linkto, pE)) ;
        else
            C.Printf("<input type=\"button\" value=\"%s\" onclick=\"window.location.href='%s'\">", *m_strContent, *m_Linkto) ;
    }
}
void    hdsRecap::Generate  (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Aggregates to the supplied chain (the HTML body), the RECAPTCHA tag responsible for the appearence of the google 'human being' test icon. This
    //  will contain the public key which must be supplied as an argument to the <project> tag.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsRecap::Generate") ;
    if (m_pApp->m_KeyPublic)
        C.Printf("<div class=\"g-recaptcha\" data-sitekey=\"%s\"></div>", *m_pApp->m_KeyPublic) ;
    else
        C << "<div class=\"g-recaptcha\" invalid data-sitekey=\"not supplied\"></div>" ;
}
void    hdsDirlist::Generate    (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Does not in itself produce HTML but the subtags do. The complete set of subtags are called for each member of the list the hdsTable controls.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsDirlist::Generate") ;
    hzMapS  <hzString,hzDirent> byName ;    //  Directory entries by name
    hzMapM  <uint32_t,hzDirent> byDate ;    //  Directory entries by name
    hzVect  <hzDirent>      dirents ;       //  List of directories
    hzList  <hdsCol>::Iter  ci ;            //  Column iterator
    hzDirent    de ;        //  Directory entry
    hzXDate     date ;      //  Last mod date
    hdsCol      col ;       //  Column
    hzAtom      atom ;      //  Current value
    hdsVE*      pVE ;       //  Visible entitiy
    hzString    apath ;     //  Translated path (absolute so includes doc root)
    hzString    rpath ;     //  Translated path (relative to doc root)
    hzString    crit ;      //  Resolved criteria
    uint32_t    nDirs ;     //  Number of directories
    uint32_t    nFiles ;    //  Number of files
    uint32_t    x ;         //  Table controller
    uint32_t    y ;         //  Table controller
    //  Do the heading
    C.AddByte(CHAR_NL) ;
    for (x = m_Indent ; x ; x--)
        C.AddByte(CHAR_TAB) ;
    //  Obtain resolved parmeters
    rpath = m_pApp->ConvertText(m_Directory, pE) ;
    apath = m_pApp->m_Docroot + rpath ;
    crit = m_pApp->ConvertText(m_Criteria, pE) ;
    //  Provide filelist matching the criteria
    ReadDir(dirents, *apath, *crit) ;
    if (!dirents.Count())
    {
        //  Print the ifnone tags
        for (pVE = m_pNone ; pVE ; pVE = pVE->Sibling())
            pVE->Generate(C, pE, nLine) ;
    }
    else
    {
        //  Sort directory entries
        for (nDirs = nFiles = x = 0 ; x < dirents.Count() ; x++)
        {
            de = dirents[x] ;
            if (de.IsDir())
                nDirs++ ;
            else
                nFiles++ ;
            if (m_Order == 1 || m_Order == 2)
                byName.Insert(de.strName(), de) ;
            if (m_Order == 3 || m_Order == 4)
                byDate.Insert(de.Mtime(), de) ;
        }
        if (m_Order)
        {
            //  Now place back in array
            if (m_Order == 1)
                for (x = 0 ; x < dirents.Count() ; x++)
                    dirents[x] = byName.GetObj(x) ;
            if (m_Order == 3)
                for (x = 0 ; x < dirents.Count() ; x++)
                    dirents[x] = byDate.GetObj(x) ;
            if (m_Order == 2)
                for (y = dirents.Count() - 1, x = 0 ; x < dirents.Count() ; y--, x++)
                    dirents[x] = byName.GetObj(y) ;
            if (m_Order == 4)
                for (y = dirents.Count() - 1, x = 0 ; x < dirents.Count() ; y--, x++)
                    dirents[x] = byDate.GetObj(y) ;
        }
            
        //  Create HTML table for listing
        C.Printf("<table width=\"%d\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"%s\">\n", m_Width, *m_CSS) ;
        C.Printf("<tr valign=\"top\" height=\"20\"><td>Listing %d directories and %d files</td></tr>\n", nDirs, nFiles) ;
        C.Printf("<tr valign=\"top\" height=\"%d\">\n\t<td>\n", m_Height) ;
        C.Printf("\t<table width=\"%d\" align=\"center\" border=\"1\" cellspacing=\"1\" cellpadding=\"1\" class=\"%s\">\n", m_Width, *m_CSS) ;
        //  Do table headers
        C << "\t<tr>\n" ;
        for (ci = m_Cols ; ci.Valid() ; ci++)
        {
            col = ci.Element() ;
            C.Printf("\t<th width=\"%d\">%s</th>\n", col.m_nSize, *col.m_Title) ;
        }
        C << "\t</tr>\n" ;
        //  Do first rows as parent directory
        if (pE->m_Resarg)
        {
            C << "\t<tr>" ;
            for (ci = m_Cols ; ci.Valid() ; ci++)
            {
                col = ci.Element() ;
                if (col.m_Title == "Mtime")
                    C << "<td>---</td>" ;
                else if (col.m_Title == "Type")
                    C << "<td>DIR</td>" ;
                else if (col.m_Title == "Size")
                    C << "<td>---</td>" ;
                else if (col.m_Title == "Name")
                    C.Printf("<td><a href=\"%s\">Back to parent dir</a></td>", *m_Directory) ;
                else
                    C << "<td> </td>" ;
            }
            C << "\t</tr>\n" ;
        }
        //  Do table rows (directories)
        for (x = 0 ; x < dirents.Count() ; x++)
        {
            de = dirents[x] ;
            if (!de.IsDir())
                continue ;
            C << "\t<tr>" ;
            for (ci = m_Cols ; ci.Valid() ; ci++)
            {
                col = ci.Element() ;
                if      (col.m_Title == "Mtime")    { date.SetByEpoch(de.Mtime()) ; C.Printf("<td>%s</td>", *date) ; }
                else if (col.m_Title == "Type")     C << "<td>DIR</td>" ;
                else if (col.m_Title == "Size")     C.Printf("<td alight=\"right\">%s</td>", FormalNumber(de.Size())) ;
                else if (col.m_Title == "Name")     C.Printf("<td><a href=\"%s-%s\"> %s</a></td>", *m_Directory, de.txtName(), de.txtName()) ;
                else
                    C << "<td> </td>" ;
            }
            C << "\t</tr>\n" ;
        }
        //  Do table rows (files)
        for (x = 0 ; x < dirents.Count() ; x++)
        {
            de = dirents[x] ;
            if (de.IsDir())
                continue ;
            C << "\t<tr>" ;
            for (ci = m_Cols ; ci.Valid() ; ci++)
            {
                col = ci.Element() ;
                if (col.m_Title == "Mtime")
                    { date.SetByEpoch(de.Mtime()) ; C.Printf("<td>%s</td>", *date) ; }
                else if (col.m_Title == "Type")
                    C << "<td>File</td>" ;
                else if (col.m_Title == "Size")
                    C.Printf("<td alight=\"right\">%s</td>", FormalNumber(de.Size())) ;
                else if (col.m_Title == "Name")
                {
                    if (pE->m_Resarg)
                        C.Printf("<td><a href=\"/userdir/%s/%s\"> %s</a></td>", *pE->m_Resarg, de.txtName(), de.txtName()) ;
                    else
                        C.Printf("<td><a href=\"/userdir/%s\"> %s</a></td>", de.txtName(), de.txtName()) ;
                }
                else
                    C << "<td> </td>" ;
            }
            C << "\t</tr>\n" ;
        }
        C << "\t</table>\n</table>\n" ;
    }
}
void    hdsTable::Generate  (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Does not in itself produce HTML but the subtags do. The complete set of subtags are called for each member of the list the hdsTable controls.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsTable::Generate") ;
    hzList<hdsCol>::Iter    ci ;        //  Column iterator
    hzVect<uint32_t>        items ;     //  Items drawn from class member (of table) to be listed as rows
    hdbObject   obj ;                   //  Retrieved data object
    hdbIdset    srchResult ;            //  Items selected from applicable repository
    hdsCol      col ;                   //  Column
    hdsVE*      pVE ;                   //  For processing subtags
    hzAtom      atom ;                  //  Current value
    hzString    value ;                 //  Value in session if set
    uint32_t    nFound ;                //  Table controller
    uint32_t    objId ;                 //  Object id
    hzEcode     rc ;                    //  Return code from Fetch()
    //  Do the heading
    C.AddByte(CHAR_NL) ;
    //  Do the select
    if (m_Criteria == "all")
        nFound = m_pRepos->Count() ;
    else
    {
        m_pRepos->Select(srchResult, *m_Criteria) ;
        nFound = srchResult.Count() ;
    }
    //  Display results
    if (nFound == 0)
    {
        //  In the event of no objects found, print the ifnone tags
        for (pVE = m_pNone ; pVE ; pVE = pVE->Sibling())
            pVE->Generate(C, pE, nLine) ;
    }
    else
    {
        //  Create HTML table for listing
        C.Printf("<table width=\"%d\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"%s\">\n", m_nWidth, *m_CSS) ;
        C.Printf("<tr valign=\"top\" height=\"20\"><td>Listing %d objects</td></tr>\n", nFound) ;
        C.Printf("<tr valign=\"top\" height=\"%d\">\n\t<td>\n", m_nHeight) ;
        C.Printf("\t<table width=\"%d\" align=\"center\" border=\"1\" cellspacing=\"1\" cellpadding=\"1\" class=\"%s\">\n", m_nWidth, *m_CSS) ;
        //  Do table headers
        C << "\t<tr>\n" ;
        for (ci = m_Cols ; ci.Valid() ; ci++)
        {
            col = ci.Element() ;
            C.Printf("\t<th width=\"%d\">%s</th>\n", col.m_nSize, *col.m_Title) ;
        }
        C << "\t</tr>\n" ;
        //  Provide filelist matching the criteria
        obj.Init(m_pRepos->Class()) ;
        for (objId = 1 ; objId <= nFound ; objId++)
        {
            //  Fetch data object
            rc = m_pRepos->Fetch(obj, objId) ;
            if (rc != E_OK)
                break ;
            //  Now display object as row
            C << "\t<tr>" ;
            for (ci = m_Cols ; ci.Valid() ; ci++)
            {
                col = ci.Element() ;
                //m_pRepos->Fetchval(atom, col.m_mbrNo, objId) ;
                obj.GetValue(atom, m_pRepos->Class()->GetMember(col.m_mbrNo)) ;
                if (atom.IsSet())
                    value = atom.Str() ;
                else
                    value.Clear() ;
                if (col.m_nSize)
                    C.Printf("<td width=\"%d\">%s</td>", col.m_nSize, *value) ; //atom.Str()) ;
                else
                    C.Printf("<td>%s</td>", *value) ;   //atom.Str()) ;
            }
            C << "</tr>\n" ;
        }
        C << "</table>\n" ;
    }
}
void    hdsField::Generate  (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Aggregates to the supplied chain, HTML code for presenting an input field as part of a form. The resulting field will be unpopulated unless a
    //  data source is specified as an attribute to <xfield> and only then if that source successfully evaluates.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsField::Generate") ;
    hzAtom          atom ;      //  For fetching field value from db
    hdbEnum*        pSlct ;     //  Selector
    const char*     pDictStr ;  //  String from dictionary string number
    hzString        value ;     //  Value in session if set
    hzString        S ;         //  Temp string
    uint32_t        rows ;      //  Selector rows for radio
    uint32_t        n ;         //  Selector items iterator
    hzEcode         rc ;        //  Return code
    if (pE && m_Source)
    {
        rc = m_pApp->PcEntConv(atom, m_Source, pE) ;
        if (atom.IsSet())
            value = atom.Str() ;
    }
    switch  (m_Fldspec.htype)
    {
    case HTMLTYPE_HIDDEN:       //  Insert hidden value
        C.Printf("<input type=\"hidden\" name=\"%s\" value=\"%s\"/>", *m_Varname, *value) ; //atom.Str()) ;
        break ;
    case HTMLTYPE_SELECT:       //  Write out the selector with already selected options marked as selected
        pSlct = (hdbEnum*) m_Fldspec.m_pType ;
        if (m_flagVE & VE_MULTIPLE)
            C.Printf("<select name=\"%s\" multiple>", *m_Varname) ;
        else
            C.Printf("<select name=\"%s\">", *m_Varname) ;
        for (n = 0 ; n < pSlct->Count() ; n++)
        {
            S = pSlct->GetStr(n) ;
            //strNo = pSlct->m_Items[n] ;
            //pDictStr = _hzGlobal_setStrings->Xlate(strNo) ;
            if (value == pDictStr)
                C.Printf("<option selected>%s</option>", *S) ;
            else
                C.Printf("<option>%s</option>", *S) ;
        }
        C << "</select>" ;
        break ;
    case HTMLTYPE_RADIO:
        //  Write out the selector as a radio button set. The seletor cannot be  multiple but the number of colums must be specified. The radio set will appear
        //  within a <table> in which each column except the last, will contain N items. N is calculated as the total selector population divided by the number
        //  of columns, then plus one. The last column will contain the remainder. The items will be displayed in the order they appear in the selector but are
        //  used to fill each column before moving on to the next. The columns are from left to right.
        pSlct = (hdbEnum*) m_Fldspec.m_pType ;
        rows = (pSlct->Count() / m_Fldspec.nCols) + 1 ;
        C << "<table>\n" ;
        C << "\t<tr>\n" ;
        C << "\t\t<td>\n" ;
        for (n = 0 ; n < pSlct->Count() ; n++)
        {
            S = pSlct->GetStr(n) ;
            if (n && (n % rows) == 0)
            {
                C.Printf("<input type=\"radio\" name=\"%s\" value=\"%d\">%s\n", *m_Varname, n, *S) ;
                C << "\t\t<td>\n\t\t</td>\n" ;
                continue ;
            }
            C.Printf("<input type=\"radio\" name=\"%s\" value=\"%d\">%s<br>\n", *m_Varname, n, *S) ;
        }
        C << "\t\t</td>\n" ;
        C << "\t</tr>\n" ;
        C << "</table>\n" ;
        break ;
    case HTMLTYPE_CHECKBOX: if (value)
                                C.Printf("<input type=\"checkbox\" name=\"%s\" checked>", *m_Varname) ;
                            else
                                C.Printf("<input type=\"checkbox\" name=\"%s\">", *m_Varname) ;
                            break ;
    case HTMLTYPE_FILE:     C.Printf("<input type=\"file\" name=\"%s\" value=\"%s\">", *m_Varname, *value) ;
                            break ;
    case HTMLTYPE_TEXT:
    case HTMLTYPE_TEXTAREA:
        if (m_Fldspec.nRows <= 1)
        {
            if (m_flagVE & VE_UNIQUE)
                C.Printf("<input type=\"text\" name=\"%s\" size=\"%d\" maxlength=\"%d\" onchange=\"ckUnique_%s()\"",
                    *m_Varname, m_Fldspec.nCols, m_Fldspec.nSize, *m_Varname) ;
            else
                C.Printf("<input type=\"text\" name=\"%s\" size=\"%d\" maxlength=\"%d\"", *m_Varname, m_Fldspec.nCols, m_Fldspec.nSize) ;
            if (m_CSS)
                C.Printf(" class=\"%s\"", *m_CSS) ;
            if (value)
                C.Printf(" value=\"%s\"", *value) ;
            if (m_flagVE & VE_DISABLED)
                C << " disabled" ;
            C << "/>" ;
        }
        else
        {
            C.Printf("<textarea name=\"%s\" rows=\"%d\" cols=\"%d\" maxlength=\"%d\"", *m_Varname, m_Fldspec.nRows, m_Fldspec.nCols, m_Fldspec.nSize) ;
            if (m_CSS)
                C.Printf(" class=\"%s\"", *m_CSS) ;
            C.AddByte(CHAR_MORE) ;
            if (value)
                C << value ;
            C << "</textarea>" ;
        }
        break ;
    case HTMLTYPE_PASSWORD:
        C.Printf("<input type=\"password\" name=\"%s\" size=\"%d\" maxlength=\"%d\"", *m_Varname, m_Fldspec.nCols, m_Fldspec.nSize) ;
        if (m_CSS)
            C.Printf(" class=\"%s\"", *m_CSS) ;
        if (value)
            C.Printf(" value=\"%s\"", *value) ;
        C << "/>" ;
        break ;
    default:
        C.Printf("Sorry: HTML Unknown type. FldDesc=%s Name=%s Type=%s htype=%d Rows=%d Cols=%d Size=%d",
            *m_Fldspec.m_Refname, *m_Varname, m_Fldspec.m_pType->txtType(), m_Fldspec.htype, m_Fldspec.nRows, m_Fldspec.nCols, m_Fldspec.nSize) ;
        break ;
    }
}
void    hdsFormref::Generate    (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Aggregates to the supplied chain (the HTML body), the HTML code for presenting a form. This will present the whole form so everything from the
    //  opening <form> tag to the closing </form> antitag.
    //
    //  The HTML for the form is actually vested with the form definition so generating HTML for a form reference is a matter of ....
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsFormref::Generate") ;
    hzList<hdsVE*>::Iter    ih ;    //  Html entity iterator
    hdsFormdef*     pFormdef ;      //  Actual form referenced
    hdsVE*          pVE ;           //  Html entity
    hzString        url ;           //  URL from hdsApp map m_FormRef2Url
    uint32_t        nV ;            //  Visual entity iterator
    uint32_t        relLn ;         //  Relative line
    uint32_t        x ;             //  Tab controller
    if (m_Line != nLine)
    {
        C.AddByte(CHAR_NL) ;
        for (x = m_Indent ; x ; x--)
            C.AddByte(CHAR_TAB) ;
        nLine = m_Line ;
    }
    pFormdef = m_pApp->m_FormDefs[m_Formname] ;
    if (!pFormdef)
    {
        C.Printf("<p>ERROR: NULL FORM DEF</p>\n") ;
        return ;
    }
    if (pFormdef->m_nActions == 1)
    {
        x = m_pApp->m_FormRef2Url.First(this) ;
        url = m_pApp->m_FormRef2Url.GetObj(x) ;
        if (m_flagVE & VE_MULTIPART)
            C.Printf("<form name=\"%s\" method=\"POST\" action=\"%s\" onsubmit=\"return ck%s()\" enctype=\"multipart/form-data\">\n",
                *m_Formname, *url, *m_Formname) ;
        else
            C.Printf("<form name=\"%s\" method=\"POST\" action=\"%s\" onsubmit=\"return ck%s()\">\n", *m_Formname, *url, *m_Formname) ;
    }
    else
    {
        if (m_flagVE & VE_MULTIPART)
            C.Printf("<form name=\"%s\" method=\"POST\" enctype=\"multipart/form-data\">\n",
                *m_Formname, *url, *url) ;
        else
            C.Printf("<form name=\"%s\" method=\"POST\">\n", *m_Formname, *url, *url) ;
    }
    relLn = nLine ;
    pE->m_pContextForm = this ;
    for (nV = 0 ; nV < pFormdef->m_VEs.Count() ; nV++)
    {
        pVE = pFormdef->m_VEs[nV] ; pVE->Generate(C, pE, relLn) ;
    }
    if (nLine != m_Line)
    {
        C.AddByte(CHAR_NL) ;
        for (x = m_Indent ; x ; x--)
            C.AddByte(CHAR_TAB) ;
    }
    C << "</form>\n" ;
}
void    hdsPage::WriteValidationJS  (void)
{
    //  This is run once upon startup and applies to each page in which there is one or more forms. It Creates the JavaScript that must be run to pre
    //  validate form content before submission to the server.
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hdsPage::WriteValidationJS") ;
    hzList<hdsFormref*>::Iter   fi ;    //  Form iterator
    hzChain         Z ;                 //  For building page validation script(s)
    hdsFormref*     pFormref ;          //  Form in page
    hdsFormdef*     pFormdef ;          //  Form in page
    hdsField*       pFld ;              //  Field in form
    uint32_t        n ;                 //  Field iterator
    //  Check for pre-submission requirements. This is where one or more fields can only be validated by querying the server. For example in a form to
    //  register a new user, the email address must be new. The 'onchange' event is used to check on the server for the existance of the field class,
    //  member and value.
    _hzGlobal_Dissemino->m_pLog->Log("DOING PAGE %s\n", *m_Title) ;
    for (fi = m_xForms ; fi.Valid() ; fi++)
    {
        pFormref = fi.Element() ;
        m_pApp->m_pLog->Log("DOING FORM REF %p\n", pFormref) ;
        if (!pFormref)
            { _hzGlobal_Dissemino->m_pLog->Log("ERROR Page %s has a NULL form reference\n", *m_Title) ; continue ; }
        //pFormdef = pFormref->m_pFormdef ;
        pFormdef = m_pApp->m_FormDefs[pFormref->m_Formname] ;
        if (!pFormdef)
            { m_pApp->m_pLog->Log("ERROR Page %s has a NULL form definition\n", *m_Title) ; continue ; }
        for (n = 0 ; n < pFormdef->m_vecFlds.Count() ; n++)
        {
            pFld = pFormdef->m_vecFlds[n] ;
            if (!pFld->m_Varname)
                continue ;
            if (!(pFld->m_flagVE & VE_UNIQUE))
                continue ;
            if (!pFld->m_pClass || !pFld->m_pMem)
                continue ;
            Z.Printf("function ckUnique_%s()\n{\n", *pFld->m_Varname) ;
            Z.Printf("\tif (ckExists('%s','%s',document.%s.%s.value))\n", pFld->m_pClass->txtType(), pFld->m_pMem->txtName(), *pFormdef->m_Formname, *pFld->m_Varname) ;
            Z << "\t{\n" ;
            Z.Printf("\t\talert(\"%s already in use\");\n", *pFld->m_Varname) ;
            Z.Printf("\t\tdocument.%s.%s.value=\"\";\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
            Z.Printf("\t\tdocument.%s.%s.focus();\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
            Z << "\t}\n}\n" ;
            m_bScriptFlags |= INC_SCRIPT_EXISTS ;
        }
        //  Now do the ordinary format checking validation scripting
        Z.Printf("function ck%s()\n{\n", *pFormdef->m_Formname) ;
        for (n = 0 ; n < pFormdef->m_vecFlds.Count() ; n++)
        {
            pFld = pFormdef->m_vecFlds[n] ;
            if (!pFld->m_Varname)
                continue ;
            if (!(pFld->m_flagVE & VE_COMPULSORY))
                continue ;
            if (pFld->m_Fldspec.m_pType->Basetype() == BASETYPE_ENUM)
                Z.Printf("\tif (document.%s.%s.value==\"0\")\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
            else
                Z.Printf("\tif (document.%s.%s.value==\"\")\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
            Z.Printf("\t\t{ alert(\"Please provide your %s\"); document.%s.%s.focus(); return false; }\n", *pFld->m_Varname, *pFormdef->m_Formname, *pFld->m_Varname) ;
            if (pFld->m_Fldspec.m_pType->Basetype() == BASETYPE_EMADDR)
            {
                Z.Printf("\tif (!ckEmaddr(document.%s.%s.value))\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
                Z.Printf("\t\t{ document.%s.%s.focus(); return false }\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
                m_bScriptFlags |= INC_SCRIPT_CKEMAIL ;
            }
            if (pFld->m_Fldspec.m_pType->Basetype() == BASETYPE_URL)
            {
                Z.Printf("\tif (!ckUrl(document.%s.%s.value))\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
                Z.Printf("\t\t{ document.%s.%s.focus(); return false }\n", *pFormdef->m_Formname, *pFld->m_Varname) ;
                m_bScriptFlags |= INC_SCRIPT_CKURL ;
            }
        }
        if (pFormdef->m_bScriptFlags & INC_SCRIPT_RECAPTCHA)
        {
            Z.Printf("\tif (grecaptcha.getResponse()==\"\")\n") ;
            Z.Printf("\t\t{ alert(\"Please prove you are human!\"); return false; }\n") ;
        }
        Z.Printf("\tdocument.%s.submit();\n\treturn true\n}\n", *pFormdef->m_Formname) ;
    }
    m_validateJS = Z ;
}
/*
**  Grapics
*/
void    hdsGraphic::Draw    (hzChain& Z)
{
    //  Draw shape
    _hzfunc("hdsGraphic::Draw") ;
    threadLog("ok .. \n") ;
    switch  (m_eShape)
    {
    case HDSGRAPH_DIAMOND:      Z.Printf("<polygon points=\"%u,%u %u,%u %u,%u %u,%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u\"/>\n",
                                    lftMidptX(), lftMidptY(),
                                    topMidptX(), topMidptY(),
                                    rhtMidptX(), rhtMidptY(),
                                    botMidptX(), botMidptY(),
                                    m_ColorFill, m_ColorLine, m_Thick) ;
                                break ;
    case HDSGRAPH_HEXAGON:      Z.Printf("<polygon points=\"%u,%u %u,%u %u,%u %u,%u, %u,%u, %u,%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u\"/>\n",
                                    m_Lft, m_Top + (m_Height/2),
                                    m_Lft + (m_Height/2), m_Top,
                                    m_Rht - (m_Height/2), m_Top,
                                    m_Rht, m_Top + (m_Height/2),
                                    m_Rht - (m_Height/2), m_Bot,
                                    m_Lft + (m_Height/2), m_Bot,
                                    m_ColorFill, m_ColorLine, m_Thick) ;
                                break ;
    case HDSGRAPH_ARROW:        Z.Printf("<polygon points=\"%u,%u %u,%u %u,%u %u,%u %u,%u %u,%u %u,%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u\"/>\n",
                                    m_Lft, m_Top + m_Height/3,
                                    topMidptX(), m_Top + m_Height/3,
                                    topMidptX(), m_Top,
                                    rhtMidptX(), rhtMidptY(),
                                    botMidptX(), botMidptY(),
                                    botMidptX(), m_Bot - m_Height/3,
                                    m_Lft, m_Bot - m_Height/3,
                                    m_ColorFill, m_ColorLine, m_Thick) ;
                                break ;
    case HDSGRAPH_RECT:         Z.Printf("<rect x=\"%u\" y=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u;\"/>\n",
                                    m_Lft, m_Top, m_Width, m_Height, m_ColorFill, m_ColorLine, m_Thick) ;
                                break ;
    case HDSGRAPH_VRECT:        break ;
    case HDSGRAPH_HRECT:        break ;
    case HDSGRAPH_RRECT:        Z.Printf("<rect x=\"%u\" y=\"%u\" rx=\"%u\" ry=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u;\"/>\n",
                                    m_Lft, m_Top, m_Rad, m_Rad, m_Width, m_Height, m_ColorFill, m_ColorLine, m_Thick) ;
                                break ;
    case HDSGRAPH_STADIUM:      Z.Printf("<rect x=\"%u\" y=\"%u\" rx=\"%u\" ry=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u;\"/>\n",
                                    m_Lft, m_Top, m_Height/2, m_Height/2, m_Width, m_Height, m_ColorFill, m_ColorLine, m_Thick) ;
                                break ;
    case HDSGRAPH_CIRCLE:       Z.Printf("<circle cx=\"%u\" cy=\"%u\" r=\"%u\" stroke=\"#%06x\" stroke-width=\"%u\" fill=\"#%06x\"/>\n",
                                    m_Lft + (m_Width/2), m_Top + (m_Height/2), m_Rad, m_ColorFill, m_ColorLine, m_Thick) ;
                                break ;
    case HDSGRAPH_LGATE_AND:    break ;
    case HDSGRAPH_LGATE_OR:     break ;
    case HDSGRAPH_LGATE_NOT:    break ;
    case HDSGRAPH_LGATE_NAND:   break ;
    case HDSGRAPH_LGATE_NOR:    break ;
    }
}
void    _hds_svg_drawTriangle   (hzChain& Z, uint32_t aX, uint32_t aY, uint32_t bX, uint32_t bY, uint32_t cX, uint32_t cY, uint32_t color)
{
    _hzfunc(__func__) ;
    Z.Printf("<polygon points=\"%u,%u %u,%u %u,%u\" style=\"fill:#%06x;\"/>\n", aX, aY, bX, bY, cX, cY, color) ;
}
void    _hds_svg_drawLine   (hzChain& Z, hdsLine& ln)
{
    _hzfunc(__func__) ;
    Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n",
        ln.m_startH, ln.m_startV, ln.m_finalH, ln.m_finalV, ln.m_Color, ln.m_Thick) ;
}
void    _hds_svg_drawLine   (hzChain& Z, uint32_t aX, uint32_t aY, uint32_t bX, uint32_t bY, uint32_t color, uint32_t width)
{
    _hzfunc(__func__) ;
    Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", aX, aY, bX, bY, color, width) ;
}
void    _hds_svg_drawText   (hzChain& Z, hzString& text, uint32_t H, uint32_t V, uint32_t color, uint16_t alignCode)
{
    //  Align code 1 center, 2 right else left
    _hzfunc(__func__) ;
    if (!text)
        return ;
    if (alignCode == 1)
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\" dominant-baseline=\"top\" text-anchor=\"middle\">%s</text>\n", H, V, color, *text) ;
    else if (alignCode == 2)
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\" dominant-baseline=\"top\" text-anchor=\"end\">%s</text>\n", H, V, color, *text) ;
    else
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\" dominant-baseline=\"top\">%s</text>\n", H, V, color, *text) ;
}
void    _hds_svg_drawMLText (hzChain& Z, hzString& text, uint32_t X, uint32_t Y, uint32_t color, uint32_t id)
{
    //  Multi-line Text
    _hzfunc(__func__) ;
    const char* i ;     //  Text iterator
    Z.Printf("<text fill=\"#%06x\" dominant-baseline=\"top\">\n", color) ;
    Z.Printf("\t<tspan x=\"%u\" dy=\"%u\">%d:", X, Y, id) ;
    for (i = *text ; *i ; i++)
    {
        if (*i == CHAR_NL)
        {
            Y += 20 ;
            Z << "\t</tspan>\n" ;
            Z.Printf("\t<tspan x=\"%u\" y=\"%u\">", X, Y) ;
            continue ;
        }
        Z.AddByte(*i) ;
    }
    Z << "\t</tspan>\n" ;
    Z << "</text>\n" ;
}
void    hdsChartPie::Generate   (hzChain& Z, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Category:   HTML Generation
    //
    //  Displays a pie chart by means of the <svg> tag and associated javascript.
    //
    //  Arguments:  1)  Z       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsChartPie::Generate") ;
    _pie*       pSlice ;        //  Pie value
    double      val ;           //  Current segment value
    double      sofar ;         //  Total value of all segments so far processed
    double      deg ;           //  Angle from top
    double      ang ;           //  Angle in radians
    uint32_t    X ;             //  Destination horizontal coord
    uint32_t    Y ;             //  Destination vertical coord
    uint32_t    last_y = 0 ;    //  y-coord
    uint32_t    last_x = 0 ;    //  x-coord
    uint32_t    n ;             //  Parts iterator
    uint32_t    bSmall ;        //  Short arc indicator
    //char buf[200] ;
    //  Open svg tag
    Z.Printf("\n<svg id=\"%s\" height=\"%d\" width=\"%d\" style=\"background:#%06x;\">", *m_Id, m_Height, m_Width, m_BgColor) ;
    //  Draw header and footer if applicable
    if (m_Header)
        _hds_svg_drawText(Z, m_Header, m_Width/2, 20, m_FgColor, 1) ;
    //  Work out what percentage of the total, each part represents
    last_x = m_ccX ;
    last_y = m_ccY - m_Rad ;
    sofar = val = 0.0 ;
    for (n = 0 ; n < m_nSlices ; n++)
    {
        pSlice = m_pSlices + n ;
        bSmall = pSlice->m_nValue/m_Total >= 0.5 ? 1 : 0 ;
        Z << "\t<path d=\"" ;
        if ((n+1) == m_nSlices)
        {
            X = m_ccX ;
            Y = m_ccY - m_Rad ;
            Z.Printf("M %u %u L %u %u A %u %u 0 %d 1 %u %u Z\" fill=\"#%06x\"/>\n", m_ccX, m_ccY, last_x, last_y, m_Rad, m_Rad, bSmall, X, Y, pSlice->color) ;
        }
        else
        {
            sofar += pSlice->m_nValue ;
            deg = (360 * sofar)/m_Total ;
            ang = (deg * M_PI)/180 ;
            X = m_ccX + (m_Rad * sin(ang)) ;
            Y = m_ccY - (m_Rad * cos(ang)) ;
            Z.Printf("M %u %u L %u %u A %u %u 0 %d 1 %u %u Z\" fill=\"#%06x\"/>\n", m_ccX, m_ccY, last_x, last_y, m_Rad, m_Rad, bSmall, X, Y, pSlice->color) ;
            last_x = X ;
            last_y = Y ;
        }
    }
    //  Write index
    X = m_ccX + m_Rad + 40 ;
    Y = m_ccY - m_Rad ;
    for (n = 0 ; n < m_nSlices ; n++)
    {
        pSlice = m_pSlices + n ;
        Z.Printf("<rect x=\"%u\" y=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:1\"/>\n",
            X, Y, 12, 12, pSlice->color, m_FgColor, 1) ;
        _hds_svg_drawText(Z, pSlice->header, X+15, Y+12, m_FgColor, 0) ;
        Y += 20 ;
    }
    if (m_Footer)
        _hds_svg_drawText(Z, m_Footer, m_Width/2, m_Height - 20, m_FgColor, 1) ;
    Z << "</svg>\n" ;
}
void    hdsChartStd::Generate   (hzChain& Z, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Category:   HTML Generation
    //
    //  Displays a chart by placing in the page HTML, a <svg> tag.
    //
    //  Arguments:  1)  Z       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsChartStd::Generate") ;
    hzList<_rset*>::Iter    ri ;    //  Dataset iterator
    _rset*      pSet ;              //  Dataset
    double      per_pixel_X ;       //  For ploting x
    double      per_pixel_Y ;       //  For ploting y
    double      val ;               //  Current value
    double      stepSize ;          //  Axis value per step
    uint32_t    X ;                 //  Current horizontal coord
    uint32_t    Y ;                 //  Current vertical coord
    uint16_t    axisPixelsX ;       //  Total pixels on V-axis
    uint16_t    axisPixelsY ;       //  Total pixels on V-axis
    uint32_t    n ;                 //  Loop iterator
    /*
    **  Start draw instrutions
    */
    Z.Printf("<svg id=\"%s\" height=\"%d\" width=\"%d\" style=\"border:1px solid #000000; background:#%06x;\">", *m_Id, m_Height, m_Width, m_BgColor) ;
    //  Draw header and footer if applicable
    if (m_Header)
        _hds_svg_drawText(Z, m_Header, m_Width/2, 20, m_FgColor, 1) ;
    //  Calculate pixels
    axisPixelsY = m_nSlotsY * m_nPxSlotY ;
    axisPixelsX = m_nSlotsX * m_nPxSlotX ;
    per_pixel_Y = (m_nMaxY - m_nMinY)/axisPixelsY ;
    per_pixel_X = (m_nMaxX - m_nMinX)/axisPixelsX ;
    //  Draw vertical and horizontal axis
    Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:1\"/>\n", m_origX, m_origY - axisPixelsY, m_origX, m_origY, m_FgColor) ;
    Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:1\"/>\n", m_origX, m_origY, m_origX + axisPixelsX, m_origY, m_FgColor) ;
    //  Draw out vertical axis heading and markers
    X = 20 ;
    Y = 30 ;
    Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%s</text>\n", X, Y, m_FgColor, *m_HdrY) ;
    stepSize = (m_nMaxY - m_nMinY)/m_nSlotsY ;
    Y = m_origY ;
    for (val = m_nMinY ; val <= m_nMaxY ; val += stepSize)
    {
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%u</text>\n", X, Y, m_FgColor, (uint32_t) val) ;
        Y -= m_nPxSlotY ;
    }
    //  Draw out horizontal axis heading and markers
    stepSize = (m_nMaxX - m_nMinX)/m_nSlotsX ;
    Y = m_origY + 20 ;
    X = m_origX ;
    for (val = m_nMinX ; val <= m_nMaxX ; val += stepSize)
    {
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%u</text>\n", X, Y, m_FgColor, (uint32_t) val) ;
        X += m_nPxSlotX ;
    }
    //  Plot values
    for (ri = m_Sets ; ri.Valid() ; ri++)
    {
        pSet = ri.Element() ;
        Z << "<polyline points=\"" ;
        X = m_origX + ((m_hVals[0] - m_nMinX)/per_pixel_X) ;
        Y = m_origY - ((pSet->m_vVals[0] - m_nMinY)/per_pixel_Y) ;
        Z.Printf("%u,%u", X, Y) ;
        for (n = 1 ; n <= m_nSlotsX ; n++)
        {
            X = m_origX + ((m_hVals[n] - m_nMinX)/per_pixel_X) ;
            Y = m_origY - ((pSet->m_vVals[n] - m_nMinY)/per_pixel_Y) ;
            Z.Printf(" %u,%u", X, Y) ;
        }
        Z.Printf("\" style=\"fill:none;stroke:#%06x;stroke-width:1\"/>\n", pSet->color) ;
    }
    //  Do the component index if applicable
    Y = m_origY + 40 ;
    if (m_Sets.Count() > 1)
    {
        X = m_origX ;
        for (ri = m_Sets ; ri.Valid() ; ri++)
        {
            pSet = ri.Element() ;
            Z.Printf("<rect x=\"%u\" y=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;\"/>\n", X, Y, 12, 12, pSet->color) ;
            Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%s</text>\n", X+15, Y+12, m_FgColor, *pSet->header) ;
            X += 10 * (pSet->header.Length()) ;
        }
        Y += 20 ;
    }
    if (m_Footer)
        _hds_svg_drawText(Z, m_Footer, m_Width/2, Y, m_FgColor, 1) ;
    Z
    << "Your Browser does not support the HTML5 SVG tag\n"
    "</svg>\n" ;
}
void    hdsChartBar::Generate   (hzChain& Z, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Category:   HTML Generation
    //
    //  Displays a chart by placing in the page HTML, a <svg> tag.
    //
    //  Arguments:  1)  Z       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsChartBar::Generate") ;
    hzList<_rset*>::Iter    ri ;    //  Dataset iterator
    _rset*      pSet ;              //  Dataset
    //double        per_pixel_X ;       //  For ploting x
    double      per_pixel_Y ;       //  For ploting y
    double      stepSize ;          //  Axis value per step
    uint32_t    val ;               //  Current value
    uint32_t    X ;                 //  Current horizontal coord
    uint32_t    Y ;                 //  Current vertical coord
    uint16_t    axisPixelsX ;       //  Total pixels on X-axis
    uint16_t    axisPixelsY ;       //  Total pixels on Y-axis
    uint32_t    n ;                 //  Loop iterator
    /*
    **  Start draw instrutions
    */
    Z.Printf("<svg id=\"%s\" height=\"%d\" width=\"%d\" style=\"border:1px solid #000000; background:#%06x;\">", *m_Id, m_Height, m_Width, m_BgColor) ;
    //  Draw header and footer if applicable
    if (m_Header)
        _hds_svg_drawText(Z, m_Header, m_Width/2, 20, m_FgColor, 1) ;
    //  Calculate pixels
    axisPixelsX = m_nSlotsX * m_nPxSlotX ;
    axisPixelsY = m_nSlotsY * m_nPxSlotY ;
    //per_pixel_X = (m_nMaxX - m_nMinX)/axisPixelsX ;
    per_pixel_Y = (m_nMaxY - m_nMinY)/axisPixelsY ;
    //  Draw vertical and horizontal axis
    Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:1\"/>\n", m_origX, m_origY - axisPixelsY, m_origX, m_origY, m_FgColor) ;
    Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:1\"/>\n", m_origX, m_origY, m_origX + axisPixelsX, m_origY, m_FgColor) ;
    //  Draw out vertical axis heading and markers
    Y = 30 ;
    X = 20 ;
    Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%s</text>\n", X, Y, m_FgColor, *m_HdrY) ;
    stepSize = (m_nMaxY - m_nMinY)/m_nSlotsY ;
    Y = m_origY ;
    for (val = m_nMinY ; val <= m_nMaxY ; val += stepSize)
    {
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%u</text>\n", X, Y, m_FgColor, (uint32_t) val) ;
        Y -= m_nPxSlotY ;
    }
    //  Draw out horizontal axis heading and markers
    stepSize = (m_nMaxX - m_nMinX)/m_nSlotsX ;
    Y = m_origY + 20 ;
    X = m_origX ;
    for (val = m_nMinX ; val <= m_nMaxX ; val += stepSize)
    {
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%u</text>\n", X, Y, m_FgColor, (uint32_t) val) ;
        X += m_nPxSlotX ;
    }
    //  Plot values
    for (n = 0 ; n <= m_nSlotsX ; n++)
    {
        Y = m_origY ;
        X = m_origX + (n * m_nPxSlotX) + 1 ;
        for (ri = m_Sets ; ri.Valid() ; ri++)
        {
            pSet = ri.Element() ;
            val = ((pSet->m_vVals[n] - m_nMinY) * per_pixel_Y) ;
            Z.Printf("<rect x=\"%u\" y=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;\"/>\n", X, Y - val, (uint32_t)m_nPxSlotX - 1, val, pSet->color) ;
            Y -= val ;
        }
    }
    //  Do the component index if applicable
    Y = m_origY + 40 ;
    if (m_Sets.Count() > 1)
    {
        X = m_origX ;
        for (ri = m_Sets ; ri.Valid() ; ri++)
        {
            pSet = ri.Element() ;
            Z.Printf("<rect x=\"%u\" y=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;\"/>\n", X, Y, 12, 12, pSet->color) ;
            Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%s</text>\n", X+15, Y+15, m_FgColor, *pSet->header) ;
            X += 10 * (pSet->header.Length()) ;
        }
        Y += 20 ;
    }
    if (m_Footer)
        _hds_svg_drawText(Z, m_Footer, m_Width/2, Y, m_FgColor, 1) ;
    Z
    << "Your Browser does not support the HTML5 SVG tag\n"
    "</svg>\n" ;
}
/*
**  Diagrams and Flowcharts
*/
static  hzString    s_fc_conn_TRUE = "Y" ;
static  hzString    s_fc_conn_FALSE = "N" ;
void    _drawFlowConn   (hzChain& Z, hdsFlowchart* pFlow, hdsConnector* pConn)
{
    //  Flowchart support function.
    _hzfunc(__func__) ;
    hdsGraphic* pG_src ;    //  Origin shape
    hdsGraphic* pG_tgt ;    //  Target shape
    uint32_t    color ;     //  Line color
    uint32_t    lw ;        //  Line width
    uint16_t    sX ;        //  Connector source X coord
    uint16_t    sY ;        //  Connector source Y coord
    uint16_t    tX ;        //  Connector target X coord
    uint16_t    tY ;        //  Connector target Y coord
    uint16_t    mY ;        //  Connector source/target Y midpoint
    if (!pFlow)
        { threadLog("FAIL NO FLOWCHART\n") ; return ; }
    if (!pConn)
        { threadLog("FAIL NO CONNECTORS\n") ; return ; }
    if (!pFlow->m_pShapes)
        { threadLog("FAIL NO SHAPES\n") ; return ; }
    if (pConn->m_Origin == pConn->m_Target)
        { threadLog("FAIL SRC=TGT %d\n", pConn->m_Origin) ; return ; }
    if (!pConn->m_Target)
        { threadLog("FAIL SRC %d TGT %d\n", pConn->m_Origin, pConn->m_Target) ; return ; }
    threadLog("PASS SRC %d TGT %d\n", pConn->m_Origin, pConn->m_Target) ;
    color = pFlow->m_ColorLine ;
    lw = pFlow->m_nWidthConn ;
    pG_src = pFlow->m_pShapes + pConn->m_Origin ;
    pG_tgt = pFlow->m_pShapes + pConn->m_Target ;
    //threadLog("case x.2\n") ;
    if (pG_tgt->rhtMidptY() == pG_src->rhtMidptY())
    {
        //  Target graphic is vertically equal to the source target - Draw line from either the RHS of source to the LHS of target, or vice versa, then triangle arrow head
        //threadLog("case A\n") ;
        if (pG_tgt->m_Lft > pG_src->m_Rht)
        {
            threadLog("case a.1\n") ;
            sX = pG_src->rhtMidptX() ;
            sY = pG_src->rhtMidptY() ;
            tX = pG_tgt->lftMidptX() ;
            tY = pG_tgt->lftMidptY() ;
            _hds_svg_drawLine(Z, sX, sY, tX, tY, color, lw) ;
            _hds_svg_drawTriangle(Z, tX-10, tY-4, tX-10, tY+4, tX-6, tY, color) ;
        }
        else
        {
            //threadLog("case a.2\n") ;
            sX = pG_src->lftMidptX() ;
            sY = pG_src->lftMidptY() ;
            tX = pG_tgt->rhtMidptX() ;
            tY = pG_tgt->rhtMidptY() ;
            _hds_svg_drawLine(Z, sX, sY, tX, tY, color, lw) ;
            _hds_svg_drawTriangle(Z, tX+10, tY-4, tX+10, tY+4, tX+6, tY, color) ;
        }
        goto doText ;
    }
    if (pG_tgt->rhtMidptY() > pG_src->rhtMidptY())
    {
        //  Downwards flow (increasing Y). Draw one or more lines with an overall down direction. Put the triangle toward the target on the last appraoch
        //threadLog("case B\n") ;
        sX = pG_src->botMidptX() ;
        sY = pG_src->botMidptY() ;
        tX = pG_tgt->topMidptX() ;
        tY = pG_tgt->topMidptY() ;
        mY = sY + ((tY-sY)/2) ;
        threadLog("case b.1\n") ;
        if (sX == tX)
        {
            //threadLog("case b.2\n") ;
            Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", sX, sY, tX, tY, color, lw) ;
        }
        else
        {
            //  Draw short vertical line (first leg), a horizontal line and then another vertical (last approach)
            Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", sX, sY, sX, mY, color, lw) ;
            Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", sX, mY, tX, mY, color, lw) ;
            Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", tX, mY, tX, tY, color, lw) ;
        }
        _hds_svg_drawTriangle(Z, tX-4, tY-10, tX+4, tY-10, tX, tY-6, color) ;
        goto doText ;
    }
    //  Upwards flow (decreasing Y). Draw one or more lines with an overall up direction. Put the triangle toward the target on the last appraoch
    //threadLog("case C\n") ;
    sX = pG_src->topMidptX() ;
    sY = pG_src->topMidptY() ;
    tX = pG_tgt->botMidptX() ;
    tY = pG_tgt->botMidptY() ;
    mY = sY - ((tY-sY)/2) ;
    //threadLog("case c.1\n") ;
    if (sX == tX)
    {
        Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", sX, sY, tX, tY, color, lw) ;
    }
    else
    {
        //  Draw short vertical line (first leg), a horizontal line and then another vertical (last approach)
        Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", sX, sY, sX, mY, color, lw) ;
        Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", sX, mY, tX, mY, color, lw) ;
        Z.Printf("<line x1=\"%u\" y1=\"%u\" x2=\"%u\" y2=\"%u\" style=\"stroke:#%06x;stroke-width:%u\"/>\n", tX, mY, tX, tY, color, lw) ;
    }
    _hds_svg_drawTriangle(Z, tX-4, tY+10, tX+4, tY+10, tX, tY+6, color) ;
doText:
    if (pConn->m_eType)
    {
        if (pConn->m_eType & HDS_CONNECTOR_TRUE)
            _hds_svg_drawText(Z, s_fc_conn_TRUE, sX, sY, 0xff0000, 1) ;
        else
        {
            if (pConn->m_eType & HDS_CONNECTOR_FALSE)
                _hds_svg_drawText(Z, s_fc_conn_FALSE, sX, sY, 0xff0000, 1) ;
        }
    }
}
void    hdsFlowchart::Generate  (hzChain& Z, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Displays a diagram by placing in the page HTML, a <svg> tag.
    //
    //  Arguments:  1)  Z       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsFlowchart::Generate") ;
    hdsText         TX ;        //  Current text
    hdsGraphic*     pG ;        //  Current shape
    hdsConnector*   pConn ;     //  Connector
    hzString        text ;      //  For diagnostics
    uint32_t        n ;         //  Shapes and connector iterator
    threadLog("Shapes %d, Connects %d\n", m_nShapes, m_nConnects) ;
    //  Use SVG
    if (m_nBoundary)
        Z.Printf("<svg id=\"%s\" height=\"%d\" width=\"%d\" style=\"border:1px solid #000000; background:#%06x;\">\n", *m_Id, m_Height, m_Width, m_BgColor) ;
    else
        Z.Printf("<svg id=\"%s\" height=\"%d\" width=\"%d\" style=\"background:#%06x;\">\n", *m_Id, m_Height, m_Width, m_BgColor) ;
    //  Draw shapes
    for (n = 0 ; n < m_nShapes ; n++)
    {
        pG = m_pShapes + n ;
        if (!pG)
            break ;
        Z.AddByte(CHAR_TAB) ;
        switch  (pG->m_eShape)
        {
        case HDSGRAPH_HEXAGON:  Z.Printf("<polygon points=\"%u,%u %u,%u %u,%u %u,%u, %u,%u, %u,%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u\"/>\n",
                                    pG->m_Lft, pG->m_Top + (pG->m_Height/2),
                                    pG->m_Lft + (pG->m_Height/2), pG->m_Top,
                                    pG->m_Rht - (pG->m_Height/2), pG->m_Top,
                                    pG->m_Rht, pG->m_Top + (pG->m_Height/2),
                                    pG->m_Rht - (pG->m_Height/2), pG->m_Bot,
                                    pG->m_Lft + (pG->m_Height/2), pG->m_Bot,
                                    m_ColorTest, m_ColorLine, m_nWidthConn) ;
                                break ;
        case HDSGRAPH_RECT:     Z.Printf("<rect x=\"%u\" y=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u;\"/>\n",
                                    pG->m_Lft, pG->m_Top, pG->m_Width, pG->m_Height, m_ColorProc, m_ColorLine, m_nWidthConn) ;
                                break ;
        case HDSGRAPH_STADIUM:  Z.Printf("<rect x=\"%u\" y=\"%u\" rx=\"%u\" ry=\"%u\" width=\"%u\" height=\"%u\" style=\"fill:#%06x;stroke:#%06x;stroke-width:%u;\"/>\n",
                                    pG->m_Lft, pG->m_Top, pG->m_Height/2, pG->m_Height/2, pG->m_Width, pG->m_Height, m_ColorTerm, m_ColorLine, m_nWidthConn) ;
                                break ;
        }
        if (pG->m_Text)
        {
            if (pG->m_nLines == 1)
                Z.Printf("\t<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%d:%s</text>\n", pG->lftMidptX(), pG->lftMidptY(), pG->m_ColorLine, n, *pG->m_Text) ;
            else
                _hds_svg_drawMLText(Z, pG->m_Text, pG->m_Lft, pG->m_Top+20, pG->m_ColorLine, n) ;
        }
        else
        {
            Z.Printf("\t<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%d: No text</text>\n", pG->lftMidptX(), pG->lftMidptY(), pG->m_ColorLine, n) ;
        }
    }
    //  Draw connectors
    for (n = 0 ; n < m_nConnects ; n++)
    {
        pConn = m_pConn + n ;
        //if (!pConn)
        //  break ;
        //if (pConn->m_Origin && pConn->m_Target)
        //{
            //pG = m_pShapes + pConn->m_Origin ;
            //pT = m_pShapes + pConn->m_Target ;
            _drawFlowConn(Z, this, pConn) ;
        //}
    }
    Z << "</svg>\n" ;
}
void    hdsDiagram::Generate    (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Displays a diagram by placing in the page HTML, a <svg> tag. This has to refer to a JavaScript which must appear earlier in the HTML
    //
    //  Arguments:  1)  Z       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsDiagram::Generate") ;
    hzList<hdsGraphic>::Iter    gI ;        //  Graphic object iterator
    hzList<hdsLine>::Iter       lI ;        //  List iterator for lines
    hzList<hdsText>::Iter       tI ;        //  List iterator for texts
    hzChain         Z ;         //  This part of chain
    hdsText         TX ;        //  Current text
    hdsLine         LN ;        //  Current line
    hdsGraphic      gObj ;      //  Current graphic object
    //  Calculate total width and height of svg image
    for (gI = m_Shapes ; gI.Valid() ; gI++)
    {
        gObj = gI.Element() ;
    }
    //  Generate the svg tag
    Z.Printf("<svg id=\"%s\" height=\"%d\" width=\"%d\" style=\"border:1px solid #000000; background:#%06x;\">", *m_Id, m_Height, m_Width, m_BgColor) ;
    for (gI = m_Shapes ; gI.Valid() ; gI++)
    {
        gObj = gI.Element() ;
        gObj.Draw(Z) ;
    }
    //  Handle lines last as a special case
    for (lI = m_Lines ; lI.Valid() ; lI++)
    {
        LN = lI.Element() ;
        _hds_svg_drawLine(Z, LN) ;
    }
    for (tI = m_Texts ; tI.Valid() ; tI++)
    {
        TX = tI.Element() ;
        Z.Printf("<text x=\"%u\" y=\"%u\" fill=\"#%06x\">%s</text>\n", TX.V(), TX.H(), TX.Color(), TX.Text()) ;
    }
    Z << "</svg>\n" ;
    C << Z ;
    //"document.getElementById('articlecontent').addEventListener('DOMCharacterDataModified',paintJob" << m_Id << ");\n"
}
void    hdsHtag::Generate   (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Display the inactive HTML tag as part of page
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsHtag::Generate") ;
    hdsVE*      pVE ;       //  For processing subtags
    //hzFixPair pa ;        //  Tag attribute
    hzPair      pa ;        //  Tag attribute
    hzString    S ;         //  Temp string
    uint32_t    n ;         //  Tab counter
    int32_t     aLo ;       //  First attribute
    int32_t     aHi ;       //  Last attribute
    int32_t     nA ;        //  Attribute iterator
    hzHtagform  tagForm ;   //  HTML tag type
    //  Write out newline and tabs if the current tag's line is greater than the supplied line
    if (m_Line != nLine)
    {
        C.AddByte(CHAR_NL) ;
        for (n = m_Indent ; n ; n--)
            C.AddByte(CHAR_TAB) ;
        nLine = m_Line ;
    }
    //  Write out the opening tag
    C.AddByte(CHAR_LESS) ;
    C << m_Tag ;
    if (m_nAttrs)
    {
        aLo = m_pApp->m_VE_attrs.First(m_VID) ;
        if (aLo >= 0)
        {
            aHi = m_pApp->m_VE_attrs.Last(m_VID) ;
            for (nA = aLo ; nA <= aHi ; nA++)
            {
                pa = m_pApp->m_VE_attrs.GetObj(nA) ;
                C.AddByte(CHAR_SPACE) ;
                C << *pa.name ;
                C.AddByte(CHAR_EQUAL) ;
                C.AddByte(CHAR_DQUOTE) ;
                if (pE && (m_flagVE & VE_AT_ACTIVE))
                    C << m_pApp->ConvertText(*pa.value, pE) ;
                else
                    C << *pa.value ;
                C.AddByte(CHAR_DQUOTE) ;
            }
        }
    }
    C.AddByte(CHAR_MORE) ;
    //  Now for each subtag, output first the pretext (part of this tag's content) and then call Display on the subtag
    for (pVE = Children() ; pVE ; pVE = pVE->Sibling())
    {
        if (pVE->m_strPretext)
        {
            S = pVE->m_strPretext ;
            if (pE && (pVE->m_flagVE & VE_PT_ACTIVE))
                C << m_pApp->ConvertText(S, pE) ;
            else
                C << S ;
        }
        pVE->Generate(C, pE, nLine) ;
    }
    //  Now write out the content
    if (m_strContent)
    {
        //  if (pLang && m_usiContent)
        //      S = pLang->m_LangStrings[m_usiContent] ;
        //  else
            S = m_strContent ;
        if (pE && m_flagVE & VE_CT_ACTIVE)
            C << m_pApp->ConvertText(S, pE) ;
        else
            C << S ;
    }
    //  Now write the antitag
    S = *m_Tag ;
    tagForm = TagLookup(S) ;
    if (tagForm.rule != HTRULE_SINGLE)
    {
        if (nLine != m_Line)
        {
            C.AddByte(CHAR_NL) ;
            for (n = m_Indent ; n ; n--)
                C.AddByte(CHAR_TAB) ;
            nLine = m_Line ;
        }
        C.AddByte(CHAR_LESS) ;
        C.AddByte(CHAR_FWSLASH) ;
        C << m_Tag ;
        C.AddByte(CHAR_MORE) ;
    }
}
void    hdsXtag::Generate   (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  The <x> tag does not itself generate a HTML tag but instead inserts textual content into the parent tag.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsHtag::Generate") ;
    hdsVE*      pVE ;       //  For processing subtags
    hzString    S ;         //  Temp string
    uint32_t    n ;         //  Tab counter
    //  Write out newline and tabs if the current tag's line is greater than the supplied line
    if (m_Line != nLine)
    {
        C.AddByte(CHAR_NL) ;
        for (n = m_Indent ; n ; n--)
            C.AddByte(CHAR_TAB) ;
        nLine = m_Line ;
    }
    //  Now for each subtag, output first the pretext (part of this tag's content) and then call Display on the subtag
    for (pVE = Children() ; pVE ; pVE = pVE->Sibling())
    {
        if (pVE->m_strPretext)
        {
            S = pVE->m_strPretext ;
            if (pE && (pVE->m_flagVE & VE_PT_ACTIVE))
                C << m_pApp->ConvertText(S, pE) ;
            else
                C << S ;
        }
        pVE->Generate(C, pE, nLine) ;
    }
    //  Now write out the content
    if (m_strContent)
    {
        S = m_strContent ;
        if (pE && m_flagVE & VE_CT_ACTIVE)
            C << m_pApp->ConvertText(S, pE) ;
        else
            C << S ;
    }
}
void    hdsBlock::Generate  (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Aggregate to the supplied chain (the HTML body), the set of tags found within this hdsBlock instance. This is the functional equivelent of a
    //  server side include.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsBlock::Generate") ;
    hzList<hdsVE*>::Iter    ih ;    //  Visible entity iterator
    hdsVE*      pVE ;       //  Child visible entity
    uint32_t    relLn ;     //  Relative line
    uint32_t    nV ;        //  Visual entity iterator
    m_pApp->m_pLog->Log("BK %p %d %s\n", this, m_VID, *m_Refname) ;
    relLn = nLine ;
    for (nV = 0 ; nV < m_VEs.Count() ; nV++)
    {
        pVE = m_VEs[nV] ;
        m_pApp->m_pLog->Log("VE %p %d %s\n", pVE, pVE->m_VID, *pVE->m_Tag) ;
        pVE->Generate(C, pE, nLine) ;
    }
    nLine = relLn ;
}
void    hdsXdiv::Generate   (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  On the condition that the user's access matches that of this <xdiv> tag, aggregate to the supplied chain (the HTML body), all child tags. Do
    //  nothing otherwise.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsXdiv::Generate") ;
    hdsVE*      pVE ;       //  Visible entity
    hdsInfo*    pInfo ;     //  Client session data
    bool        bShow ;     //  Print or not to print
    bShow = false ;
    pInfo = (hdsInfo*) pE->Session() ;
    if (!pInfo)
    {
        if (m_Access == ACCESS_PUBLIC || m_Access == ACCESS_NOBODY)
            bShow = true ;
    }
    else
    {
        if (m_Access == ACCESS_PUBLIC || (m_Access == ACCESS_ADMIN && pInfo->m_Access & ACCESS_ADMIN)
                || (pInfo->m_Access & ACCESS_MASK) == m_Access)
            bShow = true ;
    }
    if (bShow)
    {
        for (pVE = Children() ; pVE ; pVE = pVE->Sibling())
            pVE->Generate(C, pE, nLine) ;
    }
}
void    hdsCond::Generate   (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  If the flags are 0, then the variable named in m_Tag must be null for the subtags of <xcond> to be executed. Otherwise the named variable must
    //  exist and be non-null for the subtags to be executed.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              3)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsCond::Generate") ;
    //hzFixPair pa ;        //  Tag attribute
    hzPair      pa ;        //  Tag attribute
    hdsVE*      pVE ;       //  Subtag pointer
    hdsInfo*    pInfo ;     //  User session if applicable
    hzString    nam ;       //  The variable value, if applicable
    hzString    val ;       //  The variable value, if applicable
    int32_t     aLo ;       //  First attribute
    int32_t     aHi ;       //  Last attribute
    int32_t     nA ;        //  Attribute iterator
    bool        bPassed ;   //  True if condition passed
    bPassed = true ;
    if (pE)
    {
        if (m_nAttrs)
        {
            pInfo = (hdsInfo*) pE->Session() ;
            if (pInfo)
            {
                aLo = m_pApp->m_VE_attrs.First(pVE->m_VID) ;
                if (aLo >= 0)
                {
                    aHi = m_pApp->m_VE_attrs.Last(pVE->m_VID) ;
                    for (nA = aLo ; nA <= aHi ; nA++)
                    {
                        pa = m_pApp->m_VE_attrs.GetObj(nA) ;
                        if (*pa.value)
                            val = m_pApp->ConvertText(*pa.value, pE) ;
                        nam = *pa.name ;
                        if ((nam == "isnull" && val) || (nam == "exists" && !val))
                            { bPassed = false ; break ; }
                    }
                }
            }
        }
    }
    if (bPassed)
    {
        for (pVE = Children() ; pVE ; pVE = pVE->Sibling())
            pVE->Generate(C, pE, nLine) ;
    }
}
void    hdsPage::Head   (hzHttpEvent* pE)
{
    //  Send only the HTTP header for a page to the browser.
    //
    //  Arguments:  1)  pE  The HTTP event
    //
    //  Returns:    None
    _hzfunc("hdsPage::Head") ;
    ifstream    is ;            //  Read file stream
    hzXDate     d ;             //  Date for header lines
    hzChain     Z ;             //  Response for browser is built here
    const char* pEnd ;          //  Filename extension and hence type
    hzMimetype  type ;          //  File's HTTP type
    HttpRC      hrc ;           //  HTTP return code
    hzEcode     rc ;            //  Return code from sending function
    //  Establish real filename, either cpFilename or index.htm(l)
    //  Determine the type of file so that the correct header can be
    //  sent to the browser
    pEnd = strrchr(*m_Url, CHAR_PERIOD) ;
    if (pEnd)
        type = Filename2Mimetype(pEnd) ;
    else
        type = HMTYPE_TXT_HTML ;
    //  Send the header
    Z.Clear() ;
    switch  (hrc)
    {
    case HTTPMSG_OK:        Z << "HTTP/1.1 200 OK\r\n" ;        break ;
    case HTTPMSG_NOTFOUND:  Z << "HTTP/1.1 404 Not found\r\n" ; break ;
    default:
        Z.Printf("HTTP/1.1 %03d\r\n", hrc) ;
    } ;
    d.SysDateTime() ;
    Z.Printf("Date: %s\r\n", d.Txt(FMT_DT_INET)) ;
    Z << "Server: HTTP/1.0 (HadronZoo, Linux)\r\n" ;
    Z.Printf("Last-Modified: %s\r\n", d.Txt(FMT_DT_INET)) ;
    d.altdate(SECOND, 1000) ;
    Z.Printf("Expires: %s\r\n", d.Txt(FMT_DT_INET)) ;
    Z << "Accept-Ranges: bytes\r\n" ;
    if (hrc == HTTPMSG_OK)
        Z.Printf("Content-Length: 0\r\n") ;
    Z << "Content-Type: " << Mimetype2Txt(type) << "\n\n" ;
    pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, Z, 0, false) ;
}
void    hdsArtref::Generate (hzChain& C, hzHttpEvent* pE, uint32_t& nLine)
{
    //  Aggregate to the supplied chain (the HTML body), this article's preformulated value (the tags found within this article)
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //              4)  nLine   Line number tracker (controls NL printing)
    //
    //  Returns:    None
    _hzfunc("hdsArtref::Generate") ;
    hzList<hdsVE*>::Iter    ih ;    //  Html entity iterator
    hdsInfo*        pInfo = 0 ;     //  Session
    //hdsLang*      pLang ;         //  Applicable language
    hdsNavtree*     pAG = 0 ;       //  Article group
    hdsArticle*     pArt = 0 ;      //  The article
    hdsArticleStd*  pArtStd = 0 ;   //  The article as a visible entity container
    hdsArticleCIF*  pArtCIF = 0 ;   //  The article as a C-Interface function
    hzString        S ;             //  Temp string
    if (!this)
        Fatal("No instance\n") ;
    //  Get session if one applies
    if (pE)
        pInfo = (hdsInfo*) pE->Session() ;
    threadLog("info %p\n", pInfo) ;
    //  Lookup the tree of articles. This could be in the hdsApp map m_ArticleGroups or it could be vested with the session
    pAG = m_pApp->m_ArticleGroups[m_Group] ;
    if (!pAG)
    {
        if (pInfo && pInfo->m_pTree)
            if (pInfo->m_pTree->m_Groupname == m_Group)
                pAG = pInfo->m_pTree ;
    }
    if (!pAG)
    {
        C.Printf("SORRY: No such article group (%s)", *m_Group) ;
        return ;
    }
    if (m_Show == 300)
    {
        //  Export tree as script
        C << "\n<script language=\"javascript\">\n" ;
        pAG->ExportDataScript(C) ;
        C.Printf("makeTree('%s');\n", *m_Group) ;
        C << "</script>\n" ;
        return ;
    }
    if (m_Article[0] == CHAR_AT)
    {
        if (!memcmp(*m_Article, "@resarg;/", 9))
        {
            if (pE && pE->m_Resarg)
                pArt = (hdsArticle*) pAG->GetItem(pE->m_Resarg) ;
            else
            {
                S = *m_Article + 9 ;
                pArt = (hdsArticle*) pAG->GetItem(S) ;
            }
        }
    }
    else
    {
        pArt = (hdsArticle*) pAG->GetItem(m_Article) ;
        threadLog("fetched article %p %s\n", pArt, *m_Article) ;
    }
    if (!pArt)
    {
        C.Printf("SORRY: No article with group (%s) and name (%s)", *m_Group, *m_Article) ;
    }
    else
    {
        if (m_Show == 100)
        {
            C << pArt->m_Title ;
        }
        else if (m_Show == 200)
        {
            pArtStd = dynamic_cast<hdsArticleStd*>(pArt) ;
            if (pArtStd)
            {
                //  pLang = (hdsLang*) pE->m_pContextLang ;
                //  C << pLang->m_rawItems[pArtStd->m_USL] ;
                pArtStd->Display(C, pE) ;
            }
            else
            {
                pArtCIF = dynamic_cast<hdsArticleCIF*>(pArt) ;
                if (pArtCIF && pE)
                    pArtCIF->m_pFunc(C, pArtCIF, pE) ;
            }
        }
        else
        {
            C.Printf("FOUND: article with group (%s) and name (%s) but no show directive", *m_Group, *pArt->m_Title) ;
        }
    }
}
//void  hdsArticleStd::Generate (hzChain& C, hzHttpEvent* pE)
void    hdsArticleStd::EvalHtml (hzChain& C)
{
    //  Generate HTML for an article.
    //
    //  If the applicable article contains active elements and so is itself active, this function will be called to serve the article in response to an AJAX request. Otherwise this
    //  function is only called to create a fixed HTML value for the article during program initialization.
    //
    //  Note this function is never called by hdsPage::Display() since an hdsArticle cannot be a page component.
    //
    //  Argument:   C   The HTML output chain
    //  Returns:    None
    _hzfunc("hdsArticleStd::Display") ;
    hzHttpEvent     httpEv ;    //  Artificial HTTP event
    hdsVE*          pVE ;       //  Html entity
    uint32_t        nV ;        //  Visual entity iterator
    uint32_t        relLn ;     //  Relative line
    threadLog("Generating Article %s\n", *m_Title) ;
    for (nV = 0 ; nV < m_VEs.Count() ; nV++)
    {
        pVE = m_VEs[nV] ; pVE->Generate(C, &httpEv, relLn) ;
    }
}
void    hdsArticleStd::Display  (hzChain& C, hzHttpEvent* pE)
{
    //  Generate HTML for an article.
    //
    //  If the applicable article contains active elements and so is itself active, this function will be called to serve the article in response to an AJAX request. Otherwise this
    //  function is only called to create a fixed HTML value for the article during program initialization.
    //
    //  Note this function is never called by hdsPage::Display() since an hdsArticle cannot be a page component.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pE      The HTTP event being responded to
    //
    //  Returns:    None
    _hzfunc("hdsArticleStd::Display") ;
    hdsVE*      pVE ;           //  Html entity
    uint32_t    nV ;            //  Visual entity iterator
    uint32_t    relLn ;         //  Relative line
    threadLog("Generating Article %s\n", *m_Title) ;
    for (nV = 0 ; nV < m_VEs.Count() ; nV++)
    {
        pVE = m_VEs[nV] ; pVE->Generate(C, pE, relLn) ;
    }
}
void    hdsPage::EvalHtml   (hzChain& C)    //, hdsLang* pLang)
{
    //  Create HTML for a page that is inactive and so can be stored. This will be done once per inactive pager per supported language. Because this is part of the initialization
    //  process there will not be an actual HTTP event so a blank one is created. This is necessary because the language is carried by the HTTP event m_pContextLang variable.
    //
    //  CHANGE to:-
    //  Create pro-forma HTML for the page. If the page is active, this HTML will interspersed with percent entities that must be evaluated with each serving.
    //
    //  Arguments:  1)  C       The HTML output chain
    //              2)  pLang   Current language
    //
    //  Returns:    None
    _hzfunc("hdsPage::EvalHtml") ;
    hzList<hdsVE*>::Iter        ih ;    //  Html entity iterator
    hzList<hdsFormref*>::Iter   iF ;    //  Form iterator
    hzHttpEvent     httpEv ;            //  Artificial HTTP event
    hzChain         X ;                 //  For zipping output if applicable
    hdsVE*          pH ;                //  Html entity
    hdsFormref*     pFormref ;          //  Form reference
    hdsFormdef*     pFormdef ;          //  Form definition
    uint32_t        relLn ;             //  Relative line
    uint32_t        nV ;                //  Visual entity iterator
    hzEcode         rc ;                //  Return code
    //  if (!pLang)
    //      Fatal("No language supplied\n") ;
    //  httpEv.m_pContextLang = pLang ;
    m_pApp->m_pLog->Log("PAGE %s Script Flags %x\n", *m_Title, m_bScriptFlags) ;
    //  Construct the output HTML
    if (!m_Title)
        m_Title = "untitled" ;
    C << "<!DOCTYPE html>\n"
    "<html>\n"
    "<head>\n" ;
    C << "<title>" << m_Title << "</title>\n" ;
    if (m_Desc)
        C.Printf("<meta name=\"description\" content=\"%s\"/>\n", *m_Desc) ;
    else
        C.Printf("<meta name=\"description\" content=\"%s\"/>\n", *m_Title) ;
    if (m_Keys)
        C.Printf("<meta name=\"keywords\" content=\"%s\"/>\n", *m_Keys) ;
    C << s_std_metas ;
    C << "<link rel=\"stylesheet\" href=\"" << m_pApp->m_namCSS << "\"/>\n" ;
    if (m_bScriptFlags || m_xForms.Count())
    {
        if (m_bScriptFlags & INC_SCRIPT_RECAPTCHA)
            C << s_Recaptcha ;
        C << "<script language=\"javascript\">\n" ;
        //  If page contains forms then it must include validation javascript in the HTML
        if (m_xForms.Count())
        {
            if (m_bScriptFlags & INC_SCRIPT_CKEMAIL)
                C << _dsmScript_ckEmail ;
            if (m_bScriptFlags & INC_SCRIPT_CKURL)
                C << _dsmScript_ckUrl ;
            if (m_bScriptFlags & INC_SCRIPT_EXISTS)
                C << _dsmScript_ckExists ;
            for (iF = m_xForms ; iF.Valid() ; iF++)
            {
                pFormref = iF.Element() ;
                pFormdef = m_pApp->m_FormDefs[pFormref->m_Formname] ;
                
                C << pFormdef->m_ValJS ;
                //C << pFormref->m_pFormdef->m_ValJS ;
            }
            C << m_validateJS ;
        }
        C << "</script>\n" ;
    }
    C << "</head>\n\n" ;
    //  Construct the <body> tag
    C << "<body" ;
    if (m_CSS)
        C.Printf(" class=\"%s\"", *m_CSS) ;
    else
        C.Printf(" bgcolor=\"#%06x\" marginwidth=\"%d\" marginheight=\"%d\" leftmargin=\"%d\" topmargin=\"%d\"",
            m_BgColor, m_Width, m_Height, m_Width, m_Top) ;
    if (m_Onpage) C.Printf(" onpageshow=\"%s\"", *m_Onpage) ;
    if (m_Onload) C.Printf(" onload=\"%s\"", *m_Onload) ;
    if (m_Resize) C.Printf(" onresize=\"%s\"", *m_Resize) ;
    C << ">\n" ;
    m_pApp->m_pLog->Log("PAGE %s Script Flags %x\n", *m_Title, m_bScriptFlags) ;
    //  Now construct the page elements
    relLn = m_Line ;
    for (nV = 0 ; nV < m_VEs.Count() ; nV++)
    {
        pH = m_VEs[nV] ; pH->Generate(C, &httpEv, relLn) ;
    }
    //  End page construction
    C << "</body>\n</html>\n" ;
    m_pApp->m_pLog->Log("PAGE %s Script Flags %x\n", *m_Title, m_bScriptFlags) ;
}
void    hdsPage::Display    (hzHttpEvent* pE)
{
    //  Display HTML according to listed entities in the page.
    //
    //  Argument:   pE      The HTTP event being responded to
    //
    //  Returns:    None
    _hzfunc("hdsPage::Display") ;
    hzList<hdsExec*>::Iter      ei ;    //  Page exec commands iterator
    hzList<hdsVE*>::Iter        ih ;    //  Html entity iterator
    hzList<hdsFormref*>::Iter   iF ;    //  Form iterator
    hzChain         C ;                 //  For formulating HTML
    hdsExec*        pExec ;             //  Exec function
    hdsVE*          pVE ;               //  Html entity
    hdsFormref*     pFormref ;          //  Form
    hdsFormdef*     pFormdef ;          //  Form definition
    uint32_t        relLn ;             //  Relative line
    uint32_t        nV ;                //  Visual entity iterator
    hzEcode         rc ;                //  Return code
    if (!pE)                    Fatal("No HTTP Event\n") ;
    if (!pE->m_pContextLang)    Fatal("No Language\n") ;
    m_pApp->m_pLog->Log("PAGE %s Script Flags %x\n", *m_Title, m_bScriptFlags) ;
    //  Run executable commands
    for (ei = m_Exec ; ei.Valid() ; ei++)
    {
        pExec = ei.Element() ;
        m_pApp->m_pLog->Log("HAVE EXEC %s\n", Exec2Txt(pExec->m_Command)) ;
        rc = pExec->Exec(C, pE) ;
        m_pApp->m_pLog->Out(C) ;
        C.Clear() ;
    }
    //  Construct the output HTML
    if (!m_Title)
        m_Title = "untitled" ;
    C << "<!DOCTYPE html>\n"
    "<html>\n"
    "<head>\n" ;
    if (pE && pE->m_Resarg)
        C.Printf("<title>%s-%s</title>\n", *m_Title, *pE->m_Resarg) ;
    else
        C << "<title>" << m_Title << "</title>\n" ;
    if (m_Desc)
        C.Printf("<meta name=\"description\" content=\"%s\"/>\n", *m_Desc) ;
    else
        C.Printf("<meta name=\"description\" content=\"%s\"/>\n", *m_Title) ;
    if (m_Keys)
        C.Printf("<meta name=\"keywords\" content=\"%s\"/>\n", *m_Keys) ;
    C << s_std_metas ;
    C << "<link rel=\"stylesheet\" href=\"" << m_pApp->m_namCSS << "\"/>\n" ;
    if (m_bScriptFlags || m_xForms.Count())
    {
        if (m_bScriptFlags & INC_SCRIPT_RECAPTCHA)
            C << s_Recaptcha ;
        C << "<script language=\"javascript\">\n" ;
        if (m_bScriptFlags & INC_SCRIPT_WINDIM)
            C << _dsmScript_gwp ;
        if (m_bScriptFlags & INC_SCRIPT_NAVTREE)
        {
            C << _dsmScript_tog ;
            C << _dsmScript_loadArticle ;
            C << _dsmScript_navtree ;
        }
        //  If page contains forms then it must include validation javascript in the HTML
        if (m_xForms.Count())
        {
            if (m_bScriptFlags & INC_SCRIPT_CKEMAIL)    C << _dsmScript_ckEmail ;
            if (m_bScriptFlags & INC_SCRIPT_CKURL)      C << _dsmScript_ckUrl ;
            if (m_bScriptFlags & INC_SCRIPT_EXISTS)     C << _dsmScript_ckExists ;
            for (iF = m_xForms ; iF.Valid() ; iF++)
            {
                pFormref = iF.Element() ;
                pFormdef = m_pApp->m_FormDefs[pFormref->m_Formname] ;
                C << pFormdef->m_ValJS ;
            }
            C << m_validateJS ;
        }
        C << "</script>\n" ;
    }
    C << "</head>\n\n" ;
    //  Construct the <body> tag
    C << "<body" ;
    if (m_CSS)
        C.Printf(" class=\"%s\"", *m_CSS) ;
    else
        C.Printf(" bgcolor=\"#%06x\" marginwidth=\"%d\" marginheight=\"%d\" leftmargin=\"%d\" topmargin=\"%d\"",
            m_BgColor, m_Width, m_Height, m_Width, m_Top) ;
    if (m_Onpage) C.Printf(" onpageshow=\"%s\"", *m_Onpage) ;
    if (m_Onload) C.Printf(" onload=\"%s\"", *m_Onload) ;
    if (m_Resize) C.Printf(" onresize=\"%s\"", *m_Resize) ;
    C << ">\n" ;
    //  Now construct the page elements
    relLn = m_Line ;
    for (nV = 0 ; nV < m_VEs.Count() ; nV++)
    {
        pVE = m_VEs[nV] ; pVE->Generate(C, pE, relLn) ;
    }
    //  End page construction
    C << "</body>\n</html>\n" ;
    //  Send out page. Note all generated pages are sent out raw, not zipped
    rc = pE->SendRawChain(HTTPMSG_OK, HMTYPE_TXT_HTML, C, 0, false) ;
}