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