/*
 * mod_aolserver aolserver emulation --- Copyright 2000 Robert S. Thau.
 * This file derived from the actual aolserver code, and is distributed
 * in accord with its license, as follows:
 *
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.lcs.mit.edu/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


/* 
 * adpfancy.c --
 *
 *	Support for registered tags within ADPs, the <script> tag,
 *      uncached ADPs, and streaming. This is a good example of
 *	the Ns_AdpRegisterParser() API call.
 */

/*
 * Hacked by Karl Goldstein and Jim Guggemos:
 * (1) Removed check for quotes so that registered tags can be placed
 * within quoted attributes (i.e. <a href="<mytag>">)
 * (2) Added BalancedEndTag function so that registered tags can be
 * nested (i.e. <mytag> ... <mytag> ... </mytag>  ... </mytag>
 *
 */

#include "nsd.h"

/*
 * Config file stuff
 */
#define CONFIG_TAGLOCKS "TagLocks"

#define DEFAULT_TAGLOCKS NS_FALSE

/*
 * Types
 */

typedef struct {
    char          *tag;        /* The name of the tag (e.g., "netscape") */
    char          *endtag;     /* The closing tag or null (e.g., "/netscape")*/
    char          *procname;   /* TCL proc handler (e.g., "ns_adp_netscape") */
    char          *adpstring;  /* ADP to evaluate */
} RegTag;

/*
 * Local functions defined in this file
 */

static void      StartText(Ns_DString *dsPtr);
static void      StartScript(Ns_DString *dsPtr);
static void      NAppendChunk(Ns_DString *dsPtr, char *text, int length);
static void      AppendChunk(Ns_DString *dsPtr, char *text);
static void      EndChunk(Ns_DString *dsPtr);
static void      AddTextChunk(Ns_DString *dsPtr, char *text, int length);
static void      AppendTclEscaped(Ns_DString *ds, char *in);
static void      NAppendTclEscaped(Ns_DString *ds, char *in, int n);
#ifdef NOTDEF
static void      FancyParsePage(Ns_DString *outPtr, char *in);
#endif
static Ns_Set   *TagToSet(char *sTag);
static char     *ReadToken(char *in, Ns_DString *tagPtr);
static char     *BalancedEndTag(char *in, RegTag *rtPtr);
static RegTag   *GetRegTag(char *tag);

/*
 * Static variables
 */

static Tcl_HashTable htTags;
#ifdef NOTDEF
static Ns_RWLock     tlock;
#endif


/* 
 *==========================================================================
 * Exported functions
 *==========================================================================
 */

/*
 *----------------------------------------------------------------------
 * NsAdpFancyInit --
 *
 *      Initialization function.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      None.
 *
 *----------------------------------------------------------------------
 */

void
NsAdpFancyInit(char *hServer, char *path)
{
    Tcl_InitHashTable(&htTags, TCL_STRING_KEYS);

#ifdef NOTDEF    
    /*
     * Register ourselves.
     */
    
    Ns_AdpRegisterParser("fancy", FancyParsePage);
#endif    
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclRegisterTagCmd --
 *
 *	Register an ADP tag.
 *	
 *
 * Results:
 *	Std tcl retval.
 *
 * Side effects:
 *	An ADP tag may be added to the hashtable.
 *
 *----------------------------------------------------------------------
 */

int
NsTclRegisterTagCmd(ClientData ignored, Tcl_Interp *interp, int argc,
		    char **argv)
{
    char           *tag, *endtag, *proc;
    Tcl_HashEntry  *he;
    int             new;
    RegTag         *rtPtr;
#ifdef NOTDEF    
    AdpData        *adPtr;
#endif    
    
    if (argc != 4 && argc != 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
			 argv[0], " tag ?endtag? proc\"", NULL);
	return TCL_ERROR;
    }

    if (argc == 3) {
	tag = argv[1];
	endtag = NULL;
	proc = argv[2];
    } else {
	tag = argv[1];
	endtag = argv[2];
	proc = argv[3];

    }
#ifdef NOTDEF    
    adPtr = NsAdpGetData();
    if (nsconf.adp.taglocks) {
	/* LOCK */
	if (adPtr->depth > 0) {
	    /*
	     * Called from within an ADP, so unlock the read lock first
	     * to prevent deadlock
	     */
	    Ns_RWLockUnlock(&tlock);
	}
	Ns_RWLockWrLock(&tlock);
    }
#endif    

    he = Tcl_CreateHashEntry(&htTags, tag, &new);
    if (new == 0) {
	Tcl_AppendResult(interp, "ADP tag \"", tag, "\" already registered.",
			 NULL);
	return TCL_ERROR;
    }
    rtPtr = ns_malloc(sizeof(RegTag));
    rtPtr->tag = ns_strdup(tag);
    rtPtr->endtag = endtag ? ns_strdup(endtag) : NULL;
    rtPtr->procname = ns_strdup(proc);
    rtPtr->adpstring = NULL;
    Tcl_SetHashValue(he, (void *) rtPtr);

#ifdef NOTDEF    
    if (nsconf.adp.taglocks) {
	/* UNLOCK */
	Ns_RWLockUnlock(&tlock);
	if (adPtr->depth > 0) {
	    /*
	     * We were called from within an ADP, so re-lock the read
	     * lock now.
	     */
	    Ns_RWLockRdLock(&tlock);
	}
    }
#endif    

    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclRegisterAdpCmd --
 *
 *	Register an ADP tag which is an ADP.
 *	
 *
 * Results:
 *	Std tcl retval.
 *
 * Side effects:
 *	An ADP tag may be added to the hashtable.
 *
 *----------------------------------------------------------------------
 */

int
NsTclRegisterAdpCmd(ClientData ignored, Tcl_Interp *interp, int argc,
		    char **argv)
{
    char           *tag, *endtag, *adpstring;
    Tcl_HashEntry  *he;
    int             new;
    RegTag         *rtPtr;
#ifdef NOTDEF    
    AdpData        *adPtr;
#endif    
    
    if (argc != 4 && argc != 3) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
			 argv[0], " tag ?endtag? adpstring\"", NULL);
	return TCL_ERROR;
    }

    if (argc == 3) {
	tag = argv[1];
	endtag = NULL;
	adpstring = argv[2];
    } else {
	tag = argv[1];
	endtag = argv[2];
	adpstring = argv[3];

    }
#ifdef NOTDEF    
    adPtr = NsAdpGetData();
    if (nsconf.adp.taglocks) {
	/* LOCK */
	if (adPtr->depth > 0) {
	    /*
	     * Called from within an ADP, so unlock the read lock first
	     * to prevent deadlock
	     */
	    Ns_RWLockUnlock(&tlock);
	}
	Ns_RWLockWrLock(&tlock);
    }
#endif    

    he = Tcl_CreateHashEntry(&htTags, tag, &new);
    if (new == 0) {
	Tcl_AppendResult(interp, "ADP tag \"", tag, "\" already registered.",
			 NULL);
	return TCL_ERROR;
    }
    rtPtr = ns_malloc(sizeof(RegTag));
    rtPtr->tag = ns_strdup(tag);
    rtPtr->endtag = endtag ? ns_strdup(endtag) : NULL;
    rtPtr->procname = NULL;
    rtPtr->adpstring = ns_strdup(adpstring);
    Tcl_SetHashValue(he, (void *) rtPtr);

#ifdef NOTDEF    
    if (nsconf.adp.taglocks) {
	/* UNLOCK */
	Ns_RWLockUnlock(&tlock);
	if (adPtr->depth > 0) {
	    /*
	     * We were called from within an ADP, so re-lock the read
	     * lock now.
	     */
	    Ns_RWLockRdLock(&tlock);
	}
    }
#endif    

    return TCL_OK;
}


/*
 *==========================================================================
 * Static functions
 *==========================================================================
 */
 

/*
 *----------------------------------------------------------------------
 *
 * StartScript --
 *
 *	Start a chunk which is a script.
 *
 * Results:
 *	The passed-in dstring has a chunk header appended to it.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
StartScript(Ns_DString *dsPtr)
{
    Ns_DStringNAppend(dsPtr, "s", 1);
}


/*
 *----------------------------------------------------------------------
 *
 * StartText --
 *
 *	Start a chunk which is text.
 *
 * Results:
 *	The passed-in dstring has a chunk header appended to it.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
StartText(Ns_DString *dsPtr)
{
    Ns_DStringNAppend(dsPtr, "t", 1);
}


/*
 *----------------------------------------------------------------------
 *
 * NAppendChunk --
 *
 *	Append a string of length N to the chunk.
 *
 * Results:
 *	The passed-in dstring has a chunk appended to it.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
NAppendChunk(Ns_DString *dsPtr, char *text, int length)
{
    Ns_DStringNAppend(dsPtr, text, length);
}


/*
 *----------------------------------------------------------------------
 *
 * AppendChunk --
 *
 *	Append a string to the chunk.
 *
 * Results:
 *	The passed-in dstring has a chunk appended to it.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AppendChunk(Ns_DString *dsPtr, char *text)
{
    Ns_DStringAppend(dsPtr, text);
}


/*
 *----------------------------------------------------------------------
 *
 * EndChunk --
 *
 *	End a chunk
 *
 * Results:
 *	The passed-in dstring has a null appended to it.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
EndChunk(Ns_DString *dsPtr)
{
    Ns_DStringNAppend(dsPtr, "", 1);
}


/*
 *----------------------------------------------------------------------
 *
 * AddTextChunk --
 *
 *	Given a string of HTML, convert it into a text chunk suitable
 *      for caching.
 *
 * Results:
 *	The passed-in dstring has a chunk appended to it.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AddTextChunk(Ns_DString *dsPtr, char *text, int length)
{
    StartText(dsPtr);
    NAppendChunk(dsPtr, text, length);
    EndChunk(dsPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * AppendTclEscaped --
 *
 *	Append a string to a dstring after escaping it for Tcl so that
 *      it is suitable for putting inside "...". The translations are:
 *	" -> \"
 *      $ -> \$
 *      \ -> \\
 *      [ -> \[
 *
 * Results:
 *	The dstring will have stuff appended to it that is tcl-safe.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
AppendTclEscaped(Ns_DString *ds, char *in)
{
    while (*in) {
	if (*in == '"'  ||
	    *in == '$'  ||
	    *in == '\\' ||
	    *in == '[') {
	    Ns_DStringAppend(ds, "\\");
	}
	Ns_DStringNAppend(ds, in, 1);
	in++;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * NAppendTclEscaped --
 *
 *      Same as AppendTclEscaped, but only do up to N bytes.
 *
 * Results:
 *	See AppendTclEscaped.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
NAppendTclEscaped(Ns_DString *ds, char *in, int n)
{
    while (*in && (n-- > 0)) {
	if (*in == '"'  ||
	    *in == '$'  ||
	    *in == '\\' ||
	    *in == '[') {

	    Ns_DStringAppend(ds, "\\");
	}
	Ns_DStringNAppend(ds, in, 1);
	in++;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * FancyParsePage --
 *
 *	Takes an ADP as input and forms a chunky ADP as output.
 *
 * Results:
 *	A chunked ADP is put in outPtr.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

#ifdef NOTDEF
static void
#else
void
#endif
FancyParsePage(Ns_DString *outPtr, char *in)
{
    char       *top, *oldtop, *end;
    Ns_DString  tag;
    Ns_Set     *params;
    RegTag     *rtPtr;
    
    /*
     * Tags we care about:
     * <%
     * <script
     * <regtag
     */

    Ns_DStringInit(&tag);
    oldtop = top = in;
    
#ifdef NOTDEF    
    if (nsconf.adp.taglocks) {
	Ns_RWLockRdLock(&tlock);
    }
#endif    
    while ((top = ReadToken(top, &tag)) != NULL) {
	/*
	 * top points at the character after the tag.
	 * tag is a dstring containg either one tag or a bunch of text.
	 * oldtop points at where tag starts in the original string.
	 *
	 * <script language=tcl runat=server> ns_puts hi
	 * ^                                 ^
	 * oldtop                            top
	 *
	 * tag="<script language=tcl runat=server>"
	 */
	
	if (strncmp(tag.string, "<%", 2) == 0) {
	    /*
	     * Find %> tag and add script if there, otherwise spit out
	     * a warning and add as text.
	     */
	    end = strstr(top, "%>");
	    if (end == NULL) {
		Ns_ModLog(Warning, nsAdpModLogHandle, "unterminated script");
		AddTextChunk(outPtr, oldtop, strlen(oldtop));
		break;
	    } else {
		StartScript(outPtr);
		if (tag.string[2] == '=') {
		    NAppendChunk(outPtr, "ns_puts -nonewline ",
 				  sizeof("ns_puts -nonewline ")-1);
		}
		NAppendChunk(outPtr, top, end-top);
		EndChunk(outPtr);
		top = end + 2;
	    }
	} else if (strncasecmp(tag.string, "<script", 7) == 0) {
	    char *lang, *runat, *stream;
	    /*
	     * Get the paramters to the tag and then add the
	     * script chunk if appropriate, otherwise it's just
	     * text
	     */
	    
	    params = TagToSet(tag.string);
	    lang = Ns_SetIGet(params, "language");
	    stream = Ns_SetIGet(params, "stream");
	    runat = Ns_SetIGet(params, "runat");
	    if (runat != NULL &&
		strcasecmp(runat, "server") == 0 &&
		(lang == NULL || strcasecmp(lang, "tcl") == 0)) {

		/*
		 * This is a server-side script chunk!
		 * If there is an end tag, add it as a script, else
		 * spit out a warning and add as text.
		 */
		
		end = Ns_StrNStr(top, "</script>");
		if (end == NULL) {
		    Ns_ModLog(Warning, nsAdpModLogHandle,
			      "unterminated script");
		    AddTextChunk(outPtr, oldtop, strlen(oldtop));
                    Ns_SetFree(params);
		    break;
		} else {
		    StartScript(outPtr);
		    if (stream != NULL && strcasecmp(stream, "on") == 0) {
			AppendChunk(outPtr, "ns_adp_stream\n");
		    }
		    NAppendChunk(outPtr, top, end-top);
		    EndChunk(outPtr);
		    top = end + 9;
		}
	    } else {
		/*
		 * Not a server-side script, so add as text.
		 */
		
		AddTextChunk(outPtr, tag.string, tag.length);
	    }
            Ns_SetFree(params);
	} else if ((rtPtr = GetRegTag(tag.string + 1)) != NULL) {

	    /*
	     * It is a registered tag. In this case, we generate
	     * a bolus of tcl code that will call it.
	     */

	    int         i;
	    char       *end = NULL;

	    params = TagToSet(tag.string);

	    /*
	     * If it requires an endtag then ensure that there
	     * is one. If not, warn and spew text.
	     */
	    
	    if (rtPtr->endtag &&
		((end = BalancedEndTag(top, rtPtr)) == NULL)) {

		Ns_ModLog(Warning, nsAdpModLogHandle,
			  "unterminated registered tag %s", rtPtr->tag);
		AddTextChunk(outPtr, oldtop, strlen(oldtop));
                Ns_SetFree(params);
		break;
	    }

	    /*
	     * Write Tcl code to put all the parameters into a set, then
	     * call the proc with that set (and the input, if any).
	     */
	    
	    StartScript(outPtr);
	    AppendChunk(outPtr, "set _ns_tempset [ns_set create \"\"]\n");
	    for (i=0; i < Ns_SetSize(params); i++) {
		AppendChunk(outPtr, "ns_set put $_ns_tempset \"");
		AppendTclEscaped(outPtr, Ns_SetKey(params, i));
		AppendChunk(outPtr, "\" \"");
		AppendTclEscaped(outPtr, Ns_SetValue(params, i));
	        AppendChunk(outPtr, "\"\n");
	    }
	    AppendChunk(outPtr, "ns_puts -nonewline [");
	    if (rtPtr->procname) {
		/*
		 * This uses the old-style registered procedure
		 */
		AppendChunk(outPtr, rtPtr->procname);
	    } else {
		/*
		 * This uses the new and improved registered ADP.
		 */
		AppendChunk(outPtr, "ns_adp_eval \"");
		AppendTclEscaped(outPtr, rtPtr->adpstring);
		AppendChunk(outPtr, "\" ");
	    }
	    AppendChunk(outPtr, " ");

	    /*
	     * Backwards compatibility is broken here because a conn is
	     * never passed
	     */
	    if (end != NULL) {
		/*
		 * This takes an endtag, so pass it content (the text between
		 * the start and end tags).
		 */
		AppendChunk(outPtr, "\"");
		NAppendTclEscaped(outPtr, top, end-top-1);
		AppendChunk(outPtr, "\" ");
	    }
	    AppendChunk(outPtr, "$_ns_tempset]\n");
	    EndChunk(outPtr);

	    /*
	     * Advance top past the end of the close tag
	     * (if there is no closetag, top should already be
	     * properly advanced thanks get ReadToken)
	     */
	    if (end != NULL) {
		while (*end != '\0' && *end != '>') {
		    end++;
		}
		if (*end == '>') {
		    end++;
		}
		top = end;
	    }
            Ns_SetFree(params);
	} else {
	    /*
	     * It's just a chunk of text.
	     */

	    AddTextChunk(outPtr, tag.string, tag.length);
	}
	Ns_DStringTrunc(&tag, 0);

	oldtop = top;
    }
#ifdef NOTDEF    
    if (nsconf.adp.taglocks) {
	Ns_RWLockUnlock(&tlock);
    }
#endif    
    Ns_DStringFree(&tag);
}


/*
 *----------------------------------------------------------------------
 *
 * TagToSet --
 *
 *	Given a tag such as <script runat=server language=tcl> make
 *	an ns_set containing runat=server, language=tcl.
 *
 * Results:
 *	The above described set.
 *
 * Side effects:
 *	An Ns_Set is allocated.
 *
 *      NOTE: ALWAYS DO Ns_SetFree() THE RESULT OF THIS FUNCTION!
 *
 *----------------------------------------------------------------------
 */

static Ns_Set *
TagToSet(char *sTag)
{
    char *p, c;
    Ns_Set *set;
    char *p2;

    /* Skip the opening '>' */
    p = ++sTag;
    /* Get the tag name */
    while (isspace(UCHAR(*sTag)) == 0 &&
	*sTag != '\0' &&
	*sTag != '>') {

	sTag++;
    }
    c = *sTag;
    *sTag = '\0';
    set = Ns_SetCreate(p);
    *sTag = c;
    /* Handle the attribute = value pairs */
    do {
	/* Skip blanks */
	while (*sTag != '\0' && isspace(UCHAR(*sTag))) {
	    sTag++;
	}
	if (*sTag == '>') {
	    break;
	}
	/* Get attr name */
	p = sTag;
	while (*sTag != '\0' &&
	    isspace(UCHAR(*sTag)) == 0 &&
	    *sTag != '=' && *sTag != '>') {

	    sTag++;
	}
	c = *sTag;
	*sTag = '\0';
#ifdef NOTDEF	
	Ns_SetPut(set, p, NULL);
#else
	/* Waste space, but... */
	p2 = ap_pstrdup (TCL_POOL(), p);
#endif	
	*sTag = c;
	/* Skip blanks */
	while (*sTag != '\0' && isspace(UCHAR(*sTag))) {
	    sTag++;
	}
	if (*sTag == '=') {
	    /* get attr value */
	    sTag++;
	    /* Skip blanks */
	    while (*sTag != '\0' && isspace(UCHAR(*sTag))) {
		sTag++;
	    }
	    if (*sTag == '"') {
		/* get attr value in double quotes */
		sTag++;
		p = sTag;
		while (*sTag != '\0' && *sTag != '"') {
		    sTag++;
		}
		c = *sTag;
		*sTag = '\0';
#ifdef NOTDEF	
		set->fields[set->size -1].value = ns_strcopy(p);
#else		
		Ns_SetPut (set, p2, p);
#endif	
		*sTag = c;
		if (*sTag == '"') {
		    sTag++;
		}
	    } else {
		/* get attr value bounded by whitespace or end of tag */
		p = sTag;
		while (*sTag != '\0' &&
		    isspace(UCHAR(*sTag)) == 0 &&
		    *sTag != '>') {

		    sTag++;
		}
		c = *sTag;
		*sTag = '\0';
#ifdef NOTDEF	
		set->fields[set->size -1].value = ns_strcopy(p);
#else		
		Ns_SetPut (set, p2, p);
#endif	
		*sTag = c;
	    }
      } else {
#ifdef NOTDEF	  
          set->fields[set->size-1].value = ns_strcopy(set->fields[set->size-1].name);
#else
	  Ns_SetPut (set, p2, p2);
#endif	  
	}
    } while (*sTag != '\0' && *sTag != '>');

    return set;
}


/*
 *----------------------------------------------------------------------
 *
 * ReadToken --
 *
 *	Read the passed-in dstring until a token is found and return it
 *	in a dstring.
 *
 * Results:
 *	NULL on failure (including EOF), or a pointer to the next char
 *      in the input stream.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char *
ReadToken(char *in, Ns_DString *tagPtr)
{
    char c;
    int quoting = 0;

    if ((c = *in++) == '\0') {
	return NULL;
    }

    Ns_DStringNAppend(tagPtr, &c, 1);
    /* Starting to read a tag */
    if (c == '<') {
	if ((c = *in++) == '\0') {
	    in--;
	    goto done;
	}

	Ns_DStringNAppend(tagPtr, &c, 1);
	if (c == '%') {
	    if ((c = *in++) == '\0') {
		in--;
		goto done;
	    }
	    if (c == '=') {
		Ns_DStringNAppend(tagPtr, &c, 1);
		goto done;
	    } else {
		in--;
		goto done;
	    }
	} else {
	    if (c == '!') {
		if ((c = *in++) == '\0') {
		    in--;
		    goto done;
		}
		Ns_DStringNAppend(tagPtr, &c, 1);
		if (c == '-') {
		    if ((c = *in++) == '\0') {
			goto done;
		    }
		    Ns_DStringNAppend(tagPtr, &c, 1);
		    if (c == '-') {
			goto done;
		    }
		}
	    }
	    while (c != '>' && (c = *in++) != '\0') {
	      if (c == '<') {
		in--;
		goto done;
	      }
	      Ns_DStringNAppend(tagPtr, &c, 1);
	    }
	    if (c == '\0') {
		in--;
	    }
	    goto done;
	}
    } else if (c == '%') {
	if ((c = *in++) == '\0') {
	    in--;
	    goto done;
	}
	Ns_DStringNAppend(tagPtr, &c, 1);
	if (c == '>') {
	    goto done;
	}
	while ((c = *in++) != '\0') {
	    if ((c == '<') || (c == '%')) {
		in--;
		break;
	    }
	    Ns_DStringNAppend(tagPtr, &c, 1);
	}
	if (c == '\0') {
	    in--;
	}
    } else {
	while ((c = *in++) != '\0') {
	    if (c == '<' || c == '%') {
		in--;
		break;
	    }
	    Ns_DStringNAppend(tagPtr, &c, 1);
	}
	if (c == '\0') {
	    in--;
	}
    }

 done:
    return in;
}


/*
 *----------------------------------------------------------------------
 *
 * GetRegTag --
 *
 *	Looks up in The Hash Table the passed-in regtag and returns the
 *	regtag struct where available. The passed in tag should look like:
 *      "tag a=b c=d>" or "tag>"
 *
 * Results:
 *	Either a pointer to the requested regtag or null if none exists.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static RegTag *
GetRegTag(char *tag)
{
    RegTag        *rtPtr;
    char          *end, temp;
    Tcl_HashEntry *he;
    
    rtPtr = NULL;
    end = tag;
    /*
     * Locate the end of the tag
     */
    while (*end != '\0' && *end != '>' &&
	isspace(UCHAR(*end)) == 0) {

	end++;
    }
    if (*end == '\0') {
	goto done;
    }
    temp = *end;
    *end = '\0';
    he = Tcl_FindHashEntry(&htTags, tag);
    *end = temp;
    if (he == NULL) {
	goto done;
    }
    rtPtr = Tcl_GetHashValue(he);
    
 done:
    return rtPtr;
}

static char * 
BalancedEndTag(char *in, RegTag *rtPtr)
{

  int tag_depth = 1;
  int taglen, endlen;

  taglen = strlen(rtPtr->tag);
  endlen = strlen(rtPtr->endtag);

  while (tag_depth) {

    /*  
     * Scan ahead for a '<'
     */

    for ( ; *in && *in != '<'; ++in ) ;
    if (*in == '\0') return NULL;

    /* skip '<' */
    ++in;

    /*
     * The current parser seems to allow white space between < and the tag
     */

    for ( ; *in && isspace(UCHAR(*in)); ++in ) ;
    if (*in == '\0') return NULL;

    /*  
     * If the next word matches the close tag, then decrement tag_depth
     */

    if (! strncasecmp(in, rtPtr->endtag, endlen)) {
      tag_depth--;
    }

    /*  
     * else if the next word matches the open tag, then increment tag_depth
     */

    else if (! strncasecmp(in, rtPtr->tag, taglen)) {
      tag_depth++;
    }
  }

  return in;
}