[0001]
[0002]
[0003]
[0004]
[0005]
[0006]
[0007]
[0008]
[0009]
[0010]
[0011]
[0012]
[0013]
[0014]
[0015]
[0016]
[0017]
[0018]
[0019]
[0020]
[0021]
[0022]
[0023]
[0024]
[0025]
[0026]
[0027]
[0028]
[0029]
[0030]
[0031]
[0032]
[0033]
[0034]
[0035]
[0036]
[0037]
[0038]
[0039]
[0040]
[0041]
[0042]
[0043]
[0044]
[0045]
[0046]
[0047]
[0048]
[0049]
[0050]
[0051]
[0052]
[0053]
[0054]
[0055]
[0056]
[0057]
[0058]
[0059]
[0060]
[0061]
[0062]
[0063]
[0064]
[0065]
[0066]
[0067]
[0068]
[0069]
[0070]
[0071]
[0072]
[0073]
[0074]
[0075]
[0076]
[0077]
[0078]
[0079]
[0080]
[0081]
[0082]
[0083]
[0084]
[0085]
[0086]
[0087]
[0088]
[0089]
[0090]
[0091]
[0092]
[0093]
[0094]
[0095]
[0096]
[0097]
[0098]
[0099]
[0100]
[0101]
[0102]
[0103]
[0104]
[0105]
[0106]
[0107]
[0108]
[0109]
[0110]
[0111]
[0112]
[0113]
[0114]
[0115]
[0116]
[0117]
[0118]
[0119]
[0120]
[0121]
[0122]
[0123]
[0124]
[0125]
[0126]
[0127]
[0128]
[0129]
[0130]
[0131]
[0132]
[0133]
[0134]
[0135]
[0136]
[0137]
[0138]
[0139]
[0140]
[0141]
[0142]
[0143]
[0144]
[0145]
[0146]
[0147]
[0148]
[0149]
[0150]
[0151]
[0152]
[0153]
[0154]
[0155]
[0156]
[0157]
[0158]
[0159]
[0160]
[0161]
[0162]
[0163]
[0164]
[0165]
[0166]
[0167]
[0168]
[0169]
[0170]
[0171]
[0172]
[0173]
[0174]
[0175]
[0176]
[0177]
[0178]
[0179]
[0180]
[0181]
[0182]
[0183]
[0184]
[0185]
[0186]
[0187]
[0188]
[0189]
[0190]
[0191]
[0192]
[0193]
[0194]
[0195]
[0196]
[0197]
[0198]
[0199]
[0200]
[0201]
[0202]
[0203]
[0204]
[0205]
[0206]
[0207]
[0208]
[0209]
[0210]
[0211]
[0212]
[0213]
[0214]
[0215]
[0216]
[0217]
[0218]
[0219]
[0220]
[0221]
[0222]
[0223]
[0224]
[0225]
[0226]
[0227]
[0228]
[0229]
[0230]
[0231]
[0232]
[0233]
[0234]
[0235]
[0236]
[0237]
[0238]
[0239]
[0240]
[0241]
[0242]
[0243]
[0244]
[0245]
[0246]
[0247]
[0248]
[0249]
[0250]
[0251]
[0252]
[0253]
[0254]
[0255]
[0256]
[0257]
[0258]
[0259]
[0260]
[0261]
[0262]
[0263]
[0264]
[0265]
[0266]
[0267]
[0268]
[0269]
[0270]
[0271]
[0272]
[0273]
[0274]
[0275]
[0276]
[0277]
[0278]
[0279]
[0280]
[0281]
[0282]
[0283]
[0284]
[0285]
[0286]
[0287]
[0288]
[0289]
[0290]
[0291]
[0292]
[0293]
[0294]
[0295]
[0296]
[0297]
[0298]
[0299]
[0300]
[0301]
[0302]
[0303]
[0304]
[0305]
[0306]
[0307]
[0308]
[0309]
[0310]
[0311]
[0312]
[0313]
[0314]
[0315]
[0316]
[0317]
[0318]
[0319]
[0320]
[0321]
[0322]
[0323]
[0324]
[0325]
[0326]
[0327]
[0328]
[0329]
[0330]
[0331]
[0332]
[0333]
[0334]
[0335]
[0336]
[0337]
[0338]
[0339]
[0340]
[0341]
[0342]
[0343]
[0344]
[0345]
[0346]
[0347]
[0348]
[0349]
[0350]
[0351]
[0352]
[0353]
[0354]
[0355]
[0356]
[0357]
[0358]
[0359]
[0360]
[0361]
[0362]
[0363]
[0364]
[0365]
[0366]
[0367]
[0368]
[0369]
[0370]
[0371]
[0372]
[0373]
[0374]
[0375]
[0376]
[0377]
[0378]
[0379]
[0380]
[0381]
[0382]
[0383]
[0384]
[0385]
[0386]
[0387]
[0388]
[0389]
[0390]
[0391]
[0392]
[0393]
[0394]
[0395]
[0396]
[0397]
[0398]
[0399]
[0400]
[0401]
[0402]
[0403]
[0404]
[0405]
[0406]
[0407]
[0408]
[0409]
[0410]
[0411]
[0412]
[0413]
[0414]
[0415]
[0416]
[0417]
[0418]
[0419]
[0420]
[0421]
[0422]
[0423]
[0424]
[0425]
[0426]
[0427]
[0428]
[0429]
[0430]
[0431]
[0432]
[0433]
[0434]
[0435]
[0436]
[0437]
[0438]
[0439]
[0440]
[0441]
[0442]
[0443]
[0444]
[0445]
[0446]
[0447]
[0448]
[0449]
[0450]
[0451]
[0452]
[0453]
[0454]
[0455]
[0456]
[0457]
[0458]
[0459]
[0460]
[0461]
[0462]
[0463]
[0464]
[0465]
[0466]
[0467]
[0468]
[0469]
[0470]
[0471]
[0472]
[0473]
[0474]
[0475]
[0476]
[0477]
[0478]
[0479]
[0480]
[0481]
[0482]
[0483]
[0484]
[0485]
[0486]
[0487]
[0488]
[0489]
[0490]
[0491]
[0492]
[0493]
[0494]
[0495]
[0496]
[0497]
[0498]
[0499]
[0500]
[0501]
[0502]
[0503]
[0504]
[0505]
[0506]
[0507]
[0508]
[0509]
[0510]
[0511]
[0512]
[0513]
[0514]
[0515]
[0516]
[0517]
[0518]
[0519]
[0520]
[0521]
[0522]
[0523]
[0524]
[0525]
[0526]
[0527]
[0528]
[0529]
[0530]
[0531]
[0532]
[0533]
[0534]
[0535]
[0536]
[0537]
[0538]
[0539]
[0540]
[0541]
[0542]
[0543]
[0544]
[0545]
[0546]
[0547]
[0548]
[0549]
[0550]
[0551]
[0552]
[0553]
[0554]
[0555]
[0556]
[0557]
[0558]
[0559]
[0560]
[0561]
[0562]
[0563]
[0564]
[0565]
[0566]
[0567]
[0568]
[0569]
[0570]
[0571]
[0572]
[0573]
[0574]
[0575]
[0576]
[0577]
[0578]
[0579]
[0580]
[0581]
[0582]
[0583]
[0584]
[0585]
[0586]
[0587]
[0588]
[0589]
[0590]
[0591]
[0592]
[0593]
[0594]
[0595]
[0596]
[0597]
[0598]
[0599]
[0600]
[0601]
[0602]
[0603]
[0604]
[0605]
[0606]
[0607]
[0608]
[0609]
[0610]
[0611]
[0612]
[0613]
[0614]
[0615]
[0616]
[0617]
[0618]
[0619]
[0620]
[0621]
[0622]
[0623]
[0624]
[0625]
[0626]
[0627]
[0628]
[0629]
[0630]
[0631]
[0632]
[0633]
[0634]
[0635]
[0636]
[0637]
[0638]
[0639]
[0640]
[0641]
[0642]
[0643]
[0644]
[0645]
[0646]
[0647]
[0648]
[0649]
[0650]
[0651]
[0652]
[0653]
[0654]
[0655]
[0656]
[0657]
[0658]
[0659]
[0660]
[0661]
[0662]
[0663]
[0664]
[0665]
[0666]
[0667]
[0668]
[0669]
[0670]
[0671]
[0672]
[0673]
[0674]
[0675]
[0676]
[0677]
[0678]
[0679]
[0680]
[0681]
[0682]
[0683]
[0684]
[0685]
[0686]
[0687]
[0688]
[0689]
[0690]
[0691]
[0692]
[0693]
[0694]
[0695]
[0696]
[0697]
[0698]
[0699]
[0700]
[0701]
[0702]
[0703]
[0704]
[0705]
[0706]
[0707]
[0708]
[0709]
[0710]
[0711]
[0712]
[0713]
[0714]
[0715]
[0716]
[0717]
[0718]
[0719]
[0720]
[0721]
[0722]
[0723]
[0724]
[0725]
[0726]
[0727]
[0728]
[0729]
[0730]
[0731]
[0732]
[0733]
[0734]
[0735]
[0736]
[0737]
[0738]
[0739]
[0740]
[0741]
[0742]
[0743]
[0744]
[0745]
[0746]
[0747]
[0748]
[0749]
[0750]
[0751]
[0752]
[0753]
[0754]
[0755]
[0756]
[0757]
[0758]
[0759]
[0760]
[0761]
[0762]
[0763]
[0764]
[0765]
[0766]
[0767]
[0768]
[0769]
[0770]
[0771]
[0772]
[0773]
[0774]
[0775]
[0776]
[0777]
[0778]
[0779]
[0780]
[0781]
[0782]
[0783]
[0784]
[0785]
[0786]
[0787]
[0788]
[0789]
[0790]
[0791]
[0792]
[0793]
[0794]
[0795]
[0796]
[0797]
[0798]
[0799]
[0800]
[0801]
[0802]
[0803]
[0804]
[0805]
[0806]
[0807]
[0808]
[0809]
[0810]
[0811]
[0812]
[0813]
[0814]
[0815]
[0816]
[0817]
[0818]
[0819]
[0820]
[0821]
[0822]
[0823]
[0824]
[0825]
[0826]
[0827]
[0828]
[0829]
[0830]
[0831]
[0832]
[0833]
[0834]
[0835]
[0836]
[0837]
[0838]
[0839]
[0840]
[0841]
[0842]
[0843]
[0844]
[0845]
[0846]
[0847]
[0848]
[0849]
[0850]
[0851]
[0852]
[0853]
[0854]
[0855]
[0856]
[0857]
[0858]
[0859]
[0860]
[0861]
[0862]
[0863]
[0864]
[0865]
[0866]
[0867]
[0868]
[0869]
[0870]
[0871]
[0872]
[0873]
[0874]
[0875]
[0876]
[0877]
[0878]
[0879]
[0880]
[0881]
[0882]
[0883]
[0884]
[0885]
[0886]
[0887]
[0888]
[0889]
[0890]
[0891]
[0892]
[0893]
[0894]
[0895]
[0896]
[0897]
[0898]
[0899]
[0900]
[0901]
[0902]
[0903]
[0904]
[0905]
[0906]
[0907]
[0908]
[0909]
[0910]
[0911]
[0912]
[0913]
[0914]
[0915]
[0916]
[0917]
[0918]
[0919]
[0920]
[0921]
[0922]
[0923]
[0924]
[0925]
[0926]
[0927]
[0928]
[0929]
[0930]
[0931]
[0932]
[0933]
[0934]
[0935]
[0936]
[0937]
[0938]
[0939]
[0940]
[0941]
[0942]
[0943]
[0944]
[0945]
[0946]
[0947]
[0948]
[0949]
[0950]
[0951]
[0952]
[0953]
[0954]
[0955]
[0956]
[0957]
[0958]
[0959]
[0960]
[0961]
[0962]
[0963]
[0964]
[0965]
[0966]
[0967]
[0968]
[0969]
[0970]
[0971]
[0972]
[0973]
[0974]
[0975]
[0976]
[0977]
[0978]
[0979]
[0980]
[0981]
[0982]
[0983]
[0984]
[0985]
[0986]
[0987]
[0988]
[0989]
[0990]
[0991]
[0992]
[0993]
[0994]
[0995]
[0996]
[0997]
[0998]
[0999]
[1000]
[1001]
[1002]
[1003]
[1004]
[1005]
[1006]
[1007]
[1008]
[1009]
[1010]
[1011]
[1012]
[1013]
[1014]
[1015]
[1016]
/*****************************************************************************/
/*
                                  wasDOC.C

WASD document system.

Yet Another Markup Language (YAML).

In WASD's inimitable style of course :-/

A CLI/CGI/CGIplus utility to read directories containing files and output them
as HTML documents.  Suitable for VMS Apache, OSU and of course WASD.

Directives use an embedded vertical bar symbol (Vbar).  To escape a literal
Vbar prefix it with a backslash.  In between the Vbars are characters
interpreted by the WASD document processor.  See RENDER.C for a more detailed
description of tag syntax.

At startup wasDOC reads all .WASDOC files in the directory, assembling an
in-memory copy of the text of those files, and from that creating an in-memory
HTML representation of the content.

As a CGIplus script, at a maximum default periuod of once a minute, each
.WASDOC file is checked for modification since the document was processed and
if modified the document is recreated.

Table of content is generated by major and minor headings in the order
processed.  A referencing mechanism provides internal and external links.

Use /cgi-bin/wasdoc/wasd_root/src/wasdoc/doc/ to view the wasDOC documentation. 
Alternatively, access /wasd_root/src/wasdoc/doc/index.html to access a static
version of the same document.

Copyright (C) 2019-2021 Mark G.Daniel
-------------------------------------
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


VERSION HISTORY
---------------
11-JUL-2020  MGD  v2.0.0, multi-pass HTML generation
                          (pleasing to see this reduce elapsed and CPU times
                           to ~60% of v1.n - which was clunky(er) in spots)
26-JAN-2020  MGD  v1.2.0, box drawing using |draw|
                          bugfix; Navig8ReferText() if (ch == 'Z') ch = 0x7f;
14-JUL-2019  MGD  v1.1.0, Bonne Fête Nationale
                          expand conditionals
                  bugfix; broken nested conditionals
28-JUN-2019  MGD  v1.0.0, initial release
21-FEB-2019  MGD  initial development
*/
/*****************************************************************************/

#define COPYRIGHT_DATE "2019,2020"
#define SOFTWAREVN "2.0.0"
#define SOFTWARENM "WASDOC"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

#include "wasdoc.h"
#include <cgilib.h>
#include <dirent.h>

#include <starlet.h>

int  dbug, isCgi, isCgiPlus,
     CliChunk;

int  bvbar = 166,
     ellip = 133,
     multx = 215;

char  *CliDocDir,
      *CliIndex,
      *CliOutput;

char  CopyrightDate [] = COPYRIGHT_DATE,
      DefaultType [64] = DEFAULT_FILE_TYPE,
      SoftwareID [64],
      SoftwareVersion [] = SOFTWAREVN,
      Utility [] = "WASDOC";

/*****************************************************************************/
/*
*/

int main (int argc, char *argv[])

{
   CgiAccess (NULL);

   sprintf (SoftwareID, "%s (%s)", SOFTWAREID, CGILIB_SOFTWAREID);

   CgiLibEnvironmentInit (argc, argv, 0);

   isCgi = CgiLibEnvironmentIsWasd() ||
           (isCgiPlus = CgiLibEnvironmentIsCgiPlus()) ||
           CgiLibEnvironmentIsApache() ||
           CgiLibEnvironmentIsOsu() ||
           CgiLibVarNull ("SERVER_SOFTWARE");

   if (isCgi)
      CgiDoc ();
   else
      CliDoc (argc, argv);
}

/*****************************************************************************/
/*
Provide the infrastructure for the |insight@| facility.
NOTE: writes directly to <stdout>.
*/

void wasDocInsightAt (struct wasdoc_st *docptr)

{
   char  *cptr, *hptr, *sptr, *tptr, *zptr;

   if (!docptr->isDynamic || !docptr->insight) return;

   if (docptr->chunked > 0)
   {
      /* document provided in "chunks", check this chunk has the buttons */
      for (hptr = docptr->html; *hptr; hptr++)
      {
         if (*hptr != '<' || !MATCH4 (hptr, "<spa")) continue;
         if (memcmp (hptr, "<span id=\"insightstats\"></span>", 30)) continue;
         break;
      }
      /* if not found in this section */
      if (!*hptr) return;
   }

   fprintf (stdout,
"\n<script>\n\
function button(type) {\n\
   uri = location.href.substr(0,location.href.indexOf('#'));\n\
   uri += uri.indexOf(\'?\') >= 0 ? \'&\' : \'?\';\n\
   uri += type + \'=1\';\n\
   window.open(uri);\n\
}\n\
var obj = document.getElementById(\'lookstats\');\n\
obj.innerHTML = \'Files: %d  Text: %d bytes  HTML: %d bytes  \
Statistics: %s\';\n\
</script>\n",
                    docptr->ftotal, docptr->tlength, docptr->hlength,
                    docptr->statistics);
}

/*****************************************************************************/
/*
Initialise and return a document structure.
*/

struct wasdoc_st* InitDoc
(
char *uri,
char *pchunk,
char *pinfo,
char *dname
)
{
   char  *cptr, *sptr, *zptr;
   struct wasdoc_st  *docptr;

   docptr = calloc (1, sizeof(struct wasdoc_st)); 
   if (!docptr) exit (vaxc$errno);

   if (uri)
   {
      zptr = (sptr = docptr->uri) + sizeof(docptr->uri)-1;
      for (cptr = uri; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';
   }

   if (pchunk && *pchunk)
   {
      docptr->chunked = 1;
      docptr->pchunk = atoi(pchunk);
   }

   if (pinfo)
   {
      zptr = (sptr = docptr->pinfo) + sizeof(docptr->pinfo)-1;
      for (cptr = pinfo; *cptr && sptr < zptr; *sptr++ = *cptr++);
      *sptr = '\0';
   }

   zptr = (sptr = docptr->dname) + sizeof(docptr->dname)-1;
   for (cptr = dname; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';

   zptr = (sptr = docptr->ftype) + sizeof(docptr->ftype)-1;
   for (cptr = DefaultType; *cptr && sptr < zptr; *sptr++ = *cptr++);
   *sptr = '\0';
   docptr->ftlength = sptr - docptr->ftype;

   docptr->setToc2 = DEFAULT_TOC2;
   docptr->setToc2Cols = DEFAULT_TOC2_COLS;
   docptr->setTocCols = DEFAULT_TOC_COLS;
   docptr->setIdxCols = DEFAULT_IDX_COLS;
   docptr->setIdxSort = DEFAULT_IDX_SORT;
   docptr->setNavigate = DEFAULT_NAVIGATE;
   docptr->setPaginate = DEFAULT_PAGINATE;
   strcpy (docptr->setTocForm, DEFAULT_TOC_SCHEMA);
   strcpy (docptr->iconBack, DEFAULT_ICON_BACK);
   strcpy (docptr->iconForw, DEFAULT_ICON_FORW);
   strcpy (docptr->iconNext, DEFAULT_ICON_NEXT);
   strcpy (docptr->iconPrev, DEFAULT_ICON_PREV);
   strcpy (docptr->iconTop, DEFAULT_ICON_TOP);
   strcpy (docptr->idxCollate, DEFAULT_COLLATE_EN);

   return (docptr);
}

/*****************************************************************************/
/*
Free a document structure.
*/

void* FreeDoc (struct wasdoc_st *docptr)

{
   int  idx;
   struct wasdoc_st  *tocptr;

   if (!docptr) return (NULL);

   free (docptr->html);
   free (docptr->html1);
   free (docptr->list);
   free (docptr->text);
   free (docptr->spawnOutPtr);
   for (idx = 0; idx < docptr->flagCount; idx++) free (docptr->flags[idx]); 
   free (docptr);

   return (NULL);
}

/*****************************************************************************/
/*
Formatted print to the document HTML buffer.
*/

int wasDocPrintf
(
struct wasdoc_st* docptr,
const char *fmt,
...
)
{
   int  count, size;
   char  *bptr;
   va_list  args;

   if (dbug>2) dbugThis (FI_LI, "wasDocPrintf() %d/%d %s",
                         docptr->hlength, docptr->hsize, fmt);

   if (HMORE(docptr)) wasDocMoreHtml (docptr);

   vaxc$errno = 0;  /* hmmm, shouldn't need doing */
   docptr->errorValue = 0;
   for (;;)
   {
      size = docptr->hsize - docptr->hlength;
      bptr = docptr->html + docptr->hlength;
      va_start (args, fmt);
      if (dbug>99)
      {
         fputs ("+++++\n", stdout);
         vfprintf (stdout, fmt, args);
         fputs ("\n+++++", stdout);
      }
      count = vsnprintf (bptr, size, fmt, args);
      va_end (args);
      if (count < size) break;
      if (count < 0) break;
      wasDocMoreHtml (docptr);
   }
   if (count < 0)
      docptr->errorValue = vaxc$errno;
   else
      docptr->hlength += count;

   if (dbug>2) dbugThis (FI_LI, "%d %d/%d %d %d %s",
                         count, docptr->hlength, docptr->hsize,
                         docptr->errorValue,
                         vaxc$errno, strerror(EVMSERR, vaxc$errno));

   return (docptr->errorValue);
}

/*****************************************************************************/
/*
Increase the size of the HTML buffer.  More is set at 20% of the increment.
*/

void wasDocMoreHtml (struct wasdoc_st *docptr)

{
   docptr->errorValue = 0;
   if (docptr->hlength > docptr->hsize) wasDocBugcheck (FI_LI);
   docptr->hsize += HTML_INCREMENT;
   docptr->hmore = docptr->hsize - (HTML_INCREMENT / 5);
   docptr->html = realloc (docptr->html, docptr->hsize);
   if (!docptr->html) EXIT_FI_LI (vaxc$errno);
}

/*****************************************************************************/
/*
Move the first pass HTML to second from the current and reinitialise current.
*/

void wasDocHtmlShuffle (struct wasdoc_st *docptr)

{
   docptr->html1 = docptr->html;
   docptr->hlength1 = docptr->hlength;
   docptr->html = NULL;
   docptr->hlength = docptr->hmore = docptr->hsize = 0;
}

/*****************************************************************************/
/*
Insert (append) the supplied text without any other processing.
*/

void wasDocAsIs
(
struct wasdoc_st *docptr,
char *text
)
{
   char  *sptr, *tptr, *zptr;

   if (dbug>1) dbugThis (FI_LI, "wasDocAsIs() %s", dbugAll(text));

   if (!text) return;

   tptr = text;
   sptr = docptr->html + docptr->hlength; 
   zptr = docptr->html + docptr->hmore;

   while (*tptr)
   {
      if (HMORE(docptr))
      {
         wasDocMoreHtml (docptr);
         sptr = docptr->html + docptr->hlength; 
         zptr = docptr->html + docptr->hmore;
      }
      while (*tptr && sptr < zptr) *sptr++ = *tptr++;
      *sptr = '\0';
      docptr->hlength = sptr - docptr->html;
   }
}

/*****************************************************************************/
/*
Insert (append) the supplied text while entity-fying required characters.
*/

void wasDocEntify
(
struct wasdoc_st *docptr,
char *text
)
{
   char  *cptr, *sptr, *tptr, *zptr;

   if (dbug>1) dbugThis (FI_LI, "wasDocEntify() %s", dbugAll(text));
   if (!text) return;

   tptr = text;
   sptr = docptr->html + docptr->hlength; 
   zptr = docptr->html + docptr->hmore;

   while (*tptr)
   {
      if (HMORE(docptr))
      {
         wasDocMoreHtml (docptr);
         sptr = docptr->html + docptr->hlength; 
         zptr = docptr->html + docptr->hmore;
      }
      while (*tptr && sptr < zptr)
      {
         cptr = "";
         switch (*tptr)
         {
            case '<'  : cptr = "&lt;"; break;
            case '>'  : cptr = "&gt;"; break;
            case '&'  : cptr = "&amp;"; break;
            case '|'  : cptr = "&verbar;"; break;
            case '\"' : cptr = "&quot;"; break;
            case '\\' : cptr = "&bsol;"; break;
            default : *sptr++ = *tptr;
         }
         tptr++;
         while (*cptr) *sptr++ = *cptr++;
      }
      *sptr = '\0';
      docptr->hlength = sptr - docptr->html;
   }
}

/*****************************************************************************/
/*
Parse to find the specified MAJOR section identified by the fragment ID leading
digit.  It automatically includes the document prologue (e.g. style).
*/

int wasDocChunkOut (struct wasdoc_st *docptr)

{
   int  count, fragnum, len, offset1, offset2;
   char  *cptr, *lptr, *sptr,
         *title = NULL;
   char  buf [1024];

   if (dbug>1) dbugThis (FI_LI, "wasDocChunkOut() %d", docptr->pchunk);

   if (!docptr->list) RETURN_FI_LI (docptr, SS$_ABORT)

   /* first heading defines the extent of document prologue */
   docptr->proLength = 0;
   lptr = docptr->list;
   count = *lptr++;
   if (count)
   {
      lptr += count + 1;
      count = *lptr++;
      lptr += count + 1;
      count = *lptr++;
      lptr += count + 1;
      docptr->proLength = *(ULONGPTR)lptr;
   }

   /* find the requested fragment ID */
   lptr = docptr->list;
   cptr = "";
   for (;;)
   {
      count = *lptr++;
      if (!count) break;
      cptr = lptr;
      lptr += count + 1;
      count = *lptr++;
      lptr += count + 1;
      count = *lptr++;
      title = lptr;
      lptr += count + 1;
      offset1 = *(ULONGPTR)lptr;
      lptr += sizeof(ulong);
      /* chunk of -1 specifically gets zeroeth chunk */
      if (docptr->pchunk < 0 && atoi(cptr) == 0) break;
      /* if matches it breaks with offset set */
      if (docptr->pchunk == atoi(cptr)) break;
   }

   /* always should find the specified section */
   if (!count) RETURN_FI_LI (docptr, SS$_ABORT)

   /* note the offset and start of the in-document chunk */
   docptr->chunk = docptr->html + offset1;

   /* now look for end of chunk (next major heading, or end of document) */
   for (;;)
   {
      count = *lptr++;
      if (!count) break;
      cptr = lptr;
      lptr += count + 1;
      count = *lptr++;
      lptr += count + 1;
      count = *lptr++;
      lptr += count + 1;
      offset2 = *(ULONGPTR)lptr;
      lptr += sizeof(ulong);
      /* chunk of -1 specifically gets zeroeth chunk */
      if (docptr->pchunk < 0)
         if (atoi(cptr) == 1)
            break;
         else
            continue;
      /* if matches it breaks with offset set */
      if (docptr->pchunk+1 == atoi(cptr)) break;
   }
   /* if end of document */
   if (!count) offset2 = docptr->hlength;

   /* determine the length of the in-document chunk */
   docptr->clength = offset2 - offset1;

   /* first write the prologue */
   wasDocWrite (docptr, docptr->html, docptr->proLength);

   /* the page title */
   title = pass2strip (title);
   if (docptr->title[0] && strcmp (docptr->title, title))
      len = sprintf (buf, "<title>%s &ndash; %s</title>\n",
                     docptr->title, title);
   else
      len = sprintf (buf, "<title>%s</title>\n", title);
   wasDocWrite (docptr, buf, len);

   wasDocWrite (docptr, docptr->chunk, docptr->clength);

   return (0);
}

/*****************************************************************************/
/*
Get the status/errno message and insert it into the document.
*/

void wasDocInsertStatus
(
struct wasdoc_st *docptr,
int status
)
{
   ushort  slen;
   char  *cptr, *sptr, *zptr;
   char  msg [256];
   $DESCRIPTOR (MsgDsc, msg);

   status = sys$getmsg (status, &slen, &MsgDsc, 0, 0); 
   if (status & 1)
      msg[slen] = '\0';
   else
      sprintf (msg, "%%X%0808X", status);

   sptr = docptr->html + docptr->hlength;
   zptr = docptr->html + docptr->hsize;
   for (cptr = msg; *cptr && sptr < zptr; *sptr++ = *cptr++);
   docptr->hlength = sptr - docptr->html;
}

/*****************************************************************************/
/*
Some basic rendering stats.
*/

void wasDocStatistics
(
struct wasdoc_st *docptr,
int init
)
{
   /* bit of a fudge to make the elapsed and CPU times look alike! */
   static $DESCRIPTOR (StatFaoDsc,
"elapsed:!%T0 cpu:!UL.!UL0 dio:!UL bio:!UL faults:!UL\0");

   static unsigned long  dsecs = LIB$K_DELTA_SECONDS_F,
                         LibStatTimerReal = 1,
                         LibStatTimerCpu = 2,
                         LibStatTimerBio = 3,
                         LibStatTimerDio = 4,
                         LibStatTimerFaults = 5;

   static $DESCRIPTOR (StatStringDsc, "");

   int  status;
   unsigned long  CpuBinTime,
                  CountBio,
                  CountDio,
                  CountFaults;
   unsigned long  RealTime64 [2];
   float  fsecs;
   char  *cptr, *sptr;

   if (init)
   {
      lib$init_timer (0);
      docptr->statistics[0] = '\0';
      return;
   }

   lib$stat_timer (&LibStatTimerReal, &RealTime64, 0);
   lib$stat_timer (&LibStatTimerCpu, &CpuBinTime, 0);
   lib$stat_timer (&LibStatTimerBio, &CountBio, 0);
   lib$stat_timer (&LibStatTimerDio, &CountDio, 0);
   lib$stat_timer (&LibStatTimerFaults, &CountFaults, 0);

   StatStringDsc.dsc$a_pointer = docptr->statistics;
   StatStringDsc.dsc$w_length = sizeof(docptr->statistics);

   status = sys$fao (&StatFaoDsc, 0, &StatStringDsc,
                     &RealTime64, CpuBinTime/100, CpuBinTime%100,
                     CountDio, CountBio, CountFaults);

   cptr = sptr = docptr->statistics + sizeof("elapsed:")-1;
   if (*cptr == '0') cptr++;
   if (*cptr == '0') cptr++;
   if (*cptr == ':') cptr++;
   if (*cptr == '0') cptr++;
   if (*cptr == '0') cptr++;
   if (*cptr == ':') cptr++;
   if (*cptr == '0') cptr++;
   while (*cptr) *sptr++ = *cptr++;
   *sptr = '\0';

   if (docptr->insight >= 2)
      wasDocInsight (docptr, "%s bytes:%d/%d",
                     docptr->statistics, docptr->hlength, docptr->hsize);

   if (!(status & 1)) exit (status);
}

/*****************************************************************************/
/*
Read a directory looking for source files.
Append each source file to the source text.
*/

int ProcessDirectory (struct wasdoc_st *docptr)

{
   int  error = 0;
   char  *cptr, *fptr, *sptr, *zptr;
   char  fname [256],
         pname [sizeof(fname)];
   struct dirent  *entptr;
   DIR  *dirptr;

   /* pre-fill the file name buffer with the directory name */
   zptr = (sptr = fname) + sizeof(fname)-1;
   for (cptr = docptr->dname; *cptr && sptr < zptr; *sptr++ = *cptr++);
   while (sptr > docptr->dname && *sptr != '/' && *sptr != ']') sptr--;
   if (sptr > docptr->dname) sptr++;
   *(fptr = sptr) = '\0';

   if (dbug>1) dbugThis (FI_LI, "ProcessDirectory() %s", fname);

   dirptr = opendir (fname);

   if (!dirptr) return (vaxc$errno);

   pname[0] = '\0';

   while (entptr = readdir(dirptr))
   {
      if (dbug>1) dbugThis (FI_LI, "%s", entptr->d_name);

      /* any file name not beginning with an alphanumeric is ignored */
      if (!isalnum(*entptr->d_name)) continue;

      /* find the file type and ignore if not */
      for (cptr = sptr = entptr->d_name; *sptr; sptr++);
      zptr = sptr;
      while (sptr > cptr && *sptr != ';' && *sptr != '.') sptr--;
      if (*sptr == ';') zptr = sptr;
      while (sptr > cptr && *sptr != '.') sptr--;
      /* if the type is not the same */
      if (zptr - sptr != docptr->ftlength) continue; 
      if (strncasecmp (sptr, docptr->ftype, docptr->ftlength)) continue;

      if (docptr->insight >= 2) wasDocInsight (docptr, "%s", fname);

      /* append the file name to the pre-filled directory name */
      zptr = fname + sizeof(fname)-1;
      for (sptr = fptr;
           *cptr && *cptr != ';' && sptr < zptr;
           *sptr++ = *cptr++);
      *sptr = '\0';

      /* if another version of the name file */
      if (dbug>1) dbugThis (FI_LI, "%s %s", pname, fname);
      if (!strcmp (fname, pname)) continue;
      strcpy (pname, fname);
      docptr->ftotal++;

      if (error = ProcessFile (docptr, fname)) break;
   }

   closedir (dirptr);

   return (error);
}

/*****************************************************************************/
/*
Append the file content to the source text.  Return 0 or vaxc$errno.
*/

int ProcessFile
(
struct wasdoc_st *docptr,
char *fname
)
{
   int  bytes;
   char  *cptr, *sptr;
   FILE  *ifptr;
   stat_t  fstatbuf;

   if (dbug>1) dbugThis (FI_LI, "ProcessFile() %s", fname);

   if ((ifptr = fopen (fname, "r")) == NULL) return (vaxc$errno);

   if (fstat (fileno(ifptr), &fstatbuf) < 0) return (vaxc$errno);

   /* extra bytes allow EOF when reading past the EOF */
   bytes = fstatbuf.st_size;
   bytes += strlen(fname) + 32;
   docptr->tsize += bytes;

   docptr->text = realloc (docptr->text, docptr->tsize);
   if (!docptr->text) return (vaxc$errno);

   sptr = docptr->text + docptr->tlength;

   /* the source should end up as an HTML comment */
   for (cptr = "\\<!-- source:"; *cptr; *sptr++ = *cptr++);
   for (cptr = fname; *cptr; cptr++);
   while (cptr > fname && *cptr != '/' && *cptr != ']') cptr--;
   if (cptr > fname) cptr++;
   while (*cptr) *sptr++ = *cptr++;
   for (cptr = " --\\>\n"; *cptr; *sptr++ = *cptr++);
   *(cptr = sptr) = '\0';

   /* read the entire file into memory */
   while (fgets (sptr, bytes-(sptr-cptr), ifptr))
      while (*sptr) sptr++;

   fclose (ifptr);

   docptr->tlength = sptr - docptr->text;

   return (0);
}

/*****************************************************************************/
/*
Write the supplied data.
If length is 0 then determine the lemgth of the string. 
If -1 then flush/close the output.

Hmmmm.  Thought CGILIB dealt with the contraints of the Apache socket record
size but it proved necessary to write larger documents in size-constrained
records for it.  This is applied to all environments without practical impact. 
Return zero if successful or an vaxc$errno code if not.
*/

int wasDocWrite
(
struct wasdoc_st *docptr,
char *data,
int length
)
{
#define MAXWR 32000

   int  retval = 0;

   if (length <= 0 && data) length = strlen(data);

   if (docptr->isDynamic)
   {
      if (length < 0)
         fflush (stdout);
      else
      while (length)
      {
         if (length > MAXWR)
         {
            if (!fwrite (data, MAXWR, 1, stdout))
            {
               retval = vaxc$errno;
               break;
            }
            data += MAXWR;
            length -= MAXWR;
         }
         else
         {
            if (!fwrite (data, length, 1, stdout))
            {
               retval = vaxc$errno;
               break;
            }
            length = 0;
         }
      }
   }
   else
   if (docptr->oname[0])
      retval = CliWriteFile (docptr, data, length);
   else
   if (length < 0)
      fflush (stdout);
   else
   if (!fwrite (data, length, 1, stdout))
      retval = vaxc$errno;

   return (retval);
}

/*****************************************************************************/
/*
Print the insight data.
*/

void wasDocInsight
(
struct wasdoc_st *docptr,
char *fmt,
...
)
{
   int  argcnt, retval;
   char  buf [1024];
   va_list  args;

   va_start (args, fmt);
   retval = vsnprintf (buf, sizeof(buf), fmt, args);
   if (retval < 0) sprintf (buf, "[%s]", strerror(EVMSERR,vaxc$errno));
   va_end (args);

   if (docptr->isStatic && !docptr->insight2doc)
      fprintf (stdout, "%s\n", buf);
   else
   if (docptr->html)
      wasDocPrintf (docptr, "<span class=\"insight\">%s</span>", buf);
}

/*****************************************************************************/
/*
Exit with a documented SS$_BUGCHECK.
*/

void wasDocBugcheck
(
char *file,
int line
)
{
   char  *cptr, *sptr;
   cptr = sptr = strdup (file);
   while (*cptr) cptr++;
   while (cptr > sptr && *cptr != ';') cptr--;
   *cptr-- = '\0';
   while (cptr > file && *cptr != ']') cptr--;
   cptr++;

   if (isCgi) fprintf (stdout, "Status: 502\n\n<!DOCTYPE html>\n");
   fprintf (stdout, "%s:%d\n", cptr, line);
   exit (SS$_BUGCHECK);
}

/*****************************************************************************/
/*
Print the a "debug" statement.
*/

void dbugThis
(
char *file,
int line,
char *fmt,
...
)
{
   int  argcnt, retval;
   char  *cptr, *sptr, *zptr;
   char  flbuf [256];
   va_list  args;

   if (!dbug) return;

   if (file)
   {
      zptr = (sptr = flbuf) + sizeof(flbuf)-24;
      *sptr++ = '[';
      *sptr++ = '[';
      for (cptr = file; *cptr; cptr++);
      while (cptr > file && *(cptr-1) != ']') cptr--;
      while (*cptr && *cptr != ';' && sptr < zptr) *sptr++ = *cptr++;
      sptr += sprintf (sptr, ":%d]]", line);
      fprintf (stdout, "%-18s\0xff\0xff", flbuf);
   }
   else
      strcpy (flbuf, "\0xff\0xff");

   for (cptr = fmt; *cptr == '+'; cptr++);
   if (*cptr == ' ') while (*cptr == ' ') cptr++;
   va_start (args, fmt);
   retval = vfprintf (stdout, cptr, args);
   if (retval < 0) fprintf (stdout, "[%s]", strerror(EVMSERR,vaxc$errno));
   va_end (args);

   fputs ("\n", stdout);
   fflush (stdout);
}

/*****************************************************************************/
/*
Replace selected characters with hex codes for clarity.
*/

char* dbugMax (char *string) { return dbugString (string, 128); }
char* dbugAll (char *string) { return dbugString (string, INT_MAX); }

char* dbugString (char *string, int max)

{
   static int  size;
   static char  *buf;
   char  *cptr, *sptr, *zptr;

   if (!string) return ("(null)");
   if (!size) buf = realloc (buf, size += 256);
   zptr = (sptr = buf) + size-3;
   *sptr++ = bvbar;
   for (cptr = string; *cptr && max--; cptr++)
   {
      if (sptr >= zptr)
      {
         if (!size) buf = realloc (buf, size += 256);
         zptr = (sptr = buf) + size-3;
      }
      if ((*cptr >= 0 && *cptr <= 31) || *cptr == 127 ||
          *cptr == multx || *cptr == bvbar)
         sptr += sprintf (sptr, "%c%02X", multx, *cptr);
      else
         *sptr++ = *cptr;
   }
   *sptr++ = bvbar;
   if (max > 0 && *cptr) *sptr++ = ellip;
   *sptr = '\0';
   return (buf);
}

/*****************************************************************************/
/*
If |that| contains |this| then enable dbug.
For example: dbugIf("options",hptr,-1);
*/

void dbugIf
(
char *that,
char *this,
int value
)
{
#define SIZE 64
   static int  dbug2 = -999;
   int  yes;
   char  ch;

   if (dbug2 == -999) dbug2 = dbug;
   dbug = 0;
   if (!dbug2) return;
   ch = that[SIZE];
   that[SIZE] = '\0';
   if (strstr (that, this)) dbug = value;
   if (dbug) dbugThis (FI_LI, "\"%s\" in \"%s\"", this, that);
   that[SIZE] = ch;
}

/*****************************************************************************/
/*
Low-cost kludge.
*/

#ifdef __VAX

int vsnprintf
(
char *str,
size_t n,
const char *fmt,
va_list ap
)
{
  int error = 0, size;
  char  buf [65535];

  if (!str)
  {
     str = buf;
     if (n > sizeof(buf)) EXIT_FI_LI (SS$_BUGCHECK);
     n = sizeof(buf);
  }

  size = vsprintf (str, fmt, ap);
  if (size < n) return (size);
  EXIT_FI_LI (SS$_BUGCHECK);
}

#endif /* __VAX */

/*****************************************************************************/