plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

build.c (100723B)


      1 #include <u.h>
      2 #include <libc.h>
      3 #include <draw.h>
      4 #include <ctype.h>
      5 #include <html.h>
      6 #include "impl.h"
      7 
      8 /* A stack for holding integer values */
      9 enum {
     10 	Nestmax = 40	/* max nesting level of lists, font styles, etc. */
     11 };
     12 
     13 struct Stack {
     14 	int		n;				/* next available slot (top of stack is stack[n-1]) */
     15 	int		slots[Nestmax];	/* stack entries */
     16 };
     17 
     18 /* Parsing state */
     19 struct Pstate
     20 {
     21 	Pstate*	next;			/* in stack of Pstates */
     22 	int		skipping;		/* true when we shouldn't add items */
     23 	int		skipwhite;		/* true when we should strip leading space */
     24 	int		curfont;		/* font index for current font */
     25 	int		curfg;		/* current foreground color */
     26 	Background	curbg;	/* current background */
     27 	int		curvoff;		/* current baseline offset */
     28 	uchar	curul;		/* current underline/strike state */
     29 	uchar	curjust;		/* current justify state */
     30 	int		curanchor;	/* current (href) anchor id (if in one), or 0 */
     31 	int		curstate;		/* current value of item state */
     32 	int		literal;		/* current literal state */
     33 	int		inpar;		/* true when in a paragraph-like construct */
     34 	int		adjsize;		/* current font size adjustment */
     35 	Item*	items;		/* dummy head of item list we're building */
     36 	Item*	lastit;		/* tail of item list we're building */
     37 	Item*	prelastit;		/* item before lastit */
     38 	Stack	fntstylestk;	/* style stack */
     39 	Stack	fntsizestk;		/* size stack */
     40 	Stack	fgstk;		/* text color stack */
     41 	Stack	ulstk;		/* underline stack */
     42 	Stack	voffstk;		/* vertical offset stack */
     43 	Stack	listtypestk;	/* list type stack */
     44 	Stack	listcntstk;		/* list counter stack */
     45 	Stack	juststk;		/* justification stack */
     46 	Stack	hangstk;		/* hanging stack */
     47 };
     48 
     49 struct ItemSource
     50 {
     51 	Docinfo*		doc;
     52 	Pstate*		psstk;
     53 	int			nforms;
     54 	int			ntables;
     55 	int			nanchors;
     56 	int			nframes;
     57 	Form*		curform;
     58 	Map*		curmap;
     59 	Table*		tabstk;
     60 	Kidinfo*		kidstk;
     61 };
     62 
     63 /* Some layout parameters */
     64 enum {
     65 	FRKIDMARGIN = 6,	/* default margin around kid frames */
     66 	IMGHSPACE = 0,	/* default hspace for images (0 matches IE, Netscape) */
     67 	IMGVSPACE = 0,	/* default vspace for images */
     68 	FLTIMGHSPACE = 2,	/* default hspace for float images */
     69 	TABSP = 5,		/* default cellspacing for tables */
     70 	TABPAD = 1,		/* default cell padding for tables */
     71 	LISTTAB = 1,		/* number of tabs to indent lists */
     72 	BQTAB = 1,		/* number of tabs to indent blockquotes */
     73 	HRSZ = 2,			/* thickness of horizontal rules */
     74 	SUBOFF = 4,		/* vertical offset for subscripts */
     75 	SUPOFF = 6,		/* vertical offset for superscripts */
     76 	NBSP = 160		/* non-breaking space character */
     77 };
     78 
     79 /* These tables must be sorted */
     80 static StringInt *align_tab;
     81 static AsciiInt _align_tab[] = {
     82 	{"baseline",	ALbaseline},
     83 	{"bottom",	ALbottom},
     84 	{"center",	ALcenter},
     85 	{"char",		ALchar},
     86 	{"justify",	ALjustify},
     87 	{"left",		ALleft},
     88 	{"middle",	ALmiddle},
     89 	{"right",		ALright},
     90 	{"top",		ALtop}
     91 };
     92 #define NALIGNTAB (sizeof(_align_tab)/sizeof(StringInt))
     93 
     94 static StringInt *input_tab;
     95 static AsciiInt _input_tab[] = {
     96 	{"button",	Fbutton},
     97 	{"checkbox",	Fcheckbox},
     98 	{"file",		Ffile},
     99 	{"hidden",	Fhidden},
    100 	{"image",	Fimage},
    101 	{"password",	Fpassword},
    102 	{"radio",		Fradio},
    103 	{"reset",		Freset},
    104 	{"submit",	Fsubmit},
    105 	{"text",		Ftext}
    106 };
    107 #define NINPUTTAB (sizeof(_input_tab)/sizeof(StringInt))
    108 
    109 static StringInt *clear_tab;
    110 static AsciiInt _clear_tab[] = {
    111 	{"all",	IFcleft|IFcright},
    112 	{"left",	IFcleft},
    113 	{"right",	IFcright}
    114 };
    115 #define NCLEARTAB (sizeof(_clear_tab)/sizeof(StringInt))
    116 
    117 static StringInt *fscroll_tab;
    118 static AsciiInt _fscroll_tab[] = {
    119 	{"auto",	FRhscrollauto|FRvscrollauto},
    120 	{"no",	FRnoscroll},
    121 	{"yes",	FRhscroll|FRvscroll},
    122 };
    123 #define NFSCROLLTAB (sizeof(_fscroll_tab)/sizeof(StringInt))
    124 
    125 static StringInt *shape_tab;
    126 static AsciiInt _shape_tab[] = {
    127 	{"circ",		SHcircle},
    128 	{"circle",		SHcircle},
    129 	{"poly",		SHpoly},
    130 	{"polygon",	SHpoly},
    131 	{"rect",		SHrect},
    132 	{"rectangle",	SHrect}
    133 };
    134 #define NSHAPETAB (sizeof(_shape_tab)/sizeof(StringInt))
    135 
    136 static StringInt *method_tab;
    137 static AsciiInt _method_tab[] = {
    138 	{"get",		HGet},
    139 	{"post",		HPost}
    140 };
    141 #define NMETHODTAB (sizeof(_method_tab)/sizeof(StringInt))
    142 
    143 static Rune** roman;
    144 static char* _roman[15]= {
    145 	"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
    146 	"XI", "XII", "XIII", "XIV", "XV"
    147 };
    148 #define NROMAN 15
    149 
    150 /* List number types */
    151 enum {
    152 	LTdisc, LTsquare, LTcircle, LT1, LTa, LTA, LTi, LTI
    153 };
    154 
    155 enum {
    156 	SPBefore = 2,
    157 	SPAfter = 4,
    158 	BL = 1,
    159 	BLBA = (BL|SPBefore|SPAfter)
    160 };
    161 
    162 /* blockbrk[tag] is break info for a block level element, or one */
    163 /* of a few others that get the same treatment re ending open paragraphs */
    164 /* and requiring a line break / vertical space before them. */
    165 /* If we want a line of space before the given element, SPBefore is OR'd in. */
    166 /* If we want a line of space after the given element, SPAfter is OR'd in. */
    167 
    168 static uchar blockbrk[Numtags]= {
    169 /*Notfound*/ 0,
    170 /*Comment*/ 0,
    171 /*Ta*/ 0,
    172 /*Tabbr*/ 0,
    173 /*Tacronym*/ 0,
    174 /*Taddress*/ BLBA,
    175 /*Tapplet*/ 0,
    176 /*Tarea*/ 0,
    177 /*Tb*/ 0,
    178 /*Tbase*/ 0,
    179 /*Tbasefont*/ 0,
    180 /*Tbdo*/ 0,
    181 /*Tbig*/ 0,
    182 /*Tblink*/ 0,
    183 /*Tblockquote*/ BLBA,
    184 /*Tbody*/ 0,
    185 /*Tbq*/ 0,
    186 /*Tbr*/ 0,
    187 /*Tbutton*/ 0,
    188 /*Tcaption*/ 0,
    189 /*Tcenter*/ BL,
    190 /*Tcite*/ 0,
    191 /*Tcode*/ 0,
    192 /*Tcol*/ 0,
    193 /*Tcolgroup*/ 0,
    194 /*Tdd*/ BL,
    195 /*Tdel*/ 0,
    196 /*Tdfn*/ 0,
    197 /*Tdir*/ BLBA,
    198 /*Tdiv*/ BL,
    199 /*Tdl*/ BLBA,
    200 /*Tdt*/ BL,
    201 /*Tem*/ 0,
    202 /*Tfieldset*/ 0,
    203 /*Tfont*/ 0,
    204 /*Tform*/ BLBA,
    205 /*Tframe*/ 0,
    206 /*Tframeset*/ 0,
    207 /*Th1*/ BL,
    208 /*Th2*/ BL,
    209 /*Th3*/ BL,
    210 /*Th4*/ BL,
    211 /*Th5*/ BL,
    212 /*Th6*/ BL,
    213 /*Thead*/ 0,
    214 /*Thr*/ BL,
    215 /*Thtml*/ 0,
    216 /*Ti*/ 0,
    217 /*Tiframe*/ 0,
    218 /*Timg*/ 0,
    219 /*Tinput*/ 0,
    220 /*Tins*/ 0,
    221 /*Tisindex*/ BLBA,
    222 /*Tkbd*/ 0,
    223 /*Tlabel*/ 0,
    224 /*Tlegend*/ 0,
    225 /*Tli*/ BL,
    226 /*Tlink*/ 0,
    227 /*Tmap*/ 0,
    228 /*Tmenu*/ BLBA,
    229 /*Tmeta*/ 0,
    230 /*Tnobr*/ 0,
    231 /*Tnoframes*/ 0,
    232 /*Tnoscript*/ 0,
    233 /*Tobject*/ 0,
    234 /*Tol*/ BLBA,
    235 /*Toptgroup*/ 0,
    236 /*Toption*/ 0,
    237 /*Tp*/ BLBA,
    238 /*Tparam*/ 0,
    239 /*Tpre*/ BLBA,
    240 /*Tq*/ 0,
    241 /*Ts*/ 0,
    242 /*Tsamp*/ 0,
    243 /*Tscript*/ 0,
    244 /*Tselect*/ 0,
    245 /*Tsmall*/ 0,
    246 /*Tspan*/ 0,
    247 /*Tstrike*/ 0,
    248 /*Tstrong*/ 0,
    249 /*Tstyle*/ 0,
    250 /*Tsub*/ 0,
    251 /*Tsup*/ 0,
    252 /*Ttable*/ 0,
    253 /*Ttbody*/ 0,
    254 /*Ttd*/ 0,
    255 /*Ttextarea*/ 0,
    256 /*Ttfoot*/ 0,
    257 /*Tth*/ 0,
    258 /*Tthead*/ 0,
    259 /*Ttitle*/ 0,
    260 /*Ttr*/ 0,
    261 /*Ttt*/ 0,
    262 /*Tu*/ 0,
    263 /*Tul*/ BLBA,
    264 /*Tvar*/ 0,
    265 };
    266 
    267 enum {
    268 	AGEN = 1
    269 };
    270 
    271 /* attrinfo is information about attributes. */
    272 /* The AGEN value means that the attribute is generic (applies to almost all elements) */
    273 static uchar attrinfo[Numattrs]= {
    274 /*Aabbr*/ 0,
    275 /*Aaccept_charset*/ 0,
    276 /*Aaccess_key*/ 0,
    277 /*Aaction*/ 0,
    278 /*Aalign*/ 0,
    279 /*Aalink*/ 0,
    280 /*Aalt*/ 0,
    281 /*Aarchive*/ 0,
    282 /*Aaxis*/ 0,
    283 /*Abackground*/ 0,
    284 /*Abgcolor*/ 0,
    285 /*Aborder*/ 0,
    286 /*Acellpadding*/ 0,
    287 /*Acellspacing*/ 0,
    288 /*Achar*/ 0,
    289 /*Acharoff*/ 0,
    290 /*Acharset*/ 0,
    291 /*Achecked*/ 0,
    292 /*Acite*/ 0,
    293 /*Aclass*/ AGEN,
    294 /*Aclassid*/ 0,
    295 /*Aclear*/ 0,
    296 /*Acode*/ 0,
    297 /*Acodebase*/ 0,
    298 /*Acodetype*/ 0,
    299 /*Acolor*/ 0,
    300 /*Acols*/ 0,
    301 /*Acolspan*/ 0,
    302 /*Acompact*/ 0,
    303 /*Acontent*/ 0,
    304 /*Acoords*/ 0,
    305 /*Adata*/ 0,
    306 /*Adatetime*/ 0,
    307 /*Adeclare*/ 0,
    308 /*Adefer*/ 0,
    309 /*Adir*/ 0,
    310 /*Adisabled*/ 0,
    311 /*Aenctype*/ 0,
    312 /*Aface*/ 0,
    313 /*Afor*/ 0,
    314 /*Aframe*/ 0,
    315 /*Aframeborder*/ 0,
    316 /*Aheaders*/ 0,
    317 /*Aheight*/ 0,
    318 /*Ahref*/ 0,
    319 /*Ahreflang*/ 0,
    320 /*Ahspace*/ 0,
    321 /*Ahttp_equiv*/ 0,
    322 /*Aid*/ AGEN,
    323 /*Aismap*/ 0,
    324 /*Alabel*/ 0,
    325 /*Alang*/ 0,
    326 /*Alink*/ 0,
    327 /*Alongdesc*/ 0,
    328 /*Amarginheight*/ 0,
    329 /*Amarginwidth*/ 0,
    330 /*Amaxlength*/ 0,
    331 /*Amedia*/ 0,
    332 /*Amethod*/ 0,
    333 /*Amultiple*/ 0,
    334 /*Aname*/ 0,
    335 /*Anohref*/ 0,
    336 /*Anoresize*/ 0,
    337 /*Anoshade*/ 0,
    338 /*Anowrap*/ 0,
    339 /*Aobject*/ 0,
    340 /*Aonblur*/ AGEN,
    341 /*Aonchange*/ AGEN,
    342 /*Aonclick*/ AGEN,
    343 /*Aondblclick*/ AGEN,
    344 /*Aonfocus*/ AGEN,
    345 /*Aonkeypress*/ AGEN,
    346 /*Aonkeyup*/ AGEN,
    347 /*Aonload*/ AGEN,
    348 /*Aonmousedown*/ AGEN,
    349 /*Aonmousemove*/ AGEN,
    350 /*Aonmouseout*/ AGEN,
    351 /*Aonmouseover*/ AGEN,
    352 /*Aonmouseup*/ AGEN,
    353 /*Aonreset*/ AGEN,
    354 /*Aonselect*/ AGEN,
    355 /*Aonsubmit*/ AGEN,
    356 /*Aonunload*/ AGEN,
    357 /*Aprofile*/ 0,
    358 /*Aprompt*/ 0,
    359 /*Areadonly*/ 0,
    360 /*Arel*/ 0,
    361 /*Arev*/ 0,
    362 /*Arows*/ 0,
    363 /*Arowspan*/ 0,
    364 /*Arules*/ 0,
    365 /*Ascheme*/ 0,
    366 /*Ascope*/ 0,
    367 /*Ascrolling*/ 0,
    368 /*Aselected*/ 0,
    369 /*Ashape*/ 0,
    370 /*Asize*/ 0,
    371 /*Aspan*/ 0,
    372 /*Asrc*/ 0,
    373 /*Astandby*/ 0,
    374 /*Astart*/ 0,
    375 /*Astyle*/ AGEN,
    376 /*Asummary*/ 0,
    377 /*Atabindex*/ 0,
    378 /*Atarget*/ 0,
    379 /*Atext*/ 0,
    380 /*Atitle*/ AGEN,
    381 /*Atype*/ 0,
    382 /*Ausemap*/ 0,
    383 /*Avalign*/ 0,
    384 /*Avalue*/ 0,
    385 /*Avaluetype*/ 0,
    386 /*Aversion*/ 0,
    387 /*Avlink*/ 0,
    388 /*Avspace*/ 0,
    389 /*Awidth*/ 0,
    390 };
    391 
    392 static uchar scriptev[Numattrs]= {
    393 /*Aabbr*/ 0,
    394 /*Aaccept_charset*/ 0,
    395 /*Aaccess_key*/ 0,
    396 /*Aaction*/ 0,
    397 /*Aalign*/ 0,
    398 /*Aalink*/ 0,
    399 /*Aalt*/ 0,
    400 /*Aarchive*/ 0,
    401 /*Aaxis*/ 0,
    402 /*Abackground*/ 0,
    403 /*Abgcolor*/ 0,
    404 /*Aborder*/ 0,
    405 /*Acellpadding*/ 0,
    406 /*Acellspacing*/ 0,
    407 /*Achar*/ 0,
    408 /*Acharoff*/ 0,
    409 /*Acharset*/ 0,
    410 /*Achecked*/ 0,
    411 /*Acite*/ 0,
    412 /*Aclass*/ 0,
    413 /*Aclassid*/ 0,
    414 /*Aclear*/ 0,
    415 /*Acode*/ 0,
    416 /*Acodebase*/ 0,
    417 /*Acodetype*/ 0,
    418 /*Acolor*/ 0,
    419 /*Acols*/ 0,
    420 /*Acolspan*/ 0,
    421 /*Acompact*/ 0,
    422 /*Acontent*/ 0,
    423 /*Acoords*/ 0,
    424 /*Adata*/ 0,
    425 /*Adatetime*/ 0,
    426 /*Adeclare*/ 0,
    427 /*Adefer*/ 0,
    428 /*Adir*/ 0,
    429 /*Adisabled*/ 0,
    430 /*Aenctype*/ 0,
    431 /*Aface*/ 0,
    432 /*Afor*/ 0,
    433 /*Aframe*/ 0,
    434 /*Aframeborder*/ 0,
    435 /*Aheaders*/ 0,
    436 /*Aheight*/ 0,
    437 /*Ahref*/ 0,
    438 /*Ahreflang*/ 0,
    439 /*Ahspace*/ 0,
    440 /*Ahttp_equiv*/ 0,
    441 /*Aid*/ 0,
    442 /*Aismap*/ 0,
    443 /*Alabel*/ 0,
    444 /*Alang*/ 0,
    445 /*Alink*/ 0,
    446 /*Alongdesc*/ 0,
    447 /*Amarginheight*/ 0,
    448 /*Amarginwidth*/ 0,
    449 /*Amaxlength*/ 0,
    450 /*Amedia*/ 0,
    451 /*Amethod*/ 0,
    452 /*Amultiple*/ 0,
    453 /*Aname*/ 0,
    454 /*Anohref*/ 0,
    455 /*Anoresize*/ 0,
    456 /*Anoshade*/ 0,
    457 /*Anowrap*/ 0,
    458 /*Aobject*/ 0,
    459 /*Aonblur*/ SEonblur,
    460 /*Aonchange*/ SEonchange,
    461 /*Aonclick*/ SEonclick,
    462 /*Aondblclick*/ SEondblclick,
    463 /*Aonfocus*/ SEonfocus,
    464 /*Aonkeypress*/ SEonkeypress,
    465 /*Aonkeyup*/ SEonkeyup,
    466 /*Aonload*/ SEonload,
    467 /*Aonmousedown*/ SEonmousedown,
    468 /*Aonmousemove*/ SEonmousemove,
    469 /*Aonmouseout*/ SEonmouseout,
    470 /*Aonmouseover*/ SEonmouseover,
    471 /*Aonmouseup*/ SEonmouseup,
    472 /*Aonreset*/ SEonreset,
    473 /*Aonselect*/ SEonselect,
    474 /*Aonsubmit*/ SEonsubmit,
    475 /*Aonunload*/ SEonunload,
    476 /*Aprofile*/ 0,
    477 /*Aprompt*/ 0,
    478 /*Areadonly*/ 0,
    479 /*Arel*/ 0,
    480 /*Arev*/ 0,
    481 /*Arows*/ 0,
    482 /*Arowspan*/ 0,
    483 /*Arules*/ 0,
    484 /*Ascheme*/ 0,
    485 /*Ascope*/ 0,
    486 /*Ascrolling*/ 0,
    487 /*Aselected*/ 0,
    488 /*Ashape*/ 0,
    489 /*Asize*/ 0,
    490 /*Aspan*/ 0,
    491 /*Asrc*/ 0,
    492 /*Astandby*/ 0,
    493 /*Astart*/ 0,
    494 /*Astyle*/ 0,
    495 /*Asummary*/ 0,
    496 /*Atabindex*/ 0,
    497 /*Atarget*/ 0,
    498 /*Atext*/ 0,
    499 /*Atitle*/ 0,
    500 /*Atype*/ 0,
    501 /*Ausemap*/ 0,
    502 /*Avalign*/ 0,
    503 /*Avalue*/ 0,
    504 /*Avaluetype*/ 0,
    505 /*Aversion*/ 0,
    506 /*Avlink*/ 0,
    507 /*Avspace*/ 0,
    508 /*Awidth*/ 0,
    509 };
    510 
    511 /* Color lookup table */
    512 static StringInt *color_tab;
    513 static AsciiInt _color_tab[] = {
    514 	{"aqua", 0x00FFFF},
    515 	{"black",  0x000000},
    516 	{"blue", 0x0000CC},
    517 	{"fuchsia", 0xFF00FF},
    518 	{"gray", 0x808080},
    519 	{"green", 0x008000},
    520 	{"lime", 0x00FF00},
    521 	{"maroon", 0x800000},
    522 	{"navy", 0x000080,},
    523 	{"olive", 0x808000},
    524 	{"purple", 0x800080},
    525 	{"red", 0xFF0000},
    526 	{"silver", 0xC0C0C0},
    527 	{"teal", 0x008080},
    528 	{"white", 0xFFFFFF},
    529 	{"yellow", 0xFFFF00}
    530 };
    531 #define NCOLORS (sizeof(_color_tab)/sizeof(StringInt))
    532 
    533 static StringInt 		*targetmap;
    534 static int			targetmapsize;
    535 static int			ntargets;
    536 
    537 static int buildinited = 0;
    538 
    539 #define SMALLBUFSIZE 240
    540 #define BIGBUFSIZE 2000
    541 
    542 int	dbgbuild = 0;
    543 int	warn = 0;
    544 
    545 static Align		aalign(Token* tok);
    546 static int			acolorval(Token* tok, int attid, int dflt);
    547 static void			addbrk(Pstate* ps, int sp, int clr);
    548 static void			additem(Pstate* ps, Item* it, Token* tok);
    549 static void			addlinebrk(Pstate* ps, int clr);
    550 static void			addnbsp(Pstate* ps);
    551 static void			addtext(Pstate* ps, Rune* s);
    552 static Dimen		adimen(Token* tok, int attid);
    553 static int			aflagval(Token* tok, int attid);
    554 static int			aintval(Token* tok, int attid, int dflt);
    555 static Rune*		astrval(Token* tok, int attid, Rune* dflt);
    556 static int			atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt);
    557 static int			atargval(Token* tok, int dflt);
    558 static int			auintval(Token* tok, int attid, int dflt);
    559 static Rune*		aurlval(Token* tok, int attid, Rune* dflt, Rune* base);
    560 static Rune*		aval(Token* tok, int attid);
    561 static void			buildinit(void);
    562 static Pstate*		cell_pstate(Pstate* oldps, int ishead);
    563 static void			changehang(Pstate* ps, int delta);
    564 static void			changeindent(Pstate* ps, int delta);
    565 static int			color(Rune* s, int dflt);
    566 static void			copystack(Stack* tostk, Stack* fromstk);
    567 static int			dimprint(char* buf, int nbuf, Dimen d);
    568 static Pstate*		finishcell(Table* curtab, Pstate* psstk);
    569 static void			finish_table(Table* t);
    570 static void			freeanchor(Anchor* a);
    571 static void			freedestanchor(DestAnchor* da);
    572 static void			freeform(Form* f);
    573 static void			freeformfield(Formfield* ff);
    574 static void			freeitem(Item* it);
    575 static void			freepstate(Pstate* p);
    576 static void			freepstatestack(Pstate* pshead);
    577 static void			freescriptevents(SEvent* ehead);
    578 static void			freetable(Table* t);
    579 static Map*		getmap(Docinfo* di, Rune* name);
    580 static Rune*		getpcdata(Token* toks, int tokslen, int* ptoki);
    581 static Pstate*		lastps(Pstate* psl);
    582 static Rune*		listmark(uchar ty, int n);
    583 static int			listtyval(Token* tok, int dflt);
    584 static Align		makealign(int halign, int valign);
    585 static Background	makebackground(Rune* imgurl, int color);
    586 static Dimen		makedimen(int kind, int spec);
    587 static Anchor*		newanchor(int index, Rune* name, Rune* href, int target, Anchor* link);
    588 static Area*		newarea(int shape, Rune* href, int target, Area* link);
    589 static DestAnchor*	newdestanchor(int index, Rune* name, Item* item, DestAnchor* link);
    590 static Docinfo*		newdocinfo(void);
    591 static Genattr*		newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events);
    592 static Form*		newform(int formid, Rune* name, Rune* action,
    593 					int target, int method, Form* link);
    594 static Formfield*	newformfield(int ftype, int fieldid, Form* form, Rune* name,
    595 					Rune* value, int size, int maxlength, Formfield* link);
    596 static Item*		newifloat(Item* it, int side);
    597 static Item*		newiformfield(Formfield* ff);
    598 static Item*		newiimage(Rune* src, Rune* altrep, int align, int width, int height,
    599 					int hspace, int vspace, int border, int ismap, Map* map);
    600 static Item*		newirule(int align, int size, int noshade, Dimen wspec);
    601 static Item*		newispacer(int spkind);
    602 static Item*		newitable(Table* t);
    603 static ItemSource*	newitemsource(Docinfo* di);
    604 static Item*		newitext(Rune* s, int fnt, int fg, int voff, int ul);
    605 static Kidinfo*		newkidinfo(int isframeset, Kidinfo* link);
    606 static Option*		newoption(int selected, Rune* value, Rune* display, Option* link);
    607 static Pstate*		newpstate(Pstate* link);
    608 static SEvent*		newscriptevent(int type, Rune* script, SEvent* link);
    609 static Table*		newtable(int tableid, Align align, Dimen width, int border,
    610 					int cellspacing, int cellpadding, Background bg, Token* tok, Table* link);
    611 static Tablecell*	newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec,
    612 					int hspec, Background bg, int flags, Tablecell* link);
    613 static Tablerow*	newtablerow(Align align, Background bg, int flags, Tablerow* link);
    614 static Dimen		parsedim(Rune* s, int ns);
    615 static void			pop(Stack* stk);
    616 static void			popfontsize(Pstate* ps);
    617 static void			popfontstyle(Pstate* ps);
    618 static void			popjust(Pstate* ps);
    619 static int			popretnewtop(Stack* stk, int dflt);
    620 static int			push(Stack* stk, int val);
    621 static void			pushfontsize(Pstate* ps, int sz);
    622 static void			pushfontstyle(Pstate* ps, int sty);
    623 static void			pushjust(Pstate* ps, int j);
    624 static Item*		textit(Pstate* ps, Rune* s);
    625 static Rune*		removeallwhite(Rune* s);
    626 static void			resetdocinfo(Docinfo* d);
    627 static void			setcurfont(Pstate* ps);
    628 static void			setcurjust(Pstate* ps);
    629 static void			setdimarray(Token* tok, int attid, Dimen** pans, int* panslen);
    630 static Rune*		stringalign(int a);
    631 static void			targetmapinit(void);
    632 static int			toint(Rune* s);
    633 static int			top(Stack* stk, int dflt);
    634 static void			trim_cell(Tablecell* c);
    635 static int			validalign(Align a);
    636 static int			validdimen(Dimen d);
    637 static int			validformfield(Formfield* f);
    638 static int			validhalign(int a);
    639 static int			validptr(void* p);
    640 static int			validStr(Rune* s);
    641 static int			validtable(Table* t);
    642 static int			validtablerow(Tablerow* r);
    643 static int			validtablecol(Tablecol* c);
    644 static int			validtablecell(Tablecell* c);
    645 static int			validvalign(int a);
    646 static int			Iconv(Fmt *f);
    647 
    648 static void
    649 buildinit(void)
    650 {
    651 	_runetabinit();
    652 	roman = _cvtstringtab(_roman, nelem(_roman));
    653 	color_tab = _cvtstringinttab(_color_tab, nelem(_color_tab));
    654 	method_tab = _cvtstringinttab(_method_tab, nelem(_method_tab));
    655 	shape_tab = _cvtstringinttab(_shape_tab, nelem(_shape_tab));
    656 	fscroll_tab = _cvtstringinttab(_fscroll_tab, nelem(_fscroll_tab));
    657 	clear_tab = _cvtstringinttab(_clear_tab, nelem(_clear_tab));
    658 	input_tab = _cvtstringinttab(_input_tab, nelem(_input_tab));
    659 	align_tab = _cvtstringinttab(_align_tab, nelem(_align_tab));
    660 
    661 	fmtinstall('I', Iconv);
    662 	targetmapinit();
    663 	buildinited = 1;
    664 }
    665 
    666 static ItemSource*
    667 newitemsource(Docinfo* di)
    668 {
    669 	ItemSource*	is;
    670 	Pstate*	ps;
    671 
    672 	ps = newpstate(nil);
    673 	if(di->mediatype != TextHtml) {
    674 		ps->curstate &= ~IFwrap;
    675 		ps->literal = 1;
    676 		pushfontstyle(ps, FntT);
    677 	}
    678 	is = (ItemSource*)emalloc(sizeof(ItemSource));
    679 	is->doc = di;
    680 	is->psstk = ps;
    681 	is->nforms = 0;
    682 	is->ntables = 0;
    683 	is->nanchors = 0;
    684 	is->nframes = 0;
    685 	is->curform = nil;
    686 	is->curmap = nil;
    687 	is->tabstk = nil;
    688 	is->kidstk = nil;
    689 	return is;
    690 }
    691 
    692 static Item *getitems(ItemSource* is, uchar* data, int datalen);
    693 
    694 /* Parse an html document and create a list of layout items. */
    695 /* Allocate and return document info in *pdi. */
    696 /* When caller is done with the items, it should call */
    697 /* freeitems on the returned result, and then */
    698 /* freedocinfo(*pdi). */
    699 Item*
    700 parsehtml(uchar* data, int datalen, Rune* pagesrc, int mtype, int chset, Docinfo** pdi)
    701 {
    702 	Item *it;
    703 	Docinfo*	di;
    704 	ItemSource*	is;
    705 
    706 	di = newdocinfo();
    707 	di->src = _Strdup(pagesrc);
    708 	di->base = _Strdup(pagesrc);
    709 	di->mediatype = mtype;
    710 	di->chset = chset;
    711 	*pdi = di;
    712 	is = newitemsource(di);
    713 	it = getitems(is, data, datalen);
    714 	freepstatestack(is->psstk);
    715 	free(is);
    716 	return it;
    717 }
    718 
    719 /* Get a group of tokens for lexer, parse them, and create */
    720 /* a list of layout items. */
    721 /* When caller is done with the items, it should call */
    722 /* freeitems on the returned result. */
    723 static Item*
    724 getitems(ItemSource* is, uchar* data, int datalen)
    725 {
    726 	int	i;
    727 	int	j;
    728 	int	nt;
    729 	int	pt;
    730 	int	doscripts;
    731 	int	tokslen;
    732 	int	toki;
    733 	int	h;
    734 	int	sz;
    735 	int	method;
    736 	int	n;
    737 	int	nblank;
    738 	int	norsz;
    739 	int	bramt;
    740 	int	sty;
    741 	int	nosh;
    742 	int	oldcuranchor;
    743 	int	dfltbd;
    744 	int	v;
    745 	int	hang;
    746 	int	isempty;
    747 	int	tag;
    748 	int	brksp;
    749 	int	target;
    750 	uchar	brk;
    751 	uchar	flags;
    752 	uchar	align;
    753 	uchar	al;
    754 	uchar	ty;
    755 	uchar	ty2;
    756 	Pstate*	ps;
    757 	Pstate*	nextps;
    758 	Pstate*	outerps;
    759 	Table*	curtab;
    760 	Token*	tok;
    761 	Token*	toks;
    762 	Docinfo*	di;
    763 	Item*	ans;
    764 	Item*	img;
    765 	Item*	ffit;
    766 	Item*	tabitem;
    767 	Rune*	s;
    768 	Rune*	t;
    769 	Rune*	name;
    770 	Rune*	enctype;
    771 	Rune*	usemap;
    772 	Rune*	prompt;
    773 	Rune*	equiv;
    774 	Rune*	val;
    775 	Rune*	nsz;
    776 	Rune*	script;
    777 	Map*	map;
    778 	Form*	frm;
    779 	Iimage*	ii;
    780 	Kidinfo*	kd;
    781 	Kidinfo*	ks;
    782 	Kidinfo*	pks;
    783 	Dimen	wd;
    784 	Option*	option;
    785 	Table*	tab;
    786 	Tablecell*	c;
    787 	Tablerow*	tr;
    788 	Formfield*	field;
    789 	Formfield*	ff;
    790 	Rune*	href;
    791 	Rune*	src;
    792 	Rune*	scriptsrc;
    793 	Rune*	bgurl;
    794 	Rune*	action;
    795 	Background	bg;
    796 
    797 	if(!buildinited)
    798 		buildinit();
    799 	doscripts = 0;	/* for now */
    800 	ps = is->psstk;
    801 	curtab = is->tabstk;
    802 	di = is->doc;
    803 	toks = _gettoks(data, datalen, di->chset, di->mediatype, &tokslen);
    804 	toki = 0;
    805 	for(; toki < tokslen; toki++) {
    806 		tok = &toks[toki];
    807 		if(dbgbuild > 1)
    808 			fprint(2, "build: curstate %ux, token %T\n", ps->curstate, tok);
    809 		tag = tok->tag;
    810 		brk = 0;
    811 		brksp = 0;
    812 		if(tag < Numtags) {
    813 			brk = blockbrk[tag];
    814 			if(brk&SPBefore)
    815 				brksp = 1;
    816 		}
    817 		else if(tag < Numtags + RBRA) {
    818 			brk = blockbrk[tag - RBRA];
    819 			if(brk&SPAfter)
    820 				brksp = 1;
    821 		}
    822 		if(brk) {
    823 			addbrk(ps, brksp, 0);
    824 			if(ps->inpar) {
    825 				popjust(ps);
    826 				ps->inpar = 0;
    827 			}
    828 		}
    829 		/* check common case first (Data), then switch statement on tag */
    830 		if(tag == Data) {
    831 			/* Lexing didn't pay attention to SGML record boundary rules: */
    832 			/* \n after start tag or before end tag to be discarded. */
    833 			/* (Lex has already discarded all \r's). */
    834 			/* Some pages assume this doesn't happen in <PRE> text, */
    835 			/* so we won't do it if literal is true. */
    836 			/* BUG: won't discard \n before a start tag that begins */
    837 			/* the next bufferful of tokens. */
    838 			s = tok->text;
    839 			n = _Strlen(s);
    840 			if(!ps->literal) {
    841 				i = 0;
    842 				j = n;
    843 				if(toki > 0) {
    844 					pt = toks[toki - 1].tag;
    845 					/* IE and Netscape both ignore this rule (contrary to spec) */
    846 					/* if previous tag was img */
    847 					if(pt < Numtags && pt != Timg && j > 0 && s[0] == '\n')
    848 						i++;
    849 				}
    850 				if(toki < tokslen - 1) {
    851 					nt = toks[toki + 1].tag;
    852 					if(nt >= RBRA && nt < Numtags + RBRA && j > i && s[j - 1] == '\n')
    853 						j--;
    854 				}
    855 				if(i > 0 || j < n) {
    856 					t = s;
    857 					s = _Strsubstr(s, i, j);
    858 					free(t);
    859 					n = j-i;
    860 				}
    861 			}
    862 			if(ps->skipwhite) {
    863 				_trimwhite(s, n, &t, &nt);
    864 				if(t == nil) {
    865 					free(s);
    866 					s = nil;
    867 				}
    868 				else if(t != s) {
    869 					t = _Strndup(t, nt);
    870 					free(s);
    871 					s = t;
    872 				}
    873 				if(s != nil)
    874 					ps->skipwhite = 0;
    875 			}
    876 			tok->text = nil;		/* token doesn't own string anymore */
    877 			if(s != nil){
    878 				addtext(ps, s);
    879 				s = nil;
    880 			}
    881 		}
    882 		else
    883 			switch(tag) {
    884 			/* Some abbrevs used in following DTD comments */
    885 			/* %text = 	#PCDATA */
    886 			/*		| TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP */
    887 			/*		| EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE */
    888 			/*		| A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP */
    889 			/*		| INPUT | SELECT | TEXTAREA */
    890 			/* %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER */
    891 			/*		| BLOCKQUOTE | FORM | ISINDEX | HR | TABLE */
    892 			/* %flow = (%text | %block)* */
    893 			/* %body.content = (%heading | %text | %block | ADDRESS)* */
    894 
    895 			/* <!ELEMENT A - - (%text) -(A)> */
    896 			/* Anchors are not supposed to be nested, but you sometimes see */
    897 			/* href anchors inside destination anchors. */
    898 			case Ta:
    899 				if(ps->curanchor != 0) {
    900 					if(warn)
    901 						fprint(2, "warning: nested <A> or missing </A>\n");
    902 					ps->curanchor = 0;
    903 				}
    904 				name = aval(tok, Aname);
    905 				href = aurlval(tok, Ahref, nil, di->base);
    906 				/* ignore rel, rev, and title attrs */
    907 				if(href != nil) {
    908 					target = atargval(tok, di->target);
    909 					di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors);
    910 					if(name != nil)
    911 						name = _Strdup(name);	/* for DestAnchor construction, below */
    912 					ps->curanchor = is->nanchors;
    913 					ps->curfg = push(&ps->fgstk, di->link);
    914 					ps->curul = push(&ps->ulstk, ULunder);
    915 				}
    916 				if(name != nil) {
    917 					/* add a null item to be destination */
    918 					additem(ps, newispacer(ISPnull), tok);
    919 					di->dests = newdestanchor(++is->nanchors, name, ps->lastit, di->dests);
    920 				}
    921 				break;
    922 
    923 			case Ta+RBRA :
    924 				if(ps->curanchor != 0) {
    925 					ps->curfg = popretnewtop(&ps->fgstk, di->text);
    926 					ps->curul = popretnewtop(&ps->ulstk, ULnone);
    927 					ps->curanchor = 0;
    928 				}
    929 				break;
    930 
    931 			/* <!ELEMENT APPLET - - (PARAM | %text)* > */
    932 			/* We can't do applets, so ignore PARAMS, and let */
    933 			/* the %text contents appear for the alternative rep */
    934 			case Tapplet:
    935 			case Tapplet+RBRA:
    936 				if(warn && tag == Tapplet)
    937 					fprint(2, "warning: <APPLET> ignored\n");
    938 				break;
    939 
    940 			/* <!ELEMENT AREA - O EMPTY> */
    941 			case Tarea:
    942 				map = di->maps;
    943 				if(map == nil) {
    944 					if(warn)
    945 						fprint(2, "warning: <AREA> not inside <MAP>\n");
    946 					continue;
    947 				}
    948 				map->areas = newarea(atabval(tok, Ashape, shape_tab, NSHAPETAB, SHrect),
    949 					aurlval(tok, Ahref, nil, di->base),
    950 					atargval(tok, di->target),
    951 					map->areas);
    952 				setdimarray(tok, Acoords, &map->areas->coords, &map->areas->ncoords);
    953 				break;
    954 
    955 			/* <!ELEMENT (B|STRONG) - - (%text)*> */
    956 			case Tb:
    957 			case Tstrong:
    958 				pushfontstyle(ps, FntB);
    959 				break;
    960 
    961 			case Tb+RBRA:
    962 			case Tcite+RBRA:
    963 			case Tcode+RBRA:
    964 			case Tdfn+RBRA:
    965 			case Tem+RBRA:
    966 			case Tkbd+RBRA:
    967 			case Ti+RBRA:
    968 			case Tsamp+RBRA:
    969 			case Tstrong+RBRA:
    970 			case Ttt+RBRA:
    971 			case Tvar+RBRA :
    972 			case Taddress+RBRA:
    973 				popfontstyle(ps);
    974 				break;
    975 
    976 			/* <!ELEMENT BASE - O EMPTY> */
    977 			case Tbase:
    978 				t = di->base;
    979 				di->base = aurlval(tok, Ahref, di->base, di->base);
    980 				if(t != nil)
    981 					free(t);
    982 				di->target = atargval(tok, di->target);
    983 				break;
    984 
    985 			/* <!ELEMENT BASEFONT - O EMPTY> */
    986 			case Tbasefont:
    987 				ps->adjsize = aintval(tok, Asize, 3) - 3;
    988 				break;
    989 
    990 			/* <!ELEMENT (BIG|SMALL) - - (%text)*> */
    991 			case Tbig:
    992 			case Tsmall:
    993 				sz = ps->adjsize;
    994 				if(tag == Tbig)
    995 					sz += Large;
    996 				else
    997 					sz += Small;
    998 				pushfontsize(ps, sz);
    999 				break;
   1000 
   1001 			case Tbig+RBRA:
   1002 			case Tsmall+RBRA:
   1003 				popfontsize(ps);
   1004 				break;
   1005 
   1006 			/* <!ELEMENT BLOCKQUOTE - - %body.content> */
   1007 			case Tblockquote:
   1008 				changeindent(ps, BQTAB);
   1009 				break;
   1010 
   1011 			case Tblockquote+RBRA:
   1012 				changeindent(ps, -BQTAB);
   1013 				break;
   1014 
   1015 			/* <!ELEMENT BODY O O %body.content> */
   1016 			case Tbody:
   1017 				ps->skipping = 0;
   1018 				bg = makebackground(nil, acolorval(tok, Abgcolor, di->background.color));
   1019 				bgurl = aurlval(tok, Abackground, nil, di->base);
   1020 				if(bgurl != nil) {
   1021 					if(di->backgrounditem != nil)
   1022 						freeitem((Item*)di->backgrounditem);
   1023 						/* really should remove old item from di->images list, */
   1024 						/* but there should only be one BODY element ... */
   1025 					di->backgrounditem = (Iimage*)newiimage(bgurl, nil, ALnone, 0, 0, 0, 0, 0, 0, nil);
   1026 					di->backgrounditem->nextimage = di->images;
   1027 					di->images = di->backgrounditem;
   1028 				}
   1029 				ps->curbg = bg;
   1030 				di->background = bg;
   1031 				di->text = acolorval(tok, Atext, di->text);
   1032 				di->link = acolorval(tok, Alink, di->link);
   1033 				di->vlink = acolorval(tok, Avlink, di->vlink);
   1034 				di->alink = acolorval(tok, Aalink, di->alink);
   1035 				if(di->text != ps->curfg) {
   1036 					ps->curfg = di->text;
   1037 					ps->fgstk.n = 0;
   1038 				}
   1039 				break;
   1040 
   1041 			case Tbody+RBRA:
   1042 				/* HTML spec says ignore things after </body>, */
   1043 				/* but IE and Netscape don't */
   1044 				/* ps.skipping = 1; */
   1045 				break;
   1046 
   1047 			/* <!ELEMENT BR - O EMPTY> */
   1048 			case Tbr:
   1049 				addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
   1050 				break;
   1051 
   1052 			/* <!ELEMENT CAPTION - - (%text;)*> */
   1053 			case Tcaption:
   1054 				if(curtab == nil) {
   1055 					if(warn)
   1056 						fprint(2, "warning: <CAPTION> outside <TABLE>\n");
   1057 					continue;
   1058 				}
   1059 				if(curtab->caption != nil) {
   1060 					if(warn)
   1061 						fprint(2, "warning: more than one <CAPTION> in <TABLE>\n");
   1062 					continue;
   1063 				}
   1064 				ps = newpstate(ps);
   1065 				curtab->caption_place = atabval(tok, Aalign, align_tab, NALIGNTAB, ALtop);
   1066 				break;
   1067 
   1068 			case Tcaption+RBRA:
   1069 				nextps = ps->next;
   1070 				if(curtab == nil || nextps == nil) {
   1071 					if(warn)
   1072 						fprint(2, "warning: unexpected </CAPTION>\n");
   1073 					continue;
   1074 				}
   1075 				curtab->caption = ps->items->next;
   1076 				free(ps);
   1077 				ps = nextps;
   1078 				break;
   1079 
   1080 			case Tcenter:
   1081 			case Tdiv:
   1082 				if(tag == Tcenter)
   1083 					al = ALcenter;
   1084 				else
   1085 					al = atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust);
   1086 				pushjust(ps, al);
   1087 				break;
   1088 
   1089 			case Tcenter+RBRA:
   1090 			case Tdiv+RBRA:
   1091 				popjust(ps);
   1092 				break;
   1093 
   1094 			/* <!ELEMENT DD - O  %flow > */
   1095 			case Tdd:
   1096 				if(ps->hangstk.n == 0) {
   1097 					if(warn)
   1098 						fprint(2, "warning: <DD> not inside <DL\n");
   1099 					continue;
   1100 				}
   1101 				h = top(&ps->hangstk, 0);
   1102 				if(h != 0)
   1103 					changehang(ps, -10*LISTTAB);
   1104 				else
   1105 					addbrk(ps, 0, 0);
   1106 				push(&ps->hangstk, 0);
   1107 				break;
   1108 
   1109 			/*<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) > */
   1110 			/*<!ELEMENT (OL|UL) - - (LI)+> */
   1111 			case Tdir:
   1112 			case Tmenu:
   1113 			case Tol:
   1114 			case Tul:
   1115 				changeindent(ps, LISTTAB);
   1116 				push(&ps->listtypestk, listtyval(tok, (tag==Tol)? LT1 : LTdisc));
   1117 				push(&ps->listcntstk, aintval(tok, Astart, 1));
   1118 				break;
   1119 
   1120 			case Tdir+RBRA:
   1121 			case Tmenu+RBRA:
   1122 			case Tol+RBRA:
   1123 			case Tul+RBRA:
   1124 				if(ps->listtypestk.n == 0) {
   1125 					if(warn)
   1126 						fprint(2, "warning: %T ended no list\n", tok);
   1127 					continue;
   1128 				}
   1129 				addbrk(ps, 0, 0);
   1130 				pop(&ps->listtypestk);
   1131 				pop(&ps->listcntstk);
   1132 				changeindent(ps, -LISTTAB);
   1133 				break;
   1134 
   1135 			/* <!ELEMENT DL - - (DT|DD)+ > */
   1136 			case Tdl:
   1137 				changeindent(ps, LISTTAB);
   1138 				push(&ps->hangstk, 0);
   1139 				break;
   1140 
   1141 			case Tdl+RBRA:
   1142 				if(ps->hangstk.n == 0) {
   1143 					if(warn)
   1144 						fprint(2, "warning: unexpected </DL>\n");
   1145 					continue;
   1146 				}
   1147 				changeindent(ps, -LISTTAB);
   1148 				if(top(&ps->hangstk, 0) != 0)
   1149 					changehang(ps, -10*LISTTAB);
   1150 				pop(&ps->hangstk);
   1151 				break;
   1152 
   1153 			/* <!ELEMENT DT - O (%text)* > */
   1154 			case Tdt:
   1155 				if(ps->hangstk.n == 0) {
   1156 					if(warn)
   1157 						fprint(2, "warning: <DT> not inside <DL>\n");
   1158 					continue;
   1159 				}
   1160 				h = top(&ps->hangstk, 0);
   1161 				pop(&ps->hangstk);
   1162 				if(h != 0)
   1163 					changehang(ps, -10*LISTTAB);
   1164 				changehang(ps, 10*LISTTAB);
   1165 				push(&ps->hangstk, 1);
   1166 				break;
   1167 
   1168 			/* <!ELEMENT FONT - - (%text)*> */
   1169 			case Tfont:
   1170 				sz = top(&ps->fntsizestk, Normal);
   1171 				if(_tokaval(tok, Asize, &nsz, 0)) {
   1172 					if(_prefix(L(Lplus), nsz))
   1173 						sz = Normal + _Strtol(nsz+1, nil, 10) + ps->adjsize;
   1174 					else if(_prefix(L(Lminus), nsz))
   1175 						sz = Normal - _Strtol(nsz+1, nil, 10) + ps->adjsize;
   1176 					else if(nsz != nil)
   1177 						sz = Normal + (_Strtol(nsz, nil, 10) - 3);
   1178 				}
   1179 				ps->curfg = push(&ps->fgstk, acolorval(tok, Acolor, ps->curfg));
   1180 				pushfontsize(ps, sz);
   1181 				break;
   1182 
   1183 			case Tfont+RBRA:
   1184 				if(ps->fgstk.n == 0) {
   1185 					if(warn)
   1186 						fprint(2, "warning: unexpected </FONT>\n");
   1187 					continue;
   1188 				}
   1189 				ps->curfg = popretnewtop(&ps->fgstk, di->text);
   1190 				popfontsize(ps);
   1191 				break;
   1192 
   1193 			/* <!ELEMENT FORM - - %body.content -(FORM) > */
   1194 			case Tform:
   1195 				if(is->curform != nil) {
   1196 					if(warn)
   1197 						fprint(2, "warning: <FORM> nested inside another\n");
   1198 					continue;
   1199 				}
   1200 				action = aurlval(tok, Aaction, di->base, di->base);
   1201 				s = aval(tok, Aid);
   1202 				name = astrval(tok, Aname, s);
   1203 				if(s)
   1204 					free(s);
   1205 				target = atargval(tok, di->target);
   1206 				method = atabval(tok, Amethod, method_tab, NMETHODTAB, HGet);
   1207 				if(warn && _tokaval(tok, Aenctype, &enctype, 0) &&
   1208 						_Strcmp(enctype, L(Lappl_form)))
   1209 					fprint(2, "form enctype %S not handled\n", enctype);
   1210 				frm = newform(++is->nforms, name, action, target, method, di->forms);
   1211 				di->forms = frm;
   1212 				is->curform = frm;
   1213 				break;
   1214 
   1215 			case Tform+RBRA:
   1216 				if(is->curform == nil) {
   1217 					if(warn)
   1218 						fprint(2, "warning: unexpected </FORM>\n");
   1219 					continue;
   1220 				}
   1221 				/* put fields back in input order */
   1222 				is->curform->fields = (Formfield*)_revlist((List*)is->curform->fields);
   1223 				is->curform = nil;
   1224 				break;
   1225 
   1226 			/* <!ELEMENT FRAME - O EMPTY> */
   1227 			case Tframe:
   1228 				ks = is->kidstk;
   1229 				if(ks == nil) {
   1230 					if(warn)
   1231 						fprint(2, "warning: <FRAME> not in <FRAMESET>\n");
   1232 					continue;
   1233 				}
   1234 				ks->kidinfos = kd = newkidinfo(0, ks->kidinfos);
   1235 				kd->src = aurlval(tok, Asrc, nil, di->base);
   1236 				kd->name = aval(tok, Aname);
   1237 				if(kd->name == nil) {
   1238 					s = _ltoStr(++is->nframes);
   1239 					kd->name = _Strdup2(L(Lfr), s);
   1240 					free(s);
   1241 				}
   1242 				kd->marginw = auintval(tok, Amarginwidth, 0);
   1243 				kd->marginh = auintval(tok, Amarginheight, 0);
   1244 				kd->framebd = auintval(tok, Aframeborder, 1);
   1245 				kd->flags = atabval(tok, Ascrolling, fscroll_tab, NFSCROLLTAB, kd->flags);
   1246 				norsz = aflagval(tok, Anoresize);
   1247 				if(norsz)
   1248 					kd->flags |= FRnoresize;
   1249 				break;
   1250 
   1251 			/* <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+> */
   1252 			case Tframeset:
   1253 				ks = newkidinfo(1, nil);
   1254 				pks = is->kidstk;
   1255 				if(pks == nil)
   1256 					di->kidinfo = ks;
   1257 				else  {
   1258 					ks->next = pks->kidinfos;
   1259 					pks->kidinfos = ks;
   1260 				}
   1261 				ks->nextframeset = pks;
   1262 				is->kidstk = ks;
   1263 				setdimarray(tok, Arows, &ks->rows, &ks->nrows);
   1264 				if(ks->nrows == 0) {
   1265 					ks->rows = (Dimen*)emalloc(sizeof(Dimen));
   1266 					ks->nrows = 1;
   1267 					ks->rows[0] = makedimen(Dpercent, 100);
   1268 				}
   1269 				setdimarray(tok, Acols, &ks->cols, &ks->ncols);
   1270 				if(ks->ncols == 0) {
   1271 					ks->cols = (Dimen*)emalloc(sizeof(Dimen));
   1272 					ks->ncols = 1;
   1273 					ks->cols[0] = makedimen(Dpercent, 100);
   1274 				}
   1275 				break;
   1276 
   1277 			case Tframeset+RBRA:
   1278 				if(is->kidstk == nil) {
   1279 					if(warn)
   1280 						fprint(2, "warning: unexpected </FRAMESET>\n");
   1281 					continue;
   1282 				}
   1283 				ks = is->kidstk;
   1284 				/* put kids back in original order */
   1285 				/* and add blank frames to fill out cells */
   1286 				n = ks->nrows*ks->ncols;
   1287 				nblank = n - _listlen((List*)ks->kidinfos);
   1288 				while(nblank-- > 0)
   1289 					ks->kidinfos = newkidinfo(0, ks->kidinfos);
   1290 				ks->kidinfos = (Kidinfo*)_revlist((List*)ks->kidinfos);
   1291 				is->kidstk = is->kidstk->nextframeset;
   1292 				if(is->kidstk == nil) {
   1293 					/* end input */
   1294 					ans = nil;
   1295 					goto return_ans;
   1296 				}
   1297 				break;
   1298 
   1299 			/* <!ELEMENT H1 - - (%text;)*>, etc. */
   1300 			case Th1:
   1301 			case Th2:
   1302 			case Th3:
   1303 			case Th4:
   1304 			case Th5:
   1305 			case Th6:
   1306 				bramt = 1;
   1307 				if(ps->items == ps->lastit)
   1308 					bramt = 0;
   1309 				addbrk(ps, bramt, IFcleft|IFcright);
   1310 				sz = Verylarge - (tag - Th1);
   1311 				if(sz < Tiny)
   1312 					sz = Tiny;
   1313 				pushfontsize(ps, sz);
   1314 				sty = top(&ps->fntstylestk, FntR);
   1315 				if(tag == Th1)
   1316 					sty = FntB;
   1317 				pushfontstyle(ps, sty);
   1318 				pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
   1319 				ps->skipwhite = 1;
   1320 				break;
   1321 
   1322 			case Th1+RBRA:
   1323 			case Th2+RBRA:
   1324 			case Th3+RBRA:
   1325 			case Th4+RBRA:
   1326 			case Th5+RBRA:
   1327 			case Th6+RBRA:
   1328 				addbrk(ps, 1, IFcleft|IFcright);
   1329 				popfontsize(ps);
   1330 				popfontstyle(ps);
   1331 				popjust(ps);
   1332 				break;
   1333 
   1334 			case Thead:
   1335 				/* HTML spec says ignore regular markup in head, */
   1336 				/* but Netscape and IE don't */
   1337 				/* ps.skipping = 1; */
   1338 				break;
   1339 
   1340 			case Thead+RBRA:
   1341 				ps->skipping = 0;
   1342 				break;
   1343 
   1344 			/* <!ELEMENT HR - O EMPTY> */
   1345 			case Thr:
   1346 				al = atabval(tok, Aalign, align_tab, NALIGNTAB, ALcenter);
   1347 				sz = auintval(tok, Asize, HRSZ);
   1348 				wd = adimen(tok, Awidth);
   1349 				if(dimenkind(wd) == Dnone)
   1350 					wd = makedimen(Dpercent, 100);
   1351 				nosh = aflagval(tok, Anoshade);
   1352 				additem(ps, newirule(al, sz, nosh, wd), tok);
   1353 				addbrk(ps, 0, 0);
   1354 				break;
   1355 
   1356 			case Ti:
   1357 			case Tcite:
   1358 			case Tdfn:
   1359 			case Tem:
   1360 			case Tvar:
   1361 			case Taddress:
   1362 				pushfontstyle(ps, FntI);
   1363 				break;
   1364 
   1365 			/* <!ELEMENT IMG - O EMPTY> */
   1366 			case Timg:
   1367 				map = nil;
   1368 				oldcuranchor = ps->curanchor;
   1369 				if(_tokaval(tok, Ausemap, &usemap, 0)) {
   1370 					if(!_prefix(L(Lhash), usemap)) {
   1371 						if(warn)
   1372 							fprint(2, "warning: can't handle non-local map %S\n", usemap);
   1373 					}
   1374 					else {
   1375 						map = getmap(di, usemap+1);
   1376 						if(ps->curanchor == 0) {
   1377 							di->anchors = newanchor(++is->nanchors, nil, nil, di->target, di->anchors);
   1378 							ps->curanchor = is->nanchors;
   1379 						}
   1380 					}
   1381 				}
   1382 				align = atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom);
   1383 				dfltbd = 0;
   1384 				if(ps->curanchor != 0)
   1385 					dfltbd = 2;
   1386 				src = aurlval(tok, Asrc, nil, di->base);
   1387 				if(src == nil) {
   1388 					if(warn)
   1389 						fprint(2, "warning: <img> has no src attribute\n");
   1390 					ps->curanchor = oldcuranchor;
   1391 					continue;
   1392 				}
   1393 				img = newiimage(src,
   1394 						aval(tok, Aalt),
   1395 						align,
   1396 						auintval(tok, Awidth, 0),
   1397 						auintval(tok, Aheight, 0),
   1398 						auintval(tok, Ahspace, IMGHSPACE),
   1399 						auintval(tok, Avspace, IMGVSPACE),
   1400 						auintval(tok, Aborder, dfltbd),
   1401 						aflagval(tok, Aismap),
   1402 						map);
   1403 				if(align == ALleft || align == ALright) {
   1404 					additem(ps, newifloat(img, align), tok);
   1405 					/* if no hspace specified, use FLTIMGHSPACE */
   1406 					if(!_tokaval(tok, Ahspace, &val, 0))
   1407 						((Iimage*)img)->hspace = FLTIMGHSPACE;
   1408 				}
   1409 				else {
   1410 					ps->skipwhite = 0;
   1411 					additem(ps, img, tok);
   1412 				}
   1413 				if(!ps->skipping) {
   1414 					((Iimage*)img)->nextimage = di->images;
   1415 					di->images = (Iimage*)img;
   1416 				}
   1417 				ps->curanchor = oldcuranchor;
   1418 				break;
   1419 
   1420 			/* <!ELEMENT INPUT - O EMPTY> */
   1421 			case Tinput:
   1422 				ps->skipwhite = 0;
   1423 				if(is->curform == nil) {
   1424 					if(warn)
   1425 						fprint(2, "<INPUT> not inside <FORM>\n");
   1426 					continue;
   1427 				}
   1428 				is->curform->fields = field = newformfield(
   1429 						atabval(tok, Atype, input_tab, NINPUTTAB, Ftext),
   1430 						++is->curform->nfields,
   1431 						is->curform,
   1432 						aval(tok, Aname),
   1433 						aval(tok, Avalue),
   1434 						auintval(tok, Asize, 0),
   1435 						auintval(tok, Amaxlength, 1000),
   1436 						is->curform->fields);
   1437 				if(aflagval(tok, Achecked))
   1438 					field->flags = FFchecked;
   1439 
   1440 				switch(field->ftype) {
   1441 				case Ftext:
   1442 				case Fpassword:
   1443 				case Ffile:
   1444 					if(field->size == 0)
   1445 						field->size = 20;
   1446 					break;
   1447 
   1448 				case Fcheckbox:
   1449 					if(field->name == nil) {
   1450 						if(warn)
   1451 							fprint(2, "warning: checkbox form field missing name\n");
   1452 						continue;
   1453 					}
   1454 					if(field->value == nil)
   1455 						field->value = _Strdup(L(Lone));
   1456 					break;
   1457 
   1458 				case Fradio:
   1459 					if(field->name == nil || field->value == nil) {
   1460 						if(warn)
   1461 							fprint(2, "warning: radio form field missing name or value\n");
   1462 						continue;
   1463 					}
   1464 					break;
   1465 
   1466 				case Fsubmit:
   1467 					if(field->value == nil)
   1468 						field->value = _Strdup(L(Lsubmit));
   1469 					if(field->name == nil)
   1470 						field->name = _Strdup(L(Lnoname));
   1471 					break;
   1472 
   1473 				case Fimage:
   1474 					src = aurlval(tok, Asrc, nil, di->base);
   1475 					if(src == nil) {
   1476 						if(warn)
   1477 							fprint(2, "warning: image form field missing src\n");
   1478 						continue;
   1479 					}
   1480 					/* width and height attrs aren't specified in HTML 3.2, */
   1481 					/* but some people provide them and they help avoid */
   1482 					/* a relayout */
   1483 					field->image = newiimage(src,
   1484 						astrval(tok, Aalt, L(Lsubmit)),
   1485 						atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom),
   1486 						auintval(tok, Awidth, 0), auintval(tok, Aheight, 0),
   1487 						0, 0, 0, 0, nil);
   1488 					ii = (Iimage*)field->image;
   1489 					ii->nextimage = di->images;
   1490 					di->images = ii;
   1491 					break;
   1492 
   1493 				case Freset:
   1494 					if(field->value == nil)
   1495 						field->value = _Strdup(L(Lreset));
   1496 					break;
   1497 
   1498 				case Fbutton:
   1499 					if(field->value == nil)
   1500 						field->value = _Strdup(L(Lspace));
   1501 					break;
   1502 				}
   1503 				ffit = newiformfield(field);
   1504 				additem(ps, ffit, tok);
   1505 				if(ffit->genattr != nil)
   1506 					field->events = ffit->genattr->events;
   1507 				break;
   1508 
   1509 			/* <!ENTITY ISINDEX - O EMPTY> */
   1510 			case Tisindex:
   1511 				ps->skipwhite = 0;
   1512 				prompt = astrval(tok, Aprompt, L(Lindex));
   1513 				target = atargval(tok, di->target);
   1514 				additem(ps, textit(ps, prompt), tok);
   1515 				frm = newform(++is->nforms,
   1516 						nil,
   1517 						di->base,
   1518 						target,
   1519 						HGet,
   1520 						di->forms);
   1521 				di->forms = frm;
   1522 				ff = newformfield(Ftext,
   1523 						1,
   1524 						frm,
   1525 						_Strdup(L(Lisindex)),
   1526 						nil,
   1527 						50,
   1528 						1000,
   1529 						nil);
   1530 				frm->fields = ff;
   1531 				frm->nfields = 1;
   1532 				additem(ps, newiformfield(ff), tok);
   1533 				addbrk(ps, 1, 0);
   1534 				break;
   1535 
   1536 			/* <!ELEMENT LI - O %flow> */
   1537 			case Tli:
   1538 				if(ps->listtypestk.n == 0) {
   1539 					if(warn)
   1540 						fprint(2, "<LI> not in list\n");
   1541 					continue;
   1542 				}
   1543 				ty = top(&ps->listtypestk, 0);
   1544 				ty2 = listtyval(tok, ty);
   1545 				if(ty != ty2) {
   1546 					ty = ty2;
   1547 					push(&ps->listtypestk, ty2);
   1548 				}
   1549 				v = aintval(tok, Avalue, top(&ps->listcntstk, 1));
   1550 				if(ty == LTdisc || ty == LTsquare || ty == LTcircle)
   1551 					hang = 10*LISTTAB - 3;
   1552 				else
   1553 					hang = 10*LISTTAB - 1;
   1554 				changehang(ps, hang);
   1555 				addtext(ps, listmark(ty, v));
   1556 				push(&ps->listcntstk, v + 1);
   1557 				changehang(ps, -hang);
   1558 				ps->skipwhite = 1;
   1559 				break;
   1560 
   1561 			/* <!ELEMENT MAP - - (AREA)+> */
   1562 			case Tmap:
   1563 				if(_tokaval(tok, Aname, &name, 0))
   1564 					is->curmap = getmap(di, name);
   1565 				break;
   1566 
   1567 			case Tmap+RBRA:
   1568 				map = is->curmap;
   1569 				if(map == nil) {
   1570 					if(warn)
   1571 						fprint(2, "warning: unexpected </MAP>\n");
   1572 					continue;
   1573 				}
   1574 				map->areas = (Area*)_revlist((List*)map->areas);
   1575 				break;
   1576 
   1577 			case Tmeta:
   1578 				if(ps->skipping)
   1579 					continue;
   1580 				if(_tokaval(tok, Ahttp_equiv, &equiv, 0)) {
   1581 					val = aval(tok, Acontent);
   1582 					n = _Strlen(equiv);
   1583 					if(!_Strncmpci(equiv, n, L(Lrefresh)))
   1584 						di->refresh = val;
   1585 					else if(!_Strncmpci(equiv, n, L(Lcontent))) {
   1586 						n = _Strlen(val);
   1587 						if(!_Strncmpci(val, n, L(Ljavascript))
   1588 						   || !_Strncmpci(val, n, L(Ljscript1))
   1589 						   || !_Strncmpci(val, n, L(Ljscript)))
   1590 							di->scripttype = TextJavascript;
   1591 						else {
   1592 							if(warn)
   1593 								fprint(2, "unimplemented script type %S\n", val);
   1594 							di->scripttype = UnknownType;
   1595 						}
   1596 					}
   1597 				}
   1598 				break;
   1599 
   1600 			/* Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web */
   1601 			case Tnobr:
   1602 				ps->skipwhite = 0;
   1603 				ps->curstate &= ~IFwrap;
   1604 				break;
   1605 
   1606 			case Tnobr+RBRA:
   1607 				ps->curstate |= IFwrap;
   1608 				break;
   1609 
   1610 			/* We do frames, so skip stuff in noframes */
   1611 			case Tnoframes:
   1612 				ps->skipping = 1;
   1613 				break;
   1614 
   1615 			case Tnoframes+RBRA:
   1616 				ps->skipping = 0;
   1617 				break;
   1618 
   1619 			/* We do scripts (if enabled), so skip stuff in noscripts */
   1620 			case Tnoscript:
   1621 				if(doscripts)
   1622 					ps->skipping = 1;
   1623 				break;
   1624 
   1625 			case Tnoscript+RBRA:
   1626 				if(doscripts)
   1627 					ps->skipping = 0;
   1628 				break;
   1629 
   1630 			/* <!ELEMENT OPTION - O (	//PCDATA)> */
   1631 			case Toption:
   1632 				if(is->curform == nil || is->curform->fields == nil) {
   1633 					if(warn)
   1634 						fprint(2, "warning: <OPTION> not in <SELECT>\n");
   1635 					continue;
   1636 				}
   1637 				field = is->curform->fields;
   1638 				if(field->ftype != Fselect) {
   1639 					if(warn)
   1640 						fprint(2, "warning: <OPTION> not in <SELECT>\n");
   1641 					continue;
   1642 				}
   1643 				val = aval(tok, Avalue);
   1644 				option = newoption(aflagval(tok, Aselected), val, nil, field->options);
   1645 				field->options = option;
   1646 				option->display =  getpcdata(toks, tokslen, &toki);
   1647 				if(val == nil)
   1648 					option->value = _Strdup(option->display);
   1649 				break;
   1650 
   1651 			/* <!ELEMENT P - O (%text)* > */
   1652 			case Tp:
   1653 				pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
   1654 				ps->inpar = 1;
   1655 				ps->skipwhite = 1;
   1656 				break;
   1657 
   1658 			case Tp+RBRA:
   1659 				break;
   1660 
   1661 			/* <!ELEMENT PARAM - O EMPTY> */
   1662 			/* Do something when we do applets... */
   1663 			case Tparam:
   1664 				break;
   1665 
   1666 			/* <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) > */
   1667 			case Tpre:
   1668 				ps->curstate &= ~IFwrap;
   1669 				ps->literal = 1;
   1670 				ps->skipwhite = 0;
   1671 				pushfontstyle(ps, FntT);
   1672 				break;
   1673 
   1674 			case Tpre+RBRA:
   1675 				ps->curstate |= IFwrap;
   1676 				if(ps->literal) {
   1677 					popfontstyle(ps);
   1678 					ps->literal = 0;
   1679 				}
   1680 				break;
   1681 
   1682 			/* <!ELEMENT SCRIPT - - CDATA> */
   1683 			case Tscript:
   1684 				if(doscripts) {
   1685 					if(!di->hasscripts) {
   1686 						if(di->scripttype == TextJavascript) {
   1687 							/* TODO: initialize script if nec. */
   1688 							/* initjscript(di); */
   1689 							di->hasscripts = 1;
   1690 						}
   1691 					}
   1692 				}
   1693 				if(!di->hasscripts) {
   1694 					if(warn)
   1695 						fprint(2, "warning: <SCRIPT> ignored\n");
   1696 					ps->skipping = 1;
   1697 				}
   1698 				else {
   1699 					scriptsrc = aurlval(tok, Asrc, nil, di->base);
   1700 					script = nil;
   1701 					if(scriptsrc != nil) {
   1702 						if(warn)
   1703 							fprint(2, "warning: non-local <SCRIPT> ignored\n");
   1704 						free(scriptsrc);
   1705 					}
   1706 					else {
   1707 						script = getpcdata(toks, tokslen, &toki);
   1708 					}
   1709 					if(script != nil) {
   1710 						if(warn)
   1711 							fprint(2, "script ignored\n");
   1712 						free(script);
   1713 					}
   1714 				}
   1715 				break;
   1716 
   1717 			case Tscript+RBRA:
   1718 				ps->skipping = 0;
   1719 				break;
   1720 
   1721 			/* <!ELEMENT SELECT - - (OPTION+)> */
   1722 			case Tselect:
   1723 				if(is->curform == nil) {
   1724 					if(warn)
   1725 						fprint(2, "<SELECT> not inside <FORM>\n");
   1726 					continue;
   1727 				}
   1728 				field = newformfield(Fselect,
   1729 					++is->curform->nfields,
   1730 					is->curform,
   1731 					aval(tok, Aname),
   1732 					nil,
   1733 					auintval(tok, Asize, 0),
   1734 					0,
   1735 					is->curform->fields);
   1736 				is->curform->fields = field;
   1737 				if(aflagval(tok, Amultiple))
   1738 					field->flags = FFmultiple;
   1739 				ffit = newiformfield(field);
   1740 				additem(ps, ffit, tok);
   1741 				if(ffit->genattr != nil)
   1742 					field->events = ffit->genattr->events;
   1743 				/* throw away stuff until next tag (should be <OPTION>) */
   1744 				s = getpcdata(toks, tokslen, &toki);
   1745 				if(s != nil)
   1746 					free(s);
   1747 				break;
   1748 
   1749 			case Tselect+RBRA:
   1750 				if(is->curform == nil || is->curform->fields == nil) {
   1751 					if(warn)
   1752 						fprint(2, "warning: unexpected </SELECT>\n");
   1753 					continue;
   1754 				}
   1755 				field = is->curform->fields;
   1756 				if(field->ftype != Fselect)
   1757 					continue;
   1758 				/* put options back in input order */
   1759 				field->options = (Option*)_revlist((List*)field->options);
   1760 				break;
   1761 
   1762 			/* <!ELEMENT (STRIKE|U) - - (%text)*> */
   1763 			case Tstrike:
   1764 			case Tu:
   1765 				ps->curul = push(&ps->ulstk, (tag==Tstrike)? ULmid : ULunder);
   1766 				break;
   1767 
   1768 			case Tstrike+RBRA:
   1769 			case Tu+RBRA:
   1770 				if(ps->ulstk.n == 0) {
   1771 					if(warn)
   1772 						fprint(2, "warning: unexpected %T\n", tok);
   1773 					continue;
   1774 				}
   1775 				ps->curul = popretnewtop(&ps->ulstk, ULnone);
   1776 				break;
   1777 
   1778 			/* <!ELEMENT STYLE - - CDATA> */
   1779 			case Tstyle:
   1780 				if(warn)
   1781 					fprint(2, "warning: unimplemented <STYLE>\n");
   1782 				ps->skipping = 1;
   1783 				break;
   1784 
   1785 			case Tstyle+RBRA:
   1786 				ps->skipping = 0;
   1787 				break;
   1788 
   1789 			/* <!ELEMENT (SUB|SUP) - - (%text)*> */
   1790 			case Tsub:
   1791 			case Tsup:
   1792 				if(tag == Tsub)
   1793 					ps->curvoff += SUBOFF;
   1794 				else
   1795 					ps->curvoff -= SUPOFF;
   1796 				push(&ps->voffstk, ps->curvoff);
   1797 				sz = top(&ps->fntsizestk, Normal);
   1798 				pushfontsize(ps, sz - 1);
   1799 				break;
   1800 
   1801 			case Tsub+RBRA:
   1802 			case Tsup+RBRA:
   1803 				if(ps->voffstk.n == 0) {
   1804 					if(warn)
   1805 						fprint(2, "warning: unexpected %T\n", tok);
   1806 					continue;
   1807 				}
   1808 				ps->curvoff = popretnewtop(&ps->voffstk, 0);
   1809 				popfontsize(ps);
   1810 				break;
   1811 
   1812 			/* <!ELEMENT TABLE - - (CAPTION?, TR+)> */
   1813 			case Ttable:
   1814 				ps->skipwhite = 0;
   1815 				tab = newtable(++is->ntables,
   1816 						aalign(tok),
   1817 						adimen(tok, Awidth),
   1818 						aflagval(tok, Aborder),
   1819 						auintval(tok, Acellspacing, TABSP),
   1820 						auintval(tok, Acellpadding, TABPAD),
   1821 						makebackground(nil, acolorval(tok, Abgcolor, ps->curbg.color)),
   1822 						tok,
   1823 						is->tabstk);
   1824 				is->tabstk = tab;
   1825 				curtab = tab;
   1826 				break;
   1827 
   1828 			case Ttable+RBRA:
   1829 				if(curtab == nil) {
   1830 					if(warn)
   1831 						fprint(2, "warning: unexpected </TABLE>\n");
   1832 					continue;
   1833 				}
   1834 				isempty = (curtab->cells == nil);
   1835 				if(isempty) {
   1836 					if(warn)
   1837 						fprint(2, "warning: <TABLE> has no cells\n");
   1838 				}
   1839 				else {
   1840 					ps = finishcell(curtab, ps);
   1841 					if(curtab->rows != nil)
   1842 						curtab->rows->flags = 0;
   1843 					finish_table(curtab);
   1844 				}
   1845 				ps->skipping = 0;
   1846 				if(!isempty) {
   1847 					tabitem = newitable(curtab);
   1848 					al = curtab->align.halign;
   1849 					switch(al) {
   1850 					case ALleft:
   1851 					case ALright:
   1852 						additem(ps, newifloat(tabitem, al), tok);
   1853 						break;
   1854 					default:
   1855 						if(al == ALcenter)
   1856 							pushjust(ps, ALcenter);
   1857 						addbrk(ps, 0, 0);
   1858 						if(ps->inpar) {
   1859 							popjust(ps);
   1860 							ps->inpar = 0;
   1861 						}
   1862 						additem(ps, tabitem, curtab->tabletok);
   1863 						if(al == ALcenter)
   1864 							popjust(ps);
   1865 						break;
   1866 					}
   1867 				}
   1868 				if(is->tabstk == nil) {
   1869 					if(warn)
   1870 						fprint(2, "warning: table stack is wrong\n");
   1871 				}
   1872 				else
   1873 					is->tabstk = is->tabstk->next;
   1874 				curtab->next = di->tables;
   1875 				di->tables = curtab;
   1876 				curtab = is->tabstk;
   1877 				if(!isempty)
   1878 					addbrk(ps, 0, 0);
   1879 				break;
   1880 
   1881 			/* <!ELEMENT (TH|TD) - O %body.content> */
   1882 			/* Cells for a row are accumulated in reverse order. */
   1883 			/* We push ps on a stack, and use a new one to accumulate */
   1884 			/* the contents of the cell. */
   1885 			case Ttd:
   1886 			case Tth:
   1887 				if(curtab == nil) {
   1888 					if(warn)
   1889 						fprint(2, "%T outside <TABLE>\n", tok);
   1890 					continue;
   1891 				}
   1892 				if(ps->inpar) {
   1893 					popjust(ps);
   1894 					ps->inpar = 0;
   1895 				}
   1896 				ps = finishcell(curtab, ps);
   1897 				tr = nil;
   1898 				if(curtab->rows != nil)
   1899 					tr = curtab->rows;
   1900 				if(tr == nil || !tr->flags) {
   1901 					if(warn)
   1902 						fprint(2, "%T outside row\n", tok);
   1903 					tr = newtablerow(makealign(ALnone, ALnone),
   1904 							makebackground(nil, curtab->background.color),
   1905 							TFparsing,
   1906 							curtab->rows);
   1907 					curtab->rows = tr;
   1908 				}
   1909 				ps = cell_pstate(ps, tag == Tth);
   1910 				flags = TFparsing;
   1911 				if(aflagval(tok, Anowrap)) {
   1912 					flags |= TFnowrap;
   1913 					ps->curstate &= ~IFwrap;
   1914 				}
   1915 				if(tag == Tth)
   1916 					flags |= TFisth;
   1917 				c = newtablecell(curtab->cells==nil? 1 : curtab->cells->cellid+1,
   1918 						auintval(tok, Arowspan, 1),
   1919 						auintval(tok, Acolspan, 1),
   1920 						aalign(tok),
   1921 						adimen(tok, Awidth),
   1922 						auintval(tok, Aheight, 0),
   1923 						makebackground(nil, acolorval(tok, Abgcolor, tr->background.color)),
   1924 						flags,
   1925 						curtab->cells);
   1926 				curtab->cells = c;
   1927 				ps->curbg = c->background;
   1928 				if(c->align.halign == ALnone) {
   1929 					if(tr->align.halign != ALnone)
   1930 						c->align.halign = tr->align.halign;
   1931 					else if(tag == Tth)
   1932 						c->align.halign = ALcenter;
   1933 					else
   1934 						c->align.halign = ALleft;
   1935 				}
   1936 				if(c->align.valign == ALnone) {
   1937 					if(tr->align.valign != ALnone)
   1938 						c->align.valign = tr->align.valign;
   1939 					else
   1940 						c->align.valign = ALmiddle;
   1941 				}
   1942 				c->nextinrow = tr->cells;
   1943 				tr->cells = c;
   1944 				break;
   1945 
   1946 			case Ttd+RBRA:
   1947 			case Tth+RBRA:
   1948 				if(curtab == nil || curtab->cells == nil) {
   1949 					if(warn)
   1950 						fprint(2, "unexpected %T\n", tok);
   1951 					continue;
   1952 				}
   1953 				ps = finishcell(curtab, ps);
   1954 				break;
   1955 
   1956 			/* <!ELEMENT TEXTAREA - - (	//PCDATA)> */
   1957 			case Ttextarea:
   1958 				if(is->curform == nil) {
   1959 					if(warn)
   1960 						fprint(2, "<TEXTAREA> not inside <FORM>\n");
   1961 					continue;
   1962 				}
   1963 				field = newformfield(Ftextarea,
   1964 					++is->curform->nfields,
   1965 					is->curform,
   1966 					aval(tok, Aname),
   1967 					nil,
   1968 					0,
   1969 					0,
   1970 					is->curform->fields);
   1971 				is->curform->fields = field;
   1972 				field->rows = auintval(tok, Arows, 3);
   1973 				field->cols = auintval(tok, Acols, 50);
   1974 				field->value = getpcdata(toks, tokslen, &toki);
   1975 				if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttextarea + RBRA)
   1976 					fprint(2, "warning: <TEXTAREA> data ended by %T\n", &toks[toki + 1]);
   1977 				ffit = newiformfield(field);
   1978 				additem(ps, ffit, tok);
   1979 				if(ffit->genattr != nil)
   1980 					field->events = ffit->genattr->events;
   1981 				break;
   1982 
   1983 			/* <!ELEMENT TITLE - - (	//PCDATA)* -(%head.misc)> */
   1984 			case Ttitle:
   1985 				di->doctitle = getpcdata(toks, tokslen, &toki);
   1986 				if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttitle + RBRA)
   1987 					fprint(2, "warning: <TITLE> data ended by %T\n", &toks[toki + 1]);
   1988 				break;
   1989 
   1990 			/* <!ELEMENT TR - O (TH|TD)+> */
   1991 			/* rows are accumulated in reverse order in curtab->rows */
   1992 			case Ttr:
   1993 				if(curtab == nil) {
   1994 					if(warn)
   1995 						fprint(2, "warning: <TR> outside <TABLE>\n");
   1996 					continue;
   1997 				}
   1998 				if(ps->inpar) {
   1999 					popjust(ps);
   2000 					ps->inpar = 0;
   2001 				}
   2002 				ps = finishcell(curtab, ps);
   2003 				if(curtab->rows != nil)
   2004 					curtab->rows->flags = 0;
   2005 				curtab->rows = newtablerow(aalign(tok),
   2006 					makebackground(nil, acolorval(tok, Abgcolor, curtab->background.color)),
   2007 					TFparsing,
   2008 					curtab->rows);
   2009 				break;
   2010 
   2011 			case Ttr+RBRA:
   2012 				if(curtab == nil || curtab->rows == nil) {
   2013 					if(warn)
   2014 						fprint(2, "warning: unexpected </TR>\n");
   2015 					continue;
   2016 				}
   2017 				ps = finishcell(curtab, ps);
   2018 				tr = curtab->rows;
   2019 				if(tr->cells == nil) {
   2020 					if(warn)
   2021 						fprint(2, "warning: empty row\n");
   2022 					curtab->rows = tr->next;
   2023 					tr->next = nil;
   2024 				}
   2025 				else
   2026 					tr->flags = 0;
   2027 				break;
   2028 
   2029 			/* <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*> */
   2030 			case Ttt:
   2031 			case Tcode:
   2032 			case Tkbd:
   2033 			case Tsamp:
   2034 				pushfontstyle(ps, FntT);
   2035 				break;
   2036 
   2037 			/* Tags that have empty action */
   2038 			case Tabbr:
   2039 			case Tabbr+RBRA:
   2040 			case Tacronym:
   2041 			case Tacronym+RBRA:
   2042 			case Tarea+RBRA:
   2043 			case Tbase+RBRA:
   2044 			case Tbasefont+RBRA:
   2045 			case Tbr+RBRA:
   2046 			case Tdd+RBRA:
   2047 			case Tdt+RBRA:
   2048 			case Tframe+RBRA:
   2049 			case Thr+RBRA:
   2050 			case Thtml:
   2051 			case Thtml+RBRA:
   2052 			case Timg+RBRA:
   2053 			case Tinput+RBRA:
   2054 			case Tisindex+RBRA:
   2055 			case Tli+RBRA:
   2056 			case Tlink:
   2057 			case Tlink+RBRA:
   2058 			case Tmeta+RBRA:
   2059 			case Toption+RBRA:
   2060 			case Tparam+RBRA:
   2061 			case Ttextarea+RBRA:
   2062 			case Ttitle+RBRA:
   2063 				break;
   2064 
   2065 
   2066 			/* Tags not implemented */
   2067 			case Tbdo:
   2068 			case Tbdo+RBRA:
   2069 			case Tbutton:
   2070 			case Tbutton+RBRA:
   2071 			case Tdel:
   2072 			case Tdel+RBRA:
   2073 			case Tfieldset:
   2074 			case Tfieldset+RBRA:
   2075 			case Tiframe:
   2076 			case Tiframe+RBRA:
   2077 			case Tins:
   2078 			case Tins+RBRA:
   2079 			case Tlabel:
   2080 			case Tlabel+RBRA:
   2081 			case Tlegend:
   2082 			case Tlegend+RBRA:
   2083 			case Tobject:
   2084 			case Tobject+RBRA:
   2085 			case Toptgroup:
   2086 			case Toptgroup+RBRA:
   2087 			case Tspan:
   2088 			case Tspan+RBRA:
   2089 				if(warn) {
   2090 					if(tag > RBRA)
   2091 						tag -= RBRA;
   2092 					fprint(2, "warning: unimplemented HTML tag: %S\n", tagnames[tag]);
   2093 				}
   2094 				break;
   2095 
   2096 			default:
   2097 				if(warn)
   2098 					fprint(2, "warning: unknown HTML tag: %S\n", tok->text);
   2099 				break;
   2100 			}
   2101 	}
   2102 	/* some pages omit trailing </table> */
   2103 	while(curtab != nil) {
   2104 		if(warn)
   2105 			fprint(2, "warning: <TABLE> not closed\n");
   2106 		if(curtab->cells != nil) {
   2107 			ps = finishcell(curtab, ps);
   2108 			if(curtab->cells == nil) {
   2109 				if(warn)
   2110 					fprint(2, "warning: empty table\n");
   2111 			}
   2112 			else {
   2113 				if(curtab->rows != nil)
   2114 					curtab->rows->flags = 0;
   2115 				finish_table(curtab);
   2116 				ps->skipping = 0;
   2117 				additem(ps, newitable(curtab), curtab->tabletok);
   2118 				addbrk(ps, 0, 0);
   2119 			}
   2120 		}
   2121 		if(is->tabstk != nil)
   2122 			is->tabstk = is->tabstk->next;
   2123 		curtab->next = di->tables;
   2124 		di->tables = curtab;
   2125 		curtab = is->tabstk;
   2126 	}
   2127 	outerps = lastps(ps);
   2128 	ans = outerps->items->next;
   2129 	/* note: ans may be nil and di->kids not nil, if there's a frameset! */
   2130 	freeitem(outerps->items);
   2131 	outerps->items = newispacer(ISPnull);
   2132 	outerps->lastit = outerps->items;
   2133 	is->psstk = ps;
   2134 	if(ans != nil && di->hasscripts) {
   2135 		/* TODO evalscript(nil); */
   2136 		;
   2137 	}
   2138 	freeitems(outerps->items);
   2139 
   2140 return_ans:
   2141 	if(dbgbuild) {
   2142 		assert(validitems(ans));
   2143 		if(ans == nil)
   2144 			fprint(2, "getitems returning nil\n");
   2145 		else
   2146 			printitems(ans, "getitems returning:");
   2147 	}
   2148 	_freetokens(toks, tokslen);
   2149 	return ans;
   2150 }
   2151 
   2152 /* Concatenate together maximal set of Data tokens, starting at toks[toki+1]. */
   2153 /* Lexer has ensured that there will either be a following non-data token or */
   2154 /* we will be at eof. */
   2155 /* Return emallocd trimmed concatenation, and update *ptoki to last used toki */
   2156 static Rune*
   2157 getpcdata(Token* toks, int tokslen, int* ptoki)
   2158 {
   2159 	Rune*	ans;
   2160 	Rune*	p;
   2161 	Rune*	trimans;
   2162 	int	anslen;
   2163 	int	trimanslen;
   2164 	int	toki;
   2165 	Token*	tok;
   2166 
   2167 	ans = nil;
   2168 	anslen = 0;
   2169 	/* first find length of answer */
   2170 	toki = (*ptoki) + 1;
   2171 	while(toki < tokslen) {
   2172 		tok = &toks[toki];
   2173 		if(tok->tag == Data) {
   2174 			toki++;
   2175 			anslen += _Strlen(tok->text);
   2176 		}
   2177 		else
   2178 			break;
   2179 	}
   2180 	/* now make up the initial answer */
   2181 	if(anslen > 0) {
   2182 		ans = _newstr(anslen);
   2183 		p = ans;
   2184 		toki = (*ptoki) + 1;
   2185 		while(toki < tokslen) {
   2186 			tok = &toks[toki];
   2187 			if(tok->tag == Data) {
   2188 				toki++;
   2189 				p = _Stradd(p, tok->text, _Strlen(tok->text));
   2190 			}
   2191 			else
   2192 				break;
   2193 		}
   2194 		*p = 0;
   2195 		_trimwhite(ans, anslen, &trimans, &trimanslen);
   2196 		if(trimanslen != anslen) {
   2197 			p = ans;
   2198 			ans = _Strndup(trimans, trimanslen);
   2199 			free(p);
   2200 		}
   2201 	}
   2202 	*ptoki = toki-1;
   2203 	return ans;
   2204 }
   2205 
   2206 /* If still parsing head of curtab->cells list, finish it off */
   2207 /* by transferring the items on the head of psstk to the cell. */
   2208 /* Then pop the psstk and return the new psstk. */
   2209 static Pstate*
   2210 finishcell(Table* curtab, Pstate* psstk)
   2211 {
   2212 	Tablecell*	c;
   2213 	Pstate* psstknext;
   2214 
   2215 	c = curtab->cells;
   2216 	if(c != nil) {
   2217 		if((c->flags&TFparsing)) {
   2218 			psstknext = psstk->next;
   2219 			if(psstknext == nil) {
   2220 				if(warn)
   2221 					fprint(2, "warning: parse state stack is wrong\n");
   2222 			}
   2223 			else {
   2224 				c->content = psstk->items->next;
   2225 				c->flags &= ~TFparsing;
   2226 				freepstate(psstk);
   2227 				psstk = psstknext;
   2228 			}
   2229 		}
   2230 	}
   2231 	return psstk;
   2232 }
   2233 
   2234 /* Make a new Pstate for a cell, based on the old pstate, oldps. */
   2235 /* Also, put the new ps on the head of the oldps stack. */
   2236 static Pstate*
   2237 cell_pstate(Pstate* oldps, int ishead)
   2238 {
   2239 	Pstate*	ps;
   2240 	int	sty;
   2241 
   2242 	ps = newpstate(oldps);
   2243 	ps->skipwhite = 1;
   2244 	ps->curanchor = oldps->curanchor;
   2245 	copystack(&ps->fntstylestk, &oldps->fntstylestk);
   2246 	copystack(&ps->fntsizestk, &oldps->fntsizestk);
   2247 	ps->curfont = oldps->curfont;
   2248 	ps->curfg = oldps->curfg;
   2249 	ps->curbg = oldps->curbg;
   2250 	copystack(&ps->fgstk, &oldps->fgstk);
   2251 	ps->adjsize = oldps->adjsize;
   2252 	if(ishead) {
   2253 		sty = ps->curfont%NumSize;
   2254 		ps->curfont = FntB*NumSize + sty;
   2255 	}
   2256 	return ps;
   2257 }
   2258 
   2259 /* Return a new Pstate with default starting state. */
   2260 /* Use link to add it to head of a list, if any. */
   2261 static Pstate*
   2262 newpstate(Pstate* link)
   2263 {
   2264 	Pstate*	ps;
   2265 
   2266 	ps = (Pstate*)emalloc(sizeof(Pstate));
   2267 	ps->curfont = DefFnt;
   2268 	ps->curfg = Black;
   2269 	ps->curbg.image = nil;
   2270 	ps->curbg.color = White;
   2271 	ps->curul = ULnone;
   2272 	ps->curjust = ALleft;
   2273 	ps->curstate = IFwrap;
   2274 	ps->items = newispacer(ISPnull);
   2275 	ps->lastit = ps->items;
   2276 	ps->prelastit = nil;
   2277 	ps->next = link;
   2278 	return ps;
   2279 }
   2280 
   2281 /* Return last Pstate on psl list */
   2282 static Pstate*
   2283 lastps(Pstate* psl)
   2284 {
   2285 	assert(psl != nil);
   2286 	while(psl->next != nil)
   2287 		psl = psl->next;
   2288 	return psl;
   2289 }
   2290 
   2291 /* Add it to end of ps item chain, adding in current state from ps. */
   2292 /* Also, if tok is not nil, scan it for generic attributes and assign */
   2293 /* the genattr field of the item accordingly. */
   2294 static void
   2295 additem(Pstate* ps, Item* it, Token* tok)
   2296 {
   2297 	int	aid;
   2298 	int	any;
   2299 	Rune*	i;
   2300 	Rune*	c;
   2301 	Rune*	s;
   2302 	Rune*	t;
   2303 	Attr*	a;
   2304 	SEvent*	e;
   2305 
   2306 	if(ps->skipping) {
   2307 		if(warn)
   2308 			fprint(2, "warning: skipping item: %I\n", it);
   2309 		return;
   2310 	}
   2311 	it->anchorid = ps->curanchor;
   2312 	it->state |= ps->curstate;
   2313 	if(tok != nil) {
   2314 		any = 0;
   2315 		i = nil;
   2316 		c = nil;
   2317 		s = nil;
   2318 		t = nil;
   2319 		e = nil;
   2320 		for(a = tok->attr; a != nil; a = a->next) {
   2321 			aid = a->attid;
   2322 			if(!attrinfo[aid])
   2323 				continue;
   2324 			switch(aid) {
   2325 			case Aid:
   2326 				i = a->value;
   2327 				break;
   2328 
   2329 			case Aclass:
   2330 				c = a->value;
   2331 				break;
   2332 
   2333 			case Astyle:
   2334 				s = a->value;
   2335 				break;
   2336 
   2337 			case Atitle:
   2338 				t = a->value;
   2339 				break;
   2340 
   2341 			default:
   2342 				assert(aid >= Aonblur && aid <= Aonunload);
   2343 				e = newscriptevent(scriptev[a->attid], a->value, e);
   2344 				break;
   2345 			}
   2346 			a->value = nil;
   2347 			any = 1;
   2348 		}
   2349 		if(any)
   2350 			it->genattr = newgenattr(i, c, s, t, e);
   2351 	}
   2352 	ps->curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
   2353 	ps->prelastit = ps->lastit;
   2354 	ps->lastit->next = it;
   2355 	ps->lastit = it;
   2356 }
   2357 
   2358 /* Make a text item out of s, */
   2359 /* using current font, foreground, vertical offset and underline state. */
   2360 static Item*
   2361 textit(Pstate* ps, Rune* s)
   2362 {
   2363 	assert(s != nil);
   2364 	return newitext(s, ps->curfont, ps->curfg, ps->curvoff + Voffbias, ps->curul);
   2365 }
   2366 
   2367 /* Add text item or items for s, paying attention to */
   2368 /* current font, foreground, baseline offset, underline state, */
   2369 /* and literal mode.  Unless we're in literal mode, compress */
   2370 /* whitespace to single blank, and, if curstate has a break, */
   2371 /* trim any leading whitespace.  Whether in literal mode or not, */
   2372 /* turn nonbreaking spaces into spacer items with IFnobrk set. */
   2373 /* */
   2374 /* In literal mode, break up s at newlines and add breaks instead. */
   2375 /* Also replace tabs appropriate number of spaces. */
   2376 /* In nonliteral mode, break up the items every 100 or so characters */
   2377 /* just to make the layout algorithm not go quadratic. */
   2378 /* */
   2379 /* addtext assumes ownership of s. */
   2380 static void
   2381 addtext(Pstate* ps, Rune* s)
   2382 {
   2383 	int	n;
   2384 	int	i;
   2385 	int	j;
   2386 	int	k;
   2387 	int	col;
   2388 	int	c;
   2389 	int	nsp;
   2390 	Item*	it;
   2391 	Rune*	ss;
   2392 	Rune*	p;
   2393 	Rune	buf[SMALLBUFSIZE];
   2394 
   2395 	assert(s != nil);
   2396 	n = runestrlen(s);
   2397 	i = 0;
   2398 	j = 0;
   2399 	if(ps->literal) {
   2400 		col = 0;
   2401 		while(i < n) {
   2402 			if(s[i] == '\n') {
   2403 				if(i > j) {
   2404 					/* trim trailing blanks from line */
   2405 					for(k = i; k > j; k--)
   2406 						if(s[k - 1] != ' ')
   2407 							break;
   2408 					if(k > j)
   2409 						additem(ps, textit(ps, _Strndup(s+j, k-j)), nil);
   2410 				}
   2411 				addlinebrk(ps, 0);
   2412 				j = i + 1;
   2413 				col = 0;
   2414 			}
   2415 			else {
   2416 				if(s[i] == '\t') {
   2417 					col += i - j;
   2418 					nsp = 8 - (col%8);
   2419 					/* make ss = s[j:i] + nsp spaces */
   2420 					ss = _newstr(i-j+nsp);
   2421 					p = _Stradd(ss, s+j, i-j);
   2422 					p = _Stradd(p, L(Ltab2space), nsp);
   2423 					*p = 0;
   2424 					additem(ps, textit(ps, ss), nil);
   2425 					col += nsp;
   2426 					j = i + 1;
   2427 				}
   2428 				else if(s[i] == NBSP) {
   2429 					if(i > j)
   2430 						additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
   2431 					addnbsp(ps);
   2432 					col += (i - j) + 1;
   2433 					j = i + 1;
   2434 				}
   2435 			}
   2436 			i++;
   2437 		}
   2438 		if(i > j) {
   2439 			if(j == 0 && i == n) {
   2440 				/* just transfer s over */
   2441 				additem(ps, textit(ps, s), nil);
   2442 			}
   2443 			else {
   2444 				additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
   2445 				free(s);
   2446 			}
   2447 		}
   2448 		else {
   2449 			free(s);
   2450 		}
   2451 	}
   2452 	else {	/* not literal mode */
   2453 		if((ps->curstate&IFbrk) || ps->lastit == ps->items)
   2454 			while(i < n) {
   2455 				c = s[i];
   2456 				if(c >= 256 || !isspace(c))
   2457 					break;
   2458 				i++;
   2459 			}
   2460 		p = buf;
   2461 		for(j = i; i < n; i++) {
   2462 			assert(p+i-j < buf+SMALLBUFSIZE-1);
   2463 			c = s[i];
   2464 			if(c == NBSP) {
   2465 				if(i > j)
   2466 					p = _Stradd(p, s+j, i-j);
   2467 				if(p > buf)
   2468 					additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
   2469 				p = buf;
   2470 				addnbsp(ps);
   2471 				j = i + 1;
   2472 				continue;
   2473 			}
   2474 			if(c < 256 && isspace(c)) {
   2475 				if(i > j)
   2476 					p = _Stradd(p, s+j, i-j);
   2477 				*p++ = ' ';
   2478 				while(i < n - 1) {
   2479 					c = s[i + 1];
   2480 					if(c >= 256 || !isspace(c))
   2481 						break;
   2482 					i++;
   2483 				}
   2484 				j = i + 1;
   2485 			}
   2486 			if(i - j >= 100) {
   2487 				p = _Stradd(p, s+j, i+1-j);
   2488 				j = i + 1;
   2489 			}
   2490 			if(p-buf >= 100) {
   2491 				additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
   2492 				p = buf;
   2493 			}
   2494 		}
   2495 		if(i > j && j < n) {
   2496 			assert(p+i-j < buf+SMALLBUFSIZE-1);
   2497 			p = _Stradd(p, s+j, i-j);
   2498 		}
   2499 		/* don't add a space if previous item ended in a space */
   2500 		if(p-buf == 1 && buf[0] == ' ' && ps->lastit != nil) {
   2501 			it = ps->lastit;
   2502 			if(it->tag == Itexttag) {
   2503 				ss = ((Itext*)it)->s;
   2504 				k = _Strlen(ss);
   2505 				if(k > 0 && ss[k] == ' ')
   2506 					p = buf;
   2507 			}
   2508 		}
   2509 		if(p > buf)
   2510 			additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
   2511 		free(s);
   2512 	}
   2513 }
   2514 
   2515 /* Add a break to ps->curstate, with extra space if sp is true. */
   2516 /* If there was a previous break, combine this one's parameters */
   2517 /* with that to make the amt be the max of the two and the clr */
   2518 /* be the most general. (amt will be 0 or 1) */
   2519 /* Also, if the immediately preceding item was a text item, */
   2520 /* trim any whitespace from the end of it, if not in literal mode. */
   2521 /* Finally, if this is at the very beginning of the item list */
   2522 /* (the only thing there is a null spacer), then don't add the space. */
   2523 static void
   2524 addbrk(Pstate* ps, int sp, int clr)
   2525 {
   2526 	int	state;
   2527 	Rune*	l;
   2528 	int		nl;
   2529 	Rune*	r;
   2530 	int		nr;
   2531 	Itext*	t;
   2532 	Rune*	s;
   2533 
   2534 	state = ps->curstate;
   2535 	clr = clr|(state&(IFcleft|IFcright));
   2536 	if(sp && !(ps->lastit == ps->items))
   2537 		sp = IFbrksp;
   2538 	else
   2539 		sp = 0;
   2540 	ps->curstate = IFbrk|sp|(state&~(IFcleft|IFcright))|clr;
   2541 	if(ps->lastit != ps->items) {
   2542 		if(!ps->literal && ps->lastit->tag == Itexttag) {
   2543 			t = (Itext*)ps->lastit;
   2544 			_splitr(t->s, _Strlen(t->s), notwhitespace, &l, &nl, &r, &nr);
   2545 			/* try to avoid making empty items */
   2546 			/* but not crucial f the occasional one gets through */
   2547 			if(nl == 0 && ps->prelastit != nil) {
   2548 				ps->lastit = ps->prelastit;
   2549 				ps->lastit->next = nil;
   2550 				ps->prelastit = nil;
   2551 			}
   2552 			else {
   2553 				s = t->s;
   2554 				if(nl == 0) {
   2555 					/* need a non-nil pointer to empty string */
   2556 					/* (_Strdup(L(Lempty)) returns nil) */
   2557 					t->s = emalloc(sizeof(Rune));
   2558 					t->s[0] = 0;
   2559 				}
   2560 				else
   2561 					t->s = _Strndup(l, nl);
   2562 				if(s)
   2563 					free(s);
   2564 			}
   2565 		}
   2566 	}
   2567 }
   2568 
   2569 /* Add break due to a <br> or a newline within a preformatted section. */
   2570 /* We add a null item first, with current font's height and ascent, to make */
   2571 /* sure that the current line takes up at least that amount of vertical space. */
   2572 /* This ensures that <br>s on empty lines cause blank lines, and that */
   2573 /* multiple <br>s in a row give multiple blank lines. */
   2574 /* However don't add the spacer if the previous item was something that */
   2575 /* takes up space itself. */
   2576 static void
   2577 addlinebrk(Pstate* ps, int clr)
   2578 {
   2579 	int	obrkstate;
   2580 	int	b;
   2581 	int	addit;
   2582 
   2583 	/* don't want break before our null item unless the previous item */
   2584 	/* was also a null item for the purposes of line breaking */
   2585 	obrkstate = ps->curstate&(IFbrk|IFbrksp);
   2586 	b = IFnobrk;
   2587 	addit = 0;
   2588 	if(ps->lastit != nil) {
   2589 		if(ps->lastit->tag == Ispacertag) {
   2590 			if(((Ispacer*)ps->lastit)->spkind == ISPvline)
   2591 				b = IFbrk;
   2592 			addit = 1;
   2593 		}
   2594 		else if(ps->lastit->tag == Ifloattag)
   2595 			addit = 1;
   2596 	}
   2597 	if(addit) {
   2598 		ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|b;
   2599 		additem(ps, newispacer(ISPvline), nil);
   2600 		ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|obrkstate;
   2601 	}
   2602 	addbrk(ps, 0, clr);
   2603 }
   2604 
   2605 /* Add a nonbreakable space */
   2606 static void
   2607 addnbsp(Pstate* ps)
   2608 {
   2609 	/* if nbsp comes right where a break was specified, */
   2610 	/* do the break anyway (nbsp is being used to generate undiscardable */
   2611 	/* space rather than to prevent a break) */
   2612 	if((ps->curstate&IFbrk) == 0)
   2613 		ps->curstate |= IFnobrk;
   2614 	additem(ps, newispacer(ISPhspace), nil);
   2615 	/* but definitely no break on next item */
   2616 	ps->curstate |= IFnobrk;
   2617 }
   2618 
   2619 /* Change hang in ps.curstate by delta. */
   2620 /* The amount is in 1/10ths of tabs, and is the amount that */
   2621 /* the current contiguous set of items with a hang value set */
   2622 /* is to be shifted left from its normal (indented) place. */
   2623 static void
   2624 changehang(Pstate* ps, int delta)
   2625 {
   2626 	int	amt;
   2627 
   2628 	amt = (ps->curstate&IFhangmask) + delta;
   2629 	if(amt < 0) {
   2630 		if(warn)
   2631 			fprint(2, "warning: hang went negative\n");
   2632 		amt = 0;
   2633 	}
   2634 	ps->curstate = (ps->curstate&~IFhangmask)|amt;
   2635 }
   2636 
   2637 /* Change indent in ps.curstate by delta. */
   2638 static void
   2639 changeindent(Pstate* ps, int delta)
   2640 {
   2641 	int	amt;
   2642 
   2643 	amt = ((ps->curstate&IFindentmask) >> IFindentshift) + delta;
   2644 	if(amt < 0) {
   2645 		if(warn)
   2646 			fprint(2, "warning: indent went negative\n");
   2647 		amt = 0;
   2648 	}
   2649 	ps->curstate = (ps->curstate&~IFindentmask)|(amt << IFindentshift);
   2650 }
   2651 
   2652 /* Push val on top of stack, and also return value pushed */
   2653 static int
   2654 push(Stack* stk, int val)
   2655 {
   2656 	if(stk->n == Nestmax) {
   2657 		if(warn)
   2658 			fprint(2, "warning: build stack overflow\n");
   2659 	}
   2660 	else
   2661 		stk->slots[stk->n++] = val;
   2662 	return val;
   2663 }
   2664 
   2665 /* Pop top of stack */
   2666 static void
   2667 pop(Stack* stk)
   2668 {
   2669 	if(stk->n > 0)
   2670 		--stk->n;
   2671 }
   2672 
   2673 /*Return top of stack, using dflt if stack is empty */
   2674 static int
   2675 top(Stack* stk, int dflt)
   2676 {
   2677 	if(stk->n == 0)
   2678 		return dflt;
   2679 	return stk->slots[stk->n-1];
   2680 }
   2681 
   2682 /* pop, then return new top, with dflt if empty */
   2683 static int
   2684 popretnewtop(Stack* stk, int dflt)
   2685 {
   2686 	if(stk->n == 0)
   2687 		return dflt;
   2688 	stk->n--;
   2689 	if(stk->n == 0)
   2690 		return dflt;
   2691 	return stk->slots[stk->n-1];
   2692 }
   2693 
   2694 /* Copy fromstk entries into tostk */
   2695 static void
   2696 copystack(Stack* tostk, Stack* fromstk)
   2697 {
   2698 	int n;
   2699 
   2700 	n = fromstk->n;
   2701 	tostk->n = n;
   2702 	memmove(tostk->slots, fromstk->slots, n*sizeof(int));
   2703 }
   2704 
   2705 static void
   2706 popfontstyle(Pstate* ps)
   2707 {
   2708 	pop(&ps->fntstylestk);
   2709 	setcurfont(ps);
   2710 }
   2711 
   2712 static void
   2713 pushfontstyle(Pstate* ps, int sty)
   2714 {
   2715 	push(&ps->fntstylestk, sty);
   2716 	setcurfont(ps);
   2717 }
   2718 
   2719 static void
   2720 popfontsize(Pstate* ps)
   2721 {
   2722 	pop(&ps->fntsizestk);
   2723 	setcurfont(ps);
   2724 }
   2725 
   2726 static void
   2727 pushfontsize(Pstate* ps, int sz)
   2728 {
   2729 	push(&ps->fntsizestk, sz);
   2730 	setcurfont(ps);
   2731 }
   2732 
   2733 static void
   2734 setcurfont(Pstate* ps)
   2735 {
   2736 	int	sty;
   2737 	int	sz;
   2738 
   2739 	sty = top(&ps->fntstylestk, FntR);
   2740 	sz = top(&ps->fntsizestk, Normal);
   2741 	if(sz < Tiny)
   2742 		sz = Tiny;
   2743 	if(sz > Verylarge)
   2744 		sz = Verylarge;
   2745 	ps->curfont = sty*NumSize + sz;
   2746 }
   2747 
   2748 static void
   2749 popjust(Pstate* ps)
   2750 {
   2751 	pop(&ps->juststk);
   2752 	setcurjust(ps);
   2753 }
   2754 
   2755 static void
   2756 pushjust(Pstate* ps, int j)
   2757 {
   2758 	push(&ps->juststk, j);
   2759 	setcurjust(ps);
   2760 }
   2761 
   2762 static void
   2763 setcurjust(Pstate* ps)
   2764 {
   2765 	int	j;
   2766 	int	state;
   2767 
   2768 	j = top(&ps->juststk, ALleft);
   2769 	if(j != ps->curjust) {
   2770 		ps->curjust = j;
   2771 		state = ps->curstate;
   2772 		state &= ~(IFrjust|IFcjust);
   2773 		if(j == ALcenter)
   2774 			state |= IFcjust;
   2775 		else if(j == ALright)
   2776 			state |= IFrjust;
   2777 		ps->curstate = state;
   2778 	}
   2779 }
   2780 
   2781 /* Do final rearrangement after table parsing is finished */
   2782 /* and assign cells to grid points */
   2783 static void
   2784 finish_table(Table* t)
   2785 {
   2786 	int	ncol;
   2787 	int	nrow;
   2788 	int	r;
   2789 	Tablerow*	rl;
   2790 	Tablecell*	cl;
   2791 	int*	rowspancnt;
   2792 	Tablecell**	rowspancell;
   2793 	int	ri;
   2794 	int	ci;
   2795 	Tablecell*	c;
   2796 	Tablecell*	cnext;
   2797 	Tablerow*	row;
   2798 	Tablerow*	rownext;
   2799 	int	rcols;
   2800 	int	newncol;
   2801 	int	k;
   2802 	int	j;
   2803 	int	cspan;
   2804 	int	rspan;
   2805 	int	i;
   2806 
   2807 	rl = t->rows;
   2808 	t->nrow = nrow = _listlen((List*)rl);
   2809 	t->rows = (Tablerow*)emalloc(nrow * sizeof(Tablerow));
   2810 	ncol = 0;
   2811 	r = nrow - 1;
   2812 	for(row = rl; row != nil; row = rownext) {
   2813 		/* copy the data from the allocated Tablerow into the array slot */
   2814 		t->rows[r] = *row;
   2815 		rownext = row->next;
   2816 		row = &t->rows[r];
   2817 		r--;
   2818 		rcols = 0;
   2819 		c = row->cells;
   2820 
   2821 		/* If rowspan is > 1 but this is the last row, */
   2822 		/* reset the rowspan */
   2823 		if(c != nil && c->rowspan > 1 && r == nrow-2)
   2824 				c->rowspan = 1;
   2825 
   2826 		/* reverse row->cells list (along nextinrow pointers) */
   2827 		row->cells = nil;
   2828 		while(c != nil) {
   2829 			cnext = c->nextinrow;
   2830 			c->nextinrow = row->cells;
   2831 			row->cells = c;
   2832 			rcols += c->colspan;
   2833 			c = cnext;
   2834 		}
   2835 		if(rcols > ncol)
   2836 			ncol = rcols;
   2837 	}
   2838 	t->ncol = ncol;
   2839 	t->cols = (Tablecol*)emalloc(ncol * sizeof(Tablecol));
   2840 
   2841 	/* Reverse cells just so they are drawn in source order. */
   2842 	/* Also, trim their contents so they don't end in whitespace. */
   2843 	t->cells = (Tablecell*)_revlist((List*)t->cells);
   2844 	for(c = t->cells; c != nil; c= c->next)
   2845 		trim_cell(c);
   2846 	t->grid = (Tablecell***)emalloc(nrow * sizeof(Tablecell**));
   2847 	for(i = 0; i < nrow; i++)
   2848 		t->grid[i] = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
   2849 
   2850 	/* The following arrays keep track of cells that are spanning */
   2851 	/* multiple rows;  rowspancnt[i] is the number of rows left */
   2852 	/* to be spanned in column i. */
   2853 	/* When done, cell's (row,col) is upper left grid point. */
   2854 	rowspancnt = (int*)emalloc(ncol * sizeof(int));
   2855 	rowspancell = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
   2856 	for(ri = 0; ri < nrow; ri++) {
   2857 		row = &t->rows[ri];
   2858 		cl = row->cells;
   2859 		ci = 0;
   2860 		while(ci < ncol || cl != nil) {
   2861 			if(ci < ncol && rowspancnt[ci] > 0) {
   2862 				t->grid[ri][ci] = rowspancell[ci];
   2863 				rowspancnt[ci]--;
   2864 				ci++;
   2865 			}
   2866 			else {
   2867 				if(cl == nil) {
   2868 					ci++;
   2869 					continue;
   2870 				}
   2871 				c = cl;
   2872 				cl = cl->nextinrow;
   2873 				cspan = c->colspan;
   2874 				rspan = c->rowspan;
   2875 				if(ci + cspan > ncol) {
   2876 					/* because of row spanning, we calculated */
   2877 					/* ncol incorrectly; adjust it */
   2878 					newncol = ci + cspan;
   2879 					t->cols = (Tablecol*)erealloc(t->cols, newncol * sizeof(Tablecol));
   2880 					rowspancnt = (int*)erealloc(rowspancnt, newncol * sizeof(int));
   2881 					rowspancell = (Tablecell**)erealloc(rowspancell, newncol * sizeof(Tablecell*));
   2882 					k = newncol-ncol;
   2883 					memset(t->cols+ncol, 0, k*sizeof(Tablecol));
   2884 					memset(rowspancnt+ncol, 0, k*sizeof(int));
   2885 					memset(rowspancell+ncol, 0, k*sizeof(Tablecell*));
   2886 					for(j = 0; j < nrow; j++) {
   2887 						t->grid[j] = (Tablecell**)erealloc(t->grid[j], newncol * sizeof(Tablecell*));
   2888 						memset(t->grid[j], 0, k*sizeof(Tablecell*));
   2889 					}
   2890 					t->ncol = ncol = newncol;
   2891 				}
   2892 				c->row = ri;
   2893 				c->col = ci;
   2894 				for(i = 0; i < cspan; i++) {
   2895 					t->grid[ri][ci] = c;
   2896 					if(rspan > 1) {
   2897 						rowspancnt[ci] = rspan - 1;
   2898 						rowspancell[ci] = c;
   2899 					}
   2900 					ci++;
   2901 				}
   2902 			}
   2903 		}
   2904 	}
   2905 }
   2906 
   2907 /* Remove tail of cell content until it isn't whitespace. */
   2908 static void
   2909 trim_cell(Tablecell* c)
   2910 {
   2911 	int	dropping;
   2912 	Rune*	s;
   2913 	Rune*	x;
   2914 	Rune*	y;
   2915 	int		nx;
   2916 	int		ny;
   2917 	Item*	p;
   2918 	Itext*	q;
   2919 	Item*	pprev;
   2920 
   2921 	dropping = 1;
   2922 	while(c->content != nil && dropping) {
   2923 		p = c->content;
   2924 		pprev = nil;
   2925 		while(p->next != nil) {
   2926 			pprev = p;
   2927 			p = p->next;
   2928 		}
   2929 		dropping = 0;
   2930 		if(!(p->state&IFnobrk)) {
   2931 			if(p->tag == Itexttag) {
   2932 				q = (Itext*)p;
   2933 				s = q->s;
   2934 				_splitr(s, _Strlen(s), notwhitespace, &x, &nx, &y, &ny);
   2935 				if(nx != 0 && ny != 0) {
   2936 					q->s = _Strndup(x, nx);
   2937 					free(s);
   2938 				}
   2939 				break;
   2940 			}
   2941 		}
   2942 		if(dropping) {
   2943 			if(pprev == nil)
   2944 				c->content = nil;
   2945 			else
   2946 				pprev->next = nil;
   2947 			freeitem(p);
   2948 		}
   2949 	}
   2950 }
   2951 
   2952 /* Caller must free answer (eventually). */
   2953 static Rune*
   2954 listmark(uchar ty, int n)
   2955 {
   2956 	Rune*	s;
   2957 	Rune*	t;
   2958 	int	n2;
   2959 	int	i;
   2960 
   2961 	s = nil;
   2962 	switch(ty) {
   2963 	case LTdisc:
   2964 	case LTsquare:
   2965 	case LTcircle:
   2966 		s = _newstr(1);
   2967 		s[0] = (ty == LTdisc)? 0x2022		/* bullet */
   2968 			: ((ty == LTsquare)? 0x220e	/* filled square */
   2969 			    : 0x2218);				/* degree */
   2970 		s[1] = 0;
   2971 		break;
   2972 
   2973 	case LT1:
   2974 		t = _ltoStr(n);
   2975 		n2 = _Strlen(t);
   2976 		s = _newstr(n2+1);
   2977 		t = _Stradd(s, t, n2);
   2978 		*t++ = '.';
   2979 		*t = 0;
   2980 		break;
   2981 
   2982 	case LTa:
   2983 	case LTA:
   2984 		n--;
   2985 		i = 0;
   2986 		if(n < 0)
   2987 			n = 0;
   2988 		s = _newstr((n <= 25)? 2 : 3);
   2989 		if(n > 25) {
   2990 			n2 = n%26;
   2991 			n /= 26;
   2992 			if(n2 > 25)
   2993 				n2 = 25;
   2994 			s[i++] = n2 + (ty == LTa)? 'a' : 'A';
   2995 		}
   2996 		s[i++] = n + (ty == LTa)? 'a' : 'A';
   2997 		s[i++] = '.';
   2998 		s[i] = 0;
   2999 		break;
   3000 
   3001 	case LTi:
   3002 	case LTI:
   3003 		if(n >= NROMAN) {
   3004 			if(warn)
   3005 				fprint(2, "warning: unimplemented roman number > %d\n", NROMAN);
   3006 			n = NROMAN;
   3007 		}
   3008 		t = roman[n - 1];
   3009 		n2 = _Strlen(t);
   3010 		s = _newstr(n2+1);
   3011 		for(i = 0; i < n2; i++)
   3012 			s[i] = (ty == LTi)? tolower(t[i]) : t[i];
   3013 		s[i++] = '.';
   3014 		s[i] = 0;
   3015 		break;
   3016 	}
   3017 	return s;
   3018 }
   3019 
   3020 /* Find map with given name in di.maps. */
   3021 /* If not there, add one, copying name. */
   3022 /* Ownership of map remains with di->maps list. */
   3023 static Map*
   3024 getmap(Docinfo* di, Rune* name)
   3025 {
   3026 	Map*	m;
   3027 
   3028 	for(m = di->maps; m != nil; m = m->next) {
   3029 		if(!_Strcmp(name, m->name))
   3030 			return m;
   3031 	}
   3032 	m = (Map*)emalloc(sizeof(Map));
   3033 	m->name = _Strdup(name);
   3034 	m->areas = nil;
   3035 	m->next = di->maps;
   3036 	di->maps = m;
   3037 	return m;
   3038 }
   3039 
   3040 /* Transfers ownership of href to Area */
   3041 static Area*
   3042 newarea(int shape, Rune* href, int target, Area* link)
   3043 {
   3044 	Area* a;
   3045 
   3046 	a = (Area*)emalloc(sizeof(Area));
   3047 	a->shape = shape;
   3048 	a->href = href;
   3049 	a->target = target;
   3050 	a->next = link;
   3051 	return a;
   3052 }
   3053 
   3054 /* Return string value associated with attid in tok, nil if none. */
   3055 /* Caller must free the result (eventually). */
   3056 static Rune*
   3057 aval(Token* tok, int attid)
   3058 {
   3059 	Rune*	ans;
   3060 
   3061 	_tokaval(tok, attid, &ans, 1);	/* transfers string ownership from token to ans */
   3062 	return ans;
   3063 }
   3064 
   3065 /* Like aval, but use dflt if there was no such attribute in tok. */
   3066 /* Caller must free the result (eventually). */
   3067 static Rune*
   3068 astrval(Token* tok, int attid, Rune* dflt)
   3069 {
   3070 	Rune*	ans;
   3071 
   3072 	if(_tokaval(tok, attid, &ans, 1))
   3073 		return ans;	/* transfers string ownership from token to ans */
   3074 	else
   3075 		return _Strdup(dflt);
   3076 }
   3077 
   3078 /* Here we're supposed to convert to an int, */
   3079 /* and have a default when not found */
   3080 static int
   3081 aintval(Token* tok, int attid, int dflt)
   3082 {
   3083 	Rune*	ans;
   3084 
   3085 	if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
   3086 		return dflt;
   3087 	else
   3088 		return toint(ans);
   3089 }
   3090 
   3091 /* Like aintval, but result should be >= 0 */
   3092 static int
   3093 auintval(Token* tok, int attid, int dflt)
   3094 {
   3095 	Rune* ans;
   3096 	int v;
   3097 
   3098 	if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
   3099 		return dflt;
   3100 	else {
   3101 		v = toint(ans);
   3102 		return v >= 0? v : 0;
   3103 	}
   3104 }
   3105 
   3106 /* int conversion, but with possible error check (if warning) */
   3107 static int
   3108 toint(Rune* s)
   3109 {
   3110 	int ans;
   3111 	Rune* eptr;
   3112 
   3113 	ans = _Strtol(s, &eptr, 10);
   3114 	if(warn) {
   3115 		if(*eptr != 0) {
   3116 			eptr = _Strclass(eptr, notwhitespace);
   3117 			if(eptr != nil)
   3118 				fprint(2, "warning: expected integer, got %S\n", s);
   3119 		}
   3120 	}
   3121 	return ans;
   3122 }
   3123 
   3124 /* Attribute value when need a table to convert strings to ints */
   3125 static int
   3126 atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt)
   3127 {
   3128 	Rune*	aval;
   3129 	int	ans;
   3130 
   3131 	ans = dflt;
   3132 	if(_tokaval(tok, attid, &aval, 0)) {
   3133 		if(!_lookup(tab, ntab, aval, _Strlen(aval), &ans)) {
   3134 			ans = dflt;
   3135 			if(warn)
   3136 				fprint(2, "warning: name not found in table lookup: %S\n", aval);
   3137 		}
   3138 	}
   3139 	return ans;
   3140 }
   3141 
   3142 /* Attribute value when supposed to be a color */
   3143 static int
   3144 acolorval(Token* tok, int attid, int dflt)
   3145 {
   3146 	Rune*	aval;
   3147 	int	ans;
   3148 
   3149 	ans = dflt;
   3150 	if(_tokaval(tok, attid, &aval, 0))
   3151 		ans = color(aval, dflt);
   3152 	return ans;
   3153 }
   3154 
   3155 /* Attribute value when supposed to be a target frame name */
   3156 static int
   3157 atargval(Token* tok, int dflt)
   3158 {
   3159 	int	ans;
   3160 	Rune*	aval;
   3161 
   3162 	ans = dflt;
   3163 	if(_tokaval(tok, Atarget, &aval, 0)){
   3164 		ans = targetid(aval);
   3165 	}
   3166 	return ans;
   3167 }
   3168 
   3169 /* special for list types, where "i" and "I" are different, */
   3170 /* but "square" and "SQUARE" are the same */
   3171 static int
   3172 listtyval(Token* tok, int dflt)
   3173 {
   3174 	Rune*	aval;
   3175 	int	ans;
   3176 	int	n;
   3177 
   3178 	ans = dflt;
   3179 	if(_tokaval(tok, Atype, &aval, 0)) {
   3180 		n = _Strlen(aval);
   3181 		if(n == 1) {
   3182 			switch(aval[0]) {
   3183 			case '1':
   3184 				ans = LT1;
   3185 				break;
   3186 			case 'A':
   3187 				ans = LTA;
   3188 				break;
   3189 			case 'I':
   3190 				ans = LTI;
   3191 				break;
   3192 			case 'a':
   3193 				ans = LTa;
   3194 				break;
   3195 			case 'i':
   3196 				ans = LTi;
   3197 			default:
   3198 				if(warn)
   3199 					fprint(2, "warning: unknown list element type %c\n", aval[0]);
   3200 			}
   3201 		}
   3202 		else {
   3203 			if(!_Strncmpci(aval, n, L(Lcircle)))
   3204 				ans = LTcircle;
   3205 			else if(!_Strncmpci(aval, n, L(Ldisc)))
   3206 				ans = LTdisc;
   3207 			else if(!_Strncmpci(aval, n, L(Lsquare)))
   3208 				ans = LTsquare;
   3209 			else {
   3210 				if(warn)
   3211 					fprint(2, "warning: unknown list element type %S\n", aval);
   3212 			}
   3213 		}
   3214 	}
   3215 	return ans;
   3216 }
   3217 
   3218 /* Attribute value when value is a URL, possibly relative to base. */
   3219 /* FOR NOW: leave the url relative. */
   3220 /* Caller must free the result (eventually). */
   3221 static Rune*
   3222 aurlval(Token* tok, int attid, Rune* dflt, Rune* base)
   3223 {
   3224 	Rune*	ans;
   3225 	Rune*	url;
   3226 
   3227 	USED(base);
   3228 	ans = nil;
   3229 	if(_tokaval(tok, attid, &url, 0) && url != nil)
   3230 		ans = removeallwhite(url);
   3231 	if(ans == nil)
   3232 		ans = _Strdup(dflt);
   3233 	return ans;
   3234 }
   3235 
   3236 /* Return copy of s but with all whitespace (even internal) removed. */
   3237 /* This fixes some buggy URL specification strings. */
   3238 static Rune*
   3239 removeallwhite(Rune* s)
   3240 {
   3241 	int	j;
   3242 	int	n;
   3243 	int	i;
   3244 	int	c;
   3245 	Rune*	ans;
   3246 
   3247 	j = 0;
   3248 	n = _Strlen(s);
   3249 	for(i = 0; i < n; i++) {
   3250 		c = s[i];
   3251 		if(c >= 256 || !isspace(c))
   3252 			j++;
   3253 	}
   3254 	if(j < n) {
   3255 		ans = _newstr(j);
   3256 		j = 0;
   3257 		for(i = 0; i < n; i++) {
   3258 			c = s[i];
   3259 			if(c >= 256 || !isspace(c))
   3260 				ans[j++] = c;
   3261 		}
   3262 		ans[j] = 0;
   3263 	}
   3264 	else
   3265 		ans = _Strdup(s);
   3266 	return ans;
   3267 }
   3268 
   3269 /* Attribute value when mere presence of attr implies value of 1, */
   3270 /* but if there is an integer there, return it as the value. */
   3271 static int
   3272 aflagval(Token* tok, int attid)
   3273 {
   3274 	int	val;
   3275 	Rune*	sval;
   3276 
   3277 	val = 0;
   3278 	if(_tokaval(tok, attid, &sval, 0)) {
   3279 		val = 1;
   3280 		if(sval != nil)
   3281 			val = toint(sval);
   3282 	}
   3283 	return val;
   3284 }
   3285 
   3286 static Align
   3287 makealign(int halign, int valign)
   3288 {
   3289 	Align	al;
   3290 
   3291 	al.halign = halign;
   3292 	al.valign = valign;
   3293 	return al;
   3294 }
   3295 
   3296 /* Make an Align (two alignments, horizontal and vertical) */
   3297 static Align
   3298 aalign(Token* tok)
   3299 {
   3300 	return makealign(
   3301 		atabval(tok, Aalign, align_tab, NALIGNTAB, ALnone),
   3302 		atabval(tok, Avalign, align_tab, NALIGNTAB, ALnone));
   3303 }
   3304 
   3305 /* Make a Dimen, based on value of attid attr */
   3306 static Dimen
   3307 adimen(Token* tok, int attid)
   3308 {
   3309 	Rune*	wd;
   3310 
   3311 	if(_tokaval(tok, attid, &wd, 0))
   3312 		return parsedim(wd, _Strlen(wd));
   3313 	else
   3314 		return makedimen(Dnone, 0);
   3315 }
   3316 
   3317 /* Parse s[0:n] as num[.[num]][unit][%|*] */
   3318 static Dimen
   3319 parsedim(Rune* s, int ns)
   3320 {
   3321 	int	kind;
   3322 	int	spec;
   3323 	Rune*	l;
   3324 	int	nl;
   3325 	Rune*	r;
   3326 	int	nr;
   3327 	int	mul;
   3328 	int	i;
   3329 	Rune*	f;
   3330 	int	nf;
   3331 	int	Tkdpi;
   3332 	Rune*	units;
   3333 
   3334 	kind = Dnone;
   3335 	spec = 0;
   3336 	_splitl(s, ns, L(Lnot0to9), &l, &nl, &r, &nr);
   3337 	if(nl != 0) {
   3338 		spec = 1000*_Strtol(l, nil, 10);
   3339 		if(nr > 0 && r[0] == '.') {
   3340 			_splitl(r+1, nr-1, L(Lnot0to9), &f, &nf, &r, &nr);
   3341 			if(nf != 0) {
   3342 				mul = 100;
   3343 				for(i = 0; i < nf; i++) {
   3344 					spec = spec + mul*(f[i]-'0');
   3345 					mul = mul/10;
   3346 				}
   3347 			}
   3348 		}
   3349 		kind = Dpixels;
   3350 		if(nr != 0) {
   3351 			if(nr >= 2) {
   3352 				Tkdpi = 100;
   3353 				units = r;
   3354 				r = r+2;
   3355 				nr -= 2;
   3356 				if(!_Strncmpci(units, 2, L(Lpt)))
   3357 					spec = (spec*Tkdpi)/72;
   3358 				else if(!_Strncmpci(units, 2, L(Lpi)))
   3359 					spec = (spec*12*Tkdpi)/72;
   3360 				else if(!_Strncmpci(units, 2, L(Lin)))
   3361 					spec = spec*Tkdpi;
   3362 				else if(!_Strncmpci(units, 2, L(Lcm)))
   3363 					spec = (spec*100*Tkdpi)/254;
   3364 				else if(!_Strncmpci(units, 2, L(Lmm)))
   3365 					spec = (spec*10*Tkdpi)/254;
   3366 				else if(!_Strncmpci(units, 2, L(Lem)))
   3367 					spec = spec*15;
   3368 				else {
   3369 					if(warn)
   3370 						fprint(2, "warning: unknown units %C%Cs\n", units[0], units[1]);
   3371 				}
   3372 			}
   3373 			if(nr >= 1) {
   3374 				if(r[0] == '%')
   3375 					kind = Dpercent;
   3376 				else if(r[0] == '*')
   3377 					kind = Drelative;
   3378 			}
   3379 		}
   3380 		spec = spec/1000;
   3381 	}
   3382 	else if(nr == 1 && r[0] == '*') {
   3383 		spec = 1;
   3384 		kind = Drelative;
   3385 	}
   3386 	return makedimen(kind, spec);
   3387 }
   3388 
   3389 static void
   3390 setdimarray(Token* tok, int attid, Dimen** pans, int* panslen)
   3391 {
   3392 	Rune*	s;
   3393 	Dimen*	d;
   3394 	int	k;
   3395 	int	nc;
   3396 	Rune* a[SMALLBUFSIZE];
   3397 	int	an[SMALLBUFSIZE];
   3398 
   3399 	if(_tokaval(tok, attid, &s, 0)) {
   3400 		nc = _splitall(s, _Strlen(s), L(Lcommaspace), a, an, SMALLBUFSIZE);
   3401 		if(nc > 0) {
   3402 			d = (Dimen*)emalloc(nc * sizeof(Dimen));
   3403 			for(k = 0; k < nc; k++) {
   3404 				d[k] = parsedim(a[k], an[k]);
   3405 			}
   3406 			*pans = d;
   3407 			*panslen = nc;
   3408 			return;
   3409 		}
   3410 	}
   3411 	*pans = nil;
   3412 	*panslen = 0;
   3413 }
   3414 
   3415 static Background
   3416 makebackground(Rune* imageurl, int color)
   3417 {
   3418 	Background bg;
   3419 
   3420 	bg.image = imageurl;
   3421 	bg.color = color;
   3422 	return bg;
   3423 }
   3424 
   3425 static Item*
   3426 newitext(Rune* s, int fnt, int fg, int voff, int ul)
   3427 {
   3428 	Itext* t;
   3429 
   3430 	assert(s != nil);
   3431 	t = (Itext*)emalloc(sizeof(Itext));
   3432 	t->item.tag = Itexttag;
   3433 	t->s = s;
   3434 	t->fnt = fnt;
   3435 	t->fg = fg;
   3436 	t->voff = voff;
   3437 	t->ul = ul;
   3438 	return (Item*)t;
   3439 }
   3440 
   3441 static Item*
   3442 newirule(int align, int size, int noshade, Dimen wspec)
   3443 {
   3444 	Irule* r;
   3445 
   3446 	r = (Irule*)emalloc(sizeof(Irule));
   3447 	r->item.tag = Iruletag;
   3448 	r->align = align;
   3449 	r->size = size;
   3450 	r->noshade = noshade;
   3451 	r->wspec = wspec;
   3452 	return (Item*)r;
   3453 }
   3454 
   3455 /* Map is owned elsewhere. */
   3456 static Item*
   3457 newiimage(Rune* src, Rune* altrep, int align, int width, int height,
   3458 		int hspace, int vspace, int border, int ismap, Map* map)
   3459 {
   3460 	Iimage* i;
   3461 	int	state;
   3462 
   3463 	state = 0;
   3464 	if(ismap)
   3465 		state = IFsmap;
   3466 	i = (Iimage*)emalloc(sizeof(Iimage));
   3467 	i->item.tag = Iimagetag;
   3468 	i->item.state = state;
   3469 	i->imsrc = src;
   3470 	i->altrep = altrep;
   3471 	i->align = align;
   3472 	i->imwidth = width;
   3473 	i->imheight = height;
   3474 	i->hspace = hspace;
   3475 	i->vspace = vspace;
   3476 	i->border = border;
   3477 	i->map = map;
   3478 	i->ctlid = -1;
   3479 	return (Item*)i;
   3480 }
   3481 
   3482 static Item*
   3483 newiformfield(Formfield* ff)
   3484 {
   3485 	Iformfield* f;
   3486 
   3487 	f = (Iformfield*)emalloc(sizeof(Iformfield));
   3488 	f->item.tag = Iformfieldtag;
   3489 	f->formfield = ff;
   3490 	return (Item*)f;
   3491 }
   3492 
   3493 static Item*
   3494 newitable(Table* tab)
   3495 {
   3496 	Itable* t;
   3497 
   3498 	t = (Itable*)emalloc(sizeof(Itable));
   3499 	t->item.tag = Itabletag;
   3500 	t->table = tab;
   3501 	return (Item*)t;
   3502 }
   3503 
   3504 static Item*
   3505 newifloat(Item* it, int side)
   3506 {
   3507 	Ifloat* f;
   3508 
   3509 	f = (Ifloat*)emalloc(sizeof(Ifloat));
   3510 	f->_item.tag = Ifloattag;
   3511 	f->_item.state = IFwrap;
   3512 	f->item = it;
   3513 	f->side = side;
   3514 	return (Item*)f;
   3515 }
   3516 
   3517 static Item*
   3518 newispacer(int spkind)
   3519 {
   3520 	Ispacer* s;
   3521 
   3522 	s = (Ispacer*)emalloc(sizeof(Ispacer));
   3523 	s->item.tag = Ispacertag;
   3524 	s->spkind = spkind;
   3525 	return (Item*)s;
   3526 }
   3527 
   3528 /* Free one item (caller must deal with next pointer) */
   3529 static void
   3530 freeitem(Item* it)
   3531 {
   3532 	Iimage* ii;
   3533 	Genattr* ga;
   3534 
   3535 	if(it == nil)
   3536 		return;
   3537 
   3538 	switch(it->tag) {
   3539 	case Itexttag:
   3540 		free(((Itext*)it)->s);
   3541 		break;
   3542 	case Iimagetag:
   3543 		ii = (Iimage*)it;
   3544 		free(ii->imsrc);
   3545 		free(ii->altrep);
   3546 		break;
   3547 	case Iformfieldtag:
   3548 		freeformfield(((Iformfield*)it)->formfield);
   3549 		break;
   3550 	case Itabletag:
   3551 		freetable(((Itable*)it)->table);
   3552 		break;
   3553 	case Ifloattag:
   3554 		freeitem(((Ifloat*)it)->item);
   3555 		break;
   3556 	}
   3557 	ga = it->genattr;
   3558 	if(ga != nil) {
   3559 		free(ga->id);
   3560 		free(ga->class);
   3561 		free(ga->style);
   3562 		free(ga->title);
   3563 		freescriptevents(ga->events);
   3564 	}
   3565 	free(it);
   3566 }
   3567 
   3568 /* Free list of items chained through next pointer */
   3569 void
   3570 freeitems(Item* ithead)
   3571 {
   3572 	Item* it;
   3573 	Item* itnext;
   3574 
   3575 	it = ithead;
   3576 	while(it != nil) {
   3577 		itnext = it->next;
   3578 		freeitem(it);
   3579 		it = itnext;
   3580 	}
   3581 }
   3582 
   3583 static void
   3584 freeformfield(Formfield* ff)
   3585 {
   3586 	Option* o;
   3587 	Option* onext;
   3588 
   3589 	if(ff == nil)
   3590 		return;
   3591 
   3592 	free(ff->name);
   3593 	free(ff->value);
   3594 	for(o = ff->options; o != nil; o = onext) {
   3595 		onext = o->next;
   3596 		free(o->value);
   3597 		free(o->display);
   3598 	}
   3599 	free(ff);
   3600 }
   3601 
   3602 static void
   3603 freetable(Table* t)
   3604 {
   3605 	int i;
   3606 	Tablecell* c;
   3607 	Tablecell* cnext;
   3608 
   3609 	if(t == nil)
   3610 		return;
   3611 
   3612 	/* We'll find all the unique cells via t->cells and next pointers. */
   3613 	/* (Other pointers to cells in the table are duplicates of these) */
   3614 	for(c = t->cells; c != nil; c = cnext) {
   3615 		cnext = c->next;
   3616 		freeitems(c->content);
   3617 	}
   3618 	if(t->grid != nil) {
   3619 		for(i = 0; i < t->nrow; i++)
   3620 			free(t->grid[i]);
   3621 		free(t->grid);
   3622 	}
   3623 	free(t->rows);
   3624 	free(t->cols);
   3625 	freeitems(t->caption);
   3626 	free(t);
   3627 }
   3628 
   3629 static void
   3630 freeform(Form* f)
   3631 {
   3632 	if(f == nil)
   3633 		return;
   3634 
   3635 	free(f->name);
   3636 	free(f->action);
   3637 	/* Form doesn't own its fields (Iformfield items do) */
   3638 	free(f);
   3639 }
   3640 
   3641 static void
   3642 freeforms(Form* fhead)
   3643 {
   3644 	Form* f;
   3645 	Form* fnext;
   3646 
   3647 	for(f = fhead; f != nil; f = fnext) {
   3648 		fnext = f->next;
   3649 		freeform(f);
   3650 	}
   3651 }
   3652 
   3653 static void
   3654 freeanchor(Anchor* a)
   3655 {
   3656 	if(a == nil)
   3657 		return;
   3658 
   3659 	free(a->name);
   3660 	free(a->href);
   3661 	free(a);
   3662 }
   3663 
   3664 static void
   3665 freeanchors(Anchor* ahead)
   3666 {
   3667 	Anchor* a;
   3668 	Anchor* anext;
   3669 
   3670 	for(a = ahead; a != nil; a = anext) {
   3671 		anext = a->next;
   3672 		freeanchor(a);
   3673 	}
   3674 }
   3675 
   3676 static void
   3677 freedestanchor(DestAnchor* da)
   3678 {
   3679 	if(da == nil)
   3680 		return;
   3681 
   3682 	free(da->name);
   3683 	free(da);
   3684 }
   3685 
   3686 static void
   3687 freedestanchors(DestAnchor* dahead)
   3688 {
   3689 	DestAnchor* da;
   3690 	DestAnchor* danext;
   3691 
   3692 	for(da = dahead; da != nil; da = danext) {
   3693 		danext = da->next;
   3694 		freedestanchor(da);
   3695 	}
   3696 }
   3697 
   3698 static void
   3699 freearea(Area* a)
   3700 {
   3701 	if(a == nil)
   3702 		return;
   3703 	free(a->href);
   3704 	free(a->coords);
   3705 }
   3706 
   3707 static void freekidinfos(Kidinfo* khead);
   3708 
   3709 static void
   3710 freekidinfo(Kidinfo* k)
   3711 {
   3712 	if(k->isframeset) {
   3713 		free(k->rows);
   3714 		free(k->cols);
   3715 		freekidinfos(k->kidinfos);
   3716 	}
   3717 	else {
   3718 		free(k->src);
   3719 		free(k->name);
   3720 	}
   3721 	free(k);
   3722 }
   3723 
   3724 static void
   3725 freekidinfos(Kidinfo* khead)
   3726 {
   3727 	Kidinfo* k;
   3728 	Kidinfo* knext;
   3729 
   3730 	for(k = khead; k != nil; k = knext) {
   3731 		knext = k->next;
   3732 		freekidinfo(k);
   3733 	}
   3734 }
   3735 
   3736 static void
   3737 freemap(Map* m)
   3738 {
   3739 	Area* a;
   3740 	Area* anext;
   3741 
   3742 	if(m == nil)
   3743 		return;
   3744 
   3745 	free(m->name);
   3746 	for(a = m->areas; a != nil; a = anext) {
   3747 		anext = a->next;
   3748 		freearea(a);
   3749 	}
   3750 	free(m);
   3751 }
   3752 
   3753 static void
   3754 freemaps(Map* mhead)
   3755 {
   3756 	Map* m;
   3757 	Map* mnext;
   3758 
   3759 	for(m = mhead; m != nil; m = mnext) {
   3760 		mnext = m->next;
   3761 		freemap(m);
   3762 	}
   3763 }
   3764 
   3765 void
   3766 freedocinfo(Docinfo* d)
   3767 {
   3768 	if(d == nil)
   3769 		return;
   3770 	free(d->src);
   3771 	free(d->base);
   3772 	freeitem((Item*)d->backgrounditem);
   3773 	free(d->refresh);
   3774 	freekidinfos(d->kidinfo);
   3775 	freeanchors(d->anchors);
   3776 	freedestanchors(d->dests);
   3777 	freeforms(d->forms);
   3778 	freemaps(d->maps);
   3779 	/* tables, images, and formfields are freed when */
   3780 	/* the items pointing at them are freed */
   3781 	free(d);
   3782 }
   3783 
   3784 /* Currently, someone else owns all the memory */
   3785 /* pointed to by things in a Pstate. */
   3786 static void
   3787 freepstate(Pstate* p)
   3788 {
   3789 	free(p);
   3790 }
   3791 
   3792 static void
   3793 freepstatestack(Pstate* pshead)
   3794 {
   3795 	Pstate* p;
   3796 	Pstate* pnext;
   3797 
   3798 	for(p = pshead; p != nil; p = pnext) {
   3799 		pnext = p->next;
   3800 		free(p);
   3801 	}
   3802 }
   3803 
   3804 static int
   3805 Iconv(Fmt *f)
   3806 {
   3807 	Item*	it;
   3808 	Itext*	t;
   3809 	Irule*	r;
   3810 	Iimage*	i;
   3811 	Ifloat*	fl;
   3812 	int	state;
   3813 	Formfield*	ff;
   3814 	Rune*	ty;
   3815 	Tablecell*	c;
   3816 	Table*	tab;
   3817 	char*	p;
   3818 	int	cl;
   3819 	int	hang;
   3820 	int	indent;
   3821 	int	bi;
   3822 	int	nbuf;
   3823 	char	buf[BIGBUFSIZE];
   3824 
   3825 	it = va_arg(f->args, Item*);
   3826 	bi = 0;
   3827 	nbuf = sizeof(buf);
   3828 	state = it->state;
   3829 	nbuf = nbuf-1;
   3830 	if(state&IFbrk) {
   3831 		cl = state&(IFcleft|IFcright);
   3832 		p = "";
   3833 		if(cl) {
   3834 			if(cl == (IFcleft|IFcright))
   3835 				p = " both";
   3836 			else if(cl == IFcleft)
   3837 				p = " left";
   3838 			else
   3839 				p = " right";
   3840 		}
   3841 		bi = snprint(buf, nbuf, "brk(%d%s)", (state&IFbrksp)? 1 : 0, p);
   3842 	}
   3843 	if(state&IFnobrk)
   3844 		bi += snprint(buf+bi, nbuf-bi, " nobrk");
   3845 	if(!(state&IFwrap))
   3846 		bi += snprint(buf+bi, nbuf-bi, " nowrap");
   3847 	if(state&IFrjust)
   3848 		bi += snprint(buf+bi, nbuf-bi, " rjust");
   3849 	if(state&IFcjust)
   3850 		bi += snprint(buf+bi, nbuf-bi, " cjust");
   3851 	if(state&IFsmap)
   3852 		bi += snprint(buf+bi, nbuf-bi, " smap");
   3853 	indent = (state&IFindentmask) >> IFindentshift;
   3854 	if(indent > 0)
   3855 		bi += snprint(buf+bi, nbuf-bi, " indent=%d", indent);
   3856 	hang = state&IFhangmask;
   3857 	if(hang > 0)
   3858 		bi += snprint(buf+bi, nbuf-bi, " hang=%d", hang);
   3859 
   3860 	switch(it->tag) {
   3861 	case Itexttag:
   3862 		t = (Itext*)it;
   3863 		bi += snprint(buf+bi, nbuf-bi, " Text '%S', fnt=%d, fg=%x", t->s, t->fnt, t->fg);
   3864 		break;
   3865 
   3866 	case Iruletag:
   3867 		r = (Irule*)it;
   3868 		bi += snprint(buf+bi, nbuf-bi, "Rule size=%d, al=%S, wspec=", r->size, stringalign(r->align));
   3869 		bi += dimprint(buf+bi, nbuf-bi, r->wspec);
   3870 		break;
   3871 
   3872 	case Iimagetag:
   3873 		i = (Iimage*)it;
   3874 		bi += snprint(buf+bi, nbuf-bi,
   3875 			"Image src=%S, alt=%S, al=%S, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%S",
   3876 			i->imsrc, i->altrep? i->altrep : L(Lempty), stringalign(i->align), i->imwidth, i->imheight,
   3877 			i->hspace, i->vspace, i->border, i->map?i->map->name : L(Lempty));
   3878 		break;
   3879 
   3880 	case Iformfieldtag:
   3881 		ff = ((Iformfield*)it)->formfield;
   3882 		if(ff->ftype == Ftextarea)
   3883 			ty = L(Ltextarea);
   3884 		else if(ff->ftype == Fselect)
   3885 			ty = L(Lselect);
   3886 		else {
   3887 			ty = _revlookup(input_tab, NINPUTTAB, ff->ftype);
   3888 			if(ty == nil)
   3889 				ty = L(Lnone);
   3890 		}
   3891 		bi += snprint(buf+bi, nbuf-bi, "Formfield %S, fieldid=%d, formid=%d, name=%S, value=%S",
   3892 			ty, ff->fieldid, ff->form->formid, ff->name?  ff->name : L(Lempty),
   3893 			ff->value? ff->value : L(Lempty));
   3894 		break;
   3895 
   3896 	case Itabletag:
   3897 		tab = ((Itable*)it)->table;
   3898 		bi += snprint(buf+bi, nbuf-bi, "Table tableid=%d, width=", tab->tableid);
   3899 		bi += dimprint(buf+bi, nbuf-bi, tab->width);
   3900 		bi += snprint(buf+bi, nbuf-bi, ", nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
   3901 			tab->nrow, tab->ncol, tab->ncell, tab->totw, tab->toth);
   3902 		for(c = tab->cells; c != nil; c = c->next)
   3903 			bi += snprint(buf+bi, nbuf-bi, "Cell %d.%d, at (%d,%d) ",
   3904 					tab->tableid, c->cellid, c->row, c->col);
   3905 		bi += snprint(buf+bi, nbuf-bi, "End of Table %d", tab->tableid);
   3906 		break;
   3907 
   3908 	case Ifloattag:
   3909 		fl = (Ifloat*)it;
   3910 		bi += snprint(buf+bi, nbuf-bi, "Float, x=%d y=%d, side=%S, it=%I",
   3911 			fl->x, fl->y, stringalign(fl->side), fl->item);
   3912 		bi += snprint(buf+bi, nbuf-bi, "\n\t");
   3913 		break;
   3914 
   3915 	case Ispacertag:
   3916 		p = "";
   3917 		switch(((Ispacer*)it)->spkind) {
   3918 		case ISPnull:
   3919 			p = "null";
   3920 			break;
   3921 		case ISPvline:
   3922 			p = "vline";
   3923 			break;
   3924 		case ISPhspace:
   3925 			p = "hspace";
   3926 			break;
   3927 		}
   3928 		bi += snprint(buf+bi, nbuf-bi, "Spacer %s ", p);
   3929 		break;
   3930 	}
   3931 	bi += snprint(buf+bi, nbuf-bi, " w=%d, h=%d, a=%d, anchor=%d\n",
   3932 			it->width, it->height, it->ascent, it->anchorid);
   3933 	buf[bi] = 0;
   3934 	return fmtstrcpy(f, buf);
   3935 }
   3936 
   3937 /* String version of alignment 'a' */
   3938 static Rune*
   3939 stringalign(int a)
   3940 {
   3941 	Rune*	s;
   3942 
   3943 	s = _revlookup(align_tab, NALIGNTAB, a);
   3944 	if(s == nil)
   3945 		s = L(Lnone);
   3946 	return s;
   3947 }
   3948 
   3949 /* Put at most nbuf chars of representation of d into buf, */
   3950 /* and return number of characters put */
   3951 static int
   3952 dimprint(char* buf, int nbuf, Dimen d)
   3953 {
   3954 	int	n;
   3955 	int	k;
   3956 
   3957 	n = 0;
   3958 	n += snprint(buf, nbuf, "%d", dimenspec(d));
   3959 	k = dimenkind(d);
   3960 	if(k == Dpercent)
   3961 		buf[n++] = '%';
   3962 	if(k == Drelative)
   3963 		buf[n++] = '*';
   3964 	return n;
   3965 }
   3966 
   3967 void
   3968 printitems(Item* items, char* msg)
   3969 {
   3970 	Item*	il;
   3971 
   3972 	fprint(2, "%s\n", msg);
   3973 	il = items;
   3974 	while(il != nil) {
   3975 		fprint(2, "%I", il);
   3976 		il = il->next;
   3977 	}
   3978 }
   3979 
   3980 static Genattr*
   3981 newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events)
   3982 {
   3983 	Genattr* g;
   3984 
   3985 	g = (Genattr*)emalloc(sizeof(Genattr));
   3986 	g->id = id;
   3987 	g->class = class;
   3988 	g->style = style;
   3989 	g->title = title;
   3990 	g->events = events;
   3991 	return g;
   3992 }
   3993 
   3994 static Formfield*
   3995 newformfield(int ftype, int fieldid, Form* form, Rune* name,
   3996 		Rune* value, int size, int maxlength, Formfield* link)
   3997 {
   3998 	Formfield* ff;
   3999 
   4000 	ff = (Formfield*)emalloc(sizeof(Formfield));
   4001 	ff->ftype = ftype;
   4002 	ff->fieldid = fieldid;
   4003 	ff->form = form;
   4004 	ff->name = name;
   4005 	ff->value = value;
   4006 	ff->size = size;
   4007 	ff->maxlength = maxlength;
   4008 	ff->ctlid = -1;
   4009 	ff->next = link;
   4010 	return ff;
   4011 }
   4012 
   4013 /* Transfers ownership of value and display to Option. */
   4014 static Option*
   4015 newoption(int selected, Rune* value, Rune* display, Option* link)
   4016 {
   4017 	Option *o;
   4018 
   4019 	o = (Option*)emalloc(sizeof(Option));
   4020 	o->selected = selected;
   4021 	o->value = value;
   4022 	o->display = display;
   4023 	o->next = link;
   4024 	return o;
   4025 }
   4026 
   4027 static Form*
   4028 newform(int formid, Rune* name, Rune* action, int target, int method, Form* link)
   4029 {
   4030 	Form* f;
   4031 
   4032 	f = (Form*)emalloc(sizeof(Form));
   4033 	f->formid = formid;
   4034 	f->name = name;
   4035 	f->action = action;
   4036 	f->target = target;
   4037 	f->method = method;
   4038 	f->nfields = 0;
   4039 	f->fields = nil;
   4040 	f->next = link;
   4041 	return f;
   4042 }
   4043 
   4044 static Table*
   4045 newtable(int tableid, Align align, Dimen width, int border,
   4046 	int cellspacing, int cellpadding, Background bg, Token* tok, Table* link)
   4047 {
   4048 	Table* t;
   4049 
   4050 	t = (Table*)emalloc(sizeof(Table));
   4051 	t->tableid = tableid;
   4052 	t->align = align;
   4053 	t->width = width;
   4054 	t->border = border;
   4055 	t->cellspacing = cellspacing;
   4056 	t->cellpadding = cellpadding;
   4057 	t->background = bg;
   4058 	t->caption_place = ALbottom;
   4059 	t->caption_lay = nil;
   4060 	t->tabletok = tok;
   4061 	t->tabletok = nil;
   4062 	t->next = link;
   4063 	return t;
   4064 }
   4065 
   4066 static Tablerow*
   4067 newtablerow(Align align, Background bg, int flags, Tablerow* link)
   4068 {
   4069 	Tablerow* tr;
   4070 
   4071 	tr = (Tablerow*)emalloc(sizeof(Tablerow));
   4072 	tr->align = align;
   4073 	tr->background = bg;
   4074 	tr->flags = flags;
   4075 	tr->next = link;
   4076 	return tr;
   4077 }
   4078 
   4079 static Tablecell*
   4080 newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec, int hspec,
   4081 		Background bg, int flags, Tablecell* link)
   4082 {
   4083 	Tablecell* c;
   4084 
   4085 	c = (Tablecell*)emalloc(sizeof(Tablecell));
   4086 	c->cellid = cellid;
   4087 	c->lay = nil;
   4088 	c->rowspan = rowspan;
   4089 	c->colspan = colspan;
   4090 	c->align = align;
   4091 	c->flags = flags;
   4092 	c->wspec = wspec;
   4093 	c->hspec = hspec;
   4094 	c->background = bg;
   4095 	c->next = link;
   4096 	return c;
   4097 }
   4098 
   4099 static Anchor*
   4100 newanchor(int index, Rune* name, Rune* href, int target, Anchor* link)
   4101 {
   4102 	Anchor* a;
   4103 
   4104 	a = (Anchor*)emalloc(sizeof(Anchor));
   4105 	a->index = index;
   4106 	a->name = name;
   4107 	a->href = href;
   4108 	a->target = target;
   4109 	a->next = link;
   4110 	return a;
   4111 }
   4112 
   4113 static DestAnchor*
   4114 newdestanchor(int index, Rune* name, Item* item, DestAnchor* link)
   4115 {
   4116 	DestAnchor* d;
   4117 
   4118 	d = (DestAnchor*)emalloc(sizeof(DestAnchor));
   4119 	d->index = index;
   4120 	d->name = name;
   4121 	d->item = item;
   4122 	d->next = link;
   4123 	return d;
   4124 }
   4125 
   4126 static SEvent*
   4127 newscriptevent(int type, Rune* script, SEvent* link)
   4128 {
   4129 	SEvent* ans;
   4130 
   4131 	ans = (SEvent*)emalloc(sizeof(SEvent));
   4132 	ans->type = type;
   4133 	ans->script = script;
   4134 	ans->next = link;
   4135 	return ans;
   4136 }
   4137 
   4138 static void
   4139 freescriptevents(SEvent* ehead)
   4140 {
   4141 	SEvent* e;
   4142 	SEvent* nexte;
   4143 
   4144 	e = ehead;
   4145 	while(e != nil) {
   4146 		nexte = e->next;
   4147 		free(e->script);
   4148 		free(e);
   4149 		e = nexte;
   4150 	}
   4151 }
   4152 
   4153 static Dimen
   4154 makedimen(int kind, int spec)
   4155 {
   4156 	Dimen d;
   4157 
   4158 	if(spec&Dkindmask) {
   4159 		if(warn)
   4160 			fprint(2, "warning: dimension spec too big: %d\n", spec);
   4161 		spec = 0;
   4162 	}
   4163 	d.kindspec = kind|spec;
   4164 	return d;
   4165 }
   4166 
   4167 int
   4168 dimenkind(Dimen d)
   4169 {
   4170 	return (d.kindspec&Dkindmask);
   4171 }
   4172 
   4173 int
   4174 dimenspec(Dimen d)
   4175 {
   4176 	return (d.kindspec&Dspecmask);
   4177 }
   4178 
   4179 static Kidinfo*
   4180 newkidinfo(int isframeset, Kidinfo* link)
   4181 {
   4182 	Kidinfo*	ki;
   4183 
   4184 	ki = (Kidinfo*)emalloc(sizeof(Kidinfo));
   4185 	ki->isframeset = isframeset;
   4186 	if(!isframeset) {
   4187 		ki->flags = FRhscrollauto|FRvscrollauto;
   4188 		ki->marginw = FRKIDMARGIN;
   4189 		ki->marginh = FRKIDMARGIN;
   4190 		ki->framebd = 1;
   4191 	}
   4192 	ki->next = link;
   4193 	return ki;
   4194 }
   4195 
   4196 static Docinfo*
   4197 newdocinfo(void)
   4198 {
   4199 	Docinfo*	d;
   4200 
   4201 	d = (Docinfo*)emalloc(sizeof(Docinfo));
   4202 	resetdocinfo(d);
   4203 	return d;
   4204 }
   4205 
   4206 static void
   4207 resetdocinfo(Docinfo* d)
   4208 {
   4209 	memset(d, 0, sizeof(Docinfo));
   4210 	d->background = makebackground(nil, White);
   4211 	d->text = Black;
   4212 	d->link = Blue;
   4213 	d->vlink = Blue;
   4214 	d->alink = Blue;
   4215 	d->target = FTself;
   4216 	d->chset = ISO_8859_1;
   4217 	d->scripttype = TextJavascript;
   4218 	d->frameid = -1;
   4219 }
   4220 
   4221 /* Use targetmap array to keep track of name <-> targetid mapping. */
   4222 /* Use real malloc(), and never free */
   4223 static void
   4224 targetmapinit(void)
   4225 {
   4226 	targetmapsize = 10;
   4227 	targetmap = (StringInt*)emalloc(targetmapsize*sizeof(StringInt));
   4228 	memset(targetmap, 0, targetmapsize*sizeof(StringInt));
   4229 	targetmap[0].key = _Strdup(L(L_top));
   4230 	targetmap[0].val = FTtop;
   4231 	targetmap[1].key = _Strdup(L(L_self));
   4232 	targetmap[1].val = FTself;
   4233 	targetmap[2].key = _Strdup(L(L_parent));
   4234 	targetmap[2].val = FTparent;
   4235 	targetmap[3].key = _Strdup(L(L_blank));
   4236 	targetmap[3].val = FTblank;
   4237 	ntargets = 4;
   4238 }
   4239 
   4240 int
   4241 targetid(Rune* s)
   4242 {
   4243 	int i;
   4244 	int n;
   4245 
   4246 	n = _Strlen(s);
   4247 	if(n == 0)
   4248 		return FTself;
   4249 	for(i = 0; i < ntargets; i++)
   4250 		if(_Strcmp(s, targetmap[i].key) == 0)
   4251 			return targetmap[i].val;
   4252 	if(i >= targetmapsize) {
   4253 		targetmapsize += 10;
   4254 		targetmap = (StringInt*)erealloc(targetmap, targetmapsize*sizeof(StringInt));
   4255 	}
   4256 	targetmap[i].key = (Rune*)emalloc((n+1)*sizeof(Rune));
   4257 	memmove(targetmap[i].key, s, (n+1)*sizeof(Rune));
   4258 	targetmap[i].val = i;
   4259 	ntargets++;
   4260 	return i;
   4261 }
   4262 
   4263 Rune*
   4264 targetname(int targid)
   4265 {
   4266 	int i;
   4267 
   4268 	for(i = 0; i < ntargets; i++)
   4269 		if(targetmap[i].val == targid)
   4270 			return targetmap[i].key;
   4271 	return L(Lquestion);
   4272 }
   4273 
   4274 /* Convert HTML color spec to RGB value, returning dflt if can't. */
   4275 /* Argument is supposed to be a valid HTML color, or "". */
   4276 /* Return the RGB value of the color, using dflt if s */
   4277 /* is nil or an invalid color. */
   4278 static int
   4279 color(Rune* s, int dflt)
   4280 {
   4281 	int v;
   4282 	Rune* rest;
   4283 
   4284 	if(s == nil)
   4285 		return dflt;
   4286 	if(_lookup(color_tab, NCOLORS, s, _Strlen(s), &v))
   4287 		return v;
   4288 	if(s[0] == '#')
   4289 		s++;
   4290 	v = _Strtol(s, &rest, 16);
   4291 	if(*rest == 0)
   4292 		return v;
   4293 	return dflt;
   4294 }
   4295 
   4296 /* Debugging */
   4297 
   4298 #define HUGEPIX 10000
   4299 
   4300 /* A "shallow" validitem, that doesn't follow next links */
   4301 /* or descend into tables. */
   4302 static int
   4303 validitem(Item* i)
   4304 {
   4305 	int ok;
   4306 	Itext* ti;
   4307 	Irule* ri;
   4308 	Iimage* ii;
   4309 	Ifloat* fi;
   4310 	int a;
   4311 
   4312 	ok = (i->tag >= Itexttag && i->tag <= Ispacertag) &&
   4313 		(i->next == nil || validptr(i->next)) &&
   4314 		(i->width >= 0 && i->width < HUGEPIX) &&
   4315 		(i->height >= 0 && i->height < HUGEPIX) &&
   4316 		(i->ascent > -HUGEPIX && i->ascent < HUGEPIX) &&
   4317 		(i->anchorid >= 0) &&
   4318 		(i->genattr == nil || validptr(i->genattr));
   4319 	/* also, could check state for ridiculous combinations */
   4320 	/* also, could check anchorid for within-doc-range */
   4321 	if(ok)
   4322 		switch(i->tag) {
   4323 		case Itexttag:
   4324 			ti = (Itext*)i;
   4325 			ok = validStr(ti->s) &&
   4326 				(ti->fnt >= 0 && ti->fnt < NumStyle*NumSize) &&
   4327 				(ti->ul == ULnone || ti->ul == ULunder || ti->ul == ULmid);
   4328 			break;
   4329 		case Iruletag:
   4330 			ri = (Irule*)i;
   4331 			ok = (validvalign(ri->align) || validhalign(ri->align)) &&
   4332 				(ri->size >=0 && ri->size < HUGEPIX);
   4333 			break;
   4334 		case Iimagetag:
   4335 			ii = (Iimage*)i;
   4336 			ok = (ii->imsrc == nil || validptr(ii->imsrc)) &&
   4337 				(ii->item.width >= 0 && ii->item.width < HUGEPIX) &&
   4338 				(ii->item.height >= 0 && ii->item.height < HUGEPIX) &&
   4339 				(ii->imwidth >= 0 && ii->imwidth < HUGEPIX) &&
   4340 				(ii->imheight >= 0 && ii->imheight < HUGEPIX) &&
   4341 				(ii->altrep == nil || validStr(ii->altrep)) &&
   4342 				(ii->map == nil || validptr(ii->map)) &&
   4343 				(validvalign(ii->align) || validhalign(ii->align)) &&
   4344 				(ii->nextimage == nil || validptr(ii->nextimage));
   4345 			break;
   4346 		case Iformfieldtag:
   4347 			ok = validformfield(((Iformfield*)i)->formfield);
   4348 			break;
   4349 		case Itabletag:
   4350 			ok = validptr((Itable*)i);
   4351 			break;
   4352 		case Ifloattag:
   4353 			fi = (Ifloat*)i;
   4354 			ok = (fi->side == ALleft || fi->side == ALright) &&
   4355 				validitem(fi->item) &&
   4356 				(fi->item->tag == Iimagetag || fi->item->tag == Itabletag);
   4357 			break;
   4358 		case Ispacertag:
   4359 			a = ((Ispacer*)i)->spkind;
   4360 			ok = a==ISPnull || a==ISPvline || a==ISPhspace || a==ISPgeneral;
   4361 			break;
   4362 		default:
   4363 			ok = 0;
   4364 		}
   4365 	return ok;
   4366 }
   4367 
   4368 /* "deep" validation, that checks whole list of items, */
   4369 /* and descends into tables and floated tables. */
   4370 /* nil is ok for argument. */
   4371 int
   4372 validitems(Item* i)
   4373 {
   4374 	int ok;
   4375 	Item* ii;
   4376 
   4377 	ok = 1;
   4378 	while(i != nil && ok) {
   4379 		ok = validitem(i);
   4380 		if(ok) {
   4381 			if(i->tag == Itabletag) {
   4382 				ok = validtable(((Itable*)i)->table);
   4383 			}
   4384 			else if(i->tag == Ifloattag) {
   4385 				ii = ((Ifloat*)i)->item;
   4386 				if(ii->tag == Itabletag)
   4387 					ok = validtable(((Itable*)ii)->table);
   4388 			}
   4389 		}
   4390 		if(!ok) {
   4391 			fprint(2, "invalid item: %I\n", i);
   4392 		}
   4393 		i = i->next;
   4394 	}
   4395 	return ok;
   4396 }
   4397 
   4398 static int
   4399 validformfield(Formfield* f)
   4400 {
   4401 	int ok;
   4402 
   4403 	ok = (f->next == nil || validptr(f->next)) &&
   4404 		(f->ftype >= 0 && f->ftype <= Ftextarea) &&
   4405 		f->fieldid >= 0 &&
   4406 		(f->form == nil || validptr(f->form)) &&
   4407 		(f->name == nil || validStr(f->name)) &&
   4408 		(f->value == nil || validStr(f->value)) &&
   4409 		(f->options == nil || validptr(f->options)) &&
   4410 		(f->image == nil || validitem(f->image)) &&
   4411 		(f->events == nil || validptr(f->events));
   4412 	/* when all built, should have f->fieldid < f->form->nfields, */
   4413 	/* but this may be called during build... */
   4414 	return ok;
   4415 }
   4416 
   4417 /* "deep" validation -- checks cell contents too */
   4418 static int
   4419 validtable(Table* t)
   4420 {
   4421 	int ok;
   4422 	int i, j;
   4423 	Tablecell* c;
   4424 
   4425 	ok = (t->next == nil || validptr(t->next)) &&
   4426 		t->nrow >= 0 &&
   4427 		t->ncol >= 0 &&
   4428 		t->ncell >= 0 &&
   4429 		validalign(t->align) &&
   4430 		validdimen(t->width) &&
   4431 		(t->border >= 0 && t->border < HUGEPIX) &&
   4432 		(t->cellspacing >= 0 && t->cellspacing < HUGEPIX) &&
   4433 		(t->cellpadding >= 0 && t->cellpadding < HUGEPIX) &&
   4434 		validitems(t->caption) &&
   4435 		(t->caption_place == ALtop || t->caption_place == ALbottom) &&
   4436 		(t->totw >= 0 && t->totw < HUGEPIX) &&
   4437 		(t->toth >= 0 && t->toth < HUGEPIX) &&
   4438 		(t->tabletok == nil || validptr(t->tabletok));
   4439 	/* during parsing, t->rows has list; */
   4440 	/* only when parsing is done is t->nrow set > 0 */
   4441 	if(ok && t->nrow > 0 && t->ncol > 0) {
   4442 		/* table is "finished" */
   4443 		for(i = 0; i < t->nrow && ok; i++)
   4444 			ok = validtablerow(t->rows+i);
   4445 		for(j = 0; j < t->ncol && ok; j++)
   4446 			ok = validtablecol(t->cols+j);
   4447 		for(c = t->cells; c != nil && ok; c = c->next)
   4448 			ok = validtablecell(c);
   4449 		for(i = 0; i < t->nrow && ok; i++)
   4450 			for(j = 0; j < t->ncol && ok; j++)
   4451 				ok = validptr(t->grid[i][j]);
   4452 	}
   4453 	return ok;
   4454 }
   4455 
   4456 static int
   4457 validvalign(int a)
   4458 {
   4459 	return a == ALnone || a == ALmiddle || a == ALbottom || a == ALtop || a == ALbaseline;
   4460 }
   4461 
   4462 static int
   4463 validhalign(int a)
   4464 {
   4465 	return a == ALnone || a == ALleft || a == ALcenter || a == ALright ||
   4466 			a == ALjustify || a == ALchar;
   4467 }
   4468 
   4469 static int
   4470 validalign(Align a)
   4471 {
   4472 	return validhalign(a.halign) && validvalign(a.valign);
   4473 }
   4474 
   4475 static int
   4476 validdimen(Dimen d)
   4477 {
   4478 	int ok;
   4479 	int s;
   4480 
   4481 	ok = 0;
   4482 	s = d.kindspec&Dspecmask;
   4483 	switch(d.kindspec&Dkindmask) {
   4484 	case Dnone:
   4485 		ok = s==0;
   4486 		break;
   4487 	case Dpixels:
   4488 		ok = s < HUGEPIX;
   4489 		break;
   4490 	case Dpercent:
   4491 	case Drelative:
   4492 		ok = 1;
   4493 		break;
   4494 	}
   4495 	return ok;
   4496 }
   4497 
   4498 static int
   4499 validtablerow(Tablerow* r)
   4500 {
   4501 	return (r->cells == nil || validptr(r->cells)) &&
   4502 		(r->height >= 0 && r->height < HUGEPIX) &&
   4503 		(r->ascent > -HUGEPIX && r->ascent < HUGEPIX) &&
   4504 		validalign(r->align);
   4505 }
   4506 
   4507 static int
   4508 validtablecol(Tablecol* c)
   4509 {
   4510 	return c->width >= 0 && c->width < HUGEPIX
   4511 		&& validalign(c->align);
   4512 }
   4513 
   4514 static int
   4515 validtablecell(Tablecell* c)
   4516 {
   4517 	int ok;
   4518 
   4519 	ok = (c->next == nil || validptr(c->next)) &&
   4520 		(c->nextinrow == nil || validptr(c->nextinrow)) &&
   4521 		(c->content == nil || validptr(c->content)) &&
   4522 		(c->lay == nil || validptr(c->lay)) &&
   4523 		c->rowspan >= 0 &&
   4524 		c->colspan >= 0 &&
   4525 		validalign(c->align) &&
   4526 		validdimen(c->wspec) &&
   4527 		c->row >= 0 &&
   4528 		c->col >= 0;
   4529 	if(ok) {
   4530 		if(c->content != nil)
   4531 			ok = validitems(c->content);
   4532 	}
   4533 	return ok;
   4534 }
   4535 
   4536 static int
   4537 validptr(void* p)
   4538 {
   4539 	/* TODO: a better job of this. */
   4540 	/* For now, just dereference, which cause a bomb */
   4541 	/* if not valid */
   4542 	static char c;
   4543 
   4544 	c = *((char*)p);
   4545 	USED(c);
   4546 	return 1;
   4547 }
   4548 
   4549 static int
   4550 validStr(Rune* s)
   4551 {
   4552 	return s != nil && validptr(s);
   4553 }