ae1e222abc4f609e3b44fde8ff8f589cb612bae7
[oweals/cde.git] / cde / programs / dtinfo / dtinfogen / infolib / etc / dtinfogen_worker.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these libraries and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $XConsortium: dtinfogen_worker.c /main/24 1996/11/26 12:35:07 cde-hal $
24  *
25  * (c) Copyright 1996 Digital Equipment Corporation.
26  * (c) Copyright 1996 Hewlett-Packard Company.
27  * (c) Copyright 1996 International Business Machines Corp.
28  * (c) Copyright 1996 Sun Microsystems, Inc.
29  * (c) Copyright 1996 Novell, Inc. 
30  * (c) Copyright 1996 FUJITSU LIMITED.
31  * (c) Copyright 1996 Hitachi.
32  */
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <stdarg.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <unistd.h>
40 # include <libgen.h> /* for dirname() */
41 #include <ctype.h>
42 #include <signal.h>
43 #if !defined(CSRG_BASED)
44 #include <sys/sysmacros.h>
45 #endif
46 #include <sys/param.h>
47 #include <sys/stat.h>
48 #include <X11/Intrinsic.h>
49 #include <Dt/Utility.h>
50
51 #include <lib/DtSvc/DtUtil2/LocaleXlate.h>
52
53 #define LANG_COMMON     "C"             /* default os language */
54 #define CLANG_COMMON    "C.ISO-8859-1"  /* default canonical language */
55
56 #define XtsNewString(str) \
57     ((str) != NULL ? (char *)(memcpy(XtMalloc((unsigned)strlen(str) + 1), \
58      str, (unsigned)strlen(str) + 1)) : NULL)
59
60 typedef enum
61 {
62     FSTAT_EXISTS        = 1 << 0,
63     FSTAT_IS_FILE       = 1 << 1,
64     FSTAT_IS_DIR        = 1 << 2,
65     FSTAT_IS_READABLE   = 1 << 3,
66     FSTAT_IS_WRITABLE   = 1 << 4,
67     FSTAT_IS_EXECUTABLE = 1 << 5
68 } FileStatFlags;
69
70 typedef enum
71 {
72     INTEGER_CODE = 4,
73     STRING_CODE = 6,
74     OID_CODE = 7,
75     COMPRESSED_STRING_CODE = 11,
76     SHORT_LIST_CODE = 105,
77     OID_LIST_CODE = 106,
78     NODE_META = 401
79 } MMDBConstants;
80
81 typedef struct _GlobalsStruct
82 {
83     char *pathEnv;
84     char *ldLibraryPathEnv;
85     char *libPathEnv;
86     char *shlibPathEnv;
87     char *lcxPathEnv;
88     char *dtInfoHomeEnv;
89     char *dtInfoBinEnv;
90     char *tmpDirEnv;
91     char *sgmlPathEnv;
92     char *sgmlSearchPathEnv;
93     char *sgmlCatFilesEnv;
94     char *sgmlCatFiles;
95     int sgmlCatFilesLen;
96     int sgmlCatFilesMaxLen;
97     char *mmdbPathEnv;
98
99     char *install;
100     char *arch;
101     char *lang;         /* os-specific language (value of env) */
102     char *clang;        /* canonical language */
103     int   dtsridx;      /* dtsearch: index into langtbl */
104     char *sgml;
105     char *decl;
106     char *styleProlog;
107     char *tmpDir;
108     char *spec;
109     int dirMode;
110     char *searchEngine;
111     char *parser;
112
113     Boolean verbose;
114     Boolean keepWorkDir;
115     char *workDir;
116
117     char *library;
118     char *libDesc;
119     char *bookCase;
120     char *libName;
121     char *outFile;
122     char *saveESIS;
123     Boolean loadESIS;
124     int tocElemIndex;
125     char *id;
126     char *title;
127
128     char *dtsrlib;
129     char *dbdfile;
130     char *keytypes;
131 } GlobalsStruct;
132
133 typedef struct _TOCEntry
134 {
135     char *ord;
136     char *entry;
137 } TOCEntry;
138
139 typedef struct _TOCRecord
140 {
141     int code;
142     char *loc;
143     char *file;
144     char *line;
145     char *ord;
146     char *title;
147 } TOCRecord;
148
149 #define TOC_REC_COLS 6
150
151 static char *TOCElems[] =
152 {
153     "TOCPart",
154     "TOCChap",
155     "TOClevel1",
156     "TOClevel2",
157     "TOClevel3",
158     "TOClevel4",
159     "TOClevel5"
160 };
161
162 static char *emptyString = "";
163
164 #define STR(s) ((s) ? (s) : emptyString)
165
166 #define EXEC_NAME "dtinfogen"
167
168 typedef struct
169 {
170   const char* name;
171   int         dtsrlang;
172   const char* sfx;
173   const char* stp;
174   const char* knj;
175 } t_entry;
176
177 static
178 t_entry langtbl[] =
179 {
180   { "C.ISO-8859-1",     1,      "eng.sfx",      "eng.stp",      NULL },
181   { "es_ES.ISO-8859-1", 2,      "esp.sfx",      "esp.stp",      NULL },
182   { "fr_FR.ISO-8859-1", 3,      "fra.sfx",      "fra.stp",      NULL },
183   { "it_IT.ISO-8859-1", 4,      "ita.sfx",      "ita.stp",      NULL },
184   { "de_DE.ISO-8859-1", 5,      "deu.sfx",      "deu.stp",      NULL },
185   { "ja_JP.EUC-JP",     7,      NULL,           NULL,           "jpn.knj" },
186   { NULL,               0,      NULL,           NULL,           NULL }
187 };
188
189 static char *usageMsg1 = "USAGE:\n\
190   " EXEC_NAME " -h\n\
191   " EXEC_NAME " admin\n\
192   " EXEC_NAME " build [-h] [-T <tmpdir>] [-m <catalog>] [-d <library description>]\n\
193            [-n <library short name>] -l <library> <bookcase-doc>...\n\
194   " EXEC_NAME " tocgen [-h] [-T <tmpdir>] [-m <catalog>] -f <tocfile> [-id <tocid>]\n\
195             [-title <toctitle>] <document>...\n\
196   " EXEC_NAME " update [-h] [-m <catalog>] -l <library> -b <bookcase> <stylesheet>\n\
197   " EXEC_NAME " validate [-h] [-T <tmpdir>] [-m <catalog>] <document>...\n"
198   "\n"
199   "options:\n";
200
201 static char *usageMsg2 = "\
202     -T <tmpdir>          directory for intermediate processing files\n\
203     -h                   help: show usage\n\
204     -v                   verbose: more diagnostic output\n";
205
206 static GlobalsStruct *gStruct;
207
208 static void printUsage(char *preMsg, int exitCode);
209 static void dieRWD(int exitCode, char *format, ...);
210 static void die(int exitCode, char *format, ...);
211 static void sigHandler(int sig);
212 static void touchFile(char *fileName);
213 static Boolean checkStat(char *fileName, unsigned int flags);
214 static void checkDir(char *dirName);
215 static void checkExec(char *execName);
216 static char *makeTmpFile(void);
217 static char *makeWorkDir(void);
218 static void removeWorkDir(void);
219 static void runShellCmd(char *cmd);
220 static Boolean doAdmin(int argc, char *argv[]);
221 static Boolean doBuild(int argc, char *argv[]);
222 static Boolean doTocgen(int argc, char *argv[]);
223 static Boolean doUpdate(int argc, char *argv[]);
224 static Boolean doValidate(int argc, char *argv[]);
225 static Boolean doHelp(int argc, char *argv[]);
226 static void appendStr(char **str, int *curLen, int *maxLen, char *strToAppend);
227 static char *makeAbsPathEnv(char *var);
228 static char *makeAbsPathStr(char *str);
229 static char *addToEnv(char *var, char *addMe, Boolean prepend);
230 static char *buildPath(char *format, ...);
231 static char *buildSGML(void);
232 static char *buildDecl(void);
233 static char *buildStyleProlog(void);
234 static char *buildSpec(void);
235 static void defaultGlobals(void);
236 static void checkGlobals(void);
237 static int parseArgs(int argc, char *argv[]);
238 static char *parseDocument(Boolean runCmd, ...);
239 static void buildBookcase(char *cmdSrc, char *dirName);
240 static char *storeBookCase(char *cmdSrc, char *tocOpt, char *dbName,
241                            char *dirName);
242 static Boolean findBookCaseNameAndDesc(char *tmpFile, char *bookCaseName,
243                                        char *bookCaseDesc);
244 static void validateBookCaseName(char *bookCaseName);
245 static void validateBookCase(char *mapName, char *bookCaseName);
246 static void editMapFile(char *bookCaseName, char *bookCaseMap);
247 static void buildTOC(int argc, char *argv[]);
248 static char *tocBookcase(int argc, char *argv[]);
249 static void makeTOC(char *id, char *title);
250 static char *sgmlData(char *inData);
251 static char *replaceData(char *inData, char *replaceMe, char *replacement);
252 static void addTOCEntry(TOCEntry **tocEntries, int *curSize, int *maxSize,
253                         TOCEntry *newEntry);
254 static int compareTOCEntries(const void *entry1, const void *entry2);
255 static void sortTOCEntries(TOCEntry *tocEntries, int nTOCEntries);
256 static void freeTOCEntries(TOCEntry *tocEntries, int nTOCEntries);
257 static TOCRecord *getTOCRecord(FILE *fp);
258 static void freeTOCRecord(TOCRecord *);
259 static char *getTOCField(FILE *fp);
260
261 static void
262 printUsage(char *preMsg, int exitCode)
263 {
264     if (preMsg)
265         fputs(preMsg, stderr);
266
267     fputs(usageMsg1, stderr);
268     fputs(usageMsg2, stderr);
269
270     exit(exitCode);
271 }
272
273 static void
274 dieRWD(int exitCode, char *format, ...)
275 {
276     va_list ap;
277
278     va_start(ap, format);
279     vfprintf(stderr, format, ap);
280     va_end(ap);
281
282     if (!gStruct->keepWorkDir)
283         removeWorkDir();
284
285     exit(exitCode);
286 }
287
288 static void
289 die(int exitCode, char *format, ...)
290 {
291     va_list ap;
292
293     va_start(ap, format);
294     vfprintf(stderr, format, ap);
295     va_end(ap);
296
297     exit(exitCode);
298 }
299
300 static void
301 sigHandler(int sig)
302 {
303     gStruct->keepWorkDir = False;       /* Always clean up on signal. */
304     dieRWD(-1, "Received signal; exiting...\n");
305 }
306
307 static void
308 touchFile(char *fileName)
309 {
310     FILE *fp;
311
312     if ((fp = fopen(fileName, "w")) == (FILE *)NULL)
313         dieRWD(-1, "%s: %s: %s\n",
314                EXEC_NAME, fileName, strerror(errno));
315
316     fprintf(fp, "%s\n",
317             "$XConsortium: dtinfogen_worker.c /main/24 1996/11/26 12:35:07 cde-hal $");
318
319     fclose(fp);
320 }
321
322 static Boolean
323 checkStat(char *fileName, unsigned int flags)
324 {
325     struct stat statBuf;
326     Boolean userOwnsFile;
327     Boolean userInGroup;
328
329     if (!fileName)
330         return False;
331
332     errno = 0;
333
334     if (stat(fileName, &statBuf) != 0)
335         return False;
336
337     if ((flags & FSTAT_IS_FILE) &&
338         (!S_ISREG(statBuf.st_mode)))
339     {
340         errno = ENOENT;
341         return False;
342     }
343
344     if ((flags & FSTAT_IS_DIR) &&
345         (!S_ISDIR(statBuf.st_mode)))
346     {
347         errno = ENOTDIR;
348         return False;
349     }
350
351     userOwnsFile = (getuid() == statBuf.st_uid);
352     userInGroup = (getgid() == statBuf.st_gid);
353
354     errno = EACCES;
355     if (flags & FSTAT_IS_READABLE)
356     {
357         if (userOwnsFile)
358         {
359             if (!(statBuf.st_mode & S_IRUSR))
360                 return False;
361         }
362         else if (userInGroup)
363         {
364             if (!(statBuf.st_mode & S_IRGRP))
365                 return False;
366         }
367         else if (!(statBuf.st_mode & S_IROTH))
368             return False;
369     }
370
371     if (flags & FSTAT_IS_WRITABLE)
372     {
373         if (userOwnsFile)
374         {
375             if (!(statBuf.st_mode & S_IWUSR))
376                 return False;
377         }
378         else if (userInGroup)
379         {
380             if (!(statBuf.st_mode & S_IWGRP))
381                 return False;
382         }
383         else if (!(statBuf.st_mode & S_IWOTH))
384             return False;
385     }
386
387     if (flags & FSTAT_IS_EXECUTABLE)
388     {
389         if (userOwnsFile)
390         {
391             if (!(statBuf.st_mode & S_IXUSR))
392                 return False;
393         }
394         else if (userInGroup)
395         {
396             if (!(statBuf.st_mode & S_IXGRP))
397                 return False;
398         }
399         else if (!(statBuf.st_mode & S_IXOTH))
400             return False;
401     }
402
403     errno = 0;
404     return True;
405 }
406
407 static void
408 checkDir(char *dirName)
409 {
410     char cmdBuf[MAXPATHLEN + 10];
411
412     if (!dirName)
413         return;
414
415     if (checkStat(dirName, FSTAT_IS_DIR))
416     {
417         if (checkStat(dirName, FSTAT_IS_WRITABLE))
418             return;
419
420         dieRWD(-1, "%s: %s not writable\n", EXEC_NAME, dirName);
421     }
422
423     snprintf(cmdBuf, sizeof(cmdBuf), "mkdir -p %s", dirName);
424     runShellCmd(cmdBuf);
425 }
426
427 static Boolean
428 testExec(char *execName, Boolean tellIt)
429 {
430     char *path;
431     char **pathVector;
432     int i;
433     char execBuf[MAXPATHLEN + 1];
434     Boolean found;
435
436     if (!execName)
437         return True;
438
439     if ((path = getenv("PATH")) == (char *)NULL)
440         return True;
441
442     path = XtsNewString(path);
443     pathVector = _DtVectorizeInPlace(path, ':');
444
445     for (i = 0; pathVector[i] != (char *)NULL; i++)
446     {
447         snprintf(execBuf, sizeof(execBuf), "%s/%s", pathVector[i], execName);
448         if (checkStat(execBuf, FSTAT_IS_EXECUTABLE))
449             break;
450     }
451
452     found = (pathVector[i] != (char *)NULL);
453
454     XtFree((char *)pathVector);
455     XtFree(path);
456
457     if (found && tellIt)
458         fprintf(stderr, "%s ==> %s\n", execName, execBuf);
459
460     return found;
461 }
462
463 static void
464 checkExec(char *execName)
465 {
466     if (!testExec(execName, gStruct->verbose))
467         die(-1, "%s: %s not found\n", EXEC_NAME, execName);
468 }
469
470 static char *
471 makeTmpFile(void)
472 {
473     int i;
474     char *tmpFile;
475     char *workDir;
476
477     workDir = makeWorkDir();
478     for (i = 1; i != 0; i++)
479     {
480         tmpFile = buildPath("%s/tmp.%d", workDir, i);
481         if (!checkStat(tmpFile, FSTAT_EXISTS))
482             return tmpFile;
483
484         XtFree(tmpFile);
485     }
486
487     dieRWD(-1, "%s: could not create tmp file.\n", EXEC_NAME);
488
489     /* NOTREACHED */
490     return (char *)NULL;
491 }
492
493 /* The return from this function should NOT be freed. */
494 static char *
495 makeWorkDir(void)
496 {
497     char *workDir;
498     char cmdBuf[MAXPATHLEN + 10];
499     int i;
500
501     if (gStruct->workDir)
502         return gStruct->workDir;
503
504     if (!checkStat(gStruct->tmpDir, FSTAT_IS_DIR | FSTAT_IS_WRITABLE))
505         dieRWD(-1, "%s: %s: %s\n",
506                EXEC_NAME, gStruct->tmpDir, strerror(errno));
507
508     /* Start with suffix "1"; if we wrap to "0" */
509     /* (that's LOTS of directories), die. */
510     for (i = 1; i != 0; i++)
511     {
512         workDir = buildPath("%s/otk.%d", gStruct->tmpDir, i);
513         if (!checkStat(workDir, FSTAT_EXISTS))
514         {
515             snprintf(cmdBuf, sizeof(cmdBuf), "mkdir -p %s", workDir);
516             runShellCmd(cmdBuf);
517             gStruct->workDir = workDir;
518
519             return workDir;
520         }
521
522         XtFree(workDir);
523     }
524
525     dieRWD(-1, "%s: could not create work directory.\n", EXEC_NAME);
526
527     /* NOTREACHED */
528     return (char *)NULL;
529 }
530
531 static void
532 removeWorkDir(void)
533 {
534     int ret;
535     char cmdBuf[MAXPATHLEN + 10];
536
537     if (gStruct->workDir)
538     {
539         snprintf(cmdBuf, sizeof(cmdBuf), "rm -rf %s", gStruct->workDir);
540         ret = system(cmdBuf);
541         if(ret != 0) die(-1, "system for rm failed; exiting...\n");
542         XtFree(gStruct->workDir);
543         gStruct->workDir = (char *)NULL;
544     }
545 }
546
547 static void
548 runShellCmd(char *cmd)
549 {
550     if (gStruct->verbose)
551         fprintf(stdout, "%s\n", cmd);
552
553     if (system(cmd) != 0)
554         dieRWD(-1, "%s: command failed: %s\n", EXEC_NAME, cmd);
555 }
556
557 static Boolean
558 doAdmin(int argc, char *argv[])
559 {
560     if (strcmp(argv[1], "admin") == 0)
561     {
562         if (argc > 2)
563             printUsage(EXEC_NAME ": too many arguments\n", -1);
564
565         runShellCmd("Librarian");
566
567         return True;
568     }
569
570     return False;
571 }
572
573 static Boolean
574 doBuild(int argc, char *argv[])
575 {
576     if (strcmp(argv[1], "build") == 0)
577     {
578         char *bookcase;
579         char *bcCopy;
580         char *dirName;
581         char *cmdSrc;
582         char *fileToTouch;
583         int argsProcessed = parseArgs(argc - 2, &(argv[2])) + 2;
584
585         if (!gStruct->library)
586             printUsage(EXEC_NAME ": -l required\n", -1);
587         if (argsProcessed >= argc)
588             printUsage(EXEC_NAME ": no bookcases given\n", -1);
589
590         checkDir(gStruct->library);
591
592         if (!gStruct->libDesc)
593             gStruct->libDesc = "Information Library";
594
595         checkGlobals();
596
597         if (argsProcessed >= argc)
598             printUsage(EXEC_NAME ": no bookcases given\n", -1);
599
600         signal(SIGINT, sigHandler);
601         signal(SIGTERM, sigHandler);
602
603         for ( ; argsProcessed < argc; argsProcessed++)
604         {
605             bookcase = argv[argsProcessed];
606             if (!checkStat(bookcase, FSTAT_IS_READABLE))
607                 dieRWD(-1, "%s: %s: %s\n",
608                        EXEC_NAME, bookcase, strerror(errno));
609
610             bcCopy = XtsNewString(bookcase);
611             if ((dirName = dirname(bcCopy)) == (char *)NULL)
612                 dirName = ".";
613             dirName = XtsNewString(dirName);
614             XtFree(bcCopy);
615
616             if (gStruct->loadESIS)
617             {
618                 cmdSrc = buildPath("cat %s", bookcase);
619             }
620             else
621             {
622                 cmdSrc = parseDocument(False, bookcase, 0);
623             }
624
625             buildBookcase(cmdSrc, dirName);
626
627             XtFree(cmdSrc);
628             XtFree(dirName);
629         }
630
631         if (!gStruct->libName)
632             gStruct->libName = "infolib";
633         fileToTouch = buildPath("%s/%s.oli",
634                                 gStruct->library, gStruct->libName);
635         touchFile(fileToTouch);
636         XtFree(fileToTouch);
637
638         return True;
639     }
640
641     return False;
642 }
643
644 static Boolean
645 doTocgen(int argc, char *argv[])
646 {
647     if (strcmp(argv[1], "tocgen") == 0)
648     {
649         int argsProcessed = parseArgs(argc - 2, &(argv[2])) + 2;
650
651         if (!gStruct->id)
652             gStruct->id = "TOC-NODE-ID";
653         if (!gStruct->title)
654             gStruct->title = "Table of Contents";
655
656         checkGlobals();
657
658         if (argsProcessed >= argc)
659             printUsage(EXEC_NAME ": book document(s) required\n", -1);
660         if (!gStruct->outFile)
661             printUsage(EXEC_NAME ": -f required for tocgen\n", -1);
662
663         signal(SIGINT, sigHandler);
664         signal(SIGTERM, sigHandler);
665
666         buildTOC(argc - argsProcessed, &argv[argsProcessed]);
667
668         return True;
669     }
670     return False;
671 }
672
673 static Boolean
674 doUpdate(int argc, char *argv[])
675 {
676     if (strcmp(argv[1], "update") == 0)
677     {
678         char bookCaseBuf[MAXPATHLEN + 1];
679         char *styleSheet;
680         char *cmdSrc;
681         char *cmd;
682         int argsProcessed = parseArgs(argc - 2, &(argv[2])) + 2;
683
684         checkGlobals();
685
686         if (!gStruct->library)
687             printUsage(EXEC_NAME ": -l required for update\n", -1);
688         if (!checkStat(gStruct->library, FSTAT_IS_DIR))
689             die(-1, "%s: %s: %s\n", EXEC_NAME,
690                 gStruct->library, strerror(errno));
691
692         if (!gStruct->bookCase)
693             printUsage(EXEC_NAME ": -b required\n", -1);
694         snprintf(bookCaseBuf, sizeof(bookCaseBuf), "%s/%s",
695                                 gStruct->library, gStruct->bookCase);
696         if (!checkStat(bookCaseBuf, FSTAT_IS_DIR))
697             die(-1, "%s: No such bookcase: %s\n", EXEC_NAME,
698                 gStruct->bookCase);
699
700         if (!checkStat(gStruct->styleProlog, FSTAT_IS_READABLE))
701             die(-1, "%s: faulty installation: %s\n", EXEC_NAME,
702                 gStruct->styleProlog);
703
704         if (argsProcessed >= argc)
705             printUsage((char *)NULL, -1);
706         styleSheet = argv[argsProcessed++];
707         if (!checkStat(styleSheet, FSTAT_IS_READABLE))
708             die(-1, "%s: %s: %s\n", EXEC_NAME, styleSheet, strerror(errno));
709
710         cmdSrc = parseDocument(False,
711                                gStruct->styleProlog, styleSheet, 0);
712         cmd = buildPath("%s | StyleUpdate %s %s",
713                         cmdSrc, gStruct->library, gStruct->bookCase);
714         runShellCmd(cmd);
715
716         XtFree(cmdSrc);
717         XtFree(cmd);
718
719         return True;
720     }
721
722     return False;
723 }
724
725 static Boolean
726 doValidate(int argc, char *argv[])
727 {
728     if (strcmp(argv[1], "validate") == 0)
729     {
730         char *cmdSrc;
731         char *cmd;
732         char *bookcase;
733         int argsProcessed = parseArgs(argc - 2, &(argv[2])) + 2;
734
735         checkGlobals();
736
737         if (argsProcessed >= argc)
738             printUsage(EXEC_NAME ": no documents given\n", -1);
739
740         for ( ; argsProcessed < argc; argsProcessed++)
741         {
742             bookcase = argv[argsProcessed];
743             if (!checkStat(bookcase, FSTAT_IS_READABLE))
744                 dieRWD(-1, "%s: %s: %s\n",
745                        EXEC_NAME, bookcase, strerror(errno));
746
747             if (gStruct->saveESIS)
748             {
749                 cmdSrc = parseDocument(False, bookcase, 0);
750                 cmd = buildPath("%s > %s", cmdSrc, gStruct->saveESIS);
751                 runShellCmd(cmd);
752                 XtFree(cmdSrc);
753                 XtFree(cmd);
754             }
755             else
756             {
757                 parseDocument(True, bookcase, 0);
758             }
759         }
760
761         return True;
762     }
763
764     return False;
765 }
766
767 static Boolean
768 doHelp(int argc, char *argv[])
769 {
770     if (strcmp(argv[1], "-h") == 0)
771     {
772         printUsage((char *)NULL, 0);
773
774         /* NOTREACHED */
775         return True;
776     }
777
778     return False;
779 }
780
781 static void
782 appendStr(char **str, int *curLen, int *maxLen, char *strToAppend)
783 {
784     int len;
785     char *newStr;
786
787     if (!strToAppend)
788         return;
789     len = strlen(strToAppend);
790     while (*curLen + len >= *maxLen)
791     {
792         newStr = XtRealloc(*str, (*maxLen + MAXPATHLEN) * sizeof(char));
793         if (!newStr)
794             return;
795
796         *str = newStr;
797         *maxLen += MAXPATHLEN;
798     }
799
800     *((char *) memcpy (&((*str)[*curLen]), strToAppend, len) + len) = '\0';
801     *curLen += len;
802 }
803
804 static char *
805 makeAbsPathEnv(char *var)
806 {
807     char *oldPath, *newPath = NULL;
808     char *newVar;
809     int pathlen;
810
811     if (!var)
812         return (char *)NULL;
813
814     oldPath = getenv(var);
815     if (oldPath == (char *)NULL)
816         return (char *)NULL;
817
818     newPath = makeAbsPathStr(oldPath);
819
820     if (gStruct->verbose)
821         fprintf(stdout, "Expanding %s\n  from %s\n  to %s\n",
822                 var, oldPath, newPath);
823
824     pathlen = strlen(var) + strlen(newPath) + 2;
825     newVar = XtMalloc(pathlen);
826     snprintf(newVar, pathlen, "%s=%s", var, newPath);
827     putenv(newVar);
828
829     XtFree(newPath);
830
831     return newVar;
832 }
833
834 static char *
835 makeAbsPathStr(char *str)
836 {
837     char *newPath = (char *)NULL;
838     int newPathLen = 0;
839     int maxPathLen = 0;
840     char cwd[MAXPATHLEN + 1];
841     char **cwdVector;
842     int cwdVectorLen;
843     char **pathVector;
844     char **dirVector;
845     int i, j, k;
846
847     if (str == (char *)NULL)
848         return (char *)NULL;
849     str = XtsNewString(str);
850
851     if (!getcwd(cwd, MAXPATHLEN))
852         cwd[0] = '\0';
853
854     /* Vectorize current dir, skipping over leading '/' */
855     cwdVector = _DtVectorizeInPlace(cwd + 1, '/');
856     for (cwdVectorLen = 0;
857          cwdVector[cwdVectorLen] != (char *)NULL;
858          cwdVectorLen++)
859         /* EMPTY */
860         ;
861
862     /* Vectorize incoming path */
863     pathVector = _DtVectorizeInPlace(str, ':');
864
865     for (i = 0; pathVector[i] != (char *)NULL; i++)
866     {
867         if (i > 0)
868             appendStr(&newPath, &newPathLen, &maxPathLen, ":");
869
870         if ((pathVector[i][0] == '/') ||  /* Fully-specified pathname. */
871             ((pathVector[i][0] == '.') && /* Current dir. */
872              (pathVector[i][1] == '\0')))
873         {
874             appendStr(&newPath, &newPathLen, &maxPathLen, pathVector[i]);
875             continue;
876         }
877
878         dirVector = _DtVectorizeInPlace(pathVector[i], '/');
879         for (j = 0;
880              dirVector[j] != (char *)NULL && (strcmp(dirVector[j], "..") == 0);
881              j++)
882             /* EMPTY */
883             ;
884
885         if ((j >= cwdVectorLen) && (dirVector[j] == (char *)NULL))
886         {
887             appendStr(&newPath, &newPathLen, &maxPathLen, "/");
888         }
889         else
890         {
891             for (k = 0; k < cwdVectorLen - j; k++)
892             {
893                 appendStr(&newPath, &newPathLen, &maxPathLen, "/");
894                 appendStr(&newPath, &newPathLen, &maxPathLen, cwdVector[k]);
895             }
896             for ( ; dirVector[j] != (char *)NULL; j++)
897             {
898                 appendStr(&newPath, &newPathLen, &maxPathLen, "/");
899                 appendStr(&newPath, &newPathLen, &maxPathLen, dirVector[j]);
900             }
901         }
902
903         XtFree((char *)dirVector);
904     }
905
906     XtFree((char *)pathVector);
907     XtFree((char *)cwdVector);
908     XtFree(str);
909
910     return newPath;
911 }
912
913 static char *
914 addToEnv(char *var, char *addMe, Boolean prepend)
915 {
916     char *envStr;
917     int len, ptrlen;
918     char *ptr;
919
920     if (!var)
921         return (char *)NULL;
922     envStr = getenv(var);
923
924     if (!addMe)
925         return XtsNewString(envStr);
926
927     len = strlen(var) + strlen(STR(envStr)) + strlen(addMe) + 3;
928     ptrlen = len * sizeof(char);
929
930     if ((ptr = XtMalloc(ptrlen)) != (char *)NULL)
931     {
932         if (envStr)
933         {
934             if (prepend)
935                 snprintf(ptr, ptrlen, "%s=%s:%s", var, addMe, envStr);
936             else snprintf(ptr, ptrlen, "%s=%s:%s", var, envStr, addMe);
937         }
938         else snprintf(ptr, ptrlen, "%s=%s", var, addMe);
939
940         putenv(ptr);
941         return ptr;
942     }
943
944     return XtsNewString(envStr);
945 }
946
947 static char *
948 buildPath(char *format, ...)
949 {
950     char pathBuf[(2 * MAXPATHLEN) + 1];
951     va_list ap;
952
953     va_start(ap, format);
954     vsnprintf(pathBuf, sizeof(pathBuf), format, ap);
955     va_end(ap);
956
957     return XtsNewString(pathBuf);
958 }
959
960 /* Assumes gStruct->install and gStruct->clang */
961 /* are set (may be NULL) */
962 static char *
963 buildSGML(void)
964 {
965     char *sgmlPath =
966         buildPath("%s/infolib/%s/SGML",
967                   STR(gStruct->install), STR(gStruct->clang));
968
969     if (!checkStat(sgmlPath, FSTAT_IS_DIR))
970     {
971         XtFree(sgmlPath);
972
973         sgmlPath = buildPath("%s/infolib/%s/SGML",
974                                 STR(gStruct->install), LANG_COMMON);
975
976         if (!checkStat(sgmlPath, FSTAT_IS_DIR)) {
977             XtFree(sgmlPath);
978             return (char *)NULL;
979         }
980     }
981 #ifdef SGML_DEBUG
982     fprintf(stderr, "(DEBUG) buildSGML=\"%s\"\n", sgmlPath);
983 #endif
984
985     return sgmlPath;
986 }
987
988 /* Assumes gStruct->sgml is set (may be NULL) */
989 static char *
990 buildDecl(void)
991 {
992     return buildPath("%s/dtinfo.decl", STR(gStruct->sgml));
993 }
994
995 /* Assumes gStruct->sgml is set (may be NULL) */
996 static char *
997 buildStyleProlog(void)
998 {
999     return buildPath("%s/styprolog.sgml", STR(gStruct->sgml));
1000 }
1001
1002 /* Assumes gStruct->install is set (may be NULL) */
1003 static char *
1004 buildSpec(void)
1005 {
1006     return buildPath("%s/infolib/etc/mmdb.infolib.spec",
1007                      STR(gStruct->install));
1008 }
1009
1010 static void
1011 defaultGlobals(void)
1012 {
1013     memset((void *)gStruct, 0, sizeof(GlobalsStruct));
1014
1015     gStruct->verbose = False;
1016
1017     /* Clear out the ENV environment variable. */
1018     if (getenv("ENV") != (char *)NULL)
1019         putenv("ENV=");
1020
1021     gStruct->pathEnv = makeAbsPathEnv("PATH");
1022     gStruct->ldLibraryPathEnv = makeAbsPathEnv("LD_LIBRARY_PATH");
1023     gStruct->libPathEnv = makeAbsPathEnv("LIBPATH");
1024     gStruct->shlibPathEnv = makeAbsPathEnv("SHLIB_PATH");
1025     gStruct->lcxPathEnv = makeAbsPathEnv("DTLCXSEARCHPATH");
1026     gStruct->dtInfoHomeEnv = makeAbsPathEnv("DTINFO_HOME");
1027     gStruct->dtInfoBinEnv = makeAbsPathEnv("DTINFO_BIN");
1028     gStruct->tmpDirEnv = makeAbsPathEnv("TMPDIR");
1029     gStruct->sgmlPathEnv = makeAbsPathEnv("SGML_PATH");
1030     gStruct->sgmlSearchPathEnv = makeAbsPathEnv("SGML_SEARCH_PATH");
1031     gStruct->sgmlCatFilesEnv = makeAbsPathEnv("SGML_CATALOG_FILES");
1032     gStruct->sgmlCatFiles = NULL;
1033     gStruct->sgmlCatFilesLen = 0;
1034     gStruct->sgmlCatFilesMaxLen = 0;
1035
1036     gStruct->install = getenv("DTINFO_HOME");
1037     gStruct->arch = getenv("ARCH");
1038
1039     { /* resolve lang from env variable */
1040       char* lang;
1041       if ((lang = getenv("LC_ALL")) == NULL)
1042         if ((lang = getenv("LC_CTYPE")) == NULL)
1043           if ((lang = getenv("LANG")) == NULL)
1044             lang = LANG_COMMON;
1045
1046       gStruct->lang = strdup(lang); 
1047     }
1048
1049     { /* resolve canonical lang using _DtLcx routines */
1050       _DtXlateDb db = NULL;
1051       char platform[_DtPLATFORM_MAX_LEN + 1];
1052       int execver, compver;
1053
1054       gStruct->clang = NULL;
1055
1056       if (_DtLcxOpenAllDbs(&db) == 0)
1057       {
1058         if (_DtXlateGetXlateEnv(db, platform, &execver, &compver) == 0)
1059         {
1060           /* _DtLcxXlateOpToStd allocates space for std lang using strdup */
1061           _DtLcxXlateOpToStd(db, platform, compver, DtLCX_OPER_SETLOCALE,
1062                         gStruct->lang, &gStruct->clang, NULL, NULL, NULL);
1063         }
1064
1065         _DtLcxCloseDb(&db);
1066         db = NULL;
1067       }
1068
1069       if (gStruct->clang == NULL)
1070         gStruct->clang = strdup(CLANG_COMMON);
1071     }
1072
1073     { /* resolve dtsearch language based on canonical lang */
1074       t_entry* iter;
1075       for (gStruct->dtsridx = 0, iter = langtbl; iter->name; iter++) {
1076         if (strcmp(iter->name, gStruct->clang) == 0) { /* found a match */
1077           gStruct->dtsridx = iter - langtbl;
1078           break;
1079         }
1080       }
1081     }
1082
1083     if ((gStruct->sgml = buildSGML()) == NULL) {
1084         die(-1, "%s: Cannot find SGML files\n", EXEC_NAME);
1085     }
1086
1087     gStruct->decl = buildDecl();
1088     gStruct->styleProlog = buildStyleProlog();
1089
1090     if ((gStruct->tmpDir = getenv("TMPDIR")) == (char *)NULL)
1091         gStruct->tmpDir = "/usr/tmp";
1092
1093     gStruct->spec = buildSpec();
1094
1095     gStruct->dirMode = 0775;
1096     gStruct->searchEngine = "dtsearch";
1097     gStruct->parser = "nsgmls";
1098     if (!testExec(gStruct->parser, False))
1099         gStruct->parser = "sgmls";
1100
1101     gStruct->keepWorkDir = False;
1102     gStruct->workDir = (char *)NULL;
1103
1104     gStruct->tocElemIndex = 0;
1105 }
1106
1107 static void
1108 checkGlobals(void)
1109 {
1110     if ((!gStruct->install) || (!checkStat(gStruct->install, FSTAT_IS_DIR)))
1111         die(-1, "%s: Cannot find DtInfo Toolkit installation directory.\n"
1112                 "\n"
1113                 "The DTINFO_HOME variable must be set to the directory "
1114                 "where the DtInfo\n"
1115                 "toolkit is installed.\n"
1116                 "\n"
1117                 "You probably invoked this script in an unsupported manner.\n",
1118             EXEC_NAME);
1119
1120     if (!gStruct->arch)
1121         die(-1, "%s: ARCH not set\n", EXEC_NAME);
1122
1123     if (!checkStat(gStruct->sgml, FSTAT_IS_DIR))
1124         die(-1, "%s: Can't find DtInfo SGML directory (%s): %s\n",
1125             EXEC_NAME, STR(gStruct->sgml), strerror(errno));
1126
1127     if (!checkStat(gStruct->decl, FSTAT_IS_READABLE))
1128         die(-1, "%s: faulty installation: %s: %s\n",
1129             EXEC_NAME, STR(gStruct->decl), strerror(errno));
1130
1131     if (!checkStat(gStruct->spec, FSTAT_IS_READABLE))
1132         die(-1, "%s: faulty installation: %s: %s\n",
1133             EXEC_NAME, STR(gStruct->spec), strerror(errno));
1134
1135     if (!checkStat(gStruct->tmpDir, FSTAT_IS_DIR | FSTAT_IS_WRITABLE))
1136         die(-1, "%s: %s: %s\n",
1137             EXEC_NAME, STR(gStruct->tmpDir), strerror(errno));
1138
1139     checkExec("dbdrv");
1140     checkExec("NCFGen");
1141     checkExec("MixedGen");
1142     checkExec("NodeParser");
1143     checkExec("StyleUpdate");
1144     checkExec("valBase");
1145     checkExec(gStruct->parser);
1146     if (strcmp(gStruct->searchEngine, "dtsearch") == 0)
1147     {
1148         checkExec("dtsrcreate");
1149         checkExec("dtsrload");
1150         checkExec("dtsrindex");
1151
1152         gStruct->dtsrlib = buildPath("%s/infolib/etc/%s/dtsr",
1153                                      gStruct->install,
1154                                      STR(langtbl[gStruct->dtsridx].name));
1155
1156         if (!checkStat(gStruct->dtsrlib, FSTAT_IS_DIR)) {
1157             XtFree(gStruct->dtsrlib);
1158             gStruct->dtsrlib = buildPath("%s/infolib/etc/%s/dtsr",
1159                                         gStruct->install, LANG_COMMON);
1160         }
1161 #ifdef DTSR_DEBUG
1162         fprintf(stderr, "(DEBUG) gStruct->dtsrlib=\"%s\"\n", gStruct->dtsrlib);
1163 #endif
1164         gStruct->dbdfile = buildPath("%s/infolib/etc/dtsr/%s.dbd",
1165                                      gStruct->install,
1166                                      gStruct->searchEngine);
1167         gStruct->keytypes = "Default Head Graphics Example Index Table";
1168     }
1169     checkExec("validator");
1170 }
1171
1172 static void
1173 addCatFile(char *catalog, Bool needed)
1174 {
1175     Boolean parserIsNSGMLS;
1176     char pathBuf[(2 * MAXPATHLEN) + 10];
1177     char *ptr1, *ptr2;
1178     int catlen;
1179
1180     if (!checkStat(catalog, FSTAT_IS_READABLE)) {
1181         if (!needed)
1182             return;
1183         dieRWD(-1, "%s: %s: %s\n",
1184                EXEC_NAME, catalog, strerror(errno));
1185     }
1186     parserIsNSGMLS = (strcmp(gStruct->parser, "nsgmls") == 0);
1187     if (parserIsNSGMLS)
1188     {
1189         ptr1 = makeAbsPathStr(catalog);
1190         snprintf(pathBuf, sizeof(pathBuf), "-c%s ", ptr1);
1191         appendStr(&gStruct->sgmlCatFiles, &gStruct->sgmlCatFilesLen,
1192                   &gStruct->sgmlCatFilesMaxLen, pathBuf);
1193         XtFree(ptr1);
1194     }
1195     else
1196     {
1197         ptr1 = strrchr(catalog, '/');
1198         catlen = strlen(catalog);
1199         if (ptr1)
1200             catlen -= strlen(ptr1);
1201         snprintf(pathBuf, sizeof(pathBuf), "%.*s/%%P:%.*s/%%S",
1202                                 catlen, catalog, catlen, catalog);
1203         ptr1 = makeAbsPathStr(pathBuf);
1204         ptr2 = addToEnv("SGML_PATH", ptr1, False);
1205         if (gStruct->sgmlPathEnv)
1206             XtFree(gStruct->sgmlPathEnv);
1207         if (ptr1)
1208             XtFree(ptr1);
1209         gStruct->sgmlPathEnv = ptr2;
1210     }
1211 }
1212
1213 static int
1214 parseArgs(int argc, char *argv[])
1215 {
1216     int i;
1217
1218     for (i = 0; (i < argc) && (argv[i][0] == '-'); i++)
1219     {
1220         if (strcmp(argv[i], "-l") == 0)
1221         {
1222             if (++i < argc)
1223             {
1224                 gStruct->library = argv[i];
1225                 if (checkStat(gStruct->library, FSTAT_EXISTS) &&
1226                     !checkStat(gStruct->library, FSTAT_IS_WRITABLE))
1227                     dieRWD(-1, "%s: %s: %s\n",
1228                            EXEC_NAME, gStruct->library, strerror(errno));
1229             }
1230         }
1231         else if (strcmp(argv[i], "-b") == 0)
1232         {
1233             if (++i < argc)
1234                 gStruct->bookCase = argv[i];
1235         }
1236         else if (strcmp(argv[i], "-d") == 0)
1237         {
1238             if (++i < argc)
1239             {
1240                 int j;
1241
1242                 gStruct->libDesc = XtsNewString(argv[i]);
1243
1244                 /* Change TABs to SPACEs */
1245                 for (j = 0; gStruct->libDesc[j] != '\0'; j++)
1246                 {
1247                     if (gStruct->libDesc[j] == '\t')
1248                         gStruct->libDesc[j] = ' ';
1249                 }
1250             }
1251         }
1252         else if (strcmp(argv[i], "-n") == 0)
1253         {
1254             if (++i < argc)
1255             {
1256                 gStruct->libName = argv[i];
1257                 if (strlen(gStruct->libName) > 8)
1258                     dieRWD(-1, "%s: information library name must be \n"
1259                                "less than or equal to eight characters.\n", EXEC_NAME);
1260             }
1261         }
1262         else if (strcmp(argv[i], "-f") == 0)
1263         {
1264             if (++i < argc)
1265             {
1266                 gStruct->outFile = argv[i];
1267                 if (checkStat(gStruct->outFile, FSTAT_EXISTS) &&
1268                     !checkStat(gStruct->outFile, FSTAT_IS_WRITABLE))
1269                     dieRWD(-1, "%s: %s already exists and not writable\n",
1270                            EXEC_NAME, gStruct->outFile);
1271             }
1272         }
1273         else if (strcmp(argv[i], "-v") == 0)
1274         {
1275             gStruct->verbose = True;
1276         }
1277         else if (strcmp(argv[i], "-h") == 0)
1278         {
1279             printUsage((char *)NULL, 0);
1280         }
1281         else if (strcmp(argv[i], "-T") == 0)
1282         {
1283             if (++i < argc)
1284             {
1285                 gStruct->tmpDir = argv[i];
1286                 checkDir(gStruct->tmpDir);
1287             }
1288         }
1289         else if (strcmp(argv[i], "-m") == 0)
1290         {
1291             if (++i < argc)
1292                 addCatFile(argv[i], True);
1293         }
1294         else if (strcmp(argv[i], "-s") == 0)
1295         {
1296             if (++i < argc)
1297                 gStruct->searchEngine = argv[i];
1298         }
1299         else if (strcmp(argv[i], "--save-esis") == 0)
1300         {
1301             if (++i < argc)
1302                 gStruct->saveESIS = argv[i];
1303         }
1304         else if (strcmp(argv[i], "--load-esis") == 0)
1305         {
1306             if (++i < argc)
1307                 gStruct->loadESIS = True;
1308         }
1309         else if (strcmp(argv[i], "--bin") == 0)
1310         {
1311             if (++i < argc)
1312             {
1313                 char *ptr1, *ptr2;
1314
1315                 ptr1 = addToEnv("PATH", argv[i], True);
1316                 ptr2 = makeAbsPathEnv("PATH");
1317                 if (gStruct->pathEnv)
1318                     XtFree(gStruct->pathEnv);
1319                 if (ptr1)
1320                     XtFree(ptr1);
1321                 gStruct->pathEnv = ptr2;
1322             }
1323         }
1324         else if (strcmp(argv[i], "--keep") == 0)
1325         {
1326             gStruct->keepWorkDir = True;
1327         }
1328         else if (strcmp(argv[i], "-chapter") == 0)
1329         {
1330             gStruct->tocElemIndex++;
1331         }
1332         else if (strcmp(argv[i], "-id") == 0)
1333         {
1334             if (++i < argc)
1335                 gStruct->id = argv[i];
1336         }
1337         else if (strcmp(argv[i], "-title") == 0)
1338         {
1339             if (++i < argc)
1340                 gStruct->title = argv[i];
1341         }
1342         else if (strcmp(argv[i], "-parser") == 0)
1343         {
1344             if (++i < argc)
1345                 gStruct->parser = argv[i];
1346         }
1347         else
1348         {
1349             fprintf(stderr, "%s: unrecognized option %s ignored\n",
1350                     EXEC_NAME, argv[i]);
1351         }
1352     }
1353
1354     return i;
1355 }
1356
1357 static char *
1358 parseDocument(Boolean runCmd, ...)
1359 {
1360     va_list ap;
1361     char *ptr;
1362     char *cmd = (char *)NULL;
1363     int cmdLen = 0;
1364     int maxLen = 0;
1365     Boolean parserIsNSGMLS;
1366
1367     parserIsNSGMLS = (strcmp(gStruct->parser, "nsgmls") == 0);
1368
1369     if (!checkStat(gStruct->sgml, FSTAT_IS_DIR | FSTAT_IS_READABLE))
1370         dieRWD(-1, "%s: faulty installation: %s\n",
1371                EXEC_NAME, strerror(errno));
1372
1373     addCatFile(buildPath("%s/infolib/%s/SGML/catalog",
1374                          STR(gStruct->install), STR(gStruct->clang)), False);
1375     addCatFile(buildPath("%s/infolib/%s/SGML/catalog",
1376                          STR(gStruct->install), LANG_COMMON), True);
1377
1378     if (parserIsNSGMLS)
1379     {
1380         if (!gStruct->sgmlSearchPathEnv)
1381             gStruct->sgmlSearchPathEnv = addToEnv("SGML_SEARCH_PATH", ".", False);
1382     }
1383     else
1384     {
1385         ptr = addToEnv("SGML_PATH", "%S", False);
1386         if (gStruct->sgmlPathEnv)
1387             XtFree(gStruct->sgmlPathEnv);
1388         gStruct->sgmlPathEnv = ptr;
1389     }
1390
1391     appendStr(&cmd, &cmdLen, &maxLen, gStruct->parser);
1392
1393     if (parserIsNSGMLS) {
1394         appendStr(&cmd, &cmdLen, &maxLen, " -bidentity ");
1395         appendStr(&cmd, &cmdLen, &maxLen, gStruct->sgmlCatFiles);
1396     }
1397
1398     if (runCmd)
1399     {
1400         appendStr(&cmd, &cmdLen, &maxLen, " -sg ");
1401
1402         if (! parserIsNSGMLS)
1403             appendStr(&cmd, &cmdLen, &maxLen, gStruct->decl);
1404
1405         va_start(ap, runCmd);
1406         while ((ptr = va_arg(ap, char *)) != 0)
1407         {
1408             appendStr(&cmd, &cmdLen, &maxLen, " ");
1409             appendStr(&cmd, &cmdLen, &maxLen, ptr);
1410         }
1411         va_end(ap);
1412
1413         runShellCmd(cmd);
1414         XtFree(cmd);
1415         return (char *)NULL;
1416     }
1417
1418     if (parserIsNSGMLS)
1419         appendStr(&cmd, &cmdLen, &maxLen, "-oline -wno-idref ");
1420     else
1421         appendStr(&cmd, &cmdLen, &maxLen, " -l ");
1422
1423     appendStr(&cmd, &cmdLen, &maxLen, gStruct->decl);
1424     va_start(ap, runCmd);
1425     while ((ptr = va_arg(ap, char *)) != 0)
1426     {
1427         appendStr(&cmd, &cmdLen, &maxLen, " ");
1428         appendStr(&cmd, &cmdLen, &maxLen, ptr);
1429     }
1430     va_end(ap);
1431
1432     return cmd;
1433 }
1434
1435 static void
1436 buildBookcase(char *cmdSrc, char *dirName)
1437 {
1438     int ret1;
1439     char *ret2;
1440     char *dataBase;
1441     char *tmpFile;
1442     char *newMmdbPathEnv;
1443     char *bookCaseDir;
1444     char *bookCaseMap;
1445     char cmd[MAXPATHLEN * 3];
1446     char bookCaseName[MAXPATHLEN + 1]; /* Not paths, just big buffers. */
1447     char bookCaseDesc[MAXPATHLEN + 1];
1448
1449     dataBase = makeWorkDir();
1450
1451     tmpFile = storeBookCase(cmdSrc, "all", dataBase, dirName);
1452     newMmdbPathEnv = buildPath("MMDB_PATH=%s", STR(gStruct->library));
1453     if (gStruct->mmdbPathEnv)
1454         XtFree(gStruct->mmdbPathEnv);
1455     gStruct->mmdbPathEnv = newMmdbPathEnv;
1456     putenv(newMmdbPathEnv);
1457
1458     if (gStruct->verbose)
1459         fprintf(stderr, "%s\n", newMmdbPathEnv);
1460
1461     if (!findBookCaseNameAndDesc(tmpFile, bookCaseName, bookCaseDesc))
1462         dieRWD(-1, "%s: Missing Bookcase name\n", EXEC_NAME);
1463     XtFree(tmpFile);
1464
1465     bookCaseDir = buildPath("%s/%s", STR(gStruct->library), bookCaseName);
1466     if (checkStat(bookCaseDir, FSTAT_IS_DIR))
1467     {
1468         fprintf(stderr, "%s: deleting existing %s ...\n",
1469                 EXEC_NAME, bookCaseDir);
1470         snprintf(cmd, sizeof(cmd), "rm -rf %s", bookCaseDir);
1471         runShellCmd(cmd);
1472
1473         if (checkStat(bookCaseDir, FSTAT_IS_DIR))
1474             dieRWD(-1, "%s: failed to delete %s\n",
1475                    EXEC_NAME, bookCaseDir);
1476     }
1477
1478     snprintf(cmd, sizeof(cmd), "dbdrv define %s %s \"%s\"",
1479             gStruct->spec, bookCaseName, bookCaseDesc);
1480     runShellCmd(cmd);
1481
1482     bookCaseMap = buildPath("%s/bookcase.map", gStruct->library);
1483     editMapFile(bookCaseName, bookCaseMap);
1484
1485     /* 
1486      * changed not to use pipe for better error handling 
1487      */
1488     { 
1489
1490       const char* style_file = makeTmpFile();
1491
1492       snprintf(cmd, sizeof(cmd), "NCFGen -load-style %s %s > %s",
1493                         bookCaseName, dataBase, style_file);
1494
1495       runShellCmd(cmd);
1496
1497       snprintf(cmd, sizeof(cmd), "cat %s | dbdrv stdin_load %s %s.stylesheet",
1498                         style_file, bookCaseName, bookCaseName);
1499       runShellCmd(cmd);
1500
1501       snprintf(cmd, sizeof(cmd), "rm -f %s", style_file);
1502       ret1 = system(cmd);
1503       if(ret1 != 0) die(-1, "system for rm failed; exiting...\n");
1504       XtFree((char*)style_file);
1505     }
1506
1507
1508     {
1509
1510       const char* compress_file = makeTmpFile();
1511
1512       snprintf(cmd, sizeof(cmd), "NCFGen -compressed %s %s > %s",
1513                         bookCaseName, dataBase, compress_file);
1514
1515       runShellCmd(cmd);
1516
1517       snprintf(cmd, sizeof(cmd), "cat %s | dbdrv stdin_load %s %s.node",
1518                         compress_file, bookCaseName, bookCaseName);
1519       runShellCmd(cmd);
1520
1521       snprintf(cmd, sizeof(cmd), "rm -f %s", compress_file);
1522       ret1 = system(cmd);
1523       if(ret1 != 0) die(-1, "system for rm failed; exiting...\n");
1524       XtFree((char*)compress_file);
1525     }
1526
1527
1528     {
1529       const char* anonym_file = makeTmpFile();
1530
1531       snprintf(cmd, sizeof(cmd), "MixedGen -compressed %s %s > %s",
1532                         dataBase, bookCaseDir, anonym_file);
1533
1534       runShellCmd(cmd);
1535
1536       snprintf(cmd, sizeof(cmd), "cat %s | dbdrv mixed_load %s",
1537                                         anonym_file, bookCaseName);
1538
1539       runShellCmd(cmd);
1540
1541       snprintf(cmd, sizeof(cmd), "rm -f %s", anonym_file);
1542       ret1 = system(cmd);
1543       if(ret1 != 0) die(-1, "system for rm failed; exiting...\n");
1544       XtFree((char*)anonym_file);
1545     }
1546
1547     validateBookCase(bookCaseMap, bookCaseName);
1548     XtFree(bookCaseMap);
1549
1550     if (strcmp(gStruct->searchEngine, "dtsearch") == 0)
1551     {
1552         char curDir[MAXPATHLEN + 1];
1553         char newDir[MAXPATHLEN + 1];
1554         const char *dtsr_stp, *dtsr_sfx, *dtsr_knj;
1555
1556         snprintf(cmd, sizeof(cmd), "mkdir -p %s/%s",
1557                 bookCaseDir, gStruct->searchEngine);
1558         runShellCmd(cmd);
1559
1560         snprintf(cmd, sizeof(cmd), "cp %s/%s/%s.fzk %s/%s",
1561                 dataBase, gStruct->searchEngine, bookCaseName,
1562                 bookCaseDir, gStruct->searchEngine);
1563         runShellCmd(cmd);
1564
1565         snprintf(cmd, sizeof(cmd), "cp %s %s/%s",
1566                 gStruct->dbdfile, bookCaseDir, gStruct->searchEngine);
1567         runShellCmd(cmd);
1568
1569         if ((dtsr_stp = langtbl[gStruct->dtsridx].stp)) {
1570             snprintf(cmd, sizeof(cmd), "cp %s/%s %s/%s/%s.stp",
1571                     gStruct->dtsrlib, dtsr_stp, bookCaseDir,
1572                     gStruct->searchEngine, bookCaseName);
1573             runShellCmd(cmd);
1574         }
1575
1576         if ((dtsr_sfx = langtbl[gStruct->dtsridx].sfx)) {
1577             snprintf(cmd, sizeof(cmd), "cp %s/%s %s/%s/%s.sfx",
1578                     gStruct->dtsrlib, dtsr_sfx, bookCaseDir,
1579                     gStruct->searchEngine, bookCaseName);
1580             runShellCmd(cmd);
1581         }
1582
1583         if ((dtsr_knj = langtbl[gStruct->dtsridx].knj)) {
1584             snprintf(cmd, sizeof(cmd), "cp %s/%s %s/%s/%s.knj",
1585                     gStruct->dtsrlib, dtsr_knj, bookCaseDir,
1586                     gStruct->searchEngine, bookCaseName);
1587             runShellCmd(cmd);
1588         }
1589
1590         curDir[0] = '\0';
1591         ret2 = getcwd(curDir, MAXPATHLEN);
1592         if(ret2 == (char *)NULL) die(-1, "getcwd failed; exiting...\n");
1593         snprintf(newDir, sizeof(newDir), "%s/%s",
1594                                 bookCaseDir, gStruct->searchEngine);
1595         if (chdir(newDir) != 0)
1596             dieRWD(-1, "%s: Cannot find %s: %s\n",
1597                    EXEC_NAME, newDir, strerror(errno));
1598
1599         snprintf(cmd, sizeof(cmd), "dtsrcreate %s-o -a%d -l%d %s",
1600                 (gStruct->verbose) ? "" : "-q ", 210,
1601                 langtbl[gStruct->dtsridx].dtsrlang, bookCaseName);
1602         runShellCmd(cmd);
1603
1604         snprintf(cmd, sizeof(cmd), "dtsrload -d%s '-t\n' %s",
1605                 bookCaseName, bookCaseName);
1606         runShellCmd(cmd);
1607
1608         snprintf(cmd, sizeof(cmd), "dtsrindex -d%s '-t\n' %s",
1609                 bookCaseName, bookCaseName);
1610         runShellCmd(cmd);
1611
1612         snprintf(cmd, sizeof(cmd), "echo keytypes %s = %s > %s.ocf",
1613                 bookCaseName, gStruct->keytypes, gStruct->searchEngine);
1614         runShellCmd(cmd);
1615
1616         snprintf(cmd, sizeof(cmd), "%s.fzk", bookCaseName);
1617         unlink(cmd);
1618         snprintf(cmd, sizeof(cmd), "%s.dbd", gStruct->searchEngine);
1619         unlink(cmd);
1620
1621         if (chdir(curDir) != 0)
1622             dieRWD(-1, "%s: Cannot find %s: %s\n",
1623                    EXEC_NAME, curDir, strerror(errno));
1624     }
1625     XtFree(bookCaseDir);
1626
1627     if (!gStruct->keepWorkDir)
1628         removeWorkDir();
1629 }
1630
1631 static char *
1632 storeBookCase(char *cmdSrc, char *tocOpt, char *dbName,
1633               char *dirName)
1634 {
1635     char *tmpFile;
1636     char *cmd;
1637     int ret;
1638
1639     /*
1640      * changed not to use pipe for better error handling
1641      */
1642     {
1643
1644       const char* nsgmls_file = makeTmpFile();
1645
1646       cmd = buildPath("%s > %s;echo", cmdSrc, nsgmls_file);
1647
1648       runShellCmd(cmd);
1649
1650       tmpFile = makeTmpFile();
1651
1652       cmd = buildPath("cat %s | NodeParser %s %s %s > %s",
1653                         nsgmls_file, tocOpt, dbName, dirName, tmpFile);
1654       runShellCmd(cmd);
1655
1656       cmd = buildPath("rm -f %s", nsgmls_file);
1657       ret = system(cmd);
1658       if(ret != 0) die(-1, "system for rm failed; exiting...\n");
1659       XtFree((char*)nsgmls_file);
1660     }
1661
1662     XtFree(cmd);
1663
1664     return tmpFile;
1665 }
1666
1667 static Boolean
1668 findBookCaseNameAndDesc(char *tmpFile, char *bookCaseName,
1669                         char *bookCaseDesc)
1670 {
1671     FILE *fp;
1672     char lineBuf[MAXPATHLEN + 1];
1673     char *p1, *p2;
1674     static char *patt1 = "BookCase name: `";
1675     static char *patt2 = "' desc: `";
1676     unsigned int len;
1677
1678     if ((fp = fopen(tmpFile, "r")) == (FILE *)NULL)
1679         dieRWD(-1, "%s: opening %s: %s\n",
1680                EXEC_NAME, tmpFile, strerror(errno));
1681
1682     while (fgets(lineBuf, MAXPATHLEN, fp) != (char *)NULL)
1683     {
1684         if ((p1 = strstr(lineBuf, patt1)) != (char *)NULL)
1685         {
1686             p1 += strlen(patt1);
1687             if ((p2 = strstr(p1, patt2)) != (char *)NULL)
1688             {
1689                 len = p2 - p1;
1690                 *((char *) memcpy(bookCaseName, p1, len) + len) = '\0';
1691                 validateBookCaseName(bookCaseName);
1692                 p1 = p2 + strlen(patt2);
1693                 if ((p2 = strchr(p1, '\'')) != (char *)NULL)
1694                 {
1695                     len = p2 - p1;
1696                     *((char *) memcpy(bookCaseDesc, p1, len) + len) = '\0';
1697                     fclose(fp);
1698
1699                     return True;
1700                 }
1701             }
1702         }
1703     }
1704     fclose(fp);
1705
1706     return False;
1707 }
1708
1709 static void
1710 validateBookCaseName(char *bookCaseName)
1711 {
1712     Boolean isOk = False;
1713
1714     if (strlen(bookCaseName) <= 8)
1715     {
1716         int i;
1717
1718         for (i = 0; bookCaseName[i] != '\0'; i++)
1719         {
1720             if (!isalnum((unsigned char) bookCaseName[i]))
1721                 break;
1722         }
1723
1724         isOk = (bookCaseName[i] == '\0');
1725     }
1726
1727     if (!isOk)
1728         dieRWD(-1, "%s: Bookcase name `%s' is not valid;\n"
1729                    "only 8 alphanumeric characters are allowed\n",
1730                EXEC_NAME, bookCaseName);
1731 }
1732
1733 static void
1734 validateBookCase(char *mapFile, char *bookCaseName)
1735 {
1736     char *ret;
1737     FILE *fp;
1738     char lineBuf[MAXPATHLEN + 1];
1739     char cmdBuf[MAXPATHLEN + 1];
1740     char *bcName;
1741
1742     if ((fp = fopen(mapFile, "r")) == (FILE *)NULL)
1743         dieRWD(-1, "%s: cannot open bookcase.map: %s\n",
1744                EXEC_NAME, strerror(errno));
1745
1746     ret = fgets(lineBuf, MAXPATHLEN, fp); /* Skip first line. */
1747     if(ret == (char *)NULL) die(-1, "fgets failed; exiting...\n");
1748
1749     while (fgets(lineBuf, MAXPATHLEN, fp) != (char *)NULL)
1750     {
1751         if ((bcName = strtok(lineBuf, "\t\n")) != (char *)NULL)
1752         {
1753             if (strcmp(bcName, bookCaseName) != 0)
1754             {
1755                 snprintf(cmdBuf, sizeof(cmdBuf), "valBase %s %s",
1756                                         bookCaseName, bcName);
1757
1758                 /* Should this return an error code instead of */
1759                 /* exiting so that we can cleanup the bookcase? */
1760                 runShellCmd(cmdBuf);
1761             }
1762         }
1763     }
1764
1765     fclose(fp);
1766 }
1767
1768 static void
1769 editMapFile(char *bookCaseName, char *bookCaseMap)
1770 {
1771     size_t ret;
1772     struct stat statBuf;
1773     FILE *fp = NULL;
1774     char *file;
1775     char **fileVector;
1776     char *libDesc;
1777     char *oldDesc, *libID;
1778     char *firstLine;
1779     char **lineVector;
1780     char *lastLine;
1781     int i;
1782     int bcNameLen;
1783     Boolean replaced = False;
1784
1785     if ((stat(bookCaseMap, &statBuf) != 0) ||
1786         ((fp = fopen(bookCaseMap, "r")) == (FILE *)NULL))
1787         dieRWD(-1, "%s: %s: %s\n", EXEC_NAME, bookCaseMap,
1788                strerror(errno));
1789
1790     file = XtMalloc((statBuf.st_size + 1) * sizeof(char));
1791     ret = fread(file, statBuf.st_size, sizeof(char), fp);
1792     if(ret == 0) die(-1, "fread failed; exiting...\n");
1793     if (file[statBuf.st_size - 1] == '\n')
1794         file[statBuf.st_size - 1] = '\0';
1795     else file[statBuf.st_size] = '\0';
1796     fclose(fp);
1797
1798     fileVector = _DtVectorizeInPlace(file, '\n');
1799
1800     firstLine = XtsNewString(fileVector[0]);
1801     lineVector = _DtVectorizeInPlace(firstLine, '\t');
1802     if ((oldDesc = lineVector[0]) != (char *)NULL)
1803         libID = lineVector[1];
1804     else
1805         libID = (char *)NULL;
1806     XtFree((char *)lineVector);
1807
1808     if ((libDesc = gStruct->libDesc) == (char *)NULL)
1809         libDesc = oldDesc;
1810
1811     for (i = 0, lastLine = (char *)NULL; fileVector[i] != (char *)NULL; i++)
1812         lastLine = fileVector[i];
1813
1814     if ((fp = fopen(bookCaseMap, "w")) == (FILE *)NULL)
1815         dieRWD(-1, "%s: %s: %s\n", EXEC_NAME, bookCaseMap,
1816                strerror(errno));
1817
1818     bcNameLen = strlen(bookCaseName);
1819
1820     fprintf(fp, "%s\t%s\n", STR(libDesc), STR(libID));
1821     XtFree(firstLine);
1822
1823     for (i = 1; fileVector[i] != (char *)NULL; i++)
1824     {
1825         if ((strncmp(fileVector[i], bookCaseName, bcNameLen) == 0) &&
1826             (!isalnum((unsigned char) fileVector[i][bcNameLen])) &&
1827             (fileVector[i][bcNameLen] != '_'))
1828         {
1829             if (!replaced)
1830             {
1831                 fprintf(fp, "%s\n", lastLine);
1832                 replaced = True;
1833             }
1834         }
1835         else
1836         {
1837             fprintf(fp, "%s\n", fileVector[i]);
1838         }
1839     }
1840
1841     if (!replaced)
1842         fprintf(fp, "%s\n", lastLine);
1843
1844     fclose(fp);
1845
1846     XtFree((char *)fileVector);
1847     XtFree(file);
1848 }
1849
1850 static void
1851 buildTOC(int argc, char *argv[])
1852 {
1853     FILE *fp;
1854     char lineBuf[MAXPATHLEN + 1];
1855     char idBuf[MAXPATHLEN + 1];
1856     char titleBuf[MAXPATHLEN + 1];
1857     char *p1, *p2;
1858     char *tocBC;
1859     char *tocDir;
1860     char *cmdSrc;
1861     unsigned int len;
1862     static char *patt1start = "<TOC id=\"";
1863     static char *patt1end = "\">";
1864     static char *patt2start = "<TITLE>";
1865     static char *patt2end = "</TITLE>";
1866
1867     len = MIN(strlen(gStruct->id), MAXPATHLEN);
1868     *((char *) memcpy (idBuf, gStruct->id, len) + len) = '\0';
1869     len = MIN(strlen(gStruct->title), MAXPATHLEN);
1870     *((char *) memcpy (titleBuf, gStruct->title, len) + len) = '\0';
1871
1872     if (checkStat(gStruct->outFile, FSTAT_IS_FILE))
1873     {
1874         if ((fp = fopen(gStruct->outFile, "r")) == (FILE *)NULL)
1875             die(-1, "%s: %s: %s\n", EXEC_NAME, gStruct->outFile,
1876                 strerror(errno));
1877
1878         idBuf[0] = titleBuf[0] = '\0';
1879         while (fgets(lineBuf, MAXPATHLEN, fp) != (char *)NULL)
1880         {
1881             if ((p1 = strstr(lineBuf, patt1start)) != (char *)NULL)
1882             {
1883                 p1 += strlen(patt1start);
1884                 if ((p2 = strstr(p1, patt1end)) != (char *)NULL)
1885                 {
1886                     len = p2 - p1;
1887                     *((char *) memcpy(idBuf, p1, len) + len) = '\0';
1888                 }
1889             }
1890             if ((p1 = strstr(lineBuf, patt2start)) != (char *)NULL)
1891             {
1892                 p1 += strlen(patt2start);
1893                 if ((p2 = strstr(p1, patt2end)) != (char *)NULL)
1894                 {
1895                     len = p2 - p1;
1896                     *((char *) memcpy(titleBuf, p1, len) + len) = '\0';
1897                 }
1898             }
1899         }
1900         fclose(fp);
1901
1902         if (idBuf[0] == '\0')
1903             die(-1, "%s: %s has no ID\n", EXEC_NAME, gStruct->outFile);
1904         if (titleBuf[0] == '\0')
1905             die(-1, "%s: %s has not TITLE\n", EXEC_NAME, gStruct->outFile);
1906     }
1907
1908     tocBC = tocBookcase(argc, argv);
1909     cmdSrc = parseDocument(False, tocBC, 0);
1910     if ((tocDir = dirname(tocBC)) == (char *)NULL)
1911         tocDir = ".";
1912     tocDir = XtsNewString(tocDir);
1913     p1 = storeBookCase(cmdSrc, "toc", makeWorkDir(), tocDir);
1914     XtFree(p1);
1915     XtFree(tocDir);
1916     XtFree(cmdSrc);
1917     XtFree(tocBC);
1918
1919     makeTOC(idBuf, titleBuf);
1920
1921     if (!gStruct->keepWorkDir)
1922         removeWorkDir();
1923 }
1924
1925 static char *
1926 tocBookcase(int argc, char *argv[])
1927 {
1928     char *tmpFile;
1929     FILE *fp;
1930     int i;
1931
1932     tmpFile = makeTmpFile();
1933     if (gStruct->verbose)
1934         fprintf(stderr, "TOC Bookcase: %s\n", tmpFile);
1935
1936     if ((fp = fopen(tmpFile, "w")) == (FILE *)NULL)
1937         dieRWD(-1, "%s: %s: %s\n", EXEC_NAME,
1938                tmpFile, strerror(errno));
1939
1940     fputs("<!DOCTYPE Bookcase PUBLIC \n"
1941           "\"-//Common Desktop Environment//"
1942           "DTD DtInfo Bookcase Description//EN\" \n"
1943           "[\n"
1944           "        <!-- Books -->\n",
1945           fp);
1946
1947     for (i = 0; i < argc; i++)
1948     {
1949         fprintf(fp, "\t<!ENTITY book%d   SYSTEM '%s' SUBDOC>\n",
1950                 i + 1, argv[i]);
1951     }
1952
1953     fputs("]>\n"
1954           "<BOOKCASE StyleSheet=dummySty>\n"
1955           "        <BOOKCASENAME>TOCDummy</>\n"
1956           "        <BOOKCASEDESC>Dummy Bookcase for TOC Generation</>\n"
1957           "        <StyleSheet name=\"dummySty\"><path>*</>"
1958           "<online><linebreak after></></>\n"
1959           "     <BOOK>\n"
1960           "     <TITLE>Dummy Book for TOC Generation</>\n"
1961           "     <TOCFILE></>\n",
1962           fp);
1963
1964     for (i = 0; i < argc; i++)
1965     {
1966         fprintf(fp, "\t<FILE>&book%d;</>\n", i + 1);
1967     }
1968
1969     fputs("     </BOOK>\n"
1970           "</BOOKCASE>\n",
1971           fp);
1972
1973     fclose(fp);
1974
1975     return tmpFile;
1976 }
1977
1978 static void
1979 makeTOC(char *id, char *title)
1980 {
1981     char *tocTitle;
1982     char *fileIn;
1983     char *trTitle;
1984     char *ptr1, *ptr2;
1985     FILE *fpIn;
1986     FILE *fpOut;
1987     TOCRecord *tocRecord;
1988     TOCEntry newTOCEntry;
1989     TOCEntry *tocEntries = (TOCEntry *)NULL;
1990     int nTOCEntries = 0;
1991     int maxTOCEntries = 0;
1992     int i, j;
1993     int level;
1994     int olvl;
1995
1996     tocTitle = sgmlData(title);
1997     for (i = 0; id[i] != '\0'; i++)
1998     {
1999         if ((!isalnum((unsigned char) id[i])) &&
2000             (id[i] != '.') && (id[i] != '-'))
2001                 die(-1, "bad ID: %s\n", id);
2002     }
2003
2004     fileIn = buildPath("%s/NodeMeta", makeWorkDir());
2005     if ((fpIn = fopen(fileIn, "r")) == (FILE *)NULL)
2006         dieRWD(-1, "%s: internal error: %s\n",
2007                EXEC_NAME, strerror(errno));
2008
2009     if ((fpOut = fopen(gStruct->outFile, "w")) == (FILE *)NULL)
2010         dieRWD(-1, "%s: %s: %s\n",
2011                EXEC_NAME, gStruct->outFile, strerror(errno));
2012
2013     while ((tocRecord = getTOCRecord(fpIn)))
2014     {
2015         char lineBuf[MAXPATHLEN + 1];
2016
2017         if (tocRecord->code != NODE_META)
2018             dieRWD(-1, "\n");
2019
2020         trTitle = sgmlData(STR(tocRecord->title));
2021
2022         ptr1 = replaceData(STR(tocRecord->loc), "&", "&#38;");
2023         ptr2 = replaceData(ptr1, "\"", "&#34;");
2024         XtFree(ptr1);
2025
2026         snprintf(lineBuf, sizeof(lineBuf),
2027                 "<TOCEntry LinkEnd=\"%s\">%s</> <!-- %s:%s -->\n",
2028                 ptr2, trTitle, STR(tocRecord->file),
2029                 STR(tocRecord->line));
2030         XtFree(ptr2);
2031         XtFree(trTitle);
2032
2033         newTOCEntry.ord = STR(tocRecord->ord);
2034         newTOCEntry.entry = lineBuf;
2035         addTOCEntry(&tocEntries, &nTOCEntries, &maxTOCEntries,
2036                     &newTOCEntry);
2037
2038         freeTOCRecord(tocRecord);
2039     }
2040
2041     fclose(fpIn);
2042
2043     fprintf(fpOut, "<!DOCTYPE TOC PUBLIC\n"
2044                    "    \"-//Common Desktop Environment//"
2045                    "DTD DtInfo Table of Contents//EN\">\n"
2046                    "<TOC id=\"%s\">\n"
2047                    "<TITLE>%s</TITLE>\n",
2048             id, tocTitle);
2049     XtFree(tocTitle);
2050
2051     level = -1;
2052
2053     sortTOCEntries(tocEntries, nTOCEntries);
2054     for (i = 0; i < nTOCEntries; i++)
2055     {
2056         /*
2057          * From the original PERL script (I don't understand the logic):
2058          * ## The magic no. 6 here is based on the no. of sections that
2059          * ## are allowed at one level. Currently , it is 99999, ie. only
2060          * ## five digits are allowed
2061          */
2062         olvl = strlen(tocEntries[i].ord) / 6;
2063
2064         while (olvl <= level)
2065         {
2066             for (j = 0; j < level * 4; j++)
2067                 fputs(" ", fpOut);
2068             fprintf(fpOut, "</%s>\n",
2069                     TOCElems[level + gStruct->tocElemIndex]);
2070             level--;
2071         }
2072
2073         while (olvl > level)
2074         {
2075             level++;
2076             fputs("\n", fpOut);
2077             for (j = 0; j < level * 4; j++)
2078                 fputs(" ", fpOut);
2079             fprintf(fpOut, "<%s>\n",
2080                     TOCElems[level + gStruct->tocElemIndex]);
2081         }
2082         for (j = 0; j < level * 4; j++)
2083             fputs(" ", fpOut);
2084         fputs(tocEntries[i].entry, fpOut);
2085     }
2086     freeTOCEntries(tocEntries, nTOCEntries);
2087
2088     while (level >= 0)
2089     {
2090         for (j = 0; j < level * 4; j++)
2091             fputs(" ", fpOut);
2092         fprintf(fpOut, "</%s>\n\n", TOCElems[level + gStruct->tocElemIndex]);
2093         level--;
2094     }
2095
2096     fputs("</TOC>\n", fpOut);
2097     fclose(fpOut);
2098 }
2099
2100 static char *
2101 sgmlData(char *inData)
2102 {
2103     char *outData;
2104     char *p1, *p2;
2105
2106     p1 = replaceData(inData, "&", "&amp;");
2107     p2 = replaceData(p1, "<", "&lt;");
2108     outData = replaceData(p2, ">", "&gt;");
2109
2110     XtFree(p1);
2111     XtFree(p2);
2112
2113     return outData;
2114 }
2115
2116 static char *
2117 replaceData(char *inData, char *replaceMe, char *replacement)
2118 {
2119     int i, slen, len;
2120     int newLen;
2121     int replaceMeLen = strlen(replaceMe);
2122     char *p, *endP;
2123     char *newData;
2124
2125     p = inData;
2126     i = 0;
2127     while ((p = strstr(p, replaceMe)) != (char *)NULL)
2128     {
2129         i++;
2130         p += replaceMeLen;
2131     }
2132
2133     if (i == 0)
2134         return XtsNewString(inData);
2135
2136     newLen = strlen(inData) + (i * (strlen(replacement) - replaceMeLen));
2137     newData = XtMalloc((newLen + 1) * sizeof(char));
2138     newData[0] = '\0';
2139     p = inData;
2140     while ((endP = strstr(p, replaceMe)) != (char *)NULL)
2141     {
2142         slen = strlen(newData);
2143         len = endP - p;
2144         *((char *) memcpy(newData + slen, p, len) + len) = '\0';
2145
2146         slen = strlen(newData);
2147         len = MIN(strlen(replacement), (newLen - slen) * sizeof(char));
2148         *((char *) memcpy(newData + slen, replacement, len) + len) = '\0';
2149         p = endP + replaceMeLen;
2150     }
2151     slen = strlen(newData);
2152     len = MIN(strlen(p), (newLen - slen) * sizeof(char));
2153     *((char *) memcpy(newData + slen, p, len) + len) = '\0';
2154
2155     return newData;
2156 }
2157
2158 static void
2159 addTOCEntry(TOCEntry **tocEntries, int *nTOCEntries, int *maxEntries,
2160             TOCEntry *newEntry)
2161 {
2162     if (*nTOCEntries >= *maxEntries)
2163     {
2164         TOCEntry *newArray;
2165
2166         newArray =
2167             (TOCEntry *)XtRealloc((char *)*tocEntries,
2168                                   (*maxEntries + 10) * sizeof(TOCEntry));
2169         if (!newArray)
2170             return;
2171
2172         *tocEntries = newArray;
2173         *maxEntries += 10;
2174     }
2175
2176     (*tocEntries)[*nTOCEntries].ord = XtsNewString(newEntry->ord);
2177     (*tocEntries)[*nTOCEntries].entry = XtsNewString(newEntry->entry);
2178     (*nTOCEntries)++;
2179 }
2180
2181 static int
2182 compareTOCEntries(const void *entry1, const void *entry2)
2183 {
2184     TOCEntry *tEntry1 = (TOCEntry *)entry1;
2185     TOCEntry *tEntry2 = (TOCEntry *)entry2;
2186
2187     return strcmp(tEntry1->ord, tEntry2->ord);
2188 }
2189
2190 static void
2191 sortTOCEntries(TOCEntry *tocEntries, int nTOCEntries)
2192 {
2193     qsort((void *)tocEntries, nTOCEntries, sizeof(TOCEntry),
2194           compareTOCEntries);
2195 }
2196
2197 static void
2198 freeTOCEntries(TOCEntry *tocEntries, int nTOCEntries)
2199 {
2200     int i;
2201
2202     for (i = 0; i < nTOCEntries; i++)
2203     {
2204         XtFree(tocEntries[i].ord);
2205         XtFree(tocEntries[i].entry);
2206     }
2207
2208     XtFree((char *)tocEntries);
2209 }
2210
2211 static TOCRecord *
2212 getTOCRecord(FILE *fp)
2213 {
2214     char lineBuf[MAXPATHLEN + 1];
2215     int cols;
2216     TOCRecord *tocRecord;
2217     char *ptr;
2218
2219     tocRecord = (TOCRecord *)XtMalloc(sizeof(TOCRecord));
2220
2221     if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2222     {
2223         XtFree((char *)tocRecord);
2224         return (TOCRecord *)NULL;
2225     }
2226
2227     tocRecord->code = atoi(lineBuf);
2228
2229     if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2230     {
2231         XtFree((char *)tocRecord);
2232         return (TOCRecord *)NULL;
2233     }
2234     cols = atoi(lineBuf);
2235
2236     if (cols < TOC_REC_COLS)
2237     {
2238         XtFree((char *)tocRecord);
2239         return (TOCRecord *)NULL;
2240     }
2241
2242     /* DISCARD first item (book). */
2243     ptr = getTOCField(fp);
2244     XtFree(ptr);
2245     cols--;
2246
2247     tocRecord->loc = getTOCField(fp);
2248     cols--;
2249
2250     tocRecord->file = getTOCField(fp);
2251     cols--;
2252
2253     tocRecord->line = getTOCField(fp);
2254     cols--;
2255
2256     tocRecord->ord = getTOCField(fp);
2257     cols--;
2258
2259     tocRecord->title = getTOCField(fp);
2260     cols--;
2261
2262     while (cols > 0)
2263     {
2264         ptr = getTOCField(fp);
2265         XtFree(ptr);
2266         cols--;
2267     }
2268
2269     return tocRecord;
2270 }
2271
2272 static void
2273 freeTOCRecord(TOCRecord *tocRecord)
2274 {
2275     XtFree(tocRecord->loc);
2276     XtFree(tocRecord->file);
2277     XtFree(tocRecord->line);
2278     XtFree(tocRecord->ord);
2279     XtFree(tocRecord->title);
2280
2281     XtFree((char *)tocRecord);
2282 }
2283
2284 static char *
2285 getTOCField(FILE *fp)
2286 {
2287     char lineBuf[MAXPATHLEN + 1];
2288     char *ptr;
2289     int ptrLen;
2290     int lineType;
2291     char *longField = (char *)NULL;
2292     int lfLen = 0;
2293     int maxLen = 0;
2294
2295     if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2296         return (char *)NULL;
2297     if (strcmp(lineBuf, "#\n") == 0)
2298         return (char *)NULL;
2299
2300     lineType = atoi(lineBuf);
2301
2302     switch (lineType)
2303     {
2304     case COMPRESSED_STRING_CODE:
2305         if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2306             return (char *)NULL;
2307
2308         /* FALL THROUGH! */
2309
2310     case STRING_CODE:
2311         if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2312             return (char *)NULL;
2313
2314         if ((ptr = strchr(lineBuf, '\t')) != (char *)NULL)
2315             ptr++;
2316
2317         break;
2318
2319     case INTEGER_CODE:
2320     case OID_CODE:
2321         if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2322             return (char *)NULL;
2323
2324         ptr = lineBuf;
2325         break;
2326
2327     case SHORT_LIST_CODE:
2328         if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2329             return (char *)NULL;
2330
2331         if (strcmp(lineBuf, "#\n") != 0)
2332             dieRWD(-1, "\n");
2333
2334         while ((ptr = getTOCField(fp)) != (char *)NULL)
2335         {
2336             if (longField != (char *)NULL)
2337                 appendStr(&longField, &lfLen, &maxLen, "#");
2338             appendStr(&longField, &lfLen, &maxLen, ptr);
2339
2340             XtFree(ptr);
2341         }
2342
2343         ptr = longField;
2344         break;
2345
2346     case OID_LIST_CODE:
2347         if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2348             return (char *)NULL;
2349
2350         if (strcmp(lineBuf, "#\n") != 0)
2351             dieRWD(-1, "\n");
2352
2353         for ( ; ; )
2354         {
2355             if (fgets(lineBuf, MAXPATHLEN, fp) == (char *)NULL)
2356                 return (char *)NULL;
2357
2358             /* Chop newline */
2359             ptrLen = strlen(lineBuf);
2360             if ((ptrLen > 0) && (lineBuf[ptrLen - 1] == '\n'))
2361                 lineBuf[ptrLen - 1] = '\0';
2362
2363             if (strcmp(lineBuf, "#") == 0)
2364                 break;
2365
2366             if (longField != (char *)NULL)
2367                 appendStr(&longField, &lfLen, &maxLen, "#");
2368             appendStr(&longField, &lfLen, &maxLen, lineBuf);
2369         }
2370
2371         ptr = longField;
2372         break;
2373
2374     default:
2375         fprintf(stderr, "Unknown code: %d\n", lineType);
2376         ptr = (char *)NULL;
2377         break;
2378     }
2379
2380     if (ptr != (char *)NULL)
2381     {
2382         ptrLen = strlen(ptr);
2383         if ((ptrLen > 0) && (ptr[ptrLen - 1] == '\n'))
2384             ptr[ptrLen - 1] = '\0';
2385
2386         ptr = XtsNewString(ptr);
2387
2388         if (longField != (char *)NULL)
2389             XtFree(longField);
2390
2391         return ptr;
2392     }
2393
2394     return (char *)NULL;
2395 }
2396
2397 int
2398 main(int argc, char *argv[])
2399 {
2400     GlobalsStruct globalsStruct;
2401
2402     gStruct = &globalsStruct;
2403
2404     if (argc < 2)
2405         printUsage((char *)NULL, -1);
2406
2407     defaultGlobals();
2408
2409     if (!doAdmin(argc, argv) &&
2410         !doBuild(argc, argv) &&
2411         !doTocgen(argc, argv) &&
2412         !doUpdate(argc, argv) &&
2413         !doValidate(argc, argv) &&
2414         !doHelp(argc, argv))
2415         printUsage(EXEC_NAME ": unrecognized subcommand `%s'\n", -1);
2416
2417     return 0;
2418 }