blob: 7ce744497d1a3ea5f82f789da4fd23477b8da50a [file] [log] [blame]
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <ctype.h>
#include <html.h>
#include "impl.h"
/* A stack for holding integer values */
enum {
Nestmax = 40 /* max nesting level of lists, font styles, etc. */
};
struct Stack {
int n; /* next available slot (top of stack is stack[n-1]) */
int slots[Nestmax]; /* stack entries */
};
/* Parsing state */
struct Pstate
{
Pstate* next; /* in stack of Pstates */
int skipping; /* true when we shouldn't add items */
int skipwhite; /* true when we should strip leading space */
int curfont; /* font index for current font */
int curfg; /* current foreground color */
Background curbg; /* current background */
int curvoff; /* current baseline offset */
uchar curul; /* current underline/strike state */
uchar curjust; /* current justify state */
int curanchor; /* current (href) anchor id (if in one), or 0 */
int curstate; /* current value of item state */
int literal; /* current literal state */
int inpar; /* true when in a paragraph-like construct */
int adjsize; /* current font size adjustment */
Item* items; /* dummy head of item list we're building */
Item* lastit; /* tail of item list we're building */
Item* prelastit; /* item before lastit */
Stack fntstylestk; /* style stack */
Stack fntsizestk; /* size stack */
Stack fgstk; /* text color stack */
Stack ulstk; /* underline stack */
Stack voffstk; /* vertical offset stack */
Stack listtypestk; /* list type stack */
Stack listcntstk; /* list counter stack */
Stack juststk; /* justification stack */
Stack hangstk; /* hanging stack */
};
struct ItemSource
{
Docinfo* doc;
Pstate* psstk;
int nforms;
int ntables;
int nanchors;
int nframes;
Form* curform;
Map* curmap;
Table* tabstk;
Kidinfo* kidstk;
};
/* 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;
static AsciiInt _align_tab[] = {
{"baseline", ALbaseline},
{"bottom", ALbottom},
{"center", ALcenter},
{"char", ALchar},
{"justify", ALjustify},
{"left", ALleft},
{"middle", ALmiddle},
{"right", ALright},
{"top", ALtop}
};
#define NALIGNTAB (sizeof(_align_tab)/sizeof(StringInt))
static StringInt *input_tab;
static AsciiInt _input_tab[] = {
{"button", Fbutton},
{"checkbox", Fcheckbox},
{"file", Ffile},
{"hidden", Fhidden},
{"image", Fimage},
{"password", Fpassword},
{"radio", Fradio},
{"reset", Freset},
{"submit", Fsubmit},
{"text", Ftext}
};
#define NINPUTTAB (sizeof(_input_tab)/sizeof(StringInt))
static StringInt *clear_tab;
static AsciiInt _clear_tab[] = {
{"all", IFcleft|IFcright},
{"left", IFcleft},
{"right", IFcright}
};
#define NCLEARTAB (sizeof(_clear_tab)/sizeof(StringInt))
static StringInt *fscroll_tab;
static AsciiInt _fscroll_tab[] = {
{"auto", FRhscrollauto|FRvscrollauto},
{"no", FRnoscroll},
{"yes", FRhscroll|FRvscroll},
};
#define NFSCROLLTAB (sizeof(_fscroll_tab)/sizeof(StringInt))
static StringInt *shape_tab;
static AsciiInt _shape_tab[] = {
{"circ", SHcircle},
{"circle", SHcircle},
{"poly", SHpoly},
{"polygon", SHpoly},
{"rect", SHrect},
{"rectangle", SHrect}
};
#define NSHAPETAB (sizeof(_shape_tab)/sizeof(StringInt))
static StringInt *method_tab;
static AsciiInt _method_tab[] = {
{"get", HGet},
{"post", HPost}
};
#define NMETHODTAB (sizeof(_method_tab)/sizeof(StringInt))
static Rune** roman;
static char* _roman[15]= {
"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
"XI", "XII", "XIII", "XIV", "XV"
};
#define NROMAN 15
/* List number types */
enum {
LTdisc, LTsquare, LTcircle, LT1, LTa, LTA, LTi, LTI
};
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]= {
/*Notfound*/ 0,
/*Comment*/ 0,
/*Ta*/ 0,
/*Tabbr*/ 0,
/*Tacronym*/ 0,
/*Taddress*/ BLBA,
/*Tapplet*/ 0,
/*Tarea*/ 0,
/*Tb*/ 0,
/*Tbase*/ 0,
/*Tbasefont*/ 0,
/*Tbdo*/ 0,
/*Tbig*/ 0,
/*Tblink*/ 0,
/*Tblockquote*/ BLBA,
/*Tbody*/ 0,
/*Tbq*/ 0,
/*Tbr*/ 0,
/*Tbutton*/ 0,
/*Tcaption*/ 0,
/*Tcenter*/ BL,
/*Tcite*/ 0,
/*Tcode*/ 0,
/*Tcol*/ 0,
/*Tcolgroup*/ 0,
/*Tdd*/ BL,
/*Tdel*/ 0,
/*Tdfn*/ 0,
/*Tdir*/ BLBA,
/*Tdiv*/ BL,
/*Tdl*/ BLBA,
/*Tdt*/ BL,
/*Tem*/ 0,
/*Tfieldset*/ 0,
/*Tfont*/ 0,
/*Tform*/ BLBA,
/*Tframe*/ 0,
/*Tframeset*/ 0,
/*Th1*/ BL,
/*Th2*/ BL,
/*Th3*/ BL,
/*Th4*/ BL,
/*Th5*/ BL,
/*Th6*/ BL,
/*Thead*/ 0,
/*Thr*/ BL,
/*Thtml*/ 0,
/*Ti*/ 0,
/*Tiframe*/ 0,
/*Timg*/ 0,
/*Tinput*/ 0,
/*Tins*/ 0,
/*Tisindex*/ BLBA,
/*Tkbd*/ 0,
/*Tlabel*/ 0,
/*Tlegend*/ 0,
/*Tli*/ BL,
/*Tlink*/ 0,
/*Tmap*/ 0,
/*Tmenu*/ BLBA,
/*Tmeta*/ 0,
/*Tnobr*/ 0,
/*Tnoframes*/ 0,
/*Tnoscript*/ 0,
/*Tobject*/ 0,
/*Tol*/ BLBA,
/*Toptgroup*/ 0,
/*Toption*/ 0,
/*Tp*/ BLBA,
/*Tparam*/ 0,
/*Tpre*/ BLBA,
/*Tq*/ 0,
/*Ts*/ 0,
/*Tsamp*/ 0,
/*Tscript*/ 0,
/*Tselect*/ 0,
/*Tsmall*/ 0,
/*Tspan*/ 0,
/*Tstrike*/ 0,
/*Tstrong*/ 0,
/*Tstyle*/ 0,
/*Tsub*/ 0,
/*Tsup*/ 0,
/*Ttable*/ 0,
/*Ttbody*/ 0,
/*Ttd*/ 0,
/*Ttextarea*/ 0,
/*Ttfoot*/ 0,
/*Tth*/ 0,
/*Tthead*/ 0,
/*Ttitle*/ 0,
/*Ttr*/ 0,
/*Ttt*/ 0,
/*Tu*/ 0,
/*Tul*/ BLBA,
/*Tvar*/ 0,
};
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]= {
/*Aabbr*/ 0,
/*Aaccept_charset*/ 0,
/*Aaccess_key*/ 0,
/*Aaction*/ 0,
/*Aalign*/ 0,
/*Aalink*/ 0,
/*Aalt*/ 0,
/*Aarchive*/ 0,
/*Aaxis*/ 0,
/*Abackground*/ 0,
/*Abgcolor*/ 0,
/*Aborder*/ 0,
/*Acellpadding*/ 0,
/*Acellspacing*/ 0,
/*Achar*/ 0,
/*Acharoff*/ 0,
/*Acharset*/ 0,
/*Achecked*/ 0,
/*Acite*/ 0,
/*Aclass*/ AGEN,
/*Aclassid*/ 0,
/*Aclear*/ 0,
/*Acode*/ 0,
/*Acodebase*/ 0,
/*Acodetype*/ 0,
/*Acolor*/ 0,
/*Acols*/ 0,
/*Acolspan*/ 0,
/*Acompact*/ 0,
/*Acontent*/ 0,
/*Acoords*/ 0,
/*Adata*/ 0,
/*Adatetime*/ 0,
/*Adeclare*/ 0,
/*Adefer*/ 0,
/*Adir*/ 0,
/*Adisabled*/ 0,
/*Aenctype*/ 0,
/*Aface*/ 0,
/*Afor*/ 0,
/*Aframe*/ 0,
/*Aframeborder*/ 0,
/*Aheaders*/ 0,
/*Aheight*/ 0,
/*Ahref*/ 0,
/*Ahreflang*/ 0,
/*Ahspace*/ 0,
/*Ahttp_equiv*/ 0,
/*Aid*/ AGEN,
/*Aismap*/ 0,
/*Alabel*/ 0,
/*Alang*/ 0,
/*Alink*/ 0,
/*Alongdesc*/ 0,
/*Amarginheight*/ 0,
/*Amarginwidth*/ 0,
/*Amaxlength*/ 0,
/*Amedia*/ 0,
/*Amethod*/ 0,
/*Amultiple*/ 0,
/*Aname*/ 0,
/*Anohref*/ 0,
/*Anoresize*/ 0,
/*Anoshade*/ 0,
/*Anowrap*/ 0,
/*Aobject*/ 0,
/*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,
/*Aprofile*/ 0,
/*Aprompt*/ 0,
/*Areadonly*/ 0,
/*Arel*/ 0,
/*Arev*/ 0,
/*Arows*/ 0,
/*Arowspan*/ 0,
/*Arules*/ 0,
/*Ascheme*/ 0,
/*Ascope*/ 0,
/*Ascrolling*/ 0,
/*Aselected*/ 0,
/*Ashape*/ 0,
/*Asize*/ 0,
/*Aspan*/ 0,
/*Asrc*/ 0,
/*Astandby*/ 0,
/*Astart*/ 0,
/*Astyle*/ AGEN,
/*Asummary*/ 0,
/*Atabindex*/ 0,
/*Atarget*/ 0,
/*Atext*/ 0,
/*Atitle*/ AGEN,
/*Atype*/ 0,
/*Ausemap*/ 0,
/*Avalign*/ 0,
/*Avalue*/ 0,
/*Avaluetype*/ 0,
/*Aversion*/ 0,
/*Avlink*/ 0,
/*Avspace*/ 0,
/*Awidth*/ 0,
};
static uchar scriptev[Numattrs]= {
/*Aabbr*/ 0,
/*Aaccept_charset*/ 0,
/*Aaccess_key*/ 0,
/*Aaction*/ 0,
/*Aalign*/ 0,
/*Aalink*/ 0,
/*Aalt*/ 0,
/*Aarchive*/ 0,
/*Aaxis*/ 0,
/*Abackground*/ 0,
/*Abgcolor*/ 0,
/*Aborder*/ 0,
/*Acellpadding*/ 0,
/*Acellspacing*/ 0,
/*Achar*/ 0,
/*Acharoff*/ 0,
/*Acharset*/ 0,
/*Achecked*/ 0,
/*Acite*/ 0,
/*Aclass*/ 0,
/*Aclassid*/ 0,
/*Aclear*/ 0,
/*Acode*/ 0,
/*Acodebase*/ 0,
/*Acodetype*/ 0,
/*Acolor*/ 0,
/*Acols*/ 0,
/*Acolspan*/ 0,
/*Acompact*/ 0,
/*Acontent*/ 0,
/*Acoords*/ 0,
/*Adata*/ 0,
/*Adatetime*/ 0,
/*Adeclare*/ 0,
/*Adefer*/ 0,
/*Adir*/ 0,
/*Adisabled*/ 0,
/*Aenctype*/ 0,
/*Aface*/ 0,
/*Afor*/ 0,
/*Aframe*/ 0,
/*Aframeborder*/ 0,
/*Aheaders*/ 0,
/*Aheight*/ 0,
/*Ahref*/ 0,
/*Ahreflang*/ 0,
/*Ahspace*/ 0,
/*Ahttp_equiv*/ 0,
/*Aid*/ 0,
/*Aismap*/ 0,
/*Alabel*/ 0,
/*Alang*/ 0,
/*Alink*/ 0,
/*Alongdesc*/ 0,
/*Amarginheight*/ 0,
/*Amarginwidth*/ 0,
/*Amaxlength*/ 0,
/*Amedia*/ 0,
/*Amethod*/ 0,
/*Amultiple*/ 0,
/*Aname*/ 0,
/*Anohref*/ 0,
/*Anoresize*/ 0,
/*Anoshade*/ 0,
/*Anowrap*/ 0,
/*Aobject*/ 0,
/*Aonblur*/ SEonblur,
/*Aonchange*/ SEonchange,
/*Aonclick*/ SEonclick,
/*Aondblclick*/ SEondblclick,
/*Aonfocus*/ SEonfocus,
/*Aonkeypress*/ SEonkeypress,
/*Aonkeyup*/ SEonkeyup,
/*Aonload*/ SEonload,
/*Aonmousedown*/ SEonmousedown,
/*Aonmousemove*/ SEonmousemove,
/*Aonmouseout*/ SEonmouseout,
/*Aonmouseover*/ SEonmouseover,
/*Aonmouseup*/ SEonmouseup,
/*Aonreset*/ SEonreset,
/*Aonselect*/ SEonselect,
/*Aonsubmit*/ SEonsubmit,
/*Aonunload*/ SEonunload,
/*Aprofile*/ 0,
/*Aprompt*/ 0,
/*Areadonly*/ 0,
/*Arel*/ 0,
/*Arev*/ 0,
/*Arows*/ 0,
/*Arowspan*/ 0,
/*Arules*/ 0,
/*Ascheme*/ 0,
/*Ascope*/ 0,
/*Ascrolling*/ 0,
/*Aselected*/ 0,
/*Ashape*/ 0,
/*Asize*/ 0,
/*Aspan*/ 0,
/*Asrc*/ 0,
/*Astandby*/ 0,
/*Astart*/ 0,
/*Astyle*/ 0,
/*Asummary*/ 0,
/*Atabindex*/ 0,
/*Atarget*/ 0,
/*Atext*/ 0,
/*Atitle*/ 0,
/*Atype*/ 0,
/*Ausemap*/ 0,
/*Avalign*/ 0,
/*Avalue*/ 0,
/*Avaluetype*/ 0,
/*Aversion*/ 0,
/*Avlink*/ 0,
/*Avspace*/ 0,
/*Awidth*/ 0,
};
/* Color lookup table */
static StringInt *color_tab;
static AsciiInt _color_tab[] = {
{"aqua", 0x00FFFF},
{"black", 0x000000},
{"blue", 0x0000CC},
{"fuchsia", 0xFF00FF},
{"gray", 0x808080},
{"green", 0x008000},
{"lime", 0x00FF00},
{"maroon", 0x800000},
{"navy", 0x000080,},
{"olive", 0x808000},
{"purple", 0x800080},
{"red", 0xFF0000},
{"silver", 0xC0C0C0},
{"teal", 0x008080},
{"white", 0xFFFFFF},
{"yellow", 0xFFFF00}
};
#define NCOLORS (sizeof(_color_tab)/sizeof(StringInt))
static StringInt *targetmap;
static int targetmapsize;
static int ntargets;
static int buildinited = 0;
#define SMALLBUFSIZE 240
#define BIGBUFSIZE 2000
int dbgbuild = 0;
int warn = 0;
static Align aalign(Token* tok);
static int acolorval(Token* tok, int attid, int dflt);
static void addbrk(Pstate* ps, int sp, int clr);
static void additem(Pstate* ps, Item* it, Token* tok);
static void addlinebrk(Pstate* ps, int clr);
static void addnbsp(Pstate* ps);
static void addtext(Pstate* ps, Rune* s);
static Dimen adimen(Token* tok, int attid);
static int aflagval(Token* tok, int attid);
static int aintval(Token* tok, int attid, int dflt);
static Rune* astrval(Token* tok, int attid, Rune* dflt);
static int atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt);
static int atargval(Token* tok, int dflt);
static int auintval(Token* tok, int attid, int dflt);
static Rune* aurlval(Token* tok, int attid, Rune* dflt, Rune* base);
static Rune* aval(Token* tok, int attid);
static void buildinit(void);
static Pstate* cell_pstate(Pstate* oldps, int ishead);
static void changehang(Pstate* ps, int delta);
static void changeindent(Pstate* ps, int delta);
static int color(Rune* s, int dflt);
static void copystack(Stack* tostk, Stack* fromstk);
static int dimprint(char* buf, int nbuf, Dimen d);
static Pstate* finishcell(Table* curtab, Pstate* psstk);
static void finish_table(Table* t);
static void freeanchor(Anchor* a);
static void freedestanchor(DestAnchor* da);
static void freeform(Form* f);
static void freeformfield(Formfield* ff);
static void freeitem(Item* it);
static void freepstate(Pstate* p);
static void freepstatestack(Pstate* pshead);
static void freescriptevents(SEvent* ehead);
static void freetable(Table* t);
static Map* getmap(Docinfo* di, Rune* name);
static Rune* getpcdata(Token* toks, int tokslen, int* ptoki);
static Pstate* lastps(Pstate* psl);
static Rune* listmark(uchar ty, int n);
static int listtyval(Token* tok, int dflt);
static Align makealign(int halign, int valign);
static Background makebackground(Rune* imgurl, int color);
static Dimen makedimen(int kind, int spec);
static Anchor* newanchor(int index, Rune* name, Rune* href, int target, Anchor* link);
static Area* newarea(int shape, Rune* href, int target, Area* link);
static DestAnchor* newdestanchor(int index, Rune* name, Item* item, DestAnchor* link);
static Docinfo* newdocinfo(void);
static Genattr* newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events);
static Form* newform(int formid, Rune* name, Rune* action,
int target, int method, Form* link);
static Formfield* newformfield(int ftype, int fieldid, Form* form, Rune* name,
Rune* value, int size, int maxlength, Formfield* link);
static Item* newifloat(Item* it, int side);
static Item* newiformfield(Formfield* ff);
static Item* newiimage(Rune* src, Rune* altrep, int align, int width, int height,
int hspace, int vspace, int border, int ismap, Map* map);
static Item* newirule(int align, int size, int noshade, Dimen wspec);
static Item* newispacer(int spkind);
static Item* newitable(Table* t);
static ItemSource* newitemsource(Docinfo* di);
static Item* newitext(Rune* s, int fnt, int fg, int voff, int ul);
static Kidinfo* newkidinfo(int isframeset, Kidinfo* link);
static Option* newoption(int selected, Rune* value, Rune* display, Option* link);
static Pstate* newpstate(Pstate* link);
static SEvent* newscriptevent(int type, Rune* script, SEvent* link);
static Table* newtable(int tableid, Align align, Dimen width, int border,
int cellspacing, int cellpadding, Background bg, Token* tok, Table* link);
static Tablecell* newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec,
int hspec, Background bg, int flags, Tablecell* link);
static Tablerow* newtablerow(Align align, Background bg, int flags, Tablerow* link);
static Dimen parsedim(Rune* s, int ns);
static void pop(Stack* stk);
static void popfontsize(Pstate* ps);
static void popfontstyle(Pstate* ps);
static void popjust(Pstate* ps);
static int popretnewtop(Stack* stk, int dflt);
static int push(Stack* stk, int val);
static void pushfontsize(Pstate* ps, int sz);
static void pushfontstyle(Pstate* ps, int sty);
static void pushjust(Pstate* ps, int j);
static Item* textit(Pstate* ps, Rune* s);
static Rune* removeallwhite(Rune* s);
static void resetdocinfo(Docinfo* d);
static void setcurfont(Pstate* ps);
static void setcurjust(Pstate* ps);
static void setdimarray(Token* tok, int attid, Dimen** pans, int* panslen);
static Rune* stringalign(int a);
static void targetmapinit(void);
static int toint(Rune* s);
static int top(Stack* stk, int dflt);
static void trim_cell(Tablecell* c);
static int validalign(Align a);
static int validdimen(Dimen d);
static int validformfield(Formfield* f);
static int validhalign(int a);
static int validptr(void* p);
static int validStr(Rune* s);
static int validtable(Table* t);
static int validtablerow(Tablerow* r);
static int validtablecol(Tablecol* c);
static int validtablecell(Tablecell* c);
static int validvalign(int a);
static int Iconv(Fmt *f);
static void
buildinit(void)
{
_runetabinit();
roman = _cvtstringtab(_roman, nelem(_roman));
color_tab = _cvtstringinttab(_color_tab, nelem(_color_tab));
method_tab = _cvtstringinttab(_method_tab, nelem(_method_tab));
shape_tab = _cvtstringinttab(_shape_tab, nelem(_shape_tab));
fscroll_tab = _cvtstringinttab(_fscroll_tab, nelem(_fscroll_tab));
clear_tab = _cvtstringinttab(_clear_tab, nelem(_clear_tab));
input_tab = _cvtstringinttab(_input_tab, nelem(_input_tab));
align_tab = _cvtstringinttab(_align_tab, nelem(_align_tab));
fmtinstall('I', Iconv);
targetmapinit();
buildinited = 1;
}
static ItemSource*
newitemsource(Docinfo* di)
{
ItemSource* is;
Pstate* ps;
ps = newpstate(nil);
if(di->mediatype != TextHtml) {
ps->curstate &= ~IFwrap;
ps->literal = 1;
pushfontstyle(ps, FntT);
}
is = (ItemSource*)emalloc(sizeof(ItemSource));
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;
}
static Item *getitems(ItemSource* is, uchar* data, int datalen);
/* Parse an html document and create a list of layout items. */
/* Allocate and return document info in *pdi. */
/* When caller is done with the items, it should call */
/* freeitems on the returned result, and then */
/* freedocinfo(*pdi). */
Item*
parsehtml(uchar* data, int datalen, Rune* pagesrc, int mtype, int chset, Docinfo** pdi)
{
Item *it;
Docinfo* di;
ItemSource* is;
di = newdocinfo();
di->src = _Strdup(pagesrc);
di->base = _Strdup(pagesrc);
di->mediatype = mtype;
di->chset = chset;
*pdi = di;
is = newitemsource(di);
it = getitems(is, data, datalen);
freepstatestack(is->psstk);
free(is);
return it;
}
/* Get a group of tokens for lexer, parse them, and create */
/* a list of layout items. */
/* When caller is done with the items, it should call */
/* freeitems on the returned result. */
static Item*
getitems(ItemSource* is, uchar* data, int datalen)
{
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 isempty;
int tag;
int brksp;
int target;
uchar brk;
uchar flags;
uchar align;
uchar al;
uchar ty;
uchar ty2;
Pstate* ps;
Pstate* nextps;
Pstate* outerps;
Table* curtab;
Token* tok;
Token* toks;
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* 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;
Rune* href;
Rune* src;
Rune* scriptsrc;
Rune* bgurl;
Rune* action;
Background bg;
if(!buildinited)
buildinit();
doscripts = 0; /* for now */
ps = is->psstk;
curtab = is->tabstk;
di = is->doc;
toks = _gettoks(data, datalen, di->chset, di->mediatype, &tokslen);
toki = 0;
for(; toki < tokslen; toki++) {
tok = &toks[toki];
if(dbgbuild > 1)
fprint(2, "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 switch 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) {
t = s;
s = _Strsubstr(s, i, j);
free(t);
n = j-i;
}
}
if(ps->skipwhite) {
_trimwhite(s, n, &t, &nt);
if(t == nil) {
free(s);
s = nil;
}
else if(t != s) {
t = _Strndup(t, nt);
free(s);
s = t;
}
if(s != nil)
ps->skipwhite = 0;
}
tok->text = nil; /* token doesn't own string anymore */
if(s != nil){
addtext(ps, s);
s = nil;
}
}
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)
fprint(2, "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);
if(name != nil)
name = _Strdup(name); /* for DestAnchor construction, below */
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)
fprint(2, "warning: <APPLET> ignored\n");
break;
/* <!ELEMENT AREA - O EMPTY> */
case Tarea:
map = di->maps;
if(map == nil) {
if(warn)
fprint(2, "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:
t = di->base;
di->base = aurlval(tok, Ahref, di->base, di->base);
if(t != nil)
free(t);
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) {
if(di->backgrounditem != nil)
freeitem((Item*)di->backgrounditem);
/* really should remove old item from di->images list, */
/* but there should only be one BODY element ... */
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)
fprint(2, "warning: <CAPTION> outside <TABLE>\n");
continue;
}
if(curtab->caption != nil) {
if(warn)
fprint(2, "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:
nextps = ps->next;
if(curtab == nil || nextps == nil) {
if(warn)
fprint(2, "warning: unexpected </CAPTION>\n");
continue;
}
curtab->caption = ps->items->next;
free(ps);
ps = nextps;
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)
fprint(2, "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)
fprint(2, "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)
fprint(2, "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)
fprint(2, "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(Lplus), nsz))
sz = Normal + _Strtol(nsz+1, nil, 10) + ps->adjsize;
else if(_prefix(L(Lminus), 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)
fprint(2, "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)
fprint(2, "warning: <FORM> nested inside another\n");
continue;
}
action = aurlval(tok, Aaction, di->base, di->base);
s = aval(tok, Aid);
name = astrval(tok, Aname, s);
if(s)
free(s);
target = atargval(tok, di->target);
method = atabval(tok, Amethod, method_tab, NMETHODTAB, HGet);
if(warn && _tokaval(tok, Aenctype, &enctype, 0) &&
_Strcmp(enctype, L(Lappl_form)))
fprint(2, "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)
fprint(2, "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)
fprint(2, "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(Lfr), s);
free(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)
fprint(2, "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) {
/* end input */
ans = nil;
goto return_ans;
}
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(Lhash), usemap)) {
if(warn)
fprint(2, "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)
fprint(2, "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)
fprint(2, "<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)
fprint(2, "warning: checkbox form field missing name\n");
continue;
}
if(field->value == nil)
field->value = _Strdup(L(Lone));
break;
case Fradio:
if(field->name == nil || field->value == nil) {
if(warn)
fprint(2, "warning: radio form field missing name or value\n");
continue;
}
break;
case Fsubmit:
if(field->value == nil)
field->value = _Strdup(L(Lsubmit));
if(field->name == nil)
field->name = _Strdup(L(Lnoname));
break;
case Fimage:
src = aurlval(tok, Asrc, nil, di->base);
if(src == nil) {
if(warn)
fprint(2, "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(Lsubmit)),
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 = _Strdup(L(Lreset));
break;
case Fbutton:
if(field->value == nil)
field->value = _Strdup(L(Lspace));
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(Lindex));
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,
_Strdup(L(Lisindex)),
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)
fprint(2, "<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)
fprint(2, "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(Lrefresh)))
di->refresh = val;
else if(!_Strncmpci(equiv, n, L(Lcontent))) {
n = _Strlen(val);
if(!_Strncmpci(val, n, L(Ljavascript))
|| !_Strncmpci(val, n, L(Ljscript1))
|| !_Strncmpci(val, n, L(Ljscript)))
di->scripttype = TextJavascript;
else {
if(warn)
fprint(2, "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)
fprint(2, "warning: <OPTION> not in <SELECT>\n");
continue;
}
field = is->curform->fields;
if(field->ftype != Fselect) {
if(warn)
fprint(2, "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 = _Strdup(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)
fprint(2, "warning: <SCRIPT> ignored\n");
ps->skipping = 1;
}
else {
scriptsrc = aurlval(tok, Asrc, nil, di->base);
script = nil;
if(scriptsrc != nil) {
if(warn)
fprint(2, "warning: non-local <SCRIPT> ignored\n");
free(scriptsrc);
}
else {
script = getpcdata(toks, tokslen, &toki);
}
if(script != nil) {
if(warn)
fprint(2, "script ignored\n");
free(script);
}
}
break;
case Tscript+RBRA:
ps->skipping = 0;
break;
/* <!ELEMENT SELECT - - (OPTION+)> */
case Tselect:
if(is->curform == nil) {
if(warn)
fprint(2, "<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>) */
s = getpcdata(toks, tokslen, &toki);
if(s != nil)
free(s);
break;
case Tselect+RBRA:
if(is->curform == nil || is->curform->fields == nil) {
if(warn)
fprint(2, "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)
fprint(2, "warning: unexpected %T\n", tok);
continue;
}
ps->curul = popretnewtop(&ps->ulstk, ULnone);
break;
/* <!ELEMENT STYLE - - CDATA> */
case Tstyle:
if(warn)
fprint(2, "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)
fprint(2, "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)
fprint(2, "warning: unexpected </TABLE>\n");
continue;
}
isempty = (curtab->cells == nil);
if(isempty) {
if(warn)
fprint(2, "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)
fprint(2, "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)
fprint(2, "%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)
fprint(2, "%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)
fprint(2, "unexpected %T\n", tok);
continue;
}
ps = finishcell(curtab, ps);
break;
/* <!ELEMENT TEXTAREA - - ( //PCDATA)> */
case Ttextarea:
if(is->curform == nil) {
if(warn)
fprint(2, "<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)
fprint(2, "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)
fprint(2, "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)
fprint(2, "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)
fprint(2, "warning: unexpected </TR>\n");
continue;
}
ps = finishcell(curtab, ps);
tr = curtab->rows;
if(tr->cells == nil) {
if(warn)
fprint(2, "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;
fprint(2, "warning: unimplemented HTML tag: %S\n", tagnames[tag]);
}
break;
default:
if(warn)
fprint(2, "warning: unknown HTML tag: %S\n", tok->text);
break;
}
}
/* some pages omit trailing </table> */
while(curtab != nil) {
if(warn)
fprint(2, "warning: <TABLE> not closed\n");
if(curtab->cells != nil) {
ps = finishcell(curtab, ps);
if(curtab->cells == nil) {
if(warn)
fprint(2, "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); */
;
}
return_ans:
if(dbgbuild) {
assert(validitems(ans));
if(ans == nil)
fprint(2, "getitems returning nil\n");
else
printitems(ans, "getitems returning:");
}
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) {
p = ans;
ans = _Strndup(trimans, trimanslen);
free(p);
}
}
*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;
Pstate* psstknext;
c = curtab->cells;
if(c != nil) {
if((c->flags&TFparsing)) {
psstknext = psstk->next;
if(psstknext == nil) {
if(warn)
fprint(2, "warning: parse state stack is wrong\n");
}
else {
c->content = psstk->items->next;
c->flags &= ~TFparsing;
freepstate(psstk);
psstk = psstknext;
}
}
}
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*)emalloc(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;
SEvent* e;
if(ps->skipping) {
if(warn)
fprint(2, "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;
break;
case Aclass:
c = a->value;
break;
case Astyle:
s = a->value;
break;
case Atitle:
t = a->value;
break;
default:
assert(aid >= Aonblur && aid <= Aonunload);
e = newscriptevent(scriptev[a->attid], a->value, e);
break;
}
a->value = nil;
any = 1;
}
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. */
/* */
/* addtext assumes ownership of s. */
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 = runestrlen(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(Ltab2space), 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);
free(s);
}
}
}
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);
free(s);
}
}
/* 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;
Rune* s;
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 {
s = t->s;
if(nl == 0) {
/* need a non-nil pointer to empty string */
/* (_Strdup(L(Lempty)) returns nil) */
t->s = emalloc(sizeof(Rune));
t->s[0] = 0;
}
else
t->s = _Strndup(l, nl);
if(s)
free(s);
}
}
}
}
/* 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)
fprint(2, "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)
fprint(2, "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)
fprint(2, "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*)emalloc(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**)emalloc(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*)emalloc(ncol * sizeof(int));
rowspancell = (Tablecell**)emalloc(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<