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