/*		Character grid hypertext object
**		===============================
*/

#include "HTUtils.h"
#include "tcp.h"

#include "LYCurses.h" /* lynx defined curses */

#include <assert.h>
#include <ctype.h>
#include "HTString.h"
#include "GridText.h"
#include "HTFont.h"
#include "HTAccess.h"
#include "HTParse.h"
#include "HTTP.h"
#include "HTAlert.h"
#include "HTJIS.h"

/* lynx specific defines */
#include "LYUtils.h"
#include "LYStrings.h"
#include "LYStructs.h"
#include "LYGlobalDefs.h"
#include "LYGetFile.h"
#include "LYSignal.h"
#include "LYMail.h"
#include "LYList.h"
#include "LYCharSets.h"

#include "LYexit.h"
#include "LYLeaks.h"
#ifndef VMS
#ifdef SYSLOG_REQUESTED_URLS
#include <syslog.h>
#endif /* SYSLOG_REQUESTED_URLS */
#endif /* !VMS */

struct _HTStream {                      /* only know it as object */
    CONST HTStreamClass *       isa;
    /* ... */
};

#define TITLE_LINES  1

#define FREE(x) if (x) {free(x); x = NULL;}

extern enum _kcode {EUC, SJIS, JIS, NONE} kanji_code;
extern BOOL HTJapanese;

/*	From default style sheet:
*/
extern HTStyleSheet * styleSheet;	/* Default or overridden */

extern int display_lines; /* number of lines in display */
extern BOOLEAN ignore_excess; /* flag to ignore chararcters at wrap column */
extern char HTML_Last_Char;

/*	Exports
*/ 
PUBLIC HText * HTMainText = 0;		/* Equivalent of main window */
PUBLIC HTParentAnchor * HTMainAnchor = 0;	/* Anchor for HTMainText */

PUBLIC char * HTAppName = "Lynx";      /* Application name */
PUBLIC char * HTAppVersion = LYNX_VERSION;        /* Application version */

PUBLIC int HTFormNumber = 0;
PUBLIC int HTFormFields = 0;
PUBLIC char * HTCurSelectGroup=NULL;  /* form select group name */
PUBLIC int HTCurSelectGroupType = F_RADIO_TYPE; /* group type */
PUBLIC char * HTCurSelectGroupSize = NULL; /* length of select */

PUBLIC char * checked_box = "[X]";
PUBLIC char * unchecked_box = "[ ]";
PUBLIC char * checked_radio = "(*)";
PUBLIC char * unchecked_radio = "( )";

PUBLIC BOOLEAN underline_on = OFF;
PUBLIC BOOLEAN bold_on      = OFF;

typedef struct _line {
	struct _line	*next;
	struct _line	*prev;
	int unsigned	offset;		/* Implicit initial spaces */
	int unsigned	size;		/* Number of characters */
	BOOL	split_after;		/* Can we split after? */
	BOOL	bullet;			/* Do we bullet? */
	char	data[1];		/* Space for terminator at least! */
} HTLine;

#define LINE_SIZE(l) (sizeof(HTLine)+(l))	/* allow for terminator */

typedef struct _TextAnchor {
	struct _TextAnchor *	next;
	int			number;		/* For user interface */
	int			start;		/* Characters */
        int			line_pos;       /* position in text */
	int			extent;		/* Characters */
	int			line_num;       /* place in document */
	char *		        hightext;       /* the link text */
	char *		        hightext2;       /* a second line*/
        int 		    	hightext2offset; /* offset from left */
	int			link_type;	/* normal or form ? */
	FormInfo *		input_field;	/* info for form links */
	BOOL			show_anchor;    /* show the anchor? */
	HTChildAnchor *		anchor;
} TextAnchor;

typedef struct _HTTabID {
	char *			name;		/* ID value of TAB */
	int			column;		/* Zero-based column value */
} HTTabID;


/*	Notes on struct _Htext:
**	next_line is valid if state is false.
**	top_of_screen line means the line at the top of the screen
**			or just under the title if there is one.
*/
struct _HText {
	HTParentAnchor *	node_anchor;
	HTLine * 		last_line;
	int			lines;		/* Number of them */
	int			chars;		/* Number of them */
	TextAnchor *		first_anchor;	/* Singly linked list */
	TextAnchor *		last_anchor;
	int			last_anchor_number;	/* user number */
	BOOL			source;		/* is the text source? */
	BOOL			toolbar;	/* Toolbar set? */
	HTList *		tabs;		/* TAB IDs */
	BOOL			no_cache;	/* Always refresh? */
/* For Internal use: */	
	HTStyle *		style;			/* Current style */
	int			display_on_the_fly;	/* Lines left */
	int			top_of_screen;		/* Line number */
	HTLine *		top_of_screen_line;	/* Top */
	HTLine *		next_line;		/* Bottom + 1 */
	int			permissible_split;	/* in last line */
	BOOL			in_line_1;		/* of paragraph */
	BOOL			stale;			/* Must refresh */

        HTStream*               target;                 /* Output stream */
        HTStreamClass           targetClass;            /* Output routines */
};

/*	Boring static variable used for moving cursor across
*/

#define UNDERSCORES(n) (&underscore_string[(MAX_LINE-1) - (n)])

/*
 *	Memory leak fixed.
 *	05-29-94 Lynx 2-3-1 Garrett Arch Blythe
 *	Changed to arrays.
 */
PRIVATE char underscore_string[MAX_LINE + 1];
PUBLIC char star_string[MAX_LINE + 1];

PRIVATE int ctrl_chars_on_this_line=0;  /* num of ctrl chars in current line */

PRIVATE HTStyle default_style =
	{ 0,  "(Unstyled)", "",
	(HTFont)0, 1.0, HT_BLACK,		0, 0,
	0, 0, 0, HT_LEFT,		1, 0,	0, 
	NO, NO, 0, 0,			0 };	



PRIVATE HTList * loaded_texts = NULL;	 /* A list of all those in memory */
PUBLIC  HTList * search_queries = NULL;  /* isindex and whereis queries   */
PRIVATE void free_all_texts NOARGS;
PRIVATE int HText_TrueLineSize PARAMS((HTLine *line));

/*			Creation Method
**			---------------
*/
PUBLIC HText *	HText_new ARGS1(HTParentAnchor *,anchor)
{
#if defined(VMS) && defined(VAXC) && !defined(__DECC)
#include <lib$routines.h>
    int status, VMType=3, VMTotal;
#endif /* VMS && VAXC && !__DECC */
    HTLine * line = NULL;
    HText * self = (HText *) calloc(sizeof(*self),1);
    if (!self) return self;
    
#if defined(VMS) && defined (VAXC) && !defined(__DECC)
    status = lib$stat_vm(&VMType, &VMTotal);
    if (TRACE)
        fprintf(stderr, "GritText: VMTotal = %d\n", VMTotal);
#endif /* VMS && VAXC && !__DECC */

    if (!loaded_texts)	{
	loaded_texts = HTList_new();
/*
 *  All allocated memory is freed when an image exits on VMS,
 *  and more efficiently than via C RTL free's, so on VMS
 *  only use this when debugging with LY_FIND_LEAKS defined.
 */
#if !defined(VMS) || defined(LY_FIND_LEAKS)
	atexit(free_all_texts);
#endif /* !VMS || LY_FIND_LEAKS */
    }

    /*
     * Links between anchors & documents are a 1-1 relationship. If
     * an anchor is already linked to a document we didn't call
     * HTuncache_current_document(), e.g., for the showinfo, options,
     * dowload, print, etc., temporary file URLs, so we'll check now
     * and free it before reloading. - Dick Wesseling (ftu@fi.ruu.nl)
     */
    if (anchor->document) {
       HTList_removeObject(loaded_texts, anchor->document);
       if (TRACE)
           fprintf(stderr, "GridText: Auto-uncaching\n") ;
       ((HText *)anchor->document)->node_anchor=0;
       HText_free((HText *)anchor->document);
       anchor->document = 0;
    }

    HTList_addObject(loaded_texts, self);
#if defined(VMS) && defined(VAXC) && !defined(__DECC)
    while (HTList_count(loaded_texts) > HTCacheSize &&
    	   VMTotal > HTVirtualMemorySize) {
#else
    if (HTList_count(loaded_texts) > HTCacheSize) {
#endif /* VMS && VAXC && !__DECC */
        if (TRACE)
	    fprintf(stderr, "GridText: Freeing off cached doc.\n"); 
        HText_free((HText *)HTList_removeFirstObject(loaded_texts));
#if defined(VMS) && defined (VAXC) && !defined(__DECC)
        status = lib$stat_vm(&VMType, &VMTotal);
        if (TRACE)
            fprintf(stderr, "GridText: VMTotal reduced to %d\n", VMTotal);
#endif /* VMS && VAXC && !__DECC */
    }
    
    line = self->last_line = (HTLine *)calloc(sizeof(char),LINE_SIZE(MAX_LINE));
    if (line == NULL)
        outofmem(__FILE__, "HText_New");
    line->next = line->prev = line;
    line->offset = line->size = 0;
    self->lines = self->chars = 0;
    self->first_anchor = self->last_anchor = 0;
    self->style = &default_style;
    self->top_of_screen = 0;
    self->node_anchor = anchor;
    self->last_anchor_number = 0;	/* Numbering of them for references */
    self->stale = YES;
    self->toolbar = NO;
    self->tabs = 0;
    self->no_cache = NO;
    if (HTOutputFormat == WWW_SOURCE)
        self->source = YES;
    else
        self->source = NO;
    HTAnchor_setDocument(anchor, (HyperDoc *)self);
    HTFormNumber = 0;  /* no forms started yet */
    HTMainText = self;
    HTMainAnchor = anchor;
    self->display_on_the_fly = 0;
   
    /*
     *	Memory leak fixed.
     *  05-29-94 Lynx 2-3-1 Garrett Arch Blythe
     *	Check to see if our underline and star_string need initialization
     *		if the underline is not filled with dots.
     */ 
    if (underscore_string[0] != '.') { /* Make a line */
        char *p;
        for (p=underscore_string; p<underscore_string+(MAX_LINE-1); p++)
            *p = '.';           /* Used for printfs later */
        underscore_string[(MAX_LINE-1)] = '\0';
        for (p=star_string; p<star_string+(LINESIZE-1); p++)
            *p = '_';           /* Used for printfs later */
        star_string[(LINESIZE-1)] = '\0';
    }

    underline_on = FALSE; /* reset */
    bold_on = FALSE;
    
    return self;
}

/*                      Creation Method 2
**                      ---------------
**
**      Stream is assumed open and left open.
*/
PUBLIC HText *  HText_new2 ARGS2(
                HTParentAnchor *,       anchor,
                HTStream*,              stream)

{
    HText * this = HText_new(anchor);

    if (stream) {
        this->target = stream;
        this->targetClass = *stream->isa;       /* copy action procedures */
    }
    return this;
}

/*	Free Entire Text
**	----------------
*/
PUBLIC void 	HText_free ARGS1(HText *,self)
{
    if (!self)
        return;

    HTAnchor_setDocument(self->node_anchor, (HyperDoc *)0);
    
    while (YES) {	/* Free off line array */
        HTLine * l = self->last_line;
	if (l) {
	    l->next->prev = l->prev;
	    l->prev->next = l->next;	/* Unlink l */
	    self->last_line = l->prev;
	    if (l != self->last_line) {
	        FREE(l);
	    } else {
	        free(l);
	    }
	}
	if (l == self->last_line) {	/* empty */
	    l = NULL;
	    break;
	}
    };
    
    while (self->first_anchor) {		/* Free off anchor array */
        TextAnchor * l = self->first_anchor;
	self->first_anchor = l->next;

	    /* free form fields */
	if (l->link_type == INPUT_ANCHOR && l->input_field) {

		/* free off option lists */
	    if (l->input_field->type == F_OPTION_LIST_TYPE) {
		OptionType *optptr=l->input_field->select_list;
		OptionType *tmp;
		while (optptr) {
		    tmp = optptr;
		    FREE(tmp->name);
		    optptr = optptr->next;
		    FREE(tmp);
		}
	    }
	    else 
	      {
		/* don't free the value field on option
		 * lists since it points to a option value
		 * same for orig value
		 */
                FREE(l->input_field->value);
                FREE(l->input_field->orig_value);
                FREE(l->input_field->cp_submit_value);
                FREE(l->input_field->orig_submit_value);
	      }
		
            FREE(l->input_field->submit_action);
            FREE(l->input_field->title);
		
	    FREE(l->input_field);
	}

	FREE(l->hightext);
	FREE(l->hightext2);

	FREE(l);
    }

    /*
     * Free the tabs list. - FM
     */
    if (self->tabs) {
        HTTabID * tab = NULL;
	HTList * cur = self->tabs;

	while (NULL != (tab = (HTTabID *)HTList_nextObject(cur))) {
	    FREE(tab->name);
	}
	HTList_delete(self->tabs);
    }

    /*
     *	Memory leak fixed.
     *	05-27-94 Lynx 2-3-1 Garrett Arch Blythe
     *
     *  Causes ACCVIOs/CoreDumps due to problems with the hash
     *  table when anchors are deleted, so we are living with
     *  the leaks, for now. - FM
     *
     *  I looks like the problems that were causing ACCVIOs/CoreDumps are
     *  solved, so now we will delete them, but leave it easy to compile
     *  out. - FM
     */
/* #define LIVE_WITH_LEAKS */
#ifndef LIVE_WITH_LEAKS
    if (self->node_anchor)
        HTAnchor_delete(self->node_anchor);
#endif /* !LIVE_WITH_LEAKS */

    FREE(self);
}

/*		Display Methods
**		---------------
*/


/*	Output a line
**	-------------
*/
PRIVATE int display_line ARGS1(HTLine *,line)
{
    register int i,j;
    char buffer[3];
    char *data;

    buffer[0] = buffer[1] = buffer[2] = '\0';
    clrtoeol();
    /* make sure that we don't go over the COLS limit on the display! */

    /* add offset */
    j = (int)line->offset;
    if (j > (int)LYcols - 1)
        j = (int)LYcols - 1;
#ifdef USE_SLANG
    SLsmg_forward (j);
    i = j;
#else
    for (i = 0; i < j; i++)
        addch (' ');
#endif /* USE_SLANG */

    /* add data */
    data = line->data;
    i++;
    while ((i < LYcols) && ((buffer[0] = *data) != '\0')) {
	data++;
	i++;

   	switch (buffer[0]) {

	    case LY_UNDERLINE_START_CHAR:
	        if (!(dump_output_immediately && use_underscore)) {
		    start_underline();
		    i--;
		} else {
		    addch('_');
		}
		break;

	    case LY_UNDERLINE_END_CHAR:
	        if (!(dump_output_immediately && use_underscore)) {
		    stop_underline();
		    i--;
		} else {
		    addch('_');
		}
		break;

	    case LY_BOLD_START_CHAR:
	        start_bold();
		i--;
		break;

	    case LY_BOLD_END_CHAR:
	        stop_bold ();
		i--;
		break;

	    default:
		/* For Japanese strings, by Masanobu Kimura */
		if (HTJapanese && !isascii(buffer[0])) { 
		    buffer[1] = *data;
		    data++;
		    i++;
		    addstr(buffer);
		    buffer[1] = '\0';
		} else {
		    addstr(buffer);
		}
	} /* end of switch */
    } /* end of while */

    /* add the return */
    addch('\n');

    stop_underline();
    stop_bold();
    return(0);
}

/*	Output the title line
**	---------------------
*/
PRIVATE void display_title ARGS1(HText *,text)
{
    CONST char * title = HTAnchor_title(text->node_anchor);
    char percent[20], format[20];
    char *cp=NULL;
    unsigned char *tmp=NULL;
    int i=0, j=0;

    /* there shouldn't be any \n in the title, but if there is
     * lets kill it now!
     */
    if (title && (cp = strchr(title,'\n')) != NULL)
	*cp = '\0';

    if ((text->lines + 1) > (display_lines)) {
	/*	In a small attempt to correct the number of pages counted....
	 *	GAB 07-14-94
	 *
	 *	In a bigger attempt (hope it holds up 8-)....
	 *	FM 02-08-95
	 */
	int total_pages =
	 	(((text->lines + 1) + (display_lines - 1))/(display_lines));
	int start_of_last_page =
		((text->lines + 1) < display_lines) ? 0 :
		((text->lines + 1) - display_lines);	

	sprintf(percent, " (p%d of %d)",
		((text->top_of_screen >= start_of_last_page) ?
		    total_pages :
	            ((text->top_of_screen + display_lines)/(display_lines))),
		total_pages);
    } else {
	strcpy(percent, "");	/* Null string */
    }

    sprintf(format, "%%%d.%ds%%s\n",	/* Generate format string */
		    (LYcols-1)-strlen(percent),
		    (LYcols-1)-strlen(percent));

    cp = (char *)calloc(1, (LYcols * 2));
    if (cp == NULL)
        outofmem(__FILE__, "display_title");
    if (HTJapanese) {
        if (title && (tmp = (unsigned char *)calloc(1, strlen(title) + 1))) {
	    if (kanji_code == EUC) {
	        TO_EUC((unsigned char *)title, tmp);
	    } else if (kanji_code == SJIS) {
	        TO_SJIS((unsigned char *)title, tmp);
	    } else {
	        for (i = 0, j = 0; title[i]; i++) {
		    if (title[i] != '\033') {
		        tmp[j++] = title[i];
		    }
		}
	    }
	    sprintf(cp, format, tmp, percent);
	    FREE(tmp);
        } else {
	    sprintf(cp, format, (title ? title : ""), percent);
        }
    } else {
        sprintf(cp, format, (title ? title : ""), percent);
    }
    move(0,0);
    addstr(cp);
    FREE(cp);
}


/*	Output a page
**	-------------
*/
PRIVATE void display_page ARGS3(HText *,text, int,line_number, char *, target)
{
    HTLine * line = NULL;
    int i;
    char *cp, tmp[3];
    int last_screen;
    TextAnchor *Anchor_ptr = NULL;
    FormInfo *FormInfo_ptr;
    BOOL display_flag=FALSE;
    HTAnchor *link_dest;

    lynx_mode = NORMAL_LYNX_MODE;
 
    if (text == NULL) {
	/*
	 * Check whether to force a screen clear to enable scrollback,
	 * or as a hack to fix a reverse clear screen problem for some
	 * curses packages. - shf@access.digex.net & seldon@eskimo.com
	 */
	if (enable_scrollback) {
	    addch('*');
	    refresh();
	    clear();
	}
	addstr("\n\nError accessing document\nNo data available\n");
	refresh();
	nlinks = 0;  /* set number of links to 0 */
	return;
    }

    tmp[0] = tmp[1] = tmp[2] = '\0';
    last_screen = text->lines - (display_lines-2);
    line = text->last_line->prev;

/*	Constrain the line number to be within the document
*/
    if (text->lines < (display_lines))
        line_number = 0;
    else if (line_number > text->lines)
        line_number = last_screen;
    else if (line_number < 0)
        line_number = 0;
    
    for (i = 0, line = text->last_line->next;		/* Find line */
    	 i < line_number && (line != text->last_line);
         i++, line = line->next) 			/* Loop */
	assert(line->next != NULL);

    /*
     * Check whether to force a screen clear to enable scrollback,
     * or as a hack to fix a reverse clear screen problem for some
     * curses packages. - shf@access.digex.net & seldon@eskimo.com
     */
    if (enable_scrollback) {
	addch('*');
	refresh();
	clear();
    }

    text->top_of_screen = line_number;
    display_title(text);  /* will move cursor to top of screen */
    display_flag=TRUE;
    
 /*	print it
 */
    if (line) {
      for (i=0; i < (display_lines); i++)  {

        assert(line != NULL);
        display_line(line);

        /* if the target is on this line, underline it */
        if (strlen(target) > 0 &&
	    (case_sensitive ?  
	    (cp = LYno_attr_char_strstr(line->data, target)) != NULL : 
	    (cp = LYno_attr_char_case_strstr(line->data, target)) != NULL) &&
            ((int)(cp - (char *)line->data) +
	     (int)line->offset + strlen(target)) < LYcols) {

	    int itmp=0;
	    int written=0;
	    int x_pos=(int)line->offset + (int)(cp - line->data);
	    int len = strlen(target);

	    start_underline();
		/* underline string */
	    for (; written < len && (tmp[0] = line->data[itmp]) != '\0';
		   itmp++)  {
		if (IsSpecialAttrChar(tmp[0])) {
		   /* ignore special characters */
		   x_pos--;

		} else if (cp == &line->data[itmp]) {
		    /* first character of target */
            	    move(i+1, x_pos);
		    if (HTJapanese && !isascii(tmp[0])) {
		        /* For Japanese strings, by Masanobu Kimura */
		        tmp[1] = line->data[++itmp];
			addstr(tmp);
			tmp[1] = '\0';
			written += 2;
		    } else {
		        addstr(tmp);
			written++;
		    }

		} else if (&line->data[itmp] > cp) { 
		    /* print all the other target chars */
		    if (HTJapanese && !isascii(tmp[0])) {
		        /* For Japanese strings, by Masanobu Kimura */
		        tmp[1] = line->data[++itmp];
			addstr(tmp);
			tmp[1] = '\0';
			written += 2;
		    } else {
		        addstr(tmp);
			written++;
		    }
		}
	    }

	    stop_underline();
	    move(i+2, 0);
	}

	/* stop if at the last line */
	if (line == text->last_line)  {
	    /* clr remaining lines of display */
	    for (i++; i < (display_lines); i++) {
		move(i+1,0);
		clrtoeol();
	    }
	    break;
	}

	display_flag=TRUE;
	line = line->next;
      }
    }

    text->next_line = line;	/* Line after screen */
    text->stale = NO;		/* Display is up-to-date */

    /* add the anchors to lynx structures */
    nlinks = 0;
    for (Anchor_ptr=text->first_anchor;  Anchor_ptr != NULL &&
		Anchor_ptr->line_num <= line_number+(display_lines);
					    Anchor_ptr = Anchor_ptr->next) {

	if (Anchor_ptr->line_num >= line_number &&
		Anchor_ptr->line_num < line_number+(display_lines)) {

		/* load normal hypertext anchors */
	    if (Anchor_ptr->show_anchor && Anchor_ptr->hightext && 
			strlen(Anchor_ptr->hightext)>0 && 
			Anchor_ptr->link_type == HYPERTEXT_ANCHOR) {

                links[nlinks].hightext  = Anchor_ptr->hightext;
                links[nlinks].hightext2 = Anchor_ptr->hightext2;
                links[nlinks].hightext2_offset = Anchor_ptr->hightext2offset;

                links[nlinks].anchor_number = Anchor_ptr->number;

		link_dest = HTAnchor_followMainLink(
					     (HTAnchor *)Anchor_ptr->anchor);
		{
		    /*
		     *	Memory leak fixed 05-27-94
		     *	Garrett Arch Blythe
		     */
	            auto char *cp_AnchorAddress = NULL;
		    if (traversal)
		        cp_AnchorAddress = stub_HTAnchor_address(link_dest);
		    else
			cp_AnchorAddress = HTAnchor_address(link_dest);

		    FREE(links[nlinks].lname);

		    if (cp_AnchorAddress != NULL)
			links[nlinks].lname = cp_AnchorAddress;
		    else
			StrAllocCopy(links[nlinks].lname, empty_string);
		}

      	        links[nlinks].lx = Anchor_ptr->line_pos;
      	        links[nlinks].ly = (Anchor_ptr->line_num+1)-line_number;
	        links[nlinks].type = WWW_LINK_TYPE;
		links[nlinks].target = empty_string;

	        nlinks++;
		display_flag = TRUE;

	    } else if (Anchor_ptr->link_type == INPUT_ANCHOR
			&& Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {

		lynx_mode = FORMS_LYNX_MODE;

		FormInfo_ptr = Anchor_ptr->input_field;

                links[nlinks].anchor_number = Anchor_ptr->number;

	   	links[nlinks].form = FormInfo_ptr;
		links[nlinks].lx = Anchor_ptr->line_pos;
		links[nlinks].ly = (Anchor_ptr->line_num+1)-line_number;
		links[nlinks].type = WWW_FORM_LINK_TYPE;
		links[nlinks].target = empty_string;
		StrAllocCopy(links[nlinks].lname, empty_string);

		if (FormInfo_ptr->type==F_RADIO_TYPE) {
		    if (FormInfo_ptr->num_value)
			links[nlinks].hightext = checked_radio;
		    else
			links[nlinks].hightext = unchecked_radio;

		} else if (FormInfo_ptr->type==F_CHECKBOX_TYPE) {
		    if (FormInfo_ptr->num_value)
			links[nlinks].hightext = checked_box;
		    else
			links[nlinks].hightext = unchecked_box;

		} else if (FormInfo_ptr->type==F_PASSWORD_TYPE) {
		    links[nlinks].hightext = STARS(strlen(FormInfo_ptr->value));

		} else {  /* TEXT type */
		    links[nlinks].hightext = FormInfo_ptr->value;
		}

  		/* never a second line on form types */
		links[nlinks].hightext2 = 0;

		nlinks++;
	         /* bold the link after incrementing nlinks */
		highlight(OFF,nlinks-1);
	
		display_flag = TRUE;

	    } else { /* not showing anchor */
		if (TRACE &&
		    Anchor_ptr->hightext && *Anchor_ptr->hightext) 
		    fprintf(stderr,
		    	    "\nGridText: Not showing link, hightext=%s\n",
			    Anchor_ptr->hightext);
	    }
	} 

	if (Anchor_ptr == text->last_anchor)
	    break;
    }

    /*
     * If Anchor_ptr is not NULL and is not pointing to the last
     * anchor, then there are anchors farther down in the document,
     * and we need to flag this for traversals.
     */
    more_links = FALSE;
    if (traversal && Anchor_ptr) {
        if (Anchor_ptr->next)
            more_links = TRUE;
    }

    if (!display_flag) /* nothing on the page */
	addstr("\n     Document is empty");

    if (HTJapanese) {
        /* for non-multibyte curses ;_; */
        clearok(curscr, TRUE);
    }
    refresh();

}


/*			Object Building methods
**			-----------------------
**
**	These are used by a parser to build the text in an object
*/
PUBLIC void HText_beginAppend ARGS1(HText *,text)
{
    text->permissible_split = 0;
    text->in_line_1 = YES;

}


/*	Add a new line of text
**	----------------------
**
** On entry,
**
**	split	is zero for newline function, else number of characters
**		before split.
**	text->display_on_the_fly
**		may be set to indicate direct output of the finished line.
** On exit,
**		A new line has been made, justified according to the
**		current style. Text after the split (if split nonzero)
**		is taken over onto the next line.
**
**		If display_on_the_fly is set, then it is decremented and
**		the finished line is displayed.
*/
#define new_line(text) split_line(text, 0)

PRIVATE void split_line ARGS2(HText *,text, int,split)
{
    HTStyle * style = text->style;
#if defined(AIX) || defined(ultrix)
    HTLine * temp; /* for realloc() substitute. */
#endif /* AIX || ultrix */	
    int spare;
    int indent = text->in_line_1 ? text->style->indent1st
    				 : text->style->leftIndent;

/*	Make new line
*/
    HTLine * previous = text->last_line;
    int ctrl_chars_on_previous_line = 0;
    char * cp;
    HTLine * line = (HTLine *)calloc(sizeof(char), LINE_SIZE(MAX_LINE));

    ctrl_chars_on_this_line = 0; /*reset since we are going to a new line*/
    HTML_Last_Char=' ';

    if (TRACE)
	fprintf(stderr,"GridText: split_line called\n");
    
    if (line == NULL)
        outofmem(__FILE__, "split_line");
    text->lines++;
    
    previous->next->prev = line;
    line->prev = previous;
    line->next = previous->next;
    previous->next = line;
    text->last_line = line;
    line->size = 0;
    line->offset = 0;
    text->permissible_split = 0;  /* 12/13/93 */
    line->data[0] = '\0';

    /*
     * If we are not splitting and need an underline char, add it now. - FM
     */
    if ((split < 1) &&
        !(dump_output_immediately && use_underscore) && underline_on) {
	line->data[line->size++] = LY_UNDERLINE_START_CHAR;
	line->data[line->size] = '\0';
	ctrl_chars_on_this_line++;
    }
    /*
     * If we are not splitting and need a bold char, add it now. - FM
     */
    if ((split < 1) && bold_on) {
	line->data[line->size++] = LY_BOLD_START_CHAR;
	line->data[line->size] = '\0';
	ctrl_chars_on_this_line++;
    }

/*	Split at required point
*/    
    if (split > 0) {	/* Delete space at "split" splitting line */
        char *p, *prevdata = previous->data, *linedata = line->data;
        unsigned int plen;
	int i;

        /*
	 * Split the line. - FM
	 */
	prevdata[previous->size] = 0;
	previous->size = split;

	/*
	 * Trim any spaces from the beginning of our new line. - FM
	 */
	p = prevdata + split;
        while (*p == ' ')
	    p++;
        plen = strlen(p);

	/*
	 * Add underline char if needed. - FM
	 */
        if (!(dump_output_immediately && use_underscore)) {
	    /*
	     * Make sure our global flag is correct. - FM
	     */
	    underline_on = NO;
	    for (i = (split-1); i >= 0; i--) {
	        if (prevdata[i] == LY_UNDERLINE_END_CHAR) {
		    break;
		}
		if (prevdata[i] == LY_UNDERLINE_START_CHAR) {
		    underline_on = YES;
		    break;
		}
	    }
	    /*
	     * Act on the global flag if set above. - FM
	     */
	    if (underline_on) {
	        linedata[line->size++] = LY_UNDERLINE_START_CHAR;
		linedata[line->size] = '\0';
		ctrl_chars_on_this_line++;
	    }
	    for (i = (plen - 1); i >= 0; i--) {
		if (p[i] == LY_UNDERLINE_START_CHAR) {
		    underline_on = YES;
		    break;
		}
		if (p[i] == LY_UNDERLINE_END_CHAR) {
		    underline_on = NO;
		    break;
		}
	    }
	    for (i = (plen - 1); i >= 0; i--) {
	        if (p[i] == LY_UNDERLINE_START_CHAR ||
		    p[i] == LY_UNDERLINE_END_CHAR) {
		    ctrl_chars_on_this_line++;
		}
	    }
	}

	/*
	 * Add bold char if needed, first making
	 * sure that our global flag is correct. - FM
	 */
	bold_on = NO;
	for (i = (split - 1); i >= 0; i--) {
	    if (prevdata[i] == LY_BOLD_END_CHAR) {
		break;
	    }
	    if (prevdata[i] == LY_BOLD_START_CHAR) {
	        bold_on = YES;
		break;
	    }
	}
	/*
	 * Act on the global flag if set above. - FM
	 */
	if (bold_on) {
	    linedata[line->size++] = LY_BOLD_START_CHAR;
	    linedata[line->size] = '\0';
	    ctrl_chars_on_this_line++;
	}
	for (i = (plen - 1); i >= 0; i--) {
	    if (p[i] == LY_BOLD_START_CHAR) {
	        bold_on = YES;
		break;
	    }
	    if (p[i] == LY_BOLD_END_CHAR) {
		bold_on = NO;
		break;
	    }
	}
	for (i = (plen - 1); i >= 0; i--) {
	    if (p[i] == LY_BOLD_START_CHAR ||
	        p[i] == LY_BOLD_END_CHAR) {
	        ctrl_chars_on_this_line++;
	    }
	}

	/*
	 * Add the data to the new line. - FM
	 */
	strcat(linedata, p);
	line->size += plen;
    }
    
/*	Economize on space.
*/
    while ((previous->size > 0) &&
    	(previous->data[previous->size-1] == ' '))	/* Strip trailers */
        previous->size--;
	
#if !defined(AIX) && !defined(ultrix)	
    previous = (HTLine *) realloc (previous, LINE_SIZE(previous->size));
    if (previous == NULL)
        outofmem(__FILE__, "split_line");
#else
    /*
     * RS6000 has a chaotic bug in realloc argument passing.  Same
     * problem with Ultrix (4.2) : realloc() is not declared properly.
     * So we'll use a substitute for realloc.
     */
    temp = (HTLine *)calloc(sizeof(char), LINE_SIZE(previous->size));
    if (temp == NULL)
        outofmem(__FILE__, "split_line");
    memcpy(temp, previous, LINE_SIZE(previous->size));
    FREE(previous);
    previous = temp;
#endif /* !AIX && !ultrix */

    previous->prev->next = previous;	/* Link in new line */
    previous->next->prev = previous;	/* Could be same node of course */

/*	Terminate finished line for printing
*/
    previous->data[previous->size] = 0;
     
    
/*	Align left, right or center
*/

    for (cp = previous->data; *cp; cp++) {
        if (*cp == LY_UNDERLINE_START_CHAR ||
	    *cp == LY_UNDERLINE_END_CHAR ||
	    *cp == LY_BOLD_START_CHAR ||
	    *cp == LY_BOLD_END_CHAR)
	    ctrl_chars_on_previous_line++;
    }
    /* @@ first line indent */
    spare =  (LYcols-1) -
    		(int)style->rightIndent - indent +
    		ctrl_chars_on_previous_line - previous->size;

    switch (style->alignment) {
	case HT_CENTER :
	    previous->offset = previous->offset + indent + spare/2;
	    break;
	case HT_RIGHT :
	    previous->offset = previous->offset + indent + spare;
	    break;
	case HT_LEFT :
	case HT_JUSTIFY :		/* Not implemented */
	default:
	    previous->offset = previous->offset + indent;
	    break;
    } /* switch */

    text->chars = text->chars + previous->size + 1;	/* 1 for the line */
    text->in_line_1 = NO;		/* unless caller sets it otherwise */
    
} /* split_line */


/*	Allow vertical blank space
**	--------------------------
*/
PRIVATE void blank_lines ARGS2(HText *,text, int,newlines)
{
    if (!HText_LastLineSize(text)) {	/* No text on current line */
	HTLine * line = text->last_line->prev;
	while ((line != text->last_line) &&
	       (HText_TrueLineSize(line) == 0)) {
	    if (newlines==0) break;
	    newlines--;		/* Don't bother: already blank */
	    line = line->prev;
	}
    } else {
	newlines++;			/* Need also to finish this line */
    }

    for (; newlines; newlines--) {
	new_line(text);
    }
    text->in_line_1 = YES;
}


/*	New paragraph in current style
**	------------------------------
** See also: setStyle.
*/

PUBLIC void HText_appendParagraph ARGS1(HText *,text)
{
    int after = text->style->spaceAfter;
    int before = text->style->spaceBefore;
    blank_lines(text, ((after > before) ? after : before));
}


/*	Set Style
**	---------
**
**	Does not filter unnecessary style changes.
*/
PUBLIC void HText_setStyle ARGS2(HText *,text, HTStyle *,style)
{
    int after, before;

    if (!style) return;				/* Safety */
    after = text->style->spaceAfter;
    before = style->spaceBefore;
    if (TRACE) fprintf(stderr, "GridText: Change to style %s\n", style->name);

    blank_lines (text, ((after > before) ? after : before));

    text->style = style;
}

/*	Append a character to the text object
**	-------------------------------------
*/
PUBLIC void HText_appendCharacter ARGS2(HText *,text, char,ch)
{
    HTLine * line;
    HTStyle * style;
    int indent;
    static char kanji_buf = '\0';
    static enum _state
        { S_text, S_esc, S_doller, S_paren, S_nonascii_text } state = S_text;
    static int in_sjis = 0;

    /* Make sure we don't crash on NULLs */
    if (!text)
	return;

#ifdef NOTDEFINED
    /* Make sure nbsp is handled properly */
    if ((unsigned char)ch == 160)
        ch = HT_NON_BREAK_SPACE;
#endif /* NOTDEFINED */

    /* Make sure we don't hang on escape sequences */
    if (ch == '\033' && !HTJapanese)			/* decimal 27 */
	return;
    if ((unsigned char)ch == 155 && !HTJapanese) {	/* octal 233 */
        if (strncmp(LYchar_set_names[current_char_set],
		    "IBM PC character set", 20) &&
	    strncmp(LYchar_set_names[current_char_set],
		    "IBM PC codepage 850", 19) &&
	    strncmp(LYchar_set_names[current_char_set],
		    "Macintosh (8 bit)", 17) &&
	    strncmp(LYchar_set_names[current_char_set],
		    "NeXT character set", 18)) {
	    return;
	}
    }

    line = text->last_line;
    style = text->style;

    indent = text->in_line_1 ? (int)style->indent1st : (int)style->leftIndent;
    
    if (HTJapanese) {
        if (kanji_code != NONE) {
	    switch(state) {
	        case S_text:
		    if (ch == '\033') {
		        state = S_esc;
			kanji_buf = '\0';
			return;
		    }
		    break;

		case S_esc:
		    if (ch == '$') {
		        state = S_doller;
			return;
		    } else if (ch == '(') {
		        state = S_paren;
			return;
		    } else {
		        state = S_text;
		    }

		case S_doller:
		    if (ch == '@' || ch == 'B') {
		        state = S_nonascii_text;
			return;
		    } else {
		        state = S_text;
		    }
		    break;

		case S_paren:
		    if (ch == 'B' || ch == 'J')  {
		        state = S_text;
			return;
		    } else {
		        state = S_text;
		    }
		    break;

		case S_nonascii_text:
		    if (ch == '\033') {
		        state = S_esc;
			kanji_buf = '\0';
			return;
		    } else {
		        ch |= 0200;
		    }
		    break;
	    }
	} else if (ch == '\033') {
	    return;
	}
    }

    if (HTJapanese) {
        if (!kanji_buf) {
	    if (kanji_code != NONE && (ch & 0200) != 0) {
	        kanji_buf = ch;
	        text->permissible_split = line->size;   /* Can split here */
	        return;
	     }
	 } else {
	     goto check_ignore_excess;
	 }
    }

    if (IsSpecialAttrChar(ch)) 
        if (ch == LY_UNDERLINE_START_CHAR) { 
            line->data[line->size++] = LY_UNDERLINE_START_CHAR;
	    line->data[line->size] = '\0';
	    underline_on = ON;
	    if (!(dump_output_immediately && use_underscore))
		ctrl_chars_on_this_line++;
	    return;
        } else if (ch == LY_UNDERLINE_END_CHAR) {
            line->data[line->size++] = LY_UNDERLINE_END_CHAR;
	    line->data[line->size] = '\0';
	    underline_on = OFF;
	    if (!(dump_output_immediately && use_underscore))
	    	ctrl_chars_on_this_line++;
	    return;
        } else if (ch == LY_BOLD_START_CHAR) {
            line->data[line->size++] = LY_BOLD_START_CHAR;
	    line->data[line->size] = '\0';
            bold_on = ON;
	    ctrl_chars_on_this_line++;
            return;
        } else if (ch == LY_BOLD_END_CHAR) {
            line->data[line->size++] = LY_BOLD_END_CHAR;
	    line->data[line->size] = '\0';
            bold_on = OFF;
	    ctrl_chars_on_this_line++;
            return;
        }

/*		New Line
*/
    if (ch == '\n') {
	    new_line(text);
	    text->in_line_1 = YES;	/* First line of new paragraph */
	    return;
    }

    /* convert EM_SPACE to a space here so that it doesn't get
     * collapsed
     */
    if (ch == HT_EM_SPACE)
	ch = ' ';

    /* I'm going to cheat here in a BIG way.  Since I know that all
     * \r's will be trapped by HTML_put_character I'm going to use
     * \r to mean go down a line but don't start a new paragraph.  
     * i.e. use the second line indenting.
     */
    if (ch == '\r') {
	new_line(text);
	text->in_line_1 = NO;
	return;
    }


/* 		Tabs
*/

    if (ch == '\t') {
        HTTabStop * tab;
	int target;	/* Where to tab to */
	int here;

	here = (((int)line->size + (int)line->offset) + indent)
		- ctrl_chars_on_this_line; /* Consider special chars GAB */
        if (style->tabs) {	/* Use tab table */
	    for (tab = style->tabs;
	    	tab->position <= here;
		tab++)
		if (!tab->position) {
		    new_line(text);
		    return;
		}
	    target = tab->position;
	} else if (text->in_line_1) {	/* Use 2nd indent */
	    if (here >= (int)style->leftIndent) {
	        new_line(text); /* wrap */
		return;
	    } else {
	        target = (int)style->leftIndent;
	    }
	} else {		/* Default tabs align with left indent mod 8 */
#ifdef DEFAULT_TABS_8
	    target = (((int)line->offset + (int)line->size + 8) & (-8))
	    		+ (int)style->leftIndent;
#else
	    new_line(text);
	    return;
#endif
	}

	if (target > (LYcols-1) - (int)style->rightIndent &&
	    HTOutputFormat != WWW_SOURCE) {
	    new_line(text);
	    return;
	} else {
            text->permissible_split = (int)line->size;	/* Can split here */
	    if (line->size == 0) {
	        line->offset = line->offset + target - here;
	    } else {
	        for (; here<target; here++) {
		    /* Put character into line */
                    line->data[line->size++] = ' ';
		    line->data[line->size] = '\0';
	        }
	    }
	    return;
	}
	/*NOTREACHED*/
    } /* if tab */ 

    
    if (ch==' ') {
        text->permissible_split = (int)line->size;	/* Can split here */
    }

/*	Check if we should ignore characters at the wrap point.
*/    
check_ignore_excess:
    if (ignore_excess &&
        ((indent + (int)line->offset + (int)line->size) + 
	(int)style->rightIndent - ctrl_chars_on_this_line) >= (LYcols-1))
        return;

/*	Check for end of line
*/    
    if (((indent + (int)line->offset + (int)line->size) + 
	(int)style->rightIndent - ctrl_chars_on_this_line) >= (LYcols-1)) {

        if (style->wordWrap && HTOutputFormat!=WWW_SOURCE) {
	    split_line(text, text->permissible_split);
	    if (ch==' ') return;	/* Ignore space causing split */

	}  else if (HTOutputFormat==WWW_SOURCE) {
		 /* for source output we 
		  * dont want to wrap this stuff unless absolutely
		  * neccessary LJM 
		  * !
		  * If we don't wrap here we might get a segmentation fault.
		  * but let's see what happens
		  */
		if ((int)line->size >= (int)(MAX_LINE-1))
		   new_line(text);  /* try not to linewrap */
	} else {
		/* for normal stuff like pre let's go ahead and
		 * wrap so the user can see all of the text
		 */
		new_line(text);  
	}
    }

/*	Insert normal characters
*/
    if (ch == HT_NON_BREAK_SPACE) {
        ch = ' ';
    }

    {
        HTLine * line = text->last_line;	/* May have changed */
        HTFont font = style->font;
	unsigned char hi, lo, tmp[2];

	if (HTJapanese && kanji_buf) {
	    hi = (unsigned char)kanji_buf, lo = (unsigned char)ch;
	    if ((kanji_code == EUC) && (IS_SJIS(hi, lo, in_sjis))) {
		SJIS_TO_EUC1(hi, lo, tmp);
		line->data[line->size++] = tmp[0];
		line->data[line->size++] = tmp[1];
	    } else if ((kanji_code == SJIS) && (IS_EUC(hi, lo))) {
		JIS_TO_SJIS1((hi & 0177), (lo & 0177), tmp);
		line->data[line->size++] = tmp[0];
		line->data[line->size++] = tmp[1];
	    } else {
		line->data[line->size++] = hi;
		line->data[line->size++] = lo;
	    }
	    kanji_buf = 0;
	} else if (HTJapanese) {
	    line->data[line->size++] = (kanji_code != NONE) ? ch :
				font & HT_CAPITALS ? TOUPPER(ch) : ch;
	} else {
            line->data[line->size++] =	/* Put character into line */
	    	font & HT_CAPITALS ? TOUPPER(ch) : ch;
	}
	line->data[line->size] = '\0';
        if (font & HT_DOUBLE)		/* Do again if doubled */
            HText_appendCharacter(text, HT_NON_BREAK_SPACE);
	    /* NOT a permissible split */ 
    }
}

/*		Anchor handling
**		---------------
*/
/*	Start an anchor field
*/
PUBLIC void HText_beginAnchor ARGS2(HText *,text, HTChildAnchor *,anc)
{
    char marker[16];

    TextAnchor * a = (TextAnchor *) calloc(sizeof(*a),1);
    
    if (a == NULL)
        outofmem(__FILE__, "HText_beginAnchor");
    a->hightext  = 0;
    a->hightext2 = 0;
    a->start = text->chars + text->last_line->size;

    a->line_pos = text->last_line->size;
    if (text->last_anchor) {
        text->last_anchor->next = a;
    } else {
        text->first_anchor = a;
    }
    a->next = 0;
    a->anchor = anc;
    a->extent = 0;
    a->link_type = HYPERTEXT_ANCHOR;
    text->last_anchor = a;
    
    if (HTAnchor_followMainLink((HTAnchor*)anc)) {
        a->number = ++(text->last_anchor_number);
    } else {
        a->number = 0;
    }

    /* if we are doing link_numbering add the link number */
    if (keypad_mode == LINKS_ARE_NUMBERED && a->number > 0) {
	sprintf(marker,"[%d]", a->number);
        HText_appendText(text, marker);
	a->start += strlen(marker);
    }
}


PUBLIC void HText_endAnchor ARGS1(HText *,text)
{
    TextAnchor * a = text->last_anchor;
    if (a->number) {
        /*
	 * If it goes somewhere...
	 */
        a->extent += text->chars + text->last_line->size - a->start;
	if (a->extent <= 2) {
	    /*
	     * Might be a blank anchor from an ALT="". - FM
	     */
    	    int j;
	    a->show_anchor = NO;
	    for (j = (text->last_line->size - a->extent);
	         text->last_line->data[j] != '\0'; j++) {
		if (!IsSpecialAttrChar(text->last_line->data[j])) {
		    a->show_anchor = YES;
		    break;
		}
	    }
	} else {
	    a->show_anchor = YES;
	}
    } else {
        a->show_anchor = NO;
	a->extent = 0;
    }
 
}


PUBLIC void HText_appendText ARGS2(HText *,text, CONST char *,str)
{
    CONST char * p;

    if (str == NULL)
	return;
    for (p=str; *p; p++) {
        HText_appendCharacter(text, *p);
    }
}


PRIVATE void remove_special_attr_chars ARGS1(char *,buf)
{
    register char *cp;

    for (cp=buf; *cp != '\0' ; cp++) {
         /* don't print underline chars */
        if (!IsSpecialAttrChar(*cp)) {
           *buf = *cp, 
           buf++;
	}
    }
    *buf = '\0';
}


PUBLIC void HText_endAppend ARGS1(HText *,text)
{
    int cur_line, cur_char;
    TextAnchor *anchor_ptr;
    HTLine *line_ptr;
    unsigned char ch;

    if (!text)
	return;

    new_line(text);
    
    /* get the hightext from the text by finding the char
     * position, then
     * bring the anchors in line with the text by adding the
     * text offset to each of the anchors
     */
    /* get first line */
    line_ptr = text->last_line->next;
    cur_char = line_ptr->size;;
    cur_line = 0;

    /* remove the blank lines at the end of document */
    while (text->last_line->data[0]=='\0' && text->lines > 2) {
        HTLine *next_to_the_last_line;

        if (TRACE)
            fprintf(stderr, "GridText: Removing bottom blank line: %s\n",
                            text->last_line->data);
    
        next_to_the_last_line = text->last_line->prev;

        /* line_ptr points to the first line */
        next_to_the_last_line->next = line_ptr;
        line_ptr->prev = next_to_the_last_line;
	FREE(text->last_line);
        text->last_line = next_to_the_last_line;
        text->lines--;

        if (TRACE)
            fprintf(stderr, "GridText: New bottom line: %s\n",
                            text->last_line->data);

    }


    if (TRACE)
	fprintf(stderr,"Gridtext: Entering HText_endAppend\n");

    for (anchor_ptr = text->first_anchor;
         anchor_ptr; anchor_ptr=anchor_ptr->next) {

re_parse:
	/* find the right line */
	for (; anchor_ptr->start >= cur_char; line_ptr = line_ptr->next, 
				    cur_char += line_ptr->size+1, cur_line++) 
	    ; /* null body */

	if (anchor_ptr->start == cur_char)
	    anchor_ptr->line_pos = line_ptr->size;
	else
	    anchor_ptr->line_pos = anchor_ptr->start-(cur_char-line_ptr->size);

	if (anchor_ptr->line_pos < 0)
	    anchor_ptr->line_pos = 0;

	if (TRACE)
	    fprintf(stderr, "Gridtext: Anchor found on line:%d col:%d\n",
			    cur_line, anchor_ptr->line_pos);

	/* strip off any spaces or SpecialAttrChars at the beginning,
	 * if they exist, but only on HYPERTEXT_ANCHORS
	 */
	if (anchor_ptr->link_type == HYPERTEXT_ANCHOR) {
	    ch = (unsigned char)line_ptr->data[anchor_ptr->line_pos];
            while (isspace(ch) ||
	           IsSpecialAttrChar(ch)) {
		anchor_ptr->line_pos++;
		anchor_ptr->extent--;
		ch = (unsigned char)line_ptr->data[anchor_ptr->line_pos];
	    }
	}

	if (anchor_ptr->extent < 0)
	    anchor_ptr->extent = 0;

	if (TRACE)
	    fprintf(stderr, "anchor text: '%s'   pos: %d\n",
	    		    line_ptr->data, anchor_ptr->line_pos);

	/* if the link begins with a end of line then start the
	 * highlighting on the next line
	 */
	if (anchor_ptr->line_pos >= strlen(line_ptr->data)) {
	    anchor_ptr->start++;

	    if (TRACE)
		fprintf(stderr, "found anchor at end of line\n");
	    goto re_parse;
	}

	if (TRACE)
	    fprintf(stderr, "anchor text: '%s'   pos: %d\n",
	    		    line_ptr->data, anchor_ptr->line_pos);

	/* copy the link name into the data structure */
	if (line_ptr->data &&
	    anchor_ptr->extent > 0 && anchor_ptr->line_pos >= 0) {

	    StrnAllocCopy(anchor_ptr->hightext,
	    		  &line_ptr->data[anchor_ptr->line_pos],
			  anchor_ptr->extent);
	} else {
	    StrAllocCopy(anchor_ptr->hightext,""); 
	}

	/*  If true the anchor extends over two lines */
	if (anchor_ptr->extent > strlen(anchor_ptr->hightext)) {
            HTLine *line_ptr2 = line_ptr->next;
	    /* double check! */
	    if (line_ptr) {
		StrnAllocCopy(anchor_ptr->hightext2, line_ptr2->data, 
			 (anchor_ptr->extent - strlen(anchor_ptr->hightext))-1);
	        anchor_ptr->hightext2offset = line_ptr2->offset;
	 	remove_special_attr_chars(anchor_ptr->hightext2);
	    }
	}   

	remove_special_attr_chars(anchor_ptr->hightext);

        /* subtract any formatting characters from the x position
         * of the link
         */
        if (anchor_ptr->line_pos > 0) {
            register int offset=0, i=0;
            for (; i < anchor_ptr->line_pos; i++)
                if (IsSpecialAttrChar(line_ptr->data[i]))
                    offset++;
            anchor_ptr->line_pos -= offset;
        }

        anchor_ptr->line_pos += line_ptr->offset;  /* add the offset */
        anchor_ptr->line_num  = cur_line;


	if (TRACE)
	    fprintf(stderr,
	    	    "GridText: adding link on line %d in HText_endAppend\n",
		    cur_line);

 	if (anchor_ptr == text->last_anchor)
	    break;
    }
}


/* 	Dump diagnostics to stderr
*/
PUBLIC void HText_dump ARGS1(HText *,text)
{
    fprintf(stderr, "HText: Dump called\n");
}
	

/*	Return the anchor associated with this node
*/
PUBLIC HTParentAnchor * HText_nodeAnchor ARGS1(HText *,text)
{
    return text->node_anchor;
}

/*				GridText specials
**				=================
*/
/*	Return the anchor with index N
**
**	The index corresponds to the number we print in the anchor.
*/
PUBLIC HTChildAnchor * HText_childNumber ARGS1(int,number)
{
    TextAnchor * a;
    for (a = HTMainText->first_anchor; a; a = a->next) {
        if (a->number == number) return(a->anchor);
    }
    return (HTChildAnchor *)0;	/* Fail */
}

/* HTGetLinkInfo returns some link info based on the number
 */
PUBLIC int HTGetLinkInfo ARGS3(int, number, char **, hightext, char **, lname)
{
    TextAnchor * a;
    HTAnchor *link_dest;

    for (a = HTMainText->first_anchor; a; a = a->next) {
        if (a->number == number) {
	    *hightext= a->hightext;
            link_dest = HTAnchor_followMainLink(
                                               (HTAnchor *)a->anchor);
	    {
		/*
		 *	Memory Leak fixed.
		 *	05-28-94 Lynx 2-3-1 Garrett Arch Blythe
	 	 */
		auto char *cp_freeme = NULL;
		if (traversal)
		    cp_freeme = stub_HTAnchor_address(link_dest);
		else
		    cp_freeme = HTAnchor_address(link_dest);
            	StrAllocCopy(*lname, cp_freeme);
		FREE(cp_freeme);
	    }
	    return(YES);
	}
    }
    return(NO);
}

/* HText_getNumOfLines returns the number of lines in the
 * current document
 */
PUBLIC int HText_getNumOfLines NOARGS
{
     return(HTMainText->lines);
}

/* HText_getTitle returns the title of the
 * current document
 */
PUBLIC char * HText_getTitle NOARGS
{
   return((char *) HTAnchor_title(HTMainText->node_anchor));
}

/*
 * HText_pageDisplay displays a screen of text
 * starting from the line 'line_num'-1
 * this is the primary call for lynx
 */
extern char is_www_index;

PUBLIC void HText_pageDisplay ARGS2(int,line_num, char *, target)
{
    display_page(HTMainText, line_num-1, target);

    is_www_index = HTAnchor_isIndex(HTMainAnchor);
} 

/*
 * HText_LinksInLines returns the number of links in the
 * 'lines' number of lines beginning with 'line_num'-1. - FM
 */
PUBLIC int HText_LinksInLines ARGS3(HText *,text, int,line_num, int,lines)
{
    int total = 0;
    int start = (line_num - 1);
    int end = (start + lines);
    TextAnchor *Anchor_ptr = NULL;

    if (!text)
        return total;

    for (Anchor_ptr = text->first_anchor;
		Anchor_ptr != NULL && Anchor_ptr->line_num <= end;
			Anchor_ptr = Anchor_ptr->next) {
	if (Anchor_ptr->line_num >= start &&
	    Anchor_ptr->line_num < end &&
	    Anchor_ptr->show_anchor)
	    ++total;
	if (Anchor_ptr == text->last_anchor)
	    break;
    }

    return total;
}

PUBLIC void HText_setStale ARGS1(HText *,text)
{
    text->stale = YES;
}

PUBLIC void HText_refresh ARGS1(HText *,text)
{
    if (text->stale) display_page(text, text->top_of_screen, "");
}

PUBLIC int HText_sourceAnchors ARGS1(HText *,text)
{
    return (text ? text->last_anchor_number : -1);
}

PUBLIC BOOL HText_canScrollUp ARGS1(HText *,text)
{
    return (text->top_of_screen != 0);
}

PUBLIC BOOL HText_canScrollDown NOARGS
{
    HText * text = HTMainText;

    return ((text->top_of_screen + display_lines) < text->lines+1);
}

/*		Scroll actions
*/
PUBLIC void HText_scrollTop ARGS1(HText *,text)
{
    display_page(text, 0, "");
}

PUBLIC void HText_scrollDown ARGS1(HText *,text)
{
    display_page(text, text->top_of_screen + display_lines, "");
}

PUBLIC void HText_scrollUp ARGS1(HText *,text)
{
    display_page(text, text->top_of_screen - display_lines, "");
}

PUBLIC void HText_scrollBottom ARGS1(HText *,text)
{
    display_page(text, text->lines - display_lines, "");
}


/*		Browsing functions
**		==================
*/

/* Bring to front and highlight it
*/

PRIVATE int line_for_char ARGS2(HText *,text, int,char_num)
{
    int line_number =0;
    int characters = 0;
    HTLine * line = text->last_line->next;
    for (;;) {
	if (line == text->last_line) return 0;	/* Invalid */
        characters = characters + line->size + 1;
	if (characters > char_num) return line_number;
	line_number ++;
	line = line->next;
    }
}

PUBLIC BOOL HText_select ARGS1(HText *,text)
{
    if (text != HTMainText) {
        HTMainText = text;
	HTMainAnchor = text->node_anchor;
	/*
	 * Make this text the most current in the loaded texts list. - FM
	 */
	if (loaded_texts && HTList_removeObject(loaded_texts, text))
	    HTList_addObject(loaded_texts, text);
	  /* let lynx do it */
	/* display_page(text, text->top_of_screen, ""); */
    }
    return YES;
}

PUBLIC BOOL HTFindPoundSelector ARGS1(char *,selector)
{
    TextAnchor * a;

    for (a=HTMainText->first_anchor; a; a=a->next) {

        if (a->anchor && a->anchor->tag)
            if (!strcmp(a->anchor->tag, selector)) {

                 www_search_result = a->line_num+1;
                 if (TRACE) 
		    fprintf(stderr, 
		"HText: Selecting anchor [%d] at character %d, line %d\n",
                                     a->number, a->start, www_search_result);
		if (!strcmp(selector, LYToolbarName))
		    --www_search_result;

                 return(YES);
            }
    }

    return(NO);

}

PUBLIC BOOL HText_selectAnchor ARGS2(HText *,text, HTChildAnchor *,anchor)
{
    TextAnchor * a;

/* This is done later, hence HText_select is unused in GridText.c
   Should it be the contrary ? @@@
    if (text != HTMainText) {
        HText_select(text);
    }
*/

    for (a=text->first_anchor; a; a=a->next) {
        if (a->anchor == anchor) break;
    }
    if (!a) {
        if (TRACE) fprintf(stderr, "HText: No such anchor in this text!\n");
        return NO;
    }

    if (text != HTMainText) {		/* Comment out by ??? */
        HTMainText = text;		/* Put back in by tbl 921208 */
	HTMainAnchor = text->node_anchor;
    }

    {
	 int l = line_for_char(text, a->start);
	if (TRACE) fprintf(stderr,
	    "HText: Selecting anchor [%d] at character %d, line %d\n",
	    a->number, a->start, l);

	if ( !text->stale &&
	     (l >= text->top_of_screen) &&
	     ( l < text->top_of_screen + display_lines+1))
	         return YES;

	www_search_result = l - (display_lines/3); /* put in global variable */
    }
    
    return YES;
}
 

/*		Editing functions		- NOT IMPLEMENTED
**		=================
**
**	These are called from the application. There are many more functions
**	not included here from the orginal text object.
*/

/*	Style handling:
*/
/*	Apply this style to the selection
*/
PUBLIC void HText_applyStyle ARGS2(HText *, me, HTStyle *,style)
{
    
}


/*	Update all text with changed style.
*/
PUBLIC void HText_updateStyle ARGS2(HText *, me, HTStyle *,style)
{
    
}


/*	Return style of  selection
*/
PUBLIC HTStyle * HText_selectionStyle ARGS2(
	HText *,me,
	HTStyleSheet *,sheet)
{
    return 0;
}


/*	Paste in styled text
*/
PUBLIC void HText_replaceSel ARGS3(
	HText *,me,
	CONST char *,aString, 
	HTStyle *,aStyle)
{
}


/*	Apply this style to the selection and all similarly formatted text
**	(style recovery only)
*/
PUBLIC void HTextApplyToSimilar ARGS2(HText *,me, HTStyle *,style)
{
    
}

 
/*	Select the first unstyled run.
**	(style recovery only)
*/
PUBLIC void HTextSelectUnstyled ARGS2(HText *,me, HTStyleSheet *,sheet)
{
    
}


/*	Anchor handling:
*/
PUBLIC void		HText_unlinkSelection ARGS1(HText *,me)
{
    
}

PUBLIC HTAnchor *	HText_referenceSelected ARGS1(HText *,me)
{
     return 0;   
}


PUBLIC int HText_getTopOfScreen NOARGS
{
      HText * text = HTMainText;
      return text->top_of_screen;
}

PUBLIC int HText_getLines ARGS1(HText *,text)
{
      return text->lines;
}

PUBLIC HTAnchor * HText_linkSelTo ARGS2(HText *,me, HTAnchor *,anchor)
{
    return 0;
}

/* 
 * Utility for freeing the list of previous isindex and whereis queries. - FM
 */
PUBLIC void HTSearchQueries_free NOARGS
{
    char *query;
    HTList *cur = search_queries;

    if (!cur)
        return;

    while (NULL != (query = (char *)HTList_nextObject(cur))) {
	FREE(query);
    }
    HTList_delete(search_queries);
    search_queries = NULL;
    return;
}

/* 
 * Utility for listing isindex and whereis queries, making
 * any repeated queries the most current in the list. - FM
 */
PUBLIC void HTAddSearchQuery ARGS1(char *, query)
{
    char *new;
    char *old;
    HTList *cur;

    if (!(query && *query))
        return;

    if ((new = (char *)calloc(1, (strlen(query) + 1))) == NULL)
    	outofmem(__FILE__, "HTAddSearchQuery");
    strcpy(new, query);

    if (!search_queries) {
        search_queries = HTList_new();
	atexit(HTSearchQueries_free);
	HTList_addObject(search_queries, new);
	return;
    }

    cur = search_queries;
    while (NULL != (old = (char *)HTList_nextObject(cur))) {
	if (!strcmp(old, new)) {
	    HTList_removeObject(search_queries, old);
	    FREE(old);
	    break;
	}
    }
    HTList_addObject(search_queries, new);

    return;
}

PUBLIC int do_www_search ARGS1(document *,doc)
{
    char searchstring[256], temp[256], *cp, *tmpaddress=NULL;
    int ch, recall, i;
    int QueryTotal;
    int QueryNum;
    BOOLEAN PreviousSearch = FALSE;

    /*
     * Load the default query buffer
     */
    if ((cp=strchr(doc->address, '?')) != NULL) {
        /*
	 * This is an index from a previous search.
	 * Use its query as the default.
	 */
	PreviousSearch = TRUE;
	strcpy(searchstring, ++cp);
	for (cp=searchstring; *cp; cp++)
	    if (*cp == '+')
	        *cp = ' ';
	HTUnEscape(searchstring);
	strcpy(temp, searchstring);
	/*
	 * Make sure it's treated as the most recent query. - FM
	 */
	HTAddSearchQuery(searchstring);
    } else {
        /*
	 * New search; no default.
	 */
        searchstring[0] = '\0';
	temp[0] = '\0';
    }

    /*
     * Prompt for a query string.
     */
    if (searchstring[0] == '\0') {
        if (HTMainAnchor->isIndexPrompt)
            _statusline(HTMainAnchor->isIndexPrompt);
	else
            _statusline("Enter a database query: ");
    } else
        _statusline("Edit the current query: ");
    QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
    recall = (((PreviousSearch && QueryTotal >= 2) ||
    	       (!PreviousSearch && QueryTotal >= 1)) ? RECALL : NORECALL);
    QueryNum = QueryTotal;
get_query:
    if ((ch=LYgetstr(searchstring, VISIBLE,
    		     sizeof(searchstring), recall)) < 0 ||
        *searchstring == '\0' || ch == UPARROW || ch == DNARROW) {
	if (recall && ch == UPARROW) {
	    if (PreviousSearch) {
	        /*
		 * Use the second to last query in the list. - FM
		 */
	        QueryNum = 1;
		PreviousSearch = FALSE;
	    } else {
	        /*
		 * Go back to the previous query in the list. - FM
		 */
	        QueryNum++;
	    }
	    if (QueryNum >= QueryTotal)
	        /*
		 * Roll around to the last query in the list. - FM
		 */
	        QueryNum = 0;
	    if ((cp=(char *)HTList_objectAt(search_queries,
	    				    QueryNum)) != NULL) {
		strcpy(searchstring, cp);
		if (*temp && !strcmp(temp, searchstring)) {
		    _statusline("Edit the current query: ");
		} else if ((*temp && QueryTotal == 2) ||
			   (!(*temp) && QueryTotal == 1)) {
		    _statusline("Edit the previous query: ");
		} else {
		    _statusline("Edit a previous query: ");
		}
		goto get_query;
	    }
	} else if (recall && ch == DNARROW) {
	    if (PreviousSearch) {
	        /*
		 * Use the first query in the list. - FM
		 */
	        QueryNum = QueryTotal - 1;
		PreviousSearch = FALSE;
	    } else {
	        /*
		 * Advance to the next query in the list. - FM
		 */
	        QueryNum--;
	    }
	    if (QueryNum < 0)
	        /*
		 * Roll around to the first query in the list. - FM
		 */
		QueryNum = QueryTotal - 1;
	    if ((cp=(char *)HTList_objectAt(search_queries,
	    				    QueryNum)) != NULL) {
		strcpy(searchstring, cp);
		if (*temp && !strcmp(temp, searchstring)) {
		    _statusline("Edit the current query: ");
		} else if ((*temp && QueryTotal == 2) ||
			   (!(*temp) && QueryTotal == 1)) {
		    _statusline("Edit the previous query: ");
		} else {
		    _statusline("Edit a previous query: ");
		}
		goto get_query;
	    }
	}

        /*
	 * Search cancelled.
	 */
        _statusline("Cancelled!!!");
        sleep(InfoSecs);
	return(NULLFILE);
    }

    /*
     * Strip leaders and trailers. - FM
     */
    cp = searchstring;
    while (*cp && isspace((unsigned char)*cp))
        cp++;
    if (!(*cp)) {
        _statusline("Cancelled!!!");
        sleep(InfoSecs);
        return(NULLFILE);
    }
    if (cp > searchstring) {
        for (i = 0; *cp; i++)
	    searchstring[i] = *cp++;
	searchstring[i] = '\0';
    }
    cp = searchstring + strlen(searchstring) - 1;
    while ((cp > searchstring) && isspace((unsigned char)*cp))
        *cp-- = '\0';

    /*
     * Don't resubmit the same query unintentionally.
     */
    if (!LYforce_no_cache && 0==strcmp(temp, searchstring)) {
	_statusline("Use Control-R to resubmit the current query.");
	sleep(MessageSecs);
	return(NULLFILE);
    }

    /*
     * Add searchstring to the query list,
     * or make it the most current. - FM
     */
    HTAddSearchQuery(searchstring);

    /*
     * Show the URL with the new query.
     */
    if ((cp=strchr(doc->address, '?')) != NULL)
        *cp = '\0';
    StrAllocCopy(tmpaddress, doc->address);
    StrAllocCat(tmpaddress, "?");
    StrAllocCat(tmpaddress, searchstring);
    user_message(WWW_WAIT_MESSAGE, tmpaddress);
#ifndef VMS
#ifdef SYSLOG_REQUESTED_URLS
    syslog(LOG_INFO|LOG_LOCAL5, "%s", tmpaddress);
#endif /* SYSLOG_REQUESTED_URLS */
#endif /* !VMS */
    FREE(tmpaddress);
    if (cp)
        *cp = '?';

    /*
     * OK, now we do the search.
     */
    if (HTSearch(searchstring, HTMainAnchor)) {
	/*
	 *	Memory leak fixed.
	 *	05-28-94 Lynx 2-3-1 Garrett Arch Blythe
	 */
	auto char *cp_freeme = NULL;
	if (traversal)
	    cp_freeme = stub_HTAnchor_address((HTAnchor *)HTMainAnchor);
	else
	    cp_freeme = HTAnchor_address((HTAnchor *)HTMainAnchor);
        StrAllocCopy(doc->address, cp_freeme);
	FREE(cp_freeme);

	if (TRACE)
	    fprintf(stderr,"\ndo_www_search: newfile: %s\n",doc->address);

        /*
	 * Yah, the search succeeded.
	 */
	return(NORMAL);
    }

    /*
     * Either the search failed (Yuk), or we got redirection.
     * If it's redirection, use_this_url_instead is set, and
     * mainloop() will deal with it such that security features
     * and restrictions are checked before acting on the URL, or
     * rejecting it. - FM
     */
    return(NOT_FOUND);
}

/* print the contents of the file in HTMainText to
 * the file descripter fp.
 * if is_reply is true add ">" to the beginning of each
 * line to specify the file is a replied to message
 */
PUBLIC void print_wwwfile_to_fd ARGS2(FILE *,fp, int,is_reply)
{
      register int i;
      HTLine * line = HTMainText->last_line->next;
#ifdef VMS
      extern BOOLEAN HadVMSInterrupt;
#endif /* VMS */

      for (;; line = line->next) {

	  if (is_reply)
             fputc('>',fp);

            /* add offset */
          for (i = 0; i < (int)line->offset; i++)
             fputc(' ',fp);

            /* add data */
          for (i = 0; line->data[i] != '\0'; i++)
             if (!IsSpecialAttrChar(line->data[i]))
                 fputc(line->data[i],fp);
	     else if (dump_output_immediately && use_underscore) {
		switch (line->data[i]) {
		    case LY_UNDERLINE_START_CHAR:
		    case LY_UNDERLINE_END_CHAR:
			fputc('_', fp);
			break;
		    case LY_BOLD_START_CHAR:
		    case LY_BOLD_END_CHAR:
			break;
		}
	     } 

         /* add the return */
         fputc('\n',fp);

	 if (line == HTMainText->last_line)
	    break;

#ifdef VMS
	if (HadVMSInterrupt)
	    break;
#endif /* VMS */
    }

}

/* print the contents of the file in HTMainText to
 * the file descripter fp.
 * First output line is "thelink", ie, the URL for this file
 */
PUBLIC void print_crawl_to_fd ARGS3(FILE *, fp, char *, thelink,
				    char *, thetitle)
{
    register int i;
    HTLine * line = HTMainText->last_line->next;
#ifdef VMS
    extern BOOLEAN HadVMSInterrupt;
#endif /* VMS */

    fprintf(fp,"THE_URL:%s\n",thelink);
    if (thetitle != NULL)fprintf(fp,"THE_TITLE:%s\n",thetitle);;
      
    for (;; line = line->next) {
        /* add offset */
        for (i = 0; i < (int)line->offset; i++)
            fputc(' ',fp);

        /* add data */
        for (i = 0; line->data[i] != '\0'; i++)
            if (!IsSpecialAttrChar(line->data[i]))
                fputc(line->data[i],fp);

        /* add the return */
        fputc('\n',fp);

	if (line == HTMainText->last_line)
	    break;
    }

    /* add the References list if appropriate */
    if (keypad_mode == LINKS_ARE_NUMBERED && !nolist)
        printlist(fp,FALSE);

#ifdef VMS
    HadVMSInterrupt = FALSE;
#endif /* VMS */
}

PUBLIC void www_user_search ARGS2(int,start_line, char *,target)
{
    register HTLine * line = HTMainText->last_line->next;
    register int count;
    extern BOOLEAN case_sensitive;

	/* advance to the start line */
    for (count = 1; count <= start_line; line = line->next, count++)
	; /* null */

    for (;;) {
	if (case_sensitive && LYno_attr_char_strstr(line->data, target)) {
	    www_search_result=count;
	    return;
	} else if (!case_sensitive &&
		   LYno_attr_char_case_strstr(line->data, target)) {
	    www_search_result=count;
	    return;
	} else if (line == HTMainText->last_line) {  /* next line */
	    break;
	} else {			/* end */
	    line = line->next;
	    count++;
	}
    }

	/* search from the beginning */
    line = HTMainText->last_line->next; /* set to first line */
    count = 1;

    for (;;) {
	    if (case_sensitive && LYno_attr_char_strstr(line->data, target)) {
	        www_search_result=count;
		return;
	    } else if (!case_sensitive &&
	    	       LYno_attr_char_case_strstr(line->data, target)) {
	        www_search_result=count;
		return;
	    } else if (count > start_line) {  /* next line */
    		_user_message("\"%s\" could not be found in this document",
			      target);
    		sleep(MessageSecs);
	        return;			/* end */
	    } else {
	        line = line->next;
		count++;
	    }
    }


}

PUBLIC  void  user_message ARGS2(char *,message, char *,argument) 
{
    char *temp = NULL;
    char temp_arg[256];

    if (message == NULL) {
        mustshow = FALSE;
	return;
    }

   /* make sure we don't overun any buffers */
    LYstrncpy(temp_arg, ((argument == NULL) ? "" : argument), 255);
    temp_arg[255] = '\0';
    temp = (char *)malloc(strlen(message) + strlen(temp_arg) + 1);
    if (temp == NULL)
        outofmem(__FILE__, "user_message");
    sprintf(temp, message, temp_arg);

    statusline(temp);
   
    FREE(temp);
    return;
}

/* HText_getOwner returns the owner of the
 * current document
 */
PUBLIC char * HText_getOwner NOARGS
{
   return((char *)HTAnchor_owner(HTMainText->node_anchor));
}

/* HText_getRevTitle returns the RevTitle element of the
 * current document, used as the subject for mailto comments
 * to the owner.
 */
PUBLIC char * HText_getRevTitle NOARGS
{
   return((char *)HTAnchor_RevTitle(HTMainText->node_anchor));
}

PUBLIC void HTuncache_current_document NOARGS
{
    /* should remove current document from memory */
    HTList_removeObject(loaded_texts, HTMainText);
    HText_free(HTMainText);
    HTMainText=0;
}

PUBLIC int HTisDocumentSource NOARGS
{
   return(HTMainText->source);
}

PUBLIC char * HTLoadedDocumentURL NOARGS
{
   if (!HTMainText)
	return ("");

   if (HTMainText->node_anchor && HTMainText->node_anchor->address) 
       	return(HTMainText->node_anchor->address);
   else
	return ("");
}

PUBLIC char * HTLoadedDocumentPost_data NOARGS
{
   if (!HTMainText)
	return ("");

   if (HTMainText->node_anchor && HTMainText->node_anchor->post_data) 
       	return(HTMainText->node_anchor->post_data);
   else
	return ("");
}

PUBLIC char * HTLoadedDocumentTitle NOARGS
{
   if (!HTMainText)
	return ("");

   if (HTMainText->node_anchor && HTMainText->node_anchor->title) 
       	return(HTMainText->node_anchor->title);
   else
	return ("");
}

PUBLIC BOOLEAN HTLoadedDocumentIsHEAD NOARGS
{
   if (!HTMainText)
	return (FALSE);

   if (HTMainText->node_anchor && HTMainText->node_anchor->isHEAD) 
       	return(HTMainText->node_anchor->isHEAD);
   else
	return (FALSE);
}

PUBLIC int HText_LastLineSize ARGS1(HText *,text)
{
    if (!text || !text->last_line || !text->last_line->size)
        return 0;
    return HText_TrueLineSize(text->last_line);
}

PUBLIC int HText_PreviousLineSize ARGS1(HText *,text)
{
    HTLine * line;

    if (!text || !text->last_line)
        return 0;
    if (!(line = text->last_line->prev))
        return 0;
    return HText_TrueLineSize(line);
}

PRIVATE int HText_TrueLineSize ARGS1(HTLine *,line)
{
    int i, true_size=0;

    if (!line || !line->size)
        return 0;

    for (i = 0; i < line->size; i++) {
    	if (!IsSpecialAttrChar(line->data[i])) {
	    true_size++;
	}
    }
    return true_size;
}

PUBLIC void HText_NegateLineOne ARGS1(HText *,text)
{
    if (text) {
        text->in_line_1 = NO;
    }
    return;
}

/*
 * NOTE: This function presently is correct only if the
 *	 alignment is HT_LEFT.  The offset is still zero,
 *	 because that's not determined for HT_CENTER or
 *	 HT_RIGHT until subsequent characters are received
 *	 and split_line() is called. - FM
 */
PUBLIC int HText_getCurrentColumn ARGS1(HText *,text)
{
    int column = 0;

    if (text) {
        column = (text->in_line_1 ?
		  (int)text->style->indent1st : (int)text->style->leftIndent)
		  + HText_LastLineSize(text) + (int)text->last_line->offset;
    }
    return column;
}

PUBLIC int HText_getMaximumColumn ARGS1(HText *,text)
{
    int column = (LYcols-2);
    if (text) {
        column = ((int)text->style->rightIndent ? (LYcols-2) :
		  ((LYcols-1) - (int)text->style->rightIndent));
    }
    return column;
}

/*
 * NOTE: This function uses HText_getCurrentColumn() which
 *	 presently is correct only if the alignment is
 *	 HT_LEFT. - FM
 */
PUBLIC void HText_setTabID ARGS2(HText *,text, CONST char *,name)
{
    HTTabID * tab = NULL;
    HTList * cur = text->tabs;
    HTList * last = NULL;

    if (!text || !name || !*name)
    	return;

    if (!cur) {
        cur = text->tabs = HTList_new();
    } else {
        while (NULL != (tab = (HTTabID *)HTList_nextObject(cur))) {
	    if (tab->name && !strcmp(tab->name, name))
	        return; /* Already set.  Keep the first value. */
	    last = cur;
	}
	cur = last;
    }
    if (!tab) { /* New name.  Create a new node */
	tab = (HTTabID *)calloc(1, sizeof(HTTabID));
	if (tab == NULL)
	    outofmem(__FILE__, "HText_setTabID");
	HTList_addObject(cur, tab);
	StrAllocCopy(tab->name, name);
    }
    tab->column = HText_getCurrentColumn(text);
    return;
}

PUBLIC int HText_getTabIDColumn ARGS2(HText *,text, CONST char *,name)
{
    int column = 0;
    HTTabID * tab;
    HTList * cur = text->tabs;

    if (text && name && *name && cur) {
        while (NULL != (tab = (HTTabID *)HTList_nextObject(cur))) {
	    if (tab->name && !strcmp(tab->name, name))
	        break;
	}
	if (tab)
	    column = tab->column;
    }
    return column;
}


/*  Form methods
 *    These routines are used to build forms consisting
 *    of input fields 
 */

PRIVATE int HTFormMethod;
PRIVATE char * HTFormAction = NULL;
PRIVATE char * HTFormTitle = NULL;

PUBLIC void HText_beginForm ARGS3(char *,action, char *,method, char *,title)
{
    HTFormMethod = URL_GET_METHOD;
    HTFormNumber++;
    HTFormFields = 0;

    if (action != NULL) {
	if (!strncmp(action, "mailto:", 7)) {
	    HTFormMethod = URL_MAIL_METHOD;
	}
        StrAllocCopy(HTFormAction, action);
    }
    else
	StrAllocCopy(HTFormAction, HTLoadedDocumentURL());
    
    if (method != NULL)
	if (!strcasecomp(method,"post") && HTFormMethod != URL_MAIL_METHOD)
	   HTFormMethod = URL_POST_METHOD;

    if ((title != NULL) && *title)
        StrAllocCopy(HTFormTitle, title);
    else
        FREE(HTFormTitle);

    if (TRACE)
	fprintf(stderr,"BeginForm: action:%s 	Method:%d\n",HTFormAction,
								HTFormMethod);
}

PUBLIC void HText_endForm ARGS1(HText *,text)
{
    if (HTFormFields == 1 && text && text->first_anchor) {
        /*
	 * Support submission of a single text input field in
	 * the form via <return> instead of a submit botton. - FM
	 */
        TextAnchor * a = text->first_anchor;
	/*
	 * Go through list of anchors and get our input field. - FM
	 */
	while (1) {
	    if (a->link_type == INPUT_ANCHOR &&
	        a->input_field->number == HTFormNumber &&
		a->input_field->type == F_TEXT_TYPE) {
		/*
		 * Got it.  Make it submitting. - FM
		 */
		a->input_field->submit_action = NULL;
		StrAllocCopy(a->input_field->submit_action, HTFormAction);
		if (HTFormTitle != NULL)
		    StrAllocCopy(a->input_field->title, HTFormTitle);
		a->input_field->submit_method = HTFormMethod;
		a->input_field->type = F_TEXT_SUBMIT_TYPE;
		break;
	    }
	    if (a == text->last_anchor)
	        break;
	    a = a->next;
	}
    }
    FREE(HTFormAction);
    FREE(HTFormTitle);
    HTFormFields = 0;
}

PUBLIC void HText_beginSelect ARGS3(char *,name, BOOLEAN,multiple, char *, size)
{
   /* save the group name */
   HTCurSelectGroup = name;

   /* if multiple then all options are actually checkboxes */
   if (multiple)
      HTCurSelectGroupType = F_CHECKBOX_TYPE;
	/* if not multiple then all options are radio buttons */
   else
      HTCurSelectGroupType = F_RADIO_TYPE;

    /*	Length of an option list.
     */
    HTCurSelectGroupSize = size;

   if (TRACE)
       fprintf(stderr,"HText_beginSelect: name=%s type=%d size=%s\n",
	       ((HTCurSelectGroup == NULL) ? 
	       			  "<NULL>" : HTCurSelectGroup),
		HTCurSelectGroupType,
	       ((HTCurSelectGroupSize == NULL) ? 
	       			      "<NULL>" : HTCurSelectGroupSize));
} 

/* we couln't set the value field for the previous option
 * tag so we have to do it now.  Assume that the last anchor
 * was the previous options tag
 */
PUBLIC char * HText_setLastOptionValue ARGS5(HText *, text, char *, value,
						char*, submit_value,
						int, order, BOOLEAN, checked)
{
   char *cp;
   static char * selectedOptionValue=NULL;
   unsigned char *tmp=NULL;
   int number=0, i, j;

   if (text->last_anchor->link_type != INPUT_ANCHOR)
	return NULL;

   if (TRACE)
	fprintf(stderr,
		"Entering HText_setLastOptionValue: value:%s, checked:%s\n",
		value, (checked ? "on" : "off"));

#ifdef NOT_IMPLEMENTED
	/*
	 *	A return at the beginning kills this horribly.
	 *	07-08-94	GAB
	 */
   /* strip return */
   if ((cp=strchr(value,'\n'))!=NULL)
	*cp = '\0';
#endif /* NOT_IMPLEMENTED */

   /* strip end spaces, return is also whitespace. */
   if (*value) {
       cp = &value[strlen(value)-1];
       while ((cp >= value) && (isspace((unsigned char)*cp) ||
       				IsSpecialAttrChar((unsigned char)*cp)))
           cp--;
       *(cp+1) = '\0';
   }

   /* find first non space */
   cp = value;
   while (isspace((unsigned char)*cp) ||
   	  IsSpecialAttrChar((unsigned char)*cp))
       cp++;

   if (HTCurSelectGroupType == F_CHECKBOX_TYPE) {
       StrAllocCopy(text->last_anchor->input_field->value, cp);

       /* put the text on the screen as well */
       HText_appendText(text, cp);

   } else {
	/* create a linked list of option values */

	OptionType * op_ptr = text->last_anchor->input_field->select_list;
	OptionType * new_ptr=0;
	BOOLEAN first_option = FALSE;

	if (!op_ptr) {  /* no option items yet */
	    new_ptr = text->last_anchor->input_field->select_list = 
				(OptionType *) calloc(1,sizeof(OptionType));
	    if (new_ptr == NULL)
	        outofmem(__FILE__, "HText_setLastOptionValue");

	    first_option = TRUE;
	} else {
	    while (op_ptr->next) {
		number++;
		op_ptr=op_ptr->next;
	    }
	    number++;  /* add one more */

	    op_ptr->next = new_ptr =
	    			(OptionType *) calloc(1,sizeof(OptionType));
	    if (new_ptr == NULL)
	        outofmem(__FILE__, "HText_setLastOptionValue");
	}

	new_ptr->name = 0;
	new_ptr->cp_submit_value = 0;
	new_ptr->next = 0;
	StrAllocCopy(new_ptr->name, cp);
	for (i = 0; new_ptr->name[i]; i++) {
	    if (new_ptr->name[i] == 1 ||
	        new_ptr->name[i] == 2 ||
	        (unsigned char)new_ptr->name[i] == 160) {
		new_ptr->name[i] = ' ';
	    }
	}
	if (HTJapanese) {
	    if (new_ptr->name &&
	        (tmp = (unsigned char *)calloc(1, strlen(new_ptr->name)+1))) {
		if (kanji_code == EUC) {
		    TO_EUC((unsigned char *)new_ptr->name, tmp);
		} else if (kanji_code == SJIS) {
		    TO_SJIS((unsigned char *)new_ptr->name, tmp);
		} else {
		    for (i = 0, j = 0; new_ptr->name[i]; i++) {
		        if (new_ptr->name[i] != '\033') {
			    tmp[j++] = new_ptr->name[i];
			}
		    }
		}
		StrAllocCopy(new_ptr->name, (CONST char *)tmp);
		FREE(tmp);
	    }
	}
	StrAllocCopy(new_ptr->cp_submit_value,
	    	     (submit_value ? submit_value : new_ptr->name));

	if (first_option) {
	    StrAllocCopy(selectedOptionValue, new_ptr->name);
	    text->last_anchor->input_field->num_value = 0;
	    text->last_anchor->input_field->value = 
		text->last_anchor->input_field->select_list->name;
	    text->last_anchor->input_field->orig_value = 
		text->last_anchor->input_field->select_list->name;
	    text->last_anchor->input_field->cp_submit_value = 
		text->last_anchor->input_field->select_list->cp_submit_value;
	    text->last_anchor->input_field->orig_submit_value = 
		text->last_anchor->input_field->select_list->cp_submit_value;
	} else {
	    int newlen = strlen(new_ptr->name);
	    int curlen = strlen(selectedOptionValue);
		/* make the selected Option Value as long as the longest
		 * option
		 */
	    if (newlen > curlen)
		StrAllocCat(selectedOptionValue, UNDERSCORES(newlen-curlen));
	}

	if (checked) {
	    int curlen = strlen(new_ptr->name);
	    int newlen = strlen(selectedOptionValue);
		/* set the default option as this one */
	    text->last_anchor->input_field->num_value = number;
	    text->last_anchor->input_field->value = new_ptr->name;
	    text->last_anchor->input_field->orig_value = new_ptr->name;
	    text->last_anchor->input_field->cp_submit_value =
	    			   new_ptr->cp_submit_value;
	    text->last_anchor->input_field->orig_submit_value =
	    			   new_ptr->cp_submit_value;
	    StrAllocCopy(selectedOptionValue, new_ptr->name);
	    if (newlen > curlen)
		StrAllocCat(selectedOptionValue, UNDERSCORES(newlen-curlen));
	}
	     

	/* return the selected Option value to be sent to the screen */
	if (order == LAST_ORDER) {
		/* change the value */
	    text->last_anchor->input_field->size = strlen(selectedOptionValue); 
	    return(selectedOptionValue);
	} else 
	   return(NULL);
   }
	
   if (TRACE)
	fprintf(stderr,"HText_setLastOptionValue: value=%s\n", value);

   return(NULL);
}

/*
 *  Assign a form input anchor
 *  returns the number of charactors to leave blank
 *  so that the input field can fit
 */
PUBLIC int HText_beginInput ARGS2(HText *,text, InputFieldData *,I)
{
	
    TextAnchor * a = (TextAnchor *) calloc(sizeof(*a),1);
    FormInfo * f = (FormInfo *) calloc(sizeof(*f),1); 
    char *cp_option = NULL;
    char *IValue = NULL;
    unsigned char *tmp = NULL;
    int i, j;

    if (TRACE)
	fprintf(stderr,"Entering HText_beginInput\n");

    if (a == NULL || f == NULL)
        outofmem(__FILE__, "HText_beginInput");

    a->start = text->chars + text->last_line->size;
    a->line_pos = text->last_line->size;


    /*
     * If this is a radio button, and it's the first with this name,
     * make sure it's checked by default.  Otherwise, if it's checked,
     * uncheck the default or any preceding radio button with this name
     * that was checked. - FM
     */
    if (I->name && I->type && !strcasecomp(I->type, "radio")) {
        if (!text->last_anchor) {
	    I->checked = TRUE;
	} else {
	    TextAnchor * b = text->first_anchor;
	    int i = 0;
	    while (1) {
	        if (b->link_type == INPUT_ANCHOR &&
		    b->input_field->type == F_RADIO_TYPE &&
                    b->input_field->number == HTFormNumber) {
		    if (!strcmp(b->input_field->name, I->name)) {
			if (I->checked && b->input_field->num_value) {
			    b->input_field->num_value = 0;
			    StrAllocCopy(b->input_field->orig_value, "0");
			    break;
			}
			i++;
		    }
		}
		if (b == text->last_anchor) {
		    if (i == 0)
		       I->checked = TRUE;
		    break;
		}
		b = b->next;
	    }
	}
    }

    if (text->last_anchor) {
        text->last_anchor->next = a;
    } else {
        text->first_anchor = a;
    }
    a->next = 0;
    a->anchor = NULL;
    a->link_type = INPUT_ANCHOR;
    a->show_anchor = YES;

    a->hightext = NULL;
    a->extent = 2;

    a->input_field = f;

    f->select_list = 0;
    f->number = HTFormNumber;
    f->disabled = I->disabled;
    f->no_cache = LYresubmit_forms;

    HTFormFields++;


    /* Set up VALUE */
    if (I->value)
        StrAllocCopy(IValue, I->value);
    if (IValue && HTJapanese) {
	if ((tmp = (unsigned char *)calloc(1, strlen(IValue)+1))) {
	    if (kanji_code == EUC) {
		TO_EUC((unsigned char *)IValue, tmp);
	    } else if (kanji_code == SJIS) {
		TO_SJIS((unsigned char *)IValue, tmp);
	    } else {
		for (i = 0, j = 0; IValue[i]; i++) {
		    if (IValue[i] != '\033') {
		        tmp[j++] = IValue[i];
		    }
		}
	    }
	    StrAllocCopy(IValue, (CONST char *)tmp);
	    FREE(tmp);
	}
    }

    /*
     * special case of option
     */
    /* set the values and let the parsing below do the work */
    if (I->type!=NULL && !strcmp(I->type,"OPTION")) {
	cp_option = I->type;
 	if (HTCurSelectGroupType==F_RADIO_TYPE)
	    I->type = "OPTION_LIST";
	else
	    I->type = "CHECKBOX";
	I->name = HTCurSelectGroup;

	/* The option's size parameter actually gives the length and not
	 *    the width of the list.  Perform the conversion here
	 *    and get rid of the allocated HTCurSelect....
	 * 0 is ok as it means any length (arbitrary decision).
	 */
	if (HTCurSelectGroupSize != NULL) {
	    f->size_l = atoi(HTCurSelectGroupSize);
	    FREE(HTCurSelectGroupSize);
	    HTCurSelectGroupSize = NULL;
	}
    }

    /* set SIZE */
    if (I->size != NULL) {
	f->size = atoi(I->size);
	/* Leave at zero for option lists.
	 */
	if (f->size == 0 && cp_option == NULL) {
	   f->size = 20;  /* default */
	}
    } else {
	f->size = 20;  /* default */
    }

    /* set MAXLENGTH */
    if (I->maxlength != NULL) {
	f->maxlength = atoi(I->maxlength);
    } else {
	f->maxlength = 0;  /* 0 means infinite */
    }

    /* set CHECKED */
    /* num_value is only relevent to check and radio types */
    if (I->checked == TRUE)
 	f->num_value = 1; 
    else
 	f->num_value = 0;

    /* set TYPE */
    if (I->type != NULL) {
	if (!strcasecomp(I->type,"password")) {
	    f->type = F_PASSWORD_TYPE;
	} else if (!strcasecomp(I->type,"checkbox")) {
	    f->type = F_CHECKBOX_TYPE;
	} else if (!strcasecomp(I->type,"radio")) {
	    f->type = F_RADIO_TYPE;
	} else if (!strcasecomp(I->type,"submit")) {
	    f->type = F_SUBMIT_TYPE;
	} else if (!strcasecomp(I->type,"image")) {
	    /*
	     * Ugh, we have a clickable image submit button.
	     * Set the type to submit, and fake an ALT string.
	     * If the user activates it, we'll send a 0,0
	     * coordinate pair, which typically will return
	     * the image's default. FM
	     */
	    f->type = F_SUBMIT_TYPE;
	    StrAllocCopy(f->value, "[IMAGE]-Submit");
	} else if (!strcasecomp(I->type,"reset")) {
	    f->type = F_RESET_TYPE;
	} else if (!strcasecomp(I->type,"OPTION_LIST")) {
	    f->type = F_OPTION_LIST_TYPE;
	} else if (!strcasecomp(I->type,"hidden")) {
	    f->type = F_HIDDEN_TYPE;
	    HTFormFields--;
	    f->size=0;
	} else if (!strcasecomp(I->type,"textarea")) {
	    f->type = F_TEXTAREA_TYPE;
	} else if (!strcasecomp(I->type,"range")) {
	    f->type = F_RANGE_TYPE;
	} else if (!strcasecomp(I->type,"file")) {
	    f->type = F_FILE_TYPE;
	} else {
	    /* Note that TYPE="scribble" defaults to TYPE="text". - FM */
	    f->type = F_TEXT_TYPE; /* default */
	}
    } else {
	f->type = F_TEXT_TYPE;
    }

    /* set NAME */
    if (I->name!=NULL) {
        StrAllocCopy(f->name,I->name);
    } else {
	if (f->type==F_RESET_TYPE || f->type==F_SUBMIT_TYPE) {
	    /* set name to empty string */
	    StrAllocCopy(f->name,"");
	} else {
	    /* error! name must be present */
	    if (TRACE)
		fprintf(stderr,
		  "GridText: No name present in input field; not displaying\n");
	    FREE(a);
	    FREE(f);
	    FREE(IValue);
	    return(0);
	}
    }

    /* set VALUE */
    /* set the value if it exists */
    if (IValue != NULL) {
	/*
	 * OPTION VALUE is not actually the value to be seen but is to
	 *    be sent....
	 */
	if (f->type != F_OPTION_LIST_TYPE && f->type != F_CHECKBOX_TYPE) {
	    StrAllocCopy(f->value, IValue);
	} else {
	    /*
	     * Fill both with the value.  The f->value may be
	     * overwritten in HText_setLastOptionValue....
	     */
	    if (!f->value || strcmp(f->value, "[IMAGE]-Submit"))
		StrAllocCopy(f->value, IValue);
	    StrAllocCopy(f->cp_submit_value, IValue);
	}
    }
    else
        if (!f->value || strcmp(f->value, "[IMAGE]-Submit"))
	    StrAllocCopy(f->value, "");

    /* run checks and fill in neccessary values */
    if (f->type==F_RESET_TYPE) {
	if (IValue!=NULL) {
	    f->size = strlen(IValue);
	} else {
	    StrAllocCopy(f->value, "Reset");
	    f->size = 5;
	}
    } else if (f->type==F_SUBMIT_TYPE) {
        if (f->value && !strcmp(f->value, "[IMAGE]-Submit")) {
	    f->size = strlen(f->value);
	} else if (IValue!=NULL) {
	    f->size = strlen(IValue);
	} else {
	    StrAllocCopy(f->value, "Submit");
	    f->size = 6;
	}
	f->submit_action = NULL;
	StrAllocCopy(f->submit_action, HTFormAction);
	if (HTFormTitle != NULL)
	    StrAllocCopy(f->title, HTFormTitle);
	f->submit_method = HTFormMethod;

    } else if (f->type==F_RADIO_TYPE || f->type==F_CHECKBOX_TYPE ) {
	f->size=3;
	if (IValue == NULL)
	   StrAllocCopy(f->value,"on");

    }
    FREE(IValue); 

    /* set original values */
    if (f->type==F_RADIO_TYPE || f->type==F_CHECKBOX_TYPE ) {
	if (f->num_value)
            StrAllocCopy(f->orig_value, "1");
	else
            StrAllocCopy(f->orig_value, "0");
    } else if (f->type==F_OPTION_LIST_TYPE) {
	f->orig_value=0;
    } else {
        StrAllocCopy(f->orig_value, f->value);
    }

    /* restrict size to maximum allowable size */
    if (f->size > LYcols-10)
	f->size = LYcols-10;  /* maximum */

    /* add this anchor to the anchor list */
    text->last_anchor = a;

    if (TRACE)
	fprintf(stderr,"Input link: name=%s\nvalue=%s\nsize=%d\n",
		 	f->name,
			((f->value != NULL) ? f->value : ""),
			f->size);
	
    /* return the size of the input field */
    return(f->size);
}


PUBLIC void HText_SubmitForm ARGS4(FormInfo *,submit_item, document *,doc,
				   char *,link_name, char *, link_value)
{
   TextAnchor * anchor_ptr = HTMainText->first_anchor;
   int form_number = submit_item->number;
   FormInfo * form_ptr;
   int len, i;
   char *query=NULL;
   char *escaped1=NULL, *escaped2=NULL;
   int first_one=1;
   char * last_textarea_name=0;

   if (submit_item->submit_action)
        len = strlen(submit_item->submit_action) + 2048; /* plus breathing room */
   else
	return;

   /* go through list of anchors and get size first */
   while (1) {
        if (anchor_ptr->link_type == INPUT_ANCHOR) {
   	    if (anchor_ptr->input_field->number == form_number) {

	        form_ptr = anchor_ptr->input_field;
	
	        len += strlen(form_ptr->name)+10;
		/*
		 *	Calculate by the option submit value if present.
		 */
		if (form_ptr->cp_submit_value != NULL)	{
			len += strlen(form_ptr->cp_submit_value) + 10;
		}
		else	{
	        	len += strlen(form_ptr->value)+10;
		}
	        len += 32; /* plus and ampersand + safty net */

	    } else if (anchor_ptr->input_field->number > form_number) {
	        break;
	    }
	}

	if (anchor_ptr == HTMainText->last_anchor)
	    break;

	anchor_ptr = anchor_ptr->next;
   }

   /* get query ready */
   query = (char *)calloc (sizeof(char), len);
   if (query == NULL)
       outofmem(__FILE__, "HText_SubmitForm");

   if (submit_item->submit_method == URL_GET_METHOD) {
       	strcpy (query, submit_item->submit_action);
       	/* Clip out anchor. */
       	strtok (query, "#");
       	/* Clip out old query. */
       	strtok (query, "?");  
	strcat(query,"?");  /* add the question mark */
   } else {
        query[0] = '\0';
	if (submit_item->submit_method != URL_MAIL_METHOD)
	    StrAllocCopy(doc->post_content_type, 
			 "application/x-www-form-urlencoded");
   }

   /* reset anchor->ptr */
   anchor_ptr = HTMainText->first_anchor;
   /* go through list of anchors and assemble URL query */
   while (1) {
        if (anchor_ptr->link_type == INPUT_ANCHOR) {
	    if (anchor_ptr->input_field->number == form_number) {

                form_ptr = anchor_ptr->input_field;

                switch(form_ptr->type) {

	        case F_RESET_TYPE:
		    break;

	        case F_SUBMIT_TYPE:
	        case F_TEXT_SUBMIT_TYPE:
		    /*
		     *  If it has a non-zero length name (e.g., because
		     *  it's really a type="image" that's been converted
		     *  to SUBMIT_TYPE, or one of multiple submit buttons,
		     *  or a single type="text" that's been converted to
		     *  a TEXT_SUBMIT_TYPE), include the name=value pair. - FM
		     */
		    if ((form_ptr->name && *form_ptr->name != '\0' &&
		        !strcmp(form_ptr->name, link_name)) &&
		       (form_ptr->type == F_TEXT_SUBMIT_TYPE ||
		        (form_ptr->value && *form_ptr->value != '\0' &&
		         !strcmp(form_ptr->value, link_value)))) {
		        if (first_one)
                            first_one=FALSE;
                        else
                            strcat(query,"&");

		        escaped1 = HTEscape(form_ptr->name,URL_XALPHAS);

		        /*
		         * Be sure to actually look at the option submit value.
		         */
		        if (form_ptr->cp_submit_value != NULL)	{
			    for (i = 0; form_ptr->cp_submit_value[i]; i++) {
			        if (form_ptr->cp_submit_value[i] == 1 ||
				    form_ptr->cp_submit_value[i] == 2)
				    form_ptr->cp_submit_value[i] = 160;
			    }
		    	    escaped2 = HTEscape(form_ptr->cp_submit_value,
						URL_XALPHAS);
		        }
		        else {
			    for (i = 0; form_ptr->value[i]; i++) {
			        if (form_ptr->value[i] == 1 ||
				    form_ptr->value[i] == 2)
				    form_ptr->value[i] = 160;
			    }
			    escaped2 = HTEscape(form_ptr->value, URL_XALPHAS);
		        }

			if (!strcmp(form_ptr->value, "[IMAGE]-Submit"))
			    /*
			     * It's a clickable image submit button.
			     * Fake a 0,0 coordinate pair, which
			     * typically returns the image's default. - FM
			     */
			    sprintf(&query[strlen(query)],
				    "%s.x=0&%s.y=0", escaped1, escaped1);
			else
			    /*
			     * It's a standard submit button.
			     * Use the name=value pair. = FM
			     */
			    sprintf(&query[strlen(query)],
				    "%s=%s", escaped1, escaped2);
		        FREE(escaped1);
		        FREE(escaped2);
		    }
		    break;

	        case F_RADIO_TYPE:
                case F_CHECKBOX_TYPE:
		    /* only add if selected */
		    if (form_ptr->num_value) {
	                if (first_one)
		            first_one=FALSE;
	                else
		            strcat(query,"&");

		        escaped1 = HTEscape(form_ptr->name,URL_XALPHAS);
			/*
			 *	Be sure to use the submit option value.
			 */
			if (form_ptr->cp_submit_value != NULL) {
			    for (i = 0; form_ptr->cp_submit_value[i]; i++) {
			        if (form_ptr->cp_submit_value[i] == 1 ||
				    form_ptr->cp_submit_value[i] == 2)
				    form_ptr->cp_submit_value[i] = 160;
			    }
			    escaped2 = HTEscape(form_ptr->cp_submit_value,
						URL_XALPHAS);
			}
			else	{
			    for (i = 0; form_ptr->value[i]; i++) {
				if (form_ptr->value[i] == 1 ||
				    form_ptr->value[i] == 2)
				    form_ptr->value[i] = 160;
			    }
		            escaped2 = HTEscape(form_ptr->value, URL_XALPHAS);
			}

                        sprintf(&query[strlen(query)], "%s=%s",
					        escaped1, escaped2);
		        FREE(escaped1);
		        FREE(escaped2);
		    }
		    break;
		
		case F_TEXTAREA_TYPE:

		    for (i = 0; form_ptr->value[i]; i++) {
			if (form_ptr->value[i] == 1 ||
			    form_ptr->value[i] == 2)
			    form_ptr->value[i] = 160;
		    }
                    escaped2 = HTEscape(form_ptr->value,URL_XALPHAS);

		    if (!last_textarea_name || 
			strcmp(last_textarea_name, form_ptr->name))
		      {
			/* names are different so this is the first
			 * textarea or a different one from any before
			 * it.
			 */
		        if (first_one)
                            first_one=FALSE;
                        else
                            strcat(query,"&");
                        escaped1 = HTEscape(form_ptr->name,URL_XALPHAS);
                        sprintf(&query[strlen(query)], "%s=%s",
                                            escaped1, escaped2);
                        FREE(escaped1);
			last_textarea_name = form_ptr->name;
		      }
		    else
		      {
			/* this is a continuation of a previous textarea
			 * add %0a (\n) and the escaped string
			 */
			if (escaped2[0] != '\0')
			    sprintf(&query[strlen(query)], "%%0a%s", escaped2);
		      }
                    FREE(escaped2);
                    break;

                case F_PASSWORD_TYPE:
	        case F_TEXT_TYPE:
		case F_OPTION_LIST_TYPE:
		case F_HIDDEN_TYPE:
	            if (first_one)
		        first_one=FALSE;
	            else
		        strcat(query,"&");
    
		    escaped1 = HTEscape(form_ptr->name,URL_XALPHAS);

		    /*
		     *	Be sure to actually look at the option submit value.
		     */
		    if (form_ptr->cp_submit_value != NULL) {
			for (i = 0; form_ptr->cp_submit_value[i]; i++) {
			    if (form_ptr->cp_submit_value[i] == 1 ||
			        form_ptr->cp_submit_value[i] == 2)
				form_ptr->cp_submit_value[i] = 160;
			}
		    	escaped2 = HTEscape(form_ptr->cp_submit_value,
					    URL_XALPHAS);
		    }
		    else	{
			for (i = 0; form_ptr->value[i]; i++) {
			    if (form_ptr->value[i] == 1 ||
			        form_ptr->value[i] == 2)
				form_ptr->value[i] = 160;
			}
			escaped2 = HTEscape(form_ptr->value, URL_XALPHAS);
		    }

                    sprintf(&query[strlen(query)], "%s=%s",
					    escaped1, escaped2);
		    FREE(escaped1);
		    FREE(escaped2);
		    break;
	        }
	    } else if (anchor_ptr->input_field->number > form_number) {
	        break;
	    }
        }

        if (anchor_ptr == HTMainText->last_anchor)
            break;

	anchor_ptr = anchor_ptr->next;
   }

   if (submit_item->submit_method == URL_MAIL_METHOD) {
	if (strncmp(submit_item->submit_action, "mailto:", 7)) {
	    HTAlert("Malformed mailto form submission!  Cancelled.");
	    FREE(query);
	    return;
	}
	_user_message("submitting %s", submit_item->submit_action);
	if (TRACE) {
	    fprintf(stderr, "\nGridText - mailto_address: %s\n",
	    		    (submit_item->submit_action+7));
	    fprintf(stderr, "GridText - mailto_subject: %s\n",
	    		    ((submit_item->title && *submit_item->title) ?
			      submit_item->title : HText_getTitle()));
	    fprintf(stderr,"GridText - mailto_content: %s\n",query);
	}
	sleep(MessageSecs);
	mailform((submit_item->submit_action+7),
		 ((submit_item->title && *submit_item->title) ?
		   submit_item->title : HText_getTitle()), query);
	FREE(query);
	return;
   } else {
        _statusline("submitting form");
   }
   
   if (submit_item->submit_method == URL_POST_METHOD) {
       StrAllocCopy(doc->post_data, query);
       if (TRACE)
	    fprintf(stderr,"GridText - post_data: %s\n",doc->post_data);
       StrAllocCopy(doc->address, submit_item->submit_action);
       FREE(query);
       return;
   } else { /* GET_METHOD */ 
       StrAllocCopy(doc->address, query);
       free_and_clear(&doc->post_data);
       free_and_clear(&doc->post_content_type);
       FREE(query);
       return;
   }

}

PUBLIC void HText_ResetForm ARGS1(FormInfo *,form)
{
    TextAnchor * anchor_ptr = HTMainText->first_anchor;

    _statusline("resetting form...");

   /* go through list of anchors and reset values */
   while (1) {
        if (anchor_ptr->link_type == INPUT_ANCHOR) {
            if (anchor_ptr->input_field->number == form->number) {

                 if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
                     anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {

		    if (anchor_ptr->input_field->orig_value[0]=='0')
		        anchor_ptr->input_field->num_value = 0;
		    else
		        anchor_ptr->input_field->num_value = 1;
		
		 } else if (anchor_ptr->input_field->type==F_OPTION_LIST_TYPE) {
		    anchor_ptr->input_field->value =
				anchor_ptr->input_field->orig_value;
		    
		    anchor_ptr->input_field->cp_submit_value =
		    		anchor_ptr->input_field->orig_submit_value;

	         } else {
		    StrAllocCopy(anchor_ptr->input_field->value,
					anchor_ptr->input_field->orig_value);
		 }
	     } else if (anchor_ptr->input_field->number > form->number) {
                 break;
	     }

        }

        if (anchor_ptr == HTMainText->last_anchor)
            break;


        anchor_ptr = anchor_ptr->next;
   }


}

PUBLIC void HText_activateRadioButton ARGS1(FormInfo *,form)
{
    TextAnchor * anchor_ptr = HTMainText->first_anchor;
    int form_number = form->number;

    while (1) {
        if (anchor_ptr->link_type == INPUT_ANCHOR &&
                anchor_ptr->input_field->type == F_RADIO_TYPE) {
                    
	    if (anchor_ptr->input_field->number == form_number) {

		    /* if it has the same name and its on */
	         if (!strcmp(anchor_ptr->input_field->name, form->name) &&
		     anchor_ptr->input_field->num_value) {
		    anchor_ptr->input_field->num_value = 0;
		    break;
	         }
	    } else if (anchor_ptr->input_field->number > form_number) {
	            break;
	    }

        }

        if (anchor_ptr == HTMainText->last_anchor)
            break;

        anchor_ptr = anchor_ptr->next;
   }

   form->num_value = 1;
}

PUBLIC void HText_appendMHChar ARGS2(HText *, text, char, c)	{
	/*
	 *	Just return as not supported as of yet.
	 */
	return;
}

static void free_all_texts NOARGS	{
/*
 *	Purpose:	Free all currently loaded HText objects in memory.
 *	Arguments:	void
 *	Return Value:	void
 *	Remarks/Portability/Dependencies/Restrictions:
 *		Usage of this function should really be limited to program
 *			termination.
 *	Revision History:
 *		05-27-94	created Lynx 2-3-1 Garrett Arch Blythe
 */

	if (!loaded_texts)
	    return;

	/*
	 *	Simply loop through the loaded texts list killing them off.
 	 */
	while (loaded_texts && !HTList_isEmpty(loaded_texts)) {
		HText_free((HText *)HTList_removeLastObject(loaded_texts));
	}

	/*
	 *	Get rid of the text list.
 	 */
	if (loaded_texts) {
		HTList_delete(loaded_texts);
	}

	return;
}

/*
**  stub_HTAnchor_address is like HTAnchor_address, but it returns the
**  parent address for child links.  This is only useful for traversal's
**  where one does not want to index a text file N times, once for each
**  of N internal links.  Since the parent link has already been taken,
**  it won't go again, hence the (incorrect) links won't cause problems.
*/

PUBLIC char * stub_HTAnchor_address ARGS1 (HTAnchor *,me)
{
    char *addr = NULL;
    if (me)
	StrAllocCopy (addr, me->parent->address);
    return addr;
}

PUBLIC void HText_setToolbar ARGS1 (HText *, text)
{
    if (text)
        text->toolbar = TRUE;
    return;
}

PUBLIC BOOL HText_hasToolbar ARGS1 (HText *, text)
{
    return ((text && text->toolbar) ? TRUE : FALSE);
}

PUBLIC void HText_setNoCache ARGS1 (HText *, text)
{
    if (text)
        text->no_cache = TRUE;
    return;
}

PUBLIC BOOL HText_hasNoCacheSet ARGS1 (HText *, text)
{
    return ((text && text->no_cache) ? TRUE : FALSE);
}
