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