/*
 *  Copyright 1993 Open Software Foundation, Inc., Cambridge, Massachusetts.
 *  All rights reserved.
 */
/*
 * Copyright (c) 1994  
 * Open Software Foundation, Inc. 
 *  
 * Permission is hereby granted to use, copy, modify and freely distribute 
 * the software in this file and its documentation for any purpose without 
 * fee, provided that the above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation.  Further, provided that the name of Open 
 * Software Foundation, Inc. ("OSF") not be used in advertising or 
 * publicity pertaining to distribution of the software without prior 
 * written permission from OSF.  OSF makes no representations about the 
 * suitability of this software for any purpose.  It is provided "as is" 
 * without express or implied warranty. 
 */
/*
 * Copyright (c) 1996 X Consortium
 * Copyright (c) 1995, 1996 Dalrymple Consulting
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
 * X CONSORTIUM OR DALRYMPLE CONSULTING BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 * Except as contained in this notice, the names of the X Consortium and
 * Dalrymple Consulting shall not be used in advertising or otherwise to
 * promote the sale, use or other dealings in this Software without prior
 * written authorization.
 */
/* $XFree86: doctools/sgmlformat/instant/translate.c,v 1.2 1998/04/12 12:12:49 dawes Exp $ */
/* ________________________________________________________________________
 *
 *  Program to manipulate SGML instances.
 *
 *  This module is for "translating" an instance to another form, usually
 *  suitable for a formatting application.
 *
 *  Entry points for this module:
 *	DoTranslate(elem, fp)
 *      ExpandVariables(in, out, e)
 * ________________________________________________________________________
 */

#ifndef lint
static char *RCSid =
  "$Header: /home/ncvs/src/usr.bin/sgmls/instant/translate.c,v 1.1.1.1 1996/09/08 01:55:10 jfieber Exp $";
#endif

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <memory.h>
#include <sys/types.h>
#include <errno.h>
#include <regex.h>

#include "general.h"
#define STORAGE
#include "translate.h"

static Trans_t	NullTrans;		/* an empty one */

/* forward references */
void	ProcesOutputSpec(char *, Element_t *, FILE *, int);
static void	WasProcessed(Element_t *);

/* ______________________________________________________________________ */
/*  Translate the subtree starting at 'e'. Output goes to 'fp'.
 *  This is the entry point for translating an instance.
 *  Arguments:
 *	Pointer to element under consideration.
 *	FILE pointer to where to write output.
 */

void
DoTranslate(
    Element_t	*e,
    FILE	*fp
)
{
    Trans_t	*t, *tn;

    /* Find transpec for each node. */
    DescendTree(e, PrepTranspecs, 0, 0, 0);

    /* Stuff to do at start of processing */
    if ((t = FindTransByName("_Start"))) {
	if (t->starttext) ProcesOutputSpec(t->starttext, 0, fp, 1);
	if (t->replace)   ProcesOutputSpec(t->replace, 0, fp, 1);
	if (t->message)   ProcesOutputSpec(t->message, 0, stderr, 0);
	if (t->endtext)   ProcesOutputSpec(t->endtext, 0, fp, 1);
    }

    /* Translate topmost/first element.  This is recursive. */
    TransElement(e, fp, NULL);

    /* Stuff to do at end of processing */
    if ((t = FindTransByName("_End"))) {
	if (t->starttext) ProcesOutputSpec(t->starttext, 0, fp, 1);
	if (t->replace)   ProcesOutputSpec(t->replace, 0, fp, 1);
	if (t->message)   ProcesOutputSpec(t->message, 0, stderr, 0);
	if (t->endtext)   ProcesOutputSpec(t->endtext, 0, fp, 1);
    }

    /* Warn about unprocessed elements in this doc tree, if verbose mode. */
    if (verbose)
	DescendTree(e, WasProcessed, 0, 0, 0);

    /* Clean up. This is not yet complete, which is no big deal (since the
     * program is normally done at this point anyway.  */
    for (t=TrSpecs; t; ) {
	tn = t->next;
	/* free the contents of t here ... */
	(void)free((void* )t);
	t = tn;
    }
    TrSpecs = 0;
}

/* ______________________________________________________________________ */
/*  Print warning about unprocessed elements in this doc tree (if they
 *  were not explicitely ignored).
 *  Arguments:
 *	Pointer to element under consideration.
 */
static void
WasProcessed(
    Element_t	*e
)
{
    Trans_t	*t;
    t = e->trans;
    if (!e->processed && (t && !t->ignore)) {
	fprintf(stderr, "Warning: element '%s' was not processed:\n", e->gi);
	PrintLocation(e, stderr);
    }
}

/* ______________________________________________________________________ */
/*  For each element find transpec.
 *  Arguments:
 *	Pointer to element under consideration.
 */
void
PrepTranspecs(
    Element_t	*e
)
{
    Trans_t	*t;
    t = FindTrans(e, 0);
    e->trans = t;
}

/* ______________________________________________________________________ */
/*  Copy a buffer/string into another, expanding regular variables and immediate
 *  variables. (Special variables are done later.)
 *  Arguments:
 *	Pointer to string to expand.
 *	Pointer to expanded string. (return)
 *	Pointer to element under consideration.
 */
void
ExpandVariables(
    char	*in,
    char	*out,
    Element_t	*e
)
{
    register int i, j;
    char	*ip, *vp, *op;
    char	*def_val, *s, *atval, *modifier;
    char	vbuf[500];
    int		lev;

    ip = in;
    op = out;
    while (*ip) {
	/* start of regular variable? */
	if (*ip == '$' && *(ip+1) == L_CURLY && *(ip+2) != '_') {
	    ip++;
	    ip++;		/* point at variable name */
	    vp = vbuf;
	    /*	Look for matching (closing) curly. (watch for nesting)
	     *	We store the variable content in a tmp buffer, so we don't
	     *	clobber the input buffer.
	     */
	    lev = 0;
	    while (*ip) {
		if (*ip == L_CURLY) lev++;
		if (*ip == R_CURLY) {
		    if (lev == 0) {
			ip++;
			break;
		    }
		    else lev--;
		}
		*vp++ = *ip++;	/* copy to variable buffer */
	    }
	    *vp = EOS;
	    /* vbuf now contains the variable name (stuff between curlys). */
	    if (lev != 0) {
		fprintf(stderr, "Botched variable use: %s\n", in);
		/* copy rest of string if we can't recover  ?? */
		return;
	    }
	    /* Now, expand variable. */
	    vp = vbuf;

	    /* Check for immediate variables -- like _special variables but
	     * interpreted right now.  These start with a "+" */

	    if ( *vp == '+' )	{

	    	if ( ! strcmp(vp, "+content") )	{
	    	    for ( i=0;  i<e->ncont; i++ )	{
	    	    	if ( IsContData(e, i) )	{
	    	    	    j = strlen(ContData(e,i));
	    	    	    memcpy(op, ContData(e,i), j);
	    	    	    op += j;
	    	    	} else	{
	    	    	    fprintf(stderr, "warning: ${+current} skipped element content\n");
	    	    	}
	    	    }

	    	} else	{
	    	    fprintf(stderr, "unknown immediate variable: %s\n", vp);
	    	}

	    } else	{

		/* See if this variable has a default [ format: ${varname def} ] */

		def_val = vp;
		while (*def_val && *def_val != ' ') def_val++;
		if (*def_val) *def_val++ = EOS;
		else def_val = 0;
		/* def_val now points to default, if it exists, null if not. */

		modifier = vp;
		while (*modifier && *modifier != ':') modifier++;
		if (*modifier) *modifier++ = EOS;
		else modifier = 0;
		/* modifier now points to modifier if it exists, null if not. */

		s = 0;
		/* if attribute of current elem with this name found, use value */
		if (e && (atval = FindAttValByName(e, vp)))
		    s = atval;
	   	 else	/* else try for (global) variable with this name */
		    s = FindMappingVal(Variables, vp);

		/* If we found a value, copy it to the output buffer. */

		if (s) {
		    if ( modifier && *modifier == 'l' ) {
			while (*s) {
			    *op = tolower(*s);
			    op++, *s++;
			}
		    } else
			while (*s) *op++ = *s++;
		} else
		if (def_val)	{
		    while (*def_val) *op++ = *def_val++;
		}
	    }
	    continue;
	}
	*op++ = *ip++;
    }
    *op = EOS;		/* terminate string */
}

/* ______________________________________________________________________ */
/*  Process an "output" translation spec - one of StartText, EndText,
 *  Replace, Message.  (These are the ones that produce output.)
 *  Steps done:
 *	Expand attributes and regular varaibles in input string.
 *	Pass thru string, accumulating chars to be sent to output stream.
 *	If we find the start of a special variable, output what we've
 *	  accumulated, then find the special variable's "bounds" (ie, the
 *	  stuff between the curly brackets), and expand that by passing to
 *	  ExpandSpecialVar().  Continue until done the input string.
 *  Arguments:
 *	Input buffer (string) to be expanded and output.
 *	Pointer to element under consideration.
 *	FILE pointer to where to write output.
 *	Flag saying whether to track the character position we're on
 *	  (passed to OutputString).
 */
void
ProcesOutputSpec(
    char	*ib,
    Element_t	*e,
    FILE	*fp,
    int		track_pos
)
{
    char	obuf[LINESIZE];
    char	vbuf[LINESIZE];
    char	*dest, vname[LINESIZE], *cp;
    int		esc;

    obuf[0] = EOS;			/* start with empty output buffer */

    ExpandVariables(ib, vbuf, e);	/* expand regular variables */
    ib = vbuf;
    dest = obuf;

    esc = 0;
    while (*ib) {
	/* If not a $, it's a regular char.  Just copy it and go to next. */
	if (*ib != '$') {		/* look for att/variable marker */
	    *dest++ = *ib++;		/* it's not. just copy character */
	    continue;
	}

	/* We have a $.  What we have must be a "special variable" since
	 * regular variables have already been expanded, or just a lone $. */

	if (ib[1] != L_CURLY) {	/* just a stray dollar sign (no variable) */
	    *dest++ = *ib++;
	    continue;
	}

	ib++;				/* point past $ */

	/* Output what we have in buffer so far. */
	*dest = EOS;			/* terminate string */
	if (obuf[0]) OutputString(obuf, fp, track_pos);
	dest = obuf;			/* ready for new stuff in buffer */

	if (!strchr(ib, R_CURLY)) {
	    fprintf(stderr, "Mismatched braces in TranSpec: %s\n", ib);
	    /* how do we recover from this? */
	}
	ib++;
	cp = vname;
	while (*ib && *ib != R_CURLY) *cp++ = *ib++;
	*cp = EOS;			/* terminate att/var name */
	ib++;				/* point past closing curly */
	/* we now have special variable name (stuff in curly {}'s) in vname */
	ExpandSpecialVar(&vname[1], e, fp, track_pos);
    }
    *dest = EOS;			/* terminate string in output buffer */

    if (obuf[0]) OutputString(obuf, fp, track_pos);
}

/* ______________________________________________________________________ */
/*  Find the translation spec for the given tag.
 *  Returns pointer to first spec that matches (name, depth, etc., of tag).
 *  Arguments:
 *	e -- Pointer to element under consideration.
 *	specID -- name of specid that we're looking for
 *  Return:
 *	Pointer to translation spec that matches given element's context.
 */

Trans_t *
FindTrans(
    Element_t	*e,
    int		specID
)
{
    char	context[LINESIZE], buf[LINESIZE], *cp, **vec, *atval;
    int		i, a, match;
    Trans_t	*t, *tt;

    /* loop through all transpecs */
    for (t=TrSpecs; t; t=t->next)
    {
	/* Only one of gi or gilist will be set. */
	/* Check if elem name matches */
	if (t->gi && !StrEq(t->gi, e->gi) && !specID) continue;

	/* test if we're looking for a specific specID and then if
	 * this is it.. */
	if (specID)
	    if (!t->my_id || (specID != t->my_id))
		continue;

	/* Match one in the list of GIs? */
	if (t->gilist) {
	    for (match=0,vec=t->gilist; *vec; vec++) {
		if (StrEq(*vec, e->gi)) {
		    match = 1;
		    break;
		}
	    }
	    if (!match) continue;
	}

	/* Check context */

	/* Special case of context */
	if (t->parent)
	    if (!QRelation(e, t->parent, REL_Parent)) continue;

	if (t->context) {	/* no context specified -> a match */
	    FindContext(e, t->depth, context);

	    /* If reg expr set, do regex compare; else just string compare. */
	    if (t->context_re_set) {
		if (regexec(&t->context_re, context, 0, NULL, 0)) continue;
	    }
	    else {
		/* Is depth of spec deeper than element's depth? */
		if (t->depth > e->depth) continue;

		/* See if context of element matches "context" of transpec */
		match = ( (t->context[0] == context[0]) &&
			    !strcmp(t->context, context) );
		if (!match) continue;
	    }
	}

	/* Check attributes.  Loop through list, comparing each. */
	if (t->nattpairs) {	/* no att specified -> a match */
	    for (match=1,a=0; a<t->nattpairs; a++) {
		if (!(atval = FindAttValByName(e, t->attpair[a].name))) {
		    match = 0;
		    break;
		}
		if (regexec(&t->attpair[a].rex, atval, 0, NULL, 0)) match = 0;
	    }
	    if (!match) continue;
	}

	/* Check relationships:  child, parent, ancestor, sib, ...  */
	if (t->relations) {
	    Mapping_t *r;
	    match = 1;
	    for (r=t->relations->maps,i=0; i<t->relations->n_used; i++) {
		if (!CheckRelation(e, r[i].name, r[i].sval, 0, 0, RA_Current)) {
		    match = 0;
		    break;
		}
	    }
	    if (!match) continue;
	}

	/* check this element's parent's attribute */
	if (t->pattrset && e->parent) {
	    char *p, **tok;

	    i = 2;
	    match = 1;
	    tok = Split(t->pattrset, &i, S_STRDUP);
	    if ( i == 2 ) {
		p = FindAttValByName(e->parent, tok[0]);
		ExpandVariables(tok[1], buf, 0);
		if ( !p || strcmp(p, buf) )
		    match = 0;
	    } else {
		if (!FindAttValByName(e->parent, t->pattrset))
		    match = 0;
	    }
	    free(tok[0]);
	    if (!match) continue;
	}

	/* check this element's "birth order" */
	if (t->nth_child) {
	    /* First one is called "1" by the user.  Internally called "0". */
	    i = t->nth_child;
	    if (i > 0) {	/* positive # -- count from beginning */
		if (e->my_eorder != (i-1)) continue;
	    }
	    else {		/* negative # -- count from end */
		i = e->parent->necont - i;
		if (e->my_eorder != i) continue;
	    }
	}

	/* check that variables match */
	if (t->var_name) {
	    cp = FindMappingVal(Variables, t->var_name);
	    if (!cp || strcmp(cp, t->var_value)) continue;
	}

	/* check for variable regular expression match */
	if ( t->var_RE_name )	{
	    cp = FindMappingVal(Variables, t->var_RE_name);
	    if (!cp || regexec(&t->var_RE_value, cp, 0, NULL, 0)) continue;
	}

	/* check content */
	if (t->content) {		/* no att specified -> a match */
	    for (match=0,i=0; i<e->ndcont; i++) {
		if (!regexec(&t->content_re, e->dcont[i], 0, NULL, 0)) {
		    match = 1;
		    break;
		}
	    }
	    if (!match) continue;
	}

	/* -------- at this point we've passed all criteria -------- */

	/* See if we should be using another transpec's actions. */
	if (t->use_id) {
	    if (t->use_id < 0) return &NullTrans;	/* missing? */
	    /* see if we have a pointer to that transpec */
	    if (t->use_trans) return t->use_trans;
	    for (tt=TrSpecs; tt; tt=tt->next) {
		if (t->use_id == tt->my_id) {
		    /* remember pointer for next time */
		    t->use_trans = tt;
		    return t->use_trans;
		}
	    }
	    t->use_id = -1;	/* flag it as missing */
	    fprintf(stderr, "Warning: transpec ID (%d) not found for %s.\n",
		t->use_id, e->gi);
	    return &NullTrans;
	}

	return t;
    }

    /* At this point, we have not found a matching spec.  See if there
     * is a wildcard, and if so, use it. (Wildcard GI is named "*".) */
    if ((t = FindTransByName("*"))) return t;

    if (warnings && !specID)
	fprintf(stderr, "Warning: transpec not found for %s\n", e->gi);

    /* default spec - pass character data and descend node */
    return &NullTrans;
}

/* ______________________________________________________________________ */
/*  Find translation spec by (GI) name.  Returns the first one that matches.
 *  Arguments:
 *	Pointer to name of transpec (the "gi" field of the Trans structure).
 *  Return:
 *	Pointer to translation spec that matches name.
 */

Trans_t *
FindTransByName(
    char *s
)
{
    Trans_t *t;

    for (t=TrSpecs; t; t=t->next) {
	/* check if tag name matches (first check 1st char, for efficiency) */
	if (t->gi) {
	    if (*(t->gi) != *s) continue;	/* check 1st character */
	    if (!strcmp(t->gi, s)) return t;
	}
    }
    return NULL;
}

/*  Find translation spec by its ID (SpecID).
 *  Arguments:
 *	Spec ID (an int).
 *  Return:
 *	Pointer to translation spec that matches name.
 */
Trans_t *
FindTranByID(int n)
{
    Trans_t	*t;

    for (t=TrSpecs; t; t=t->next)
	if (n == t->my_id) return t;
    return NULL;
}

/* ______________________________________________________________________ */
/*  Process a "chunk" of content data of an element.
 *  Arguments:
 *	Pointer to data content to process
 *	FILE pointer to where to write output.
 */

void
DoData(
    char *data,
    FILE *fp,
    int  verbatim
)
{
    if (!fp) return;
    OutputString(data, fp, 1);
}

/* ______________________________________________________________________ */
/*  Handle a processing instruction.  This is done similarly to elements,
 *  where we find a transpec, then do what it says.  Differences: PI names
 *  start with '_' in the spec file (if a GI does not start with '_', it
 *  may be forced to upper case, sgmls keeps PIs as mixed case); the args
 *  to the PI are treated as the data of an element.  Note that a PI wildcard
 *  is "_*"
 *  Arguments:
 *	Pointer to the PI.
 *	FILE pointer to where to write output.
 */

void
DoPI(
    char *pi,
    FILE *fp
)
{
    char	buf[250], **tok;
    int		n;
    Trans_t	*t;

    buf[0] = '_';
    strcpy(&buf[1], pi);
    n = 2;
    tok = Split(buf, &n, 0);
    if ((t = FindTransByName(tok[0])) ||
        (t = FindTransByName("_*"))) {
	if (t->replace)   ProcesOutputSpec(t->replace, 0, fp, 1);
	else {
	    if (t->starttext) ProcesOutputSpec(t->starttext, 0, fp, 1);
	    if (t->ignore != IGN_DATA)	/* skip data nodes? */
		if (n > 1) OutputString(tok[1], fp, 1);
	    if (t->endtext)   ProcesOutputSpec(t->endtext, 0, fp, 1);
	}
	if (t->message)   ProcesOutputSpec(t->message, 0, stderr, 0);
    }
    else {
	/* If not found, just print the PI in square brackets, along
	 * with a warning message. */
	fprintf(fp, "[%s]", pi);
	if (warnings) fprintf(stderr, "Warning: Unrecognized PI: [%s]\n", pi);
    }
}

/* ______________________________________________________________________ */
/*  Set and increment variables, as appropriate, if the transpec says to.
 *  Arguments:
 *	Pointer to translation spec for current element.
 */

static void
set_and_increment(
    Trans_t	*t,
    Element_t	*e
)
{
    Mapping_t	*m;
    int		i, inc, n;
    char	*cp, buf[50];
    char	ebuf[500];

    /* set/reset variables */
    if (t->set_var) {
	for (m=t->set_var->maps,i=0; i<t->set_var->n_used; i++)	{
	    ExpandVariables(m[i].sval, ebuf, e);	/* do some expansion */
	    SetMappingNV(Variables, m[i].name, ebuf);
    	}
    }

    /* increment counters */
    if (t->incr_var) {
	for (m=t->incr_var->maps,i=0; i<t->incr_var->n_used; i++) {
	    cp = FindMappingVal(Variables, m[i].name);
	    /* if not set at all, set to 1 */
	    if (!cp) SetMappingNV(Variables, m[i].name, "1");
	    else {
		if (isdigit(*cp) || (*cp == '-' && isdigit(cp[1]))) {
		    n = atoi(cp);
		    if (m[i].sval && isdigit(*m[i].sval)) inc = atoi(m[i].sval);
		    else inc = 1;
		    sprintf(buf, "%d", (n + inc));
		    SetMappingNV(Variables, m[i].name, buf);
		} else
		if (!*(cp+1) && isalpha(*cp))	{
		    buf[0] = *cp + 1;
		    buf[1] = 0;
		    SetMappingNV(Variables, m[i].name, buf);
		}
	    }
	}
    }
}

/* ______________________________________________________________________ */
/*  Translate one element.
 *  Arguments:
 *	Pointer to element under consideration.
 *	FILE pointer to where to write output.
 *	Pointer to translation spec for current element, or null.
 */
void
TransElement(
    Element_t	*e,
    FILE	*fp,
    Trans_t	*t
)
{
    int		i;

    if (!t) t = ((e && e->trans) ? e->trans : &NullTrans);

    /* see if we should quit. */
    if (t->quit) {
	fprintf(stderr, "Quitting at location:\n");
	PrintLocation(e, fp);
	fprintf(stderr, "%s\n", t->quit);
	exit(1);
    }

    /* See if we want to replace subtree (do text, don't descend subtree) */
    if (t->replace) {
	ProcesOutputSpec(t->replace, e, fp, 1);
	if (t->message) ProcesOutputSpec(t->message, e, stderr, 0);
	set_and_increment(t, e);	/* adjust variables, if appropriate */
	return;
    }

    if (t->starttext) ProcesOutputSpec(t->starttext, e, fp, 1);
    if (t->message)   ProcesOutputSpec(t->message, e, stderr, 0);

    /* Process data for this node and descend child elements/nodes. */
    if (t->ignore != IGN_ALL) {
	/* Is there a "generated" node at the front of this one? */
	if (e->gen_trans[0]) {
	    Trans_t *tp;
	    if ((tp = FindTranByID(e->gen_trans[0]))) {
		if (tp->starttext) ProcesOutputSpec(tp->starttext, e, fp, 1);
		if (tp->message)   ProcesOutputSpec(tp->message, e, stderr, 0);
		if (tp->endtext)   ProcesOutputSpec(tp->endtext, e, fp, 1);
	    }
	}
	/* Loop thruthe "nodes", whether data, child element, or PI. */
	for (i=0; i<e->ncont; i++) {
	    if (IsContElem(e,i)) {
		if (t->ignore != IGN_CHILDREN)	/* skip child nodes? */
		    TransElement(ContElem(e,i), fp, NULL);
	    }
	    else if (IsContData(e,i)) {
		if (t->ignore != IGN_DATA)	/* skip data nodes? */
		    DoData(ContData(e,i), fp, t->verbatim);
	    }
	    else if (IsContPI(e,i))
		DoPI(e->cont[i].ch.data, fp);
	}
	/* Is there a "generated" node at the end of this one? */
	if (e->gen_trans[1]) {
	    Trans_t *tp;
	    if ((tp = FindTranByID(e->gen_trans[1]))) {
		if (tp->starttext) ProcesOutputSpec(tp->starttext, e, fp, 1);
		if (tp->message)   ProcesOutputSpec(tp->message, e, stderr, 0);
		if (tp->endtext)   ProcesOutputSpec(tp->endtext, e, fp, 1);
	    }
	}
    }

    set_and_increment(t, e);		/* adjust variables, if appropriate */

    if (t->endtext) ProcesOutputSpec(t->endtext, e, fp, 1);

    e->processed = 1;
}

/* ______________________________________________________________________ */
/* Check if element matches specified relationship, and, if it does, perform
 * action on either current element or matching element (depends on flag).
 *  Arguments:
 *	Pointer to element under consideration.
 *	Pointer to relationship name.
 *	Pointer to related element name (GI).
 *	Pointer to action to take (string - turned into an int).
 *	FILE pointer to where to write output.
 *	Flag saying whether to do action on related element (RA_Related)
 *		or on current element (RA_Current).
 *  Return:
 *	Bool, saying whether (1) or not (0) relationship matches.
 */

int
CheckRelation(
    Element_t	*e,
    char	*relname,	/* relationship name */
    char	*related,	/* related element */
    char	*actname,	/* action to take */
    FILE	*fp,
    RelAction_t	flag
)
{
    Element_t	*ep;
    Relation_t	r;

    if ((r = FindRelByName(relname)) == REL_Unknown) return 0;
    if (!(ep=QRelation(e, related, r)))	return 0;

    if (!actname) return 1;		/* no action - return what we found */

    switch (flag) {
	case RA_Related:	TranTByAction(ep, actname, fp);	break;
	case RA_Current:	TranTByAction(e, actname, fp);	break;
    }
    return 1;
}

/* ______________________________________________________________________ */
/* Perform action given by a SpecID on the given element.
 *  Arguments:
 *	Pointer to element under consideration.
 *	SpecID of action to perform.
 *	FILE pointer to where to write output.
 *
 */
void
TranByAction(
    Element_t	*e,
    int		n,
    FILE	*fp
)
{
    Trans_t	*t;

    t = FindTranByID(n);
    if (!t) {
	fprintf(stderr, "Could not find named action for %d.\n", n);
	return;
    }
    TransElement(e, fp, t);
}

/* ______________________________________________________________________ */
/* Perhaps perform action given by a SpecID on the given element.
 *  Arguments:
 *	Pointer to element under consideration.
 *	SpecID of action to perform.  Unlike TranByAction, this is the argument
 *	  as it occurred in the transpec (ASCII) and may end with the letter
 *	  "t" which means that the transpec mustpass criteria selection.
 *	FILE pointer to where to write output.
 */
void
TranTByAction(
    Element_t	*e,
    char	*strn,
    FILE	*fp
)
{
    int n;
    Trans_t	*t;

    n = atoi(strn);
    if ( strn[strlen(strn)-1] != 't' )	{
    	t = FindTranByID(n);
    	if (!t) {
	    fprintf(stderr, "Could not find named action for %d.\n", n);
	    return;
    	}
    } else	{
	t = FindTrans(e, n);
	if ( !t || !t->my_id )
	    return;
    }
    TransElement(e, fp, t);
}

/* ______________________________________________________________________ */
