/*-----------------------------------------------------------------------
 * Xclasses code file
 * ($Id: html_parser.cxx,v 1.10 2003/02/19 11:06:19 schmitzj Exp $)
 * 
 * Copyright (C) 1998 Jürgen Schmitz
 *
 * HTML Parser system
 * (c) 1998 by Jürgen Schmitz - all rights reserved
 * THIS IS NOT GPL OR SOMETHING OTHER FREE LICENSE!!
 * it may only be used together with Xclasses!
 *-----------------------------------------------------------------------
 */

/*
 * Tags:
 *
 * BODY BGCOLOR
 * DL, DT, DD
 * UL, LI
 * OL, LI
 * I
 * B
 * P [ALIGN=LEFT|CENTER|RIGHT]
 * BR
 * HR (*)
 * FONT SIZE
 * Hx
 * TT
 * PRE
 * CENTER
 * DIV ALIGN=LEFT|CENTER|RIGHT
 * BLOCKQUOTE
 *
 * (*) - known, but not (completely) working
 */

#include "include/html_parser.h"
#include "include/html_item.h"
#include "include/html_box.h"
#include "include/chartable.h"
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <ctype.h>

static int xcl_strcncmp(char *s1,char *s2)
{ // strcasencmp
  register unsigned char *p1 = (unsigned char *) s1;
  register unsigned char *p2 = (unsigned char *) s2;
  register int ret=1;
  unsigned char c1;

  for (;*p1 && !(ret = (c1 = tolower(*p1)) - tolower(*p2)); p1++, p2++)
    if (c1 == '\0') break;
  return ret;
}
static int strtagcmp(char *s1,char *s2) // s2[last]==' ' || s2[last]=='>'
{ // strcasencmp
  register unsigned char *p1 = (unsigned char *) s1;
  register unsigned char *p2 = (unsigned char *) s2;
  register int ret=1;
  unsigned char c1;

  for (;*p1 && !(ret = (c1 = tolower(*p1)) - tolower(*p2)); p1++, p2++)
    if (c1 == '\0') break;

  return (*p2==' '||*p2=='>'||!*p2)?ret:1;
}


html_parser::html_parser(char *txt) : cb()
{
	value_dd_offset=20;
	value_blockquote_offset=80;
	value_li_offset=20;


	para_value=NULL;
	enti_size=0;
	enti_string=NULL;

	master=new html_box();
	stack=new html_stack();

	fontstylestack=new html_valuestack();
	fontstylestack->Push(FSY_NONE);
	fontsizestack=new html_valuestack();
	fontsizestack->Push((unsigned int)NORMAL);
	olicountstack=new html_valuestack();
	
	char *t=strdup(txt);
	char *c=t,*cinit=t;

	linemode=0;
	inbody=0;
	while (*c)
	{
		if (*c=='\n' || *c=='\r')
		{
			*c='\n';
		}
		c++;
	}

	c=t;
	while (*c)
	{
		if (*c=='<')
		{ // Tag mode
			if (cinit!=c)
			{
				if (inbody) master->AddItem(new html_textitem(entity(cinit,c-cinit)));
			}
			char *c2=c;
			while (*c2 && *c2!='>')
			{
				if (*c2=='\n') *c2=' ';
				c2++;
			}
			// Tag analysis
			newtag(c,c2-c);
			c=c2;
			cinit=c+1;
		}
		else if ((!linemode && (*c==' ' || *c=='\t')) || *c=='\n')
		{
			if (cinit!=c)
			{
				if (inbody) master->AddItem(new html_textitem(entity(cinit,c-cinit)));
			}
			if (!linemode)
			{
				while (*(c+1)==' ' || *(c+1)=='\t' || *(c+1)=='\n') c++;
				if (inbody) master->AddItem(new html_spaceitem);
			}
			else
				if (inbody) master->AddItem(new html_newlineitem);
			cinit=c+1;
		}
		c++;
	}
	if (*c!=' ' && *c!='\t' && (linemode || *c!='\n'))
	{
		if (cinit!=c)
		{
			if (inbody) master->AddItem(new html_textitem(entity(cinit,c-cinit)));
		}
	}
	master->AddItem(new html_newlineitem);

	free(t);
	master->CalcSize();
}
html_parser::~html_parser()
{
	delete olicountstack;
	delete fontstylestack;
	delete fontsizestack;
	delete master;
	delete [] para_value;
	delete [] enti_string;
}
char *html_parser::entity(char *t,int size)
{
	if (!size) size=strlen(t);
	if (enti_size<=size)
	{
		delete [] enti_string;
		enti_string=new char[size+1];
		enti_size=size;
	}
	char *c1=t,*c2=enti_string;

	while (size>0)
	{
		if (*c1=='&')
		{
			char *c3=c1;
			while (*c3 && *c3!=';') c3++;
			if (*c3=';')
			{
				char buf[c3-c1+2];
				buf[0]=0;
				strncat(buf,c1,c3-c1+1);
				*c2=CharTable::Translate(buf);
				size-=c3-c1+1;
				c1=c3+1;
			}
		}
		else
		{
			*c2=*c1;
			c1++;
			size--;
		}
		c2++;
	}
	*c2=0;
	return enti_string;
}

void html_parser::newtag(char *t,int size)
{
	if (!size) size=strlen(t);
 	if (*t=='<') t++; else size++;

	char *buf=new char[size];
 	char *c=buf;

 	while (size>0)
 	{
 		*c=*t;
 		t++; c++; size--;
 	}
	c--;
	*c=0;

	int endtag=(buf[0]=='/');
	c=endtag?buf+1:buf;

// <body> ------------------------------------------------------------
  	if (!strtagcmp("body",c))
  	{
		if (endtag)
		{
			inbody=0;
		}
		else
		{
			char *v=parameter(c,"bgcolor",1);
			inbody=1;
			if (v)
			{
		  		master->AddItem(new html_backcoloritem(v));
			}
		}
  	}
	if (!inbody)
	{
		delete [] buf;
		return;
	}

// <br> --------------------------------------------------------------
  	if (!strtagcmp("br",c))
  	{
  		master->AddItem(new html_newlineitem);
  	}
// <p> ---------------------------------------------------------------
  	else if (!strtagcmp("p",c))
  	{
		if (!endtag)
		{
			char *v=parameter(c,"align",1);

  			master->AddItem(new html_newparagraphitem);
			if (v)
			{
				if (!strcasecmp(v,"CENTER"))
					newtag("div align=center");
				else if (!strcasecmp(v,"RIGHT"))
					newtag("div align=right");
				else if (!strcasecmp(v,"LEFT"))
					newtag("div align=left");
			}
		}
		else
		{
			newtag("/div");
		}
  	}
// <dl> --------------------------------------------------------------
  	else if (!strtagcmp("dl",c))
  	{
		if (!endtag)
		{
			stack->Push("dl");
  			master->AddItem(new html_newparagraphitem);
		}
		else
		{
			if (stack->Exists("dl"))
			{
				tag_dl_restore();
				stack->PullUntil("dl");
			}
  			master->AddItem(new html_nextlineitem);
		}
  	}
  	else if (!strtagcmp("dt",c))
  	{
		if (!endtag)
		{
			if (stack->Exists("dl"))
			{
				tag_dl_restore();
				stack->PullBefore("dl");
				stack->Push("dt");
		  		master->AddItem(new html_nextlineitem);
			}
		}
		else
		{
			if (stack->Exists("dt"))
			{
				tag_dl_restore();
				stack->PullUntil("dt");
		  		master->AddItem(new html_nextlineitem);
			}
		}
  	}
  	else if (!strtagcmp("dd",c))
  	{
		if (!endtag)
		{
			if (stack->Exists("dl"))
			{
				tag_dl_restore();
				stack->Push("dd");
		  		master->AddItem(new html_nextlineitem);
				master->AddItem(new html_offsetitem(value_dd_offset));
			}
		}
		else
		{
			if (stack->Exists("dd"))
			{
				tag_dl_restore();
	  			master->AddItem(new html_nextlineitem);
			}
		}
  	}
// <ul> --------------------------------------------------------------
  	else if (!strtagcmp("ul",c))
  	{
		if (!endtag)
		{
			stack->Push("ul");
  			master->AddItem(new html_newparagraphitem);
		}
		else
		{
			if (stack->Exists("ul"))
			{
				if (stack->Exists("uli","ul"))
				{
					master->AddItem(new html_offsetitem(-value_li_offset));
				}
				stack->PullUntil("ul");
			}
  			master->AddItem(new html_nextlineitem);
		}
  	}
  	else if (!strtagcmp("li",c) && stack->Exists("ul","ol"))
  	{
		if (!endtag)
		{
			if (stack->Exists("ul"))
			{
				if (stack->Exists("uli","ul"))
				{
					master->AddItem(new html_offsetitem(-value_li_offset));
					stack->PullBefore("ul");
				}
				stack->Push("uli");
		  		master->AddItem(new html_nextlineitem);
				master->AddItem(new html_offsetitem(value_li_offset));
				master->AddItem(new html_bulletitem(stack->Count("uli")-1));
			}
		}
		else
		{
			if (stack->Exists("uli"))
			{
				if (stack->Exists("uli","ul"))
				{
					master->AddItem(new html_offsetitem(-value_li_offset));
					stack->PullBefore("ul");
				}
	  			master->AddItem(new html_nextlineitem);
			}
		}
  	}
// <ol> --------------------------------------------------------------
  	else if (!strtagcmp("ol",c))
  	{
		if (!endtag)
		{
			stack->Push("ol");
  			master->AddItem(new html_newparagraphitem);
			olicountstack->Push(1);
		}
		else
		{
			if (stack->Exists("ol"))
			{
				if (stack->Exists("oli","ol"))
				{
					master->AddItem(new html_offsetitem(-value_li_offset));
				}
				stack->PullUntil("ol");
				olicountstack->Pull();
			}
  			master->AddItem(new html_nextlineitem);
		}
  	}
  	else if (!strtagcmp("li",c) && stack->Exists("ol","ul"))
  	{
		if (!endtag)
		{
			if (stack->Exists("ol"))
			{
				if (stack->Exists("oli","ol"))
				{
					master->AddItem(new html_offsetitem(-value_li_offset));
					stack->PullBefore("ol");
				}
				stack->Push("oli");
				int n=olicountstack->Pull();
		  		master->AddItem(new html_nextlineitem);
				master->AddItem(new html_offsetitem(value_li_offset));
				master->AddItem(new html_countitem(n));
				olicountstack->Push(n+1);
			}
		}
		else
		{
			if (stack->Exists("oli"))
			{
				if (stack->Exists("oli","ol"))
				{
					master->AddItem(new html_offsetitem(-value_li_offset));
					stack->PullBefore("ol");
				}
	  			master->AddItem(new html_nextlineitem);
			}
		}
  	}
// <center> ----------------------------------------------------------
  	else if (!strtagcmp("center",c))
  	{
		if (!endtag)
		{
			stack->Push("div");
  			master->AddItem(new html_nextlineitem);
			master->AddItem(new html_adjustitem(CENTER));
	  	}
		else
	  	{
			if (stack->Exists("div"))
			{
				stack->PullUntil("div");
		  		master->AddItem(new html_nextlineitem);
				master->AddItem(new html_adjustitem(LEFT));
			}
		}
  	}
// <div> -------------------------------------------------------------
  	else if (!strtagcmp("div",c))
  	{
		if (!endtag)
		{
			if (stack->Exists("div"))
			{
				stack->PullUntil("div");
			}
			char *v=parameter(c,"align",1);
			stack->Push("div");
  			master->AddItem(new html_nextlineitem);
			if (v && !strcasecmp(v,"CENTER"))
				master->AddItem(new html_adjustitem(CENTER));
			else if (v && !strcasecmp(v,"RIGHT"))
				master->AddItem(new html_adjustitem(RIGHT));
			else if (v && !strcasecmp(v,"LEFT"))
				master->AddItem(new html_adjustitem(LEFT));
	  	}
		else
	  	{
			if (stack->Exists("div"))
			{
				stack->PullUntil("div");
		  		master->AddItem(new html_nextlineitem);
				master->AddItem(new html_adjustitem(LEFT));
			}
		}
  	}
// <font> ------------------------------------------------------------
  	else if (!strtagcmp("font",c))
  	{
		if (!endtag)
		{
			char *v=parameter(c,"size",1);
			stack->Push("font");

			if (v && *v=='+')
			{
				FONTSIZE val=(FONTSIZE)fontsizestack->Read();
				if (fontsizestack->Read()-1>=(unsigned int)FONT1) val=(FONTSIZE)((unsigned int)val-1);
				master->AddItem(new html_fontsizeitem(val));
				fontsizestack->Push((unsigned int)val);
			}
			else if (v && *v=='-')
			{
				FONTSIZE val=(FONTSIZE)fontsizestack->Read();
				if (fontsizestack->Read()+1<=(unsigned int)FONT6) val=(FONTSIZE)((unsigned int)val+1);
				master->AddItem(new html_fontsizeitem(val));
				fontsizestack->Push((unsigned int)val);
			}
			else if (v)
			{
				switch (atoi(v))
				{
					case 1:
						master->AddItem(new html_fontsizeitem(FONT6));
						fontsizestack->Push((unsigned int)FONT6);
						break;
					case 2:
						master->AddItem(new html_fontsizeitem(FONT5));
						fontsizestack->Push((unsigned int)FONT5);
						break;
					case 3:
						master->AddItem(new html_fontsizeitem(FONT4));
						fontsizestack->Push((unsigned int)FONT4);
						break;
					case 4:
						master->AddItem(new html_fontsizeitem(FONT3));
						fontsizestack->Push((unsigned int)FONT3);
						break;
					case 5:
						master->AddItem(new html_fontsizeitem(FONT2));
						fontsizestack->Push((unsigned int)FONT2);
						break;
					case 6:
						master->AddItem(new html_fontsizeitem(FONT1));
						fontsizestack->Push((unsigned int)FONT1);
						break;
					default:
						fontsizestack->Push((unsigned int)NORMAL);
						master->AddItem(new html_fontsizeitem(NORMAL));
						break;
				}
			}
  		}
		else
		{
			if (stack->Exists("font"))
			{
				stack->PullUntil("font");
				fontsizestack->Pull();
				master->AddItem(new html_fontsizeitem((FONTSIZE)fontsizestack->Read()));
			}
		}
  	}
// <hr> ------------------------------------------------------------
  	else if (!strtagcmp("hr",c))
  	{
  		master->AddItem(new html_newparagraphitem);
	}
// <Hx> ------------------------------------------------------------
  	else if (!xcl_strcncmp("h",c))
  	{
		if (!endtag)
		{
//			char *v=parameter(c,"align",1);
			stack->Push("hx");

  			master->AddItem(new html_newparagraphitem);
			newtag("b");
			switch (c[1])
			{
				case '1':
					newtag("font size=6");
					break;
				case '2':
					newtag("font size=5");
					break;
				case '3':
					newtag("font size=4");
					break;
				case '4':
					newtag("font size=3");
					break;
				case '5':
					newtag("font size=2");
					break;
				case '6':
					newtag("font size=1");
					break;
				default:
					newtag("font size=3");
					break;
			}
	  	}
		else
		{
			if (stack->Exists("hx"))
			{
				newtag("/font");
				newtag("/b");
				stack->PullUntil("hx");
		  		master->AddItem(new html_newlineitem);
			}
		}
  	}
// <pre> -------------------------------------------------------------
  	else if (!strtagcmp("pre",c))
  	{
		if (!endtag)
		{
			stack->Push("pre");
  			master->AddItem(new html_nextlineitem);
			newtag("tt");
			linemode=1;
	  	}
		else
		{
			if (stack->Exists("pre"))
			{
				newtag("/tt");
				stack->PullUntil("pre");
		  		master->AddItem(new html_nextlineitem);
				linemode=0;
			}
		}
  	}
// <tt> --------------------------------------------------------------
  	else if (!strtagcmp("tt",c))
  	{
		if (!endtag)
		{
			unsigned int val=fontstylestack->Read()|FSY_FIXED;
			stack->Push("tt");
			master->AddItem(new html_fontstyleitem(val));
			fontstylestack->Push(val);
	  	}
		else
	  	{
			if (stack->Exists("tt"))
			{
				fontstylestack->Pull();
				stack->PullUntil("tt");
				master->AddItem(new html_fontstyleitem(fontstylestack->Read()));
			}
		}
  	}
// <b> ---------------------------------------------------------------
  	else if (!strtagcmp("b",c))
  	{
		if (!endtag)
		{
			unsigned int val=fontstylestack->Read()|FSY_BOLD;
			stack->Push("b");
			master->AddItem(new html_fontstyleitem(val));
			fontstylestack->Push(val);
	  	}
		else
	  	{
			if (stack->Exists("b"))
			{
				fontstylestack->Pull();
				stack->PullUntil("b");
				master->AddItem(new html_fontstyleitem(fontstylestack->Read()));
			}
		}
  	}
// <i> ---------------------------------------------------------------
  	else if (!strtagcmp("i",c))
  	{
		if (!endtag)
		{
			unsigned int val=fontstylestack->Read()|FSY_ITALIC;
			stack->Push("i");
			master->AddItem(new html_fontstyleitem(val));
			fontstylestack->Push(val);
	  	}
		else
	  	{
			if (stack->Exists("i"))
			{
				fontstylestack->Pull();
				stack->PullUntil("i");
				master->AddItem(new html_fontstyleitem(fontstylestack->Read()));
			}
		}
  	}
// <blockquote> ------------------------------------------------------
  	else if (!strtagcmp("blockquote",c))
  	{
		if (!endtag)
		{
			stack->Push("blockquote");
	  		master->AddItem(new html_nextlineitem);
			master->AddItem(new html_offsetitem(value_blockquote_offset));
		}
		else
		{
			if (stack->Exists("blockquote"))
			{
				master->AddItem(new html_offsetitem(-value_blockquote_offset));
		  		master->AddItem(new html_nextlineitem);
				stack->PullUntil("blockquote");
			}
		}
  	}
// <a> ---------------------------------------------------------------
  	else if (!strtagcmp("a",c))
  	{
		if (!endtag)
		{
			char *v=parameter(c,"href",1);
			if (v)
			{
				stack->Push("ahref");
		  		master->AddItem(new html_boxitem(master));
		  		master->AddItem(new html_coloritem("#0000cc"));
		  		master->AddItem(new html_callbackitem(this));
		  		master->AddItem(new html_backcoloritem("#ff0000"));

				master->SetData(v);
			}

			v=parameter(c,"name",1);
			if (v)
			{
				stack->Push("aname");
		  		master->AddItem(new html_boxitem(master));
				master->SetData(v);
			}
	  	}
		else
	  	{
			if (stack->Exists("ahref"))
			{
				stack->PullUntil("ahref");
		  		master->AddItem(new html_coloritem());
		  		master->AddItem(new html_boxenditem(master));
			}
			else if (stack->Exists("aname"))
			{
				stack->PullUntil("aname");
		  		master->AddItem(new html_boxenditem(master));
			}
		}
  	}
// <????> ------------------------------------------------------------


	delete [] buf;
}

void html_parser::Create(html_drawspec *parent)
{
	int real_w,real_h;
	master->CalcBox(0,0,master->GetWidth(),real_w,real_h);
	master->CreateBox(parent);
}
void html_parser::Free(void)
{
	master->FreeBox();
}
void html_parser::Calc(int x,int y,int w,int &real_w,int &real_h)
{
	master->CalcBox(x,y,w,real_w,real_h);
	if (real_w!=w) master->CalcBox(x,y,real_w,real_w,real_h);
}
void html_parser::Draw(int x,int y,int w,int &real_w,int &real_h)
{
	master->CalcBox(x,y,w,real_w,real_h);
	if (real_w!=w) master->CalcBox(x,y,real_w,real_w,real_h);
	master->MoveBox();
	master->DrawBox();
}
void html_parser::Move(int x,int y,int w,int &real_w,int &real_h)
{
	master->CalcBox(x,y,w,real_w,real_h);
	if (real_w!=w) master->CalcBox(x,y,real_w,real_w,real_h);
	master->MoveBox();
}

char *html_parser::parameter(char *buf,char *name,int hasvalue)
{
	char *c=buf;
	int quote=0;

	delete [] para_value;
	para_value=NULL;

	while (*c && *c!=' ') c++;
	while (*c)
	{
		while (*c && *c==' ') c++;
		if (!xcl_strcncmp(name,c))
		{
			if (hasvalue)
			{
				c+=strlen(name);
				while (*c && *c++!='=') ;
				while (*c && *c==' ') c++;
				if (*c)
				{
					if (*c=='"')
					{
						quote=1;
						c++;
					}

					char *c2=c;
					while (*c2 && ((quote==0 && *c2!=' ')||(quote==1 && *c2!='"'))) c2++;
					if (quote && *c2=='"') c2--;
					para_value=new char[c2-c+2];

					char *c3=para_value;
					while (c<=c2)
					{
						*c3=*c;
						c3++; c++;
					}
					*c3=0;
				}
			}
			else
			{
				para_value=new char[3];
				para_value[0]='O';
				para_value[1]='N';
				para_value[2]=0;
			}
			break;
		}
		while (*c && *c!=' ') c++;
	}
	return para_value;
}

void html_parser::tag_dl_restore(void)
{
	if (stack->Exists("dd","dl"))
	{
		master->AddItem(new html_offsetitem(-value_dd_offset));
		stack->PullUntil("dd");
	}
}

void html_parser::Callback(callable *c,void (callable::*f)(char *))
{
	cb.set(c,(CALLFUNCTION)f);
}
void html_parser::Event(html_box *b)
{
	int x=0,y=0;

	b->Position(x,y);

// call gadget's handler, calls gadget's callback
	cb.call((void*)b->GetData());
}
