dtdocbook: Change to ANSI function definitions
[oweals/cde.git] / cde / programs / dtdocbook / instant / util.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 /*
24  *  Copyright 1993 Open Software Foundation, Inc., Cambridge, Massachusetts.
25  *  All rights reserved.
26  */
27 /*
28  * Copyright (c) 1994  
29  * Open Software Foundation, Inc. 
30  *  
31  * Permission is hereby granted to use, copy, modify and freely distribute 
32  * the software in this file and its documentation for any purpose without 
33  * fee, provided that the above copyright notice appears in all copies and 
34  * that both the copyright notice and this permission notice appear in 
35  * supporting documentation.  Further, provided that the name of Open 
36  * Software Foundation, Inc. ("OSF") not be used in advertising or 
37  * publicity pertaining to distribution of the software without prior 
38  * written permission from OSF.  OSF makes no representations about the 
39  * suitability of this software for any purpose.  It is provided "as is" 
40  * without express or implied warranty. 
41  */
42 /* ________________________________________________________________________
43  *
44  *  General utility functions for 'instant' program.  These are used
45  *  throughout the rest of the program.
46  *
47  *  Entry points for this module:
48  *      Split(s, &n, flags)             split string into n tokens
49  *      NewMap(slot_incr)               create a new mapping structure
50  *      FindMapping(map, name)          find mapping by name; return mapping
51  *      FindMappingVal(map, name)       find mapping by name; return value
52  *      SetMapping(map, s)              set mapping based on string
53  *      OpenFile(filename)              open file, looking in inst path
54  *      FindElementPath(elem, s)        find path to element
55  *      PrintLocation(ele, fp)          print location of element in tree
56  *      NearestOlderElem(elem, name)    find prev elem up tree with name
57  *      OutputString(s, fp, track_pos)  output string
58  *      AddElemName(name)               add elem to list of known elements
59  *      AddAttName(name)                add att name to list of known atts
60  *      FindAttByName(elem, name)       find an elem's att by name
61  *      FindContext(elem, lev, context) find context of elem
62  *      QRelation(elem, name, rel_flag) find relation elem has to named elem
63  *      DescendTree(elem, enter_f, leave_f, data_f, dp) descend doc tree,
64  *                                      calling functions for each elem/node
65  * ________________________________________________________________________
66  */
67
68 #ifndef lint
69 static char *RCSid =
70   "$TOG: util.c /main/13 1997/10/09 16:09:50 bill $";
71 #endif
72
73 #include <stdio.h>
74 #include <stdlib.h>
75 #include <ctype.h>
76 #include <string.h>
77 #include <memory.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <sys/file.h>
81 #if !defined(CSRG_BASED)
82 #include <values.h>
83 #endif
84
85 #include "general.h"
86
87 /* forward references */
88 static char     *LookupSDATA(char *);
89 static int       CheckOutputBuffer(int length);
90
91 static OutputBuffer_t outputBuffer; /* init'd to all 0 by compiler */
92
93 /* ______________________________________________________________________ */
94 /*  "Split" a string into tokens.  Given a string that has space-separated
95  *  (space/tab) tokens, return a pointer to an array of pointers to the
96  *  tokens.  Like what the shell does with *argv[].  The array can be is
97  *  static or allocated.  Space can be allocated for string, or allocated.
98  *  Arguments:
99  *      Pointer to string to pick apart.
100  *      Pointer to max number of tokens to find; actual number found is
101  *        returned. If 0 or null pointer, use a 'sane' maximum number (hard-
102  *        code). If more tokens than the number specified, make last token be
103  *        a single string composed of the rest of the tokens (includes spaces).
104  *      Flag. Bit 0 says whether to make a copy of input string (since we'll
105  *        clobber parts of it).  To free the string, use the pointer to
106  *        the first token returned by the function (or *ret_value).
107  *        Bit 1 says whether to allocate the vector itself.  If not, use
108  *        (and return) a static vector.
109  *  Return:
110  *      Pointer to the provided string (for convenience of caller).
111  */
112
113 char **
114 Split(
115     char        *s,             /* input string */
116     int         *ntok,          /* # of tokens desired (input)/found (return) */
117     int         flag            /* dup string? allocate a vector? */
118 )
119 {
120     int         maxnt, i=0;
121     int         n_alloc;
122     char        **tokens;
123     static char *local_tokens[100];
124
125     /* Figure max number of tokens (maxnt) to find.  0 means find them all. */
126     if (ntok == NULL)
127         maxnt = 100;
128     else {
129         if (*ntok <= 0 || *ntok > 100) maxnt = 100;     /* arbitrary size */
130         else maxnt = *ntok;
131         *ntok = 0;
132     }
133
134     if (!s) return 0;                   /* no string */
135
136     /* Point to 1st token (there may be initial space) */
137     while (*s && IsWhite(*s)) s++;      /* skip initial space, if any */
138     if (*s == EOS) return 0;            /* none found? */
139
140     /* See if caller wants us to copy the input string. */
141     if (flag & S_STRDUP) s = strdup(s);
142
143     /* See if caller wants us to allocate the returned vector. */
144     if (flag & S_ALVEC) {
145         n_alloc = 20;
146         Malloc(n_alloc, tokens, char *);
147         /* if caller did not specify max tokens to find, set to more than
148          * there will possibly ever be */
149         if (!ntok || !(*ntok)) maxnt = 10000;
150     }
151     else tokens = local_tokens;
152
153     i = 0;                      /* index into vector */
154     tokens[0] = s;              /* s already points to 1st token */
155     while (i<maxnt) {
156         tokens[i] = s;          /* point vector member at start of token */
157         i++;
158         /* If we allocated vector, see if we need more space. */
159         if ((flag & S_ALVEC) && i >= n_alloc) {
160             n_alloc += 20;
161             Realloc(n_alloc, tokens, char *);
162         }
163         if (i >= maxnt) break;                  /* is this the last one? */
164         while (*s && !IsWhite(*s)) s++;         /* skip past end of token */
165         if (*s == EOS) break;                   /* at end of input string? */
166         if (*s) *s++ = EOS;                     /* terminate token string */
167         while (*s && IsWhite(*s)) s++;          /* skip space - to next token */
168     }
169     if (ntok) *ntok = i;                /* return number of tokens found */
170     tokens[i] = 0;                      /* null-terminate vector */
171     return tokens;
172 }
173
174 /* ______________________________________________________________________ */
175 /*  Mapping routines.  These are used for name-value pairs, like attributes,
176  *  variables, and counters.  A "Map" is an opaque data structure used
177  *  internally by these routines.  The caller gets one when creating a new
178  *  map, then hands it to other routines that need it.  A "Mapping" is a
179  *  name/value pair.  The user has access to this.
180  *  Here's some sample usage:
181  *
182  *      Map *V;
183  *      V = NewMap(20);
184  *      SetMappingNV(V, "home", "/users/bowe");
185  *      printf("Home: %s\n", FindMappingVal(V, "home");
186  */
187
188 /*  Allocate new map structure.  Only done once for each map/variable list.
189  *  Arg:
190  *      Number of initial slots to allocate space for.  This is also the
191  *      "chunk size" - how much to allocate when we use up the given space.
192  *  Return:
193  *      Pointer to the (opaque) map structure. (User passes this to other
194  *      mapping routines.)
195  */
196 Map_t *
197 NewMap(
198     int         slot_increment
199 )
200 {
201     Map_t       *M;
202     Calloc(1, M, Map_t);
203     if (!slot_increment) slot_increment = 1;
204     M->slot_incr = slot_increment;
205     return M;
206 }
207
208 /*  Given pointer to a Map and a name, find the mapping.
209  *  Arguments:
210  *      Pointer to map structure (as returned by NewMap().
211  *      Variable name.
212  *  Return:
213  *      Pointer to the matching mapping structure, or null if not found.
214  */
215 Mapping_t *
216 FindMapping(
217     Map_t       *M,
218     char        *name
219 )
220 {
221     int         i;
222     Mapping_t   *m;
223
224     if (!M || M->n_used == 0) return NULL;
225     for (m=M->maps,i=0; i<M->n_used; i++)
226         if (m[i].name[0] == name[0] && !strcmp(m[i].name, name)) return &m[i];
227     return NULL;
228
229 }
230
231 /*  Given pointer to a Map and a name, return string value of the mapping.
232  *  Arguments:
233  *      Pointer to map structure (as returned by NewMap().
234  *      Variable name.
235  *  Return:
236  *      Pointer to the value (string), or null if not found.
237  */
238 char *
239 FindMappingVal(
240     Map_t       *M,
241     char        *name
242 )
243 {
244     Mapping_t   *m;
245     if (!M || M->n_used == 0) return NULL;
246     if ((m = FindMapping(M, name))) return m->sval;
247     return NULL;
248
249 }
250
251 /*  Set a mapping/variable in Map M.  Input string is a name-value pair where
252  *  there is some amount of space after the name.  The correct mapping is done.
253  *  Arguments:
254  *      Pointer to map structure (as returned by NewMap().
255  *      Pointer to variable name (string).
256  *      Pointer to variable value (string).
257  */
258 void
259 SetMappingNV(
260     Map_t       *M,
261     char        *name,
262     char        *value
263 )
264 {
265     FILE        *pp;
266     char        buf[LINESIZE], *cp, *s;
267     int         i;
268     Mapping_t   *m;
269
270     /* First, look to see if it's a "well-known" variable. */
271     if (!strcmp(name, "verbose"))  { verbose   = atoi(value); return; }
272     if (!strcmp(name, "warnings")) { warnings  = atoi(value); return; }
273     if (!strcmp(name, "foldcase")) { fold_case = atoi(value); return; }
274
275     m = FindMapping(M, name);           /* find existing mapping (if set) */
276
277     /* Need more slots for mapping structures?  Allocate in clumps. */
278     if (M->n_used == 0) {
279         M->n_alloc = M->slot_incr;
280         Malloc(M->n_alloc, M->maps, Mapping_t);
281     }
282     else if (M->n_used >= M->n_alloc) {
283         M->n_alloc += M->slot_incr;
284         Realloc(M->n_alloc, M->maps, Mapping_t);
285     }
286
287     /* OK, we have a string mapping */
288     if (m) {                            /* exists - just replace value */
289         free(m->sval);
290         m->sval = strdup(value);
291         if (value) m->sval = strdup(value);
292         else m->sval = NULL;
293     }
294     else {
295         if (name) {             /* just in case */
296             m = &M->maps[M->n_used];
297             M->n_used++;
298             m->name = strdup(name);
299             if (value) m->sval = strdup(value);
300             else m->sval = NULL;
301         }
302     }
303
304     if (value)
305     {
306         /* See if the value is a command to run.  If so, run the command
307          * and replace the value with the output.
308          */
309         s = value;
310         if (*s == '!') {
311             s++;                                /* point to command */
312             if ((pp = popen(s, "r"))) {         /* run cmd, read its output */
313                 i = 0;
314                 cp = buf;
315                 while (fgets(cp, LINESIZE-i, pp)) {
316                     i += strlen(cp);
317                     cp = &buf[i];
318                     if (i >= LINESIZE) {
319                         fprintf(stderr,
320                             "Prog execution of variable '%s' too long.\n",
321                             m->name);
322                         break;
323                     }
324                 }
325                 free(m->sval);
326                 stripNL(buf);
327                 m->sval = strdup(buf);
328                 pclose(pp);
329             }
330             else {
331                 sprintf(buf, "Could not start program '%s'", s);
332                 perror(buf);
333             }
334         }
335     }
336 }
337
338 /*  Separate name and value from input string, then pass to SetMappingNV.
339  *  Arguments:
340  *      Pointer to map structure (as returned by NewMap().
341  *      Pointer to variable name and value (string), in form "name value".
342  */
343 void
344 SetMapping(
345     Map_t       *M,
346     char        *s
347 )
348 {
349     char        buf[LINESIZE];
350     char        *name, *val;
351
352     if (!M) {
353         fprintf(stderr, "SetMapping: Map not initialized.\n");
354         return;
355     }
356     strcpy(buf, s);
357     name = val = buf;
358     while (*val && !IsWhite(*val)) val++;       /* point past end of name */
359     if (*val) {
360         *val++ = EOS;                           /* terminate name */
361         while (*val && IsWhite(*val)) val++;    /* point to value */
362     }
363     if (name) SetMappingNV(M, name, val);
364 }
365
366 /* ______________________________________________________________________ */
367 /*  Opens a file for reading.  If not found in current directory, try
368  *  lib directories (from TPT_LIB env variable, or -l option).
369  *  Arguments:
370  *      Filename (string).
371  *  Return:
372  *      FILE pointer to open file, or null if it not found or can't open.
373  */
374
375 FILE *
376 OpenFile(
377     char        *filename
378 )
379 {
380     FILE        *fp;
381     char        buf[LINESIZE];
382     int         i;
383     static char **libdirs;
384     static int  nlibdirs = -1;
385
386     if ((fp=fopen(filename, "r"))) return fp;
387
388     if (*filename == '/') return NULL;          /* full path specified? */
389
390     if (nlibdirs < 0) {
391         char *cp, *s;
392         if (tpt_lib) {
393             s = strdup(tpt_lib);
394             for (cp=s; *cp; cp++) if (*cp == ':') *cp = ' ';
395             nlibdirs = 0;
396             libdirs = Split(s, &nlibdirs, S_ALVEC);
397         }
398         else nlibdirs = 0;
399     }
400     for (i=0; i<nlibdirs; i++) {
401         sprintf(buf, "%s/%s", libdirs[i], filename);
402         if ((fp=fopen(buf, "r"))) return fp;
403     }
404     return NULL;
405 }
406
407 /* ______________________________________________________________________ */
408 /*  This will find the path to an tag.  The format is the:
409  *      tag1(n1):tag2(n2):tag3
410  *  where the tags are going down the tree and the numbers indicate which
411  *  child (the first is numbered 1) the next tag is.
412  *  Returns pointer to the string just written to (so you can use this
413  *  function as a printf arg).
414  *  Arguments:
415  *      Pointer to element under consideration.
416  *      String to write path into (provided by caller).
417  *  Return:
418  *      Pointer to the provided string (for convenience of caller).
419  */
420 char *
421 FindElementPath(
422     Element_t   *e,
423     char        *s
424 )
425 {
426     Element_t   *ep;
427     int         i, e_path[MAX_DEPTH];
428     char        *cp;
429
430     /* Move up the tree, noting "birth order" of each element encountered */
431     for (ep=e; ep->parent; ep=ep->parent)
432         e_path[ep->depth-1] = ep->my_eorder;
433     /* Move down the tree, printing the element names to the string. */
434     for (cp=s,i=0,ep=DocTree; i<e->depth; ep=ep->econt[e_path[i]],i++) {
435         sprintf(cp, "%s(%d) ", ep->gi, e_path[i]);
436         cp += strlen(cp);
437     }
438     sprintf(cp, "%s", e->gi);
439     return s;
440 }
441
442 /* ______________________________________________________________________ */
443 /*  Print some location info about a tag.  Helps user locate error.
444  *  Messages are indented 2 spaces (convention for multi-line messages).
445  *  Arguments:
446  *      Pointer to element under consideration.
447  *      FILE pointer of where to print.
448  */
449
450 void
451 PrintLocation(
452     Element_t   *e,
453     FILE        *fp
454 )
455 {
456     char        *s, buf[LINESIZE];
457
458     if (!e || !fp) return;
459     fprintf(fp, "  Path: %s\n", FindElementPath(e, buf));
460     if ((s=NearestOlderElem(e, "TITLE")))
461         fprintf(fp, "  Position hint: TITLE='%s'\n", s);
462     if (e->lineno) {
463         if (e->infile)
464             fprintf(fp, "  At or near instance file: %s, line: %d\n",
465                         e->infile, e->lineno);
466         else
467             fprintf(fp, "  At or near instance line: %d\n", e->lineno);
468     }
469     if (e->id)
470         fprintf(fp, "  ID: %s\n", e->id);
471 }
472
473 /* ______________________________________________________________________ */
474 /*  Finds the data part of the nearest "older" tag (up the tree, and
475  *  preceding) whose tag name matches the argument, or "TITLE", if null.
476  *  Returns a pointer to the first chunk of character data.
477  *  Arguments:
478  *      Pointer to element under consideration.
479  *      Name (GI) of element we'll return data from.
480  *  Return:
481  *      Pointer to that element's data content.
482  */
483 char *
484 NearestOlderElem(
485     Element_t   *e,
486     char        *name
487 )
488 {
489     int         i;
490     Element_t   *ep;
491
492     if (!e) return 0;
493     if (!name) name = "TITLE";                  /* useful default */
494
495     for (; e->parent; e=e->parent)              /* move up tree */
496         for (i=0; i<=e->my_eorder; i++) {       /* check preceding sibs */
497             ep = e->parent;
498             if (!strcmp(name, ep->econt[i]->gi))
499                 return ep->econt[i]->ndcont ?
500                         ep->econt[i]->dcont[0] : "-empty-";
501         }
502
503     return NULL;
504 }
505
506 /* ______________________________________________________________________ */
507 /*  Expands escaped strings in the input buffer (things like tabs, newlines,
508  *  octal characters - using C style escapes) if outputting the buffer to
509  *  the specified fp.  If fp is NULL, we're only preparing the output
510  *  for the interpreter so don't expand escaped strings.  The hat/anchor
511  *  character forces that position to appear at the beginning of a line.
512  *  The cursor position is kept track of (optionally) so that this can be
513  *  done.
514  *  Arguments:
515  *      Pointer to element under consideration.
516  *      FILE pointer of where to print.
517  *      Flag saying whether or not to keep track of our position in the output
518  *        stream. (We want to when writing to a file, but not for stderr.)
519  */
520
521 void
522 OutputString(
523     char        *s,
524     FILE        *fp,
525     int         track_pos
526 )
527 {
528     char        c, *sdata, *cp;
529     static int  char_pos;   /* remembers our character position */
530     static int  interp_pos; /* like char_pos but when output is to interp */
531     int         *ppos;      /* points to appropriate line position var */
532
533     if (fp)
534         ppos = &char_pos;   /* writing to file */
535     else
536         ppos = &interp_pos; /* buffer will be read by interpreter */
537
538     if (!s) s = "^";        /* no string - go to start of line */
539
540     for ( ; *s; s++) {
541         if (fp && (*s == '\\')) { /* don't expand for interpreter */
542             s++;
543             if (track_pos) (*ppos)++;
544             switch (*s) {
545                 default:        c = *s;         break;
546
547                 case 's':       c = ' ';        break;
548
549                 case 't':       c = TAB;        break;
550
551                 case 'n':       c = NL;         *ppos = 0;      break;
552
553                 case 'r':       c = CR;         *ppos = 0;      break;
554
555                 case '0': case '1': case '2': case '3':
556                 case '4': case '5': case '6': case '7':
557                     /* for octal numbers (C style) of the form \012 */
558                     c = *s - '0';
559                     for (s++; ((*s >= '0') && (*s <= '7')); s++)
560                         c = (c << 3) + (*s - '0');
561                     s--;
562                     break;
563
564                 case '|':               /* SDATA */
565                     s++;                /* point past \| */
566                     sdata = s;
567                     /* find matching/closing \| */
568                     cp = s;
569                     while (*cp && *cp != '\\' && cp[1] != '|') cp++;
570                     if (!*cp) break;
571
572                     *cp = EOS;          /* terminate sdata string */
573                     cp++;
574                     s = cp;             /* s now points to | */
575
576                     cp = LookupSDATA(sdata);
577                     if (cp) OutputString(cp, fp, track_pos);
578                     else {
579                         /* not found - output sdata thing in brackets */
580                         Putc('[', fp);
581                         FPuts(sdata, fp);
582                         Putc(']', fp);
583                     }
584                     c = 0;
585                     break;
586             }
587         }
588         else {          /* not escaped - just pass the character */
589             c = *s;
590             /* If caller wants us to track position, see if it's an anchor
591              * (ie, align at a newline). */
592             if (track_pos) {
593                 if (c == ANCHOR) {
594                     /* If we're already at the start of a line, don't do
595                      * another newline. */
596                     if (*ppos != 0) c = NL;
597                     else c = 0;
598                 }
599                 else (*ppos)++;
600                 if (c == NL) *ppos = 0;
601             }
602             else if (c == ANCHOR) c = NL;
603         }
604         if (c) Putc(c, fp);
605     }
606 }
607
608 /* ______________________________________________________________________ */
609 /* resets the output buffer
610  */
611 void ClearOutputBuffer(void)
612 {
613 outputBuffer.current = outputBuffer.base;
614 }
615
616 /* ______________________________________________________________________ */
617 /* determines if there is already some text in the output buffer,
618  * returns 1 if so, else 0
619  */
620 int OutputBufferActive(void)
621 {
622 return (outputBuffer.current != outputBuffer.base);
623 }
624
625 /* ______________________________________________________________________ */
626 /* terminates output buffer with a null char and returns the buffer
627  */
628 char *GetOutputBuffer(void)
629 {
630 if (CheckOutputBuffer(1))
631     *(outputBuffer.current)++ = '\0';
632
633 return outputBuffer.base;
634 }
635
636 /* ______________________________________________________________________ */
637 /* insures that there's enough room in outputBuffer to hold a string
638  * of the given length.
639  * Arguments: the length of the string
640  */
641 static int CheckOutputBuffer(
642     int length
643 )
644 {
645     char *oldBase;
646     int   oldSize, incr = OBUF_INCR;
647
648     while (length > incr) incr += OBUF_INCR;
649
650     if ((outputBuffer.current - outputBuffer.base + length)
651                         >
652                 outputBuffer.size) {
653         oldBase = outputBuffer.base;
654         oldSize = outputBuffer.size;
655         outputBuffer.size += incr;
656         outputBuffer.base =
657             outputBuffer.base ?
658                 realloc(outputBuffer.base, outputBuffer.size) :
659                 malloc(outputBuffer.size);
660         if (outputBuffer.base == NULL) {
661             outputBuffer.base = oldBase;
662             outputBuffer.size = oldSize;
663             return 0;
664         }
665         outputBuffer.current =
666             outputBuffer.base + (outputBuffer.current - oldBase);
667     }
668
669     return 1;
670 }
671
672
673 /* ______________________________________________________________________ */
674 /* local version of fflush(3S)
675  *
676  * special cases a FILE of NULL to simply return success
677  *
678  */
679 int FFlush(FILE *stream)
680 {
681 if (stream) return fflush(stream);
682 return 0;
683 }
684
685
686 /* ______________________________________________________________________ */
687 /* local version of putc(3S)
688  *
689  * special cases a FILE of NULL by working into a buffer for later
690  * use by the interpreter
691  *
692  * extra special hack: call Tcl interpreter with the character; worry
693  * about "stream" somo other time, we'll default to stdout
694  */
695 int Putc(
696     int c,
697     FILE *stream
698 )
699 {
700     int result;
701     char *pc;
702     static char commandBuf[] = "OutputString \"      ";
703
704     if (stream) {
705         pc = &(commandBuf[14]);
706         switch (c) { /* escape those things that throw off tcl */
707             case '{':
708             case '}':
709             case '"':
710             case '\'':
711             case '[':
712             case ']':
713             case '$':
714             case '\\':
715                 *pc++ = '\\';
716         }
717         *pc++ = c;
718         *pc++ = '"';
719         *pc++ = 0;
720         result = Tcl_Eval(interpreter, commandBuf);
721
722         if (result != TCL_OK) {
723             fprintf(stderr,
724                     "interpreter error \"%s\" at line %d executing:\n",
725                     interpreter->result,
726                     interpreter->errorLine);
727             fprintf(stderr, "\"%s\"\n", commandBuf);
728             return EOF;
729         }
730         return c;
731     }
732
733     if ((CheckOutputBuffer(1)) == 0)
734         return EOF; /* out of space and can't grow the buffer */
735
736     *(outputBuffer.current)++ = (char) c;
737
738     return c;
739 }
740
741 /* ______________________________________________________________________ */
742 /* local version of fputs(3S)
743  *
744  * special cases a FILE of NULL by working into a buffer for later
745  * use by the interpreter
746  */
747 int FPuts(
748     const char *s,
749     FILE *stream
750 )
751 {
752     static char commandBuf[128] = "OutputString \"";
753     char *pBuff,*pb;
754     const char *ps;
755     int sLength;
756     int result;
757
758     if ((sLength = strlen(s)) == 0)
759         return 0; /* no need to call CheckOutputBuffer() */
760
761     if (stream) {
762         if (sLength > 100/2) { /* assume that every char must be escaped */
763             pBuff = malloc(sLength + 14 + 1);
764             commandBuf[14] = 0;
765             strcpy(pBuff, commandBuf);
766         } else
767             pBuff = commandBuf;
768         ps = s;
769         pb = pBuff + 14;
770         do {
771             switch (*ps) { /* escape those things that throw off Tcl */
772                 case '{':
773                 case '}':
774                 case '"':
775                 case '\'':
776                 case '[':
777                 case ']':
778                 case '\\':
779                     *pb++ = '\\';
780             }
781             *pb++ = *ps++;
782         } while (*ps);
783         *pb++ = '"';
784         *pb = 0;
785         result = Tcl_Eval(interpreter, pBuff);
786
787         if (result != TCL_OK) {
788             fprintf(stderr,
789                     "interpreter error \"%s\" at line %d executing:\n",
790                     interpreter->result,
791                     interpreter->errorLine);
792             fprintf(stderr, "\"%s\"\n", pBuff);
793             if (pBuff != commandBuf) free(pBuff);
794             return EOF;
795         }
796         if (pBuff != commandBuf) free(pBuff);
797         return 0;
798     }
799
800     if ((CheckOutputBuffer(sLength)) == 0)
801         return EOF; /* out of space and can't grow the buffer */
802
803     strncpy(outputBuffer.current, s, sLength);
804     outputBuffer.current += sLength;
805
806     return sLength; /* arbitrary non-negative number */
807 }
808
809 /* ______________________________________________________________________ */
810 /* Figure out value of SDATA entity.
811  * We rememeber lookup hits in a "cache" (a shorter list), and look in
812  * cache before general list.  Typically there will be LOTS of entries
813  * in the general list and only a handful in the hit list.  Often, if an
814  * entity is used once, it'll be used again.
815  *  Arguments:
816  *      Pointer to SDATA entity token in ESIS.
817  *  Return:
818  *      Mapped value of the SDATA entity.
819  */
820
821 static char *
822 LookupSDATA(
823     char        *s
824 )
825 {
826     char        *v;
827     static Map_t *Hits;         /* remember lookup hits */
828
829     /* SDL SDL SDL SDL --- special (i.e., hack); see below      */
830     /* we're going to replace the "123456" below with the SDATA */
831     /*                         0123456789 012                   */
832     static char spcString[] = "<SPC NAME=\"[123456]\">\0";
833     static char spc[sizeof(spcString)];
834
835
836     /* If we have a hit list, check it. */
837     if (Hits) {
838         if ((v = FindMappingVal(Hits, s))) return v;
839     }
840
841     v = FindMappingVal(SDATAmap, s);
842
843     /* If mapping found, remember it, then return it. */
844     if ((v = FindMappingVal(SDATAmap, s))) {
845         if (!Hits) Hits = NewMap(IMS_sdatacache);
846         SetMappingNV(Hits, s, v);
847         return v;
848     }
849
850     /* SDL SDL SDL SDL --- special (i.e., hack)
851        Special case sdata values of six letters surrounded by square
852        brackets.  Just convert them over to the SDL <SPC> stuff
853     */
854     if ((strlen(s) == 8) &&
855         (s[0] == '[')    &&
856         (s[7] == ']')) {
857         if (strcmp(s, "[newlin]") == 0) {
858             return "&\n";
859         } else {
860             strcpy(spc, spcString);
861             strncpy(spc+12, s+1, 6);
862             return spc;
863         }
864     }
865
866     fprintf(stderr, "Error: Could not find SDATA substitution '%s'.\n", s);
867     return NULL;
868 }
869
870 /* ______________________________________________________________________ */
871 /*  Add tag 'name' of length 'len' to list of tag names (if not there).
872  *  This is a list of null-terminated strings so that we don't have to
873  *  keep using the name length.
874  *  Arguments:
875  *      Pointer to element name (GI) to remember.
876  *  Return:
877  *      Pointer to the SAVED element name (GI).
878  */
879
880 char *
881 AddElemName(
882     char        *name
883 )
884 {
885     int         i;
886     static int  n_alloc=0;      /* number of slots allocated so far */
887
888     /* See if it's already in the list. */
889     for (i=0; i<nUsedElem; i++)
890         if (UsedElem[i][0] == name[0] && !strcmp(UsedElem[i], name))
891             return UsedElem[i];
892
893     /* Allocate slots in blocks of N, so we don't have to call malloc
894      * so many times. */
895     if (n_alloc == 0) {
896         n_alloc = IMS_elemnames;
897         Calloc(n_alloc, UsedElem, char *);
898     }
899     else if (nUsedElem >= n_alloc) {
900         n_alloc += IMS_elemnames;
901         Realloc(n_alloc, UsedElem, char *);
902     }
903     UsedElem[nUsedElem] = strdup(name);
904     return UsedElem[nUsedElem++];
905 }
906 /* ______________________________________________________________________ */
907 /*  Add attrib name to list of attrib names (if not there).
908  *  This is a list of null-terminated strings so that we don't have to
909  *  keep using the name length.
910  *  Arguments:
911  *      Pointer to attr name to remember.
912  *  Return:
913  *      Pointer to the SAVED attr name.
914  */
915
916 char *
917 AddAttName(
918     char        *name
919 )
920 {
921     int         i;
922     static int  n_alloc=0;      /* number of slots allocated so far */
923
924     /* See if it's already in the list. */
925     for (i=0; i<nUsedAtt; i++)
926         if (UsedAtt[i][0] == name[0] && !strcmp(UsedAtt[i], name))
927             return UsedAtt[i];
928
929     /* Allocate slots in blocks of N, so we don't have to call malloc
930      * so many times. */
931     if (n_alloc == 0) {
932         n_alloc = IMS_attnames;
933         Calloc(n_alloc, UsedAtt, char *);
934     }
935     else if (nUsedAtt >= n_alloc) {
936         n_alloc += IMS_attnames;
937         Realloc(n_alloc, UsedAtt, char *);
938     }
939     UsedAtt[nUsedAtt] = strdup(name);
940     return UsedAtt[nUsedAtt++];
941 }
942
943 /* ______________________________________________________________________ */
944 /*  Find an element's attribute value given element pointer and attr name.
945  *  Typical use: 
946  *      a=FindAttByName("TYPE", t); 
947  *      do something with a->val;
948  *  Arguments:
949  *      Pointer to element under consideration.
950  *      Pointer to attribute name.
951  *  Return:
952  *      Pointer to the value of the attribute.
953  */
954
955 /*
956 Mapping_t *
957 FindAttByName(
958     Element_t   *e,
959     char        *name
960 )
961 {
962     int         i;
963     if (!e) return NULL;
964     for (i=0; i<e->natts; i++)
965         if (e->atts[i].name[0] == name[0] && !strcmp(e->atts[i].name, name))
966                 return &(e->atts[i]);
967     return NULL;
968 }
969 */
970
971 char *
972 FindAttValByName(
973     Element_t   *e,
974     char        *name
975 )
976 {
977     int         i;
978     if (!e) return NULL;
979     for (i=0; i<e->natts; i++)
980         if (e->atts[i].name[0] == name[0] && !strcmp(e->atts[i].name, name))
981             return e->atts[i].sval;
982     return NULL;
983 }
984
985 /* ______________________________________________________________________ */
986 /*  Find context of a tag, 'levels' levels up the tree.
987  *  Space for string is passed by caller.
988  *  Arguments:
989  *      Pointer to element under consideration.
990  *      Number of levels to look up tree.
991  *      String to write path into (provided by caller).
992  *  Return:
993  *      Pointer to the provided string (for convenience of caller).
994  */
995
996 char *
997 FindContext(
998     Element_t   *e,
999     int         levels,
1000     char        *con
1001 )
1002 {
1003     char        *s;
1004     Element_t   *ep;
1005     int         i;
1006
1007     if (!e) return NULL;
1008     s = con;
1009     *s = EOS;
1010     for (i=0,ep=e->parent; ep && levels; ep=ep->parent,i++,levels--) {
1011         if (i != 0) *s++ = ' ';
1012         strcpy(s, ep->gi);
1013         s += strlen(s);
1014     }
1015     return con;
1016 }
1017
1018
1019 /* ______________________________________________________________________ */
1020 /*  Tests relationship (specified by argument/flag) between given element
1021  *  (structure pointer) and named element.
1022  *  Returns pointer to matching tag if found, null otherwise.
1023  *  Arguments:
1024  *      Pointer to element under consideration.
1025  *      Pointer to name of elem whose relationsip we are trying to determine.
1026  *      Relationship we are testing.
1027  *  Return:
1028  *      Pointer to the provided string (for convenience of caller).
1029  */
1030
1031 Element_t *
1032 QRelation(
1033     Element_t   *e,
1034     char        *s,
1035     Relation_t  rel
1036 )
1037 {
1038     int         i;
1039     Element_t   *ep;
1040
1041     if (!e) return 0;
1042
1043     /* we'll call e the "given element" */
1044     switch (rel)
1045     {
1046         case REL_Parent:
1047             if (!e->parent || !e->parent->gi) return 0;
1048             if (!strcmp(e->parent->gi, s)) return e->parent;
1049             break;
1050         case REL_Child:
1051             for (i=0; i<e->necont; i++)
1052                 if (!strcmp(s, e->econt[i]->gi)) return e->econt[i];
1053             break;
1054         case REL_Ancestor:
1055             if (!e->parent || !e->parent->gi) return 0;
1056             for (ep=e->parent; ep; ep=ep->parent)
1057                 if (!strcmp(ep->gi, s)) return ep;
1058             break;
1059         case REL_Descendant:
1060             if (e->necont == 0) return 0;
1061             /* check immediate children first */
1062             for (i=0; i<e->necont; i++)
1063                 if (!strcmp(s, e->econt[i]->gi)) return e->econt[i];
1064             /* then children's children (recursively) */
1065             for (i=0; i<e->necont; i++)
1066                 if ((ep=QRelation(e->econt[i], s, REL_Descendant)))
1067                     return ep;
1068             break;
1069         case REL_Sibling:
1070             if (!e->parent) return 0;
1071             ep = e->parent;
1072             for (i=0; i<ep->necont; i++)
1073                 if (!strcmp(s, ep->econt[i]->gi) && i != e->my_eorder)
1074                     return ep->econt[i];
1075             break;
1076         case REL_Preceding:
1077             if (!e->parent || e->my_eorder == 0) return 0;
1078             ep = e->parent;
1079             for (i=0; i<e->my_eorder; i++)
1080                 if (!strcmp(s, ep->econt[i]->gi)) return ep->econt[i];
1081             break;
1082         case REL_ImmPreceding:
1083             if (!e->parent || e->my_eorder == 0) return 0;
1084             ep = e->parent->econt[e->my_eorder-1];
1085             if (!strcmp(s, ep->gi)) return ep;
1086             break;
1087         case REL_Following:
1088             if (!e->parent || e->my_eorder == (e->parent->necont-1))
1089                 return 0;       /* last? */
1090             ep = e->parent;
1091             for (i=(e->my_eorder+1); i<ep->necont; i++)
1092                 if (!strcmp(s, ep->econt[i]->gi)) return ep->econt[i];
1093             break;
1094         case REL_ImmFollowing:
1095             if (!e->parent || e->my_eorder == (e->parent->necont-1))
1096                 return 0;       /* last? */
1097             ep = e->parent->econt[e->my_eorder+1];
1098             if (!strcmp(s, ep->gi)) return ep;
1099             break;
1100         case REL_Cousin:
1101             if (!e->parent) return 0;
1102             /* Now, see if element's parent has that thing as a child. */
1103             return QRelation(e->parent, s, REL_Child);
1104             break;
1105         case REL_Is1stContent:
1106             /* first confirm that our parent is an "s" */
1107             if (!(ep = QRelation(e, s, REL_Parent))) return 0;
1108             /* then check that we are the first content in that parent */
1109             if ((ep->cont->type == CMD_OPEN) && (ep->cont->ch.elem == e))
1110                 return ep;
1111             break;
1112         case REL_HasOnlyContent:
1113             /* first confirm that we have a child of "s" */
1114             if (!(ep = QRelation(e, s, REL_Child))) return 0;
1115             /* then check that it is our only content */
1116             if (e->ncont == 1) return 0;
1117             break;
1118         case REL_None:
1119         case REL_Unknown:
1120             fprintf(stderr, "You cannot query 'REL_None' or 'REL_Unknown'.\n");
1121             break;
1122     }
1123     return NULL;
1124 }
1125
1126 /*  Given a relationship name (string), determine enum symbol for it.
1127  *  Arguments:
1128  *      Pointer to relationship name.
1129  *  Return:
1130  *      Relation_t enum.
1131  */
1132 Relation_t
1133 FindRelByName(
1134     char        *relname
1135 )
1136 {
1137     if (!strcmp(relname, "?")) {
1138         fprintf(stderr, "Supported query/relationships %s\n%s\n%s.\n", 
1139             "child, parent, ancestor, descendant,",
1140             "sibling, sibling+, sibling+1, sibling-, sibling-1,",
1141             "cousin, isfirstcon, hasonlycon");
1142         return REL_None;
1143     }
1144     else if (StrEq(relname, "child"))           return REL_Child;
1145     else if (StrEq(relname, "parent"))          return REL_Parent;
1146     else if (StrEq(relname, "ancestor"))        return REL_Ancestor;
1147     else if (StrEq(relname, "descendant"))      return REL_Descendant;
1148     else if (StrEq(relname, "sibling"))         return REL_Sibling;
1149     else if (StrEq(relname, "sibling-"))        return REL_Preceding;
1150     else if (StrEq(relname, "sibling-1"))       return REL_ImmPreceding;
1151     else if (StrEq(relname, "sibling+"))        return REL_Following;
1152     else if (StrEq(relname, "sibling+1"))       return REL_ImmFollowing;
1153     else if (StrEq(relname, "cousin"))          return REL_Cousin;
1154     else if (StrEq(relname, "isfirstcon"))      return REL_Is1stContent;
1155     else if (StrEq(relname, "hasonlycon"))      return REL_HasOnlyContent;
1156     else fprintf(stderr, "Unknown relationship: %s\n", relname);
1157     return REL_Unknown;
1158 }
1159
1160 /* ______________________________________________________________________ */
1161 /*  This will descend the element tree in-order. (enter_f)() is called
1162  *  upon entering the node.  Then all children (data and child elements)
1163  *  are operated on, calling either DescendTree() with a pointer to
1164  *  the child element or (data_f)() for each non-element child node.
1165  *  Before leaving the node (ascending), (leave_f)() is called.  enter_f
1166  *  and leave_f are passed a pointer to this node and data_f is passed
1167  *  a pointer to the data/content (which includes the data itself and
1168  *  type information).  dp is an opaque pointer to any data the caller
1169  *  wants to pass.
1170  *  Arguments:
1171  *      Pointer to element under consideration.
1172  *      Pointer to procedure to call when entering element.
1173  *      Pointer to procedure to call when leaving element.
1174  *      Pointer to procedure to call for each "chunk" of content data.
1175  *      Void data pointer, passed to the avobe 3 procedures.
1176  */
1177
1178 void
1179 DescendTree(
1180     Element_t   *e,
1181     void        (*enter_f)(),
1182     void        (*leave_f)(),
1183     void        (*data_f)(),
1184     void        *dp
1185 )
1186 {
1187     int         i;
1188     if (enter_f) (enter_f)(e, dp);
1189     for (i=0; i<e->ncont; i++) {
1190         if (e->cont[i].type == CMD_OPEN)
1191             DescendTree(e->cont[i].ch.elem, enter_f, leave_f, data_f, dp);
1192         else
1193             if (data_f) (data_f)(&e->cont[i], dp);
1194     }
1195     if (leave_f) (leave_f)(e, dp);
1196 }
1197
1198 /* ______________________________________________________________________ */
1199 /*  Add element, 'e', whose ID is 'idval', to a list of IDs.
1200  *  This makes it easier to find an element by ID later.
1201  *  Arguments:
1202  *      Pointer to element under consideration.
1203  *      Element's ID attribute value (a string).
1204  */
1205
1206 void
1207 AddID(
1208     Element_t   *e,
1209     char        *idval
1210 )
1211 {
1212     static ID_t *id_last;
1213
1214     if (!IDList) {
1215         Malloc(1, id_last, ID_t);
1216         IDList = id_last;
1217     }
1218     else {
1219         Malloc(1, id_last->next, ID_t);
1220         id_last = id_last->next;
1221     }
1222     id_last->elem = e;
1223     id_last->id   = idval;
1224 }
1225
1226 /* ______________________________________________________________________ */
1227 /*  Return pointer to element who's ID is given.
1228  *  Arguments:
1229  *      Element's ID attribute value (a string).
1230  *  Return:
1231  *      Pointer to element whose ID matches.
1232  */
1233
1234 Element_t *
1235 FindElemByID(
1236     char        *idval
1237 )
1238 {
1239     ID_t        *id;
1240     for (id=IDList; id; id=id->next)
1241         if (id->id[0] == idval[0] && !strcmp(id->id, idval)) return id->elem;
1242     return 0;
1243 }
1244
1245 /* ______________________________________________________________________ */
1246