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