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