Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/planix-v0/sys/src/libhtml/build.c – Rev 2

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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