#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 <PRE> 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)*
// <!ELEMENT A - - (%text) -(A)>
// 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 <A> or missing </A>\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;
// <!ELEMENT APPLET - - (PARAM | %text)* >
// 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: <APPLET> ignored\n");
break;
// <!ELEMENT AREA - O EMPTY>
case Tarea:
map = di->maps;
if(map == nil) {
if(warn)
trace("warning: <AREA> not inside <MAP>\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;
// <!ELEMENT (B|STRONG) - - (%text)*>
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;
// <!ELEMENT BASE - O EMPTY>
case Tbase:
di->base = aurlval(tok, Ahref, di->base, di->base);
di->target = atargval(tok, di->target);
break;
// <!ELEMENT BASEFONT - O EMPTY>
case Tbasefont:
ps->adjsize = aintval(tok, Asize, 3) - 3;
break;
// <!ELEMENT (BIG|SMALL) - - (%text)*>
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;
// <!ELEMENT BLOCKQUOTE - - %body.content>
case Tblockquote:
changeindent(ps, BQTAB);
break;
case Tblockquote+RBRA:
changeindent(ps, -BQTAB);
break;
// <!ELEMENT BODY O O %body.content>
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 </body>,
// but IE and Netscape don't
// ps.skipping = 1;
break;
// <!ELEMENT BR - O EMPTY>
case Tbr:
addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
break;
// <!ELEMENT CAPTION - - (%text;)*>
case Tcaption:
if(curtab == nil) {
if(warn)
trace("warning: <CAPTION> outside <TABLE>\n");
continue;
}
if(curtab->caption != nil) {
if(warn)
trace("warning: more than one <CAPTION> in <TABLE>\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 </CAPTION>\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;
// <!ELEMENT DD - O %flow >
case Tdd:
if(ps->hangstk.n == 0) {
if(warn)
trace("warning: <DD> not inside <DL\n");
continue;
}
h = top(&ps->hangstk, 0);
if(h != 0)
changehang(ps, -10*LISTTAB);
else
addbrk(ps, 0, 0);
push(&ps->hangstk, 0);
break;
//<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) >
//<!ELEMENT (OL|UL) - - (LI)+>
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;
// <!ELEMENT DL - - (DT|DD)+ >
case Tdl:
changeindent(ps, LISTTAB);
push(&ps->hangstk, 0);
break;
case Tdl+RBRA:
if(ps->hangstk.n == 0) {
if(warn)
trace("warning: unexpected </DL>\n");
continue;
}
changeindent(ps, -LISTTAB);
if(top(&ps->hangstk, 0) != 0)
changehang(ps, -10*LISTTAB);
pop(&ps->hangstk);
break;
// <!ELEMENT DT - O (%text)* >
case Tdt:
if(ps->hangstk.n == 0) {
if(warn)
trace("warning: <DT> not inside <DL>\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;
// <!ELEMENT FONT - - (%text)*>
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 </FONT>\n");
continue;
}
ps->curfg = popretnewtop(&ps->fgstk, di->text);
popfontsize(ps);
break;
// <!ELEMENT FORM - - %body.content -(FORM) >
case Tform:
if(is->curform != nil) {
if(warn)
trace("warning: <FORM> 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 </FORM>\n");
continue;
}
// put fields back in input order
is->curform->fields = (Formfield*)revlist((List*)is->curform->fields);
is->curform = nil;
break;
// <!ELEMENT FRAME - O EMPTY>
case Tframe:
ks = is->kidstk;
if(ks == nil) {
if(warn)
trace("warning: <FRAME> not in <FRAMESET>\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;
// <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+>
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 </FRAMESET>\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;
// <!ELEMENT H1 - - (%text;)*>, 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;
// <!ELEMENT HR - O EMPTY>
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;
// <!ELEMENT IMG - O EMPTY>
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: <img> 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;
// <!ELEMENT INPUT - O EMPTY>
case Tinput:
ps->skipwhite = 0;
if(is->curform == nil) {
if(warn)
trace("<INPUT> not inside <FORM>\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;
// <!ENTITY ISINDEX - O EMPTY>
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;
// <!ELEMENT LI - O %flow>
case Tli:
if(ps->listtypestk.n == 0) {
if(warn)
trace("<LI> 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;
// <!ELEMENT MAP - - (AREA)+>
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 </MAP>\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;
// <!ELEMENT OPTION - O ( //PCDATA)>
case Toption:
if(is->curform == nil || is->curform->fields == nil) {
if(warn)
trace("warning: <OPTION> not in <SELECT>\n");
continue;
}
field = is->curform->fields;
if(field->ftype != Fselect) {
if(warn)
trace("warning: <OPTION> not in <SELECT>\n");
continue;
}
val = aval(tok, Avalue);
option = newoption(aflagval(tok, Aselected), val, nil, field->options);
field->options = option;
option->display = getpcdata(toks, tokslen, &toki);
if(val == nil)
option->value = option->display;
break;
// <!ELEMENT P - O (%text)* >
case Tp:
pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
ps->inpar = 1;
ps->skipwhite = 1;
break;
case Tp+RBRA:
break;
// <!ELEMENT PARAM - O EMPTY>
// Do something when we do applets...
case Tparam:
break;
// <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) >
case Tpre:
ps->curstate &= ~IFwrap;
ps->literal = 1;
ps->skipwhite = 0;
pushfontstyle(ps, FntT);
break;
case Tpre+RBRA:
ps->curstate |= IFwrap;
if(ps->literal) {
popfontstyle(ps);
ps->literal = 0;
}
break;
// <!ELEMENT SCRIPT - - CDATA>
case Tscript:
if(doscripts) {
if(!di->hasscripts) {
if(di->scripttype == TextJavascript) {
// TODO: initialize script if nec.
// initjscript(di);
di->hasscripts = 1;
}
}
}
if(!di->hasscripts) {
if(warn)
trace("warning: <SCRIPT> ignored\n");
ps->skipping = 1;
}
else {
scriptsrc = aurlval(tok, Asrc, nil, di->base);
script = nil;
scripttoki = toki;
if(scriptsrc != nil) {
if(warn)
trace("warning: non-local <SCRIPT> ignored\n");
}
else {
script = getpcdata(toks, tokslen, &toki);
}
if(script != nil) {
err = L"no script engine";
replace = nil;
// TODO
// err = evalscript(script, &replace);
if(err != nil) {
if(warn)
trace("Javascript error: %S\n", err);
}
else {
if(di->text != ps->curfg) {
if(ps->fgstk.n == 0)
ps->curfg = di->text;
}
scripttoks = lexstring(replace, &ns);
if(ns > 0) {
// splice scripttoks into toks, replacing <SCRIPT>...</SCRIPT>
if(toki + 1 < tokslen && toks[toki + 1].tag == Tscript + RBRA)
toki++;
newtokslen = tokslen - (toki + 1 - scripttoki) + ns;
newtoks = (Token*)emalloc(newtokslen * sizeof(Token));
memmove(newtoks, toks, scripttoki*sizeof(Token));
memmove(newtoks+scripttoki, scripttoks, ns*sizeof(Token));
if(toki + 1 < tokslen)
memmove(newtoks+scripttoki+ns, toks+toki+1,
tokslen-(toki+1));
toks = newtoks;
tokslen = newtokslen;
toki = scripttoki - 1;
}
}
}
}
break;
case Tscript+RBRA:
ps->skipping = 0;
break;
// <!ELEMENT SELECT - - (OPTION+)>
case Tselect:
if(is->curform == nil) {
if(warn)
trace("<SELECT> not inside <FORM>\n");
continue;
}
field = newformfield(Fselect,
++is->curform->nfields,
is->curform,
aval(tok, Aname),
nil,
auintval(tok, Asize, 0),
0,
is->curform->fields);
is->curform->fields = field;
if(aflagval(tok, Amultiple))
field->flags = FFmultiple;
ffit = newiformfield(field);
additem(ps, ffit, tok);
if(ffit->genattr != nil)
field->events = ffit->genattr->events;
// throw away stuff until next tag (should be <OPTION>)
getpcdata(toks, tokslen, &toki);
break;
case Tselect+RBRA:
if(is->curform == nil || is->curform->fields == nil) {
if(warn)
trace("warning: unexpected </SELECT>\n");
continue;
}
field = is->curform->fields;
if(field->ftype != Fselect)
continue;
// put options back in input order
field->options = (Option*)revlist((List*)field->options);
break;
// <!ELEMENT (STRIKE|U) - - (%text)*>
case Tstrike:
case Tu:
ps->curul = push(&ps->ulstk, (tag==Tstrike)? ULmid : ULunder);
break;
case Tstrike+RBRA:
case Tu+RBRA:
if(ps->ulstk.n == 0) {
if(warn)
trace("warning: unexpected %T\n", tok);
continue;
}
ps->curul = popretnewtop(&ps->ulstk, ULnone);
break;
// <!ELEMENT STYLE - - CDATA>
case Tstyle:
if(warn)
trace("warning: unimplemented <STYLE>\n");
ps->skipping = 1;
break;
case Tstyle+RBRA:
ps->skipping = 0;
break;
// <!ELEMENT (SUB|SUP) - - (%text)*>
case Tsub:
case Tsup:
if(tag == Tsub)
ps->curvoff += SUBOFF;
else
ps->curvoff -= SUPOFF;
push(&ps->voffstk, ps->curvoff);
sz = top(&ps->fntsizestk, Normal);
pushfontsize(ps, sz - 1);
break;
case Tsub+RBRA:
case Tsup+RBRA:
if(ps->voffstk.n == 0) {
if(warn)
trace("warning: unexpected %T\n", tok);
continue;
}
ps->curvoff = popretnewtop(&ps->voffstk, 0);
popfontsize(ps);
break;
// <!ELEMENT TABLE - - (CAPTION?, TR+)>
case Ttable:
ps->skipwhite = 0;
tab = newtable(++is->ntables,
aalign(tok),
adimen(tok, Awidth),
aflagval(tok, Aborder),
auintval(tok, Acellspacing, TABSP),
auintval(tok, Acellpadding, TABPAD),
makebackground(nil, acolorval(tok, Abgcolor, ps->curbg.color)),
tok,
is->tabstk);
is->tabstk = tab;
curtab = tab;
break;
case Ttable+RBRA:
if(curtab == nil) {
if(warn)
trace("warning: unexpected </TABLE>\n");
continue;
}
isempty = (curtab->cells == nil);
if(isempty) {
if(warn)
trace("warning: <TABLE> has no cells\n");
}
else {
ps = finishcell(curtab, ps);
if(curtab->rows != nil)
curtab->rows->flags = 0;
finish_table(curtab);
}
ps->skipping = 0;
if(!isempty) {
tabitem = newitable(curtab);
al = curtab->align.halign;
switch(al) {
case ALleft:
case ALright:
additem(ps, newifloat(tabitem, al), tok);
break;
default:
if(al == ALcenter)
pushjust(ps, ALcenter);
addbrk(ps, 0, 0);
if(ps->inpar) {
popjust(ps);
ps->inpar = 0;
}
additem(ps, tabitem, curtab->tabletok);
if(al == ALcenter)
popjust(ps);
break;
}
}
if(is->tabstk == nil) {
if(warn)
trace("warning: table stack is wrong\n");
}
else
is->tabstk = is->tabstk->next;
curtab->next = di->tables;
di->tables = curtab;
curtab = is->tabstk;
if(!isempty)
addbrk(ps, 0, 0);
break;
// <!ELEMENT (TH|TD) - O %body.content>
// Cells for a row are accumulated in reverse order.
// We push ps on a stack, and use a new one to accumulate
// the contents of the cell.
case Ttd:
case Tth:
if(curtab == nil) {
if(warn)
trace("%T outside <TABLE>\n", tok);
continue;
}
if(ps->inpar) {
popjust(ps);
ps->inpar = 0;
}
ps = finishcell(curtab, ps);
tr = nil;
if(curtab->rows != nil)
tr = curtab->rows;
if(tr == nil || !tr->flags) {
if(warn)
trace("%T outside row\n", tok);
tr = newtablerow(makealign(ALnone, ALnone),
makebackground(nil, curtab->background.color),
TFparsing,
curtab->rows);
curtab->rows = tr;
}
ps = cell_pstate(ps, tag == Tth);
flags = TFparsing;
if(aflagval(tok, Anowrap)) {
flags |= TFnowrap;
ps->curstate &= ~IFwrap;
}
if(tag == Tth)
flags |= TFisth;
c = newtablecell(curtab->cells==nil? 1 : curtab->cells->cellid+1,
auintval(tok, Arowspan, 1),
auintval(tok, Acolspan, 1),
aalign(tok),
adimen(tok, Awidth),
auintval(tok, Aheight, 0),
makebackground(nil, acolorval(tok, Abgcolor, tr->background.color)),
flags,
curtab->cells);
curtab->cells = c;
ps->curbg = c->background;
if(c->align.halign == ALnone) {
if(tr->align.halign != ALnone)
c->align.halign = tr->align.halign;
else if(tag == Tth)
c->align.halign = ALcenter;
else
c->align.halign = ALleft;
}
if(c->align.valign == ALnone) {
if(tr->align.valign != ALnone)
c->align.valign = tr->align.valign;
else
c->align.valign = ALmiddle;
}
c->nextinrow = tr->cells;
tr->cells = c;
break;
case Ttd+RBRA:
case Tth+RBRA:
if(curtab == nil || curtab->cells == nil) {
if(warn)
trace("unexpected %T\n", tok);
continue;
}
ps = finishcell(curtab, ps);
break;
// <!ELEMENT TEXTAREA - - ( //PCDATA)>
case Ttextarea:
if(is->curform == nil) {
if(warn)
trace("<TEXTAREA> not inside <FORM>\n");
continue;
}
field = newformfield(Ftextarea,
++is->curform->nfields,
is->curform,
aval(tok, Aname),
nil,
0,
0,
is->curform->fields);
is->curform->fields = field;
field->rows = auintval(tok, Arows, 3);
field->cols = auintval(tok, Acols, 50);
field->value = getpcdata(toks, tokslen, &toki);
if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttextarea + RBRA)
trace("warning: <TEXTAREA> data ended by %T\n", &toks[toki + 1]);
ffit = newiformfield(field);
additem(ps, ffit, tok);
if(ffit->genattr != nil)
field->events = ffit->genattr->events;
break;
// <!ELEMENT TITLE - - ( //PCDATA)* -(%head.misc)>
case Ttitle:
di->doctitle = getpcdata(toks, tokslen, &toki);
if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttitle + RBRA)
trace("warning: <TITLE> data ended by %T\n", &toks[toki + 1]);
break;
// <!ELEMENT TR - O (TH|TD)+>
// rows are accumulated in reverse order in curtab->rows
case Ttr:
if(curtab == nil) {
if(warn)
trace("warning: <TR> outside <TABLE>\n");
continue;
}
if(ps->inpar) {
popjust(ps);
ps->inpar = 0;
}
ps = finishcell(curtab, ps);
if(curtab->rows != nil)
curtab->rows->flags = 0;
curtab->rows = newtablerow(aalign(tok),
makebackground(nil, acolorval(tok, Abgcolor, curtab->background.color)),
TFparsing,
curtab->rows);
break;
case Ttr+RBRA:
if(curtab == nil || curtab->rows == nil) {
if(warn)
trace("warning: unexpected </TR>\n");
continue;
}
ps = finishcell(curtab, ps);
tr = curtab->rows;
if(tr->cells == nil) {
if(warn)
trace("warning: empty row\n");
curtab->rows = tr->next;
tr->next = nil;
}
else
tr->flags = 0;
break;
// <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*>
case Ttt:
case Tcode:
case Tkbd:
case Tsamp:
pushfontstyle(ps, FntT);
break;
// Tags that have empty action
case Tabbr:
case Tabbr+RBRA:
case Tacronym:
case Tacronym+RBRA:
case Tarea+RBRA:
case Tbase+RBRA:
case Tbasefont+RBRA:
case Tbr+RBRA:
case Tdd+RBRA:
case Tdt+RBRA:
case Tframe+RBRA:
case Thr+RBRA:
case Thtml:
case Thtml+RBRA:
case Timg+RBRA:
case Tinput+RBRA:
case Tisindex+RBRA:
case Tli+RBRA:
case Tlink:
case Tlink+RBRA:
case Tmeta+RBRA:
case Toption+RBRA:
case Tparam+RBRA:
case Ttextarea+RBRA:
case Ttitle+RBRA:
break;
// Tags not implemented
case Tbdo:
case Tbdo+RBRA:
case Tbutton:
case Tbutton+RBRA:
case Tdel:
case Tdel+RBRA:
case Tfieldset:
case Tfieldset+RBRA:
case Tiframe:
case Tiframe+RBRA:
case Tins:
case Tins+RBRA:
case Tlabel:
case Tlabel+RBRA:
case Tlegend:
case Tlegend+RBRA:
case Tobject:
case Tobject+RBRA:
case Toptgroup:
case Toptgroup+RBRA:
case Tspan:
case Tspan+RBRA:
if(warn) {
if(tag > RBRA)
tag -= RBRA;
trace("warning: unimplemented HTML tag: %S\n", tagnames[tag]);
}
break;
default:
if(warn)
trace("warning: unknown HTML tag: %S\n", tok->text);
break;
}
}
if(tokslen == 0) {
// we might have hit eof from lexer
// some pages omit trailing </table>
if(is->ts->b->lim == is->ts->b->hdr->length) {
while(curtab != nil) {
if(warn)
trace("warning: <TABLE> not closed\n");
if(curtab->cells != nil) {
ps = finishcell(curtab, ps);
if(curtab->cells == nil) {
if(warn)
trace("warning: empty table\n");
}
else {
if(curtab->rows != nil)
curtab->rows->flags = 0;
finish_table(curtab);
ps->skipping = 0;
additem(ps, newitable(curtab), curtab->tabletok);
addbrk(ps, 0, 0);
}
}
if(is->tabstk != nil)
is->tabstk = is->tabstk->next;
curtab->next = di->tables;
di->tables = curtab;
curtab = is->tabstk;
}
}
}
outerps = lastps(ps);
ans = outerps->items->next;
// note: ans may be nil and di->kids not nil, if there's a frameset!
outerps->items = newispacer(ISPnull);
outerps->lastit = outerps->items;
is->psstk = ps;
if(ans != nil && di->hasscripts) {
// TODO evalscript(nil);
;
}
if(dbgbuild) {
assert(validitems(ans));
if(ans == nil)
trace("getitems returning nil\n");
else
printitems(ans, "getitems returning:");
}
return ans;
}
static Token*
lexstring(Rune* s, int* panslen)
{
ByteSource* bs;
TokenSource* ts;
Token* ans;
Token* newans;
Token* toks;
int anslen;
int tlen;
bs = newstringbytesource(s);
ts = newtokensource(bs, Unicode, TextHtml);
ans = nil;
anslen = 0;
while(1) {
toks = gettoks(ts, &tlen);
if(tlen == 0)
break;
if(ans != nil) {
newans = (Token*)erealloc(ans, (anslen + tlen) * sizeof(Token));
memmove(newans+anslen, toks, tlen*sizeof(Token));
anslen += tlen;
ans = newans;
}
else {
ans = toks;
anslen = tlen;
}
}
*panslen = anslen;
return ans;
}
// Concatenate together maximal set of Data tokens, starting at toks[toki+1].
// Lexer has ensured that there will either be a following non-data token or
// we will be at eof.
// Return emallocd trimmed concatenation, and update *ptoki to last used toki
static Rune*
getpcdata(Token* toks, int tokslen, int* ptoki)
{
Rune* ans;
Rune* p;
Rune* trimans;
int anslen;
int trimanslen;
int toki;
Token* tok;
ans = nil;
anslen = 0;
// first find length of answer
toki = (*ptoki) + 1;
while(toki < tokslen) {
tok = &toks[toki];
if(tok->tag == Data) {
toki++;
anslen += Strlen(tok->text);
}
else
break;
}
// now make up the initial answer
if(anslen > 0) {
ans = newstr(anslen);
p = ans;
toki = (*ptoki) + 1;
while(toki < tokslen) {
tok = &toks[toki];
if(tok->tag == Data) {
toki++;
p = Stradd(p, tok->text, Strlen(tok->text));
}
else
break;
}
*p = 0;
trimwhite(ans, anslen, &trimans, &trimanslen);
if(trimanslen != anslen) {
ans = Strndup(trimans, trimanslen);
}
}
*ptoki = toki-1;
return ans;
}
// If still parsing head of curtab->cells list, finish it off
// by transferring the items on the head of psstk to the cell.
// Then pop the psstk and return the new psstk.
static Pstate*
finishcell(Table* curtab, Pstate* psstk)
{
Tablecell* c;
c = curtab->cells;
if(c != nil) {
if((c->flags&TFparsing)) {
if(psstk->next == nil) {
if(warn)
trace("warning: parse state stack is wrong\n");
}
else {
c->content = psstk->items->next;
c->flags &= ~TFparsing;
psstk = psstk->next;
}
}
}
return psstk;
}
// Make a new Pstate for a cell, based on the old pstate, oldps.
// Also, put the new ps on the head of the oldps stack.
static Pstate*
cell_pstate(Pstate* oldps, int ishead)
{
Pstate* ps;
int sty;
ps = newpstate(oldps);
ps->skipwhite = 1;
ps->curanchor = oldps->curanchor;
copystack(&ps->fntstylestk, &oldps->fntstylestk);
copystack(&ps->fntsizestk, &oldps->fntsizestk);
ps->curfont = oldps->curfont;
ps->curfg = oldps->curfg;
ps->curbg = oldps->curbg;
copystack(&ps->fgstk, &oldps->fgstk);
ps->adjsize = oldps->adjsize;
if(ishead) {
sty = ps->curfont%NumSize;
ps->curfont = FntB*NumSize + sty;
}
return ps;
}
// Return a new Pstate with default starting state.
// Use link to add it to head of a list, if any.
static Pstate*
newpstate(Pstate* link)
{
Pstate* ps;
ps = (Pstate*)emallocz(sizeof(Pstate));
ps->curfont = DefFnt;
ps->curfg = Black;
ps->curbg.image = nil;
ps->curbg.color = White;
ps->curul = ULnone;
ps->curjust = ALleft;
ps->curstate = IFwrap;
ps->items = newispacer(ISPnull);
ps->lastit = ps->items;
ps->prelastit = nil;
ps->next = link;
return ps;
}
// Return last Pstate on psl list
static Pstate*
lastps(Pstate* psl)
{
assert(psl != nil);
while(psl->next != nil)
psl = psl->next;
return psl;
}
// Add it to end of ps item chain, adding in current state from ps.
// Also, if tok is not nil, scan it for generic attributes and assign
// the genattr field of the item accordingly.
static void
additem(Pstate* ps, Item* it, Token* tok)
{
int aid;
int any;
Rune* i;
Rune* c;
Rune* s;
Rune* t;
Attr* a;
Attr* e;
if(ps->skipping) {
if(warn)
trace("warning: skipping item: %I\n", it);
return;
}
it->anchorid = ps->curanchor;
it->state |= ps->curstate;
if(tok != nil) {
any = 0;
i = nil;
c = nil;
s = nil;
t = nil;
e = nil;
for(a = tok->attr; a != nil; a = a->next) {
aid = a->attid;
if(!attrinfo[aid])
continue;
switch(aid) {
case Aid:
i = a->value;
a->value = nil;
any = 1;
break;
case Aclass:
c = a->value;
a->value = nil;
any = 1;
break;
case Astyle:
s = a->value;
a->value = nil;
any = 1;
break;
case Atitle:
t = a->value;
a->value = nil;
any = 1;
break;
default:
assert(aid >= Aonblur && aid <= Aonunload);
e = newattr(a->attid, a->value, e);
a->value = nil;
any = 1;
break;
}
}
if(any)
it->genattr = newgenattr(i, c, s, t, e);
}
ps->curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
ps->prelastit = ps->lastit;
ps->lastit->next = it;
ps->lastit = it;
}
// Make a text item out of s,
// using current font, foreground, vertical offset and underline state.
static Item*
textit(Pstate* ps, Rune* s)
{
assert(s != nil);
return newitext(s, ps->curfont, ps->curfg, ps->curvoff + Voffbias, ps->curul);
}
// Add text item or items for s, paying attention to
// current font, foreground, baseline offset, underline state,
// and literal mode. Unless we're in literal mode, compress
// whitespace to single blank, and, if curstate has a break,
// trim any leading whitespace. Whether in literal mode or not,
// turn nonbreaking spaces into spacer items with IFnobrk set.
//
// In literal mode, break up s at newlines and add breaks instead.
// Also replace tabs appropriate number of spaces.
// In nonliteral mode, break up the items every 100 or so characters
// just to make the layout algorithm not go quadratic.
static void
addtext(Pstate* ps, Rune* s)
{
int n;
int i;
int j;
int k;
int col;
int c;
int nsp;
Item* it;
Rune* ss;
Rune* p;
Rune buf[SMALLBUFSIZE];
assert(s != nil);
n = Strlen(s);
i = 0;
j = 0;
if(ps->literal) {
col = 0;
while(i < n) {
if(s[i] == '\n') {
if(i > j) {
// trim trailing blanks from line
for(k = i; k > j; k--)
if(s[k - 1] != ' ')
break;
if(k > j)
additem(ps, textit(ps, Strndup(s+j, k-j)), nil);
}
addlinebrk(ps, 0);
j = i + 1;
col = 0;
}
else {
if(s[i] == '\t') {
col += i - j;
nsp = 8 - (col%8);
// make ss = s[j:i] + nsp spaces
ss = newstr(i-j+nsp);
p = Stradd(ss, s+j, i-j);
p = Stradd(p, L" ", nsp);
*p = 0;
additem(ps, textit(ps, ss), nil);
col += nsp;
j = i + 1;
}
else if(s[i] == NBSP) {
if(i > j)
additem(ps, textit(ps, Strndup(s+j, i-j)), nil);
addnbsp(ps);
col += (i - j) + 1;
j = i + 1;
}
}
i++;
}
if(i > j) {
if(j == 0 && i == n) {
// just transfer s over
additem(ps, textit(ps, s), nil);
}
else
additem(ps, textit(ps, Strndup(s+j, i-j)), nil);
}
}
else { // not literal mode
if((ps->curstate&IFbrk) || ps->lastit == ps->items)
while(i < n) {
c = s[i];
if(c >= 256 || !isspace(c))
break;
i++;
}
p = buf;
for(j = i; i < n; i++) {
assert(p+i-j < buf+SMALLBUFSIZE-1);
c = s[i];
if(c == NBSP) {
if(i > j)
p = Stradd(p, s+j, i-j);
if(p > buf)
additem(ps, textit(ps, Strndup(buf, p-buf)), nil);
p = buf;
addnbsp(ps);
j = i + 1;
continue;
}
if(c < 256 && isspace(c)) {
if(i > j)
p = Stradd(p, s+j, i-j);
*p++ = ' ';
while(i < n - 1) {
c = s[i + 1];
if(c >= 256 || !isspace(c))
break;
i++;
}
j = i + 1;
}
if(i - j >= 100) {
p = Stradd(p, s+j, i+1-j);
j = i + 1;
}
if(p-buf >= 100) {
additem(ps, textit(ps,Strndup(buf, p-buf)), nil);
p = buf;
}
}
if(i > j && j < n) {
assert(p+i-j < buf+SMALLBUFSIZE-1);
p = Stradd(p, s+j, i-j);
}
// don't add a space if previous item ended in a space
if(p-buf == 1 && buf[0] == ' ' && ps->lastit != nil) {
it = ps->lastit;
if(it->tag == Itexttag) {
ss = ((Itext*)it)->s;
k = Strlen(ss);
if(k > 0 && ss[k] == ' ')
p = buf;
}
}
if(p > buf)
additem(ps, textit(ps, Strndup(buf, p-buf)), nil);
}
}
// Add a break to ps->curstate, with extra space if sp is true.
// If there was a previous break, combine this one's parameters
// with that to make the amt be the max of the two and the clr
// be the most general. (amt will be 0 or 1)
// Also, if the immediately preceding item was a text item,
// trim any whitespace from the end of it, if not in literal mode.
// Finally, if this is at the very beginning of the item list
// (the only thing there is a null spacer), then don't add the space.
static void
addbrk(Pstate* ps, int sp, int clr)
{
int state;
Rune* l;
int nl;
Rune* r;
int nr;
Itext* t;
state = ps->curstate;
clr = clr|(state&(IFcleft|IFcright));
if(sp && !(ps->lastit == ps->items))
sp = IFbrksp;
else
sp = 0;
ps->curstate = IFbrk|sp|(state&~(IFcleft|IFcright))|clr;
if(ps->lastit != ps->items) {
if(!ps->literal && ps->lastit->tag == Itexttag) {
t = (Itext*)ps->lastit;
splitr(t->s, Strlen(t->s), notwhitespace, &l, &nl, &r, &nr);
// try to avoid making empty items
// but not crucial f the occasional one gets through
if(nl == 0 && ps->prelastit != nil) {
ps->lastit = ps->prelastit;
ps->lastit->next = nil;
ps->prelastit = nil;
}
else {
if(nl == 0)
t->s = L"";
else
t->s = Strndup(l, nl);
}
}
}
}
// Add break due to a <br> or a newline within a preformatted section.
// We add a null item first, with current font's height and ascent, to make
// sure that the current line takes up at least that amount of vertical space.
// This ensures that <br>s on empty lines cause blank lines, and that
// multiple <br>s in a row give multiple blank lines.
// However don't add the spacer if the previous item was something that
// takes up space itself.
static void
addlinebrk(Pstate* ps, int clr)
{
int obrkstate;
int b;
int addit;
// don't want break before our null item unless the previous item
// was also a null item for the purposes of line breaking
obrkstate = ps->curstate&(IFbrk|IFbrksp);
b = IFnobrk;
addit = 0;
if(ps->lastit != nil) {
if(ps->lastit->tag == Ispacertag) {
if(((Ispacer*)ps->lastit)->spkind == ISPvline)
b = IFbrk;
addit = 1;
}
else if(ps->lastit->tag == Ifloattag)
addit = 1;
}
if(addit) {
ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|b;
additem(ps, newispacer(ISPvline), nil);
ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|obrkstate;
}
addbrk(ps, 0, clr);
}
// Add a nonbreakable space
static void
addnbsp(Pstate* ps)
{
// if nbsp comes right where a break was specified,
// do the break anyway (nbsp is being used to generate undiscardable
// space rather than to prevent a break)
if((ps->curstate&IFbrk) == 0)
ps->curstate |= IFnobrk;
additem(ps, newispacer(ISPhspace), nil);
// but definitely no break on next item
ps->curstate |= IFnobrk;
}
// Change hang in ps.curstate by delta.
// The amount is in 1/10ths of tabs, and is the amount that
// the current contiguous set of items with a hang value set
// is to be shifted left from its normal (indented) place.
static void
changehang(Pstate* ps, int delta)
{
int amt;
amt = (ps->curstate&IFhangmask) + delta;
if(amt < 0) {
if(warn)
trace("warning: hang went negative\n");
amt = 0;
}
ps->curstate = (ps->curstate&~IFhangmask)|amt;
}
// Change indent in ps.curstate by delta.
static void
changeindent(Pstate* ps, int delta)
{
int amt;
amt = ((ps->curstate&IFindentmask) >> IFindentshift) + delta;
if(amt < 0) {
if(warn)
trace("warning: indent went negative\n");
amt = 0;
}
ps->curstate = (ps->curstate&~IFindentmask)|(amt << IFindentshift);
}
// Push val on top of stack, and also return value pushed
static int
push(Stack* stk, int val)
{
if(stk->n == Nestmax) {
if(warn)
trace("warning: build stack overflow\n");
}
else
stk->slots[stk->n++] = val;
return val;
}
// Pop top of stack
static void
pop(Stack* stk)
{
if(stk->n > 0)
--stk->n;
}
//Return top of stack, using dflt if stack is empty
static int
top(Stack* stk, int dflt)
{
if(stk->n == 0)
return dflt;
return stk->slots[stk->n-1];
}
// pop, then return new top, with dflt if empty
static int
popretnewtop(Stack* stk, int dflt)
{
if(stk->n == 0)
return dflt;
stk->n--;
if(stk->n == 0)
return dflt;
return stk->slots[stk->n-1];
}
// Copy fromstk entries into tostk
static void
copystack(Stack* tostk, Stack* fromstk)
{
int n;
n = fromstk->n;
tostk->n = n;
memmove(tostk->slots, fromstk->slots, n*sizeof(int));
}
static void
popfontstyle(Pstate* ps)
{
pop(&ps->fntstylestk);
setcurfont(ps);
}
static void
pushfontstyle(Pstate* ps, int sty)
{
push(&ps->fntstylestk, sty);
setcurfont(ps);
}
static void
popfontsize(Pstate* ps)
{
pop(&ps->fntsizestk);
setcurfont(ps);
}
static void
pushfontsize(Pstate* ps, int sz)
{
push(&ps->fntsizestk, sz);
setcurfont(ps);
}
static void
setcurfont(Pstate* ps)
{
int sty;
int sz;
sty = top(&ps->fntstylestk, FntR);
sz = top(&ps->fntsizestk, Normal);
if(sz < Tiny)
sz = Tiny;
if(sz > Verylarge)
sz = Verylarge;
ps->curfont = sty*NumSize + sz;
}
static void
popjust(Pstate* ps)
{
pop(&ps->juststk);
setcurjust(ps);
}
static void
pushjust(Pstate* ps, int j)
{
push(&ps->juststk, j);
setcurjust(ps);
}
static void
setcurjust(Pstate* ps)
{
int j;
int state;
j = top(&ps->juststk, ALleft);
if(j != ps->curjust) {
ps->curjust = j;
state = ps->curstate;
state &= ~(IFrjust|IFcjust);
if(j == ALcenter)
state |= IFcjust;
else if(j == ALright)
state |= IFrjust;
ps->curstate = state;
}
}
// Do final rearrangement after table parsing is finished
// and assign cells to grid points
static void
finish_table(Table* t)
{
int ncol;
int nrow;
int r;
Tablerow* rl;
Tablecell* cl;
int* rowspancnt;
Tablecell** rowspancell;
int ri;
int ci;
Tablecell* c;
Tablecell* cnext;
Tablerow* row;
Tablerow* rownext;
int rcols;
int newncol;
int k;
int j;
int cspan;
int rspan;
int i;
rl = t->rows;
t->nrow = nrow = listlen((List*)rl);
t->rows = (Tablerow*)emalloc(nrow * sizeof(Tablerow));
ncol = 0;
r = nrow - 1;
for(row = rl; row != nil; row = rownext) {
// copy the data from the allocated Tablerow into the array slot
t->rows[r] = *row;
rownext = row->next;
row = &t->rows[r];
r--;
rcols = 0;
c = row->cells;
// If rowspan is > 1 but this is the last row,
// reset the rowspan
if(c != nil && c->rowspan > 1 && r == nrow-2)
c->rowspan = 1;
// reverse row->cells list (along nextinrow pointers)
row->cells = nil;
while(c != nil) {
cnext = c->nextinrow;
c->nextinrow = row->cells;
row->cells = c;
rcols += c->colspan;
c = cnext;
}
if(rcols > ncol)
ncol = rcols;
}
t->ncol = ncol;
t->cols = (Tablecol*)emallocz(ncol * sizeof(Tablecol));
// Reverse cells just so they are drawn in source order.
// Also, trim their contents so they don't end in whitespace.
t->cells = (Tablecell*)revlist((List*)t->cells);
for(c = t->cells; c != nil; c= c->next)
trim_cell(c);
t->grid = (Tablecell***)emalloc(nrow * sizeof(Tablecell**));
for(i = 0; i < nrow; i++)
t->grid[i] = (Tablecell**)emallocz(ncol * sizeof(Tablecell*));
// The following arrays keep track of cells that are spanning
// multiple rows; rowspancnt[i] is the number of rows left
// to be spanned in column i.
// When done, cell's (row,col) is upper left grid point.
rowspancnt = (int*)emallocz(ncol * sizeof(int));
rowspancell = (Tablecell**)emallocz(ncol * sizeof(Tablecell*));
for(ri = 0; ri < nrow; ri++) {
row = &t->rows[ri];
cl = row->cells;
ci = 0;
while(ci < ncol || cl != nil) {
if(ci < ncol && rowspancnt[ci] > 0) {
t->grid[ri][ci] = rowspancell[ci];
rowspancnt[ci]--;
ci++;
}
else {
if(cl == nil) {
ci++;
continue;
}
c = cl;
cl = cl->nextinrow;
cspan = c->colspan;
rspan = c->rowspan;
if(ci + cspan > ncol) {
// because of row spanning, we calculated
// ncol incorrectly; adjust it
newncol = ci + cspan;
t->cols = (Tablecol*)erealloc(t->cols, newncol * sizeof(Tablecol));
rowspancnt = (int*)erealloc(rowspancnt, newncol * sizeof(int));
rowspancell = (Tablecell**)erealloc(rowspancell, newncol * sizeof(Tablecell*));
k = newncol-ncol;
memset(t->cols+ncol, 0, k*sizeof(Tablecol));
memset(rowspancnt+ncol, 0, k*sizeof(int));
memset(rowspancell+ncol, 0, k*sizeof(Tablecell*));
for(j = 0; j < nrow; j++) {
t->grid[j] = (Tablecell**)erealloc(t->grid[j], newncol * sizeof(Tablecell*));
memset(t->grid[j], 0, k*sizeof(Tablecell*));
}
t->ncol = ncol = newncol;
}
c->row = ri;
c->col = ci;
for(i = 0; i < cspan; i++) {
t->grid[ri][ci] = c;
if(rspan > 1) {
rowspancnt[ci] = rspan - 1;
rowspancell[ci] = c;
}
ci++;
}
}
}
}
}
// Remove tail of cell content until it isn't whitespace.
static void
trim_cell(Tablecell* c)
{
int dropping;
Rune* s;
Rune* x;
Rune* y;
int nx;
int ny;
Item* p;
Itext* q;
Item* pprev;
dropping = 1;
while(c->content != nil && dropping) {
p = c->content;
pprev = nil;
while(p->next != nil) {
pprev = p;
p = p->next;
}
dropping = 0;
if(!(p->state&IFnobrk)) {
if(p->tag == Itexttag) {
q = (Itext*)p;
s = q->s;
splitr(s, Strlen(s), notwhitespace, &x, &nx, &y, &ny);
if(nx != 0 && ny != 0)
q->s = Strndup(x, nx);
break;
}
}
if(dropping) {
if(pprev == nil)
c->content = nil;
else
pprev->next = nil;
}
}
}
static Rune*
listmark(uchar ty, int n)
{
Rune* s;
Rune* t;
int n2;
int i;
s = nil;
switch(ty) {
case LTdisc:
case LTsquare:
case LTcircle:
s = newstr(1);
s[0] = (ty == LTdisc)? 0x2022 // bullet
: ((ty == LTsquare)? 0x220e // filled square
: 0x2218); // degree
s[1] = 0;
break;
case LT1:
t = ltoStr(n);
n2 = Strlen(t);
s = newstr(n2+1);
t = Stradd(s, t, n2);
*t++ = '.';
*t = 0;
break;
case LTa:
case LTA:
n--;
i = 0;
if(n < 0)
n = 0;
s = newstr((n <= 25)? 2 : 3);
if(n > 25) {
n2 = n%26;
n /= 26;
if(n2 > 25)
n2 = 25;
s[i++] = n2 + (ty == LTa)? 'a' : 'A';
}
s[i++] = n + (ty == LTa)? 'a' : 'A';
s[i++] = '.';
s[i] = 0;
break;
case LTi:
case LTI:
if(n >= NROMAN) {
if(warn)
trace("warning: unimplemented roman number > %d\n", NROMAN);
n = NROMAN;
}
t = roman[n - 1];
n2 = Strlen(t);
s = newstr(n2+1);
for(i = 0; i < n2; i++)
s[i] = (ty == LTi)? tolower(t[i]) : t[i];
s[i++] = '.';
s[i] = 0;
break;
}
return s;
}
// Find map with given name in di.maps.
// If not there, add one, copying name.
static Map*
getmap(Docinfo* di, Rune* name)
{
Map* m;
for(m = di->maps; m != nil; m = m->next) {
if(!Strcmp(name, m->name))
return m;
}
m = (Map*)emalloc(sizeof(Map));
m->name = name;
m->areas = nil;
m->next = di->maps;
di->maps = m;
return m;
}
Area*
newarea(int shape, ParsedUrl* href, int target, Area* link)
{
Area* a;
a = (Area*)emallocz(sizeof(Area));
a->shape = shape;
a->href = href;
a->target = target;
a->next = link;
return a;
}
// Return string value associated with attid in tok, nil if none.
static Rune*
aval(Token* tok, int attid)
{
Rune* ans;
tokaval(tok, attid, &ans, 1);
return ans;
}
// Like aval, but use dflt if there was no such attribute in tok.
static Rune*
astrval(Token* tok, int attid, Rune* dflt)
{
Rune* ans;
if(tokaval(tok, attid, &ans, 1))
return ans;
else
return dflt;
}
// Here we're supposed to convert to an int,
// and have a default when not found
static int
aintval(Token* tok, int attid, int dflt)
{
Rune* ans;
if(!tokaval(tok, attid, &ans, 0) || ans == nil)
return dflt;
else
return toint(ans);
}
// Like aintval, but result should be >= 0
static int
auintval(Token* tok, int attid, int dflt)
{
Rune* ans;
if(!tokaval(tok, attid, &ans, 0) || ans == nil)
return dflt;
else
return max(toint(ans), 0);
}
// int conversion, but with possible error check (if warning)
static int
toint(Rune* s)
{
int ans;
Rune* eptr;
ans = Strtol(s, &eptr, 10);
if(warn) {
if(*eptr != 0) {
eptr = Strclass(eptr, notwhitespace);
if(eptr != nil)
trace("warning: expected integer, got %S\n", s);
}
}
return ans;
}
// Attribute value when need a table to convert strings to ints
static int
atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt)
{
Rune* aval;
int ans;
ans = dflt;
if(tokaval(tok, attid, &aval, 0)) {
if(!lookup(tab, ntab, aval, Strlen(aval), &ans)) {
ans = dflt;
if(warn)
trace("warning: name not found in table lookup: %S\n", aval);
}
}
return ans;
}
// Attribute value when supposed to be a color
static int
acolorval(Token* tok, int attid, int dflt)
{
Rune* aval;
int ans;
ans = dflt;
if(tokaval(tok, attid, &aval, 0))
ans = color(aval, dflt);
return ans;
}
// Attribute value when supposed to be a target frame name
static int
atargval(Token* tok, int dflt)
{
int ans;
Rune* aval;
ans = dflt;
if(tokaval(tok, Atarget, &aval, 0))
ans = targetid(aval);
return ans;
}
// special for list types, where "i" and "I" are different,
// but "square" and "SQUARE" are the same
static int
listtyval(Token* tok, int dflt)
{
Rune* aval;
int ans;
int n;
ans = dflt;
if(tokaval(tok, Atype, &aval, 0)) {
n = Strlen(aval);
if(n == 1) {
switch(aval[0]) {
case '1':
ans = LT1;
break;
case 'A':
ans = LTA;
break;
case 'I':
ans = LTI;
break;
case 'a':
ans = LTa;
break;
case 'i':
ans = LTi;
default:
if(warn)
trace("warning: unknown list element type %c\n", aval[0]);
}
}
else {
if(!Strncmpci(aval, n, L"circle"))
ans = LTcircle;
else if(!Strncmpci(aval, n, L"disc"))
ans = LTdisc;
else if(!Strncmpci(aval, n, L"square"))
ans = LTsquare;
else {
if(warn)
trace("warning: unknown list element type %S\n", aval);
}
}
}
return ans;
}
// Attribute value when value is a URL, possibly relative to base.
static ParsedUrl*
aurlval(Token* tok, int attid, ParsedUrl* dflt, ParsedUrl* base)
{
ParsedUrl* ans;
Rune* url;
ans = nil;
if(tokaval(tok, attid, &url, 0) && url != nil) {
url = removeallwhite(url);
ans = makeurl(url, 0);
if(ans->scheme == UNKNOWN) {
if(warn)
trace("warning: couldn't parse URL %S\n", url);
ans = dflt;
}
else if(base != nil)
ans = makeabsoluteurl(ans, base);
}
if(ans == nil)
ans = dflt;
return ans;
}
// Return s but with all whitespace (even internal) removed.
// This fixes some buggy URL specification strings.
static Rune*
removeallwhite(Rune* s)
{
int j;
int n;
int i;
int c;
Rune* ans;
j = 0;
n = Strlen(s);
for(i = 0; i < n; i++) {
c = s[i];
if(c >= 256 || !isspace(c))
j++;
}
if(j < n) {
ans = newstr(j);
j = 0;
for(i = 0; i < n; i++) {
c = s[i];
if(c >= 256 || !isspace(c))
ans[j++] = c;
}
ans[j] = 0;
return ans;
}
return s;
}
// Attribute value when mere presence of attr implies value of 1,
// but if there is an integer there, return it as the value.
static int
aflagval(Token* tok, int attid)
{
int val;
Rune* sval;
val = 0;
if(tokaval(tok, attid, &sval, 0)) {
val = 1;
if(sval != nil)
val = toint(sval);
}
return val;
}
static Align
makealign(int halign, int valign)
{
Align al;
al.halign = halign;
al.valign = valign;
return al;
}
// Make an Align (two alignments, horizontal and vertical)
static Align
aalign(Token* tok)
{
return makealign(
atabval(tok, Aalign, align_tab, NALIGNTAB, ALnone),
atabval(tok, Avalign, align_tab, NALIGNTAB, ALnone));
}
// Make a Dimen, based on value of attid attr
static Dimen
adimen(Token* tok, int attid)
{
Rune* wd;
if(tokaval(tok, attid, &wd, 0))
return parsedim(wd, Strlen(wd));
else
return makedimen(Dnone, 0);
}
// Parse s[0:n] as num[.[num]][unit][%|*]
static Dimen
parsedim(Rune* s, int ns)
{
int kind;
int spec;
Rune* l;
int nl;
Rune* r;
int nr;
int mul;
int i;
Rune* f;
int nf;
int Tkdpi;
Rune* units;
kind = Dnone;
spec = 0;
splitl(s, ns, L"^0-9", &l, &nl, &r, &nr);
if(nl != 0) {
spec = 1000*Strtol(l, nil, 10);
if(nr > 0 && r[0] == '.') {
splitl(r+1, nr-1, L"^0-9", &f, &nf, &r, &nr);
if(nf != 0) {
mul = 100;
for(i = 0; i < nf; i++) {
spec = spec + mul*(f[i]-'0');
mul = mul/10;
}
}
}
kind = Dpixels;
if(nr != 0) {
if(nr >= 2) {
Tkdpi = 100;
units = r;
r = r+2;
nr -= 2;
if(!Strncmpci(units, 2, L"pt"))
spec = (spec*Tkdpi)/72;
else if(!Strncmpci(units, 2, L"pi"))
spec = (spec*12*Tkdpi)/72;
else if(!Strncmpci(units, 2, L"in"))
spec = spec*Tkdpi;
else if(!Strncmpci(units, 2, L"cm"))
spec = (spec*100*Tkdpi)/254;
else if(!Strncmpci(units, 2, L"mm"))
spec = (spec*10*Tkdpi)/254;
else if(!Strncmpci(units, 2, L"em"))
spec = spec*15;
else {
if(warn)
trace("warning: unknown units %C%Cs\n", units[0], units[1]);
}
}
if(nr >= 1) {
if(r[0] == '%')
kind = Dpercent;
else if(r[0] == '*')
kind = Drelative;
}
}
spec = spec/1000;
}
else if(nr == 1 && r[0] == '*') {
spec = 1;
kind = Drelative;
}
return makedimen(kind, spec);
}
static void
setdimarray(Token* tok, int attid, Dimen** pans, int* panslen)
{
Rune* s;
Dimen* d;
int k;
int nc;
Rune* a[SMALLBUFSIZE];
int an[SMALLBUFSIZE];
if(tokaval(tok, attid, &s, 0)) {
nc = splitall(s, Strlen(s), L", ", a, an, SMALLBUFSIZE);
if(nc > 0) {
d = (Dimen*)emalloc(nc * sizeof(Dimen));
for(k = 0; k < nc; k++) {
d[k] = parsedim(a[k], an[k]);
}
*pans = d;
*panslen = nc;
return;
}
}
*pans = nil;
*panslen = 0;
}
Background
makebackground(CImage* ci, int color)
{
Background bg;
bg.image = ci;
bg.color = color;
return bg;
}
Item*
newitext(Rune* s, int fnt, int fg, int voff, int ul)
{
Itext* t;
assert(s != nil);
t = (Itext*)emallocz(sizeof(Itext));
t->tag = Itexttag;
t->s = s;
t->fnt = fnt;
t->fg = fg;
t->voff = voff;
t->ul = ul;
return (Item*)t;
}
Item*
newirule(int align, int size, int noshade, Dimen wspec)
{
Irule* r;
r = (Irule*)emallocz(sizeof(Irule));
r->tag = Iruletag;
r->align = align;
r->size = size;
r->noshade = noshade;
r->wspec = wspec;
return (Item*)r;
}
Item*
newiimage(ParsedUrl* src, Rune* altrep, int align, int width, int height,
int hspace, int vspace, int border, int ismap, Map* map)
{
Iimage* i;
CImage* ci;
int state;
ci = newcimage(src, width, height);
state = 0;
if(ismap)
state = IFsmap;
i = (Iimage*)emallocz(sizeof(Iimage));
i->tag = Iimagetag;
i->state = state;
i->ci = ci;
i->altrep = altrep;
i->align = align;
i->imwidth = width;
i->imheight = height;
i->hspace = hspace;
i->vspace = vspace;
i->border = border;
i->map = map;
i->ctlid = -1;
return (Item*)i;
}
Item*
newiformfield(Formfield* ff)
{
Iformfield* f;
f = (Iformfield*)emallocz(sizeof(Iformfield));
f->tag = Iformfieldtag;
f->formfield = ff;
return (Item*)f;
}
Item*
newitable(Table* tab)
{
Itable* t;
t = (Itable*)emallocz(sizeof(Itable));
t->tag = Itabletag;
t->table = tab;
return (Item*)t;
}
Item*
newifloat(Item* it, int side)
{
Ifloat* f;
f = (Ifloat*)emallocz(sizeof(Ifloat));
f->tag = Ifloattag;
f->state = IFwrap;
f->item = it;
f->side = side;
return (Item*)f;
}
Item*
newispacer(int spkind)
{
Ispacer* s;
s = (Ispacer*)emallocz(sizeof(Ispacer));
s->tag = Ispacertag;
s->spkind = spkind;
return (Item*)s;
}
Itemlist*
newitemlist(Item* val, Itemlist* rest)
{
Itemlist* l;
l = (Itemlist*)emalloc(sizeof(Itemlist));
l->val = val;
l->next = rest;
return l;
}
int
Ifmt(Fmt *f)
{
Item* it;
Itext* t;
Irule* r;
Iimage* i;
Ifloat* fl;
int state;
Formfield* ff;
Rune* ty;
Tablecell* c;
Table* tab;
char* p;
int cl;
int hang;
int indent;
int bi;
int nbuf;
char buf[BIGBUFSIZE];
it = va_arg(f->args, Item*);
bi = 0;
nbuf = sizeof(buf);
state = it->state;
nbuf = nbuf-1;
if(state&IFbrk) {
cl = state&(IFcleft|IFcright);
p = "";
if(cl) {
if(cl == (IFcleft|IFcright))
p = " both";
else if(cl == IFcleft)
p = " left";
else
p = " right";
}
bi = snprint(buf, nbuf, "brk(%d%s)", (state&IFbrksp)? 1 : 0, p);
}
if(state&IFnobrk)
bi += snprint(buf+bi, nbuf-bi, " nobrk");
if(!(state&IFwrap))
bi += snprint(buf+bi, nbuf-bi, " nowrap");
if(state&IFrjust)
bi += snprint(buf+bi, nbuf-bi, " rjust");
if(state&IFcjust)
bi += snprint(buf+bi, nbuf-bi, " cjust");
if(state&IFsmap)
bi += snprint(buf+bi, nbuf-bi, " smap");
indent = (state&IFindentmask) >> IFindentshift;
if(indent > 0)
bi += snprint(buf+bi, nbuf-bi, " indent=%d", indent);
hang = state&IFhangmask;
if(hang > 0)
bi += snprint(buf+bi, nbuf-bi, " hang=%d", hang);
switch(it->tag) {
case Itexttag:
t = (Itext*)it;
bi += snprint(buf+bi, nbuf-bi, " Text '%S', fnt=%d, fg=%x", t->s, t->fnt, t->fg);
break;
case Iruletag:
r = (Irule*)it;
bi += snprint(buf+bi, nbuf-bi, "Rule size=%d, al=%S, wspec=", r->size, stringalign(r->align));
bi += dimprint(buf+bi, nbuf-bi, r->wspec);
break;
case Iimagetag:
i = (Iimage*)it;
bi += snprint(buf+bi, nbuf-bi,
"Image src=%U, alt=%S, al=%S, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%S",
i->ci->src, i->altrep? i->altrep : L"", stringalign(i->align), i->imwidth, i->imheight,
i->hspace, i->vspace, i->border, i->map? i->map->name : L"");
break;
case Iformfieldtag:
ff = ((Iformfield*)it)->formfield;
if(ff->ftype == Ftextarea)
ty = L"textarea";
else if(ff->ftype == Fselect)
ty = L"select";
else {
ty = revlookup(input_tab, NINPUTTAB, ff->ftype);
if(ty == nil)
ty = L"none";
}
bi += snprint(buf+bi, nbuf-bi, "Formfield %S, fieldid=%d, formid=%d, name=%S, value=%S",
ty, ff->fieldid, ff->form->formid, ff->name? ff->name : L"",
ff->value? ff->value : L"");
break;
case Itabletag:
tab = ((Itable*)it)->table;
bi += snprint(buf+bi, nbuf-bi, "Table tableid=%d, width=", tab->tableid);
bi += dimprint(buf+bi, nbuf-bi, tab->width);
bi += snprint(buf+bi, nbuf-bi, ", nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
tab->nrow, tab->ncol, tab->ncell, tab->totw, tab->toth);
for(c = tab->cells; c != nil; c = c->next)
bi += snprint(buf+bi, nbuf-bi, "Cell %d.%d, at (%d,%d) ",
tab->tableid, c->cellid, c->row, c->col);
bi += snprint(buf+bi, nbuf-bi, "End of Table %d", tab->tableid);
break;
case Ifloattag:
fl = (Ifloat*)it;
bi += snprint(buf+bi, nbuf-bi, "Float, x=%d y=%d, side=%S, it=%I",
fl->x, fl->y, stringalign(fl->side), fl->item);
bi += snprint(buf+bi, nbuf-bi, "\n\t");
break;
case Ispacertag:
p = "";
switch(((Ispacer*)it)->spkind) {
case ISPnull:
p = "null";
break;
case ISPvline:
p = "vline";
break;
case ISPhspace:
p = "hspace";
break;
}
bi += snprint(buf+bi, nbuf-bi, "Spacer %s ", p);
break;
}
bi += snprint(buf+bi, nbuf-bi, " w=%d, h=%d, a=%d, anchor=%d\n",
it->width, it->height, it->ascent, it->anchorid);
buf[bi] = 0;
return fmtstrcpy(f, buf);
}
// String version of alignment 'a'
static Rune*
stringalign(int a)
{
Rune* s;
s = revlookup(align_tab, NALIGNTAB, a);
if(!Strcmp(s, nil))
s = L"none";
return s;
}
// Put at most nbuf chars of representation of d into buf,
// and return number of characters put
static int
dimprint(char* buf, int nbuf, Dimen d)
{
int n;
int k;
n = 0;
n += snprint(buf, nbuf, "%d", dimenspec(d));
k = dimenkind(d);
if(k == Dpercent)
buf[n++] = '%';
if(k == Drelative)
buf[n++] = '*';
return n;
}
void
printitems(Item* items, char* msg)
{
Item* il;
trace("%s\n", msg);
il = items;
while(il != nil) {
trace("%I", il);
il = il->next;
}
}
Genattr*
newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, Attr* events)
{
Genattr* g;
g = (Genattr*)emalloc(sizeof(Genattr));
g->id = id;
g->class = class;
g->style = style;
g->title = title;
g->events = events;
return g;
}
Formfield*
newformfield(int ftype, int fieldid, Form* form, Rune* name,
Rune* value, int size, int maxlength, Formfield* link)
{
Formfield* ff;
ff = (Formfield*)emallocz(sizeof(Formfield));
ff->ftype = ftype;
ff->fieldid = fieldid;
ff->form = form;
ff->name = name;
ff->value = value;
ff->size = size;
ff->maxlength = maxlength;
ff->ctlid = -1;
ff->next = link;
return ff;
}
Option*
newoption(int selected, Rune* value, Rune* display, Option* link)
{
Option *o;
o = (Option*)emalloc(sizeof(Option));
o->selected = selected;
o->value = value;
o->display = display;
o->next = link;
return o;
}
Form*
newform(int formid, Rune* name, ParsedUrl* action, int target, int method, Form* link)
{
Form* f;
f = (Form*)emalloc(sizeof(Form));
f->formid = formid;
f->name = name;
f->action = action;
f->target = target;
f->method = method;
f->nfields = 0;
f->fields = nil;
f->next = link;
return f;
}
Table*
newtable(int tableid, Align align, Dimen width, int border,
int cellspacing, int cellpadding, Background bg, Token* tok, Table* link)
{
Table* t;
t = (Table*)emallocz(sizeof(Table));
t->tableid = tableid;
t->align = align;
t->width = width;
t->border = border;
t->cellspacing = cellspacing;
t->cellpadding = cellpadding;
t->background = bg;
t->caption_place = ALbottom;
t->caption_lay = nil;
t->tabletok = tok;
t->tabletok = nil;
t->next = link;
return t;
}
Tablerow*
newtablerow(Align align, Background bg, int flags, Tablerow* link)
{
Tablerow* tr;
tr = (Tablerow*)emallocz(sizeof(Tablerow));
tr->align = align;
tr->background = bg;
tr->flags = flags;
tr->next = link;
return tr;
}
Tablecell*
newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec, int hspec,
Background bg, int flags, Tablecell* link)
{
Tablecell* c;
c = (Tablecell*)emallocz(sizeof(Tablecell));
c->cellid = cellid;
c->lay = nil;
c->rowspan = rowspan;
c->colspan = colspan;
c->align = align;
c->flags = flags;
c->wspec = wspec;
c->hspec = hspec;
c->background = bg;
c->next = link;
return c;
}
Anchor*
newanchor(int index, Rune* name, ParsedUrl* href, int target, Anchor* link)
{
Anchor* a;
a = (Anchor*)emalloc(sizeof(Anchor));
a->index = index;
a->name = name;
a->href = href;
a->target = target;
a->next = link;
return a;
}
DestAnchor*
newdestanchor(int index, Rune* name, Item* item, DestAnchor* link)
{
DestAnchor* d;
d = (DestAnchor*)emalloc(sizeof(DestAnchor));
d->index = index;
d->name = name;
d->item = item;
d->next = link;
return d;
}
Dimen
makedimen(int kind, int spec)
{
Dimen d;
if(spec&Dkindmask) {
if(warn)
trace("warning: dimension spec too big: %d\n", spec);
spec = 0;
}
d.kindspec = kind|spec;
return d;
}
int
dimenkind(Dimen d)
{
return (d.kindspec&Dkindmask);
}
int
dimenspec(Dimen d)
{
return (d.kindspec&Dspecmask);
}
Kidinfo*
newkidinfo(int isframeset, Kidinfo* link)
{
Kidinfo* ki;
ki = (Kidinfo*)emallocz(sizeof(Kidinfo));
ki->isframeset = isframeset;
if(!isframeset) {
ki->flags = FRhscrollauto|FRvscrollauto;
ki->marginw = FRKIDMARGIN;
ki->marginh = FRKIDMARGIN;
ki->framebd = 1;
}
ki->next = link;
return ki;
}
Docinfo*
newdocinfo(void)
{
Docinfo* d;
d = (Docinfo*)emallocz(sizeof(Docinfo));
resetdocinfo(d);
return d;
}
void
resetdocinfo(Docinfo* d)
{
memset(d, 0, sizeof(Docinfo));
d->background = makebackground(nil, White);
d->text = Black;
d->link = Blue;
d->vlink = Blue;
d->alink = Blue;
d->target = FTself;
d->chset = ISO_8859_1;
d->scripttype = TextJavascript;
d->frameid = -1;
}
// Debugging
#define HUGEPIX 10000
// A "shallow" validitem, that doesn't follow next links
// or descend into tables.
int
validitem(Item* i)
{
int ok;
Itext* ti;
Irule* ri;
Iimage* ii;
Ifloat* fi;
int a;
ok = (i->tag >= Itexttag && i->tag <= Ispacertag) &&
(i->next == nil || validptr(i->next)) &&
(i->width >= 0 && i->width < HUGEPIX) &&
(i->height >= 0 && i->height < HUGEPIX) &&
(i->ascent > -HUGEPIX && i->ascent < HUGEPIX) &&
(i->anchorid >= 0) &&
(i->genattr == nil || validptr(i->genattr));
assert(ok);
// also, could check state for ridiculous combinations
// also, could check anchorid for within-doc-range
if(ok)
switch(i->tag) {
case Itexttag:
ti = (Itext*)i;
ok = validStr(ti->s) &&
(ti->fnt >= 0 && ti->fnt < NumStyle*NumSize) &&
(ti->ul == ULnone || ti->ul == ULunder || ti->ul == ULmid);
assert(ok);
break;
case Iruletag:
ri = (Irule*)i;
ok = (validvalign(ri->align) || validhalign(ri->align)) &&
(ri->size >=0 && ri->size < HUGEPIX);
assert(ok);
break;
case Iimagetag:
ii = (Iimage*)i;
ok = (ii->ci == nil || validptr(ii->ci)) &&
(ii->width >= 0 && ii->width < HUGEPIX) &&
(ii->height >= 0 && ii->height < HUGEPIX) &&
(ii->imwidth >= 0 && ii->imwidth < HUGEPIX) &&
(ii->imheight >= 0 && ii->imheight < HUGEPIX) &&
(ii->altrep == nil || validStr(ii->altrep)) &&
(ii->map == nil || validptr(ii->map)) &&
(validvalign(ii->align) || validhalign(ii->align)) &&
(ii->nextimage == nil || validptr(ii->nextimage));
assert(ok);
break;
case Iformfieldtag:
ok = validformfield(((Iformfield*)i)->formfield);
assert(ok);
break;
case Itabletag:
ok = validptr((Itable*)i);
assert(ok);
break;
case Ifloattag:
fi = (Ifloat*)i;
ok = (fi->side == ALleft || fi->side == ALright) &&
validitem(fi->item) &&
(fi->item->tag == Iimagetag || fi->item->tag == Itabletag);
assert(ok);
break;
case Ispacertag:
a = ((Ispacer*)i)->spkind;
ok = a==ISPnull || a==ISPvline || a==ISPhspace || a==ISPgeneral;
assert(ok);
break;
default:
ok = 0;
assert(ok);
}
return ok;
}
// "deep" validation, that checks whole list of items,
// and descends into tables and floated tables.
// nil is ok for argument.
int
validitems(Item* i)
{
int ok;
Item* ii;
ok = 1;
while(i != nil && ok) {
ok = validitem(i);
if(ok) {
if(i->tag == Itabletag) {
ok = validtable(((Itable*)i)->table);
}
else if(i->tag == Ifloattag) {
ii = ((Ifloat*)i)->item;
if(ii->tag == Itabletag)
ok = validtable(((Itable*)ii)->table);
}
}
if(!ok) {
trace("invalid item: %I\n", i);
}
i = i->next;
}
return ok;
}
int
validformfield(Formfield* f)
{
int ok;
ok = (f->next == nil || validptr(f->next)) &&
(f->ftype >= 0 && f->ftype <= Ftextarea) &&
f->fieldid >= 0 &&
(f->form == nil || validptr(f->form)) &&
(f->name == nil || validStr(f->name)) &&
(f->value == nil || validStr(f->value)) &&
(f->options == nil || validptr(f->options)) &&
(f->image == nil || validitem(f->image)) &&
(f->events == nil || validptr(f->events));
// when all built, should have f->fieldid < f->form->nfields,
// but this may be called during build...
return ok;
}
// "deep" validation -- checks cell contents too
int
validtable(Table* t)
{
int ok;
int i, j;
Tablecell* c;
ok = (t->next == nil || validptr(t->next)) &&
t->nrow >= 0 &&
t->ncol >= 0 &&
t->ncell >= 0 &&
validalign(t->align) &&
validdimen(t->width) &&
(t->border >= 0 && t->border < HUGEPIX) &&
(t->cellspacing >= 0 && t->cellspacing < HUGEPIX) &&
(t->cellpadding >= 0 && t->cellpadding < HUGEPIX) &&
validitems(t->caption) &&
(t->caption_place == ALtop || t->caption_place == ALbottom) &&
(t->totw >= 0 && t->totw < HUGEPIX) &&
(t->toth >= 0 && t->toth < HUGEPIX) &&
(t->tabletok == nil || validptr(t->tabletok));
// during parsing, t->rows has list;
// only when parsing is done is t->nrow set > 0
if(ok && t->nrow > 0 && t->ncol > 0) {
// table is "finished"
for(i = 0; i < t->nrow && ok; i++)
ok = validtablerow(t->rows+i);
for(j = 0; j < t->ncol && ok; j++)
ok = validtablecol(t->cols+j);
for(c = t->cells; c != nil && ok; c = c->next)
ok = validtablecell(c);
for(i = 0; i < t->nrow && ok; i++)
for(j = 0; j < t->ncol && ok; j++)
ok = validptr(t->grid[i][j]);
}
return ok;
}
static int
validvalign(int a)
{
return a == ALnone || a == ALmiddle || a == ALbottom || a == ALtop || a == ALbaseline;
}
static int
validhalign(int a)
{
return a == ALnone || a == ALleft || a == ALcenter || a == ALright ||
a == ALjustify || a == ALchar;
}
static int
validalign(Align a)
{
return validhalign(a.halign) && validvalign(a.valign);
}
static int
validdimen(Dimen d)
{
int ok;
int s;
ok = 0;
s = d.kindspec&Dspecmask;
switch(d.kindspec&Dkindmask) {
case Dnone:
ok = s==0;
break;
case Dpixels:
ok = s < HUGEPIX;
break;
case Dpercent:
case Drelative:
ok = 1;
break;
}
return ok;
}
int
validtablerow(Tablerow* r)
{
return (r->cells == nil || validptr(r->cells)) &&
(r->height >= 0 && r->height < HUGEPIX) &&
(r->ascent > -HUGEPIX && r->ascent < HUGEPIX) &&
validalign(r->align);
}
int
validtablecol(Tablecol* c)
{
return c->width >= 0 && c->width < HUGEPIX
&& validalign(c->align);
}
int
validtablecell(Tablecell* c)
{
int ok;
ok = (c->next == nil || validptr(c->next)) &&
(c->nextinrow == nil || validptr(c->nextinrow)) &&
(c->content == nil || validptr(c->content)) &&
(c->lay == nil || validptr(c->lay)) &&
c->rowspan >= 0 &&
c->colspan >= 0 &&
validalign(c->align) &&
validdimen(c->wspec) &&
c->row >= 0 &&
c->col >= 0;
if(ok) {
if(c->content != nil)
ok = validitems(c->content);
}
return ok;
}
|