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);
879 s = nil;
882 else
883 switch(tag) {
884 /* Some abbrevs used in following DTD comments */
885 /* %text = #PCDATA */
886 /* | TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP */
887 /* | EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE */
888 /* | A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP */
889 /* | INPUT | SELECT | TEXTAREA */
890 /* %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER */
891 /* | BLOCKQUOTE | FORM | ISINDEX | HR | TABLE */
892 /* %flow = (%text | %block)* */
893 /* %body.content = (%heading | %text | %block | ADDRESS)* */
895 /* <!ELEMENT A - - (%text) -(A)> */
896 /* Anchors are not supposed to be nested, but you sometimes see */
897 /* href anchors inside destination anchors. */
898 case Ta:
899 if(ps->curanchor != 0) {
900 if(warn)
901 fprint(2, "warning: nested <A> or missing </A>\n");
902 ps->curanchor = 0;
904 name = aval(tok, Aname);
905 href = aurlval(tok, Ahref, nil, di->base);
906 /* ignore rel, rev, and title attrs */
907 if(href != nil) {
908 target = atargval(tok, di->target);
909 di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors);
910 if(name != nil)
911 name = _Strdup(name); /* for DestAnchor construction, below */
912 ps->curanchor = is->nanchors;
913 ps->curfg = push(&ps->fgstk, di->link);
914 ps->curul = push(&ps->ulstk, ULunder);
916 if(name != nil) {
917 /* add a null item to be destination */
918 additem(ps, newispacer(ISPnull), tok);
919 di->dests = newdestanchor(++is->nanchors, name, ps->lastit, di->dests);
921 break;
923 case Ta+RBRA :
924 if(ps->curanchor != 0) {
925 ps->curfg = popretnewtop(&ps->fgstk, di->text);
926 ps->curul = popretnewtop(&ps->ulstk, ULnone);
927 ps->curanchor = 0;
929 break;
931 /* <!ELEMENT APPLET - - (PARAM | %text)* > */
932 /* We can't do applets, so ignore PARAMS, and let */
933 /* the %text contents appear for the alternative rep */
934 case Tapplet:
935 case Tapplet+RBRA:
936 if(warn && tag == Tapplet)
937 fprint(2, "warning: <APPLET> ignored\n");
938 break;
940 /* <!ELEMENT AREA - O EMPTY> */
941 case Tarea:
942 map = di->maps;
943 if(map == nil) {
944 if(warn)
945 fprint(2, "warning: <AREA> not inside <MAP>\n");
946 continue;
948 map->areas = newarea(atabval(tok, Ashape, shape_tab, NSHAPETAB, SHrect),
949 aurlval(tok, Ahref, nil, di->base),
950 atargval(tok, di->target),
951 map->areas);
952 setdimarray(tok, Acoords, &map->areas->coords, &map->areas->ncoords);
953 break;
955 /* <!ELEMENT (B|STRONG) - - (%text)*> */
956 case Tb:
957 case Tstrong:
958 pushfontstyle(ps, FntB);
959 break;
961 case Tb+RBRA:
962 case Tcite+RBRA:
963 case Tcode+RBRA:
964 case Tdfn+RBRA:
965 case Tem+RBRA:
966 case Tkbd+RBRA:
967 case Ti+RBRA:
968 case Tsamp+RBRA:
969 case Tstrong+RBRA:
970 case Ttt+RBRA:
971 case Tvar+RBRA :
972 case Taddress+RBRA:
973 popfontstyle(ps);
974 break;
976 /* <!ELEMENT BASE - O EMPTY> */
977 case Tbase:
978 t = di->base;
979 di->base = aurlval(tok, Ahref, di->base, di->base);
980 if(t != nil)
981 free(t);
982 di->target = atargval(tok, di->target);
983 break;
985 /* <!ELEMENT BASEFONT - O EMPTY> */
986 case Tbasefont:
987 ps->adjsize = aintval(tok, Asize, 3) - 3;
988 break;
990 /* <!ELEMENT (BIG|SMALL) - - (%text)*> */
991 case Tbig:
992 case Tsmall:
993 sz = ps->adjsize;
994 if(tag == Tbig)
995 sz += Large;
996 else
997 sz += Small;
998 pushfontsize(ps, sz);
999 break;
1001 case Tbig+RBRA:
1002 case Tsmall+RBRA:
1003 popfontsize(ps);
1004 break;
1006 /* <!ELEMENT BLOCKQUOTE - - %body.content> */
1007 case Tblockquote:
1008 changeindent(ps, BQTAB);
1009 break;
1011 case Tblockquote+RBRA:
1012 changeindent(ps, -BQTAB);
1013 break;
1015 /* <!ELEMENT BODY O O %body.content> */
1016 case Tbody:
1017 ps->skipping = 0;
1018 bg = makebackground(nil, acolorval(tok, Abgcolor, di->background.color));
1019 bgurl = aurlval(tok, Abackground, nil, di->base);
1020 if(bgurl != nil) {
1021 if(di->backgrounditem != nil)
1022 freeitem((Item*)di->backgrounditem);
1023 /* really should remove old item from di->images list, */
1024 /* but there should only be one BODY element ... */
1025 di->backgrounditem = (Iimage*)newiimage(bgurl, nil, ALnone, 0, 0, 0, 0, 0, 0, nil);
1026 di->backgrounditem->nextimage = di->images;
1027 di->images = di->backgrounditem;
1029 ps->curbg = bg;
1030 di->background = bg;
1031 di->text = acolorval(tok, Atext, di->text);
1032 di->link = acolorval(tok, Alink, di->link);
1033 di->vlink = acolorval(tok, Avlink, di->vlink);
1034 di->alink = acolorval(tok, Aalink, di->alink);
1035 if(di->text != ps->curfg) {
1036 ps->curfg = di->text;
1037 ps->fgstk.n = 0;
1039 break;
1041 case Tbody+RBRA:
1042 /* HTML spec says ignore things after </body>, */
1043 /* but IE and Netscape don't */
1044 /* ps.skipping = 1; */
1045 break;
1047 /* <!ELEMENT BR - O EMPTY> */
1048 case Tbr:
1049 addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
1050 break;
1052 /* <!ELEMENT CAPTION - - (%text;)*> */
1053 case Tcaption:
1054 if(curtab == nil) {
1055 if(warn)
1056 fprint(2, "warning: <CAPTION> outside <TABLE>\n");
1057 continue;
1059 if(curtab->caption != nil) {
1060 if(warn)
1061 fprint(2, "warning: more than one <CAPTION> in <TABLE>\n");
1062 continue;
1064 ps = newpstate(ps);
1065 curtab->caption_place = atabval(tok, Aalign, align_tab, NALIGNTAB, ALtop);
1066 break;
1068 case Tcaption+RBRA:
1069 nextps = ps->next;
1070 if(curtab == nil || nextps == nil) {
1071 if(warn)
1072 fprint(2, "warning: unexpected </CAPTION>\n");
1073 continue;
1075 curtab->caption = ps->items->next;
1076 free(ps);
1077 ps = nextps;
1078 break;
1080 case Tcenter:
1081 case Tdiv:
1082 if(tag == Tcenter)
1083 al = ALcenter;
1084 else
1085 al = atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust);
1086 pushjust(ps, al);
1087 break;
1089 case Tcenter+RBRA:
1090 case Tdiv+RBRA:
1091 popjust(ps);
1092 break;
1094 /* <!ELEMENT DD - O %flow > */
1095 case Tdd:
1096 if(ps->hangstk.n == 0) {
1097 if(warn)
1098 fprint(2, "warning: <DD> not inside <DL\n");
1099 continue;
1101 h = top(&ps->hangstk, 0);
1102 if(h != 0)
1103 changehang(ps, -10*LISTTAB);
1104 else
1105 addbrk(ps, 0, 0);
1106 push(&ps->hangstk, 0);
1107 break;
1109 /*<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) > */
1110 /*<!ELEMENT (OL|UL) - - (LI)+> */
1111 case Tdir:
1112 case Tmenu:
1113 case Tol:
1114 case Tul:
1115 changeindent(ps, LISTTAB);
1116 push(&ps->listtypestk, listtyval(tok, (tag==Tol)? LT1 : LTdisc));
1117 push(&ps->listcntstk, aintval(tok, Astart, 1));
1118 break;
1120 case Tdir+RBRA:
1121 case Tmenu+RBRA:
1122 case Tol+RBRA:
1123 case Tul+RBRA:
1124 if(ps->listtypestk.n == 0) {
1125 if(warn)
1126 fprint(2, "warning: %T ended no list\n", tok);
1127 continue;
1129 addbrk(ps, 0, 0);
1130 pop(&ps->listtypestk);
1131 pop(&ps->listcntstk);
1132 changeindent(ps, -LISTTAB);
1133 break;
1135 /* <!ELEMENT DL - - (DT|DD)+ > */
1136 case Tdl:
1137 changeindent(ps, LISTTAB);
1138 push(&ps->hangstk, 0);
1139 break;
1141 case Tdl+RBRA:
1142 if(ps->hangstk.n == 0) {
1143 if(warn)
1144 fprint(2, "warning: unexpected </DL>\n");
1145 continue;
1147 changeindent(ps, -LISTTAB);
1148 if(top(&ps->hangstk, 0) != 0)
1149 changehang(ps, -10*LISTTAB);
1150 pop(&ps->hangstk);
1151 break;
1153 /* <!ELEMENT DT - O (%text)* > */
1154 case Tdt:
1155 if(ps->hangstk.n == 0) {
1156 if(warn)
1157 fprint(2, "warning: <DT> not inside <DL>\n");
1158 continue;
1160 h = top(&ps->hangstk, 0);
1161 pop(&ps->hangstk);
1162 if(h != 0)
1163 changehang(ps, -10*LISTTAB);
1164 changehang(ps, 10*LISTTAB);
1165 push(&ps->hangstk, 1);
1166 break;
1168 /* <!ELEMENT FONT - - (%text)*> */
1169 case Tfont:
1170 sz = top(&ps->fntsizestk, Normal);
1171 if(_tokaval(tok, Asize, &nsz, 0)) {
1172 if(_prefix(L(Lplus), nsz))
1173 sz = Normal + _Strtol(nsz+1, nil, 10) + ps->adjsize;
1174 else if(_prefix(L(Lminus), nsz))
1175 sz = Normal - _Strtol(nsz+1, nil, 10) + ps->adjsize;
1176 else if(nsz != nil)
1177 sz = Normal + (_Strtol(nsz, nil, 10) - 3);
1179 ps->curfg = push(&ps->fgstk, acolorval(tok, Acolor, ps->curfg));
1180 pushfontsize(ps, sz);
1181 break;
1183 case Tfont+RBRA:
1184 if(ps->fgstk.n == 0) {
1185 if(warn)
1186 fprint(2, "warning: unexpected </FONT>\n");
1187 continue;
1189 ps->curfg = popretnewtop(&ps->fgstk, di->text);
1190 popfontsize(ps);
1191 break;
1193 /* <!ELEMENT FORM - - %body.content -(FORM) > */
1194 case Tform:
1195 if(is->curform != nil) {
1196 if(warn)
1197 fprint(2, "warning: <FORM> nested inside another\n");
1198 continue;
1200 action = aurlval(tok, Aaction, di->base, di->base);
1201 s = aval(tok, Aid);
1202 name = astrval(tok, Aname, s);
1203 if(s)
1204 free(s);
1205 target = atargval(tok, di->target);
1206 method = atabval(tok, Amethod, method_tab, NMETHODTAB, HGet);
1207 if(warn && _tokaval(tok, Aenctype, &enctype, 0) &&
1208 _Strcmp(enctype, L(Lappl_form)))
1209 fprint(2, "form enctype %S not handled\n", enctype);
1210 frm = newform(++is->nforms, name, action, target, method, di->forms);
1211 di->forms = frm;
1212 is->curform = frm;
1213 break;
1215 case Tform+RBRA:
1216 if(is->curform == nil) {
1217 if(warn)
1218 fprint(2, "warning: unexpected </FORM>\n");
1219 continue;
1221 /* put fields back in input order */
1222 is->curform->fields = (Formfield*)_revlist((List*)is->curform->fields);
1223 is->curform = nil;
1224 break;
1226 /* <!ELEMENT FRAME - O EMPTY> */
1227 case Tframe:
1228 ks = is->kidstk;
1229 if(ks == nil) {
1230 if(warn)
1231 fprint(2, "warning: <FRAME> not in <FRAMESET>\n");
1232 continue;
1234 ks->kidinfos = kd = newkidinfo(0, ks->kidinfos);
1235 kd->src = aurlval(tok, Asrc, nil, di->base);
1236 kd->name = aval(tok, Aname);
1237 if(kd->name == nil) {
1238 s = _ltoStr(++is->nframes);
1239 kd->name = _Strdup2(L(Lfr), s);
1240 free(s);
1242 kd->marginw = auintval(tok, Amarginwidth, 0);
1243 kd->marginh = auintval(tok, Amarginheight, 0);
1244 kd->framebd = auintval(tok, Aframeborder, 1);
1245 kd->flags = atabval(tok, Ascrolling, fscroll_tab, NFSCROLLTAB, kd->flags);
1246 norsz = aflagval(tok, Anoresize);
1247 if(norsz)
1248 kd->flags |= FRnoresize;
1249 break;
1251 /* <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+> */
1252 case Tframeset:
1253 ks = newkidinfo(1, nil);
1254 pks = is->kidstk;
1255 if(pks == nil)
1256 di->kidinfo = ks;
1257 else {
1258 ks->next = pks->kidinfos;
1259 pks->kidinfos = ks;
1261 ks->nextframeset = pks;
1262 is->kidstk = ks;
1263 setdimarray(tok, Arows, &ks->rows, &ks->nrows);
1264 if(ks->nrows == 0) {
1265 ks->rows = (Dimen*)emalloc(sizeof(Dimen));
1266 ks->nrows = 1;
1267 ks->rows[0] = makedimen(Dpercent, 100);
1269 setdimarray(tok, Acols, &ks->cols, &ks->ncols);
1270 if(ks->ncols == 0) {
1271 ks->cols = (Dimen*)emalloc(sizeof(Dimen));
1272 ks->ncols = 1;
1273 ks->cols[0] = makedimen(Dpercent, 100);
1275 break;
1277 case Tframeset+RBRA:
1278 if(is->kidstk == nil) {
1279 if(warn)
1280 fprint(2, "warning: unexpected </FRAMESET>\n");
1281 continue;
1283 ks = is->kidstk;
1284 /* put kids back in original order */
1285 /* and add blank frames to fill out cells */
1286 n = ks->nrows*ks->ncols;
1287 nblank = n - _listlen((List*)ks->kidinfos);
1288 while(nblank-- > 0)
1289 ks->kidinfos = newkidinfo(0, ks->kidinfos);
1290 ks->kidinfos = (Kidinfo*)_revlist((List*)ks->kidinfos);
1291 is->kidstk = is->kidstk->nextframeset;
1292 if(is->kidstk == nil) {
1293 /* end input */
1294 ans = nil;
1295 goto return_ans;
1297 break;
1299 /* <!ELEMENT H1 - - (%text;)*>, etc. */
1300 case Th1:
1301 case Th2:
1302 case Th3:
1303 case Th4:
1304 case Th5:
1305 case Th6:
1306 bramt = 1;
1307 if(ps->items == ps->lastit)
1308 bramt = 0;
1309 addbrk(ps, bramt, IFcleft|IFcright);
1310 sz = Verylarge - (tag - Th1);
1311 if(sz < Tiny)
1312 sz = Tiny;
1313 pushfontsize(ps, sz);
1314 sty = top(&ps->fntstylestk, FntR);
1315 if(tag == Th1)
1316 sty = FntB;
1317 pushfontstyle(ps, sty);
1318 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
1319 ps->skipwhite = 1;
1320 break;
1322 case Th1+RBRA:
1323 case Th2+RBRA:
1324 case Th3+RBRA:
1325 case Th4+RBRA:
1326 case Th5+RBRA:
1327 case Th6+RBRA:
1328 addbrk(ps, 1, IFcleft|IFcright);
1329 popfontsize(ps);
1330 popfontstyle(ps);
1331 popjust(ps);
1332 break;
1334 case Thead:
1335 /* HTML spec says ignore regular markup in head, */
1336 /* but Netscape and IE don't */
1337 /* ps.skipping = 1; */
1338 break;
1340 case Thead+RBRA:
1341 ps->skipping = 0;
1342 break;
1344 /* <!ELEMENT HR - O EMPTY> */
1345 case Thr:
1346 al = atabval(tok, Aalign, align_tab, NALIGNTAB, ALcenter);
1347 sz = auintval(tok, Asize, HRSZ);
1348 wd = adimen(tok, Awidth);
1349 if(dimenkind(wd) == Dnone)
1350 wd = makedimen(Dpercent, 100);
1351 nosh = aflagval(tok, Anoshade);
1352 additem(ps, newirule(al, sz, nosh, wd), tok);
1353 addbrk(ps, 0, 0);
1354 break;
1356 case Ti:
1357 case Tcite:
1358 case Tdfn:
1359 case Tem:
1360 case Tvar:
1361 case Taddress:
1362 pushfontstyle(ps, FntI);
1363 break;
1365 /* <!ELEMENT IMG - O EMPTY> */
1366 case Timg:
1367 map = nil;
1368 oldcuranchor = ps->curanchor;
1369 if(_tokaval(tok, Ausemap, &usemap, 0)) {
1370 if(!_prefix(L(Lhash), usemap)) {
1371 if(warn)
1372 fprint(2, "warning: can't handle non-local map %S\n", usemap);
1374 else {
1375 map = getmap(di, usemap+1);
1376 if(ps->curanchor == 0) {
1377 di->anchors = newanchor(++is->nanchors, nil, nil, di->target, di->anchors);
1378 ps->curanchor = is->nanchors;
1382 align = atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom);
1383 dfltbd = 0;
1384 if(ps->curanchor != 0)
1385 dfltbd = 2;
1386 src = aurlval(tok, Asrc, nil, di->base);
1387 if(src == nil) {
1388 if(warn)
1389 fprint(2, "warning: <img> has no src attribute\n");
1390 ps->curanchor = oldcuranchor;
1391 continue;
1393 img = newiimage(src,
1394 aval(tok, Aalt),
1395 align,
1396 auintval(tok, Awidth, 0),
1397 auintval(tok, Aheight, 0),
1398 auintval(tok, Ahspace, IMGHSPACE),
1399 auintval(tok, Avspace, IMGVSPACE),
1400 auintval(tok, Aborder, dfltbd),
1401 aflagval(tok, Aismap),
1402 map);
1403 if(align == ALleft || align == ALright) {
1404 additem(ps, newifloat(img, align), tok);
1405 /* if no hspace specified, use FLTIMGHSPACE */
1406 if(!_tokaval(tok, Ahspace, &val, 0))
1407 ((Iimage*)img)->hspace = FLTIMGHSPACE;
1409 else {
1410 ps->skipwhite = 0;
1411 additem(ps, img, tok);
1413 if(!ps->skipping) {
1414 ((Iimage*)img)->nextimage = di->images;
1415 di->images = (Iimage*)img;
1417 ps->curanchor = oldcuranchor;
1418 break;
1420 /* <!ELEMENT INPUT - O EMPTY> */
1421 case Tinput:
1422 ps->skipwhite = 0;
1423 if(is->curform == nil) {
1424 if(warn)
1425 fprint(2, "<INPUT> not inside <FORM>\n");
1426 continue;
1428 is->curform->fields = field = newformfield(
1429 atabval(tok, Atype, input_tab, NINPUTTAB, Ftext),
1430 ++is->curform->nfields,
1431 is->curform,
1432 aval(tok, Aname),
1433 aval(tok, Avalue),
1434 auintval(tok, Asize, 0),
1435 auintval(tok, Amaxlength, 1000),
1436 is->curform->fields);
1437 if(aflagval(tok, Achecked))
1438 field->flags = FFchecked;
1440 switch(field->ftype) {
1441 case Ftext:
1442 case Fpassword:
1443 case Ffile:
1444 if(field->size == 0)
1445 field->size = 20;
1446 break;
1448 case Fcheckbox:
1449 if(field->name == nil) {
1450 if(warn)
1451 fprint(2, "warning: checkbox form field missing name\n");
1452 continue;
1454 if(field->value == nil)
1455 field->value = _Strdup(L(Lone));
1456 break;
1458 case Fradio:
1459 if(field->name == nil || field->value == nil) {
1460 if(warn)
1461 fprint(2, "warning: radio form field missing name or value\n");
1462 continue;
1464 break;
1466 case Fsubmit:
1467 if(field->value == nil)
1468 field->value = _Strdup(L(Lsubmit));
1469 if(field->name == nil)
1470 field->name = _Strdup(L(Lnoname));
1471 break;
1473 case Fimage:
1474 src = aurlval(tok, Asrc, nil, di->base);
1475 if(src == nil) {
1476 if(warn)
1477 fprint(2, "warning: image form field missing src\n");
1478 continue;
1480 /* width and height attrs aren't specified in HTML 3.2, */
1481 /* but some people provide them and they help avoid */
1482 /* a relayout */
1483 field->image = newiimage(src,
1484 astrval(tok, Aalt, L(Lsubmit)),
1485 atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom),
1486 auintval(tok, Awidth, 0), auintval(tok, Aheight, 0),
1487 0, 0, 0, 0, nil);
1488 ii = (Iimage*)field->image;
1489 ii->nextimage = di->images;
1490 di->images = ii;
1491 break;
1493 case Freset:
1494 if(field->value == nil)
1495 field->value = _Strdup(L(Lreset));
1496 break;
1498 case Fbutton:
1499 if(field->value == nil)
1500 field->value = _Strdup(L(Lspace));
1501 break;
1503 ffit = newiformfield(field);
1504 additem(ps, ffit, tok);
1505 if(ffit->genattr != nil)
1506 field->events = ffit->genattr->events;
1507 break;
1509 /* <!ENTITY ISINDEX - O EMPTY> */
1510 case Tisindex:
1511 ps->skipwhite = 0;
1512 prompt = astrval(tok, Aprompt, L(Lindex));
1513 target = atargval(tok, di->target);
1514 additem(ps, textit(ps, prompt), tok);
1515 frm = newform(++is->nforms,
1516 nil,
1517 di->base,
1518 target,
1519 HGet,
1520 di->forms);
1521 di->forms = frm;
1522 ff = newformfield(Ftext,
1524 frm,
1525 _Strdup(L(Lisindex)),
1526 nil,
1527 50,
1528 1000,
1529 nil);
1530 frm->fields = ff;
1531 frm->nfields = 1;
1532 additem(ps, newiformfield(ff), tok);
1533 addbrk(ps, 1, 0);
1534 break;
1536 /* <!ELEMENT LI - O %flow> */
1537 case Tli:
1538 if(ps->listtypestk.n == 0) {
1539 if(warn)
1540 fprint(2, "<LI> not in list\n");
1541 continue;
1543 ty = top(&ps->listtypestk, 0);
1544 ty2 = listtyval(tok, ty);
1545 if(ty != ty2) {
1546 ty = ty2;
1547 push(&ps->listtypestk, ty2);
1549 v = aintval(tok, Avalue, top(&ps->listcntstk, 1));
1550 if(ty == LTdisc || ty == LTsquare || ty == LTcircle)
1551 hang = 10*LISTTAB - 3;
1552 else
1553 hang = 10*LISTTAB - 1;
1554 changehang(ps, hang);
1555 addtext(ps, listmark(ty, v));
1556 push(&ps->listcntstk, v + 1);
1557 changehang(ps, -hang);
1558 ps->skipwhite = 1;
1559 break;
1561 /* <!ELEMENT MAP - - (AREA)+> */
1562 case Tmap:
1563 if(_tokaval(tok, Aname, &name, 0))
1564 is->curmap = getmap(di, name);
1565 break;
1567 case Tmap+RBRA:
1568 map = is->curmap;
1569 if(map == nil) {
1570 if(warn)
1571 fprint(2, "warning: unexpected </MAP>\n");
1572 continue;
1574 map->areas = (Area*)_revlist((List*)map->areas);
1575 break;
1577 case Tmeta:
1578 if(ps->skipping)
1579 continue;
1580 if(_tokaval(tok, Ahttp_equiv, &equiv, 0)) {
1581 val = aval(tok, Acontent);
1582 n = _Strlen(equiv);
1583 if(!_Strncmpci(equiv, n, L(Lrefresh)))
1584 di->refresh = val;
1585 else if(!_Strncmpci(equiv, n, L(Lcontent))) {
1586 n = _Strlen(val);
1587 if(!_Strncmpci(val, n, L(Ljavascript))
1588 || !_Strncmpci(val, n, L(Ljscript1))
1589 || !_Strncmpci(val, n, L(Ljscript)))
1590 di->scripttype = TextJavascript;
1591 else {
1592 if(warn)
1593 fprint(2, "unimplemented script type %S\n", val);
1594 di->scripttype = UnknownType;
1598 break;
1600 /* Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web */
1601 case Tnobr:
1602 ps->skipwhite = 0;
1603 ps->curstate &= ~IFwrap;
1604 break;
1606 case Tnobr+RBRA:
1607 ps->curstate |= IFwrap;
1608 break;
1610 /* We do frames, so skip stuff in noframes */
1611 case Tnoframes:
1612 ps->skipping = 1;
1613 break;
1615 case Tnoframes+RBRA:
1616 ps->skipping = 0;
1617 break;
1619 /* We do scripts (if enabled), so skip stuff in noscripts */
1620 case Tnoscript:
1621 if(doscripts)
1622 ps->skipping = 1;
1623 break;
1625 case Tnoscript+RBRA:
1626 if(doscripts)
1627 ps->skipping = 0;
1628 break;
1630 /* <!ELEMENT OPTION - O ( //PCDATA)> */
1631 case Toption:
1632 if(is->curform == nil || is->curform->fields == nil) {
1633 if(warn)
1634 fprint(2, "warning: <OPTION> not in <SELECT>\n");
1635 continue;
1637 field = is->curform->fields;
1638 if(field->ftype != Fselect) {
1639 if(warn)
1640 fprint(2, "warning: <OPTION> not in <SELECT>\n");
1641 continue;
1643 val = aval(tok, Avalue);
1644 option = newoption(aflagval(tok, Aselected), val, nil, field->options);
1645 field->options = option;
1646 option->display = getpcdata(toks, tokslen, &toki);
1647 if(val == nil)
1648 option->value = _Strdup(option->display);
1649 break;
1651 /* <!ELEMENT P - O (%text)* > */
1652 case Tp:
1653 pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
1654 ps->inpar = 1;
1655 ps->skipwhite = 1;
1656 break;
1658 case Tp+RBRA:
1659 break;
1661 /* <!ELEMENT PARAM - O EMPTY> */
1662 /* Do something when we do applets... */
1663 case Tparam:
1664 break;
1666 /* <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) > */
1667 case Tpre:
1668 ps->curstate &= ~IFwrap;
1669 ps->literal = 1;
1670 ps->skipwhite = 0;
1671 pushfontstyle(ps, FntT);
1672 break;
1674 case Tpre+RBRA:
1675 ps->curstate |= IFwrap;
1676 if(ps->literal) {
1677 popfontstyle(ps);
1678 ps->literal = 0;
1680 break;
1682 /* <!ELEMENT SCRIPT - - CDATA> */
1683 case Tscript:
1684 if(doscripts) {
1685 if(!di->hasscripts) {
1686 if(di->scripttype == TextJavascript) {
1687 /* TODO: initialize script if nec. */
1688 /* initjscript(di); */
1689 di->hasscripts = 1;
1693 if(!di->hasscripts) {
1694 if(warn)
1695 fprint(2, "warning: <SCRIPT> ignored\n");
1696 ps->skipping = 1;
1698 else {
1699 scriptsrc = aurlval(tok, Asrc, nil, di->base);
1700 script = nil;
1701 if(scriptsrc != nil) {
1702 if(warn)
1703 fprint(2, "warning: non-local <SCRIPT> ignored\n");
1704 free(scriptsrc);
1706 else {
1707 script = getpcdata(toks, tokslen, &toki);
1709 if(script != nil) {
1710 if(warn)
1711 fprint(2, "script ignored\n");
1712 free(script);
1715 break;
1717 case Tscript+RBRA:
1718 ps->skipping = 0;
1719 break;
1721 /* <!ELEMENT SELECT - - (OPTION+)> */
1722 case Tselect:
1723 if(is->curform == nil) {
1724 if(warn)
1725 fprint(2, "<SELECT> not inside <FORM>\n");
1726 continue;
1728 field = newformfield(Fselect,
1729 ++is->curform->nfields,
1730 is->curform,
1731 aval(tok, Aname),
1732 nil,
1733 auintval(tok, Asize, 0),
1735 is->curform->fields);
1736 is->curform->fields = field;
1737 if(aflagval(tok, Amultiple))
1738 field->flags = FFmultiple;
1739 ffit = newiformfield(field);
1740 additem(ps, ffit, tok);
1741 if(ffit->genattr != nil)
1742 field->events = ffit->genattr->events;
1743 /* throw away stuff until next tag (should be <OPTION>) */
1744 s = getpcdata(toks, tokslen, &toki);
1745 if(s != nil)
1746 free(s);
1747 break;
1749 case Tselect+RBRA:
1750 if(is->curform == nil || is->curform->fields == nil) {
1751 if(warn)
1752 fprint(2, "warning: unexpected </SELECT>\n");
1753 continue;
1755 field = is->curform->fields;
1756 if(field->ftype != Fselect)
1757 continue;
1758 /* put options back in input order */
1759 field->options = (Option*)_revlist((List*)field->options);
1760 break;
1762 /* <!ELEMENT (STRIKE|U) - - (%text)*> */
1763 case Tstrike:
1764 case Tu:
1765 ps->curul = push(&ps->ulstk, (tag==Tstrike)? ULmid : ULunder);
1766 break;
1768 case Tstrike+RBRA:
1769 case Tu+RBRA:
1770 if(ps->ulstk.n == 0) {
1771 if(warn)
1772 fprint(2, "warning: unexpected %T\n", tok);
1773 continue;
1775 ps->curul = popretnewtop(&ps->ulstk, ULnone);
1776 break;
1778 /* <!ELEMENT STYLE - - CDATA> */
1779 case Tstyle:
1780 if(warn)
1781 fprint(2, "warning: unimplemented <STYLE>\n");
1782 ps->skipping = 1;
1783 break;
1785 case Tstyle+RBRA:
1786 ps->skipping = 0;
1787 break;
1789 /* <!ELEMENT (SUB|SUP) - - (%text)*> */
1790 case Tsub:
1791 case Tsup:
1792 if(tag == Tsub)
1793 ps->curvoff += SUBOFF;
1794 else
1795 ps->curvoff -= SUPOFF;
1796 push(&ps->voffstk, ps->curvoff);
1797 sz = top(&ps->fntsizestk, Normal);
1798 pushfontsize(ps, sz - 1);
1799 break;
1801 case Tsub+RBRA:
1802 case Tsup+RBRA:
1803 if(ps->voffstk.n == 0) {
1804 if(warn)
1805 fprint(2, "warning: unexpected %T\n", tok);
1806 continue;
1808 ps->curvoff = popretnewtop(&ps->voffstk, 0);
1809 popfontsize(ps);
1810 break;
1812 /* <!ELEMENT TABLE - - (CAPTION?, TR+)> */
1813 case Ttable:
1814 ps->skipwhite = 0;
1815 tab = newtable(++is->ntables,
1816 aalign(tok),
1817 adimen(tok, Awidth),
1818 aflagval(tok, Aborder),
1819 auintval(tok, Acellspacing, TABSP),
1820 auintval(tok, Acellpadding, TABPAD),
1821 makebackground(nil, acolorval(tok, Abgcolor, ps->curbg.color)),
1822 tok,
1823 is->tabstk);
1824 is->tabstk = tab;
1825 curtab = tab;
1826 break;
1828 case Ttable+RBRA:
1829 if(curtab == nil) {
1830 if(warn)
1831 fprint(2, "warning: unexpected </TABLE>\n");
1832 continue;
1834 isempty = (curtab->cells == nil);
1835 if(isempty) {
1836 if(warn)
1837 fprint(2, "warning: <TABLE> has no cells\n");
1839 else {
1840 ps = finishcell(curtab, ps);
1841 if(curtab->rows != nil)
1842 curtab->rows->flags = 0;
1843 finish_table(curtab);
1845 ps->skipping = 0;
1846 if(!isempty) {
1847 tabitem = newitable(curtab);
1848 al = curtab->align.halign;
1849 switch(al) {
1850 case ALleft:
1851 case ALright:
1852 additem(ps, newifloat(tabitem, al), tok);
1853 break;
1854 default:
1855 if(al == ALcenter)
1856 pushjust(ps, ALcenter);
1857 addbrk(ps, 0, 0);
1858 if(ps->inpar) {
1859 popjust(ps);
1860 ps->inpar = 0;
1862 additem(ps, tabitem, curtab->tabletok);
1863 if(al == ALcenter)
1864 popjust(ps);
1865 break;
1868 if(is->tabstk == nil) {
1869 if(warn)
1870 fprint(2, "warning: table stack is wrong\n");
1872 else
1873 is->tabstk = is->tabstk->next;
1874 curtab->next = di->tables;
1875 di->tables = curtab;
1876 curtab = is->tabstk;
1877 if(!isempty)
1878 addbrk(ps, 0, 0);
1879 break;
1881 /* <!ELEMENT (TH|TD) - O %body.content> */
1882 /* Cells for a row are accumulated in reverse order. */
1883 /* We push ps on a stack, and use a new one to accumulate */
1884 /* the contents of the cell. */
1885 case Ttd:
1886 case Tth:
1887 if(curtab == nil) {
1888 if(warn)
1889 fprint(2, "%T outside <TABLE>\n", tok);
1890 continue;
1892 if(ps->inpar) {
1893 popjust(ps);
1894 ps->inpar = 0;
1896 ps = finishcell(curtab, ps);
1897 tr = nil;
1898 if(curtab->rows != nil)
1899 tr = curtab->rows;
1900 if(tr == nil || !tr->flags) {
1901 if(warn)
1902 fprint(2, "%T outside row\n", tok);
1903 tr = newtablerow(makealign(ALnone, ALnone),
1904 makebackground(nil, curtab->background.color),
1905 TFparsing,
1906 curtab->rows);
1907 curtab->rows = tr;
1909 ps = cell_pstate(ps, tag == Tth);
1910 flags = TFparsing;
1911 if(aflagval(tok, Anowrap)) {
1912 flags |= TFnowrap;
1913 ps->curstate &= ~IFwrap;
1915 if(tag == Tth)
1916 flags |= TFisth;
1917 c = newtablecell(curtab->cells==nil? 1 : curtab->cells->cellid+1,
1918 auintval(tok, Arowspan, 1),
1919 auintval(tok, Acolspan, 1),
1920 aalign(tok),
1921 adimen(tok, Awidth),
1922 auintval(tok, Aheight, 0),
1923 makebackground(nil, acolorval(tok, Abgcolor, tr->background.color)),
1924 flags,
1925 curtab->cells);
1926 curtab->cells = c;
1927 ps->curbg = c->background;
1928 if(c->align.halign == ALnone) {
1929 if(tr->align.halign != ALnone)
1930 c->align.halign = tr->align.halign;
1931 else if(tag == Tth)
1932 c->align.halign = ALcenter;
1933 else
1934 c->align.halign = ALleft;
1936 if(c->align.valign == ALnone) {
1937 if(tr->align.valign != ALnone)
1938 c->align.valign = tr->align.valign;
1939 else
1940 c->align.valign = ALmiddle;
1942 c->nextinrow = tr->cells;
1943 tr->cells = c;
1944 break;
1946 case Ttd+RBRA:
1947 case Tth+RBRA:
1948 if(curtab == nil || curtab->cells == nil) {
1949 if(warn)
1950 fprint(2, "unexpected %T\n", tok);
1951 continue;
1953 ps = finishcell(curtab, ps);
1954 break;
1956 /* <!ELEMENT TEXTAREA - - ( //PCDATA)> */
1957 case Ttextarea:
1958 if(is->curform == nil) {
1959 if(warn)
1960 fprint(2, "<TEXTAREA> not inside <FORM>\n");
1961 continue;
1963 field = newformfield(Ftextarea,
1964 ++is->curform->nfields,
1965 is->curform,
1966 aval(tok, Aname),
1967 nil,
1970 is->curform->fields);
1971 is->curform->fields = field;
1972 field->rows = auintval(tok, Arows, 3);
1973 field->cols = auintval(tok, Acols, 50);
1974 field->value = getpcdata(toks, tokslen, &toki);
1975 if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttextarea + RBRA)
1976 fprint(2, "warning: <TEXTAREA> data ended by %T\n", &toks[toki + 1]);
1977 ffit = newiformfield(field);
1978 additem(ps, ffit, tok);
1979 if(ffit->genattr != nil)
1980 field->events = ffit->genattr->events;
1981 break;
1983 /* <!ELEMENT TITLE - - ( //PCDATA)* -(%head.misc)> */
1984 case Ttitle:
1985 di->doctitle = getpcdata(toks, tokslen, &toki);
1986 if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttitle + RBRA)
1987 fprint(2, "warning: <TITLE> data ended by %T\n", &toks[toki + 1]);
1988 break;
1990 /* <!ELEMENT TR - O (TH|TD)+> */
1991 /* rows are accumulated in reverse order in curtab->rows */
1992 case Ttr:
1993 if(curtab == nil) {
1994 if(warn)
1995 fprint(2, "warning: <TR> outside <TABLE>\n");
1996 continue;
1998 if(ps->inpar) {
1999 popjust(ps);
2000 ps->inpar = 0;
2002 ps = finishcell(curtab, ps);
2003 if(curtab->rows != nil)
2004 curtab->rows->flags = 0;
2005 curtab->rows = newtablerow(aalign(tok),
2006 makebackground(nil, acolorval(tok, Abgcolor, curtab->background.color)),
2007 TFparsing,
2008 curtab->rows);
2009 break;
2011 case Ttr+RBRA:
2012 if(curtab == nil || curtab->rows == nil) {
2013 if(warn)
2014 fprint(2, "warning: unexpected </TR>\n");
2015 continue;
2017 ps = finishcell(curtab, ps);
2018 tr = curtab->rows;
2019 if(tr->cells == nil) {
2020 if(warn)
2021 fprint(2, "warning: empty row\n");
2022 curtab->rows = tr->next;
2023 tr->next = nil;
2025 else
2026 tr->flags = 0;
2027 break;
2029 /* <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*> */
2030 case Ttt:
2031 case Tcode:
2032 case Tkbd:
2033 case Tsamp:
2034 pushfontstyle(ps, FntT);
2035 break;
2037 /* Tags that have empty action */
2038 case Tabbr:
2039 case Tabbr+RBRA:
2040 case Tacronym:
2041 case Tacronym+RBRA:
2042 case Tarea+RBRA:
2043 case Tbase+RBRA:
2044 case Tbasefont+RBRA:
2045 case Tbr+RBRA:
2046 case Tdd+RBRA:
2047 case Tdt+RBRA:
2048 case Tframe+RBRA:
2049 case Thr+RBRA:
2050 case Thtml:
2051 case Thtml+RBRA:
2052 case Timg+RBRA:
2053 case Tinput+RBRA:
2054 case Tisindex+RBRA:
2055 case Tli+RBRA:
2056 case Tlink:
2057 case Tlink+RBRA:
2058 case Tmeta+RBRA:
2059 case Toption+RBRA:
2060 case Tparam+RBRA:
2061 case Ttextarea+RBRA:
2062 case Ttitle+RBRA:
2063 break;
2066 /* Tags not implemented */
2067 case Tbdo:
2068 case Tbdo+RBRA:
2069 case Tbutton:
2070 case Tbutton+RBRA:
2071 case Tdel:
2072 case Tdel+RBRA:
2073 case Tfieldset:
2074 case Tfieldset+RBRA:
2075 case Tiframe:
2076 case Tiframe+RBRA:
2077 case Tins:
2078 case Tins+RBRA:
2079 case Tlabel:
2080 case Tlabel+RBRA:
2081 case Tlegend:
2082 case Tlegend+RBRA:
2083 case Tobject:
2084 case Tobject+RBRA:
2085 case Toptgroup:
2086 case Toptgroup+RBRA:
2087 case Tspan:
2088 case Tspan+RBRA:
2089 if(warn) {
2090 if(tag > RBRA)
2091 tag -= RBRA;
2092 fprint(2, "warning: unimplemented HTML tag: %S\n", tagnames[tag]);
2094 break;
2096 default:
2097 if(warn)
2098 fprint(2, "warning: unknown HTML tag: %S\n", tok->text);
2099 break;
2102 /* some pages omit trailing </table> */
2103 while(curtab != nil) {
2104 if(warn)
2105 fprint(2, "warning: <TABLE> not closed\n");
2106 if(curtab->cells != nil) {
2107 ps = finishcell(curtab, ps);
2108 if(curtab->cells == nil) {
2109 if(warn)
2110 fprint(2, "warning: empty table\n");
2112 else {
2113 if(curtab->rows != nil)
2114 curtab->rows->flags = 0;
2115 finish_table(curtab);
2116 ps->skipping = 0;
2117 additem(ps, newitable(curtab), curtab->tabletok);
2118 addbrk(ps, 0, 0);
2121 if(is->tabstk != nil)
2122 is->tabstk = is->tabstk->next;
2123 curtab->next = di->tables;
2124 di->tables = curtab;
2125 curtab = is->tabstk;
2127 outerps = lastps(ps);
2128 ans = outerps->items->next;
2129 /* note: ans may be nil and di->kids not nil, if there's a frameset! */
2130 freeitem(outerps->items);
2131 outerps->items = newispacer(ISPnull);
2132 outerps->lastit = outerps->items;
2133 is->psstk = ps;
2134 if(ans != nil && di->hasscripts) {
2135 /* TODO evalscript(nil); */
2138 freeitems(outerps->items);
2140 return_ans:
2141 if(dbgbuild) {
2142 assert(validitems(ans));
2143 if(ans == nil)
2144 fprint(2, "getitems returning nil\n");
2145 else
2146 printitems(ans, "getitems returning:");
2148 _freetokens(toks, tokslen);
2149 return ans;
2152 /* Concatenate together maximal set of Data tokens, starting at toks[toki+1]. */
2153 /* Lexer has ensured that there will either be a following non-data token or */
2154 /* we will be at eof. */
2155 /* Return emallocd trimmed concatenation, and update *ptoki to last used toki */
2156 static Rune*
2157 getpcdata(Token* toks, int tokslen, int* ptoki)
2159 Rune* ans;
2160 Rune* p;
2161 Rune* trimans;
2162 int anslen;
2163 int trimanslen;
2164 int toki;
2165 Token* tok;
2167 ans = nil;
2168 anslen = 0;
2169 /* first find length of answer */
2170 toki = (*ptoki) + 1;
2171 while(toki < tokslen) {
2172 tok = &toks[toki];
2173 if(tok->tag == Data) {
2174 toki++;
2175 anslen += _Strlen(tok->text);
2177 else
2178 break;
2180 /* now make up the initial answer */
2181 if(anslen > 0) {
2182 ans = _newstr(anslen);
2183 p = ans;
2184 toki = (*ptoki) + 1;
2185 while(toki < tokslen) {
2186 tok = &toks[toki];
2187 if(tok->tag == Data) {
2188 toki++;
2189 p = _Stradd(p, tok->text, _Strlen(tok->text));
2191 else
2192 break;
2194 *p = 0;
2195 _trimwhite(ans, anslen, &trimans, &trimanslen);
2196 if(trimanslen != anslen) {
2197 p = ans;
2198 ans = _Strndup(trimans, trimanslen);
2199 free(p);
2202 *ptoki = toki-1;
2203 return ans;
2206 /* If still parsing head of curtab->cells list, finish it off */
2207 /* by transferring the items on the head of psstk to the cell. */
2208 /* Then pop the psstk and return the new psstk. */
2209 static Pstate*
2210 finishcell(Table* curtab, Pstate* psstk)
2212 Tablecell* c;
2213 Pstate* psstknext;
2215 c = curtab->cells;
2216 if(c != nil) {
2217 if((c->flags&TFparsing)) {
2218 psstknext = psstk->next;
2219 if(psstknext == nil) {
2220 if(warn)
2221 fprint(2, "warning: parse state stack is wrong\n");
2223 else {
2224 c->content = psstk->items->next;
2225 c->flags &= ~TFparsing;
2226 freepstate(psstk);
2227 psstk = psstknext;
2231 return psstk;
2234 /* Make a new Pstate for a cell, based on the old pstate, oldps. */
2235 /* Also, put the new ps on the head of the oldps stack. */
2236 static Pstate*
2237 cell_pstate(Pstate* oldps, int ishead)
2239 Pstate* ps;
2240 int sty;
2242 ps = newpstate(oldps);
2243 ps->skipwhite = 1;
2244 ps->curanchor = oldps->curanchor;
2245 copystack(&ps->fntstylestk, &oldps->fntstylestk);
2246 copystack(&ps->fntsizestk, &oldps->fntsizestk);
2247 ps->curfont = oldps->curfont;
2248 ps->curfg = oldps->curfg;
2249 ps->curbg = oldps->curbg;
2250 copystack(&ps->fgstk, &oldps->fgstk);
2251 ps->adjsize = oldps->adjsize;
2252 if(ishead) {
2253 sty = ps->curfont%NumSize;
2254 ps->curfont = FntB*NumSize + sty;
2256 return ps;
2259 /* Return a new Pstate with default starting state. */
2260 /* Use link to add it to head of a list, if any. */
2261 static Pstate*
2262 newpstate(Pstate* link)
2264 Pstate* ps;
2266 ps = (Pstate*)emalloc(sizeof(Pstate));
2267 ps->curfont = DefFnt;
2268 ps->curfg = Black;
2269 ps->curbg.image = nil;
2270 ps->curbg.color = White;
2271 ps->curul = ULnone;
2272 ps->curjust = ALleft;
2273 ps->curstate = IFwrap;
2274 ps->items = newispacer(ISPnull);
2275 ps->lastit = ps->items;
2276 ps->prelastit = nil;
2277 ps->next = link;
2278 return ps;
2281 /* Return last Pstate on psl list */
2282 static Pstate*
2283 lastps(Pstate* psl)
2285 assert(psl != nil);
2286 while(psl->next != nil)
2287 psl = psl->next;
2288 return psl;
2291 /* Add it to end of ps item chain, adding in current state from ps. */
2292 /* Also, if tok is not nil, scan it for generic attributes and assign */
2293 /* the genattr field of the item accordingly. */
2294 static void
2295 additem(Pstate* ps, Item* it, Token* tok)
2297 int aid;
2298 int any;
2299 Rune* i;
2300 Rune* c;
2301 Rune* s;
2302 Rune* t;
2303 Attr* a;
2304 SEvent* e;
2306 if(ps->skipping) {
2307 if(warn)
2308 fprint(2, "warning: skipping item: %I\n", it);
2309 return;
2311 it->anchorid = ps->curanchor;
2312 it->state |= ps->curstate;
2313 if(tok != nil) {
2314 any = 0;
2315 i = nil;
2316 c = nil;
2317 s = nil;
2318 t = nil;
2319 e = nil;
2320 for(a = tok->attr; a != nil; a = a->next) {
2321 aid = a->attid;
2322 if(!attrinfo[aid])
2323 continue;
2324 switch(aid) {
2325 case Aid:
2326 i = a->value;
2327 break;
2329 case Aclass:
2330 c = a->value;
2331 break;
2333 case Astyle:
2334 s = a->value;
2335 break;
2337 case Atitle:
2338 t = a->value;
2339 break;
2341 default:
2342 assert(aid >= Aonblur && aid <= Aonunload);
2343 e = newscriptevent(scriptev[a->attid], a->value, e);
2344 break;
2346 a->value = nil;
2347 any = 1;
2349 if(any)
2350 it->genattr = newgenattr(i, c, s, t, e);
2352 ps->curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
2353 ps->prelastit = ps->lastit;
2354 ps->lastit->next = it;
2355 ps->lastit = it;
2358 /* Make a text item out of s, */
2359 /* using current font, foreground, vertical offset and underline state. */
2360 static Item*
2361 textit(Pstate* ps, Rune* s)
2363 assert(s != nil);
2364 return newitext(s, ps->curfont, ps->curfg, ps->curvoff + Voffbias, ps->curul);
2367 /* Add text item or items for s, paying attention to */
2368 /* current font, foreground, baseline offset, underline state, */
2369 /* and literal mode. Unless we're in literal mode, compress */
2370 /* whitespace to single blank, and, if curstate has a break, */
2371 /* trim any leading whitespace. Whether in literal mode or not, */
2372 /* turn nonbreaking spaces into spacer items with IFnobrk set. */
2373 /* */
2374 /* In literal mode, break up s at newlines and add breaks instead. */
2375 /* Also replace tabs appropriate number of spaces. */
2376 /* In nonliteral mode, break up the items every 100 or so characters */
2377 /* just to make the layout algorithm not go quadratic. */
2378 /* */
2379 /* addtext assumes ownership of s. */
2380 static void
2381 addtext(Pstate* ps, Rune* s)
2383 int n;
2384 int i;
2385 int j;
2386 int k;
2387 int col;
2388 int c;
2389 int nsp;
2390 Item* it;
2391 Rune* ss;
2392 Rune* p;
2393 Rune buf[SMALLBUFSIZE];
2395 assert(s != nil);
2396 n = runestrlen(s);
2397 i = 0;
2398 j = 0;
2399 if(ps->literal) {
2400 col = 0;
2401 while(i < n) {
2402 if(s[i] == '\n') {
2403 if(i > j) {
2404 /* trim trailing blanks from line */
2405 for(k = i; k > j; k--)
2406 if(s[k - 1] != ' ')
2407 break;
2408 if(k > j)
2409 additem(ps, textit(ps, _Strndup(s+j, k-j)), nil);
2411 addlinebrk(ps, 0);
2412 j = i + 1;
2413 col = 0;
2415 else {
2416 if(s[i] == '\t') {
2417 col += i - j;
2418 nsp = 8 - (col%8);
2419 /* make ss = s[j:i] + nsp spaces */
2420 ss = _newstr(i-j+nsp);
2421 p = _Stradd(ss, s+j, i-j);
2422 p = _Stradd(p, L(Ltab2space), nsp);
2423 *p = 0;
2424 additem(ps, textit(ps, ss), nil);
2425 col += nsp;
2426 j = i + 1;
2428 else if(s[i] == NBSP) {
2429 if(i > j)
2430 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
2431 addnbsp(ps);
2432 col += (i - j) + 1;
2433 j = i + 1;
2436 i++;
2438 if(i > j) {
2439 if(j == 0 && i == n) {
2440 /* just transfer s over */
2441 additem(ps, textit(ps, s), nil);
2443 else {
2444 additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
2445 free(s);
2448 else {
2449 free(s);
2452 else { /* not literal mode */
2453 if((ps->curstate&IFbrk) || ps->lastit == ps->items)
2454 while(i < n) {
2455 c = s[i];
2456 if(c >= 256 || !isspace(c))
2457 break;
2458 i++;
2460 p = buf;
2461 for(j = i; i < n; i++) {
2462 assert(p+i-j < buf+SMALLBUFSIZE-1);
2463 c = s[i];
2464 if(c == NBSP) {
2465 if(i > j)
2466 p = _Stradd(p, s+j, i-j);
2467 if(p > buf)
2468 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2469 p = buf;
2470 addnbsp(ps);
2471 j = i + 1;
2472 continue;
2474 if(c < 256 && isspace(c)) {
2475 if(i > j)
2476 p = _Stradd(p, s+j, i-j);
2477 *p++ = ' ';
2478 while(i < n - 1) {
2479 c = s[i + 1];
2480 if(c >= 256 || !isspace(c))
2481 break;
2482 i++;
2484 j = i + 1;
2486 if(i - j >= 100) {
2487 p = _Stradd(p, s+j, i+1-j);
2488 j = i + 1;
2490 if(p-buf >= 100) {
2491 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2492 p = buf;
2495 if(i > j && j < n) {
2496 assert(p+i-j < buf+SMALLBUFSIZE-1);
2497 p = _Stradd(p, s+j, i-j);
2499 /* don't add a space if previous item ended in a space */
2500 if(p-buf == 1 && buf[0] == ' ' && ps->lastit != nil) {
2501 it = ps->lastit;
2502 if(it->tag == Itexttag) {
2503 ss = ((Itext*)it)->s;
2504 k = _Strlen(ss);
2505 if(k > 0 && ss[k] == ' ')
2506 p = buf;
2509 if(p > buf)
2510 additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
2511 free(s);
2515 /* Add a break to ps->curstate, with extra space if sp is true. */
2516 /* If there was a previous break, combine this one's parameters */
2517 /* with that to make the amt be the max of the two and the clr */
2518 /* be the most general. (amt will be 0 or 1) */
2519 /* Also, if the immediately preceding item was a text item, */
2520 /* trim any whitespace from the end of it, if not in literal mode. */
2521 /* Finally, if this is at the very beginning of the item list */
2522 /* (the only thing there is a null spacer), then don't add the space. */
2523 static void
2524 addbrk(Pstate* ps, int sp, int clr)
2526 int state;
2527 Rune* l;
2528 int nl;
2529 Rune* r;
2530 int nr;
2531 Itext* t;
2532 Rune* s;
2534 state = ps->curstate;
2535 clr = clr|(state&(IFcleft|IFcright));
2536 if(sp && !(ps->lastit == ps->items))
2537 sp = IFbrksp;
2538 else
2539 sp = 0;
2540 ps->curstate = IFbrk|sp|(state&~(IFcleft|IFcright))|clr;
2541 if(ps->lastit != ps->items) {
2542 if(!ps->literal && ps->lastit->tag == Itexttag) {
2543 t = (Itext*)ps->lastit;
2544 _splitr(t->s, _Strlen(t->s), notwhitespace, &l, &nl, &r, &nr);
2545 /* try to avoid making empty items */
2546 /* but not crucial f the occasional one gets through */
2547 if(nl == 0 && ps->prelastit != nil) {
2548 ps->lastit = ps->prelastit;
2549 ps->lastit->next = nil;
2550 ps->prelastit = nil;
2552 else {
2553 s = t->s;
2554 if(nl == 0) {
2555 /* need a non-nil pointer to empty string */
2556 /* (_Strdup(L(Lempty)) returns nil) */
2557 t->s = emalloc(sizeof(Rune));
2558 t->s[0] = 0;
2560 else
2561 t->s = _Strndup(l, nl);
2562 if(s)
2563 free(s);
2569 /* Add break due to a <br> or a newline within a preformatted section. */
2570 /* We add a null item first, with current font's height and ascent, to make */
2571 /* sure that the current line takes up at least that amount of vertical space. */
2572 /* This ensures that <br>s on empty lines cause blank lines, and that */
2573 /* multiple <br>s in a row give multiple blank lines. */
2574 /* However don't add the spacer if the previous item was something that */
2575 /* takes up space itself. */
2576 static void
2577 addlinebrk(Pstate* ps, int clr)
2579 int obrkstate;
2580 int b;
2581 int addit;
2583 /* don't want break before our null item unless the previous item */
2584 /* was also a null item for the purposes of line breaking */
2585 obrkstate = ps->curstate&(IFbrk|IFbrksp);
2586 b = IFnobrk;
2587 addit = 0;
2588 if(ps->lastit != nil) {
2589 if(ps->lastit->tag == Ispacertag) {
2590 if(((Ispacer*)ps->lastit)->spkind == ISPvline)
2591 b = IFbrk;
2592 addit = 1;
2594 else if(ps->lastit->tag == Ifloattag)
2595 addit = 1;
2597 if(addit) {
2598 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|b;
2599 additem(ps, newispacer(ISPvline), nil);
2600 ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|obrkstate;
2602 addbrk(ps, 0, clr);
2605 /* Add a nonbreakable space */
2606 static void
2607 addnbsp(Pstate* ps)
2609 /* if nbsp comes right where a break was specified, */
2610 /* do the break anyway (nbsp is being used to generate undiscardable */
2611 /* space rather than to prevent a break) */
2612 if((ps->curstate&IFbrk) == 0)
2613 ps->curstate |= IFnobrk;
2614 additem(ps, newispacer(ISPhspace), nil);
2615 /* but definitely no break on next item */
2616 ps->curstate |= IFnobrk;
2619 /* Change hang in ps.curstate by delta. */
2620 /* The amount is in 1/10ths of tabs, and is the amount that */
2621 /* the current contiguous set of items with a hang value set */
2622 /* is to be shifted left from its normal (indented) place. */
2623 static void
2624 changehang(Pstate* ps, int delta)
2626 int amt;
2628 amt = (ps->curstate&IFhangmask) + delta;
2629 if(amt < 0) {
2630 if(warn)
2631 fprint(2, "warning: hang went negative\n");
2632 amt = 0;
2634 ps->curstate = (ps->curstate&~IFhangmask)|amt;
2637 /* Change indent in ps.curstate by delta. */
2638 static void
2639 changeindent(Pstate* ps, int delta)
2641 int amt;
2643 amt = ((ps->curstate&IFindentmask) >> IFindentshift) + delta;
2644 if(amt < 0) {
2645 if(warn)
2646 fprint(2, "warning: indent went negative\n");
2647 amt = 0;
2649 ps->curstate = (ps->curstate&~IFindentmask)|(amt << IFindentshift);
2652 /* Push val on top of stack, and also return value pushed */
2653 static int
2654 push(Stack* stk, int val)
2656 if(stk->n == Nestmax) {
2657 if(warn)
2658 fprint(2, "warning: build stack overflow\n");
2660 else
2661 stk->slots[stk->n++] = val;
2662 return val;
2665 /* Pop top of stack */
2666 static void
2667 pop(Stack* stk)
2669 if(stk->n > 0)
2670 --stk->n;
2673 /*Return top of stack, using dflt if stack is empty */
2674 static int
2675 top(Stack* stk, int dflt)
2677 if(stk->n == 0)
2678 return dflt;
2679 return stk->slots[stk->n-1];
2682 /* pop, then return new top, with dflt if empty */
2683 static int
2684 popretnewtop(Stack* stk, int dflt)
2686 if(stk->n == 0)
2687 return dflt;
2688 stk->n--;
2689 if(stk->n == 0)
2690 return dflt;
2691 return stk->slots[stk->n-1];
2694 /* Copy fromstk entries into tostk */
2695 static void
2696 copystack(Stack* tostk, Stack* fromstk)
2698 int n;
2700 n = fromstk->n;
2701 tostk->n = n;
2702 memmove(tostk->slots, fromstk->slots, n*sizeof(int));
2705 static void
2706 popfontstyle(Pstate* ps)
2708 pop(&ps->fntstylestk);
2709 setcurfont(ps);
2712 static void
2713 pushfontstyle(Pstate* ps, int sty)
2715 push(&ps->fntstylestk, sty);
2716 setcurfont(ps);
2719 static void
2720 popfontsize(Pstate* ps)
2722 pop(&ps->fntsizestk);
2723 setcurfont(ps);
2726 static void
2727 pushfontsize(Pstate* ps, int sz)
2729 push(&ps->fntsizestk, sz);
2730 setcurfont(ps);
2733 static void
2734 setcurfont(Pstate* ps)
2736 int sty;
2737 int sz;
2739 sty = top(&ps->fntstylestk, FntR);
2740 sz = top(&ps->fntsizestk, Normal);
2741 if(sz < Tiny)
2742 sz = Tiny;
2743 if(sz > Verylarge)
2744 sz = Verylarge;
2745 ps->curfont = sty*NumSize + sz;
2748 static void
2749 popjust(Pstate* ps)
2751 pop(&ps->juststk);
2752 setcurjust(ps);
2755 static void
2756 pushjust(Pstate* ps, int j)
2758 push(&ps->juststk, j);
2759 setcurjust(ps);
2762 static void
2763 setcurjust(Pstate* ps)
2765 int j;
2766 int state;
2768 j = top(&ps->juststk, ALleft);
2769 if(j != ps->curjust) {
2770 ps->curjust = j;
2771 state = ps->curstate;
2772 state &= ~(IFrjust|IFcjust);
2773 if(j == ALcenter)
2774 state |= IFcjust;
2775 else if(j == ALright)
2776 state |= IFrjust;
2777 ps->curstate = state;
2781 /* Do final rearrangement after table parsing is finished */
2782 /* and assign cells to grid points */
2783 static void
2784 finish_table(Table* t)
2786 int ncol;
2787 int nrow;
2788 int r;
2789 Tablerow* rl;
2790 Tablecell* cl;
2791 int* rowspancnt;
2792 Tablecell** rowspancell;
2793 int ri;
2794 int ci;
2795 Tablecell* c;
2796 Tablecell* cnext;
2797 Tablerow* row;
2798 Tablerow* rownext;
2799 int rcols;
2800 int newncol;
2801 int k;
2802 int j;
2803 int cspan;
2804 int rspan;
2805 int i;
2807 rl = t->rows;
2808 t->nrow = nrow = _listlen((List*)rl);
2809 t->rows = (Tablerow*)emalloc(nrow * sizeof(Tablerow));
2810 ncol = 0;
2811 r = nrow - 1;
2812 for(row = rl; row != nil; row = rownext) {
2813 /* copy the data from the allocated Tablerow into the array slot */
2814 t->rows[r] = *row;
2815 rownext = row->next;
2816 row = &t->rows[r];
2817 r--;
2818 rcols = 0;
2819 c = row->cells;
2821 /* If rowspan is > 1 but this is the last row, */
2822 /* reset the rowspan */
2823 if(c != nil && c->rowspan > 1 && r == nrow-2)
2824 c->rowspan = 1;
2826 /* reverse row->cells list (along nextinrow pointers) */
2827 row->cells = nil;
2828 while(c != nil) {
2829 cnext = c->nextinrow;
2830 c->nextinrow = row->cells;
2831 row->cells = c;
2832 rcols += c->colspan;
2833 c = cnext;
2835 if(rcols > ncol)
2836 ncol = rcols;
2838 t->ncol = ncol;
2839 t->cols = (Tablecol*)emalloc(ncol * sizeof(Tablecol));
2841 /* Reverse cells just so they are drawn in source order. */
2842 /* Also, trim their contents so they don't end in whitespace. */
2843 t->cells = (Tablecell*)_revlist((List*)t->cells);
2844 for(c = t->cells; c != nil; c= c->next)
2845 trim_cell(c);
2846 t->grid = (Tablecell***)emalloc(nrow * sizeof(Tablecell**));
2847 for(i = 0; i < nrow; i++)
2848 t->grid[i] = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
2850 /* The following arrays keep track of cells that are spanning */
2851 /* multiple rows; rowspancnt[i] is the number of rows left */
2852 /* to be spanned in column i. */
2853 /* When done, cell's (row,col) is upper left grid point. */
2854 rowspancnt = (int*)emalloc(ncol * sizeof(int));
2855 rowspancell = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
2856 for(ri = 0; ri < nrow; ri++) {
2857 row = &t->rows[ri];
2858 cl = row->cells;
2859 ci = 0;
2860 while(ci < ncol || cl != nil) {
2861 if(ci < ncol && rowspancnt[ci] > 0) {
2862 t->grid[ri][ci] = rowspancell[ci];
2863 rowspancnt[ci]--;
2864 ci++;
2866 else {
2867 if(cl == nil) {
2868 ci++;
2869 continue;
2871 c = cl;
2872 cl = cl->nextinrow;
2873 cspan = c->colspan;
2874 rspan = c->rowspan;
2875 if(ci + cspan > ncol) {
2876 /* because of row spanning, we calculated */
2877 /* ncol incorrectly; adjust it */
2878 newncol = ci + cspan;
2879 t->cols = (Tablecol*)erealloc(t->cols, newncol * sizeof(Tablecol));
2880 rowspancnt = (int*)erealloc(rowspancnt, newncol * sizeof(int));
2881 rowspancell = (Tablecell**)erealloc(rowspancell, newncol * sizeof(Tablecell*));
2882 k = newncol-ncol;
2883 memset(t->cols+ncol, 0, k*sizeof(Tablecol));
2884 memset(rowspancnt+ncol, 0, k*sizeof(int));
2885 memset(rowspancell+ncol, 0, k*sizeof(Tablecell*));
2886 for(j = 0; j < nrow; j++) {
2887 t->grid[j] = (Tablecell**)erealloc(t->grid[j], newncol * sizeof(Tablecell*));
2888 memset(t->grid[j], 0, k*sizeof(Tablecell*));
2890 t->ncol = ncol = newncol;
2892 c->row = ri;
2893 c->col = ci;
2894 for(i = 0; i < cspan; i++) {
2895 t->grid[ri][ci] = c;
2896 if(rspan > 1) {
2897 rowspancnt[ci] = rspan - 1;
2898 rowspancell[ci] = c;
2900 ci++;
2907 /* Remove tail of cell content until it isn't whitespace. */
2908 static void
2909 trim_cell(Tablecell* c)
2911 int dropping;
2912 Rune* s;
2913 Rune* x;
2914 Rune* y;
2915 int nx;
2916 int ny;
2917 Item* p;
2918 Itext* q;
2919 Item* pprev;
2921 dropping = 1;
2922 while(c->content != nil && dropping) {
2923 p = c->content;
2924 pprev = nil;
2925 while(p->next != nil) {
2926 pprev = p;
2927 p = p->next;
2929 dropping = 0;
2930 if(!(p->state&IFnobrk)) {
2931 if(p->tag == Itexttag) {
2932 q = (Itext*)p;
2933 s = q->s;
2934 _splitr(s, _Strlen(s), notwhitespace, &x, &nx, &y, &ny);
2935 if(nx != 0 && ny != 0) {
2936 q->s = _Strndup(x, nx);
2937 free(s);
2939 break;
2942 if(dropping) {
2943 if(pprev == nil)
2944 c->content = nil;
2945 else
2946 pprev->next = nil;
2947 freeitem(p);
2952 /* Caller must free answer (eventually). */
2953 static Rune*
2954 listmark(uchar ty, int n)
2956 Rune* s;
2957 Rune* t;
2958 int n2;
2959 int i;
2961 s = nil;
2962 switch(ty) {
2963 case LTdisc:
2964 case LTsquare:
2965 case LTcircle:
2966 s = _newstr(1);
2967 s[0] = (ty == LTdisc)? 0x2022 /* bullet */
2968 : ((ty == LTsquare)? 0x220e /* filled square */
2969 : 0x2218); /* degree */
2970 s[1] = 0;
2971 break;
2973 case LT1:
2974 t = _ltoStr(n);
2975 n2 = _Strlen(t);
2976 s = _newstr(n2+1);
2977 t = _Stradd(s, t, n2);
2978 *t++ = '.';
2979 *t = 0;
2980 break;
2982 case LTa:
2983 case LTA:
2984 n--;
2985 i = 0;
2986 if(n < 0)
2987 n = 0;
2988 s = _newstr((n <= 25)? 2 : 3);
2989 if(n > 25) {
2990 n2 = n%26;
2991 n /= 26;
2992 if(n2 > 25)
2993 n2 = 25;
2994 s[i++] = n2 + (ty == LTa)? 'a' : 'A';
2996 s[i++] = n + (ty == LTa)? 'a' : 'A';
2997 s[i++] = '.';
2998 s[i] = 0;
2999 break;
3001 case LTi:
3002 case LTI:
3003 if(n >= NROMAN) {
3004 if(warn)
3005 fprint(2, "warning: unimplemented roman number > %d\n", NROMAN);
3006 n = NROMAN;
3008 t = roman[n - 1];
3009 n2 = _Strlen(t);
3010 s = _newstr(n2+1);
3011 for(i = 0; i < n2; i++)
3012 s[i] = (ty == LTi)? tolower(t[i]) : t[i];
3013 s[i++] = '.';
3014 s[i] = 0;
3015 break;
3017 return s;
3020 /* Find map with given name in di.maps. */
3021 /* If not there, add one, copying name. */
3022 /* Ownership of map remains with di->maps list. */
3023 static Map*
3024 getmap(Docinfo* di, Rune* name)
3026 Map* m;
3028 for(m = di->maps; m != nil; m = m->next) {
3029 if(!_Strcmp(name, m->name))
3030 return m;
3032 m = (Map*)emalloc(sizeof(Map));
3033 m->name = _Strdup(name);
3034 m->areas = nil;
3035 m->next = di->maps;
3036 di->maps = m;
3037 return m;
3040 /* Transfers ownership of href to Area */
3041 static Area*
3042 newarea(int shape, Rune* href, int target, Area* link)
3044 Area* a;
3046 a = (Area*)emalloc(sizeof(Area));
3047 a->shape = shape;
3048 a->href = href;
3049 a->target = target;
3050 a->next = link;
3051 return a;
3054 /* Return string value associated with attid in tok, nil if none. */
3055 /* Caller must free the result (eventually). */
3056 static Rune*
3057 aval(Token* tok, int attid)
3059 Rune* ans;
3061 _tokaval(tok, attid, &ans, 1); /* transfers string ownership from token to ans */
3062 return ans;
3065 /* Like aval, but use dflt if there was no such attribute in tok. */
3066 /* Caller must free the result (eventually). */
3067 static Rune*
3068 astrval(Token* tok, int attid, Rune* dflt)
3070 Rune* ans;
3072 if(_tokaval(tok, attid, &ans, 1))
3073 return ans; /* transfers string ownership from token to ans */
3074 else
3075 return _Strdup(dflt);
3078 /* Here we're supposed to convert to an int, */
3079 /* and have a default when not found */
3080 static int
3081 aintval(Token* tok, int attid, int dflt)
3083 Rune* ans;
3085 if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
3086 return dflt;
3087 else
3088 return toint(ans);
3091 /* Like aintval, but result should be >= 0 */
3092 static int
3093 auintval(Token* tok, int attid, int dflt)
3095 Rune* ans;
3096 int v;
3098 if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
3099 return dflt;
3100 else {
3101 v = toint(ans);
3102 return v >= 0? v : 0;
3106 /* int conversion, but with possible error check (if warning) */
3107 static int
3108 toint(Rune* s)
3110 int ans;
3111 Rune* eptr;
3113 ans = _Strtol(s, &eptr, 10);
3114 if(warn) {
3115 if(*eptr != 0) {
3116 eptr = _Strclass(eptr, notwhitespace);
3117 if(eptr != nil)
3118 fprint(2, "warning: expected integer, got %S\n", s);
3121 return ans;
3124 /* Attribute value when need a table to convert strings to ints */
3125 static int
3126 atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt)
3128 Rune* aval;
3129 int ans;
3131 ans = dflt;
3132 if(_tokaval(tok, attid, &aval, 0)) {
3133 if(!_lookup(tab, ntab, aval, _Strlen(aval), &ans)) {
3134 ans = dflt;
3135 if(warn)
3136 fprint(2, "warning: name not found in table lookup: %S\n", aval);
3139 return ans;
3142 /* Attribute value when supposed to be a color */
3143 static int
3144 acolorval(Token* tok, int attid, int dflt)
3146 Rune* aval;
3147 int ans;
3149 ans = dflt;
3150 if(_tokaval(tok, attid, &aval, 0))
3151 ans = color(aval, dflt);
3152 return ans;
3155 /* Attribute value when supposed to be a target frame name */
3156 static int
3157 atargval(Token* tok, int dflt)
3159 int ans;
3160 Rune* aval;
3162 ans = dflt;
3163 if(_tokaval(tok, Atarget, &aval, 0)){
3164 ans = targetid(aval);
3166 return ans;
3169 /* special for list types, where "i" and "I" are different, */
3170 /* but "square" and "SQUARE" are the same */
3171 static int
3172 listtyval(Token* tok, int dflt)
3174 Rune* aval;
3175 int ans;
3176 int n;
3178 ans = dflt;
3179 if(_tokaval(tok, Atype, &aval, 0)) {
3180 n = _Strlen(aval);
3181 if(n == 1) {
3182 switch(aval[0]) {
3183 case '1':
3184 ans = LT1;
3185 break;
3186 case 'A':
3187 ans = LTA;
3188 break;
3189 case 'I':
3190 ans = LTI;
3191 break;
3192 case 'a':
3193 ans = LTa;
3194 break;
3195 case 'i':
3196 ans = LTi;
3197 default:
3198 if(warn)
3199 fprint(2, "warning: unknown list element type %c\n", aval[0]);
3202 else {
3203 if(!_Strncmpci(aval, n, L(Lcircle)))
3204 ans = LTcircle;
3205 else if(!_Strncmpci(aval, n, L(Ldisc)))
3206 ans = LTdisc;
3207 else if(!_Strncmpci(aval, n, L(Lsquare)))
3208 ans = LTsquare;
3209 else {
3210 if(warn)
3211 fprint(2, "warning: unknown list element type %S\n", aval);
3215 return ans;
3218 /* Attribute value when value is a URL, possibly relative to base. */
3219 /* FOR NOW: leave the url relative. */
3220 /* Caller must free the result (eventually). */
3221 static Rune*
3222 aurlval(Token* tok, int attid, Rune* dflt, Rune* base)
3224 Rune* ans;
3225 Rune* url;
3227 USED(base);
3228 ans = nil;
3229 if(_tokaval(tok, attid, &url, 0) && url != nil)
3230 ans = removeallwhite(url);
3231 if(ans == nil)
3232 ans = _Strdup(dflt);
3233 return ans;
3236 /* Return copy of s but with all whitespace (even internal) removed. */
3237 /* This fixes some buggy URL specification strings. */
3238 static Rune*
3239 removeallwhite(Rune* s)
3241 int j;
3242 int n;
3243 int i;
3244 int c;
3245 Rune* ans;
3247 j = 0;
3248 n = _Strlen(s);
3249 for(i = 0; i < n; i++) {
3250 c = s[i];
3251 if(c >= 256 || !isspace(c))
3252 j++;
3254 if(j < n) {
3255 ans = _newstr(j);
3256 j = 0;
3257 for(i = 0; i < n; i++) {
3258 c = s[i];
3259 if(c >= 256 || !isspace(c))
3260 ans[j++] = c;
3262 ans[j] = 0;
3264 else
3265 ans = _Strdup(s);
3266 return ans;
3269 /* Attribute value when mere presence of attr implies value of 1, */
3270 /* but if there is an integer there, return it as the value. */
3271 static int
3272 aflagval(Token* tok, int attid)
3274 int val;
3275 Rune* sval;
3277 val = 0;
3278 if(_tokaval(tok, attid, &sval, 0)) {
3279 val = 1;
3280 if(sval != nil)
3281 val = toint(sval);
3283 return val;
3286 static Align
3287 makealign(int halign, int valign)
3289 Align al;
3291 al.halign = halign;
3292 al.valign = valign;
3293 return al;
3296 /* Make an Align (two alignments, horizontal and vertical) */
3297 static Align
3298 aalign(Token* tok)
3300 return makealign(
3301 atabval(tok, Aalign, align_tab, NALIGNTAB, ALnone),
3302 atabval(tok, Avalign, align_tab, NALIGNTAB, ALnone));
3305 /* Make a Dimen, based on value of attid attr */
3306 static Dimen
3307 adimen(Token* tok, int attid)
3309 Rune* wd;
3311 if(_tokaval(tok, attid, &wd, 0))
3312 return parsedim(wd, _Strlen(wd));
3313 else
3314 return makedimen(Dnone, 0);
3317 /* Parse s[0:n] as num[.[num]][unit][%|*] */
3318 static Dimen
3319 parsedim(Rune* s, int ns)
3321 int kind;
3322 int spec;
3323 Rune* l;
3324 int nl;
3325 Rune* r;
3326 int nr;
3327 int mul;
3328 int i;
3329 Rune* f;
3330 int nf;
3331 int Tkdpi;
3332 Rune* units;
3334 kind = Dnone;
3335 spec = 0;
3336 _splitl(s, ns, L(Lnot0to9), &l, &nl, &r, &nr);
3337 if(nl != 0) {
3338 spec = 1000*_Strtol(l, nil, 10);
3339 if(nr > 0 && r[0] == '.') {
3340 _splitl(r+1, nr-1, L(Lnot0to9), &f, &nf, &r, &nr);
3341 if(nf != 0) {
3342 mul = 100;
3343 for(i = 0; i < nf; i++) {
3344 spec = spec + mul*(f[i]-'0');
3345 mul = mul/10;
3349 kind = Dpixels;
3350 if(nr != 0) {
3351 if(nr >= 2) {
3352 Tkdpi = 100;
3353 units = r;
3354 r = r+2;
3355 nr -= 2;
3356 if(!_Strncmpci(units, 2, L(Lpt)))
3357 spec = (spec*Tkdpi)/72;
3358 else if(!_Strncmpci(units, 2, L(Lpi)))
3359 spec = (spec*12*Tkdpi)/72;
3360 else if(!_Strncmpci(units, 2, L(Lin)))
3361 spec = spec*Tkdpi;
3362 else if(!_Strncmpci(units, 2, L(Lcm)))
3363 spec = (spec*100*Tkdpi)/254;
3364 else if(!_Strncmpci(units, 2, L(Lmm)))
3365 spec = (spec*10*Tkdpi)/254;
3366 else if(!_Strncmpci(units, 2, L(Lem)))
3367 spec = spec*15;
3368 else {
3369 if(warn)
3370 fprint(2, "warning: unknown units %C%Cs\n", units[0], units[1]);
3373 if(nr >= 1) {
3374 if(r[0] == '%')
3375 kind = Dpercent;
3376 else if(r[0] == '*')
3377 kind = Drelative;
3380 spec = spec/1000;
3382 else if(nr == 1 && r[0] == '*') {
3383 spec = 1;
3384 kind = Drelative;
3386 return makedimen(kind, spec);
3389 static void
3390 setdimarray(Token* tok, int attid, Dimen** pans, int* panslen)
3392 Rune* s;
3393 Dimen* d;
3394 int k;
3395 int nc;
3396 Rune* a[SMALLBUFSIZE];
3397 int an[SMALLBUFSIZE];
3399 if(_tokaval(tok, attid, &s, 0)) {
3400 nc = _splitall(s, _Strlen(s), L(Lcommaspace), a, an, SMALLBUFSIZE);
3401 if(nc > 0) {
3402 d = (Dimen*)emalloc(nc * sizeof(Dimen));
3403 for(k = 0; k < nc; k++) {
3404 d[k] = parsedim(a[k], an[k]);
3406 *pans = d;
3407 *panslen = nc;
3408 return;
3411 *pans = nil;
3412 *panslen = 0;
3415 static Background
3416 makebackground(Rune* imageurl, int color)
3418 Background bg;
3420 bg.image = imageurl;
3421 bg.color = color;
3422 return bg;
3425 static Item*
3426 newitext(Rune* s, int fnt, int fg, int voff, int ul)
3428 Itext* t;
3430 assert(s != nil);
3431 t = (Itext*)emalloc(sizeof(Itext));
3432 t->item.tag = Itexttag;
3433 t->s = s;
3434 t->fnt = fnt;
3435 t->fg = fg;
3436 t->voff = voff;
3437 t->ul = ul;
3438 return (Item*)t;
3441 static Item*
3442 newirule(int align, int size, int noshade, Dimen wspec)
3444 Irule* r;
3446 r = (Irule*)emalloc(sizeof(Irule));
3447 r->item.tag = Iruletag;
3448 r->align = align;
3449 r->size = size;
3450 r->noshade = noshade;
3451 r->wspec = wspec;
3452 return (Item*)r;
3455 /* Map is owned elsewhere. */
3456 static Item*
3457 newiimage(Rune* src, Rune* altrep, int align, int width, int height,
3458 int hspace, int vspace, int border, int ismap, Map* map)
3460 Iimage* i;
3461 int state;
3463 state = 0;
3464 if(ismap)
3465 state = IFsmap;
3466 i = (Iimage*)emalloc(sizeof(Iimage));
3467 i->item.tag = Iimagetag;
3468 i->item.state = state;
3469 i->imsrc = src;
3470 i->altrep = altrep;
3471 i->align = align;
3472 i->imwidth = width;
3473 i->imheight = height;
3474 i->hspace = hspace;
3475 i->vspace = vspace;
3476 i->border = border;
3477 i->map = map;
3478 i->ctlid = -1;
3479 return (Item*)i;
3482 static Item*
3483 newiformfield(Formfield* ff)
3485 Iformfield* f;
3487 f = (Iformfield*)emalloc(sizeof(Iformfield));
3488 f->item.tag = Iformfieldtag;
3489 f->formfield = ff;
3490 return (Item*)f;
3493 static Item*
3494 newitable(Table* tab)
3496 Itable* t;
3498 t = (Itable*)emalloc(sizeof(Itable));
3499 t->item.tag = Itabletag;
3500 t->table = tab;
3501 return (Item*)t;
3504 static Item*
3505 newifloat(Item* it, int side)
3507 Ifloat* f;
3509 f = (Ifloat*)emalloc(sizeof(Ifloat));
3510 f->_item.tag = Ifloattag;
3511 f->_item.state = IFwrap;
3512 f->item = it;
3513 f->side = side;
3514 return (Item*)f;
3517 static Item*
3518 newispacer(int spkind)
3520 Ispacer* s;
3522 s = (Ispacer*)emalloc(sizeof(Ispacer));
3523 s->item.tag = Ispacertag;
3524 s->spkind = spkind;
3525 return (Item*)s;
3528 /* Free one item (caller must deal with next pointer) */
3529 static void
3530 freeitem(Item* it)
3532 Iimage* ii;
3533 Genattr* ga;
3535 if(it == nil)
3536 return;
3538 switch(it->tag) {
3539 case Itexttag:
3540 free(((Itext*)it)->s);
3541 break;
3542 case Iimagetag:
3543 ii = (Iimage*)it;
3544 free(ii->imsrc);
3545 free(ii->altrep);
3546 break;
3547 case Iformfieldtag:
3548 freeformfield(((Iformfield*)it)->formfield);
3549 break;
3550 case Itabletag:
3551 freetable(((Itable*)it)->table);
3552 break;
3553 case Ifloattag:
3554 freeitem(((Ifloat*)it)->item);
3555 break;
3557 ga = it->genattr;
3558 if(ga != nil) {
3559 free(ga->id);
3560 free(ga->class);
3561 free(ga->style);
3562 free(ga->title);
3563 freescriptevents(ga->events);
3565 free(it);
3568 /* Free list of items chained through next pointer */
3569 void
3570 freeitems(Item* ithead)
3572 Item* it;
3573 Item* itnext;
3575 it = ithead;
3576 while(it != nil) {
3577 itnext = it->next;
3578 freeitem(it);
3579 it = itnext;
3583 static void
3584 freeformfield(Formfield* ff)
3586 Option* o;
3587 Option* onext;
3589 if(ff == nil)
3590 return;
3592 free(ff->name);
3593 free(ff->value);
3594 for(o = ff->options; o != nil; o = onext) {
3595 onext = o->next;
3596 free(o->value);
3597 free(o->display);
3599 free(ff);
3602 static void
3603 freetable(Table* t)
3605 int i;
3606 Tablecell* c;
3607 Tablecell* cnext;
3609 if(t == nil)
3610 return;
3612 /* We'll find all the unique cells via t->cells and next pointers. */
3613 /* (Other pointers to cells in the table are duplicates of these) */
3614 for(c = t->cells; c != nil; c = cnext) {
3615 cnext = c->next;
3616 freeitems(c->content);
3618 if(t->grid != nil) {
3619 for(i = 0; i < t->nrow; i++)
3620 free(t->grid[i]);
3621 free(t->grid);
3623 free(t->rows);
3624 free(t->cols);
3625 freeitems(t->caption);
3626 free(t);
3629 static void
3630 freeform(Form* f)
3632 if(f == nil)
3633 return;
3635 free(f->name);
3636 free(f->action);
3637 /* Form doesn't own its fields (Iformfield items do) */
3638 free(f);
3641 static void
3642 freeforms(Form* fhead)
3644 Form* f;
3645 Form* fnext;
3647 for(f = fhead; f != nil; f = fnext) {
3648 fnext = f->next;
3649 freeform(f);
3653 static void
3654 freeanchor(Anchor* a)
3656 if(a == nil)
3657 return;
3659 free(a->name);
3660 free(a->href);
3661 free(a);
3664 static void
3665 freeanchors(Anchor* ahead)
3667 Anchor* a;
3668 Anchor* anext;
3670 for(a = ahead; a != nil; a = anext) {
3671 anext = a->next;
3672 freeanchor(a);
3676 static void
3677 freedestanchor(DestAnchor* da)
3679 if(da == nil)
3680 return;
3682 free(da->name);
3683 free(da);
3686 static void
3687 freedestanchors(DestAnchor* dahead)
3689 DestAnchor* da;
3690 DestAnchor* danext;
3692 for(da = dahead; da != nil; da = danext) {
3693 danext = da->next;
3694 freedestanchor(da);
3698 static void
3699 freearea(Area* a)
3701 if(a == nil)
3702 return;
3703 free(a->href);
3704 free(a->coords);
3707 static void freekidinfos(Kidinfo* khead);
3709 static void
3710 freekidinfo(Kidinfo* k)
3712 if(k->isframeset) {
3713 free(k->rows);
3714 free(k->cols);
3715 freekidinfos(k->kidinfos);
3717 else {
3718 free(k->src);
3719 free(k->name);
3721 free(k);
3724 static void
3725 freekidinfos(Kidinfo* khead)
3727 Kidinfo* k;
3728 Kidinfo* knext;
3730 for(k = khead; k != nil; k = knext) {
3731 knext = k->next;
3732 freekidinfo(k);
3736 static void
3737 freemap(Map* m)
3739 Area* a;
3740 Area* anext;
3742 if(m == nil)
3743 return;
3745 free(m->name);
3746 for(a = m->areas; a != nil; a = anext) {
3747 anext = a->next;
3748 freearea(a);
3750 free(m);
3753 static void
3754 freemaps(Map* mhead)
3756 Map* m;
3757 Map* mnext;
3759 for(m = mhead; m != nil; m = mnext) {
3760 mnext = m->next;
3761 freemap(m);
3765 void
3766 freedocinfo(Docinfo* d)
3768 if(d == nil)
3769 return;
3770 free(d->src);
3771 free(d->base);
3772 freeitem((Item*)d->backgrounditem);
3773 free(d->refresh);
3774 freekidinfos(d->kidinfo);
3775 freeanchors(d->anchors);
3776 freedestanchors(d->dests);
3777 freeforms(d->forms);
3778 freemaps(d->maps);
3779 /* tables, images, and formfields are freed when */
3780 /* the items pointing at them are freed */
3781 free(d);
3784 /* Currently, someone else owns all the memory */
3785 /* pointed to by things in a Pstate. */
3786 static void
3787 freepstate(Pstate* p)
3789 free(p);
3792 static void
3793 freepstatestack(Pstate* pshead)
3795 Pstate* p;
3796 Pstate* pnext;
3798 for(p = pshead; p != nil; p = pnext) {
3799 pnext = p->next;
3800 free(p);
3804 static int
3805 Iconv(Fmt *f)
3807 Item* it;
3808 Itext* t;
3809 Irule* r;
3810 Iimage* i;
3811 Ifloat* fl;
3812 int state;
3813 Formfield* ff;
3814 Rune* ty;
3815 Tablecell* c;
3816 Table* tab;
3817 char* p;
3818 int cl;
3819 int hang;
3820 int indent;
3821 int bi;
3822 int nbuf;
3823 char buf[BIGBUFSIZE];
3825 it = va_arg(f->args, Item*);
3826 bi = 0;
3827 nbuf = sizeof(buf);
3828 state = it->state;
3829 nbuf = nbuf-1;
3830 if(state&IFbrk) {
3831 cl = state&(IFcleft|IFcright);
3832 p = "";
3833 if(cl) {
3834 if(cl == (IFcleft|IFcright))
3835 p = " both";
3836 else if(cl == IFcleft)
3837 p = " left";
3838 else
3839 p = " right";
3841 bi = snprint(buf, nbuf, "brk(%d%s)", (state&IFbrksp)? 1 : 0, p);
3843 if(state&IFnobrk)
3844 bi += snprint(buf+bi, nbuf-bi, " nobrk");
3845 if(!(state&IFwrap))
3846 bi += snprint(buf+bi, nbuf-bi, " nowrap");
3847 if(state&IFrjust)
3848 bi += snprint(buf+bi, nbuf-bi, " rjust");
3849 if(state&IFcjust)
3850 bi += snprint(buf+bi, nbuf-bi, " cjust");
3851 if(state&IFsmap)
3852 bi += snprint(buf+bi, nbuf-bi, " smap");
3853 indent = (state&IFindentmask) >> IFindentshift;
3854 if(indent > 0)
3855 bi += snprint(buf+bi, nbuf-bi, " indent=%d", indent);
3856 hang = state&IFhangmask;
3857 if(hang > 0)
3858 bi += snprint(buf+bi, nbuf-bi, " hang=%d", hang);
3860 switch(it->tag) {
3861 case Itexttag:
3862 t = (Itext*)it;
3863 bi += snprint(buf+bi, nbuf-bi, " Text '%S', fnt=%d, fg=%x", t->s, t->fnt, t->fg);
3864 break;
3866 case Iruletag:
3867 r = (Irule*)it;
3868 bi += snprint(buf+bi, nbuf-bi, "Rule size=%d, al=%S, wspec=", r->size, stringalign(r->align));
3869 bi += dimprint(buf+bi, nbuf-bi, r->wspec);
3870 break;
3872 case Iimagetag:
3873 i = (Iimage*)it;
3874 bi += snprint(buf+bi, nbuf-bi,
3875 "Image src=%S, alt=%S, al=%S, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%S",
3876 i->imsrc, i->altrep? i->altrep : L(Lempty), stringalign(i->align), i->imwidth, i->imheight,
3877 i->hspace, i->vspace, i->border, i->map?i->map->name : L(Lempty));
3878 break;
3880 case Iformfieldtag:
3881 ff = ((Iformfield*)it)->formfield;
3882 if(ff->ftype == Ftextarea)
3883 ty = L(Ltextarea);
3884 else if(ff->ftype == Fselect)
3885 ty = L(Lselect);
3886 else {
3887 ty = _revlookup(input_tab, NINPUTTAB, ff->ftype);
3888 if(ty == nil)
3889 ty = L(Lnone);
3891 bi += snprint(buf+bi, nbuf-bi, "Formfield %S, fieldid=%d, formid=%d, name=%S, value=%S",
3892 ty, ff->fieldid, ff->form->formid, ff->name? ff->name : L(Lempty),
3893 ff->value? ff->value : L(Lempty));
3894 break;
3896 case Itabletag:
3897 tab = ((Itable*)it)->table;
3898 bi += snprint(buf+bi, nbuf-bi, "Table tableid=%d, width=", tab->tableid);
3899 bi += dimprint(buf+bi, nbuf-bi, tab->width);
3900 bi += snprint(buf+bi, nbuf-bi, ", nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
3901 tab->nrow, tab->ncol, tab->ncell, tab->totw, tab->toth);
3902 for(c = tab->cells; c != nil; c = c->next)
3903 bi += snprint(buf+bi, nbuf-bi, "Cell %d.%d, at (%d,%d) ",
3904 tab->tableid, c->cellid, c->row, c->col);
3905 bi += snprint(buf+bi, nbuf-bi, "End of Table %d", tab->tableid);
3906 break;
3908 case Ifloattag:
3909 fl = (Ifloat*)it;
3910 bi += snprint(buf+bi, nbuf-bi, "Float, x=%d y=%d, side=%S, it=%I",
3911 fl->x, fl->y, stringalign(fl->side), fl->item);
3912 bi += snprint(buf+bi, nbuf-bi, "\n\t");
3913 break;
3915 case Ispacertag:
3916 p = "";
3917 switch(((Ispacer*)it)->spkind) {
3918 case ISPnull:
3919 p = "null";
3920 break;
3921 case ISPvline:
3922 p = "vline";
3923 break;
3924 case ISPhspace:
3925 p = "hspace";
3926 break;
3928 bi += snprint(buf+bi, nbuf-bi, "Spacer %s ", p);
3929 break;
3931 bi += snprint(buf+bi, nbuf-bi, " w=%d, h=%d, a=%d, anchor=%d\n",
3932 it->width, it->height, it->ascent, it->anchorid);
3933 buf[bi] = 0;
3934 return fmtstrcpy(f, buf);
3937 /* String version of alignment 'a' */
3938 static Rune*
3939 stringalign(int a)
3941 Rune* s;
3943 s = _revlookup(align_tab, NALIGNTAB, a);
3944 if(s == nil)
3945 s = L(Lnone);
3946 return s;
3949 /* Put at most nbuf chars of representation of d into buf, */
3950 /* and return number of characters put */
3951 static int
3952 dimprint(char* buf, int nbuf, Dimen d)
3954 int n;
3955 int k;
3957 n = 0;
3958 n += snprint(buf, nbuf, "%d", dimenspec(d));
3959 k = dimenkind(d);
3960 if(k == Dpercent)
3961 buf[n++] = '%';
3962 if(k == Drelative)
3963 buf[n++] = '*';
3964 return n;
3967 void
3968 printitems(Item* items, char* msg)
3970 Item* il;
3972 fprint(2, "%s\n", msg);
3973 il = items;
3974 while(il != nil) {
3975 fprint(2, "%I", il);
3976 il = il->next;
3980 static Genattr*
3981 newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events)
3983 Genattr* g;
3985 g = (Genattr*)emalloc(sizeof(Genattr));
3986 g->id = id;
3987 g->class = class;
3988 g->style = style;
3989 g->title = title;
3990 g->events = events;
3991 return g;
3994 static Formfield*
3995 newformfield(int ftype, int fieldid, Form* form, Rune* name,
3996 Rune* value, int size, int maxlength, Formfield* link)
3998 Formfield* ff;
4000 ff = (Formfield*)emalloc(sizeof(Formfield));
4001 ff->ftype = ftype;
4002 ff->fieldid = fieldid;
4003 ff->form = form;
4004 ff->name = name;
4005 ff->value = value;
4006 ff->size = size;
4007 ff->maxlength = maxlength;
4008 ff->ctlid = -1;
4009 ff->next = link;
4010 return ff;
4013 /* Transfers ownership of value and display to Option. */
4014 static Option*
4015 newoption(int selected, Rune* value, Rune* display, Option* link)
4017 Option *o;
4019 o = (Option*)emalloc(sizeof(Option));
4020 o->selected = selected;
4021 o->value = value;
4022 o->display = display;
4023 o->next = link;
4024 return o;
4027 static Form*
4028 newform(int formid, Rune* name, Rune* action, int target, int method, Form* link)
4030 Form* f;
4032 f = (Form*)emalloc(sizeof(Form));
4033 f->formid = formid;
4034 f->name = name;
4035 f->action = action;
4036 f->target = target;
4037 f->method = method;
4038 f->nfields = 0;
4039 f->fields = nil;
4040 f->next = link;
4041 return f;
4044 static Table*
4045 newtable(int tableid, Align align, Dimen width, int border,
4046 int cellspacing, int cellpadding, Background bg, Token* tok, Table* link)
4048 Table* t;
4050 t = (Table*)emalloc(sizeof(Table));
4051 t->tableid = tableid;
4052 t->align = align;
4053 t->width = width;
4054 t->border = border;
4055 t->cellspacing = cellspacing;
4056 t->cellpadding = cellpadding;
4057 t->background = bg;
4058 t->caption_place = ALbottom;
4059 t->caption_lay = nil;
4060 t->tabletok = tok;
4061 t->tabletok = nil;
4062 t->next = link;
4063 return t;
4066 static Tablerow*
4067 newtablerow(Align align, Background bg, int flags, Tablerow* link)
4069 Tablerow* tr;
4071 tr = (Tablerow*)emalloc(sizeof(Tablerow));
4072 tr->align = align;
4073 tr->background = bg;
4074 tr->flags = flags;
4075 tr->next = link;
4076 return tr;
4079 static Tablecell*
4080 newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec, int hspec,
4081 Background bg, int flags, Tablecell* link)
4083 Tablecell* c;
4085 c = (Tablecell*)emalloc(sizeof(Tablecell));
4086 c->cellid = cellid;
4087 c->lay = nil;
4088 c->rowspan = rowspan;
4089 c->colspan = colspan;
4090 c->align = align;
4091 c->flags = flags;
4092 c->wspec = wspec;
4093 c->hspec = hspec;
4094 c->background = bg;
4095 c->next = link;
4096 return c;
4099 static Anchor*
4100 newanchor(int index, Rune* name, Rune* href, int target, Anchor* link)
4102 Anchor* a;
4104 a = (Anchor*)emalloc(sizeof(Anchor));
4105 a->index = index;
4106 a->name = name;
4107 a->href = href;
4108 a->target = target;
4109 a->next = link;
4110 return a;
4113 static DestAnchor*
4114 newdestanchor(int index, Rune* name, Item* item, DestAnchor* link)
4116 DestAnchor* d;
4118 d = (DestAnchor*)emalloc(sizeof(DestAnchor));
4119 d->index = index;
4120 d->name = name;
4121 d->item = item;
4122 d->next = link;
4123 return d;
4126 static SEvent*
4127 newscriptevent(int type, Rune* script, SEvent* link)
4129 SEvent* ans;
4131 ans = (SEvent*)emalloc(sizeof(SEvent));
4132 ans->type = type;
4133 ans->script = script;
4134 ans->next = link;
4135 return ans;
4138 static void
4139 freescriptevents(SEvent* ehead)
4141 SEvent* e;
4142 SEvent* nexte;
4144 e = ehead;
4145 while(e != nil) {
4146 nexte = e->next;
4147 free(e->script);
4148 free(e);
4149 e = nexte;
4153 static Dimen
4154 makedimen(int kind, int spec)
4156 Dimen d;
4158 if(spec&Dkindmask) {
4159 if(warn)
4160 fprint(2, "warning: dimension spec too big: %d\n", spec);
4161 spec = 0;
4163 d.kindspec = kind|spec;
4164 return d;
4167 int
4168 dimenkind(Dimen d)
4170 return (d.kindspec&Dkindmask);
4173 int
4174 dimenspec(Dimen d)
4176 return (d.kindspec&Dspecmask);
4179 static Kidinfo*
4180 newkidinfo(int isframeset, Kidinfo* link)
4182 Kidinfo* ki;
4184 ki = (Kidinfo*)emalloc(sizeof(Kidinfo));
4185 ki->isframeset = isframeset;
4186 if(!isframeset) {
4187 ki->flags = FRhscrollauto|FRvscrollauto;
4188 ki->marginw = FRKIDMARGIN;
4189 ki->marginh = FRKIDMARGIN;
4190 ki->framebd = 1;
4192 ki->next = link;
4193 return ki;
4196 static Docinfo*
4197 newdocinfo(void)
4199 Docinfo* d;
4201 d = (Docinfo*)emalloc(sizeof(Docinfo));
4202 resetdocinfo(d);
4203 return d;
4206 static void
4207 resetdocinfo(Docinfo* d)
4209 memset(d, 0, sizeof(Docinfo));
4210 d->background = makebackground(nil, White);
4211 d->text = Black;
4212 d->link = Blue;
4213 d->vlink = Blue;
4214 d->alink = Blue;
4215 d->target = FTself;
4216 d->chset = ISO_8859_1;
4217 d->scripttype = TextJavascript;
4218 d->frameid = -1;
4221 /* Use targetmap array to keep track of name <-> targetid mapping. */
4222 /* Use real malloc(), and never free */
4223 static void
4224 targetmapinit(void)
4226 targetmapsize = 10;
4227 targetmap = (StringInt*)emalloc(targetmapsize*sizeof(StringInt));
4228 memset(targetmap, 0, targetmapsize*sizeof(StringInt));
4229 targetmap[0].key = _Strdup(L(L_top));
4230 targetmap[0].val = FTtop;
4231 targetmap[1].key = _Strdup(L(L_self));
4232 targetmap[1].val = FTself;
4233 targetmap[2].key = _Strdup(L(L_parent));
4234 targetmap[2].val = FTparent;
4235 targetmap[3].key = _Strdup(L(L_blank));
4236 targetmap[3].val = FTblank;
4237 ntargets = 4;
4240 int
4241 targetid(Rune* s)
4243 int i;
4244 int n;
4246 n = _Strlen(s);
4247 if(n == 0)
4248 return FTself;
4249 for(i = 0; i < ntargets; i++)
4250 if(_Strcmp(s, targetmap[i].key) == 0)
4251 return targetmap[i].val;
4252 if(i >= targetmapsize) {
4253 targetmapsize += 10;
4254 targetmap = (StringInt*)erealloc(targetmap, targetmapsize*sizeof(StringInt));
4256 targetmap[i].key = (Rune*)emalloc((n+1)*sizeof(Rune));
4257 memmove(targetmap[i].key, s, (n+1)*sizeof(Rune));
4258 targetmap[i].val = i;
4259 ntargets++;
4260 return i;
4263 Rune*
4264 targetname(int targid)
4266 int i;
4268 for(i = 0; i < ntargets; i++)
4269 if(targetmap[i].val == targid)
4270 return targetmap[i].key;
4271 return L(Lquestion);
4274 /* Convert HTML color spec to RGB value, returning dflt if can't. */
4275 /* Argument is supposed to be a valid HTML color, or "". */
4276 /* Return the RGB value of the color, using dflt if s */
4277 /* is nil or an invalid color. */
4278 static int
4279 color(Rune* s, int dflt)
4281 int v;
4282 Rune* rest;
4284 if(s == nil)
4285 return dflt;
4286 if(_lookup(color_tab, NCOLORS, s, _Strlen(s), &v))
4287 return v;
4288 if(s[0] == '#')
4289 s++;
4290 v = _Strtol(s, &rest, 16);
4291 if(*rest == 0)
4292 return v;
4293 return dflt;
4296 /* Debugging */
4298 #define HUGEPIX 10000
4300 /* A "shallow" validitem, that doesn't follow next links */
4301 /* or descend into tables. */
4302 static int
4303 validitem(Item* i)
4305 int ok;
4306 Itext* ti;
4307 Irule* ri;
4308 Iimage* ii;
4309 Ifloat* fi;
4310 int a;
4312 ok = (i->tag >= Itexttag && i->tag <= Ispacertag) &&
4313 (i->next == nil || validptr(i->next)) &&
4314 (i->width >= 0 && i->width < HUGEPIX) &&
4315 (i->height >= 0 && i->height < HUGEPIX) &&
4316 (i->ascent > -HUGEPIX && i->ascent < HUGEPIX) &&
4317 (i->anchorid >= 0) &&
4318 (i->genattr == nil || validptr(i->genattr));
4319 /* also, could check state for ridiculous combinations */
4320 /* also, could check anchorid for within-doc-range */
4321 if(ok)
4322 switch(i->tag) {
4323 case Itexttag:
4324 ti = (Itext*)i;
4325 ok = validStr(ti->s) &&
4326 (ti->fnt >= 0 && ti->fnt < NumStyle*NumSize) &&
4327 (ti->ul == ULnone || ti->ul == ULunder || ti->ul == ULmid);
4328 break;
4329 case Iruletag:
4330 ri = (Irule*)i;
4331 ok = (validvalign(ri->align) || validhalign(ri->align)) &&
4332 (ri->size >=0 && ri->size < HUGEPIX);
4333 break;
4334 case Iimagetag:
4335 ii = (Iimage*)i;
4336 ok = (ii->imsrc == nil || validptr(ii->imsrc)) &&
4337 (ii->item.width >= 0 && ii->item.width < HUGEPIX) &&
4338 (ii->item.height >= 0 && ii->item.height < HUGEPIX) &&
4339 (ii->imwidth >= 0 && ii->imwidth < HUGEPIX) &&
4340 (ii->imheight >= 0 && ii->imheight < HUGEPIX) &&
4341 (ii->altrep == nil || validStr(ii->altrep)) &&
4342 (ii->map == nil || validptr(ii->map)) &&
4343 (validvalign(ii->align) || validhalign(ii->align)) &&
4344 (ii->nextimage == nil || validptr(ii->nextimage));
4345 break;
4346 case Iformfieldtag:
4347 ok = validformfield(((Iformfield*)i)->formfield);
4348 break;
4349 case Itabletag:
4350 ok = validptr((Itable*)i);
4351 break;
4352 case Ifloattag:
4353 fi = (Ifloat*)i;
4354 ok = (fi->side == ALleft || fi->side == ALright) &&
4355 validitem(fi->item) &&
4356 (fi->item->tag == Iimagetag || fi->item->tag == Itabletag);
4357 break;
4358 case Ispacertag:
4359 a = ((Ispacer*)i)->spkind;
4360 ok = a==ISPnull || a==ISPvline || a==ISPhspace || a==ISPgeneral;
4361 break;
4362 default:
4363 ok = 0;
4365 return ok;
4368 /* "deep" validation, that checks whole list of items, */
4369 /* and descends into tables and floated tables. */
4370 /* nil is ok for argument. */
4371 int
4372 validitems(Item* i)
4374 int ok;
4375 Item* ii;
4377 ok = 1;
4378 while(i != nil && ok) {
4379 ok = validitem(i);
4380 if(ok) {
4381 if(i->tag == Itabletag) {
4382 ok = validtable(((Itable*)i)->table);
4384 else if(i->tag == Ifloattag) {
4385 ii = ((Ifloat*)i)->item;
4386 if(ii->tag == Itabletag)
4387 ok = validtable(((Itable*)ii)->table);
4390 if(!ok) {
4391 fprint(2, "invalid item: %I\n", i);
4393 i = i->next;
4395 return ok;
4398 static int
4399 validformfield(Formfield* f)
4401 int ok;
4403 ok = (f->next == nil || validptr(f->next)) &&
4404 (f->ftype >= 0 && f->ftype <= Ftextarea) &&
4405 f->fieldid >= 0 &&
4406 (f->form == nil || validptr(f->form)) &&
4407 (f->name == nil || validStr(f->name)) &&
4408 (f->value == nil || validStr(f->value)) &&
4409 (f->options == nil || validptr(f->options)) &&
4410 (f->image == nil || validitem(f->image)) &&
4411 (f->events == nil || validptr(f->events));
4412 /* when all built, should have f->fieldid < f->form->nfields, */
4413 /* but this may be called during build... */
4414 return ok;
4417 /* "deep" validation -- checks cell contents too */
4418 static int
4419 validtable(Table* t)
4421 int ok;
4422 int i, j;
4423 Tablecell* c;
4425 ok = (t->next == nil || validptr(t->next)) &&
4426 t->nrow >= 0 &&
4427 t->ncol >= 0 &&
4428 t->ncell >= 0 &&
4429 validalign(t->align) &&
4430 validdimen(t->width) &&
4431 (t->border >= 0 && t->border < HUGEPIX) &&
4432 (t->cellspacing >= 0 && t->cellspacing < HUGEPIX) &&
4433 (t->cellpadding >= 0 && t->cellpadding < HUGEPIX) &&
4434 validitems(t->caption) &&
4435 (t->caption_place == ALtop || t->caption_place == ALbottom) &&
4436 (t->totw >= 0 && t->totw < HUGEPIX) &&
4437 (t->toth >= 0 && t->toth < HUGEPIX) &&
4438 (t->tabletok == nil || validptr(t->tabletok));
4439 /* during parsing, t->rows has list; */
4440 /* only when parsing is done is t->nrow set > 0 */
4441 if(ok && t->nrow > 0 && t->ncol > 0) {
4442 /* table is "finished" */
4443 for(i = 0; i < t->nrow && ok; i++)
4444 ok = validtablerow(t->rows+i);
4445 for(j = 0; j < t->ncol && ok; j++)
4446 ok = validtablecol(t->cols+j);
4447 for(c = t->cells; c != nil && ok; c = c->next)
4448 ok = validtablecell(c);
4449 for(i = 0; i < t->nrow && ok; i++)
4450 for(j = 0; j < t->ncol && ok; j++)
4451 ok = validptr(t->grid[i][j]);
4453 return ok;
4456 static int
4457 validvalign(int a)
4459 return a == ALnone || a == ALmiddle || a == ALbottom || a == ALtop || a == ALbaseline;
4462 static int
4463 validhalign(int a)
4465 return a == ALnone || a == ALleft || a == ALcenter || a == ALright ||
4466 a == ALjustify || a == ALchar;
4469 static int
4470 validalign(Align a)
4472 return validhalign(a.halign) && validvalign(a.valign);
4475 static int
4476 validdimen(Dimen d)
4478 int ok;
4479 int s;
4481 ok = 0;
4482 s = d.kindspec&Dspecmask;
4483 switch(d.kindspec&Dkindmask) {
4484 case Dnone:
4485 ok = s==0;
4486 break;
4487 case Dpixels:
4488 ok = s < HUGEPIX;
4489 break;
4490 case Dpercent:
4491 case Drelative:
4492 ok = 1;
4493 break;
4495 return ok;
4498 static int
4499 validtablerow(Tablerow* r)
4501 return (r->cells == nil || validptr(r->cells)) &&
4502 (r->height >= 0 && r->height < HUGEPIX) &&
4503 (r->ascent > -HUGEPIX && r->ascent < HUGEPIX) &&
4504 validalign(r->align);
4507 static int
4508 validtablecol(Tablecol* c)
4510 return c->width >= 0 && c->width < HUGEPIX
4511 && validalign(c->align);
4514 static int
4515 validtablecell(Tablecell* c)
4517 int ok;
4519 ok = (c->next == nil || validptr(c->next)) &&
4520 (c->nextinrow == nil || validptr(c->nextinrow)) &&
4521 (c->content == nil || validptr(c->content)) &&
4522 (c->lay == nil || validptr(c->lay)) &&
4523 c->rowspan >= 0 &&
4524 c->colspan >= 0 &&
4525 validalign(c->align) &&
4526 validdimen(c->wspec) &&
4527 c->row >= 0 &&
4528 c->col >= 0;
4529 if(ok) {
4530 if(c->content != nil)
4531 ok = validitems(c->content);
4533 return ok;
4536 static int
4537 validptr(void* p)
4539 /* TODO: a better job of this. */
4540 /* For now, just dereference, which cause a bomb */
4541 /* if not valid */
4542 static char c;
4544 c = *((char*)p);
4545 USED(c);
4546 return 1;
4549 static int
4550 validStr(Rune* s)
4552 return s != nil && validptr(s);