//
//  File:   hdsResource.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.
//
//
//  Dissemino Resource Management
//
//  Resources such as pages, articles and include blocks have inherently unique and human readable identifiers such as pathname and title but internal operation
//  is enhanced by allocation of a unique numeric id. This is best done internally as it is a nuicence to have to allocate ids to these entities in the configs.
//  Instead, the human operator only needs to set the version number of each resource. This will be ver="1" in all cases to begin with but will be increased by
//  1 if editied.
//
//  It is important to understand that Dissemino makes a distinction between resorces that are explicitly defined in the configs and those that are derived from
//  other sources such as uploads from users. While the latter may amount to whole pages or articles to Dissemino they are meaningless binaries to be stored in
//  the database and given addresses accordingly. The regime herein described applies only to config defined resources.
//
//  To this end, Dissemino manages a series of files in the document root as follows:-
//
//  1) website.res.xml: This lists all config defined resources and adds the internally assigned id, the date and time stamp and the version to the pathname and
//  title. The 
//
//  2) website.$$.txt:  Where $$ in this case is the default language code. This file will contain a series of strings extracted from the currently applicable
//                      resources.
//
//  If ANY of the above files do not exist in the document root, Dissemino will take the configs as autorative and create a complete set of new files missing files from the configs
//  a veriety of reasons, it is these have practical limitations. In
//  particular, change over time
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <stdarg.h>
#include <dirent.h>
#include <netdb.h>
#include <signal.h>
#include "hzErrcode.h"
#include "hzTokens.h"
#include "hzTextproc.h"
#include "hzDissemino.h"
using namespace std ;
/*
**  Variables
*/
//static    hdsNavtree      s_treeDataModel ;           //  Tree for object model display
extern  hzString    _dsmScript_tog ;            //  Navtree toggle script
extern  hzString    _dsmScript_loadArticle ;    //  Navtree load article script
extern  hzString    _dsmScript_navtree ;        //  Navtree script
/*
**  Functions
*/
hzEcode hdsApp::IndexPages  (void)
{
    //  Go through all pages found in the config files and index them
    //
    //  Arguments:  None
    //
    //  Returns:    E_FORMAT    If any page could not be tokenized
    //              E_OK        If the pages were indexed
    _hzfunc("hdsApp::IndexPages") ;
    hzVect<hzToken> toks ;          //  Token list
    hzChain         pageVal ;       //  Extract content from tags into chain, then tokenize to get words to index
    hzToken         T ;             //  Tokens
    hdsResource*    pRes ;          //  Resource under consideration
    hdsPage*        pPage ;         //  Current page
    uint32_t        nD ;            //  Document number
    uint32_t        nCount ;        //  Loop counter
    uint32_t        nDone ;         //  Count of actual inserts
    hzEcode         rc = E_OK ;     //  Return code
    /*
    **  Allocate working buffers and load HTML page
    */
    for (nD = 0 ; nD < m_ResourcesName.Count() ; nD++)
    {
        pRes = m_ResourcesName.GetObj(nD) ;
        pPage = dynamic_cast<hdsPage*>(pRes) ;
        if (!pPage)
            continue ;
        if (!pPage->m_Bodytext.Size())
            continue ;
        //  Pass thru page tags looking for indexable content. This will include the page title, description metatags and the content of paragraphs.
        //  Note tha paragraph content must be assumed to be complex and so is comprised of the pretext of the subtags and only lastly the content.
        pageVal.Clear() ;
        pageVal << pPage->m_Title ;
        pageVal.AddByte(CHAR_NL) ;
        pageVal << pPage->m_Desc ;
        pageVal.AddByte(CHAR_NL) ;
        pageVal << pPage->m_Bodytext ;
        rc = TokenizeChain(toks, pageVal, TOK_MO_WHITE) ;
        if (rc != E_OK)
        {
            m_pLog->Out("Abandoning indexation of page %s (%s)\n", *pPage->m_Url, *pPage->m_Title) ;
            break ;
        }
        for (nDone = nCount = 0 ; rc == E_OK && nCount < toks.Count() ; nCount++)
        {
            T = toks[nCount] ;
            if (!T.Value())
                continue ;
            nDone++ ;
            rc = m_PageIndex.Insert(T.Value(), nD) ;
        }
        m_pLog->Out("Indexing page %s (%s), %d of %d tokens\n", *pPage->m_Url, *pPage->m_Title, nDone, toks.Count()) ;
    }
    return rc ;
}
/*
**  Language Support, Text String Import/Export
*/
bool    _strhasalphas   (const char* str)
{
    //  Does string contain alpha chars?
    //
    //  Arguments:  1)  str     Test string
    //
    //  Returns:    True    If the supplied cstr contains alpha chars
    //              False   If it does not
    const char* i ;             //  Input string iterator
    uint32_t    uVal ;          //  Entity value
    uint32_t    nLen ;          //  Entity length
    uint32_t    nAlphas = 0 ;   //  Count of alpha characters
    if (!str || !str[0])
        return false ;
    for (i = str ; *i ; i++)
    {
        if (IsEntity(uVal, nLen, i))
            { i += (nLen - 1) ; continue ; }
        if (IsAlpha(*i))
            nAlphas++ ;
    }
    return nAlphas > 1 ? true : false ;
}
            
//void  hdsApp::_exportStr  (hzChain& Z, hdsVE* pVE, const hdsUSL& rid)
void    hdsApp::_exportStr  (hzChain& Z, hdsVE* pVE, uint32_t rid)
{
    //  Now for each subtag, output first the pretext (part of this tag's content) and then call recursively on the subtag
    //
    //  Arguments:  1)  Z   Export output chain
    //              2)  pVE Visual entity
    //
    //  Returns:    None
    hdsVE*      pX ;        //  Visual entity pointer
    uint32_t    nLen ;      //  Lenght of visual entity strings
    if (!pVE)
        Fatal("hdsApp::_exportStr: No visible entity poiter\n") ;
    if (pVE->m_Tag == "pre")    return ;
    if (pVE->m_Tag == "script") return ;
    for (pX = pVE->Children() ; pX ; pX = pX->Sibling())
    {
        if (pX->m_strPretext)
        {
            //  Test string for alpha chars first, if none, don't export the string
            if (_strhasalphas(*pX->m_strPretext))
            {
                //Z.Printf("%s.%u.0) %s\n", *usl, pVE->m_VID, *pX->m_strPretext) ;
                Z.Printf("%u.%u.0) %s\n", rid, pVE->m_VID, *pX->m_strPretext) ;
                nLen = pX->m_strPretext.Length() ;
                if (pX->m_strPretext[nLen-1] != CHAR_NL)
                    Z.AddByte(CHAR_NL) ;
            }
        }
        //_exportStr(Z, pX, usl) ;
        _exportStr(Z, pX, rid) ;
    }
    if (pVE->m_strContent)
    {
        if (_strhasalphas(*pVE->m_strContent))
        {
            //Z.Printf("%s.%u.1) %s\n", *usl, pVE->m_VID, *pVE->m_strContent) ;
            Z.Printf("%u.%u.1) %s\n", rid, pVE->m_VID, *pVE->m_strContent) ;
            nLen = pVE->m_strContent.Length() ;
            if (pVE->m_strContent[nLen-1] != CHAR_NL)
                Z.AddByte(CHAR_NL) ;
        }
    }
}
hzEcode hdsApp::ExportStrings   (void)
{
    //  Export all strings for external language translation
    //
    //  Arguments:  None
    //
    //  Returns:    E_OPENFAIL  If the string export output stream cannot be opened
    //              E_OK        If the strings are exported
    _hzfunc("hdsApp::ExportStrings") ;
    hzList<hdsVE*>::Iter    ih ;    //  Html entity iterator
    hdsArticle*     pArt ;          //  Generic article pointer
    hdsArticleStd*  pArtStd ;       //  Standar article pointer
    ofstream        os ;            //  Output stream
    hzChain         Z ;             //  Output chain
    hdsNavtree*     pAG ;           //  Article group
    hdsPage*        pPage ;         //  Page pointer
    hdsBlock*       pBlok ;         //  Include Block
    //hdsSubject*       pSubj ;         //  Subject (navbar heading)
    hdsVE*          pVE ;           //  Visible entity
    hzString        filepath ;      //  File path
    hzString        S ;             //  Temp string
    uint32_t        nG ;            //  Article group iterator
    uint32_t        n ;             //  Page iterator
    uint32_t        nV ;            //  Visual entity iterator
    //  Do strings by page
    Z.Printf("%s/website.%s.txt", *m_Docroot, *m_DefaultLang) ;
    filepath = Z ;
    Z.Clear() ;
    os.open(*filepath) ;
    if (os.fail())
        return hzerr(E_OPENFAIL, "Cannot open file [%s]\n", *filepath) ;
    for (n = 0 ; n < m_setPgSubjects.Count() ; n++)
    {
        S = m_setPgSubjects.GetObj(n) ;
        Z.Printf("s%d) %s\n\n", n, *S) ;
    }
    for (n = 0 ; n < m_Includes.Count() ; n++)
    {
        pBlok = m_Includes.GetObj(n) ;
        for (pVE = pBlok->Children() ; pVE ; pVE = pVE->Sibling())
            _exportStr(Z, pVE, n+1) ;   //pBlok->m_USL) ;
    }
    //for (n = 0 ; n < m_PagesUid.Count() ; n++)
    for (n = 0 ; n < m_vecPages.Count() ; n++)
    {
        //pPage = m_PagesUid.GetObj(n) ;
        pPage = m_vecPages[n] ;
        if (!(pPage->m_flagVE & VE_LANG))
            { m_pLog->Log("Skipping page %s\n", *pPage->m_Url) ; continue ; }
        m_pLog->Log("Doing Page %s\n", *pPage->m_Url) ;
        //  for (ih = pPage->m_VEs ; ih.Valid() ; ih++)
        //  {
        //      pVE = ih.Element() ;
        //      _exportStr(Z, pVE) ;
        //  }
        for (nV = 0 ; nV < pPage->m_VEs.Count() ; nV++)
        {
            pVE = pPage->m_VEs[nV] ;
            //_exportStr(Z, pVE, pPage->m_USL) ;
            _exportStr(Z, pVE, pPage->m_RID) ;
        }
    }
    os << Z ;
    os.close() ;
    os.clear() ;
    Z.Clear() ;
    m_pLog->Log("File %s Total %d pages\n", *filepath, n) ;
    //  Do article groups
    for (nG = 0 ; nG < m_ArticleGroups.Count() ; nG++)
    {
        pAG = m_ArticleGroups.GetObj(nG) ;
        //m_pLog->Out("Doing article group %s (total %d articles)\n", *pAG->m_Groupname, pAG->m_Articles.Count()) ;
        m_pLog->Out("Doing article group %s (total %d articles)\n", *pAG->m_Groupname, pAG->Count()) ;
        Z.Printf("%s/%s.%s.txt", *m_Docroot, *pAG->m_Groupname, *m_DefaultLang) ;
        filepath = Z ;
        Z.Clear() ;
        os.open(*filepath) ;
        if (os.fail())
        {
            m_pLog->Out("%s. Cannot open file [%s]\n", *filepath) ;
            return E_OPENFAIL ;
        }
        for (n = 0 ; n < pAG->Count() ; n++)
        {
            pArt = (hdsArticle*) pAG->GetItem(n) ;
            if (!pArt)
                continue ;
            pArtStd = dynamic_cast<hdsArticleStd*>(pArt) ;
            if (pArtStd)
            {
                if (!(pArtStd->m_flagVE & VE_LANG))
                    { m_pLog->Out("Skipping article %s\n", *pArtStd->m_Title) ; continue ; }
                //  for (pVE = pArt->Root() ; pVE ; pVE = pVE->Sibling())
                //      _exportStr(Z, pVE) ;
                for (nV = 0 ; nV < pArtStd->m_VEs.Count() ; nV++)
                {
                    pVE = pArtStd->m_VEs[nV] ;
                    //_exportStr(Z, pVE, pArtStd->m_USL) ;
                    _exportStr(Z, pVE, pArtStd->m_RID) ;
                }
            }
        }
        os << Z ;
        os.close() ;
        os.clear() ;
        Z.Clear() ;
        m_pLog->Log("File %s Total %d articles (size now %d)\n", *filepath, n, Z.Size()) ;
    }
    return E_OK ;
}
#if 0
void    hdsApp::ImportStrings   (void)
{
    //  For each supported language other than the default, import translated text. The export produces files of the form 'projectname.lang' for pages declared
    //  with an <xpage> and 'projectname.articlegroup.lang' for all articles groups. The import process expects these to have been manually converted offline to
    //  files of the form 'projectname.lc' and 'projectname.articlegroup.lc' where 'lc' is the non-default language code. The supported languages are indicated
    //  in the <siteLanguages> tag.
    //
    //  Arguments:  None
    //  Returns:    None
    _hzfunc("hdsApp::ImportStrings") ;
    hzList<hdsVE*>::Iter    ih ;    //  Html entity iterator
    ifstream        is ;            //  Output stream
    chIter          zi ;            //  File iterator
    hzChain         Z ;             //  Output chain
    hzChain         Para ;          //  Construction chain
    hdsLang*        pLang ;         //  Langauge
    hdsNavtree*     pAG ;           //  Article group
    hzString        filepath ;      //  File path
    hzString        tmpStr ;        //  Temp string for value
    hzString        S ;             //  Temp string for value
    hdsUSL          U ;             //  Temp string for uid
    uint32_t        nL ;            //  Language iterator
    uint32_t        n ;             //  Article group iterator
    for (nL = 0 ; nL < m_Languages.Count() ; nL++)
    {
        pLang = m_Languages.GetObj(nL) ;
        if (pLang->m_name == m_DefaultLang)
            continue ;
        //  Do strings by page
        Z.Printf("%s/website.%s.txt", *m_Docroot, *pLang->m_code) ;
        filepath = Z ;
        Z.Clear() ;
        is.open(*filepath) ;
        if (is.fail())
            { m_pLog->Log("Cannot open file [%s]\n", *filepath) ; continue ; }
        Z << is ;
        is.close() ;
        is.clear() ;
        m_pLog->Log("Opened language file [%s] (total %d bytes)\n", *filepath, Z.Size()) ;
        //  Scan the file content and load strings into the string table for the language. Pages begin with a http:// type URL and are identified from this. The
        //  strings for the page will begin with the form 'page_uid.string_id)' and end with a double newline.
        U.Clear() ;
        S = (char*) 0 ;
        for (zi = Z ; !zi.eof() && *zi <= CHAR_SPACE ; zi++) ;
        for (; !zi.eof() ;)
        {
            //  First part is the id string followed by the ')'
            for (; !zi.eof() && *zi > CHAR_SPACE ; zi++)
            {
                if (*zi == CHAR_PARCLOSE)
                    break ;
                Para.AddByte(*zi) ;
            }
            for (zi++ ; !zi.eof() && *zi < CHAR_SPACE ; zi++) ;
            tmpStr = Para ;
            U.SetByText(*tmpStr) ;
            Para.Clear() ;
            //  Second part is the content terminated by double newline
            for (; !zi.eof() ; zi++)
            {
                if (zi == "\n\n")
                {
                    S = Para ;
                    Para.Clear() ;
                    if (!m_pDfltLang->m_LangStrings.Exists(U))
                        m_pLog->Log("WARNING No match on string veId (%s=[%s])\n", *U, *S) ;
                    else
                        pLang->m_LangStrings.Insert(U, S) ;
                    U.Clear() ;
                    S = (char*) 0 ;
                    for (; !zi.eof() && *zi <= CHAR_SPACE ; zi++) ;
                    break ;
                }
                else
                    Para.AddByte(*zi) ;
            }
        }
        Z.Clear() ;
        m_pLog->Log("File %s Total %d strings\n", *filepath, pLang->m_LangStrings.Count()) ;
        //  Do article groups
        for (n = 0 ; n < m_ArticleGroups.Count() ; n++)
        {
            pAG = m_ArticleGroups.GetObj(n) ;
            //m_pLog->Out("Doing article group %s (total %d articles)\n", *pAG->m_Groupname, pAG->m_Articles.Count()) ;
            m_pLog->Out("Doing article group %s (total %d articles)\n", *pAG->m_Groupname, pAG->Count()) ;
            Z.Printf("%s/%s.%s.txt", *m_Docroot, *pAG->m_Groupname, *pLang->m_code) ;
            filepath = Z ;
            Z.Clear() ;
            is.open(*filepath) ;
            if (is.fail())
                { m_pLog->Log("Cannot open file [%s]\n", *filepath) ; continue ; }
            Z << is ;
            is.close() ;
            is.clear() ;
            m_pLog->Log("Opened language file [%s] (total %d bytes)\n", *filepath, Z.Size()) ;
            //  Scan the file content and load strings into the string table for the language. Articles begin with a http:// type URL and are identified from this. The
            //  strings for the page will begin with the form 'page_uid.string_id)' and end with a double newline.
            U.Clear() ;
            S = (char*) 0 ;
            for (zi = Z ; !zi.eof() && *zi <= CHAR_SPACE ; zi++) ;
            for (; !zi.eof() ;)
            {
                //  First part is the id string followed by the ')'
                for (; !zi.eof() && *zi > CHAR_SPACE ; zi++)
                {
                    if (*zi == CHAR_PARCLOSE)
                        break ;
                    Para.AddByte(*zi) ;
                }
                for (zi++ ; !zi.eof() && *zi < CHAR_SPACE ; zi++) ;
                //U = Para ;
                tmpStr = Para ;
                U.SetByText(*tmpStr) ;
                //stRef = U ;
                Para.Clear() ;
                //  Second part is the content terminated by double newline
                for (; !zi.eof() ; zi++)
                {
                    if (zi == "\n\n")
                    {
                        S = Para ;
                        Para.Clear() ;
                        if (!m_pDfltLang->m_LangStrings.Exists(U))
                            m_pLog->Log("WARNING No match on string veId (%s=[%s])\n", *U, *S) ;
                        else
                            pLang->m_LangStrings.Insert(U, S) ;
                        U.Clear() ;
                        S = (char*) 0 ;
                        for (; !zi.eof() && *zi <= CHAR_SPACE ; zi++) ;
                        break ;
                    }
                    else
                        Para.AddByte(*zi) ;
                }
            }
            Z.Clear() ;
            m_pLog->Log("File %s Total %d strings\n", *filepath, pLang->m_LangStrings.Count()) ;
        }
    }
}
#endif
hzEcode hdsApp::CreateDefaultForm   (const hzString& cname)
{
    //  Create a form definition and form handler for the named data class.
    _hzfunc("hdsApp::CreateDefaultForm") ;
    const hdbClass*     pClass ;    //  Data class pointer
    const hdbMember*    pMbr ;      //  Data class member
    hdsFormdef*     pFormdef ;      //  Form definition
    hdsFormhdl*     pFormhdl ;      //  Form handler
    hdsFormref*     pFormref ;      //  Form reference
    hdsField*       pFld ;          //  Field being processed
    hzString        S ;             //  Temp string
    uint32_t        mbrNo ;         //  Member number
    hzEcode         rc = E_OK ;     //  Return code
    //  Establish the class exists and does not already have a default form and from-hamdler (it can have other forms and form-handlers in the configs).
    if (cname == "subscriber")
    {
        //  Special case. Both success and failure lead to the re-displaying the current page
        m_pLog->Log("Subscriber class is a special case. No default form is required\n") ;
        return E_OK ;
    }
    pClass = m_ADP.GetPureClass(cname) ;
    if (!pClass)
        { m_pLog->Out("No such class as %s\n", *cname) ; return E_NOTFOUND ; }
    m_pLog->Log("Doing class %s with %d members\n", *cname, pClass->MbrCount()) ;
    //  Create the form definition and add the fields
    pFormdef = new hdsFormdef() ;
    pFormdef->m_Formname = "dflt_form_" + cname ;
    pFormdef->m_DfltAct = "/dflt_fhdl_" + cname ;
    pFormdef->m_pClass = pClass ;
    m_FormDefs.Insert(pFormdef->m_Formname, pFormdef) ;
    for (mbrNo = 0 ; mbrNo < pClass->MbrCount() ; mbrNo++)
    {
        pMbr = pClass->GetMember(mbrNo) ;
        //  Create feild for each atomic member
        pFld = new hdsField(this) ;
        pFld->InitVE(this) ;
        pFld->m_strPretext = pMbr->strName() ;
        pFld->m_Line = mbrNo ;
        pFld->m_Indent = 0 ;
        //  pFld->m_Fldspec.m_pType = m_ADP.GetDatatype(dtS) ;
        //  if (!pFld->m_Fldspec.m_pType)
           //    { m_pLog->Log("File %s Line %d: xfield %s: Illegal fld type %s\n", *cfg, pN->Line(), *vnam, *dtS) ; goto failed ; }
        //  pFld->m_Fldspec.m_Refname = pFormdef->m_Formname + "->" + vnam ;
        //  pFld->m_Varname = vnam ;
        //  pFld->m_flagVE |= flags ;
        //  pFld->m_Fldspec.nRows = nRows ;
        //  pFld->m_Fldspec.nCols = nCols ;
        //  pFld->m_Fldspec.nSize = nSize ;
        //  if (!pFormdef->m_mapFlds.Exists(pFld->m_Varname))
        //      pFormdef->m_mapFlds.Insert(pFld->m_Varname, pFld) ;
        pFormdef->m_vecFlds.Add(pFld) ;
    }
    pFormhdl = new hdsFormhdl() ;
    pFormhdl->m_Refname = "dflt_fhdl_" + cname ;
    pFormhdl->m_pFormdef = pFormdef ;
    pFormhdl->m_flgFH |= VE_COOKIES ;
    m_FormHdls.Insert(pFormhdl->m_Refname, pFormhdl) ;
    m_FormUrl2Hdl.Insert(pFormdef->m_DfltAct, pFormhdl->m_Refname) ;
    pFormref = new hdsFormref(this) ;
    pFormref->m_Formname = pFormdef->m_Formname ;
    //pFormref->m_flagVE |= VE_LOGINFORM ;
    m_FormUrl2Ref.Insert(pFormdef->m_DfltAct, pFormref) ;
    m_FormRef2Url.Insert(pFormref, pFormdef->m_DfltAct) ;
    return rc ;
}
hzEcode hdsApp::ExportDefaultForm   (const hzString& cname)
{
    //  Export data classes found in the configs as XML fragments that define default forms and form-handlers. This facility is provided as a development aid in
    //  order to save time. The output is to file name "classforms.def" in the current directory which should always be the development config directory for the
    //  website. The facility is invoked by calling Dissemino with "-forms" supplied as an argument. Dissemino will load the configs, write the classforms.def
    //  file and then exit.
    //
    //  There will be one form and one form-handler generated for each class found in the configs, including those for which form(s) currently exists. Forms in
    //  HTML cannot be nested so in cases where class members are composite (have a data type which is another class), the form produced will contain an extra
    //  button for each composite member. The class which a member has as its data type will have to have been declare beforehand (otherwise the configs cannot
    //  be read in), and this will have a form and form handler. The ...
    _hzfunc("hdsApp::ExportDefaultForm") ;
    ofstream            os ;            //  Output file
    hzChain             Z ;             //  Output chain
    const hdsFldspec*   pSpec ;         //  Field spec
    const hdbMember*    pMem ;          //  Data class member pointer
    const hdbClass*     pClass ;        //  Data class pointer
    hdsBlock*           pIncl ;         //  Pointer to default include block
    hzString            dfltInclude ;   //  Use a <xinclude> block in pages?
    const char*         mname ;         //  Member name
    uint32_t            nM ;            //  Data class member iterator
    bool                bUser ;         //  True is class is a user class
    char                fname[80] ;     //  Filename
    char                pebuf[4] ;      //  Percent entity form
    hzEcode             rc = E_OK ;     //  Return code
    m_pLog->Log("Have %d classes and %d repositories\n", m_ADP.CountDataClass(), m_ADP.CountObjRepos()) ;
    pebuf[0] = CHAR_PERCENT ;
    pebuf[1] = 's' ;
    pebuf[2] = ':' ;
    pebuf[3] = 0 ;
    if (m_Includes.Count())
    {
        pIncl = m_Includes.GetObj(0) ;
        dfltInclude = pIncl->m_Refname ;
    }
    pClass = m_ADP.GetPureClass(cname) ;
    if (!pClass)
        return hzerr(E_NOTFOUND, "No such class as %s\n", *cname) ;
    m_pLog->Log("Doing class %s with %d members\n", *cname, pClass->MbrCount()) ;
    bUser = false ;
    if (m_UserTypes.Exists(cname))
        bUser = true ;
    //  Start the XML output
    Z << "<disseminoInclude>\n" ;
    /*
    **  Write out a basic list page for the class
    */
    Z.Printf("<xpage path=\"/list_%s\" subject=\"%s\" title=\"List %s\" access=\"any\" bgcolor=\"eeeeee\" ops=\"noindex\">\n",
        *cname, *pClass->m_Category, *cname) ;
    Z << "\t<desc>None</desc>\n" ;
    if (dfltInclude)
        Z.Printf("\t<xblock name=\"%s\"/>\n", *dfltInclude) ;
    Z.AddByte(CHAR_NL) ;
    Z.Printf("\t<xtable repos=\"%s\" criteria=\"all\" rows=\"10000\" width=\"90\" height=\"500\" css=\"main\">\n", *cname) ;
    Z <<
    "\t\t<ifnone><p>Sorry no records ...</p></ifnone>\n"
    "\t\t<header>Table header ...</header>\n"
    "\t\t<footer>Table footer ...</footer>\n" ;
    for (nM = 0 ; nM < pClass->MbrCount() ; nM++)
    {
        pMem = pClass->GetMember(nM) ;
        mname = pMem->txtName() ;
        Z.Printf("\t\t<xcol member=\"%s\" title=\"%s\"/>\n", mname, mname) ;
    }
    Z << "\t</xtable>\n" ;
    Z << "</xpage>\n" ;
    /*
    **  Write out a basic host page for the form for the class
    */
    Z.Printf("<xpage path=\"/add_%s\" subject=\"%s\" title=\"Add %s\" access=\"any\" bgcolor=\"eeeeee\" ops=\"noindex\">\n", *cname, *pClass->m_Category, *cname) ;
    Z << "\t<desc>None</desc>\n" ;
    if (dfltInclude)
        Z.Printf("\t<xblock name=\"%s\"/>\n", *dfltInclude) ;
    Z.AddByte(CHAR_NL) ;
    Z <<
    "<style>\n"
    ".tooltip { position: relative; display: inline-block; border-bottom: 1px dotted black; }\n"
    ".tooltip .tooltiptext { visibility: hidden; width: 120px; background-color: black; color: #fff; text-align: center; padding: 5px 0; border-radius: 6px; position: absolute; z-index: 1; }\n"
    ".tooltip:hover .tooltiptext { visibility: visible; }\n"
    "</style>\n\n" ;
    Z <<
    "\t<table width=\"96%\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n"
    "\t\t<tr><td height=\"20\"> </td></tr>\n"
    "\t\t<tr><td class=\"title\" align=\"center\">Title: Add a " << cname << "</td></tr>\n"
    "\t\t<tr>\n"
    "\t\t<td valign=\"top\" align=\"center\" class=\"main\">\n"
    "\t\t<p>\n"
    "\t\tUse this form to create an instance of " << cname << "\n"
    "\t\t</p>\n"
    "\t\t</td>\n"
    "\t</tr>\n"
    "\t<xdiv user=\"public\">\n"
    "\t<tr><td height=\"20\"> </td></tr>\n"
    "\t<tr>\n"
    "\t\t<td height=\"20\" align=\"center\" class=\"main\">\n"
    "\t\tPlease be aware that by submitting this form, you are consenting to the use of cookies. Please read our cookie policy to <a href=\"/cookies\">find out more</a>\n"
    "\t\t</td>\n"
    "\t</tr>\n"
    "\t</xdiv>\n"
    "\t<tr><td height=\"40\"> </td></tr>\n"
    "\t</table>\n\n" ;
    //  Now write out the form itself
    if (bUser)
        Z.Printf("\t<xform name=\"form_reg_%s\" action=\"fhdl_phase1_%s\" class=\"%s\">\n", *cname, *cname, *cname) ;
    else
        Z.Printf("\t<xform name=\"form_add_%s\" action=\"fhdl_add_%s\" class=\"%s\">\n", *cname, *cname, *cname) ;
    Z << "\t<xhide name=\"lastpage\" value=\"%x:referer;\"/>\n\n" ;
    Z << "\t<table width=\"56%\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"main\">\n" ;
    for (nM = 0 ; nM < pClass->MbrCount() ; nM++)
    {
        pMem = pClass->GetMember(nM) ;
        mname = pMem->txtName() ;
        pSpec = pMem->GetSpec() ;
        if (!pSpec)
            { m_pLog->Log("Warning No Field specification for member %s\n", mname) ; continue ; }
        if (pSpec->m_Desc)
            Z.Printf("\t\t<tr height=\"18\"><td class=\"fld\"><div class=\"tooltip\">%s<span class=\"tooltiptext\">%s</span></div>:</td>\t<td><xfield member=\"%s\"",
                *pSpec->m_Title, *pSpec->m_Desc, mname) ;
        else
            Z.Printf("\t\t<tr height=\"18\"><td class=\"fld\">%s:</td>\t<td><xfield member=\"%s\"", *pSpec->m_Title, mname) ;
        Z << "\tdata=\"" ; Z << pebuf ; Z << mname << "\"" ;
        Z << "\tflags=\"req\"/></td></tr>\n" ;
    }
    Z << "\t</table>\n" ;
    //  Write out a rudimentry page footer
    Z <<
    "\t<table width=\"96%\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" class=\"fld\">\n"
    "\t\t<tr height=\"50\"><td align=\"center\"><xbutton show=\"Abort\"/> <xbutton show=\"Submit\"/></td></tr>\n"
    "\t\t<tr height=\"50\" bgcolor=\"#CCCCCC\"><td class=\"vb10blue\"><center>Site Name</center></td></tr>\n"
    "\t</table>\n" ;
    Z << "\t</xform>\n" ;
    Z << "</xpage>\n\n" ;
    //  Write form-handler for class
    if (bUser)
    {
        //  Write out user registration form-handler. This will be the first form-handler (with name of the form fhdl_classname), with a sendemail step to
        //  conduct email verification and reference to a second form-handler. This second form handler will also be produced and will commit a new user.
        Z.Printf("<formhdl name=\"fhdl_phase1_%s\" form=\"form_reg_%s\" ops=\"cookie\">\n", *cname, *cname) ;
        Z << "\t<procdata>\n" ;
        //  Do the <setvar tags for all the members
        Z << "\t\t<setvar name=\"iniCode\" type=\"uint32_t\" value=\"%x:usecs;\"/>\n" ;
        for (nM = 0 ; nM < pClass->MbrCount() ; nM++)
        {
            pMem = pClass->GetMember(nM) ;
            mname = pMem->txtName() ;
            Z.Printf("\t\t<setvar name=\"%s\" type=\"%s\" value=\"%ce:%s;\"/>\n", mname, pSpec->m_pType->txtType() , CHAR_PERCENT, mname) ;
        }
        //  Do the send email code thing
        Z.Printf("\t\t<sendemail from=\"%s\" to=\"%ce:email;\" smtpuser=\"%s\" smtppass=\"%s\" subject=\"Membership Application\">\n",
            *m_SmtpAddr, CHAR_PERCENT, *m_SmtpUser, *m_SmtpPass) ;
        Z.Printf("Thank you for registering with %s. Your initial login code is %cs:iniCode;i\n\n", *m_Domain, CHAR_PERCENT) ;
        Z << "The code is good for 1 hour. Enter this code instead of your selected password in the login screen.\n\n" ;
        Z << "This email has been generated by a submission to the Registration form at " ;
        Z << m_Domain ;
        Z << ". If you have recived this in error, please ignore\n" ;
        Z << "\t\t</sendemail>\n" ;
        Z << "\t</procdata>\n" ;
        Z << "\t<error goto=\"/register\"/>\n\n" ;
        Z.Printf("\t<response name=\"form_reg_%s\" bgcolor=\"eeeeee\">\n", *cname) ;
        Z << "\t\t<xblock name=\"gdInclude\"/>\n" ;
        Z.Printf("<xform name=\"formCkCorp1\" action=\"hdlCkCorp\">\n") ;
        Z <<
        "\t<table width=\"96%\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n"
        "\t\t<tr><td width=5 height=25> </td></tr>\n"
        "\t\t<tr><td align=center class=title>Email Verification</td></tr>\n"
        "\t\t<tr height=\"250\">\n"
        "\t\t<td class=\"main\">\n"
        "\t\tThank you %s:fname; %s:lname; for your registration on belhalf of %s:orgname; to the gdpr360 XLS document processing service.\n"
        "\t\t</td>\n"
        "\t\t</tr>\n"
        "\t\t<tr height=\"50\"><td align=\"center\" class=\"fld\">Code:</td><td><xfield fldspec=\"fs_Usec\" var=\"testCode\" flags=\"req\"/></td></tr>\n"
        "\t\t<tr height=\"50\"><td align=\"center\"><xbutton show=\"Complete Registration\"/></td></tr>\n"
        "\t\t</table>\n"
        "\t</xform>\n"
        "\t</response>\n"
        "</formhdl>\n\n" ;
        Z.Printf("<formhdl name=\"fhdl_phase2_%s\" form=\"form_reg_%s\" ops=\"cookie\">\n", *cname, *cname) ;
        Z <<
        "\t<procdata>\n"
        "\t\t<testeq param1=\"%e:testCode;\" param2=\"%s:iniCode;\">\n"
        "\t\t\t<error name=\"InvalidCode\" bgcolor=\"eeeeee\">\n"
        "\t\t\t\t<xblock name=\"gdInclude\"/>\n"
        "\t\t\t\t<xform name=\"formCkCorp2\" action=\"hdlCkCorp\">\n"
        "\t\t\t\t\t<table width=\"96%\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n"
        "\t\t\t\t\t\t<tr><td width=5 height=25> </td></tr>\n"
        "\t\t\t\t\t\t<tr><td align=center class=title>Oops! Wrong Code!</td></tr>\n"
        "\t\t\t\t\t\t<tr height=\"250\"><td class=\"main\">Please check the email again and type in or cut and paste the code.</td></tr>\n"
        "\t\t\t\t\t\t<tr height=\"50\"><td align=\"center\" class=\"fld\">Code:</td><td><xfield fldspec=\"fs_Usec\" var=\"testCode\" flags=\"req\"/></td></tr>\n"
        "\t\t\t\t\t\t<tr height=\"50\"><td align=\"center\"><xbutton show=\"Complete Registration\"/></td></tr>\n"
        "\t\t\t\t\t</table>\n"
        "\t\t\t\t</xform>\n"
        "\t\t\t</error>\n"
        "\t\t</testeq>\n" ;
        Z.Printf("<addSubscriber class=\"%s\" userName=\"%css:orgname;\" userPass=\"%css:password;\" userEmail=\"%css:email;\">\n",
            *cname, CHAR_PERCENT, CHAR_PERCENT, CHAR_PERCENT) ;
        for (nM = 0 ; nM < pClass->MbrCount() ; nM++)
        {
            pMem = pClass->GetMember(nM) ;
            mname = pMem->txtName() ;
            Z.Printf("\t\t\t<seteq member=\"%s\" input=\"%cs%s\"/>\n", mname, CHAR_PERCENT, mname) ;
        }
        Z <<
        "\t\t</addSubscriber>\n"
        "\t\t<logon user=\"%s:orgname;\"/>\n"
        "\t</procdata>\n" ;
        Z <<
        "\t<error name=\"RegistrationError\" bgcolor=\"eeeeee\">\n"
        "\t\t<xblock name=\"gdInclude\"/>\n"
        "\t\t<div class=\"stdpage\">\n"
        "\t\t<table bgcolor=\"#FFDDDD\" width=\"100%\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n"
        "\t\t<tr><td width=5 height=25> </td></tr>\n"
        "\t\t<tr><td align=center class=title>Internal Error</td></tr>\n"
        "\t\t<tr height=\"160\"><td> </td></tr>\n"
        "\t\t<tr>\n"
        "\t\t<td>An Internal error has occured and system administration has been notified. Please try again later.</td>\n"
        "\t\t</tr>\n"
        "\t\t<tr height=\"160\"><td> </td></tr>\n"
        "\t\t<tr height=\"50\"><td align=\"center\"><xbutton show=\"Return to Site\" goto=\"/index.html\"/></td></tr>\n"
        "\t\t</table>\n"
        "\t\t</div>\n"
        "\t</error>\n" ;
        Z <<
        "\t<response name=\"CorpCodeOK\" bgcolor=\"eeeeee\">\n"
        "\t\t<xblock name=\"gdInclude\"/>\n"
        "\t\t<table width=\"96%\" align=\"center\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">\n"
        "\t\t\t<tr><td width=5 height=25> </td></tr>\n"
        "\t\t\t<tr><td align=center class=title>Registration Complete</td></tr>\n"
        "\t\t\t<tr height=\"250\">\n"
        "\t\t\t\t<td class=\"main\">\n"
        "\t\t\t\tYour registration as %u:orgname; is now complete. You are logged in and may now use the document processing facility.\n"
        "\t\t\t\t</td>\n"
        "\t\t\t</tr>\n"
        "\t\t\t<tr height=\"50\"><td align=\"center\"><xbutton show=\"Continue\" goto=\"/menu\"/></td></tr>\n"
        "\t\t</table>\n"
        "\t</response>\n"
        "</formhdl>\n" ;
    }
    else
    {
        //  Write out standard commit form-handler. (fhdl_classname)
        Z.Printf("<formhdl name=\"fhdl_add_%s\" form=\"form_add_%s\">\n", *cname, *cname) ;
        Z << "\t<procdata>\n" ;
        Z.Printf("<commit class=\"%s\">\n", *cname) ;
        for (nM = 0 ; nM < pClass->MbrCount() ; nM++)
        {
            pMem = pClass->GetMember(nM) ;
            mname = pMem->txtName() ;
            Z.Printf("<seteq member=\"%s\" input=\"%ce:%s;\"/>\n", mname, CHAR_PERCENT, mname) ;
        }
        Z <<
        "\t\t</commit>\n"
        "\t</procdata>\n\n" ;
        Z.Printf("<error goto=\"/add_%s\"/>\n", *cname) ;
        Z.Printf("<response name=\"%s\" bgcolor=\"eeeeee\">\n", *cname) ;
        Z <<
        "\t</response>\n"
        "</formhdl>\n" ;
    }
    Z << "</disseminoInclude>\n" ;
    //  Commit the pro-forma page, form and form-handlers to a file called 'classname.forms' This can then be adapted and included in the main XML file for
    //  the site
    if (Z.Size())
    {
        sprintf(fname, "%s/%s.forms", *m_Configdir, *cname) ;
        m_pLog->Log("Writing form of %d bytes to %s\n", Z.Size(), fname) ;
        os.open(fname) ;
        if (os.fail())
            threadLog("Could not open export forms file (classforms.dat)\n") ;
        else
        {
            os << Z ;
            if (os.fail())
                threadLog("Could not write export forms file (classforms.dat)\n") ;
            os.close() ;
        }
        os.clear() ;
    }
    Z.Clear() ;
    return rc ;
}