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