Blob


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