/* * html.c * * libhtml - HTML->X renderer * * Copyright (c) 1994-1997, John Kilburg * * 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 #ifdef HAVE_STDLIB_H #include #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, ""); 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 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, "", strlen(""))); 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); }