chimera2/html/html.c

823 lines
14 KiB
C

/*
* html.c
*
* libhtml - HTML->X renderer
*
* Copyright (c) 1994-1997, John Kilburg <john@cs.unlv.edu>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "port_before.h"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include "port_after.h"
#include "html.h"
static void EndTag _ArgProto((HTMLInfo, HTMLTag, MLElement));
static void StartTag _ArgProto((HTMLInfo, HTMLTag, MLElement));
static void AddObject _ArgProto((HTMLInfo, HTMLObjectType, void *));
static void DoEnd _ArgProto((HTMLInfo, HTMLEnv, MLElement));
static bool BoxifyObject _ArgProto((HTMLInfo, HTMLEnv, HTMLObject));
static bool Boxify _ArgProto((HTMLInfo, HTMLEnv));
static void HTMLDocumentBegin _ArgProto((HTMLInfo, HTMLEnv, MLElement));
static void HTMLDocumentEnd _ArgProto((HTMLInfo, HTMLEnv, MLElement));
static void HTMLDocumentAddBox _ArgProto((HTMLInfo, HTMLEnv, HTMLBox));
static unsigned int HTMLDocumentWidth _ArgProto((HTMLInfo, HTMLEnv));
static void Unboxify _ArgProto((HTMLInfo, HTMLEnv));
static void PrintObject _ArgProto((HTMLObject));
#include "htmltags.h"
typedef struct
{
HTMLTag tag;
MLElement p;
} PendingEnd;
struct av
{
char *name;
HTMLAttribID align;
};
struct av alist[] =
{
{ "left", ATTRIB_LEFT },
{ "right", ATTRIB_RIGHT },
{ "center", ATTRIB_CENTER },
{ "top", ATTRIB_TOP },
{ "bottom", ATTRIB_BOTTOM },
{ "middle", ATTRIB_MIDDLE },
{ "all", ATTRIB_ALL },
{ NULL, ATTRIB_UNKNOWN }
};
static int alist_len = sizeof(alist) / sizeof(alist[0]);
/*
* HTMLAttributeToID
*/
HTMLAttribID
HTMLAttributeToID(p, name)
MLElement p;
char *name;
{
char *value;
int i;
if ((value = MLFindAttribute(p, name)) == NULL) return(ATTRIB_UNKNOWN);
for (i = 0; i < alist_len; i++)
{
if (alist[i].name != NULL && strcasecmp(value, alist[i].name) == 0)
{
return(alist[i].align);
}
}
return(ATTRIB_UNKNOWN);
}
/*
* HTMLGetTag
*/
HTMLTag
HTMLGetTag(p)
MLElement p;
{
int i;
char *name;
if ((name = MLTagName(p)) == NULL) return(NULL);
for (i = 0; i < tlist_len; i++)
{
if (tlist[i].name != NULL &&
strlen(name) == strlen(tlist[i].name) &&
strcasecmp(name, tlist[i].name) == 0 &&
!HTMLTestM(&tlist[i], MARKUP_FAKE))
{
return(&tlist[i]);
}
}
return(NULL);
}
/*
* HTMLTagIDToTag
*/
HTMLTag
HTMLTagIDToTag(tagid)
HTMLTagID tagid;
{
int i;
for (i = 0; i < tlist_len; i++)
{
if (tlist[i].id == tagid) return(&tlist[i]);
}
return(NULL);
}
/*
* HTMLFindEnv
*/
HTMLEnv
HTMLFindEnv(li, tagid)
HTMLInfo li;
HTMLTagID tagid;
{
HTMLEnv env;
GList list;
list = li->envstack;
for (env = (HTMLEnv)GListGetHead(list); env != NULL;
env = (HTMLEnv)GListGetNext(list))
{
if (env->tag->id == tagid) return(env);
}
return(NULL);
}
/*
* HTMLDelayLayout
*/
void
HTMLDelayLayout(li)
HTMLInfo li;
{
li->delayed++;
return;
}
/*
* HTMLContinueLayout
*/
void
HTMLContinueLayout(li)
HTMLInfo li;
{
myassert(li->delayed > 0, "Layout was not delayed.");
li->delayed--;
if (li->delayed == 0) Boxify(li, li->topenv);
return;
}
/*
* HTMLTagToID
*/
HTMLTagID
HTMLTagToID(tag)
HTMLTag tag;
{
return(tag->id);
}
/*
* HTMLPopEnv
*/
HTMLEnv
HTMLPopEnv(li, tagid)
HTMLInfo li;
HTMLTagID tagid;
{
HTMLEnv c;
/* if (tagid == TAG_DOCUMENT) abort(); */
for (c = (HTMLEnv)GListGetHead(li->envstack); c != NULL;
c = (HTMLEnv)GListGetNext(li->envstack))
{
if (c->tag->id == tagid) break;
}
if (c == NULL) return(NULL);
while ((c = (HTMLEnv)GListGetHead(li->envstack)) != NULL)
{
if (c->tag->id == tagid) break;
DoEnd(li, c, NULL);
}
return(c);
}
/*
* HTMLStartEnv
*/
void
HTMLStartEnv(li, tagid, p)
HTMLInfo li;
HTMLTagID tagid;
MLElement p;
{
char *str;
HTMLTag tag;
tag = HTMLTagIDToTag(tagid);
if (p == NULL)
{
str = MPGet(li->mp, strlen(tag->name) + 3);
strcpy(str, "<");
strcat(str, tag->name);
strcat(str, ">");
p = MLCreateTag(li->hs, str, strlen(str));
}
StartTag(li, tag, p);
return;
}
/*
* HTMLEndEnv
*/
void
HTMLEndEnv(li, tagid)
HTMLInfo li;
HTMLTagID tagid;
{
EndTag(li, HTMLTagIDToTag(tagid), NULL);
return;
}
/*
* HTMLGetMaxWidth
*/
unsigned int
HTMLGetMaxWidth(li, env)
HTMLInfo li;
HTMLEnv env;
{
HTMLEnv c;
for (c = env; c != NULL; c = c->penv)
{
if (c->tag->w != NULL) return((c->tag->w)(li, c));
}
return(0);
}
/*
* PrintObject
*/
static void
PrintObject(ho)
HTMLObject ho;
{
char *text;
size_t len;
MLElementType mt;
if (ho->type == HTML_ELEMENT)
{
MLGetText(ho->o.p, &text, &len);
fwrite (text, 1, len, stdout);
printf ("\n");
}
else if (ho->type == HTML_TAG || ho->type == HTML_BEGINTAG ||
ho->type == HTML_ENDTAG)
{
mt = MLGetType(ho->o.p);
if (mt == ML_ENDTAG) printf ("End tag: %s\n", MLTagName(ho->o.p));
else printf ("Begin tag: %s\n", MLTagName(ho->o.p));
}
return;
}
/*
* AddObject
*/
static void
AddObject(li, hot, obj)
HTMLInfo li;
HTMLObjectType hot;
void *obj;
{
HTMLEnv env;
HTMLObject ho;
ho = (HTMLObject)MPGet(li->mp, sizeof(struct HTMLObjectP));
ho->type = hot;
if (hot == HTML_ENV) ho->o.env = (HTMLEnv)obj;
else if (hot == HTML_ELEMENT) ho->o.p = (MLElement)obj;
else if (hot == HTML_TAG) ho->o.p = (MLElement)obj;
else if (hot == HTML_BEGINTAG) ho->o.p = (MLElement)obj;
else if (hot == HTML_ENDTAG) ho->o.p = (MLElement)obj;
else abort();
if (li->printTags) PrintObject(ho);
if (hot != HTML_BEGINTAG && hot != HTML_ENDTAG)
{
for (env = (HTMLEnv)GListGetHead(li->envstack); env != NULL;
env = (HTMLEnv)GListGetNext(li->envstack))
{
if (env->tag->m == NULL || (env->tag->m)(li, ho)) break;
}
if (env == NULL) return;
}
else env = (HTMLEnv)GListGetHead(li->envstack);
if (hot == HTML_ENV) ho->o.env->penv = env;
/*
* Add object to the object list for the environment. The first list
* will be modified later. The second list will always stay the
* same so there is always a place to find all the objects in an
* environment.
*/
GListAddTail(env->olist, ho);
GListAddTail(env->slist, ho);
return;
}
/*
* DoEnd
*/
static void
DoEnd(li, env, p)
HTMLInfo li;
HTMLEnv env;
MLElement p;
{
char *name;
char *str;
if (p == NULL)
{
if (env->tag->name == NULL) name = "internal";
else name = env->tag->name;
str = (char *)MPGet(li->mp, strlen(name) + 4);
strcpy(str, "</");
strcat(str, name);
strcat(str, ">");
p = MLCreateTag(li->hs, str, strlen(str));
}
AddObject(li, HTML_ENDTAG, p);
env = (HTMLEnv)GListPop(li->envstack);
AddObject(li, HTML_ENV, env);
return;
}
/*
* EndTag
*/
static void
EndTag(li, tag, p)
HTMLInfo li;
HTMLTag tag;
MLElement p;
{
HTMLEnv etop;
PendingEnd *pe;
HTMLEnv env;
/*
* If there was no start tag then ignore.
*/
if (HTMLFindEnv(li, tag->id) == NULL) return;
/*
* Check to see if the end tag is supposed to clamp down on all
* unterminated environments.
*/
if (HTMLTestM(tag, MARKUP_CLAMP))
{
if (tag->c != NULL)
{
if ((tag->c)(li, (HTMLEnv)GListGetHead(li->envstack)))
{
while ((env = (HTMLEnv)GListGetHead(li->envstack)) != NULL)
{
DoEnd(li, env, NULL);
if (env->tag->id == tag->id) break;
}
}
}
else
{
while ((env = (HTMLEnv)GListGetHead(li->envstack)) != NULL)
{
DoEnd(li, env, NULL);
if (env->tag->id == tag->id) break;
}
}
if ((pe = (PendingEnd *)GListPop(li->endstack)) != NULL)
{
EndTag(li, pe->tag, pe->p);
return;
}
}
else
{
/*
* Make sure the end tag matches the current environment before
* terminating the environment. If it doesn't match then stick it
* in a list for possible use later.
*/
etop = (HTMLEnv)GListGetHead(li->envstack);
if (tag->id == etop->tag->id)
{
DoEnd(li, etop, p);
if ((pe = (PendingEnd *)GListPop(li->endstack)) != NULL)
{
EndTag(li, pe->tag, pe->p);
return;
}
}
else
{
pe = MPGet(li->mp, sizeof(PendingEnd));
pe->tag = tag;
pe->p = p;
GListAddTail(li->endstack, pe);
}
}
return;
}
/*
* StartTag
*/
static void
StartTag(li, tag, p)
HTMLInfo li;
HTMLTag tag;
MLElement p;
{
HTMLEnv env;
HTMLEnv etop;
HTMLInsertStatus status;
if ((etop = (HTMLEnv)GListGetHead(li->envstack)) != NULL)
{
if (tag->p != NULL)
{
if ((status = (tag->p)(li, etop, p)) == HTMLInsertReject) return;
}
else status = HTMLInsertOK;
}
else status = HTMLInsertOK;
if (!HTMLTestM(tag, MARKUP_EMPTY) || status == HTMLInsertEmpty)
{
env = MPCGet(li->mp, sizeof(struct HTMLEnvP));
env->tag = tag;
env->olist = GListCreateX(li->mp);
env->blist = GListCreateX(li->mp);
env->slist = GListCreateX(li->mp);
if (etop == NULL) li->topenv = env;
GListAddHead(li->envstack, env);
AddObject(li, HTML_BEGINTAG, p);
}
else AddObject(li, HTML_TAG, p);
return;
}
/*
* HTMLHandler
*
* This is where elements from the ML scanner come into the HTML parser.
*/
void
HTMLHandler(closure, p)
void *closure;
MLElement p;
{
HTMLInfo li = (HTMLInfo)closure;
HTMLTag tag;
MLElementType mt;
if ((mt = MLGetType(p)) == ML_EOF)
{
/*
* fake a </xxx-document> tag.
*/
HTMLFinish(li);
/*
* Render the parsed HTML
*/
Boxify(li, li->topenv);
return;
}
else if (mt == ML_DATA) AddObject(li, HTML_ELEMENT, p);
else if ((tag = HTMLGetTag(p)) != NULL)
{
if (mt == ML_ENDTAG) EndTag(li, tag, p);
else StartTag(li, tag, p);
}
else if (li->printTags)
{
printf ("Unknown tag: %s\n", MLTagName(p));
}
return;
}
/*
* BoxifyObject
*/
static bool
BoxifyObject(li, env, obj)
HTMLInfo li;
HTMLEnv env;
HTMLObject obj;
{
HTMLTag tag;
HTMLEnv cenv;
CSSSelector cs;
if (obj->type == HTML_ELEMENT)
{
if (env->tag->d != NULL) (env->tag->d)(li, env, obj->o.p);
}
else if (obj->type == HTML_TAG)
{
if ((tag = HTMLGetTag(obj->o.p)) != NULL)
{
myassert(tag->b != NULL, "No tag handler for lone tag!");
(tag->b)(li, env, obj->o.p);
}
}
else if (obj->type == HTML_ENV)
{
cenv = obj->o.env;
if (!cenv->visited)
{
cenv->ff = env->ff;
cenv->anchor = env->anchor;
cenv->fi = HTMLDupFont(li, env->fi);
if (HTMLTestM(cenv->tag, MARKUP_SPACER))
{
HTMLAddBlankLine(li, env);
}
cenv->visited = true;
}
if ((cs = (CSSSelector)GListPop(li->oldselectors)) == NULL)
{
cs = CSSCreateSelector(li->mp);
}
CSSSetSelector(cs, cenv->tag->name, NULL, NULL, NULL);
GListAddHead(li->selectors, cs);
if (Boxify(li, cenv)) return(true);
GListAddHead(li->oldselectors, GListPop(li->selectors));
if (HTMLTestM(cenv->tag, MARKUP_SPACER))
{
HTMLAddBlankLine(li, env);
}
}
else if (obj->type == HTML_BEGINTAG)
{
if (env->tag->b != NULL) (env->tag->b)(li, env, obj->o.p);
}
else if (obj->type == HTML_ENDTAG)
{
if (env->tag->e != NULL) (env->tag->e)(li, env, obj->o.p);
}
else abort();
if (li->delayed > 0)
{
return(true);
}
return(false);
}
/*
* Unboxify
*/
static void
Unboxify(li, env)
HTMLInfo li;
HTMLEnv env;
{
HTMLObject c;
GList t;
env->visited = false;
while ((c = (HTMLObject)GListPop(env->olist)) != NULL)
{
GListAddTail(env->blist, c);
}
t = env->olist;
env->olist = env->blist;
env->blist = t;
for (c = (HTMLObject)GListGetHead(env->olist); c != NULL;
c = (HTMLObject)GListGetNext(env->olist))
{
if (c->type == HTML_ENV) Unboxify(li, c->o.env);
}
return;
}
/*
* Boxify
*/
bool
Boxify(li, env)
HTMLInfo li;
HTMLEnv env;
{
HTMLObject c;
myassert(li->delayed == 0, "yikes");
while ((c = (HTMLObject)GListPop(env->olist)) != NULL)
{
if (BoxifyObject(li, env, c))
{
if (c->type == HTML_ENV) GListAddHead(env->olist, c);
else GListAddTail(env->blist, c);
return(true);
}
else GListAddTail(env->blist, c);
}
if (HTMLTestM(env->tag, MARKUP_TWOPASS) && env->pass == 0)
{
env->pass++;
Unboxify(li, env);
Boxify(li, env);
}
return(false);
}
/*
* HTMLEnvAddBox
*/
void
HTMLEnvAddBox(li, env, box)
HTMLInfo li;
HTMLEnv env;
HTMLBox box;
{
HTMLEnv c;
for (c = env; c != NULL; c = c->penv)
{
if (c->tag->a != NULL)
{
(c->tag->a)(li, c, box);
break;
}
}
return;
}
/*
* HTMLDocumentEnd
*/
static void
HTMLDocumentEnd(li, env, p)
HTMLInfo li;
HTMLEnv env;
MLElement p;
{
HTMLFinishFlowBox(li, li->firstbox);
return;
}
/*
* HTMLDocumentBegin
*/
static void
HTMLDocumentBegin(li, env, p)
HTMLInfo li;
HTMLEnv env;
MLElement p;
{
return;
}
/*
* HTMLDocumentWidth
*/
static unsigned int
HTMLDocumentWidth(li, env)
HTMLInfo li;
HTMLEnv env;
{
return(HTMLGetBoxWidth(li, li->firstbox));
}
/*
* HTMLDocumentAddBox
*/
static void
HTMLDocumentAddBox(li, env, box)
HTMLInfo li;
HTMLEnv env;
HTMLBox box;
{
HTMLLayoutBox(li, li->firstbox, box);
return;
}
/*
* HTMLFinish
*/
void
HTMLFinish(li)
HTMLInfo li;
{
HTMLPopEnv(li, TAG_DOCUMENT);
HTMLEndEnv(li, TAG_DOCUMENT);
return;
}
/*
* HTMLStart
*/
void
HTMLStart(li)
HTMLInfo li;
{
HTMLStartEnv(li, TAG_DOCUMENT,
MLCreateTag(li->hs,
"<xxx-document>", strlen("<xxx-document>")));
li->topenv->ff = FLOW_LEFT_JUSTIFY;
li->topenv->anchor = NULL;
li->topenv->fi = HTMLDupFont(li, li->cfi);
li->firstbox = HTMLCreateFlowBox(li, li->topenv,
li->maxwidth - (li->lmargin + li->rmargin));
li->firstbox->x = li->lmargin;
li->firstbox->y = li->rmargin;
HTMLSetB(li->firstbox, BOX_TOPLEVEL);
return;
}
/*
* HTMLGetIDEnv
*/
HTMLEnv
HTMLGetIDEnv(env, tid)
HTMLEnv env;
HTMLTagID tid;
{
HTMLEnv penv;
HTMLTagID ptid;
penv = env;
while ((ptid = HTMLTagToID(penv->tag)) != TAG_DOCUMENT)
{
if (ptid == tid) return(penv);
penv = penv->penv;
}
return(NULL);
}