#include "i.h" // Some layout parameters enum { FRKIDMARGIN = 6, // default margin around kid frames IMGHSPACE = 0, // default hspace for images (0 matches IE, Netscape) IMGVSPACE = 0, // default vspace for images FLTIMGHSPACE = 2, // default hspace for float images TABSP = 5, // default cellspacing for tables TABPAD = 1, // default cell padding for tables LISTTAB = 1, // number of tabs to indent lists BQTAB = 1, // number of tabs to indent blockquotes HRSZ = 2, // thickness of horizontal rules SUBOFF = 4, // vertical offset for subscripts SUPOFF = 6, // vertical offset for superscripts NBSP = 160 // non-breaking space character }; // These tables must be sorted static StringInt align_tab[] = { {L"baseline", ALbaseline}, {L"bottom", ALbottom}, {L"center", ALcenter}, {L"char", ALchar}, {L"justify", ALjustify}, {L"left", ALleft}, {L"middle", ALmiddle}, {L"right", ALright}, {L"top", ALtop} }; #define NALIGNTAB (sizeof(align_tab)/sizeof(StringInt)) static StringInt input_tab[] = { {L"button", Fbutton}, {L"checkbox", Fcheckbox}, {L"file", Ffile}, {L"hidden", Fhidden}, {L"image", Fimage}, {L"password", Fpassword}, {L"radio", Fradio}, {L"reset", Freset}, {L"submit", Fsubmit}, {L"text", Ftext} }; #define NINPUTTAB (sizeof(input_tab)/sizeof(StringInt)) static StringInt clear_tab[] = { {L"all", IFcleft|IFcright}, {L"left", IFcleft}, {L"right", IFcright} }; #define NCLEARTAB (sizeof(clear_tab)/sizeof(StringInt)) static StringInt fscroll_tab[] = { {L"auto", FRhscrollauto|FRvscrollauto}, {L"no", FRnoscroll}, {L"yes", FRhscroll|FRvscroll}, }; #define NFSCROLLTAB (sizeof(fscroll_tab)/sizeof(StringInt)) static StringInt shape_tab[] = { {L"circ", SHcircle}, {L"circle", SHcircle}, {L"poly", SHpoly}, {L"polygon", SHpoly}, {L"rect", SHrect}, {L"rectangle", SHrect} }; #define NSHAPETAB (sizeof(shape_tab)/sizeof(StringInt)) static StringInt method_tab[] = { {L"get", HGet}, {L"post", HPost} }; #define NMETHODTAB (sizeof(method_tab)/sizeof(StringInt)) static Rune* roman[15]= { L"I", L"II", L"III", L"IV", L"V", L"VI", L"VII", L"VIII", L"IX", L"X", L"XI", L"XII", L"XIII", L"XIV", L"XV" }; #define NROMAN 15 enum { SPBefore = 2, SPAfter = 4, BL = 1, BLBA = (BL|SPBefore|SPAfter) }; // blockbrk[tag] is break info for a block level element, or one // of a few others that get the same treatment re ending open paragraphs // and requiring a line break / vertical space before them. // If we want a line of space before the given element, SPBefore is OR'd in. // If we want a line of space after the given element, SPAfter is OR'd in. static uchar blockbrk[Numtags]= { [Taddress] BLBA, [Tblockquote] BLBA, [Tcenter] BL, [Tdir] BLBA, [Tdiv] BL, [Tdd] BL, [Tdl] BLBA, [Tdt] BL, [Tform] BLBA, // headings and tables get breaks added manually [Th1] BL, [Th2] BL, [Th3] BL, [Th4] BL, [Th5] BL, [Th6] BL, [Thr] BL, [Tisindex] BLBA, [Tli] BL, [Tmenu] BLBA, [Tol] BLBA, [Tp] BLBA, [Tpre] BLBA, [Tul] BLBA }; enum { AGEN = 1 }; // attrinfo is information about attributes. // The AGEN value means that the attribute is generic (applies to almost all elements) static uchar attrinfo[Numattrs]= { [Aid] AGEN, [Aclass] AGEN, [Astyle] AGEN, [Atitle] AGEN, [Aonblur] AGEN, [Aonchange] AGEN, [Aonclick] AGEN, [Aondblclick] AGEN, [Aonfocus] AGEN, [Aonkeypress] AGEN, [Aonkeyup] AGEN, [Aonload] AGEN, [Aonmousedown] AGEN, [Aonmousemove] AGEN, [Aonmouseout] AGEN, [Aonmouseover] AGEN, [Aonmouseup] AGEN, [Aonreset] AGEN, [Aonselect] AGEN, [Aonsubmit] AGEN, [Aonunload] AGEN }; int dbgbuild = 0; static Token* lexstring(Rune* s, int* panslen); static Rune* getpcdata(Token* toks, int tokslen, int* ptoki); static Pstate* finishcell(Table* curtab, Pstate* psstk); static Pstate* cell_pstate(Pstate* oldps, int ishead); static Pstate* newpstate(Pstate* link); static Pstate* lastps(Pstate* psl); static void additem(Pstate* ps, Item* it, Token* tok); static Item* textit(Pstate* ps, Rune* s); static void addtext(Pstate* ps, Rune* s); static void addbrk(Pstate* ps, int sp, int clr); static void addlinebrk(Pstate* ps, int clr); static void addnbsp(Pstate* ps); static void changehang(Pstate* ps, int delta); static void changeindent(Pstate* ps, int delta); static int push(Stack* stk, int val); static void pop(Stack* stk); static int popretnewtop(Stack* stk, int dflt); static int top(Stack* stk, int dflt); static void copystack(Stack* tostk, Stack* fromstk); static void popfontstyle(Pstate* ps); static void pushfontstyle(Pstate* ps, int sty); static void popfontsize(Pstate* ps); static void pushfontsize(Pstate* ps, int sz); static void setcurfont(Pstate* ps); static void popjust(Pstate* ps); static void pushjust(Pstate* ps, int j); static void setcurjust(Pstate* ps); static void finish_table(Table* t); static void trim_cell(Tablecell* c); static Rune* listmark(uchar ty, int n); static Map* getmap(Docinfo* di, Rune* name); static Rune* aval(Token* tok, int attid); static Rune* astrval(Token* tok, int attid, Rune* dflt); static int aintval(Token* tok, int attid, int dflt); static int auintval(Token* tok, int attid, int dflt); static int toint(Rune* s); static int atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt); static int acolorval(Token* tok, int attid, int dflt); static int atargval(Token* tok, int dflt); static int listtyval(Token* tok, int dflt); static ParsedUrl* aurlval(Token* tok, int attid, ParsedUrl* dflt, ParsedUrl* base); static Rune* removeallwhite(Rune* s); static int aflagval(Token* tok, int attid); static Align makealign(int halign, int valign); static Align aalign(Token* tok); static Dimen adimen(Token* tok, int attid); static Dimen parsedim(Rune* s, int ns); static void setdimarray(Token* tok, int attid, Dimen** pans, int* panslen); static Rune* stringalign(int a); static int dimprint(char* buf, int nbuf, Dimen d); int Ifmt(Fmt *f); static int validvalign(int a); static int validhalign(int a); static int validalign(Align a); static int validdimen(Dimen d); void buildinit(void) { fmtinstall('I', Ifmt); } // Assume f has been reset, and then had any values from HTTP headers // filled in (e.g., base, chset). ItemSource* newitemsource(ByteSource* bs, Docinfo* di, int mtype) { ItemSource* is; Pstate* ps; ps = newpstate(nil); if(mtype != TextHtml) { ps->curstate &= ~IFwrap; ps->literal = 1; pushfontstyle(ps, FntT); } is = (ItemSource*)emalloc(sizeof(ItemSource)); is->ts = newtokensource(bs, di->chset, mtype); is->mtype = mtype; is->doc = di; is->psstk = ps; is->nforms = 0; is->ntables = 0; is->nanchors = 0; is->nframes = 0; is->curform = nil; is->curmap = nil; is->tabstk = nil; is->kidstk = nil; return is; } // Get a group of tokens for lexer, parse them, and create // a list of layout items. Item* getitems(ItemSource* is) { int i; int j; int nt; int pt; int doscripts; int tokslen; int toki; int h; int sz; int method; int n; int nblank; int norsz; int bramt; int sty; int nosh; int oldcuranchor; int dfltbd; int v; int hang; int newtokslen; int isempty; int ns; int scripttoki; int tag; int brksp; int target; uchar brk; uchar flags; uchar align; uchar al; uchar ty; uchar ty2; Pstate* ps; Pstate* outerps; Table* curtab; Token* tok; Token* toks; Token* newtoks; Token* scripttoks; Docinfo* di; Item* ans; Item* img; Item* ffit; Item* tabitem; Rune* s; Rune* t; Rune* name; Rune* enctype; Rune* usemap; Rune* prompt; Rune* equiv; Rune* val; Rune* nsz; Rune* err; Rune* replace; Rune* script; Map* map; Form* frm; Iimage* ii; Kidinfo* kd; Kidinfo* ks; Kidinfo* pks; Dimen wd; Option* option; Table* tab; Tablecell* c; Tablerow* tr; Formfield* field; Formfield* ff; ParsedUrl* href; ParsedUrl* src; ParsedUrl* scriptsrc; ParsedUrl* bgurl; ParsedUrl* action; Background bg; dbgbuild = config.dbg['h']; doscripts = config.doscripts; ps = is->psstk; curtab = nil; if(is->tabstk != nil) curtab = is->tabstk; toks = nil; tokslen = 0; toki = 0; for(di = is->doc; ; toki++) { if(toki == tokslen) { outerps = lastps(ps); if(outerps->items->next != nil) break; toks = gettoks(is->ts, &tokslen); if(dbgbuild) trace("build: got %d tokens from token source\n", tokslen); if(tokslen == 0) break; toki = 0; } tok = &toks[toki]; if(dbgbuild > 1) trace("build: curstate %ux, token %T\n", ps->curstate, tok); tag = tok->tag; brk = 0; brksp = 0; if(tag < Numtags) { brk = blockbrk[tag]; if(brk&SPBefore) brksp = 1; } else if(tag < Numtags + RBRA) { brk = blockbrk[tag - RBRA]; if(brk&SPAfter) brksp = 1; } if(brk) { addbrk(ps, brksp, 0); if(ps->inpar) { popjust(ps); ps->inpar = 0; } } // check common case first (Data), then case statement on tag if(tag == Data) { // Lexing didn't pay attention to SGML record boundary rules: // \n after start tag or before end tag to be discarded. // (Lex has already discarded all \r's). // Some pages assume this doesn't happen in
 text,
			// so we won't do it if literal is true.
			// BUG: won't discard \n before a start tag that begins
			// the next bufferful of tokens.
			s = tok->text;
			n = Strlen(s);
			if(!ps->literal) {
				i = 0;
				j = n;
				if(toki > 0) {
					pt = toks[toki - 1].tag;
					// IE and Netscape both ignore this rule (contrary to spec)
					// if previous tag was img
					if(pt < Numtags && pt != Timg && j > 0 && s[0] == '\n')
						i++;
				}
				if(toki < tokslen - 1) {
					nt = toks[toki + 1].tag;
					if(nt >= RBRA && nt < Numtags + RBRA && j > i && s[j - 1] == '\n')
						j--;
				}
				if(i > 0 || j < n) {
					s = Strsubstr(s, i, j);
					n = j-i;
				}
			}
			if(ps->skipwhite) {
				trimwhite(s, n, &t, &nt);
				if(t == nil)
					s = nil;
				else if(t != s)
					s = Strndup(t, nt);
				if(s != nil)
					ps->skipwhite = 0;
			}
			tok->text = nil;		// token doesn't own string anymore
			if(s != nil)
				addtext(ps, s);
		}
		else
			switch(tag) {
			// Some abbrevs used in following DTD comments
			// %text = 	#PCDATA
			//		| TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP
			//		| EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE
			//		| A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP
			//		| INPUT | SELECT | TEXTAREA
			// %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER
			//		| BLOCKQUOTE | FORM | ISINDEX | HR | TABLE
			// %flow = (%text | %block)*
			// %body.content = (%heading | %text | %block | ADDRESS)*

			// 
			// Anchors are not supposed to be nested, but you sometimes see
			// href anchors inside destination anchors.
			case Ta:
				if(ps->curanchor != 0) {
					if(warn)
						trace("warning: nested  or missing \n");
					ps->curanchor = 0;
				}
				name = aval(tok, Aname);
				href = aurlval(tok, Ahref, nil, di->base);
				// ignore rel, rev, and title attrs
				if(href != nil) {
					target = atargval(tok, di->target);
					di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors);
					ps->curanchor = is->nanchors;
					ps->curfg = push(&ps->fgstk, di->link);
					ps->curul = push(&ps->ulstk, ULunder);
				}
				if(name != nil) {
					// add a null item to be destination
					additem(ps, newispacer(ISPnull), tok);
					di->dests = newdestanchor(++is->nanchors, name, ps->lastit, di->dests);
				}
				break;

			case Ta+RBRA :
				if(ps->curanchor != 0) {
					ps->curfg = popretnewtop(&ps->fgstk, di->text);
					ps->curul = popretnewtop(&ps->ulstk, ULnone);
					ps->curanchor = 0;
				}
				break;

			// 
			// We can't do applets, so ignore PARAMS, and let
			// the %text contents appear for the alternative rep
			case Tapplet:
			case Tapplet+RBRA:
				if(warn && tag == Tapplet)
					trace("warning:  ignored\n");
				break;

			// 
			case Tarea:
				map = di->maps;
				if(map == nil) {
					if(warn)
						trace("warning:  not inside \n");
					continue;
				}
				map->areas = newarea(atabval(tok, Ashape, shape_tab, NSHAPETAB, SHrect),
					aurlval(tok, Ahref, nil, di->base),
					atargval(tok, di->target),
					map->areas);
				setdimarray(tok, Acoords, &map->areas->coords, &map->areas->ncoords);
				break;

			// 
			case Tb:
			case Tstrong:
				pushfontstyle(ps, FntB);
				break;

			case Tb+RBRA:
			case Tcite+RBRA:
			case Tcode+RBRA:
			case Tdfn+RBRA:
			case Tem+RBRA:
			case Tkbd+RBRA:
			case Ti+RBRA:
			case Tsamp+RBRA:
			case Tstrong+RBRA:
			case Ttt+RBRA:
			case Tvar+RBRA :
			case Taddress+RBRA:
				popfontstyle(ps);
				break;

			// 
			case Tbase:
				di->base = aurlval(tok, Ahref, di->base, di->base);
				di->target = atargval(tok, di->target);
				break;

			// 
			case Tbasefont:
				ps->adjsize = aintval(tok, Asize, 3) - 3;
				break;

			// 
			case Tbig:
			case Tsmall:
				sz = ps->adjsize;
				if(tag == Tbig)
					sz += Large;
				else
					sz += Small;
				pushfontsize(ps, sz);
				break;

			case Tbig+RBRA:
			case Tsmall+RBRA:
				popfontsize(ps);
				break;

			// 
			case Tblockquote:
				changeindent(ps, BQTAB);
				break;

			case Tblockquote+RBRA:
				changeindent(ps, -BQTAB);
				break;

			// 
			case Tbody:
				ps->skipping = 0;
				bg = makebackground(nil, acolorval(tok, Abgcolor, di->background.color));
				bgurl = aurlval(tok, Abackground, nil, di->base);
				if(bgurl != nil) {
					di->backgrounditem = (Iimage*)newiimage(bgurl, nil, ALnone, 0, 0, 0, 0, 0, 0, nil);
					di->backgrounditem->nextimage = di->images;
					di->images = di->backgrounditem;
				}
				ps->curbg = bg;
				di->background = bg;
				di->text = acolorval(tok, Atext, di->text);
				di->link = acolorval(tok, Alink, di->link);
				di->vlink = acolorval(tok, Avlink, di->vlink);
				di->alink = acolorval(tok, Aalink, di->alink);
				if(di->text != ps->curfg) {
					ps->curfg = di->text;
					ps->fgstk.n = 0;
				}
				break;

			case Tbody+RBRA:
				// HTML spec says ignore things after ,
				// but IE and Netscape don't
				// ps.skipping = 1;
				break;

			// 
			case Tbr:
				addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
				break;

			// 
			case Tcaption:
				if(curtab == nil) {
					if(warn)
						trace("warning:  outside \n");
					continue;
				}
				if(curtab->caption != nil) {
					if(warn)
						trace("warning: more than one 
in \n"); continue; } ps = newpstate(ps); curtab->caption_place = atabval(tok, Aalign, align_tab, NALIGNTAB, ALtop); break; case Tcaption+RBRA: if(curtab == nil || ps->next == nil) { if(warn) trace("warning: unexpected \n"); continue; } curtab->caption = ps->items->next; ps = ps->next; break; case Tcenter: case Tdiv: if(tag == Tcenter) al = ALcenter; else al = atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust); pushjust(ps, al); break; case Tcenter+RBRA: case Tdiv+RBRA: popjust(ps); break; // case Tdd: if(ps->hangstk.n == 0) { if(warn) trace("warning:
not inside hangstk, 0); if(h != 0) changehang(ps, -10*LISTTAB); else addbrk(ps, 0, 0); push(&ps->hangstk, 0); break; // // case Tdir: case Tmenu: case Tol: case Tul: changeindent(ps, LISTTAB); push(&ps->listtypestk, listtyval(tok, (tag==Tol)? LT1 : LTdisc)); push(&ps->listcntstk, aintval(tok, Astart, 1)); break; case Tdir+RBRA: case Tmenu+RBRA: case Tol+RBRA: case Tul+RBRA: if(ps->listtypestk.n == 0) { if(warn) trace("warning: %T ended no list\n", tok); continue; } addbrk(ps, 0, 0); pop(&ps->listtypestk); pop(&ps->listcntstk); changeindent(ps, -LISTTAB); break; // case Tdl: changeindent(ps, LISTTAB); push(&ps->hangstk, 0); break; case Tdl+RBRA: if(ps->hangstk.n == 0) { if(warn) trace("warning: unexpected \n"); continue; } changeindent(ps, -LISTTAB); if(top(&ps->hangstk, 0) != 0) changehang(ps, -10*LISTTAB); pop(&ps->hangstk); break; // case Tdt: if(ps->hangstk.n == 0) { if(warn) trace("warning:
not inside
\n"); continue; } h = top(&ps->hangstk, 0); pop(&ps->hangstk); if(h != 0) changehang(ps, -10*LISTTAB); changehang(ps, 10*LISTTAB); push(&ps->hangstk, 1); break; // case Tfont: sz = top(&ps->fntsizestk, Normal); if(tokaval(tok, Asize, &nsz, 0)) { if(prefix(L"+", nsz)) sz = Normal + Strtol(nsz+1, nil, 10) + ps->adjsize; else if(prefix(L"-", nsz)) sz = Normal - Strtol(nsz+1, nil, 10) + ps->adjsize; else if(nsz != nil) sz = Normal + (Strtol(nsz, nil, 10) - 3); } ps->curfg = push(&ps->fgstk, acolorval(tok, Acolor, ps->curfg)); pushfontsize(ps, sz); break; case Tfont+RBRA: if(ps->fgstk.n == 0) { if(warn) trace("warning: unexpected \n"); continue; } ps->curfg = popretnewtop(&ps->fgstk, di->text); popfontsize(ps); break; // case Tform: if(is->curform != nil) { if(warn) trace("warning:
nested inside another\n"); continue; } action = aurlval(tok, Aaction, di->base, di->base); name = astrval(tok, Aname, aval(tok, Aid)); target = atargval(tok, di->target); method = atabval(tok, Amethod, method_tab, NMETHODTAB, HGet); if(warn && tokaval(tok, Aenctype, &enctype, 0) && Strcmp(enctype, L"application/x-www-form-urlencoded")) trace("form enctype %S not handled\n", enctype); frm = newform(++is->nforms, name, action, target, method, di->forms); di->forms = frm; is->curform = frm; break; case Tform+RBRA: if(is->curform == nil) { if(warn) trace("warning: unexpected \n"); continue; } // put fields back in input order is->curform->fields = (Formfield*)revlist((List*)is->curform->fields); is->curform = nil; break; // case Tframe: ks = is->kidstk; if(ks == nil) { if(warn) trace("warning: not in \n"); continue; } ks->kidinfos = kd = newkidinfo(0, ks->kidinfos); kd->src = aurlval(tok, Asrc, nil, di->base); kd->name = aval(tok, Aname); if(kd->name == nil) { s = ltoStr(++is->nframes); kd->name = Strdup2(L"_fr", s); } kd->marginw = auintval(tok, Amarginwidth, 0); kd->marginh = auintval(tok, Amarginheight, 0); kd->framebd = auintval(tok, Aframeborder, 1); kd->flags = atabval(tok, Ascrolling, fscroll_tab, NFSCROLLTAB, kd->flags); norsz = aflagval(tok, Anoresize); if(norsz) kd->flags |= FRnoresize; break; // case Tframeset: ks = newkidinfo(1, nil); pks = is->kidstk; if(pks == nil) di->kidinfo = ks; else { ks->next = pks->kidinfos; pks->kidinfos = ks; } ks->nextframeset = pks; is->kidstk = ks; setdimarray(tok, Arows, &ks->rows, &ks->nrows); if(ks->nrows == 0) { ks->rows = (Dimen*)emalloc(sizeof(Dimen)); ks->nrows = 1; ks->rows[0] = makedimen(Dpercent, 100); } setdimarray(tok, Acols, &ks->cols, &ks->ncols); if(ks->ncols == 0) { ks->cols = (Dimen*)emalloc(sizeof(Dimen)); ks->ncols = 1; ks->cols[0] = makedimen(Dpercent, 100); } break; case Tframeset+RBRA: if(is->kidstk == nil) { if(warn) trace("warning: unexpected \n"); continue; } ks = is->kidstk; // put kids back in original order // and add blank frames to fill out cells n = ks->nrows*ks->ncols; nblank = n - listlen((List*)ks->kidinfos); while(nblank-- > 0) ks->kidinfos = newkidinfo(0, ks->kidinfos); ks->kidinfos = (Kidinfo*)revlist((List*)ks->kidinfos); is->kidstk = is->kidstk->nextframeset; if(is->kidstk == nil) { while(1) { gettoks(is->ts, &tokslen); if(tokslen == 0) break; } return nil; } break; // , etc. case Th1: case Th2: case Th3: case Th4: case Th5: case Th6: bramt = 1; if(ps->items == ps->lastit) bramt = 0; addbrk(ps, bramt, IFcleft|IFcright); sz = Verylarge - (tag - Th1); if(sz < Tiny) sz = Tiny; pushfontsize(ps, sz); sty = top(&ps->fntstylestk, FntR); if(tag == Th1) sty = FntB; pushfontstyle(ps, sty); pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust)); ps->skipwhite = 1; break; case Th1+RBRA: case Th2+RBRA: case Th3+RBRA: case Th4+RBRA: case Th5+RBRA: case Th6+RBRA: addbrk(ps, 1, IFcleft|IFcright); popfontsize(ps); popfontstyle(ps); popjust(ps); break; case Thead: // HTML spec says ignore regular markup in head, // but Netscape and IE don't // ps.skipping = 1; break; case Thead+RBRA: ps->skipping = 0; break; // case Thr: al = atabval(tok, Aalign, align_tab, NALIGNTAB, ALcenter); sz = auintval(tok, Asize, HRSZ); wd = adimen(tok, Awidth); if(dimenkind(wd) == Dnone) wd = makedimen(Dpercent, 100); nosh = aflagval(tok, Anoshade); additem(ps, newirule(al, sz, nosh, wd), tok); addbrk(ps, 0, 0); break; case Ti: case Tcite: case Tdfn: case Tem: case Tvar: case Taddress: pushfontstyle(ps, FntI); break; // case Timg: map = nil; oldcuranchor = ps->curanchor; if(tokaval(tok, Ausemap, &usemap, 0)) { if(!prefix(L"#", usemap)) { if(warn) trace("warning: can't handle non-local map %S\n", usemap); } else { map = getmap(di, usemap+1); if(ps->curanchor == 0) { di->anchors = newanchor(++is->nanchors, nil, nil, di->target, di->anchors); ps->curanchor = is->nanchors; } } } align = atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom); dfltbd = 0; if(ps->curanchor != 0) dfltbd = 2; src = aurlval(tok, Asrc, nil, di->base); if(src == nil) { if(warn) trace("warning: has no src attribute\n"); ps->curanchor = oldcuranchor; continue; } img = newiimage(src, aval(tok, Aalt), align, auintval(tok, Awidth, 0), auintval(tok, Aheight, 0), auintval(tok, Ahspace, IMGHSPACE), auintval(tok, Avspace, IMGVSPACE), auintval(tok, Aborder, dfltbd), aflagval(tok, Aismap), map); if(align == ALleft || align == ALright) { additem(ps, newifloat(img, align), tok); // if no hspace specified, use FLTIMGHSPACE if(!tokaval(tok, Ahspace, &val, 0)) ((Iimage*)img)->hspace = FLTIMGHSPACE; } else { ps->skipwhite = 0; additem(ps, img, tok); } if(!ps->skipping) { ((Iimage*)img)->nextimage = di->images; di->images = (Iimage*)img; } ps->curanchor = oldcuranchor; break; // case Tinput: ps->skipwhite = 0; if(is->curform == nil) { if(warn) trace(" not inside
\n"); continue; } is->curform->fields = field = newformfield( atabval(tok, Atype, input_tab, NINPUTTAB, Ftext), ++is->curform->nfields, is->curform, aval(tok, Aname), aval(tok, Avalue), auintval(tok, Asize, 0), auintval(tok, Amaxlength, 1000), is->curform->fields); if(aflagval(tok, Achecked)) field->flags = FFchecked; switch(field->ftype) { case Ftext: case Fpassword: case Ffile: if(field->size == 0) field->size = 20; break; case Fcheckbox: if(field->name == nil) { if(warn) trace("warning: checkbox form field missing name\n"); continue; } if(field->value == nil) field->value = L"1"; break; case Fradio: if(field->name == nil || field->value == nil) { if(warn) trace("warning: radio form field missing name or value\n"); continue; } break; case Fsubmit: if(field->value == nil) field->value = L"Submit"; if(field->name == nil) field->name = L"_no_name_submit_"; break; case Fimage: src = aurlval(tok, Asrc, nil, di->base); if(src == nil) { if(warn) trace("warning: image form field missing src\n"); continue; } // width and height attrs aren't specified in HTML 3.2, // but some people provide them and they help avoid // a relayout field->image = newiimage(src, astrval(tok, Aalt, L"Submit"), atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom), auintval(tok, Awidth, 0), auintval(tok, Aheight, 0), 0, 0, 0, 0, nil); ii = (Iimage*)field->image; ii->nextimage = di->images; di->images = ii; break; case Freset: if(field->value == nil) field->value = L"Reset"; break; case Fbutton: if(field->value == nil) field->value = L" "; break; } ffit = newiformfield(field); additem(ps, ffit, tok); if(ffit->genattr != nil) field->events = ffit->genattr->events; break; // case Tisindex: ps->skipwhite = 0; prompt = astrval(tok, Aprompt, L"Index search terms:"); if(prompt == nil) prompt = L""; target = atargval(tok, di->target); additem(ps, textit(ps, prompt), tok); frm = newform(++is->nforms, nil, di->base, target, HGet, di->forms); di->forms = frm; ff = newformfield(Ftext, 1, frm, L"_ISINDEX_", nil, 50, 1000, nil); frm->fields = ff; frm->nfields = 1; additem(ps, newiformfield(ff), tok); addbrk(ps, 1, 0); break; // case Tli: if(ps->listtypestk.n == 0) { if(warn) trace("
  • not in list\n"); continue; } ty = top(&ps->listtypestk, 0); ty2 = listtyval(tok, ty); if(ty != ty2) { ty = ty2; push(&ps->listtypestk, ty2); } v = aintval(tok, Avalue, top(&ps->listcntstk, 1)); if(ty == LTdisc || ty == LTsquare || ty == LTcircle) hang = 10*LISTTAB - 3; else hang = 10*LISTTAB - 1; changehang(ps, hang); addtext(ps, listmark(ty, v)); push(&ps->listcntstk, v + 1); changehang(ps, -hang); ps->skipwhite = 1; break; // case Tmap: if(tokaval(tok, Aname, &name, 0)) is->curmap = getmap(di, name); break; case Tmap+RBRA: map = is->curmap; if(map == nil) { if(warn) trace("warning: unexpected \n"); continue; } map->areas = (Area*)revlist((List*)map->areas); break; case Tmeta: if(ps->skipping) continue; if(tokaval(tok, Ahttp_equiv, &equiv, 0)) { val = aval(tok, Acontent); n = Strlen(equiv); if(!Strncmpci(equiv, n, L"refresh")) di->refresh = val; else if(!Strncmpci(equiv, n, L"content-script-type")) { n = Strlen(val); if(!Strncmpci(val, n, L"javascript") || !Strncmpci(val, n, L"jscript1.1") || !Strncmpci(val, n, L"jscript")) di->scripttype = TextJavascript; else { if(warn) trace("unimplemented script type %S\n", val); di->scripttype = UnknownType; } } } break; // Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web case Tnobr: ps->skipwhite = 0; ps->curstate &= ~IFwrap; break; case Tnobr+RBRA: ps->curstate |= IFwrap; break; // We do frames, so skip stuff in noframes case Tnoframes: ps->skipping = 1; break; case Tnoframes+RBRA: ps->skipping = 0; break; // We do scripts (if enabled), so skip stuff in noscripts case Tnoscript: if(doscripts) ps->skipping = 1; break; case Tnoscript+RBRA: if(doscripts) ps->skipping = 0; break; // case Toption: if(is->curform == nil || is->curform->fields == nil) { if(warn) trace("warning: