Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / doc / util / dbtoman / instant / tranvar.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 /* $XConsortium: tranvar.c /main/2 1996/12/07 13:15:57 rws $ */
24 /*
25  *  Copyright 1993 Open Software Foundation, Inc., Cambridge, Massachusetts.
26  *  All rights reserved.
27  */
28 /*
29  * Copyright (c) 1994  
30  * Open Software Foundation, Inc. 
31  *  
32  * Permission is hereby granted to use, copy, modify and freely distribute 
33  * the software in this file and its documentation for any purpose without 
34  * fee, provided that the above copyright notice appears in all copies and 
35  * that both the copyright notice and this permission notice appear in 
36  * supporting documentation.  Further, provided that the name of Open 
37  * Software Foundation, Inc. ("OSF") not be used in advertising or 
38  * publicity pertaining to distribution of the software without prior 
39  * written permission from OSF.  OSF makes no representations about the 
40  * suitability of this software for any purpose.  It is provided "as is" 
41  * without express or implied warranty. 
42  */
43 /*
44  * Copyright (c) 1996 X Consortium
45  * Copyright (c) 1995, 1996 Dalrymple Consulting
46  * 
47  * Permission is hereby granted, free of charge, to any person obtaining a copy
48  * of this software and associated documentation files (the "Software"), to deal
49  * in the Software without restriction, including without limitation the rights
50  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
51  * copies of the Software, and to permit persons to whom the Software is
52  * furnished to do so, subject to the following conditions:
53  * 
54  * The above copyright notice and this permission notice shall be included in
55  * all copies or substantial portions of the Software.
56  * 
57  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
58  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
59  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
60  * X CONSORTIUM OR DALRYMPLE CONSULTING BE LIABLE FOR ANY CLAIM, DAMAGES OR
61  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
62  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
63  * OTHER DEALINGS IN THE SOFTWARE.
64  * 
65  * Except as contained in this notice, the names of the X Consortium and
66  * Dalrymple Consulting shall not be used in advertising or otherwise to
67  * promote the sale, use or other dealings in this Software without prior
68  * written authorization.
69  */
70 /* ________________________________________________________________________
71  *
72  *  instant - a program to manipulate SGML instances.
73  *
74  *  This module is for handling "special variables".  These act a lot like
75  *  procedure calls
76  * ________________________________________________________________________
77  */
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 <errno.h>
86
87 #include <tptregexp.h>
88 #include "general.h"
89 #include "translate.h"
90
91 static char     **idrefs;               /* list of IDREF att names to follow */
92 static char     *def_idrefs[] = { "LINKEND", "LINKENDS", "IDREF", 0 };
93 static char     *each_A = 0;    /* last seen _eachatt */
94 static char     *each_C = 0;    /* last seen _eachcon */
95
96 /* forward references */
97 void    ChaseIDRefs(Element_t *, char *, char *, FILE *);
98 void    Find(Element_t *, int, char **, FILE *);
99 void    GetIDREFnames();
100
101 /* ______________________________________________________________________ */
102 /*  Handle "special" variable - read file, run command, do action, etc.
103  *  Arguments:
104  *      Name of special variable to expand.
105  *      Pointer to element under consideration.
106  *      FILE pointer to where to write output.
107  *      Flag saying whether to track the character position we're on
108  *        (passed to OutputString).
109  */
110
111 void
112 ExpandSpecialVar(
113     char        *name,
114     Element_t   *e,
115     FILE        *fp,
116     int         track_pos
117 )
118 {
119     FILE        *infile;
120     char        buf[LINESIZE], *cp, *cp2, *atval;
121     char        **tok;
122     int         ntok, n, i, actioni;
123     char        *action, *action1;
124     Element_t   *ep;
125     Trans_t     *t, *tt;
126
127     /* Run a command.
128      * Format: _! command args ... */
129     if (*name == '!') {
130         name++;
131         if ((infile = popen(name, "r"))) {
132             while (fgets(buf, LINESIZE, infile)) fputs(buf, fp);
133             pclose(infile);
134             fflush(fp);
135         }
136         else {
137             fprintf(stderr, "Could not start program '%s': %s",
138                 name, strerror(errno));
139         }
140         return;
141     }
142
143     /* See if caller wants one of the tokens from _eachatt or _eachcon.
144      * If so, output it and return.  (Yes, I admit that this is a hack.)
145      */
146     if (*name == 'A' && name[1] == EOS && each_A) {
147         OutputString(each_A, fp, track_pos);
148         return;
149     }
150     if (*name == 'C' && name[1] == EOS && each_C) {
151         OutputString(each_C, fp, track_pos);
152         return;
153     }
154
155     ntok = 0;
156     tok = Split(name, &ntok, 0);
157
158     /* Include another file.
159      * Format: _include filename */
160     if (StrEq(tok[0], "include")) {
161         name = tok[1];
162         if (ntok > 1 ) {
163             if ((infile=OpenFile(name)) == NULL) {
164                 sprintf(buf, "Can not open included file '%s'", name);
165                 perror(buf);
166                 return;
167             }
168             while (fgets(buf, LINESIZE, infile)) fputs(buf, fp);
169             fclose(infile);
170         }
171         else fprintf(stderr, "No file name specified for include\n");
172         return;
173     }
174
175     /* Print location (nearest title, line no, path).
176      * Format: _location */
177     else if (StrEq(tok[0], "location")) {
178         PrintLocation(e, fp);
179     }
180
181     /* Print path to this element.
182      * Format: _path */
183     else if (StrEq(tok[0], "path")) {
184         (void)FindElementPath(e, buf);
185         OutputString(buf, fp, track_pos);
186     }
187
188     /* Print name of this element (gi).
189      * Format: _gi [M|L|U] */
190     else if (StrEq(tok[0], "gi")) {
191         strcpy(buf, e->gi);
192         if (ntok >= 2) {
193             if (*tok[1] == 'L' || *tok[1] == 'l' ||
194                 *tok[1] == 'M' || *tok[1] == 'm') {
195                 for (cp=buf; *cp; cp++)
196                     if (isupper(*cp)) *cp = tolower(*cp);
197             }
198             if (*tok[1] == 'M' || *tok[1] == 'm')
199                 if (islower(buf[0])) buf[0] = toupper(buf[0]);
200         }
201         OutputString(buf, fp, track_pos);
202     }
203
204     /* Print filename of this element's associated external entity.
205      * Format: _filename */
206     else if (StrEq(tok[0], "filename")) {
207         if (!e->entity) {
208             fprintf(stderr, "Expected ext entity (internal error? bug?):\n");
209             PrintLocation(e, stderr);
210             return;
211         }
212         if (!e->entity->fname) {
213             fprintf(stderr, "Expected filename (internal error? bug?):\n");
214             PrintLocation(e, stderr);
215             return;
216         }
217         OutputString(e->entity->fname, fp, track_pos);
218     }
219
220     /* Value of parent's attribute, by attr name.
221      * Format: _pattr attname */
222     else if (StrEq(tok[0], "pattr")) {
223         ep = e->parent;
224         if (!ep) {
225             fprintf(stderr, "Element does not have a parent:\n");
226             PrintLocation(ep, stderr);
227             return;
228         }
229         if ((atval = FindAttValByName(ep, tok[1]))) {
230             OutputString(atval, fp, track_pos);
231         }
232     }
233
234     /* Use an action, given transpec's SID.
235      * Format: _action action */
236     else if (StrEq(tok[0], "action")) {
237         TranTByAction(e, tok[1], fp);
238     }
239
240     /* Number of child elements of this element.
241      * Format: _nchild */
242     else if (StrEq(tok[0], "nchild")) {
243         if (ntok > 1) {
244             for (n=0,i=0; i<e->necont; i++)
245                 if (StrEq(e->econt[i]->gi, tok[1])) n++;
246         }
247         else n = e->necont;
248         sprintf(buf, "%d", n);
249         OutputString(buf, fp, track_pos);
250     }
251
252     /* number of 1st child's child elements (grandchildren from first child).
253      * Format: _n1gchild */
254     else if (StrEq(tok[0], "n1gchild")) {
255         if (e->necont) {
256             sprintf(buf, "%d", e->econt[0]->necont);
257             OutputString(buf, fp, track_pos);
258         }
259     }
260
261     /* Chase this element's pointers until we hit the named GI.
262      * Do the action if it matches.
263      * Format: _chasetogi gi action */
264     else if (StrEq(tok[0], "chasetogi")) {
265         if (ntok < 3) {
266             fprintf(stderr, "Error: Not enough args for _chasetogi.\n");
267             return;
268         }
269         actioni = atoi(tok[2]);
270         if (actioni) ChaseIDRefs(e, tok[1], tok[2], fp);
271     }
272
273     /* Follow link to element pointed to, then do action.
274      * Format: _followlink [attname] action. */
275     else if (StrEq(tok[0], "followlink")) {
276         char **s;
277         if (ntok > 2) {
278             if ((atval = FindAttValByName(e, tok[1]))) {
279                 if ((ep = FindElemByID(atval))) {
280                     TranTByAction(ep, tok[2], fp);
281                     return;
282                 }
283             }
284             else fprintf(stderr, "Error: Did not find attr: %s.\n", tok[1]);
285             return;
286         }
287         GetIDREFnames();
288         for (s=idrefs; *s; s++) {
289             /* is this IDREF attr set? */
290             if ((atval = FindAttValByName(e, *s))) {
291                 ntok = 0;
292                 tok = Split(atval, &ntok, S_STRDUP);
293                 /* we'll follow the first one... */
294                 if ((ep = FindElemByID(tok[0]))) {
295                     TranTByAction(ep, tok[1], fp);
296                     return;
297                 }
298                 else fprintf(stderr, "Error: Can not find elem for ID: %s.\n",
299                         tok[0]);
300             }
301         }
302         fprintf(stderr, "Error: Element does not have IDREF attribute set:\n");
303         PrintLocation(e, stderr);
304         return;
305     }
306
307     /* Starting at this element, decend tree (in-order), finding GI.
308      * Do the action if it matches.
309      * Format: _find args ... */
310     else if (StrEq(tok[0], "find")) {
311         Find(e, ntok, tok, fp);
312     }
313
314     /* Starting at this element's parent, decend tree (in-order), finding GI.
315      * Do the action if it matches.
316      * Format: _pfind args ... */
317     else if (StrEq(tok[0], "pfind")) {
318         Find(e->parent ? e->parent : e, ntok, tok, fp);
319     }
320
321     /* Content is supposed to be a list of IDREFs.  Follow each, doing action.
322      * If 2 actions are specified, use 1st for the 1st ID, 2nd for the rest.
323      * Format: _namelist action [action2] */
324     else if (StrEq(tok[0], "namelist")) {
325         int id;
326         action1 = tok[1];
327         if (ntok > 2) action = tok[2];
328         else action = action1;
329         for (i=0; i<e->ndcont; i++) {
330             n = 0;
331             tok = Split(e->dcont[i], &n, S_STRDUP);
332             for (id=0; id<n; id++) {
333                 if (fold_case)
334                     for (cp=tok[id]; *cp; cp++)
335                         if (islower(*cp)) *cp = toupper(*cp);
336                 if ((e = FindElemByID(tok[id]))) {
337                     if (id) TranTByAction(e, action, fp);
338                     else TranTByAction(e, action1, fp); /* first one */
339                 }
340                 else fprintf(stderr, "Error: Can not find ID: %s.\n", tok[id]);
341             }
342         }
343     }
344
345     /* For each word in the element's content, do action.
346      * Format: _eachcon action [action] */
347     else if (StrEq(tok[0], "eachcon")) {
348         int id;
349         action1 = tok[1];
350         if (ntok > 3) action = tok[2];
351         else action = action1;
352         for (i=0; i<e->ndcont; i++) {
353             n = 0;
354             tok = Split(e->dcont[i], &n, S_STRDUP|S_ALVEC);
355             for (id=0; id<n; id++) {
356                 each_C = tok[id];
357                 TranTByAction(e, action, fp);
358             }
359             free(*tok);
360         }
361     }
362     /* For each word in the given attribute's value, do action.
363      * Format: _eachatt attname action [action] */
364     else if (StrEq(tok[0], "eachatt")) {
365         int id;
366         action1 = tok[2];
367         if (ntok > 3) action = tok[3];
368         else action = action1;
369         if ((atval = FindAttValByName(e, tok[1]))) {
370             n = 0;
371             tok = Split(atval, &n, S_STRDUP|S_ALVEC);
372             for (id=0; id<n; id++) {
373                 each_A = tok[id];
374                 if (id) TranTByAction(e, action, fp);
375                 else TranTByAction(e, action1, fp);     /* first one */
376             }
377             free(*tok);
378         }
379     }
380
381     /* Do action on this element if element has [relationship] with gi.
382      * Format: _relation relationship gi action [action] */
383     else if (StrEq(tok[0], "relation")) {
384         if (ntok >= 4) {
385             if (!CheckRelation(e, tok[1], tok[2], tok[3], fp, RA_Current)) {
386                 /* action not done, see if alt action specified */
387                 if (ntok >= 5)
388                     TranTByAction(e, tok[4], fp);
389             }
390         }
391     }
392
393     /* Do action on followed element if element has [relationship] with gi.
394      * Format: _followrel relationship gi action */
395     else if (StrEq(tok[0], "followrel")) {
396         if (ntok >= 4)
397             (void)CheckRelation(e, tok[1], tok[2], tok[3], fp, RA_Related);
398     }
399
400     /* Find element with matching ID and do action.  If action not specified,
401      * choose the right one appropriate for its context.
402      * Format: _id id [action] */
403     else if (StrEq(tok[0], "id")) {
404         if ((ep = FindElemByID(tok[1]))) {
405             if (ntok > 2) TranTByAction(ep, tok[2], fp);
406             else {
407                 t = FindTrans(ep, 0);
408                 TransElement(ep, fp, t);
409             }
410         }
411     }
412
413     /* Set variable to value.
414      * Format: _set name value */
415     else if (StrEq(tok[0], "set")) {
416         SetMappingNV(Variables, tok[1], tok[2]);
417     }
418
419     /* Do action if variable is set, optionally to value.
420      * If not set, do nothing.
421      * Format: _isset varname [value] action 
422      * Format: _issete varname [value] action  --  expands value */
423     else if (StrEq(tok[0], "isset") || StrEq(tok[0], "issete")) {
424         if ((cp = FindMappingVal(Variables, tok[1]))) {
425             if (ntok == 3) TranTByAction(e, tok[2], fp);
426             else
427             if (ntok > 3)       {
428                 if ( StrEq(tok[0], "issete") )  {
429                         ExpandVariables(tok[2], buf, e);
430                         cp2 = buf;
431                 } else
432                         cp2 = tok[2];
433                 if ( !strcmp(cp, cp2))
434                         TranTByAction(e, tok[3], fp);
435             }
436         }
437     }
438
439     /* Insert a node into the tree at start/end, pointing to action to perform.
440      * Format: _insertnode S|E action */
441     else if (StrEq(tok[0], "insertnode")) {
442         actioni = atoi(tok[2]);
443         if (*tok[1] == 'S') e->gen_trans[0] = actioni;
444         else if (*tok[1] == 'E') e->gen_trans[1] = actioni;
445     }
446
447     /* Do an CALS DTD table spec for TeX or troff.  Looks through attributes
448      * and determines what to output. "check" means to check consistency,
449      * and print error messages.
450      * This is (hopefully) the only hard-coded part of instant.
451      *
452      * This was originally written for the OSF DTDs and recoded by FLD for
453      * CALS tables (since no one will ever use the OSF tables).  Although
454      * TeX was addressed first, it seems that a fresh approach was required,
455      * and so, tbl is the first to be really *fixed*.  Once tbl is stable,
456      * and there is a need for TeX again, that part will be recoded.
457      *
458      * *Obsolete* form (viz, for TeX):
459      *    Format: _calstable [clear|check|tex]
460      *                    [cellstart|cellend|rowstart|rowend|top|bottom]
461      *
462      * New, good form:
463      *
464      *    Format: _calstable [tbl]
465      *                    [tablestart|tableend|tablegroup|tablefoot|rowstart|
466      *                     rowend|entrystart|entryend]
467      */
468
469     else if (StrEq(tok[0], "calstable")) {
470         CALStable(e, fp, tok, ntok);
471     }
472
473     /* Do action if element's attr is set, optionally to value.
474      * If not set, do nothing.
475      * Format: _attval att [value] action */
476     else if (StrEq(tok[0], "attval")) {
477         if ((atval = FindAttValByName(e, tok[1]))) {
478             if (ntok == 3) TranTByAction(e, tok[2], fp);
479             else if (ntok > 3 && !strcmp(atval, tok[2]))
480                 TranTByAction(e, tok[3], fp);
481         }
482     }
483     /* Same thing, but look at parent */
484     else if (StrEq(tok[0], "pattval")) {
485         if ((atval = FindAttValByName(e->parent, tok[1]))) {
486             if (ntok == 3) {
487                 TranTByAction(e, tok[2], fp);
488             }
489             if (ntok > 3 && !strcmp(atval, tok[2]))
490                 TranTByAction(e, tok[3], fp);
491         }
492     }
493
494     /* Print each attribute and value for the current element, hopefully
495      * in a legal sgml form: <elem-name att1="value1" att2="value2:> .
496      * Format: _allatts */
497     else if (StrEq(tok[0], "allatts")) {
498         for (i=0; i<e->natts; i++) {
499             if (i != 0) putc(' ', fp);
500             fputs(e->atts[i].name, fp);
501             fputs("=\"", fp);
502             fputs(e->atts[i].sval, fp);
503             putc('"', fp);
504         }
505     }
506
507     /* Print the element's input filename, and optionally, the line number.
508      * Format: _infile [line] */
509     else if (StrEq(tok[0], "infile")) {
510         if (e->infile) {
511             if (ntok > 1 && !strcmp(tok[1], "root")) {
512                 strcpy(buf, e->infile);
513                 if ((cp = strrchr(buf, '.'))) *cp = EOS;
514                 fputs(buf, fp);
515             }
516             else {
517                 fputs(e->infile, fp);
518                 if (ntok > 1 && !strcmp(tok[1], "line"))
519                     fprintf(fp, " %d", e->lineno);
520             }
521             return;
522         }
523         else fputs("input-file??", fp);
524     }
525
526     /* Get value of an environement variable */
527     else if (StrEq(tok[0], "env")) {
528         if (ntok > 1 && (cp = getenv(tok[1]))) {
529             OutputString(cp, fp, track_pos);
530         }
531     }
532
533     /* Something unknown */
534     else {
535         fprintf(stderr, "Unknown special variable: %s\n", tok[0]);
536         tt = e->trans;
537         if (tt && tt->lineno)
538             fprintf(stderr, "Used in transpec, line %d\n", tt->lineno);
539     }
540     return;
541 }
542
543 /* ______________________________________________________________________ */
544 /*  return the value for the special variables _A (last processed _eachatt)
545  *  and _C (last processed _eachcon)
546  */
547
548 char *
549 Get_A_C_value(char * name)
550 {
551     if ( !strcmp(name, "each_A") )      {
552         if ( each_A )   {
553             return each_A;
554         } else  {
555             fprintf(stderr, "Requested value for unset _A variable\n");
556         }
557     } else
558     if ( !strcmp(name, "each_C") )      {
559         if ( each_C )   {
560             return each_C;
561         } else  {
562             fprintf(stderr, "Requested value for unset _C variable\n");
563         }
564     } else      {
565         fprintf(stderr, "Requested value for unknown special variable '%s'\n",
566                                 name);
567     }
568     return "";
569 }
570
571 /* ______________________________________________________________________ */
572 /*  Chase IDs until we find an element whose GI matches.  We also check
573  *  child element names, not just the names of elements directly pointed
574  *  at (by IDREF attributes).
575  */
576
577 void
578 GetIDREFnames()
579 {
580     char        *cp;
581
582     if (!idrefs) {
583         /* did user or transpec set the variable */
584         if ((cp = FindMappingVal(Variables, "link_atts")))
585             idrefs = Split(cp, 0, S_STRDUP|S_ALVEC);
586         else
587             idrefs = def_idrefs;
588     }
589 }
590
591 /* ______________________________________________________________________ */
592 /*  Chase ID references - follow IDREF(s) attributes until we find
593  *  a GI named 'gi', then perform given action on that GI.
594  *  Arguments:
595  *      Pointer to element under consideration.
596  *      Name of GI we're looking for.
597  *      Spec ID of action to take.
598  *      FILE pointer to where to write output.
599  */
600 void
601 ChaseIDRefs(
602     Element_t   *e,
603     char        *gi,
604     char *      action,
605     FILE        *fp
606 )
607 {
608     int         ntok, i, ei;
609     char        **tok, **s, *atval;
610
611     /* First, see if we got what we came for with this element */
612     if (StrEq(e->gi, gi)) {
613         TranTByAction(e, action, fp);
614         return;
615     }
616     GetIDREFnames();
617
618     /* loop for each attribute of type IDREF(s) */
619     for (s=idrefs; *s; s++) {
620         /* is this IDREF attr set? */
621         if ((atval = FindAttValByName(e, *s))) {
622             ntok = 0;
623             tok = Split(atval, &ntok, 0);
624             for (i=0; i<ntok; i++) {
625                 /* get element pointed to */
626                 if ((e = FindElemByID(tok[i]))) {
627                     /* OK, we found a matching GI name */
628                     if (StrEq(e->gi, gi)) {
629                         /* process using named action */
630                         TranTByAction(e, action, fp);
631                         return;
632                     }
633                     else {
634                         /* this elem itself did not match, try its children */
635                         for (ei=0; ei<e->necont; ei++) {
636                             if (StrEq(e->econt[ei]->gi, gi)) {
637                                 TranTByAction(e->econt[ei], action, fp);
638                                 return;
639                             }
640                         }
641                         /* try this elem's IDREF attributes */
642                         ChaseIDRefs(e, gi, action, fp);
643                         return;
644                     }
645                 }
646                 else {
647                     /* should not happen, since parser checks ID/IDREFs */
648                     fprintf(stderr, "Error: Could not find ID %s\n", atval);
649                     return;
650                 }
651             }
652         }
653     }
654     /* if the pointers didn't lead to the GI, give error */
655     if (!s)
656         fprintf(stderr, "Error: Could not find '%s'\n", gi);
657 }
658
659 /* ______________________________________________________________________ */
660
661 /* state to pass to recursive routines - so we don't have to use
662  * global variables. */
663 typedef struct {
664     char        *gi;
665     char        *gi2;
666     char        action[10];
667     Element_t   *elem;
668     FILE        *fp;
669 } Descent_t;
670
671 static void
672 tr_find_gi(
673     Element_t   *e,
674     Descent_t   *ds
675 )
676 {
677     if (StrEq(ds->gi, e->gi))
678         if (ds->action[0]) TranTByAction(e, ds->action, ds->fp);
679 }
680
681 static void
682 tr_find_gipar(
683     Element_t   *e,
684     Descent_t   *ds
685 )
686 {
687     if (StrEq(ds->gi, e->gi) && e->parent &&
688                 StrEq(ds->gi2, e->parent->gi))
689         if (ds->action[0]) TranTByAction(e, ds->action, ds->fp);
690 }
691
692 static void
693 tr_find_attr(
694     Element_t   *e,
695     Descent_t   *ds
696 )
697 {
698     char        *atval;
699     if ((atval = FindAttValByName(e, ds->gi)) && StrEq(ds->gi2, atval))
700         TranTByAction(e, ds->action, ds->fp);
701 }
702
703 static void
704 tr_find_parent(
705     Element_t   *e,
706     Descent_t   *ds
707 )
708 {
709     if (QRelation(e, ds->gi, REL_Parent)) {
710         if (ds->action[0]) TranTByAction(e, ds->action, ds->fp);
711     }
712 }
713
714 /* ______________________________________________________________________ */
715 /*  Descend tree, finding elements that match criteria, then perform
716  *  given action.
717  *  Arguments:
718  *      Pointer to element under consideration.
719  *      Number of tokens in special variable.
720  *      Vector of tokens in special variable (eg, "find" "gi" "TITLE")
721  *      FILE pointer to where to write output.
722  */
723 void
724 Find(
725     Element_t   *e,
726     int         ac,
727     char        **av,
728     FILE        *fp
729 )
730 {
731     Descent_t   DS;             /* state passed to recursive routine */
732
733     memset(&DS, 0, sizeof(Descent_t));
734     DS.elem = e;
735     DS.fp   = fp;
736
737     /* see if we should start at the top of instance tree */
738     if (StrEq(av[1], "top")) {
739         av++;
740         ac--;
741         e = DocTree;
742     }
743     if (ac < 4) {
744         fprintf(stderr, "Bad '_find' specification - missing args.\n");
745         return;
746     }
747     /* Find elem whose GI is av[2] */
748     if (StrEq(av[1], "gi")) {
749         DS.gi     = av[2];
750         strcpy(DS.action, av[3]);
751         DescendTree(e, tr_find_gi, 0, 0, &DS);
752     }
753     /* Find elem whose GI is av[2] and whose parent GI is av[3] */
754     else if (StrEq(av[1], "gi-parent")) {
755         DS.gi     = av[2];
756         DS.gi2    = av[3];
757         strcpy(DS.action, av[4]);
758         DescendTree(e, tr_find_gipar, 0, 0, &DS);
759     }
760     /* Find elem whose parent GI is av[2] */
761     else if (StrEq(av[0], "parent")) {
762         DS.gi     = av[2];
763         strcpy(DS.action, av[3]);
764         DescendTree(e, tr_find_parent, 0, 0, &DS);
765     }
766     /* Find elem whose attribute av[2] has value av[3] */
767     else if (StrEq(av[0], "attr")) {
768         DS.gi     = av[2];
769         DS.gi2    = av[3];
770         strcpy(DS.action, av[4]);
771         DescendTree(e, tr_find_attr, 0, 0, &DS);
772     }
773 }
774
775 /* ______________________________________________________________________ */
776