/*			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 "../config.h"
#include "SGML.h"

#include <ctype.h>
#include <stdio.h>
#include "HTUtils.h"
#include "HTChunk.h"
#include "../libnut/str-tools.h"
#ifndef DISABLE_TRACE
extern int www2Trace;
#endif

#define INVALID (-1)

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

#define MAX_ATTRIBUTES 20	/* 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 {
    WWW_CONST HTStreamClass *isa;		/* inherited from HTStream */
    
    WWW_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_squoted, S_dquoted, S_end, S_entity, 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))


/*	Handle Attribute
**	----------------
*/

PRIVATE void handle_attribute_name(HTStream *context, char * s)
{
    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 = my_strcasecmp(attributes[i].name, s);
	if (diff == 0) {			/* success: found it */
    	    context->current_attribute_number = i;
	    context->present[i] = YES;
	    if (context->value[i]) {
		free(context->value[i]);
		context->value[i] = NULL;
	    }
	    return;
	}
    }
    
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "SGML: Unknown attribute %s for tag %s\n",
	        s, context->current_tag->name);
#endif
    context->current_attribute_number = INVALID;	/* Invalid */
}


/*	Handle attribute value
**	----------------------
*/
PRIVATE void handle_attribute_value(HTStream *context, char *s)
{
    if (context->current_attribute_number != INVALID) {
	StrAllocCopy(context->value[context->current_attribute_number], s);
    } else {
#ifndef DISABLE_TRACE
        if (www2Trace)
	    fprintf(stderr, "SGML: Attribute value %s ignored\n", s);
#endif
    }
    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 '<'
*/
PRIVATE void handle_entity(HTStream *context, char term)
{
    WWW_CONST char **entities = context->dtd->entity_names;
    WWW_CONST char *s = context->string->data;
    int high, low, i, diff;

    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);
	    return;
	}
    }
    /* If entity string not found, display as text */
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "SGML: Unknown entity %s\n", s); 
#endif
    PUTC('&');
    {
        WWW_CONST char *p;

	for (p = s; *p; p++)
	    PUTC(*p);
    }
    PUTC(term);
}


/*	End element
**	-----------
*/
PRIVATE void end_element(HTStream *context, HTTag *old_tag)
{
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "SGML: End   </%s>\n", old_tag->name);
#endif
    if (old_tag->contents == SGML_EMPTY) {
#ifndef DISABLE_TRACE
        if (www2Trace)
	    fprintf(stderr,"SGML: Illegal end tag </%s> found.\n",
		    old_tag->name);
#endif
	return;
    }
    while (context->element_stack) 	{/* Loop is error path only */
	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 */
#ifndef DISABLE_TRACE
		if (www2Trace)
		    fprintf(stderr,
	    	     "SGML: Found </%s> when expecting </%s>. </%s> assumed.\n",
		      old_tag->name, t->name, t->name);
#endif
	    } else {			/* last level */
#ifndef DISABLE_TRACE
		if (www2Trace) fprintf(stderr,
	            "SGML: Found </%s> when expecting </%s>. </%s> Ignored.\n",
		    old_tag->name, t->name, old_tag->name);
#endif
	        return;			/* Ignore */
	    }
	}
	
	context->element_stack = N->next;		/* Remove from stack */
	free(N);
	(*context->actions->end_element)(context->target,
		 t - context->dtd->tags);
	if (old_tag == t)
	    return;  /* Correct sequence */
	
	/* Syntax error path only */
	
    }
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr,
	       "SGML: Extra end tag </%s> found and ignored.\n", old_tag->name);
#endif
}


/*	Start a element
*/
PRIVATE void start_element(HTStream *context)
{
    HTTag *new_tag = context->current_tag;
    
#ifndef DISABLE_TRACE
    if (www2Trace)
	fprintf(stderr, "SGML: Start <%s>\n", new_tag->name);
#endif
    (*context->actions->start_element)(
    	context->target,
	new_tag - context->dtd->tags,
	context->present,
	(WWW_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
*/
PRIVATE HTTag *find_tag ARGS2(WWW_CONST SGML_dtd *, dtd, 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 = my_strcasecmp(dtd->tags[i].name, string);  /* Case insensitive */
	if (diff == 0)			/* success: found it */
	    return &dtd->tags[i];
    }
    return NULL;
}

/*________________________________________________________________________
**			Public Methods
*/


PUBLIC void SGML_end ARGS1(HTStream *, context)
{
/*	Could check that we are back to bottom of stack! @@  */

    (*context->actions->end_document)(context->target);
}


PUBLIC void SGML_free ARGS1(HTStream *, context)
{
    (*context->actions->free)(context->target);
    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)

{
    WWW_CONST SGML_dtd	*dtd	=	context->dtd;
    HTChunk		*string = 	context->string;

    switch(context->state) {
    case S_text:
	if (c == '&' && (!context->element_stack || (
	    		 context->element_stack->tag  &&
	    		 ( context->element_stack->tag->contents == SGML_MIXED
			   || context->element_stack->tag->contents ==
			      				 SGML_RCDATA)
			))) {
	    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 {
	    PUTC(c);
	}
	break;

	/*	In litteral mode, waits only for specific end tag!
	**	Only foir 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, c);
	    context->state = S_text;
	}
	break;

	/*	Character reference
	*/
    case S_cro:
	if (isalnum(c)) {
	    HTChunkPutc(string, c);	/* accumulate a character NUMBER */
	} else {
	    int value;

	    HTChunkTerminate(string);
	    if (sscanf(string->data, "%d", &value) == 1)
	        PUTC((char)value);
	    context->state = S_text;
	}
	break;

	/*		Tag
	*/	    
    case S_tag:				/* new tag */
	if (isalnum(c)) {
	    HTChunkPutc(string, c);
	} else {			/* End of tag name */
	    HTTag *t;

	    if (c == '/') {
#ifndef DISABLE_TRACE
		if ((www2Trace) && (string->size != 0))
		    fprintf(stderr, "SGML:  `<%s/' found!\n", string->data);
#endif
		context->state = S_end;
		break;
	    }
	    HTChunkTerminate(string);

	    t = find_tag(dtd, string->data);
	    if (!t) {
#ifndef DISABLE_TRACE
		if (www2Trace)
		    fprintf(stderr, "SGML: *** Unknown element %s\n",
			    string->data);
#endif
		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_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 */
#ifndef DISABLE_TRACE
	    if (www2Trace)
		fprintf(stderr, "SGML: found = but no value\n");
#endif
	    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 == '"') {		/* 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_end:					/* </ */
	if (isalnum(c)) {
	    HTChunkPutc(string, c);
	} else {				/* End of end tag name */
	    HTTag *t;

	    HTChunkTerminate(string);
	    if (!*string->data)	{	/* Empty end tag */
	        t = context->element_stack->tag;
	    } else {
		t = find_tag(dtd, string->data);
	    }
	    if (!t) {
#ifndef DISABLE_TRACE
		if (www2Trace)
		    fprintf(stderr, "Unknown end tag </%s>\n", string->data); 
#endif
	    } else {
	        context->current_tag = t;
		end_element(context, context->current_tag);
	    }

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

}  /* SGML_character */


PUBLIC void SGML_string ARGS2(HTStream *, context, WWW_CONST char *, str)
{
    WWW_CONST char *p;

    for (p = str; *p; p++)
        SGML_character(context, *p);
}


PUBLIC void SGML_write ARGS3(HTStream *, context, WWW_CONST char *, str, int, l)
{
    WWW_CONST char *p;
    WWW_CONST char *e = str + l;

    for (p = str; p < e; p++)
        SGML_character(context, *p);
}

/*_______________________________________________________________________
*/

PRIVATE void SGML_handle_interrupt ARGS1(HTStream *, context)
{
}

/*	Structured Object Class
**	-----------------------
*/
PUBLIC WWW_CONST HTStreamClass SGMLParser = 
{		
	"SGMLParser",
	SGML_free,
	SGML_end,
	SGML_character, 	SGML_string,  SGML_write,
        SGML_handle_interrupt
}; 

/*	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(
	WWW_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;
}

