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