/*			General SGML Parser code		SGML.c
**			========================
**
**	This module implements an HTStream object. To parse an
**	SGML file, create this object which is a parser. The object
**	is (currently) created by being passed a DTD structure,
**	and a target HTStructured oject at which to throw the parsed stuff.
**	
**	 6 Feb 93  Binary seraches used. Intreface modified.
*/
#include "HTUtils.h"
#include "tcp.h"		/* For FROMASCII */

#include "SGML.h"
#include "HTJIS.h"

#include <ctype.h>
/*#include <stdio.h> included in HTUtils.h -- FM */
#include "HTChunk.h"

#include "LYLeaks.h"

#define INVALID (-1)

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

PUBLIC BOOL HTJapanese = FALSE;

/*	The State (context) of the parser
**
**	This is passed with each call to make the parser reentrant
**
*/

#define MAX_ATTRIBUTES 24	/* Max number of attributes per element */

	
/*		Element Stack
**		-------------
**	This allows us to return down the stack reselcting styles.
**	As we return, attribute values will be garbage in general.
*/
typedef struct _HTElement HTElement;
struct _HTElement {
	HTElement *	next;	/* Previously nested element or 0 */
	HTTag*		tag;	/* The tag at this level  */
};


/*	Internal Context Data Structure
**	-------------------------------
*/
struct _HTStream {

    CONST HTStreamClass *	isa;		/* inherited from HTStream */
    
    CONST SGML_dtd 		*dtd;
    HTStructuredClass	*actions;	/* target class  */
    HTStructured	*target;	/* target object */

    HTTag 		*current_tag;
    int 		current_attribute_number;
    HTChunk		*string;
    HTElement		*element_stack;
    enum sgml_state { S_text, S_litteral, S_tag, S_tag_gap,
		S_attr, S_attr_gap, S_equals, S_value,
		S_ero, S_cro, S_exclamation, S_comment, S_doctype,
		S_squoted, S_dquoted, S_end, S_entity,
		S_esc, S_doller, S_paren, S_nonascii_text,
		S_junk_tag} state;
#ifdef CALLERDATA
    void *		callerData;
#endif
    BOOL present[MAX_ATTRIBUTES];	/* Flags: attribute is present? */
    char * value[MAX_ATTRIBUTES];	/* malloc'd strings or NULL if none */
} ;


#define PUTC(ch) ((*context->actions->put_character)(context->target, ch))

extern BOOL historical_comments;
extern BOOL minimal_comments;
extern BOOL soft_dquotes;


/*	Handle Attribute
**	----------------
*/
/* PUBLIC CONST char * SGML_default = "";   ?? */

#ifdef __STDC__
PRIVATE void handle_attribute_name(HTStream * context, const char * s)
#else
PRIVATE void handle_attribute_name(context, s)
    HTStream * context;
    char *s;
#endif
{

    HTTag * tag = context->current_tag;
    attr* attributes = tag->attributes;

    int high, low, i, diff;		/* Binary search for attribute name */
    for(low=0, high=tag->number_of_attributes;
    		high > low ;
		diff < 0 ? (low = i+1) : (high = i) )  {
	i = (low + (high-low)/2);
	diff = strcasecomp(attributes[i].name, s);
	if (diff==0) {			/* success: found it */
    	    context->current_attribute_number = i;
	    context->present[i] = YES;
	    FREE(context->value[i]);
	    return;
	} /* if */
	
    } /* for */
    
    if (TRACE)
	fprintf(stderr, "SGML: Unknown attribute %s for tag %s\n",
	    s, context->current_tag->name);
    context->current_attribute_number = INVALID;	/* Invalid */
}


/*	Handle attribute value
**	----------------------
*/
#ifdef __STDC__
PRIVATE void handle_attribute_value(HTStream * context, const char * s)
#else
PRIVATE void handle_attribute_value(context, s)
    HTStream * context;
    char *s;
#endif
{
    if (context->current_attribute_number != INVALID) {
	StrAllocCopy(context->value[context->current_attribute_number], s);
    } else {
        if (TRACE)
	    fprintf(stderr, "SGML: Attribute value %s ignored\n", s);
    }
    context->current_attribute_number = INVALID; /* can't have two assignments! */
}


/*	Handle entity
**	-------------
**
** On entry,
**	s	contains the entity name zero terminated
** Bugs:
**	If the entity name is unknown, the terminator is treated as
**	a printable non-special character in all cases, even if it is '<'
** Bug-fix:
**	Modified SGML_character() so we only come here with terminator
**	as '\0' and check a FoundEntity flag. -- Foteos Macrides
*/

PRIVATE BOOL FoundEntity = FALSE;

#ifdef __STDC__
PRIVATE void handle_entity(HTStream * context, char term)
#else
PRIVATE void handle_entity(context, term)
    HTStream * context;
    char term;
#endif
{

    CONST char ** entities = context->dtd->entity_names;
    CONST char *s = context->string->data;
    int high, low, i, diff;

    FoundEntity = FALSE;
    for(low=0, high = context->dtd->number_of_entities;
    		high > low ;
		diff < 0 ? (low = i+1) : (high = i))   {  /* Binary serach */
	i = (low + (high-low)/2);
	diff = strcmp(entities[i], s);	/* Case sensitive! */
	if (diff==0) {			/* success: found it */
	    (*context->actions->put_entity)(context->target, i);
	    FoundEntity = TRUE;
	    return;
	}
    }
    /* If entity string not found, display as text */
    if (TRACE)
	fprintf(stderr, "SGML: Unknown entity %s\n", s); 
    PUTC('&');
    {
	CONST char *p;
	for (p=s; *p; p++) {
	    PUTC(*p);
	}
    }
    if (term != '\0')
        PUTC(term);
}


/*	End element
**	-----------
*/
#ifdef __STDC__
PRIVATE void end_element(HTStream * context, HTTag * old_tag)
#else
PRIVATE void end_element(context, old_tag)
    HTTag * old_tag;
    HTStream * context;
#endif
{
    if (TRACE)
        fprintf(stderr, "SGML: End   </%s>\n", old_tag->name);
    if (old_tag->contents == SGML_EMPTY) {
        if (TRACE)
	    fprintf(stderr, "SGML: Illegal end tag </%s> found.\n",
	    		    old_tag->name);
	return;
    }
#ifdef WIND_DOWN_STACK
    while (context->element_stack) { /* Loop is error path only */
#else
    if (context->element_stack) { /* Substitute and remove one stack element */ 
#endif /* WIND_DOWN_STACK */
	HTElement * N = context->element_stack;
	HTTag * t = N->tag;
	
	if (old_tag != t) {		/* Mismatch: syntax error */
	    if (context->element_stack->next) {	/* This is not the last level */
		if (TRACE) fprintf(stderr,
	    	"SGML: Found </%s> when expecting </%s>. </%s> assumed.\n",
		    old_tag->name, t->name, t->name);
	    } else {			/* last level */
		if (TRACE) fprintf(stderr,
	            "SGML: Found </%s> when expecting </%s>. </%s> Ignored.\n",
		    old_tag->name, t->name, old_tag->name);
	        return;			/* Ignore */
	    }
	}
	
	context->element_stack = N->next;		/* Remove from stack */
	FREE(N);
	(*context->actions->end_element)(context->target,
		 t - context->dtd->tags);
#ifdef WIND_DOWN_STACK
	if (old_tag == t)
	    return;  /* Correct sequence */
#else
	return;
#endif /* WIND_DOWN_STACK */
	
	/* Syntax error path only */
	
    }
    if (TRACE)
        fprintf(stderr, "SGML: Extra end tag </%s> found and ignored.\n",
			old_tag->name);
}


/*	Start a element
*/
#ifdef __STDC__
PRIVATE void start_element(HTStream * context)
#else
PRIVATE void start_element(context)
    HTStream * context;
#endif
{
    HTTag * new_tag = context->current_tag;
    
    if (TRACE)
        fprintf(stderr, "SGML: Start <%s>\n", new_tag->name);
    (*context->actions->start_element)(
    	context->target,
	new_tag - context->dtd->tags,
	context->present,
	(CONST char**) context->value);  /* coerce type for think c */
    if (new_tag->contents != SGML_EMPTY) {		/* i.e. tag not empty */
	HTElement * N = (HTElement *)malloc(sizeof(HTElement));
        if (N == NULL)
	    outofmem(__FILE__, "start_element");
	N->next = context->element_stack;
	N->tag = new_tag;
	context->element_stack = N;
    }
}


/*		Find Tag in DTD tag list
**		------------------------
**
** On entry,
**	dtd	points to dtd structire including valid tag list
**	string	points to name of tag in question
**
** On exit,
**	returns:
**		NULL		tag not found
**		else		address of tag structure in dtd
*/
PUBLIC HTTag * SGMLFindTag ARGS2(CONST SGML_dtd*, dtd, CONST char *, string)
{
    int high, low, i, diff;
    for(low=0, high=dtd->number_of_tags;
    		high > low ;
		diff < 0 ? (low = i+1) : (high = i))   {  /* Binary serach */
	i = (low + (high-low)/2);
	diff = strcasecomp(dtd->tags[i].name, string);	/* Case insensitive */
	if (diff==0) {			/* success: found it */
	    return &dtd->tags[i];
	}
    }
    return NULL;
}

/*________________________________________________________________________
**			Public Methods
*/


/*	Could check that we are back to bottom of stack! @@  */

PUBLIC void SGML_free  ARGS1(HTStream *, context)
{
    int i;

    (*context->actions->_free)(context->target);
    HTChunkFree(context->string);

    /* free strings */
    for (i=0; i < MAX_ATTRIBUTES; i++) 
	FREE(context->value[i]);

    FREE(context);
}

PUBLIC void SGML_abort  ARGS2(HTStream *, context, HTError, e)
{
    (*context->actions->_abort)(context->target, e);
    HTChunkFree(context->string);
    FREE(context);
}


/*	Read and write user callback handle
**	-----------------------------------
**
**   The callbacks from the SGML parser have an SGML context parameter.
**   These calls allow the caller to associate his own context with a
**   particular SGML context.
*/

#ifdef CALLERDATA		  
PUBLIC void* SGML_callerData ARGS1(HTStream *, context)
{
    return context->callerData;
}

PUBLIC void SGML_setCallerData ARGS2(HTStream *, context, void*, data)
{
    context->callerData = data;
}
#endif

PUBLIC void SGML_character ARGS2(HTStream *, context, char,c)

{
    CONST SGML_dtd *dtd	=	context->dtd;
    HTChunk	*string = 	context->string;
    static BOOLEAN lead_exclamation = FALSE;
    static BOOLEAN first_dash = FALSE;
    static BOOLEAN end_comment = FALSE;
    static BOOLEAN doctype_bracket = FALSE;
    CONST char * EntityName;
    extern int current_char_set;
    extern char *LYchar_set_names[];
    extern CONST char * HTMLGetEntityName PARAMS((int i));
    extern BOOLEAN HTUseRawLatin;

top:
    if (!HTJapanese &&
        (((unsigned char)c < 32 &&
	  c != 9 && c != 10 && c != 13) ||
	 c == 127))
        return;	/* Ignore invalid ISO 646 7-bit control characters. */
    if (!HTJapanese &&
        (unsigned char)c > 127 && (unsigned char)c < 160)
        return;	/* Ignore 8-bit control characters. */
    switch(context->state) {
    case S_text:
	if (HTJapanese && c == '\033') {
	    context->state = S_esc;
	    PUTC(c);
	    break;
	}
	if (c=='&' && (!context->element_stack || (
	    		 context->element_stack->tag  &&
	    		 ( context->element_stack->tag->contents == SGML_MIXED
			   || context->element_stack->tag->contents ==
			      				 SGML_RCDATA)
			))) {
	    /*	Setting up for entity, without the leading '&'
	     */
	    string->size = 0;
	    context->state = S_ero;
	    
	} else if (c=='<') {
	    string->size = 0;
	    context->state = (context->element_stack &&
	    		context->element_stack->tag  &&
	    		context->element_stack->tag->contents == SGML_LITTERAL) ?
	    			S_litteral : S_tag;
	} else if (!HTJapanese && (unsigned char)c == 160) {
            PUTC(1);	/* Convert nbsp to Lynx special character. */
	} else if ((unsigned char)c > 160 && !HTUseRawLatin && !HTJapanese &&
		   strncmp(LYchar_set_names[current_char_set],
		   	   "ISO Latin 1", 11)) {
	    int i;
	    int value;

	    string->size = 0;
	    value = (int)((unsigned char)c - 160);
	    EntityName = HTMLGetEntityName(value);
	    for (i = 0; EntityName[i]; i++)
	        HTChunkPutc(string, EntityName[i]);
	    HTChunkTerminate(string);
	    handle_entity(context, '\0');
	    if (!FoundEntity)
	        PUTC(';');
	} else {
	    PUTC(c);
	}
	break;

/*	In litteral mode, waits only for specific end tag!
**	Only for compatibility with old servers.
*/
    case S_litteral :
	HTChunkPutc(string, c);
	if ( TOUPPER(c) != ((string->size ==1) ? '/'
		: context->element_stack->tag->name[string->size-2])) {
	    int i;
	    
	    /*	If complete match, end litteral */
	    if ((c=='>') &&
	        (!context->element_stack->tag->name[string->size-2])) {
		end_element(context, context->element_stack->tag);
		string->size = 0;
		context->current_attribute_number = INVALID;
		context->state = S_text;
		break;
	    }		/* If Mismatch: recover string. */
	    PUTC( '<');
	    for (i=0; i<string->size; i++)	/* recover */
	       PUTC(string->data[i]);
	    context->state = S_text;	
	}
	
        break;

/*	Character reference or Entity
*/
   case S_ero:
   	if (c=='#') {
	    context->state = S_cro;  /*   &# is Char Ref Open */ 
	    break;
	}
	context->state = S_entity;    /* Fall through! */
	
/*	Handle Entities
*/
    case S_entity:
	if (isalnum(c)) {
	    HTChunkPutc(string, c);
	} else {
	    HTChunkTerminate(string);
	    handle_entity(context, '\0');
	    context->state = S_text;
	    if (!FoundEntity && c != '<' && c != '&' &&
	    			c != '>' && c != '"')
	        PUTC(c);
	    if (c == '<' || c == '&' || c == '>' && c == '"')
	        goto top;
	}
	break;

/*	Character reference
*/
    case S_cro:
	if (isalnum(c))
	    HTChunkPutc(string, c);	/* accumulate a character NUMBER */
	else {
	    CONST char *p;
	    int value;
	    HTChunkTerminate(string);
	    if (sscanf(string->data, "%d", &value) == 1) {
	        if (value > 255 ||
		    (!HTJapanese &&
		     value < 32 &&
		     value != 9 && value != 10 && value != 13) ||
		    (!HTJapanese &&
		     value > 126 && value < 160)) {	/* Illegal value. */
		    PUTC('&');
		    PUTC('#');
		    for (p=string->data; *p; p++) {
		        PUTC(*p);
		    }
		    context->state = S_text;
		    goto top;
		} else if (value == 160) {
		    if (!HTJapanese) {
		        PUTC(1);   /* Convert nbsp to Lynx special character. */
		    } else {
		        PUTC(FROMASCII((char)value));
		    }
		} else if (value < 160 || HTUseRawLatin || HTJapanese) {
		    /* No conversion needed. */
	            PUTC(FROMASCII((char)value));
		} else {	    /* Convert and handle as named entity */
		    int i;
		    string->size = 0;
		    value -= 160;
		    EntityName = HTMLGetEntityName(value);
		    for (i = 0; EntityName[i]; i++)
		        HTChunkPutc(string, EntityName[i]);
		    HTChunkTerminate(string);
		    handle_entity(context, '\0');
		    if (!FoundEntity && c != '<' && c != '&')
		        PUTC(c);
		}
		context->state = S_text;
		if (c == '<' || c == '&' || c == '>' && c == '"')
		    goto top;
	    } else {
	        PUTC('&');
	        PUTC('#');
		for (p=string->data; *p; p++) {
		    PUTC(*p);
		}
		context->state = S_text;
		goto top;
	    }
	}
	break;

/*		Tag
*/	    
    case S_tag:					/* new tag */
	if (isalnum(c))
	    HTChunkPutc(string, c);
        else if (c == '!' && !string->size) {	/* <! */
	    context->state = S_exclamation;
	    lead_exclamation = TRUE;
	    doctype_bracket = FALSE;
	    HTChunkPutc(string, c);
	    break;
	} else {				/* End of tag name */
	    HTTag * t;
	    if (c=='/') {
		if (TRACE)
		    if (string->size!=0)
		        fprintf(stderr,"SGML: `<%s/' found!\n", string->data);
		context->state = S_end;
		break;
	    }
	    HTChunkTerminate(string) ;

	    t = SGMLFindTag(dtd, string->data);
	    if (!t) {
		if(TRACE)
		    fprintf(stderr, "SGML: *** Unknown element %s\n",
		    		    string->data);
		context->state = (c=='>') ? S_text : S_junk_tag;
		break;
	    }
	    context->current_tag = t;
	    
	    /*  Clear out attributes
	    */
	    {
	        int i;
	        for (i=0; i< context->current_tag->number_of_attributes; i++)
	    	    context->present[i] = NO;
	    }
	    string->size = 0;
	    context->current_attribute_number = INVALID;
	    
	    if (c=='>') {
		if (context->current_tag->name)
		    start_element(context);
		context->state = S_text;
	    } else {
	        context->state = S_tag_gap;
	    }
	}
	break;

    case S_exclamation:
        if (lead_exclamation && c == '-') {
	    lead_exclamation = FALSE;
	    first_dash = TRUE;
	    HTChunkPutc(string, c);
	    break;
	}
	if (first_dash && c == '-') {
	    lead_exclamation = FALSE;
	    first_dash = FALSE;
	    end_comment = FALSE;
	    HTChunkPutc(string, c);
	    context->state = S_comment;
	    break;
	}
	lead_exclamation = FALSE;
	first_dash = FALSE;
	if (WHITE(c) && string->size == 8 &&
	    !strncasecomp(string->data, "!DOCTYPE", 8)) {
	    HTChunkPutc(string, c);
	    doctype_bracket = FALSE;
	    context->state = S_doctype;
	    break;
	}
	if (c == '>') {
	    HTChunkTerminate(string);
	    if(TRACE)
	        fprintf(stderr, "SGML: Identifier <%s>\n", string->data);
	    /* Could call function here, someday. - FM */
	    string->size = 0;
	    context->state = S_text;
	    break;
	}
	HTChunkPutc(string, c);
	break;

    case S_comment:
        if (historical_comments) { /* Any '>' terminates. */
	    if (c == '>') {
	        HTChunkTerminate(string);
		if(TRACE)
		    fprintf(stderr, "SGML: Comment <%s>\n", string->data);
		string->size = 0;
		end_comment = FALSE;
		first_dash = FALSE;
		context->state = S_text;
		break;
	    }
	    HTChunkPutc(string, c);
	    break;
	}
        if (!first_dash && c == '-') {
	    HTChunkPutc(string, c);
	    first_dash = TRUE;
	    break;
	}
	if (first_dash && c == '-') {
	    HTChunkPutc(string, c);
	    first_dash = FALSE;
	    if (!end_comment)
	        end_comment = TRUE;
	    else if (!minimal_comments)
	        /* validly treat '--' pairs as successive comments */
	        end_comment = FALSE;
	    break;
	}
	if (end_comment && c == '>') {
	    HTChunkTerminate(string);
	    if(TRACE)
	        fprintf(stderr, "SGML: Comment <%s>\n", string->data);
	    /* Could call function here, someday. - FM */
	    string->size = 0;
	    end_comment = FALSE;
	    first_dash = FALSE;
	    context->state = S_text;
	    break;
	}
	first_dash = FALSE;
	if (end_comment && !isspace(c))
	    end_comment = FALSE;
	HTChunkPutc(string, c);
	break;

    case S_doctype:
        if (doctype_bracket) {
	    HTChunkPutc(string, c);
	    if (c == ']')
	        doctype_bracket = FALSE;
	    break;
	}
	if (c == '[' && WHITE(string->data[string->size - 1])) {
	    HTChunkPutc(string, c);
	    doctype_bracket = TRUE;
	    break;
	}
	if (c == '>') {
	    HTChunkTerminate(string);
	    if(TRACE)
	        fprintf(stderr, "SGML: Identifier <%s>\n", string->data);
	    /* Could call function here, someday. - FM */
	    string->size = 0;
	    context->state = S_text;
	    break;
	}
	HTChunkPutc(string, c);
	break;

    case S_tag_gap:		/* Expecting attribute or > */
	if (WHITE(c))
	    break;		/* Gap between attributes */
	if (c=='>') {		/* End of tag */
	    if (context->current_tag->name)
	    	start_element(context);
	    context->state = S_text;
	    break;
	}
	HTChunkPutc(string, c);
	context->state = S_attr;		/* Get attribute */
	break;
	
   				/* accumulating value */
    case S_attr:
	if (WHITE(c) || (c=='>') || (c=='=')) {		/* End of word */
	    HTChunkTerminate(string);
	    handle_attribute_name(context, string->data);
	    string->size = 0;
	    if (c=='>') {		/* End of tag */
		if (context->current_tag->name)
		    start_element(context);
		context->state = S_text;
		break;
	    }
	    context->state = (c=='=' ?  S_equals: S_attr_gap);
	} else {
	    HTChunkPutc(string, c);
	}
	break;
		
    case S_attr_gap:		/* Expecting attribute or = or > */
	if (WHITE(c))
	    break;		/* Gap after attribute */
	if (c=='>') {		/* End of tag */
	    if (context->current_tag->name)
	        start_element(context);
	    context->state = S_text;
	    break;
	} else if (c=='=') {
	    context->state = S_equals;
	    break;
	}
	HTChunkPutc(string, c);
	context->state = S_attr;		/* Get next attribute */
	break;
	
    case S_equals:		/* After attr = */ 
	if (WHITE(c))
	    break;		/* Before attribute value */
	if (c=='>') {		/* End of tag */
	    if (TRACE)
	        fprintf(stderr, "SGML: found = but no value\n");
	    if (context->current_tag->name)
	        start_element(context);
	    context->state = S_text;
	    break;
	    
	} else if (c=='\'') {
	    context->state = S_squoted;
	    break;

	} else if (c=='"') {
	    context->state = S_dquoted;
	    break;
	}
	HTChunkPutc(string, c);
	context->state = S_value;
	break;
	
    case S_value:
	if (WHITE(c) || (c=='>')) {		/* End of word */
	    HTChunkTerminate(string) ;
	    handle_attribute_value(context, string->data);
	    string->size = 0;
	    if (c=='>') {		/* End of tag */
		if (context->current_tag->name)
		    start_element(context);
		context->state = S_text;
		break;
	    }
	    else context->state = S_tag_gap;
	} else {
	    HTChunkPutc(string, c);
	}
	break;
		
    case S_squoted:		/* Quoted attribute value */
	if (c=='\'') {		/* End of attribute value */
	    HTChunkTerminate(string) ;
	    handle_attribute_value(context, string->data);
	    string->size = 0;
	    context->state = S_tag_gap;
	} else {
	    HTChunkPutc(string, c);
	}
	break;
	
    case S_dquoted:		/* Quoted attribute value */
	if (c=='"' ||		/* Valid end of attribute value */
	    (soft_dquotes &&	/*  If emulating old Netscape bug, treat '>' */
	     c=='>')) {		/*  as a co-terminator of dquoted and tag    */
	    HTChunkTerminate(string) ;
	    handle_attribute_value(context, string->data);
	    string->size = 0;
	    context->state = S_tag_gap;
	    if (c=='>')		/* We emulated the Netscape bug, so we go  */
	        goto top;	/* back and treat it as the tag terminator */
	} else {
	    HTChunkPutc(string, c);
	}
	break;
	
    case S_end:					/* </ */
	if (isalnum(c))
	    HTChunkPutc(string, c);
	else {				/* End of end tag name */
	    HTTag * t=0;
	    HTChunkTerminate(string) ;
	    if (!*string->data)	{	/* Empty end tag */
		if(context->element_stack)
	            t = context->element_stack->tag;
	    } else {
		t = SGMLFindTag(dtd, string->data);
	    }
	    if (!t) {
		if(TRACE)
		    fprintf(stderr, "Unknown end tag </%s>\n", string->data); 
	    } else {
		BOOL tag_OK = (c == '>' || WHITE(c));
	        context->current_tag = t;
		if (tag_OK &&
		    (!strcasecomp(string->data, "DD") ||
		     !strcasecomp(string->data, "DT") ||
		     !strcasecomp(string->data, "LI") ||
		     !strcasecomp(string->data, "TD") ||
		     !strcasecomp(string->data, "TH") ||
		     !strcasecomp(string->data, "TR"))) {
		    /*
		     *  Don't treat these end tags as invalid,
		     *  nor act on it. - FM
		     */
		    if (TRACE)
		        fprintf(stderr,
				"SGML: `</%s%c' found!  Ignoring it.\n",
				string->data, c);
		} else if (tag_OK &&
			   !strcasecomp(string->data, "P")) {
		    /*
		     *  Treat a P end tag like a P start tag (Ugh,
		     *  what a hack! 8-). - FM
		     */
		    if (TRACE)
		        fprintf(stderr,
				"SGML: `</%s%c' found!  Treating as '<%s%c'.\n",
				string->data, c, string->data, c);
		    {
		        int i;
			for (i=0;
			     i< context->current_tag->number_of_attributes; i++)
			    context->present[i] = NO;
		    }
		    string->size = 0;
		    context->current_attribute_number = INVALID;
		    if (context->current_tag->name)
			start_element(context);
		    context->state = S_text;
		    break;
		} else {
		    /*
		     *  Handle all other end tags normally. - FM
		     */
		    end_element( context, context->current_tag);
		}
	    }

	    string->size = 0;
	    context->current_attribute_number = INVALID;
	    if (c != '>') {
		if (TRACE && !WHITE(c))
		    fprintf(stderr,"SGML: `</%s%c' found!\n", string->data, c);
		context->state = S_junk_tag;
	    } else {
	        context->state = S_text;
	    }
	}
	break;


    case S_esc:
	if (c=='$') {
	    context->state = S_doller;
	} else if (c=='(') {
	    context->state = S_paren;
	} else {
	    context->state = S_text;
	}
	PUTC(c);
	break;

    case S_doller:
	if (c=='@' || c=='B') {
	    context->state = S_nonascii_text;
	} else {
	    context->state = S_text;
	}
	PUTC(c);
	break;

    case S_paren:
	if (c=='B' || c=='J')  {
	    context->state = S_text;
	} else {
	    context->state = S_text;
	}
	PUTC(c);
	break;

    case S_nonascii_text:
	if (c=='\033') {
	    context->state = S_esc;
	    PUTC(c);
	} else {
	    PUTC(c);
	}
	break;


    case S_junk_tag:
	if (c=='>') {
	    context->state = S_text;
	}
	
    } /* switch on context->state */

}  /* SGML_character */


PUBLIC void SGML_string ARGS2(HTStream *, context, CONST char*, str)
{
    CONST char *p;
    for(p=str; *p; p++)
        SGML_character(context, *p);
}


PUBLIC void SGML_write ARGS3(HTStream *, context, CONST char*, str, int, l)
{
    CONST char *p;
    CONST char *e = str+l;
    for(p=str; p<e; p++)
        SGML_character(context, *p);
}

/*_______________________________________________________________________
*/

/*	Structured Object Class
**	-----------------------
*/
PUBLIC CONST HTStreamClass SGMLParser = 
{		
	"SGMLParser",
	SGML_free,
	SGML_abort,
	SGML_character, 
	SGML_string,
	SGML_write,
}; 

/*	Create SGML Engine
**	------------------
**
** On entry,
**	dtd		represents the DTD, along with
**	actions		is the sink for the data as a set of routines.
**
*/

PUBLIC HTStream* SGML_new  ARGS2(
	CONST SGML_dtd *,	dtd,
	HTStructured *,		target)
{
    int i;
    HTStream* context = (HTStream *) malloc(sizeof(*context));
    if (!context)
        outofmem(__FILE__, "SGML_begin");

    context->isa = &SGMLParser;
    context->string = HTChunkCreate(128);	/* Grow by this much */
    context->dtd = dtd;
    context->target = target;
    context->actions = (HTStructuredClass*)(((HTStream*)target)->isa);
    					/* Ugh: no OO */
    context->state = S_text;
    context->element_stack = 0;			/* empty */
#ifdef CALLERDATA		  
    context->callerData = (void*) callerData;
#endif    
    for(i=0; i<MAX_ATTRIBUTES; i++) context->value[i] = 0;

    return context;
}

/*		Japanese character conversion functions
**		=======================================
**
**	Added 24-Mar-96 by FM, based on:
**
////////////////////////////////////////////////////////////////////////
Copyright (c) 1993 Electrotechnical Laboratry (ETL)

Permission to use, copy, modify, and distribute this material 
for any purpose and without fee is hereby granted, provided 
that the above copyright notice and this permission notice 
appear in all copies, and that the name of ETL not be 
used in advertising or publicity pertaining to this 
material without the specific, prior written permission 
of an authorized representative of ETL.
ETL MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY 
OF THIS MATERIAL FOR ANY PURPOSE.  IT IS PROVIDED "AS IS", 
WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
/////////////////////////////////////////////////////////////////////////
Content-Type:	program/C; charset=US-ASCII
Program:	SJIS.c
Author:		Yutaka Sato <ysato@etl.go.jp>
Description:
History:
	930923	extracted from codeconv.c of cosmos
///////////////////////////////////////////////////////////////////////
*/

PUBLIC int TREAT_SJIS = 1;

PUBLIC unsigned char * SJIS_TO_JIS1 ARGS3(
	register unsigned char,		HI,
	register unsigned char,		LO,
	register unsigned char *,	JCODE)
{
    HI -= (HI <= 0x9F) ? 0x71 : 0xB1;
    HI = (HI << 1) + 1;
    if (0x7F < LO)
	LO--;
    if (0x9E <= LO) {
	LO -= 0x7D;
	HI++;
    } else {
        LO -= 0x1F;
    }
    JCODE[0] = HI;
    JCODE[1] = LO;
    return JCODE;
}
PUBLIC unsigned char * JIS_TO_SJIS1 ARGS3(
	register unsigned char,		HI,
	register unsigned char,		LO,
	register unsigned char *,	SJCODE)
{
    if (HI & 1)
	LO += 0x1F;
    else
	LO += 0x7D;
    if (0x7F <= LO)
	LO++;

    HI = ((HI - 0x21) >> 1) + 0x81;
    if (0x9F < HI)
	HI += 0x40;
    SJCODE[0] = HI;
    SJCODE[1] = LO;
    return SJCODE;
}

PUBLIC unsigned char * SJIS_TO_EUC1 ARGS3(
	unsigned char,		HI,
	unsigned char,		LO,
	unsigned char *,	EUC)
{
    SJIS_TO_JIS1(HI, LO, EUC);
    EUC[0] |= 0x80;
    EUC[1] |= 0x80;
    return EUC;
}

PUBLIC unsigned char * SJIS_TO_EUC ARGS2(
	unsigned char *,	src,
	unsigned char *,	dst)
{
    register unsigned char hi, lo, *sp, *dp;
    register int in_sjis = 0;

    for (sp = src, dp = dst; (0 != (hi = sp[0]));) {
	lo = sp[1];
	if (TREAT_SJIS && IS_SJIS(hi, lo, in_sjis)) {
	    SJIS_TO_JIS1(hi,lo,dp);
	    dp[0] |= 0x80;
	    dp[1] |= 0x80;
	    dp += 2;
	    sp += 2;
	} else {
	    *dp++ = *sp++;
	}
    }
    *dp = 0;
    return dst;
}

PUBLIC unsigned char * EUC_TO_SJIS ARGS2(
	unsigned char *,	src,
	unsigned char *,	dst)
{
    register unsigned char *sp, *dp;

    for (sp = src, dp = dst; *sp;) {
	if (*sp & 0x80) {
	    if (sp[1] && (sp[1] & 0x80)) {
		JIS_TO_SJIS1(sp[0]&0x7F, sp[1]&0x7F, dp);
		dp += 2;
		sp += 2;
	    } else {
	        sp++;
	    }
	} else {
	    *dp++ = *sp++;
	}
    }
    *dp = 0;
    return dst;
}

PUBLIC unsigned char * EUC_TO_JIS ARGS4(
	unsigned char *,	src,
	unsigned char *,	dst,
	CONST char *,		toK,
	CONST char *,		toA)
{
    register unsigned char kana_mode = 0;
    register unsigned char cch;
    register unsigned char *sp = src;
    register unsigned char *dp = dst;
    register int i;

    while (0 != (cch = *sp++)) {
	if (cch & 0x80) {
	    if (!kana_mode) {
		kana_mode = ~kana_mode;
		for (i = 0; toK[i]; i++) {
		    *dp++ = (unsigned char)toK[i];
		}
	    }
	    if (*sp & 0x80) {
		*dp++ = cch & ~0x80;
		*dp++ = *sp++ & ~0x80;
	    }
	} else {
	    if (kana_mode) {
		kana_mode = ~kana_mode;
		for (i = 0; toA[i]; i++) {
		    *dp++ = (unsigned char)toA[i];
		    *dp = '\0';
		}
	    }
	    *dp++ = cch;
	}
    }
    if (kana_mode) {
	for (i = 0; toA[i]; i++) {
	    *dp++ = (unsigned char)toA[i];
	}
    }

    if (dp)
        *dp = 0;
    return dst;
}

PUBLIC unsigned char * TO_EUC ARGS2(
	unsigned char *,	jis,
	unsigned char *,	euc)
{
    register unsigned char *s, *d, c, jis_stat;
    register to1B, to2B;
    register int in_sjis = 0;

    s = jis;
    d = euc;
    jis_stat = 0;
    to2B = TO_2BCODE;
    to1B = TO_1BCODE;

    while (0 != (c = *s++)) {
	if (c == ESC) {
	    if (*s == to2B) {
		if ((s[1] == 'B') || (s[1] == '@')) {
		    jis_stat = 0x80;
		    s += 2;
		    continue;
		}
	    } else {
		if (*s == to1B) {
		    if ((s[1]=='B') || (s[1]=='J') || (s[1]=='H')) {
			jis_stat = 0;
			s += 2;
			continue;
		    }
		}
	    }
	}
	if (IS_SJIS(c,*s,in_sjis)) {
	    SJIS_TO_EUC1(c, *s, d);
	    d += 2;
	    s++;
	} else {
	    if (jis_stat && (0x20 < c)) {
		*d++ = jis_stat | c;
	    } else {
	        *d++ = c;
	    }
	}
    }
    *d = 0;
    return euc;
}

PUBLIC void TO_SJIS ARGS2(
	unsigned char *,	any,
	unsigned char *,	sjis)
{
    unsigned char *euc;

    if (!any || !sjis)
       return;

    euc = (unsigned char*)malloc(strlen((CONST char *)any)+1);
    if (euc == NULL)
	outofmem(__FILE__, "TO_SJIS");

    TO_EUC(any, euc);
    EUC_TO_SJIS(euc, sjis);
    FREE(euc);
}

PUBLIC void TO_JIS ARGS2(
	unsigned char *,	any,
	unsigned char *,	jis)
{
    unsigned char *euc;

    if (!any || !jis)
       return;

    euc = (unsigned char*)malloc(strlen((CONST char *)any)+1);
    if (euc == NULL)
	outofmem(__FILE__, "TO_JIS");

    TO_EUC(any, euc);
    EUC_TO_JIS(euc, jis, TO_KANJI, TO_ASCII);
    FREE(euc);
}
