mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-15 11:20:03 +00:00
4544 lines
97 KiB
C
4544 lines
97 KiB
C
#include <u.h>
|
|
#include <libc.h>
|
|
#include <draw.h>
|
|
#include <ctype.h>
|
|
#include <html.h>
|
|
#include "impl.h"
|
|
|
|
// A stack for holding integer values
|
|
enum {
|
|
Nestmax = 40 // max nesting level of lists, font styles, etc.
|
|
};
|
|
|
|
struct Stack {
|
|
int n; // next available slot (top of stack is stack[n-1])
|
|
int slots[Nestmax]; // stack entries
|
|
};
|
|
|
|
// Parsing state
|
|
struct Pstate
|
|
{
|
|
Pstate* next; // in stack of Pstates
|
|
int skipping; // true when we shouldn't add items
|
|
int skipwhite; // true when we should strip leading space
|
|
int curfont; // font index for current font
|
|
int curfg; // current foreground color
|
|
Background curbg; // current background
|
|
int curvoff; // current baseline offset
|
|
uchar curul; // current underline/strike state
|
|
uchar curjust; // current justify state
|
|
int curanchor; // current (href) anchor id (if in one), or 0
|
|
int curstate; // current value of item state
|
|
int literal; // current literal state
|
|
int inpar; // true when in a paragraph-like construct
|
|
int adjsize; // current font size adjustment
|
|
Item* items; // dummy head of item list we're building
|
|
Item* lastit; // tail of item list we're building
|
|
Item* prelastit; // item before lastit
|
|
Stack fntstylestk; // style stack
|
|
Stack fntsizestk; // size stack
|
|
Stack fgstk; // text color stack
|
|
Stack ulstk; // underline stack
|
|
Stack voffstk; // vertical offset stack
|
|
Stack listtypestk; // list type stack
|
|
Stack listcntstk; // list counter stack
|
|
Stack juststk; // justification stack
|
|
Stack hangstk; // hanging stack
|
|
};
|
|
|
|
struct ItemSource
|
|
{
|
|
Docinfo* doc;
|
|
Pstate* psstk;
|
|
int nforms;
|
|
int ntables;
|
|
int nanchors;
|
|
int nframes;
|
|
Form* curform;
|
|
Map* curmap;
|
|
Table* tabstk;
|
|
Kidinfo* kidstk;
|
|
};
|
|
|
|
// Some layout parameters
|
|
enum {
|
|
FRKIDMARGIN = 6, // default margin around kid frames
|
|
IMGHSPACE = 0, // default hspace for images (0 matches IE, Netscape)
|
|
IMGVSPACE = 0, // default vspace for images
|
|
FLTIMGHSPACE = 2, // default hspace for float images
|
|
TABSP = 5, // default cellspacing for tables
|
|
TABPAD = 1, // default cell padding for tables
|
|
LISTTAB = 1, // number of tabs to indent lists
|
|
BQTAB = 1, // number of tabs to indent blockquotes
|
|
HRSZ = 2, // thickness of horizontal rules
|
|
SUBOFF = 4, // vertical offset for subscripts
|
|
SUPOFF = 6, // vertical offset for superscripts
|
|
NBSP = 160 // non-breaking space character
|
|
};
|
|
|
|
// These tables must be sorted
|
|
static StringInt *align_tab;
|
|
static AsciiInt _align_tab[] = {
|
|
{"baseline", ALbaseline},
|
|
{"bottom", ALbottom},
|
|
{"center", ALcenter},
|
|
{"char", ALchar},
|
|
{"justify", ALjustify},
|
|
{"left", ALleft},
|
|
{"middle", ALmiddle},
|
|
{"right", ALright},
|
|
{"top", ALtop}
|
|
};
|
|
#define NALIGNTAB (sizeof(align_tab)/sizeof(StringInt))
|
|
|
|
static StringInt *input_tab;
|
|
static AsciiInt _input_tab[] = {
|
|
{"button", Fbutton},
|
|
{"checkbox", Fcheckbox},
|
|
{"file", Ffile},
|
|
{"hidden", Fhidden},
|
|
{"image", Fimage},
|
|
{"password", Fpassword},
|
|
{"radio", Fradio},
|
|
{"reset", Freset},
|
|
{"submit", Fsubmit},
|
|
{"text", Ftext}
|
|
};
|
|
#define NINPUTTAB (sizeof(input_tab)/sizeof(StringInt))
|
|
|
|
static StringInt *clear_tab;
|
|
static AsciiInt _clear_tab[] = {
|
|
{"all", IFcleft|IFcright},
|
|
{"left", IFcleft},
|
|
{"right", IFcright}
|
|
};
|
|
#define NCLEARTAB (sizeof(clear_tab)/sizeof(StringInt))
|
|
|
|
static StringInt *fscroll_tab;
|
|
static AsciiInt _fscroll_tab[] = {
|
|
{"auto", FRhscrollauto|FRvscrollauto},
|
|
{"no", FRnoscroll},
|
|
{"yes", FRhscroll|FRvscroll},
|
|
};
|
|
#define NFSCROLLTAB (sizeof(fscroll_tab)/sizeof(StringInt))
|
|
|
|
static StringInt *shape_tab;
|
|
static AsciiInt _shape_tab[] = {
|
|
{"circ", SHcircle},
|
|
{"circle", SHcircle},
|
|
{"poly", SHpoly},
|
|
{"polygon", SHpoly},
|
|
{"rect", SHrect},
|
|
{"rectangle", SHrect}
|
|
};
|
|
#define NSHAPETAB (sizeof(shape_tab)/sizeof(StringInt))
|
|
|
|
static StringInt *method_tab;
|
|
static AsciiInt _method_tab[] = {
|
|
{"get", HGet},
|
|
{"post", HPost}
|
|
};
|
|
#define NMETHODTAB (sizeof(method_tab)/sizeof(StringInt))
|
|
|
|
static Rune** roman;
|
|
static char* _roman[15]= {
|
|
"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
|
|
"XI", "XII", "XIII", "XIV", "XV"
|
|
};
|
|
#define NROMAN 15
|
|
|
|
// List number types
|
|
enum {
|
|
LTdisc, LTsquare, LTcircle, LT1, LTa, LTA, LTi, LTI
|
|
};
|
|
|
|
enum {
|
|
SPBefore = 2,
|
|
SPAfter = 4,
|
|
BL = 1,
|
|
BLBA = (BL|SPBefore|SPAfter)
|
|
};
|
|
|
|
// blockbrk[tag] is break info for a block level element, or one
|
|
// of a few others that get the same treatment re ending open paragraphs
|
|
// and requiring a line break / vertical space before them.
|
|
// If we want a line of space before the given element, SPBefore is OR'd in.
|
|
// If we want a line of space after the given element, SPAfter is OR'd in.
|
|
|
|
static uchar blockbrk[Numtags]= {
|
|
/*Notfound*/ 0,
|
|
/*Comment*/ 0,
|
|
/*Ta*/ 0,
|
|
/*Tabbr*/ 0,
|
|
/*Tacronym*/ 0,
|
|
/*Taddress*/ BLBA,
|
|
/*Tapplet*/ 0,
|
|
/*Tarea*/ 0,
|
|
/*Tb*/ 0,
|
|
/*Tbase*/ 0,
|
|
/*Tbasefont*/ 0,
|
|
/*Tbdo*/ 0,
|
|
/*Tbig*/ 0,
|
|
/*Tblink*/ 0,
|
|
/*Tblockquote*/ BLBA,
|
|
/*Tbody*/ 0,
|
|
/*Tbq*/ 0,
|
|
/*Tbr*/ 0,
|
|
/*Tbutton*/ 0,
|
|
/*Tcaption*/ 0,
|
|
/*Tcenter*/ BL,
|
|
/*Tcite*/ 0,
|
|
/*Tcode*/ 0,
|
|
/*Tcol*/ 0,
|
|
/*Tcolgroup*/ 0,
|
|
/*Tdd*/ BL,
|
|
/*Tdel*/ 0,
|
|
/*Tdfn*/ 0,
|
|
/*Tdir*/ BLBA,
|
|
/*Tdiv*/ BL,
|
|
/*Tdl*/ BLBA,
|
|
/*Tdt*/ BL,
|
|
/*Tem*/ 0,
|
|
/*Tfieldset*/ 0,
|
|
/*Tfont*/ 0,
|
|
/*Tform*/ BLBA,
|
|
/*Tframe*/ 0,
|
|
/*Tframeset*/ 0,
|
|
/*Th1*/ BL,
|
|
/*Th2*/ BL,
|
|
/*Th3*/ BL,
|
|
/*Th4*/ BL,
|
|
/*Th5*/ BL,
|
|
/*Th6*/ BL,
|
|
/*Thead*/ 0,
|
|
/*Thr*/ BL,
|
|
/*Thtml*/ 0,
|
|
/*Ti*/ 0,
|
|
/*Tiframe*/ 0,
|
|
/*Timg*/ 0,
|
|
/*Tinput*/ 0,
|
|
/*Tins*/ 0,
|
|
/*Tisindex*/ BLBA,
|
|
/*Tkbd*/ 0,
|
|
/*Tlabel*/ 0,
|
|
/*Tlegend*/ 0,
|
|
/*Tli*/ BL,
|
|
/*Tlink*/ 0,
|
|
/*Tmap*/ 0,
|
|
/*Tmenu*/ BLBA,
|
|
/*Tmeta*/ 0,
|
|
/*Tnobr*/ 0,
|
|
/*Tnoframes*/ 0,
|
|
/*Tnoscript*/ 0,
|
|
/*Tobject*/ 0,
|
|
/*Tol*/ BLBA,
|
|
/*Toptgroup*/ 0,
|
|
/*Toption*/ 0,
|
|
/*Tp*/ BLBA,
|
|
/*Tparam*/ 0,
|
|
/*Tpre*/ BLBA,
|
|
/*Tq*/ 0,
|
|
/*Ts*/ 0,
|
|
/*Tsamp*/ 0,
|
|
/*Tscript*/ 0,
|
|
/*Tselect*/ 0,
|
|
/*Tsmall*/ 0,
|
|
/*Tspan*/ 0,
|
|
/*Tstrike*/ 0,
|
|
/*Tstrong*/ 0,
|
|
/*Tstyle*/ 0,
|
|
/*Tsub*/ 0,
|
|
/*Tsup*/ 0,
|
|
/*Ttable*/ 0,
|
|
/*Ttbody*/ 0,
|
|
/*Ttd*/ 0,
|
|
/*Ttextarea*/ 0,
|
|
/*Ttfoot*/ 0,
|
|
/*Tth*/ 0,
|
|
/*Tthead*/ 0,
|
|
/*Ttitle*/ 0,
|
|
/*Ttr*/ 0,
|
|
/*Ttt*/ 0,
|
|
/*Tu*/ 0,
|
|
/*Tul*/ BLBA,
|
|
/*Tvar*/ 0,
|
|
};
|
|
|
|
enum {
|
|
AGEN = 1
|
|
};
|
|
|
|
// attrinfo is information about attributes.
|
|
// The AGEN value means that the attribute is generic (applies to almost all elements)
|
|
static uchar attrinfo[Numattrs]= {
|
|
/*Aabbr*/ 0,
|
|
/*Aaccept_charset*/ 0,
|
|
/*Aaccess_key*/ 0,
|
|
/*Aaction*/ 0,
|
|
/*Aalign*/ 0,
|
|
/*Aalink*/ 0,
|
|
/*Aalt*/ 0,
|
|
/*Aarchive*/ 0,
|
|
/*Aaxis*/ 0,
|
|
/*Abackground*/ 0,
|
|
/*Abgcolor*/ 0,
|
|
/*Aborder*/ 0,
|
|
/*Acellpadding*/ 0,
|
|
/*Acellspacing*/ 0,
|
|
/*Achar*/ 0,
|
|
/*Acharoff*/ 0,
|
|
/*Acharset*/ 0,
|
|
/*Achecked*/ 0,
|
|
/*Acite*/ 0,
|
|
/*Aclass*/ AGEN,
|
|
/*Aclassid*/ 0,
|
|
/*Aclear*/ 0,
|
|
/*Acode*/ 0,
|
|
/*Acodebase*/ 0,
|
|
/*Acodetype*/ 0,
|
|
/*Acolor*/ 0,
|
|
/*Acols*/ 0,
|
|
/*Acolspan*/ 0,
|
|
/*Acompact*/ 0,
|
|
/*Acontent*/ 0,
|
|
/*Acoords*/ 0,
|
|
/*Adata*/ 0,
|
|
/*Adatetime*/ 0,
|
|
/*Adeclare*/ 0,
|
|
/*Adefer*/ 0,
|
|
/*Adir*/ 0,
|
|
/*Adisabled*/ 0,
|
|
/*Aenctype*/ 0,
|
|
/*Aface*/ 0,
|
|
/*Afor*/ 0,
|
|
/*Aframe*/ 0,
|
|
/*Aframeborder*/ 0,
|
|
/*Aheaders*/ 0,
|
|
/*Aheight*/ 0,
|
|
/*Ahref*/ 0,
|
|
/*Ahreflang*/ 0,
|
|
/*Ahspace*/ 0,
|
|
/*Ahttp_equiv*/ 0,
|
|
/*Aid*/ AGEN,
|
|
/*Aismap*/ 0,
|
|
/*Alabel*/ 0,
|
|
/*Alang*/ 0,
|
|
/*Alink*/ 0,
|
|
/*Alongdesc*/ 0,
|
|
/*Amarginheight*/ 0,
|
|
/*Amarginwidth*/ 0,
|
|
/*Amaxlength*/ 0,
|
|
/*Amedia*/ 0,
|
|
/*Amethod*/ 0,
|
|
/*Amultiple*/ 0,
|
|
/*Aname*/ 0,
|
|
/*Anohref*/ 0,
|
|
/*Anoresize*/ 0,
|
|
/*Anoshade*/ 0,
|
|
/*Anowrap*/ 0,
|
|
/*Aobject*/ 0,
|
|
/*Aonblur*/ AGEN,
|
|
/*Aonchange*/ AGEN,
|
|
/*Aonclick*/ AGEN,
|
|
/*Aondblclick*/ AGEN,
|
|
/*Aonfocus*/ AGEN,
|
|
/*Aonkeypress*/ AGEN,
|
|
/*Aonkeyup*/ AGEN,
|
|
/*Aonload*/ AGEN,
|
|
/*Aonmousedown*/ AGEN,
|
|
/*Aonmousemove*/ AGEN,
|
|
/*Aonmouseout*/ AGEN,
|
|
/*Aonmouseover*/ AGEN,
|
|
/*Aonmouseup*/ AGEN,
|
|
/*Aonreset*/ AGEN,
|
|
/*Aonselect*/ AGEN,
|
|
/*Aonsubmit*/ AGEN,
|
|
/*Aonunload*/ AGEN,
|
|
/*Aprofile*/ 0,
|
|
/*Aprompt*/ 0,
|
|
/*Areadonly*/ 0,
|
|
/*Arel*/ 0,
|
|
/*Arev*/ 0,
|
|
/*Arows*/ 0,
|
|
/*Arowspan*/ 0,
|
|
/*Arules*/ 0,
|
|
/*Ascheme*/ 0,
|
|
/*Ascope*/ 0,
|
|
/*Ascrolling*/ 0,
|
|
/*Aselected*/ 0,
|
|
/*Ashape*/ 0,
|
|
/*Asize*/ 0,
|
|
/*Aspan*/ 0,
|
|
/*Asrc*/ 0,
|
|
/*Astandby*/ 0,
|
|
/*Astart*/ 0,
|
|
/*Astyle*/ AGEN,
|
|
/*Asummary*/ 0,
|
|
/*Atabindex*/ 0,
|
|
/*Atarget*/ 0,
|
|
/*Atext*/ 0,
|
|
/*Atitle*/ AGEN,
|
|
/*Atype*/ 0,
|
|
/*Ausemap*/ 0,
|
|
/*Avalign*/ 0,
|
|
/*Avalue*/ 0,
|
|
/*Avaluetype*/ 0,
|
|
/*Aversion*/ 0,
|
|
/*Avlink*/ 0,
|
|
/*Avspace*/ 0,
|
|
/*Awidth*/ 0,
|
|
};
|
|
|
|
static uchar scriptev[Numattrs]= {
|
|
/*Aabbr*/ 0,
|
|
/*Aaccept_charset*/ 0,
|
|
/*Aaccess_key*/ 0,
|
|
/*Aaction*/ 0,
|
|
/*Aalign*/ 0,
|
|
/*Aalink*/ 0,
|
|
/*Aalt*/ 0,
|
|
/*Aarchive*/ 0,
|
|
/*Aaxis*/ 0,
|
|
/*Abackground*/ 0,
|
|
/*Abgcolor*/ 0,
|
|
/*Aborder*/ 0,
|
|
/*Acellpadding*/ 0,
|
|
/*Acellspacing*/ 0,
|
|
/*Achar*/ 0,
|
|
/*Acharoff*/ 0,
|
|
/*Acharset*/ 0,
|
|
/*Achecked*/ 0,
|
|
/*Acite*/ 0,
|
|
/*Aclass*/ 0,
|
|
/*Aclassid*/ 0,
|
|
/*Aclear*/ 0,
|
|
/*Acode*/ 0,
|
|
/*Acodebase*/ 0,
|
|
/*Acodetype*/ 0,
|
|
/*Acolor*/ 0,
|
|
/*Acols*/ 0,
|
|
/*Acolspan*/ 0,
|
|
/*Acompact*/ 0,
|
|
/*Acontent*/ 0,
|
|
/*Acoords*/ 0,
|
|
/*Adata*/ 0,
|
|
/*Adatetime*/ 0,
|
|
/*Adeclare*/ 0,
|
|
/*Adefer*/ 0,
|
|
/*Adir*/ 0,
|
|
/*Adisabled*/ 0,
|
|
/*Aenctype*/ 0,
|
|
/*Aface*/ 0,
|
|
/*Afor*/ 0,
|
|
/*Aframe*/ 0,
|
|
/*Aframeborder*/ 0,
|
|
/*Aheaders*/ 0,
|
|
/*Aheight*/ 0,
|
|
/*Ahref*/ 0,
|
|
/*Ahreflang*/ 0,
|
|
/*Ahspace*/ 0,
|
|
/*Ahttp_equiv*/ 0,
|
|
/*Aid*/ 0,
|
|
/*Aismap*/ 0,
|
|
/*Alabel*/ 0,
|
|
/*Alang*/ 0,
|
|
/*Alink*/ 0,
|
|
/*Alongdesc*/ 0,
|
|
/*Amarginheight*/ 0,
|
|
/*Amarginwidth*/ 0,
|
|
/*Amaxlength*/ 0,
|
|
/*Amedia*/ 0,
|
|
/*Amethod*/ 0,
|
|
/*Amultiple*/ 0,
|
|
/*Aname*/ 0,
|
|
/*Anohref*/ 0,
|
|
/*Anoresize*/ 0,
|
|
/*Anoshade*/ 0,
|
|
/*Anowrap*/ 0,
|
|
/*Aobject*/ 0,
|
|
/*Aonblur*/ SEonblur,
|
|
/*Aonchange*/ SEonchange,
|
|
/*Aonclick*/ SEonclick,
|
|
/*Aondblclick*/ SEondblclick,
|
|
/*Aonfocus*/ SEonfocus,
|
|
/*Aonkeypress*/ SEonkeypress,
|
|
/*Aonkeyup*/ SEonkeyup,
|
|
/*Aonload*/ SEonload,
|
|
/*Aonmousedown*/ SEonmousedown,
|
|
/*Aonmousemove*/ SEonmousemove,
|
|
/*Aonmouseout*/ SEonmouseout,
|
|
/*Aonmouseover*/ SEonmouseover,
|
|
/*Aonmouseup*/ SEonmouseup,
|
|
/*Aonreset*/ SEonreset,
|
|
/*Aonselect*/ SEonselect,
|
|
/*Aonsubmit*/ SEonsubmit,
|
|
/*Aonunload*/ SEonunload,
|
|
/*Aprofile*/ 0,
|
|
/*Aprompt*/ 0,
|
|
/*Areadonly*/ 0,
|
|
/*Arel*/ 0,
|
|
/*Arev*/ 0,
|
|
/*Arows*/ 0,
|
|
/*Arowspan*/ 0,
|
|
/*Arules*/ 0,
|
|
/*Ascheme*/ 0,
|
|
/*Ascope*/ 0,
|
|
/*Ascrolling*/ 0,
|
|
/*Aselected*/ 0,
|
|
/*Ashape*/ 0,
|
|
/*Asize*/ 0,
|
|
/*Aspan*/ 0,
|
|
/*Asrc*/ 0,
|
|
/*Astandby*/ 0,
|
|
/*Astart*/ 0,
|
|
/*Astyle*/ 0,
|
|
/*Asummary*/ 0,
|
|
/*Atabindex*/ 0,
|
|
/*Atarget*/ 0,
|
|
/*Atext*/ 0,
|
|
/*Atitle*/ 0,
|
|
/*Atype*/ 0,
|
|
/*Ausemap*/ 0,
|
|
/*Avalign*/ 0,
|
|
/*Avalue*/ 0,
|
|
/*Avaluetype*/ 0,
|
|
/*Aversion*/ 0,
|
|
/*Avlink*/ 0,
|
|
/*Avspace*/ 0,
|
|
/*Awidth*/ 0,
|
|
};
|
|
|
|
// Color lookup table
|
|
static StringInt *color_tab;
|
|
static AsciiInt _color_tab[] = {
|
|
{"aqua", 0x00FFFF},
|
|
{"black", 0x000000},
|
|
{"blue", 0x0000CC},
|
|
{"fuchsia", 0xFF00FF},
|
|
{"gray", 0x808080},
|
|
{"green", 0x008000},
|
|
{"lime", 0x00FF00},
|
|
{"maroon", 0x800000},
|
|
{"navy", 0x000080,},
|
|
{"olive", 0x808000},
|
|
{"purple", 0x800080},
|
|
{"red", 0xFF0000},
|
|
{"silver", 0xC0C0C0},
|
|
{"teal", 0x008080},
|
|
{"white", 0xFFFFFF},
|
|
{"yellow", 0xFFFF00}
|
|
};
|
|
#define NCOLORS (sizeof(color_tab)/sizeof(StringInt))
|
|
|
|
static StringInt *targetmap;
|
|
static int targetmapsize;
|
|
static int ntargets;
|
|
|
|
static int buildinited = 0;
|
|
|
|
#define SMALLBUFSIZE 240
|
|
#define BIGBUFSIZE 2000
|
|
|
|
int dbgbuild = 0;
|
|
int warn = 0;
|
|
|
|
static Align aalign(Token* tok);
|
|
static int acolorval(Token* tok, int attid, int dflt);
|
|
static void addbrk(Pstate* ps, int sp, int clr);
|
|
static void additem(Pstate* ps, Item* it, Token* tok);
|
|
static void addlinebrk(Pstate* ps, int clr);
|
|
static void addnbsp(Pstate* ps);
|
|
static void addtext(Pstate* ps, Rune* s);
|
|
static Dimen adimen(Token* tok, int attid);
|
|
static int aflagval(Token* tok, int attid);
|
|
static int aintval(Token* tok, int attid, int dflt);
|
|
static Rune* astrval(Token* tok, int attid, Rune* dflt);
|
|
static int atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt);
|
|
static int atargval(Token* tok, int dflt);
|
|
static int auintval(Token* tok, int attid, int dflt);
|
|
static Rune* aurlval(Token* tok, int attid, Rune* dflt, Rune* base);
|
|
static Rune* aval(Token* tok, int attid);
|
|
static void buildinit(void);
|
|
static Pstate* cell_pstate(Pstate* oldps, int ishead);
|
|
static void changehang(Pstate* ps, int delta);
|
|
static void changeindent(Pstate* ps, int delta);
|
|
static int color(Rune* s, int dflt);
|
|
static void copystack(Stack* tostk, Stack* fromstk);
|
|
static int dimprint(char* buf, int nbuf, Dimen d);
|
|
static Pstate* finishcell(Table* curtab, Pstate* psstk);
|
|
static void finish_table(Table* t);
|
|
static void freeanchor(Anchor* a);
|
|
static void freedestanchor(DestAnchor* da);
|
|
static void freeform(Form* f);
|
|
static void freeformfield(Formfield* ff);
|
|
static void freeitem(Item* it);
|
|
static void freepstate(Pstate* p);
|
|
static void freepstatestack(Pstate* pshead);
|
|
static void freescriptevents(SEvent* ehead);
|
|
static void freetable(Table* t);
|
|
static Map* getmap(Docinfo* di, Rune* name);
|
|
static Rune* getpcdata(Token* toks, int tokslen, int* ptoki);
|
|
static Pstate* lastps(Pstate* psl);
|
|
static Rune* listmark(uchar ty, int n);
|
|
static int listtyval(Token* tok, int dflt);
|
|
static Align makealign(int halign, int valign);
|
|
static Background makebackground(Rune* imgurl, int color);
|
|
static Dimen makedimen(int kind, int spec);
|
|
static Anchor* newanchor(int index, Rune* name, Rune* href, int target, Anchor* link);
|
|
static Area* newarea(int shape, Rune* href, int target, Area* link);
|
|
static DestAnchor* newdestanchor(int index, Rune* name, Item* item, DestAnchor* link);
|
|
static Docinfo* newdocinfo(void);
|
|
static Genattr* newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events);
|
|
static Form* newform(int formid, Rune* name, Rune* action,
|
|
int target, int method, Form* link);
|
|
static Formfield* newformfield(int ftype, int fieldid, Form* form, Rune* name,
|
|
Rune* value, int size, int maxlength, Formfield* link);
|
|
static Item* newifloat(Item* it, int side);
|
|
static Item* newiformfield(Formfield* ff);
|
|
static Item* newiimage(Rune* src, Rune* altrep, int align, int width, int height,
|
|
int hspace, int vspace, int border, int ismap, Map* map);
|
|
static Item* newirule(int align, int size, int noshade, Dimen wspec);
|
|
static Item* newispacer(int spkind);
|
|
static Item* newitable(Table* t);
|
|
static ItemSource* newitemsource(Docinfo* di);
|
|
static Item* newitext(Rune* s, int fnt, int fg, int voff, int ul);
|
|
static Kidinfo* newkidinfo(int isframeset, Kidinfo* link);
|
|
static Option* newoption(int selected, Rune* value, Rune* display, Option* link);
|
|
static Pstate* newpstate(Pstate* link);
|
|
static SEvent* newscriptevent(int type, Rune* script, SEvent* link);
|
|
static Table* newtable(int tableid, Align align, Dimen width, int border,
|
|
int cellspacing, int cellpadding, Background bg, Token* tok, Table* link);
|
|
static Tablecell* newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec,
|
|
int hspec, Background bg, int flags, Tablecell* link);
|
|
static Tablerow* newtablerow(Align align, Background bg, int flags, Tablerow* link);
|
|
static Dimen parsedim(Rune* s, int ns);
|
|
static void pop(Stack* stk);
|
|
static void popfontsize(Pstate* ps);
|
|
static void popfontstyle(Pstate* ps);
|
|
static void popjust(Pstate* ps);
|
|
static int popretnewtop(Stack* stk, int dflt);
|
|
static int push(Stack* stk, int val);
|
|
static void pushfontsize(Pstate* ps, int sz);
|
|
static void pushfontstyle(Pstate* ps, int sty);
|
|
static void pushjust(Pstate* ps, int j);
|
|
static Item* textit(Pstate* ps, Rune* s);
|
|
static Rune* removeallwhite(Rune* s);
|
|
static void resetdocinfo(Docinfo* d);
|
|
static void setcurfont(Pstate* ps);
|
|
static void setcurjust(Pstate* ps);
|
|
static void setdimarray(Token* tok, int attid, Dimen** pans, int* panslen);
|
|
static Rune* stringalign(int a);
|
|
static void targetmapinit(void);
|
|
static int toint(Rune* s);
|
|
static int top(Stack* stk, int dflt);
|
|
static void trim_cell(Tablecell* c);
|
|
static int validalign(Align a);
|
|
static int validdimen(Dimen d);
|
|
static int validformfield(Formfield* f);
|
|
static int validhalign(int a);
|
|
static int validptr(void* p);
|
|
static int validStr(Rune* s);
|
|
static int validtable(Table* t);
|
|
static int validtablerow(Tablerow* r);
|
|
static int validtablecol(Tablecol* c);
|
|
static int validtablecell(Tablecell* c);
|
|
static int validvalign(int a);
|
|
static int Iconv(Fmt *f);
|
|
|
|
static void
|
|
buildinit(void)
|
|
{
|
|
runetabinit();
|
|
roman = cvtstringtab(_roman, nelem(_roman));
|
|
color_tab = cvtstringinttab(_color_tab, nelem(_color_tab));
|
|
method_tab = cvtstringinttab(_method_tab, nelem(_method_tab));
|
|
shape_tab = cvtstringinttab(_shape_tab, nelem(_shape_tab));
|
|
fscroll_tab = cvtstringinttab(_fscroll_tab, nelem(_fscroll_tab));
|
|
clear_tab = cvtstringinttab(_clear_tab, nelem(_clear_tab));
|
|
input_tab = cvtstringinttab(_input_tab, nelem(_input_tab));
|
|
align_tab = cvtstringinttab(_align_tab, nelem(_align_tab));
|
|
|
|
fmtinstall('I', Iconv);
|
|
targetmapinit();
|
|
buildinited = 1;
|
|
}
|
|
|
|
static ItemSource*
|
|
newitemsource(Docinfo* di)
|
|
{
|
|
ItemSource* is;
|
|
Pstate* ps;
|
|
|
|
ps = newpstate(nil);
|
|
if(di->mediatype != TextHtml) {
|
|
ps->curstate &= ~IFwrap;
|
|
ps->literal = 1;
|
|
pushfontstyle(ps, FntT);
|
|
}
|
|
is = (ItemSource*)emalloc(sizeof(ItemSource));
|
|
is->doc = di;
|
|
is->psstk = ps;
|
|
is->nforms = 0;
|
|
is->ntables = 0;
|
|
is->nanchors = 0;
|
|
is->nframes = 0;
|
|
is->curform = nil;
|
|
is->curmap = nil;
|
|
is->tabstk = nil;
|
|
is->kidstk = nil;
|
|
return is;
|
|
}
|
|
|
|
static Item *getitems(ItemSource* is, uchar* data, int datalen);
|
|
|
|
// Parse an html document and create a list of layout items.
|
|
// Allocate and return document info in *pdi.
|
|
// When caller is done with the items, it should call
|
|
// freeitems on the returned result, and then
|
|
// freedocinfo(*pdi).
|
|
Item*
|
|
parsehtml(uchar* data, int datalen, Rune* pagesrc, int mtype, int chset, Docinfo** pdi)
|
|
{
|
|
Item *it;
|
|
Docinfo* di;
|
|
ItemSource* is;
|
|
|
|
di = newdocinfo();
|
|
di->src = _Strdup(pagesrc);
|
|
di->base = _Strdup(pagesrc);
|
|
di->mediatype = mtype;
|
|
di->chset = chset;
|
|
*pdi = di;
|
|
is = newitemsource(di);
|
|
it = getitems(is, data, datalen);
|
|
freepstatestack(is->psstk);
|
|
free(is);
|
|
return it;
|
|
}
|
|
|
|
// Get a group of tokens for lexer, parse them, and create
|
|
// a list of layout items.
|
|
// When caller is done with the items, it should call
|
|
// freeitems on the returned result.
|
|
static Item*
|
|
getitems(ItemSource* is, uchar* data, int datalen)
|
|
{
|
|
int i;
|
|
int j;
|
|
int nt;
|
|
int pt;
|
|
int doscripts;
|
|
int tokslen;
|
|
int toki;
|
|
int h;
|
|
int sz;
|
|
int method;
|
|
int n;
|
|
int nblank;
|
|
int norsz;
|
|
int bramt;
|
|
int sty;
|
|
int nosh;
|
|
int oldcuranchor;
|
|
int dfltbd;
|
|
int v;
|
|
int hang;
|
|
int isempty;
|
|
int tag;
|
|
int brksp;
|
|
int target;
|
|
uchar brk;
|
|
uchar flags;
|
|
uchar align;
|
|
uchar al;
|
|
uchar ty;
|
|
uchar ty2;
|
|
Pstate* ps;
|
|
Pstate* nextps;
|
|
Pstate* outerps;
|
|
Table* curtab;
|
|
Token* tok;
|
|
Token* toks;
|
|
Docinfo* di;
|
|
Item* ans;
|
|
Item* img;
|
|
Item* ffit;
|
|
Item* tabitem;
|
|
Rune* s;
|
|
Rune* t;
|
|
Rune* name;
|
|
Rune* enctype;
|
|
Rune* usemap;
|
|
Rune* prompt;
|
|
Rune* equiv;
|
|
Rune* val;
|
|
Rune* nsz;
|
|
Rune* script;
|
|
Map* map;
|
|
Form* frm;
|
|
Iimage* ii;
|
|
Kidinfo* kd;
|
|
Kidinfo* ks;
|
|
Kidinfo* pks;
|
|
Dimen wd;
|
|
Option* option;
|
|
Table* tab;
|
|
Tablecell* c;
|
|
Tablerow* tr;
|
|
Formfield* field;
|
|
Formfield* ff;
|
|
Rune* href;
|
|
Rune* src;
|
|
Rune* scriptsrc;
|
|
Rune* bgurl;
|
|
Rune* action;
|
|
Background bg;
|
|
|
|
if(!buildinited)
|
|
buildinit();
|
|
doscripts = 0; // for now
|
|
ps = is->psstk;
|
|
curtab = is->tabstk;
|
|
di = is->doc;
|
|
toks = _gettoks(data, datalen, di->chset, di->mediatype, &tokslen);
|
|
toki = 0;
|
|
for(; toki < tokslen; toki++) {
|
|
tok = &toks[toki];
|
|
if(dbgbuild > 1)
|
|
fprint(2, "build: curstate %ux, token %T\n", ps->curstate, tok);
|
|
tag = tok->tag;
|
|
brk = 0;
|
|
brksp = 0;
|
|
if(tag < Numtags) {
|
|
brk = blockbrk[tag];
|
|
if(brk&SPBefore)
|
|
brksp = 1;
|
|
}
|
|
else if(tag < Numtags + RBRA) {
|
|
brk = blockbrk[tag - RBRA];
|
|
if(brk&SPAfter)
|
|
brksp = 1;
|
|
}
|
|
if(brk) {
|
|
addbrk(ps, brksp, 0);
|
|
if(ps->inpar) {
|
|
popjust(ps);
|
|
ps->inpar = 0;
|
|
}
|
|
}
|
|
// check common case first (Data), then switch statement on tag
|
|
if(tag == Data) {
|
|
// Lexing didn't pay attention to SGML record boundary rules:
|
|
// \n after start tag or before end tag to be discarded.
|
|
// (Lex has already discarded all \r's).
|
|
// Some pages assume this doesn't happen in <PRE> text,
|
|
// so we won't do it if literal is true.
|
|
// BUG: won't discard \n before a start tag that begins
|
|
// the next bufferful of tokens.
|
|
s = tok->text;
|
|
n = _Strlen(s);
|
|
if(!ps->literal) {
|
|
i = 0;
|
|
j = n;
|
|
if(toki > 0) {
|
|
pt = toks[toki - 1].tag;
|
|
// IE and Netscape both ignore this rule (contrary to spec)
|
|
// if previous tag was img
|
|
if(pt < Numtags && pt != Timg && j > 0 && s[0] == '\n')
|
|
i++;
|
|
}
|
|
if(toki < tokslen - 1) {
|
|
nt = toks[toki + 1].tag;
|
|
if(nt >= RBRA && nt < Numtags + RBRA && j > i && s[j - 1] == '\n')
|
|
j--;
|
|
}
|
|
if(i > 0 || j < n) {
|
|
t = s;
|
|
s = _Strsubstr(s, i, j);
|
|
free(t);
|
|
n = j-i;
|
|
}
|
|
}
|
|
if(ps->skipwhite) {
|
|
_trimwhite(s, n, &t, &nt);
|
|
if(t == nil) {
|
|
free(s);
|
|
s = nil;
|
|
}
|
|
else if(t != s) {
|
|
t = _Strndup(t, nt);
|
|
free(s);
|
|
s = t;
|
|
}
|
|
if(s != nil)
|
|
ps->skipwhite = 0;
|
|
}
|
|
tok->text = nil; // token doesn't own string anymore
|
|
if(s != nil)
|
|
addtext(ps, s);
|
|
}
|
|
else
|
|
switch(tag) {
|
|
// Some abbrevs used in following DTD comments
|
|
// %text = #PCDATA
|
|
// | TT | I | B | U | STRIKE | BIG | SMALL | SUB | SUP
|
|
// | EM | STRONG | DFN | CODE | SAMP | KBD | VAR | CITE
|
|
// | A | IMG | APPLET | FONT | BASEFONT | BR | SCRIPT | MAP
|
|
// | INPUT | SELECT | TEXTAREA
|
|
// %block = P | UL | OL | DIR | MENU | DL | PRE | DL | DIV | CENTER
|
|
// | BLOCKQUOTE | FORM | ISINDEX | HR | TABLE
|
|
// %flow = (%text | %block)*
|
|
// %body.content = (%heading | %text | %block | ADDRESS)*
|
|
|
|
// <!ELEMENT A - - (%text) -(A)>
|
|
// Anchors are not supposed to be nested, but you sometimes see
|
|
// href anchors inside destination anchors.
|
|
case Ta:
|
|
if(ps->curanchor != 0) {
|
|
if(warn)
|
|
fprint(2, "warning: nested <A> or missing </A>\n");
|
|
ps->curanchor = 0;
|
|
}
|
|
name = aval(tok, Aname);
|
|
href = aurlval(tok, Ahref, nil, di->base);
|
|
// ignore rel, rev, and title attrs
|
|
if(href != nil) {
|
|
target = atargval(tok, di->target);
|
|
di->anchors = newanchor(++is->nanchors, name, href, target, di->anchors);
|
|
if(name != nil)
|
|
name = _Strdup(name); // for DestAnchor construction, below
|
|
ps->curanchor = is->nanchors;
|
|
ps->curfg = push(&ps->fgstk, di->link);
|
|
ps->curul = push(&ps->ulstk, ULunder);
|
|
}
|
|
if(name != nil) {
|
|
// add a null item to be destination
|
|
additem(ps, newispacer(ISPnull), tok);
|
|
di->dests = newdestanchor(++is->nanchors, name, ps->lastit, di->dests);
|
|
}
|
|
break;
|
|
|
|
case Ta+RBRA :
|
|
if(ps->curanchor != 0) {
|
|
ps->curfg = popretnewtop(&ps->fgstk, di->text);
|
|
ps->curul = popretnewtop(&ps->ulstk, ULnone);
|
|
ps->curanchor = 0;
|
|
}
|
|
break;
|
|
|
|
// <!ELEMENT APPLET - - (PARAM | %text)* >
|
|
// We can't do applets, so ignore PARAMS, and let
|
|
// the %text contents appear for the alternative rep
|
|
case Tapplet:
|
|
case Tapplet+RBRA:
|
|
if(warn && tag == Tapplet)
|
|
fprint(2, "warning: <APPLET> ignored\n");
|
|
break;
|
|
|
|
// <!ELEMENT AREA - O EMPTY>
|
|
case Tarea:
|
|
map = di->maps;
|
|
if(map == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <AREA> not inside <MAP>\n");
|
|
continue;
|
|
}
|
|
map->areas = newarea(atabval(tok, Ashape, shape_tab, NSHAPETAB, SHrect),
|
|
aurlval(tok, Ahref, nil, di->base),
|
|
atargval(tok, di->target),
|
|
map->areas);
|
|
setdimarray(tok, Acoords, &map->areas->coords, &map->areas->ncoords);
|
|
break;
|
|
|
|
// <!ELEMENT (B|STRONG) - - (%text)*>
|
|
case Tb:
|
|
case Tstrong:
|
|
pushfontstyle(ps, FntB);
|
|
break;
|
|
|
|
case Tb+RBRA:
|
|
case Tcite+RBRA:
|
|
case Tcode+RBRA:
|
|
case Tdfn+RBRA:
|
|
case Tem+RBRA:
|
|
case Tkbd+RBRA:
|
|
case Ti+RBRA:
|
|
case Tsamp+RBRA:
|
|
case Tstrong+RBRA:
|
|
case Ttt+RBRA:
|
|
case Tvar+RBRA :
|
|
case Taddress+RBRA:
|
|
popfontstyle(ps);
|
|
break;
|
|
|
|
// <!ELEMENT BASE - O EMPTY>
|
|
case Tbase:
|
|
t = di->base;
|
|
di->base = aurlval(tok, Ahref, di->base, di->base);
|
|
if(t != nil)
|
|
free(t);
|
|
di->target = atargval(tok, di->target);
|
|
break;
|
|
|
|
// <!ELEMENT BASEFONT - O EMPTY>
|
|
case Tbasefont:
|
|
ps->adjsize = aintval(tok, Asize, 3) - 3;
|
|
break;
|
|
|
|
// <!ELEMENT (BIG|SMALL) - - (%text)*>
|
|
case Tbig:
|
|
case Tsmall:
|
|
sz = ps->adjsize;
|
|
if(tag == Tbig)
|
|
sz += Large;
|
|
else
|
|
sz += Small;
|
|
pushfontsize(ps, sz);
|
|
break;
|
|
|
|
case Tbig+RBRA:
|
|
case Tsmall+RBRA:
|
|
popfontsize(ps);
|
|
break;
|
|
|
|
// <!ELEMENT BLOCKQUOTE - - %body.content>
|
|
case Tblockquote:
|
|
changeindent(ps, BQTAB);
|
|
break;
|
|
|
|
case Tblockquote+RBRA:
|
|
changeindent(ps, -BQTAB);
|
|
break;
|
|
|
|
// <!ELEMENT BODY O O %body.content>
|
|
case Tbody:
|
|
ps->skipping = 0;
|
|
bg = makebackground(nil, acolorval(tok, Abgcolor, di->background.color));
|
|
bgurl = aurlval(tok, Abackground, nil, di->base);
|
|
if(bgurl != nil) {
|
|
if(di->backgrounditem != nil)
|
|
freeitem((Item*)di->backgrounditem);
|
|
// really should remove old item from di->images list,
|
|
// but there should only be one BODY element ...
|
|
di->backgrounditem = (Iimage*)newiimage(bgurl, nil, ALnone, 0, 0, 0, 0, 0, 0, nil);
|
|
di->backgrounditem->nextimage = di->images;
|
|
di->images = di->backgrounditem;
|
|
}
|
|
ps->curbg = bg;
|
|
di->background = bg;
|
|
di->text = acolorval(tok, Atext, di->text);
|
|
di->link = acolorval(tok, Alink, di->link);
|
|
di->vlink = acolorval(tok, Avlink, di->vlink);
|
|
di->alink = acolorval(tok, Aalink, di->alink);
|
|
if(di->text != ps->curfg) {
|
|
ps->curfg = di->text;
|
|
ps->fgstk.n = 0;
|
|
}
|
|
break;
|
|
|
|
case Tbody+RBRA:
|
|
// HTML spec says ignore things after </body>,
|
|
// but IE and Netscape don't
|
|
// ps.skipping = 1;
|
|
break;
|
|
|
|
// <!ELEMENT BR - O EMPTY>
|
|
case Tbr:
|
|
addlinebrk(ps, atabval(tok, Aclear, clear_tab, NCLEARTAB, 0));
|
|
break;
|
|
|
|
// <!ELEMENT CAPTION - - (%text;)*>
|
|
case Tcaption:
|
|
if(curtab == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <CAPTION> outside <TABLE>\n");
|
|
continue;
|
|
}
|
|
if(curtab->caption != nil) {
|
|
if(warn)
|
|
fprint(2, "warning: more than one <CAPTION> in <TABLE>\n");
|
|
continue;
|
|
}
|
|
ps = newpstate(ps);
|
|
curtab->caption_place = atabval(tok, Aalign, align_tab, NALIGNTAB, ALtop);
|
|
break;
|
|
|
|
case Tcaption+RBRA:
|
|
nextps = ps->next;
|
|
if(curtab == nil || nextps == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </CAPTION>\n");
|
|
continue;
|
|
}
|
|
curtab->caption = ps->items->next;
|
|
free(ps);
|
|
ps = nextps;
|
|
break;
|
|
|
|
case Tcenter:
|
|
case Tdiv:
|
|
if(tag == Tcenter)
|
|
al = ALcenter;
|
|
else
|
|
al = atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust);
|
|
pushjust(ps, al);
|
|
break;
|
|
|
|
case Tcenter+RBRA:
|
|
case Tdiv+RBRA:
|
|
popjust(ps);
|
|
break;
|
|
|
|
// <!ELEMENT DD - O %flow >
|
|
case Tdd:
|
|
if(ps->hangstk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "warning: <DD> not inside <DL\n");
|
|
continue;
|
|
}
|
|
h = top(&ps->hangstk, 0);
|
|
if(h != 0)
|
|
changehang(ps, -10*LISTTAB);
|
|
else
|
|
addbrk(ps, 0, 0);
|
|
push(&ps->hangstk, 0);
|
|
break;
|
|
|
|
//<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) >
|
|
//<!ELEMENT (OL|UL) - - (LI)+>
|
|
case Tdir:
|
|
case Tmenu:
|
|
case Tol:
|
|
case Tul:
|
|
changeindent(ps, LISTTAB);
|
|
push(&ps->listtypestk, listtyval(tok, (tag==Tol)? LT1 : LTdisc));
|
|
push(&ps->listcntstk, aintval(tok, Astart, 1));
|
|
break;
|
|
|
|
case Tdir+RBRA:
|
|
case Tmenu+RBRA:
|
|
case Tol+RBRA:
|
|
case Tul+RBRA:
|
|
if(ps->listtypestk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "warning: %T ended no list\n", tok);
|
|
continue;
|
|
}
|
|
addbrk(ps, 0, 0);
|
|
pop(&ps->listtypestk);
|
|
pop(&ps->listcntstk);
|
|
changeindent(ps, -LISTTAB);
|
|
break;
|
|
|
|
// <!ELEMENT DL - - (DT|DD)+ >
|
|
case Tdl:
|
|
changeindent(ps, LISTTAB);
|
|
push(&ps->hangstk, 0);
|
|
break;
|
|
|
|
case Tdl+RBRA:
|
|
if(ps->hangstk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </DL>\n");
|
|
continue;
|
|
}
|
|
changeindent(ps, -LISTTAB);
|
|
if(top(&ps->hangstk, 0) != 0)
|
|
changehang(ps, -10*LISTTAB);
|
|
pop(&ps->hangstk);
|
|
break;
|
|
|
|
// <!ELEMENT DT - O (%text)* >
|
|
case Tdt:
|
|
if(ps->hangstk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "warning: <DT> not inside <DL>\n");
|
|
continue;
|
|
}
|
|
h = top(&ps->hangstk, 0);
|
|
pop(&ps->hangstk);
|
|
if(h != 0)
|
|
changehang(ps, -10*LISTTAB);
|
|
changehang(ps, 10*LISTTAB);
|
|
push(&ps->hangstk, 1);
|
|
break;
|
|
|
|
// <!ELEMENT FONT - - (%text)*>
|
|
case Tfont:
|
|
sz = top(&ps->fntsizestk, Normal);
|
|
if(_tokaval(tok, Asize, &nsz, 0)) {
|
|
if(_prefix(L(Lplus), nsz))
|
|
sz = Normal + _Strtol(nsz+1, nil, 10) + ps->adjsize;
|
|
else if(_prefix(L(Lminus), nsz))
|
|
sz = Normal - _Strtol(nsz+1, nil, 10) + ps->adjsize;
|
|
else if(nsz != nil)
|
|
sz = Normal + (_Strtol(nsz, nil, 10) - 3);
|
|
}
|
|
ps->curfg = push(&ps->fgstk, acolorval(tok, Acolor, ps->curfg));
|
|
pushfontsize(ps, sz);
|
|
break;
|
|
|
|
case Tfont+RBRA:
|
|
if(ps->fgstk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </FONT>\n");
|
|
continue;
|
|
}
|
|
ps->curfg = popretnewtop(&ps->fgstk, di->text);
|
|
popfontsize(ps);
|
|
break;
|
|
|
|
// <!ELEMENT FORM - - %body.content -(FORM) >
|
|
case Tform:
|
|
if(is->curform != nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <FORM> nested inside another\n");
|
|
continue;
|
|
}
|
|
action = aurlval(tok, Aaction, di->base, di->base);
|
|
s = aval(tok, Aid);
|
|
name = astrval(tok, Aname, s);
|
|
if(s)
|
|
free(s);
|
|
target = atargval(tok, di->target);
|
|
method = atabval(tok, Amethod, method_tab, NMETHODTAB, HGet);
|
|
if(warn && _tokaval(tok, Aenctype, &enctype, 0) &&
|
|
_Strcmp(enctype, L(Lappl_form)))
|
|
fprint(2, "form enctype %S not handled\n", enctype);
|
|
frm = newform(++is->nforms, name, action, target, method, di->forms);
|
|
di->forms = frm;
|
|
is->curform = frm;
|
|
break;
|
|
|
|
case Tform+RBRA:
|
|
if(is->curform == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </FORM>\n");
|
|
continue;
|
|
}
|
|
// put fields back in input order
|
|
is->curform->fields = (Formfield*)_revlist((List*)is->curform->fields);
|
|
is->curform = nil;
|
|
break;
|
|
|
|
// <!ELEMENT FRAME - O EMPTY>
|
|
case Tframe:
|
|
ks = is->kidstk;
|
|
if(ks == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <FRAME> not in <FRAMESET>\n");
|
|
continue;
|
|
}
|
|
ks->kidinfos = kd = newkidinfo(0, ks->kidinfos);
|
|
kd->src = aurlval(tok, Asrc, nil, di->base);
|
|
kd->name = aval(tok, Aname);
|
|
if(kd->name == nil) {
|
|
s = _ltoStr(++is->nframes);
|
|
kd->name = _Strdup2(L(Lfr), s);
|
|
free(s);
|
|
}
|
|
kd->marginw = auintval(tok, Amarginwidth, 0);
|
|
kd->marginh = auintval(tok, Amarginheight, 0);
|
|
kd->framebd = auintval(tok, Aframeborder, 1);
|
|
kd->flags = atabval(tok, Ascrolling, fscroll_tab, NFSCROLLTAB, kd->flags);
|
|
norsz = aflagval(tok, Anoresize);
|
|
if(norsz)
|
|
kd->flags |= FRnoresize;
|
|
break;
|
|
|
|
// <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+>
|
|
case Tframeset:
|
|
ks = newkidinfo(1, nil);
|
|
pks = is->kidstk;
|
|
if(pks == nil)
|
|
di->kidinfo = ks;
|
|
else {
|
|
ks->next = pks->kidinfos;
|
|
pks->kidinfos = ks;
|
|
}
|
|
ks->nextframeset = pks;
|
|
is->kidstk = ks;
|
|
setdimarray(tok, Arows, &ks->rows, &ks->nrows);
|
|
if(ks->nrows == 0) {
|
|
ks->rows = (Dimen*)emalloc(sizeof(Dimen));
|
|
ks->nrows = 1;
|
|
ks->rows[0] = makedimen(Dpercent, 100);
|
|
}
|
|
setdimarray(tok, Acols, &ks->cols, &ks->ncols);
|
|
if(ks->ncols == 0) {
|
|
ks->cols = (Dimen*)emalloc(sizeof(Dimen));
|
|
ks->ncols = 1;
|
|
ks->cols[0] = makedimen(Dpercent, 100);
|
|
}
|
|
break;
|
|
|
|
case Tframeset+RBRA:
|
|
if(is->kidstk == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </FRAMESET>\n");
|
|
continue;
|
|
}
|
|
ks = is->kidstk;
|
|
// put kids back in original order
|
|
// and add blank frames to fill out cells
|
|
n = ks->nrows*ks->ncols;
|
|
nblank = n - _listlen((List*)ks->kidinfos);
|
|
while(nblank-- > 0)
|
|
ks->kidinfos = newkidinfo(0, ks->kidinfos);
|
|
ks->kidinfos = (Kidinfo*)_revlist((List*)ks->kidinfos);
|
|
is->kidstk = is->kidstk->nextframeset;
|
|
if(is->kidstk == nil) {
|
|
// end input
|
|
ans = nil;
|
|
goto return_ans;
|
|
}
|
|
break;
|
|
|
|
// <!ELEMENT H1 - - (%text;)*>, etc.
|
|
case Th1:
|
|
case Th2:
|
|
case Th3:
|
|
case Th4:
|
|
case Th5:
|
|
case Th6:
|
|
bramt = 1;
|
|
if(ps->items == ps->lastit)
|
|
bramt = 0;
|
|
addbrk(ps, bramt, IFcleft|IFcright);
|
|
sz = Verylarge - (tag - Th1);
|
|
if(sz < Tiny)
|
|
sz = Tiny;
|
|
pushfontsize(ps, sz);
|
|
sty = top(&ps->fntstylestk, FntR);
|
|
if(tag == Th1)
|
|
sty = FntB;
|
|
pushfontstyle(ps, sty);
|
|
pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
|
|
ps->skipwhite = 1;
|
|
break;
|
|
|
|
case Th1+RBRA:
|
|
case Th2+RBRA:
|
|
case Th3+RBRA:
|
|
case Th4+RBRA:
|
|
case Th5+RBRA:
|
|
case Th6+RBRA:
|
|
addbrk(ps, 1, IFcleft|IFcright);
|
|
popfontsize(ps);
|
|
popfontstyle(ps);
|
|
popjust(ps);
|
|
break;
|
|
|
|
case Thead:
|
|
// HTML spec says ignore regular markup in head,
|
|
// but Netscape and IE don't
|
|
// ps.skipping = 1;
|
|
break;
|
|
|
|
case Thead+RBRA:
|
|
ps->skipping = 0;
|
|
break;
|
|
|
|
// <!ELEMENT HR - O EMPTY>
|
|
case Thr:
|
|
al = atabval(tok, Aalign, align_tab, NALIGNTAB, ALcenter);
|
|
sz = auintval(tok, Asize, HRSZ);
|
|
wd = adimen(tok, Awidth);
|
|
if(dimenkind(wd) == Dnone)
|
|
wd = makedimen(Dpercent, 100);
|
|
nosh = aflagval(tok, Anoshade);
|
|
additem(ps, newirule(al, sz, nosh, wd), tok);
|
|
addbrk(ps, 0, 0);
|
|
break;
|
|
|
|
case Ti:
|
|
case Tcite:
|
|
case Tdfn:
|
|
case Tem:
|
|
case Tvar:
|
|
case Taddress:
|
|
pushfontstyle(ps, FntI);
|
|
break;
|
|
|
|
// <!ELEMENT IMG - O EMPTY>
|
|
case Timg:
|
|
map = nil;
|
|
oldcuranchor = ps->curanchor;
|
|
if(_tokaval(tok, Ausemap, &usemap, 0)) {
|
|
if(!_prefix(L(Lhash), usemap)) {
|
|
if(warn)
|
|
fprint(2, "warning: can't handle non-local map %S\n", usemap);
|
|
}
|
|
else {
|
|
map = getmap(di, usemap+1);
|
|
if(ps->curanchor == 0) {
|
|
di->anchors = newanchor(++is->nanchors, nil, nil, di->target, di->anchors);
|
|
ps->curanchor = is->nanchors;
|
|
}
|
|
}
|
|
}
|
|
align = atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom);
|
|
dfltbd = 0;
|
|
if(ps->curanchor != 0)
|
|
dfltbd = 2;
|
|
src = aurlval(tok, Asrc, nil, di->base);
|
|
if(src == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <img> has no src attribute\n");
|
|
ps->curanchor = oldcuranchor;
|
|
continue;
|
|
}
|
|
img = newiimage(src,
|
|
aval(tok, Aalt),
|
|
align,
|
|
auintval(tok, Awidth, 0),
|
|
auintval(tok, Aheight, 0),
|
|
auintval(tok, Ahspace, IMGHSPACE),
|
|
auintval(tok, Avspace, IMGVSPACE),
|
|
auintval(tok, Aborder, dfltbd),
|
|
aflagval(tok, Aismap),
|
|
map);
|
|
if(align == ALleft || align == ALright) {
|
|
additem(ps, newifloat(img, align), tok);
|
|
// if no hspace specified, use FLTIMGHSPACE
|
|
if(!_tokaval(tok, Ahspace, &val, 0))
|
|
((Iimage*)img)->hspace = FLTIMGHSPACE;
|
|
}
|
|
else {
|
|
ps->skipwhite = 0;
|
|
additem(ps, img, tok);
|
|
}
|
|
if(!ps->skipping) {
|
|
((Iimage*)img)->nextimage = di->images;
|
|
di->images = (Iimage*)img;
|
|
}
|
|
ps->curanchor = oldcuranchor;
|
|
break;
|
|
|
|
// <!ELEMENT INPUT - O EMPTY>
|
|
case Tinput:
|
|
ps->skipwhite = 0;
|
|
if(is->curform == nil) {
|
|
if(warn)
|
|
fprint(2, "<INPUT> not inside <FORM>\n");
|
|
continue;
|
|
}
|
|
is->curform->fields = field = newformfield(
|
|
atabval(tok, Atype, input_tab, NINPUTTAB, Ftext),
|
|
++is->curform->nfields,
|
|
is->curform,
|
|
aval(tok, Aname),
|
|
aval(tok, Avalue),
|
|
auintval(tok, Asize, 0),
|
|
auintval(tok, Amaxlength, 1000),
|
|
is->curform->fields);
|
|
if(aflagval(tok, Achecked))
|
|
field->flags = FFchecked;
|
|
|
|
switch(field->ftype) {
|
|
case Ftext:
|
|
case Fpassword:
|
|
case Ffile:
|
|
if(field->size == 0)
|
|
field->size = 20;
|
|
break;
|
|
|
|
case Fcheckbox:
|
|
if(field->name == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: checkbox form field missing name\n");
|
|
continue;
|
|
}
|
|
if(field->value == nil)
|
|
field->value = _Strdup(L(Lone));
|
|
break;
|
|
|
|
case Fradio:
|
|
if(field->name == nil || field->value == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: radio form field missing name or value\n");
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case Fsubmit:
|
|
if(field->value == nil)
|
|
field->value = _Strdup(L(Lsubmit));
|
|
if(field->name == nil)
|
|
field->name = _Strdup(L(Lnoname));
|
|
break;
|
|
|
|
case Fimage:
|
|
src = aurlval(tok, Asrc, nil, di->base);
|
|
if(src == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: image form field missing src\n");
|
|
continue;
|
|
}
|
|
// width and height attrs aren't specified in HTML 3.2,
|
|
// but some people provide them and they help avoid
|
|
// a relayout
|
|
field->image = newiimage(src,
|
|
astrval(tok, Aalt, L(Lsubmit)),
|
|
atabval(tok, Aalign, align_tab, NALIGNTAB, ALbottom),
|
|
auintval(tok, Awidth, 0), auintval(tok, Aheight, 0),
|
|
0, 0, 0, 0, nil);
|
|
ii = (Iimage*)field->image;
|
|
ii->nextimage = di->images;
|
|
di->images = ii;
|
|
break;
|
|
|
|
case Freset:
|
|
if(field->value == nil)
|
|
field->value = _Strdup(L(Lreset));
|
|
break;
|
|
|
|
case Fbutton:
|
|
if(field->value == nil)
|
|
field->value = _Strdup(L(Lspace));
|
|
break;
|
|
}
|
|
ffit = newiformfield(field);
|
|
additem(ps, ffit, tok);
|
|
if(ffit->genattr != nil)
|
|
field->events = ffit->genattr->events;
|
|
break;
|
|
|
|
// <!ENTITY ISINDEX - O EMPTY>
|
|
case Tisindex:
|
|
ps->skipwhite = 0;
|
|
prompt = astrval(tok, Aprompt, L(Lindex));
|
|
target = atargval(tok, di->target);
|
|
additem(ps, textit(ps, prompt), tok);
|
|
frm = newform(++is->nforms,
|
|
nil,
|
|
di->base,
|
|
target,
|
|
HGet,
|
|
di->forms);
|
|
di->forms = frm;
|
|
ff = newformfield(Ftext,
|
|
1,
|
|
frm,
|
|
_Strdup(L(Lisindex)),
|
|
nil,
|
|
50,
|
|
1000,
|
|
nil);
|
|
frm->fields = ff;
|
|
frm->nfields = 1;
|
|
additem(ps, newiformfield(ff), tok);
|
|
addbrk(ps, 1, 0);
|
|
break;
|
|
|
|
// <!ELEMENT LI - O %flow>
|
|
case Tli:
|
|
if(ps->listtypestk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "<LI> not in list\n");
|
|
continue;
|
|
}
|
|
ty = top(&ps->listtypestk, 0);
|
|
ty2 = listtyval(tok, ty);
|
|
if(ty != ty2) {
|
|
ty = ty2;
|
|
push(&ps->listtypestk, ty2);
|
|
}
|
|
v = aintval(tok, Avalue, top(&ps->listcntstk, 1));
|
|
if(ty == LTdisc || ty == LTsquare || ty == LTcircle)
|
|
hang = 10*LISTTAB - 3;
|
|
else
|
|
hang = 10*LISTTAB - 1;
|
|
changehang(ps, hang);
|
|
addtext(ps, listmark(ty, v));
|
|
push(&ps->listcntstk, v + 1);
|
|
changehang(ps, -hang);
|
|
ps->skipwhite = 1;
|
|
break;
|
|
|
|
// <!ELEMENT MAP - - (AREA)+>
|
|
case Tmap:
|
|
if(_tokaval(tok, Aname, &name, 0))
|
|
is->curmap = getmap(di, name);
|
|
break;
|
|
|
|
case Tmap+RBRA:
|
|
map = is->curmap;
|
|
if(map == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </MAP>\n");
|
|
continue;
|
|
}
|
|
map->areas = (Area*)_revlist((List*)map->areas);
|
|
break;
|
|
|
|
case Tmeta:
|
|
if(ps->skipping)
|
|
continue;
|
|
if(_tokaval(tok, Ahttp_equiv, &equiv, 0)) {
|
|
val = aval(tok, Acontent);
|
|
n = _Strlen(equiv);
|
|
if(!_Strncmpci(equiv, n, L(Lrefresh)))
|
|
di->refresh = val;
|
|
else if(!_Strncmpci(equiv, n, L(Lcontent))) {
|
|
n = _Strlen(val);
|
|
if(!_Strncmpci(val, n, L(Ljavascript))
|
|
|| !_Strncmpci(val, n, L(Ljscript1))
|
|
|| !_Strncmpci(val, n, L(Ljscript)))
|
|
di->scripttype = TextJavascript;
|
|
else {
|
|
if(warn)
|
|
fprint(2, "unimplemented script type %S\n", val);
|
|
di->scripttype = UnknownType;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web
|
|
case Tnobr:
|
|
ps->skipwhite = 0;
|
|
ps->curstate &= ~IFwrap;
|
|
break;
|
|
|
|
case Tnobr+RBRA:
|
|
ps->curstate |= IFwrap;
|
|
break;
|
|
|
|
// We do frames, so skip stuff in noframes
|
|
case Tnoframes:
|
|
ps->skipping = 1;
|
|
break;
|
|
|
|
case Tnoframes+RBRA:
|
|
ps->skipping = 0;
|
|
break;
|
|
|
|
// We do scripts (if enabled), so skip stuff in noscripts
|
|
case Tnoscript:
|
|
if(doscripts)
|
|
ps->skipping = 1;
|
|
break;
|
|
|
|
case Tnoscript+RBRA:
|
|
if(doscripts)
|
|
ps->skipping = 0;
|
|
break;
|
|
|
|
// <!ELEMENT OPTION - O ( //PCDATA)>
|
|
case Toption:
|
|
if(is->curform == nil || is->curform->fields == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <OPTION> not in <SELECT>\n");
|
|
continue;
|
|
}
|
|
field = is->curform->fields;
|
|
if(field->ftype != Fselect) {
|
|
if(warn)
|
|
fprint(2, "warning: <OPTION> not in <SELECT>\n");
|
|
continue;
|
|
}
|
|
val = aval(tok, Avalue);
|
|
option = newoption(aflagval(tok, Aselected), val, nil, field->options);
|
|
field->options = option;
|
|
option->display = getpcdata(toks, tokslen, &toki);
|
|
if(val == nil)
|
|
option->value = _Strdup(option->display);
|
|
break;
|
|
|
|
// <!ELEMENT P - O (%text)* >
|
|
case Tp:
|
|
pushjust(ps, atabval(tok, Aalign, align_tab, NALIGNTAB, ps->curjust));
|
|
ps->inpar = 1;
|
|
ps->skipwhite = 1;
|
|
break;
|
|
|
|
case Tp+RBRA:
|
|
break;
|
|
|
|
// <!ELEMENT PARAM - O EMPTY>
|
|
// Do something when we do applets...
|
|
case Tparam:
|
|
break;
|
|
|
|
// <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) >
|
|
case Tpre:
|
|
ps->curstate &= ~IFwrap;
|
|
ps->literal = 1;
|
|
ps->skipwhite = 0;
|
|
pushfontstyle(ps, FntT);
|
|
break;
|
|
|
|
case Tpre+RBRA:
|
|
ps->curstate |= IFwrap;
|
|
if(ps->literal) {
|
|
popfontstyle(ps);
|
|
ps->literal = 0;
|
|
}
|
|
break;
|
|
|
|
// <!ELEMENT SCRIPT - - CDATA>
|
|
case Tscript:
|
|
if(doscripts) {
|
|
if(!di->hasscripts) {
|
|
if(di->scripttype == TextJavascript) {
|
|
// TODO: initialize script if nec.
|
|
// initjscript(di);
|
|
di->hasscripts = 1;
|
|
}
|
|
}
|
|
}
|
|
if(!di->hasscripts) {
|
|
if(warn)
|
|
fprint(2, "warning: <SCRIPT> ignored\n");
|
|
ps->skipping = 1;
|
|
}
|
|
else {
|
|
scriptsrc = aurlval(tok, Asrc, nil, di->base);
|
|
script = nil;
|
|
if(scriptsrc != nil) {
|
|
if(warn)
|
|
fprint(2, "warning: non-local <SCRIPT> ignored\n");
|
|
free(scriptsrc);
|
|
}
|
|
else {
|
|
script = getpcdata(toks, tokslen, &toki);
|
|
}
|
|
if(script != nil) {
|
|
if(warn)
|
|
fprint(2, "script ignored\n");
|
|
free(script);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Tscript+RBRA:
|
|
ps->skipping = 0;
|
|
break;
|
|
|
|
// <!ELEMENT SELECT - - (OPTION+)>
|
|
case Tselect:
|
|
if(is->curform == nil) {
|
|
if(warn)
|
|
fprint(2, "<SELECT> not inside <FORM>\n");
|
|
continue;
|
|
}
|
|
field = newformfield(Fselect,
|
|
++is->curform->nfields,
|
|
is->curform,
|
|
aval(tok, Aname),
|
|
nil,
|
|
auintval(tok, Asize, 0),
|
|
0,
|
|
is->curform->fields);
|
|
is->curform->fields = field;
|
|
if(aflagval(tok, Amultiple))
|
|
field->flags = FFmultiple;
|
|
ffit = newiformfield(field);
|
|
additem(ps, ffit, tok);
|
|
if(ffit->genattr != nil)
|
|
field->events = ffit->genattr->events;
|
|
// throw away stuff until next tag (should be <OPTION>)
|
|
s = getpcdata(toks, tokslen, &toki);
|
|
if(s != nil)
|
|
free(s);
|
|
break;
|
|
|
|
case Tselect+RBRA:
|
|
if(is->curform == nil || is->curform->fields == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </SELECT>\n");
|
|
continue;
|
|
}
|
|
field = is->curform->fields;
|
|
if(field->ftype != Fselect)
|
|
continue;
|
|
// put options back in input order
|
|
field->options = (Option*)_revlist((List*)field->options);
|
|
break;
|
|
|
|
// <!ELEMENT (STRIKE|U) - - (%text)*>
|
|
case Tstrike:
|
|
case Tu:
|
|
ps->curul = push(&ps->ulstk, (tag==Tstrike)? ULmid : ULunder);
|
|
break;
|
|
|
|
case Tstrike+RBRA:
|
|
case Tu+RBRA:
|
|
if(ps->ulstk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected %T\n", tok);
|
|
continue;
|
|
}
|
|
ps->curul = popretnewtop(&ps->ulstk, ULnone);
|
|
break;
|
|
|
|
// <!ELEMENT STYLE - - CDATA>
|
|
case Tstyle:
|
|
if(warn)
|
|
fprint(2, "warning: unimplemented <STYLE>\n");
|
|
ps->skipping = 1;
|
|
break;
|
|
|
|
case Tstyle+RBRA:
|
|
ps->skipping = 0;
|
|
break;
|
|
|
|
// <!ELEMENT (SUB|SUP) - - (%text)*>
|
|
case Tsub:
|
|
case Tsup:
|
|
if(tag == Tsub)
|
|
ps->curvoff += SUBOFF;
|
|
else
|
|
ps->curvoff -= SUPOFF;
|
|
push(&ps->voffstk, ps->curvoff);
|
|
sz = top(&ps->fntsizestk, Normal);
|
|
pushfontsize(ps, sz - 1);
|
|
break;
|
|
|
|
case Tsub+RBRA:
|
|
case Tsup+RBRA:
|
|
if(ps->voffstk.n == 0) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected %T\n", tok);
|
|
continue;
|
|
}
|
|
ps->curvoff = popretnewtop(&ps->voffstk, 0);
|
|
popfontsize(ps);
|
|
break;
|
|
|
|
// <!ELEMENT TABLE - - (CAPTION?, TR+)>
|
|
case Ttable:
|
|
ps->skipwhite = 0;
|
|
tab = newtable(++is->ntables,
|
|
aalign(tok),
|
|
adimen(tok, Awidth),
|
|
aflagval(tok, Aborder),
|
|
auintval(tok, Acellspacing, TABSP),
|
|
auintval(tok, Acellpadding, TABPAD),
|
|
makebackground(nil, acolorval(tok, Abgcolor, ps->curbg.color)),
|
|
tok,
|
|
is->tabstk);
|
|
is->tabstk = tab;
|
|
curtab = tab;
|
|
break;
|
|
|
|
case Ttable+RBRA:
|
|
if(curtab == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </TABLE>\n");
|
|
continue;
|
|
}
|
|
isempty = (curtab->cells == nil);
|
|
if(isempty) {
|
|
if(warn)
|
|
fprint(2, "warning: <TABLE> has no cells\n");
|
|
}
|
|
else {
|
|
ps = finishcell(curtab, ps);
|
|
if(curtab->rows != nil)
|
|
curtab->rows->flags = 0;
|
|
finish_table(curtab);
|
|
}
|
|
ps->skipping = 0;
|
|
if(!isempty) {
|
|
tabitem = newitable(curtab);
|
|
al = curtab->align.halign;
|
|
switch(al) {
|
|
case ALleft:
|
|
case ALright:
|
|
additem(ps, newifloat(tabitem, al), tok);
|
|
break;
|
|
default:
|
|
if(al == ALcenter)
|
|
pushjust(ps, ALcenter);
|
|
addbrk(ps, 0, 0);
|
|
if(ps->inpar) {
|
|
popjust(ps);
|
|
ps->inpar = 0;
|
|
}
|
|
additem(ps, tabitem, curtab->tabletok);
|
|
if(al == ALcenter)
|
|
popjust(ps);
|
|
break;
|
|
}
|
|
}
|
|
if(is->tabstk == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: table stack is wrong\n");
|
|
}
|
|
else
|
|
is->tabstk = is->tabstk->next;
|
|
curtab->next = di->tables;
|
|
di->tables = curtab;
|
|
curtab = is->tabstk;
|
|
if(!isempty)
|
|
addbrk(ps, 0, 0);
|
|
break;
|
|
|
|
// <!ELEMENT (TH|TD) - O %body.content>
|
|
// Cells for a row are accumulated in reverse order.
|
|
// We push ps on a stack, and use a new one to accumulate
|
|
// the contents of the cell.
|
|
case Ttd:
|
|
case Tth:
|
|
if(curtab == nil) {
|
|
if(warn)
|
|
fprint(2, "%T outside <TABLE>\n", tok);
|
|
continue;
|
|
}
|
|
if(ps->inpar) {
|
|
popjust(ps);
|
|
ps->inpar = 0;
|
|
}
|
|
ps = finishcell(curtab, ps);
|
|
tr = nil;
|
|
if(curtab->rows != nil)
|
|
tr = curtab->rows;
|
|
if(tr == nil || !tr->flags) {
|
|
if(warn)
|
|
fprint(2, "%T outside row\n", tok);
|
|
tr = newtablerow(makealign(ALnone, ALnone),
|
|
makebackground(nil, curtab->background.color),
|
|
TFparsing,
|
|
curtab->rows);
|
|
curtab->rows = tr;
|
|
}
|
|
ps = cell_pstate(ps, tag == Tth);
|
|
flags = TFparsing;
|
|
if(aflagval(tok, Anowrap)) {
|
|
flags |= TFnowrap;
|
|
ps->curstate &= ~IFwrap;
|
|
}
|
|
if(tag == Tth)
|
|
flags |= TFisth;
|
|
c = newtablecell(curtab->cells==nil? 1 : curtab->cells->cellid+1,
|
|
auintval(tok, Arowspan, 1),
|
|
auintval(tok, Acolspan, 1),
|
|
aalign(tok),
|
|
adimen(tok, Awidth),
|
|
auintval(tok, Aheight, 0),
|
|
makebackground(nil, acolorval(tok, Abgcolor, tr->background.color)),
|
|
flags,
|
|
curtab->cells);
|
|
curtab->cells = c;
|
|
ps->curbg = c->background;
|
|
if(c->align.halign == ALnone) {
|
|
if(tr->align.halign != ALnone)
|
|
c->align.halign = tr->align.halign;
|
|
else if(tag == Tth)
|
|
c->align.halign = ALcenter;
|
|
else
|
|
c->align.halign = ALleft;
|
|
}
|
|
if(c->align.valign == ALnone) {
|
|
if(tr->align.valign != ALnone)
|
|
c->align.valign = tr->align.valign;
|
|
else
|
|
c->align.valign = ALmiddle;
|
|
}
|
|
c->nextinrow = tr->cells;
|
|
tr->cells = c;
|
|
break;
|
|
|
|
case Ttd+RBRA:
|
|
case Tth+RBRA:
|
|
if(curtab == nil || curtab->cells == nil) {
|
|
if(warn)
|
|
fprint(2, "unexpected %T\n", tok);
|
|
continue;
|
|
}
|
|
ps = finishcell(curtab, ps);
|
|
break;
|
|
|
|
// <!ELEMENT TEXTAREA - - ( //PCDATA)>
|
|
case Ttextarea:
|
|
if(is->curform == nil) {
|
|
if(warn)
|
|
fprint(2, "<TEXTAREA> not inside <FORM>\n");
|
|
continue;
|
|
}
|
|
field = newformfield(Ftextarea,
|
|
++is->curform->nfields,
|
|
is->curform,
|
|
aval(tok, Aname),
|
|
nil,
|
|
0,
|
|
0,
|
|
is->curform->fields);
|
|
is->curform->fields = field;
|
|
field->rows = auintval(tok, Arows, 3);
|
|
field->cols = auintval(tok, Acols, 50);
|
|
field->value = getpcdata(toks, tokslen, &toki);
|
|
if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttextarea + RBRA)
|
|
fprint(2, "warning: <TEXTAREA> data ended by %T\n", &toks[toki + 1]);
|
|
ffit = newiformfield(field);
|
|
additem(ps, ffit, tok);
|
|
if(ffit->genattr != nil)
|
|
field->events = ffit->genattr->events;
|
|
break;
|
|
|
|
// <!ELEMENT TITLE - - ( //PCDATA)* -(%head.misc)>
|
|
case Ttitle:
|
|
di->doctitle = getpcdata(toks, tokslen, &toki);
|
|
if(warn && toki < tokslen - 1 && toks[toki + 1].tag != Ttitle + RBRA)
|
|
fprint(2, "warning: <TITLE> data ended by %T\n", &toks[toki + 1]);
|
|
break;
|
|
|
|
// <!ELEMENT TR - O (TH|TD)+>
|
|
// rows are accumulated in reverse order in curtab->rows
|
|
case Ttr:
|
|
if(curtab == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <TR> outside <TABLE>\n");
|
|
continue;
|
|
}
|
|
if(ps->inpar) {
|
|
popjust(ps);
|
|
ps->inpar = 0;
|
|
}
|
|
ps = finishcell(curtab, ps);
|
|
if(curtab->rows != nil)
|
|
curtab->rows->flags = 0;
|
|
curtab->rows = newtablerow(aalign(tok),
|
|
makebackground(nil, acolorval(tok, Abgcolor, curtab->background.color)),
|
|
TFparsing,
|
|
curtab->rows);
|
|
break;
|
|
|
|
case Ttr+RBRA:
|
|
if(curtab == nil || curtab->rows == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: unexpected </TR>\n");
|
|
continue;
|
|
}
|
|
ps = finishcell(curtab, ps);
|
|
tr = curtab->rows;
|
|
if(tr->cells == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: empty row\n");
|
|
curtab->rows = tr->next;
|
|
tr->next = nil;
|
|
}
|
|
else
|
|
tr->flags = 0;
|
|
break;
|
|
|
|
// <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*>
|
|
case Ttt:
|
|
case Tcode:
|
|
case Tkbd:
|
|
case Tsamp:
|
|
pushfontstyle(ps, FntT);
|
|
break;
|
|
|
|
// Tags that have empty action
|
|
case Tabbr:
|
|
case Tabbr+RBRA:
|
|
case Tacronym:
|
|
case Tacronym+RBRA:
|
|
case Tarea+RBRA:
|
|
case Tbase+RBRA:
|
|
case Tbasefont+RBRA:
|
|
case Tbr+RBRA:
|
|
case Tdd+RBRA:
|
|
case Tdt+RBRA:
|
|
case Tframe+RBRA:
|
|
case Thr+RBRA:
|
|
case Thtml:
|
|
case Thtml+RBRA:
|
|
case Timg+RBRA:
|
|
case Tinput+RBRA:
|
|
case Tisindex+RBRA:
|
|
case Tli+RBRA:
|
|
case Tlink:
|
|
case Tlink+RBRA:
|
|
case Tmeta+RBRA:
|
|
case Toption+RBRA:
|
|
case Tparam+RBRA:
|
|
case Ttextarea+RBRA:
|
|
case Ttitle+RBRA:
|
|
break;
|
|
|
|
|
|
// Tags not implemented
|
|
case Tbdo:
|
|
case Tbdo+RBRA:
|
|
case Tbutton:
|
|
case Tbutton+RBRA:
|
|
case Tdel:
|
|
case Tdel+RBRA:
|
|
case Tfieldset:
|
|
case Tfieldset+RBRA:
|
|
case Tiframe:
|
|
case Tiframe+RBRA:
|
|
case Tins:
|
|
case Tins+RBRA:
|
|
case Tlabel:
|
|
case Tlabel+RBRA:
|
|
case Tlegend:
|
|
case Tlegend+RBRA:
|
|
case Tobject:
|
|
case Tobject+RBRA:
|
|
case Toptgroup:
|
|
case Toptgroup+RBRA:
|
|
case Tspan:
|
|
case Tspan+RBRA:
|
|
if(warn) {
|
|
if(tag > RBRA)
|
|
tag -= RBRA;
|
|
fprint(2, "warning: unimplemented HTML tag: %S\n", tagnames[tag]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if(warn)
|
|
fprint(2, "warning: unknown HTML tag: %S\n", tok->text);
|
|
break;
|
|
}
|
|
}
|
|
// some pages omit trailing </table>
|
|
while(curtab != nil) {
|
|
if(warn)
|
|
fprint(2, "warning: <TABLE> not closed\n");
|
|
if(curtab->cells != nil) {
|
|
ps = finishcell(curtab, ps);
|
|
if(curtab->cells == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: empty table\n");
|
|
}
|
|
else {
|
|
if(curtab->rows != nil)
|
|
curtab->rows->flags = 0;
|
|
finish_table(curtab);
|
|
ps->skipping = 0;
|
|
additem(ps, newitable(curtab), curtab->tabletok);
|
|
addbrk(ps, 0, 0);
|
|
}
|
|
}
|
|
if(is->tabstk != nil)
|
|
is->tabstk = is->tabstk->next;
|
|
curtab->next = di->tables;
|
|
di->tables = curtab;
|
|
curtab = is->tabstk;
|
|
}
|
|
outerps = lastps(ps);
|
|
ans = outerps->items->next;
|
|
// note: ans may be nil and di->kids not nil, if there's a frameset!
|
|
outerps->items = newispacer(ISPnull);
|
|
outerps->lastit = outerps->items;
|
|
is->psstk = ps;
|
|
if(ans != nil && di->hasscripts) {
|
|
// TODO evalscript(nil);
|
|
;
|
|
}
|
|
|
|
return_ans:
|
|
if(dbgbuild) {
|
|
assert(validitems(ans));
|
|
if(ans == nil)
|
|
fprint(2, "getitems returning nil\n");
|
|
else
|
|
printitems(ans, "getitems returning:");
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
// Concatenate together maximal set of Data tokens, starting at toks[toki+1].
|
|
// Lexer has ensured that there will either be a following non-data token or
|
|
// we will be at eof.
|
|
// Return emallocd trimmed concatenation, and update *ptoki to last used toki
|
|
static Rune*
|
|
getpcdata(Token* toks, int tokslen, int* ptoki)
|
|
{
|
|
Rune* ans;
|
|
Rune* p;
|
|
Rune* trimans;
|
|
int anslen;
|
|
int trimanslen;
|
|
int toki;
|
|
Token* tok;
|
|
|
|
ans = nil;
|
|
anslen = 0;
|
|
// first find length of answer
|
|
toki = (*ptoki) + 1;
|
|
while(toki < tokslen) {
|
|
tok = &toks[toki];
|
|
if(tok->tag == Data) {
|
|
toki++;
|
|
anslen += _Strlen(tok->text);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
// now make up the initial answer
|
|
if(anslen > 0) {
|
|
ans = _newstr(anslen);
|
|
p = ans;
|
|
toki = (*ptoki) + 1;
|
|
while(toki < tokslen) {
|
|
tok = &toks[toki];
|
|
if(tok->tag == Data) {
|
|
toki++;
|
|
p = _Stradd(p, tok->text, _Strlen(tok->text));
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
*p = 0;
|
|
_trimwhite(ans, anslen, &trimans, &trimanslen);
|
|
if(trimanslen != anslen) {
|
|
p = ans;
|
|
ans = _Strndup(trimans, trimanslen);
|
|
free(p);
|
|
}
|
|
}
|
|
*ptoki = toki-1;
|
|
return ans;
|
|
}
|
|
|
|
// If still parsing head of curtab->cells list, finish it off
|
|
// by transferring the items on the head of psstk to the cell.
|
|
// Then pop the psstk and return the new psstk.
|
|
static Pstate*
|
|
finishcell(Table* curtab, Pstate* psstk)
|
|
{
|
|
Tablecell* c;
|
|
Pstate* psstknext;
|
|
|
|
c = curtab->cells;
|
|
if(c != nil) {
|
|
if((c->flags&TFparsing)) {
|
|
psstknext = psstk->next;
|
|
if(psstknext == nil) {
|
|
if(warn)
|
|
fprint(2, "warning: parse state stack is wrong\n");
|
|
}
|
|
else {
|
|
c->content = psstk->items->next;
|
|
c->flags &= ~TFparsing;
|
|
freepstate(psstk);
|
|
psstk = psstknext;
|
|
}
|
|
}
|
|
}
|
|
return psstk;
|
|
}
|
|
|
|
// Make a new Pstate for a cell, based on the old pstate, oldps.
|
|
// Also, put the new ps on the head of the oldps stack.
|
|
static Pstate*
|
|
cell_pstate(Pstate* oldps, int ishead)
|
|
{
|
|
Pstate* ps;
|
|
int sty;
|
|
|
|
ps = newpstate(oldps);
|
|
ps->skipwhite = 1;
|
|
ps->curanchor = oldps->curanchor;
|
|
copystack(&ps->fntstylestk, &oldps->fntstylestk);
|
|
copystack(&ps->fntsizestk, &oldps->fntsizestk);
|
|
ps->curfont = oldps->curfont;
|
|
ps->curfg = oldps->curfg;
|
|
ps->curbg = oldps->curbg;
|
|
copystack(&ps->fgstk, &oldps->fgstk);
|
|
ps->adjsize = oldps->adjsize;
|
|
if(ishead) {
|
|
sty = ps->curfont%NumSize;
|
|
ps->curfont = FntB*NumSize + sty;
|
|
}
|
|
return ps;
|
|
}
|
|
|
|
// Return a new Pstate with default starting state.
|
|
// Use link to add it to head of a list, if any.
|
|
static Pstate*
|
|
newpstate(Pstate* link)
|
|
{
|
|
Pstate* ps;
|
|
|
|
ps = (Pstate*)emalloc(sizeof(Pstate));
|
|
ps->curfont = DefFnt;
|
|
ps->curfg = Black;
|
|
ps->curbg.image = nil;
|
|
ps->curbg.color = White;
|
|
ps->curul = ULnone;
|
|
ps->curjust = ALleft;
|
|
ps->curstate = IFwrap;
|
|
ps->items = newispacer(ISPnull);
|
|
ps->lastit = ps->items;
|
|
ps->prelastit = nil;
|
|
ps->next = link;
|
|
return ps;
|
|
}
|
|
|
|
// Return last Pstate on psl list
|
|
static Pstate*
|
|
lastps(Pstate* psl)
|
|
{
|
|
assert(psl != nil);
|
|
while(psl->next != nil)
|
|
psl = psl->next;
|
|
return psl;
|
|
}
|
|
|
|
// Add it to end of ps item chain, adding in current state from ps.
|
|
// Also, if tok is not nil, scan it for generic attributes and assign
|
|
// the genattr field of the item accordingly.
|
|
static void
|
|
additem(Pstate* ps, Item* it, Token* tok)
|
|
{
|
|
int aid;
|
|
int any;
|
|
Rune* i;
|
|
Rune* c;
|
|
Rune* s;
|
|
Rune* t;
|
|
Attr* a;
|
|
SEvent* e;
|
|
|
|
if(ps->skipping) {
|
|
if(warn)
|
|
fprint(2, "warning: skipping item: %I\n", it);
|
|
return;
|
|
}
|
|
it->anchorid = ps->curanchor;
|
|
it->state |= ps->curstate;
|
|
if(tok != nil) {
|
|
any = 0;
|
|
i = nil;
|
|
c = nil;
|
|
s = nil;
|
|
t = nil;
|
|
e = nil;
|
|
for(a = tok->attr; a != nil; a = a->next) {
|
|
aid = a->attid;
|
|
if(!attrinfo[aid])
|
|
continue;
|
|
switch(aid) {
|
|
case Aid:
|
|
i = a->value;
|
|
break;
|
|
|
|
case Aclass:
|
|
c = a->value;
|
|
break;
|
|
|
|
case Astyle:
|
|
s = a->value;
|
|
break;
|
|
|
|
case Atitle:
|
|
t = a->value;
|
|
break;
|
|
|
|
default:
|
|
assert(aid >= Aonblur && aid <= Aonunload);
|
|
e = newscriptevent(scriptev[a->attid], a->value, e);
|
|
break;
|
|
}
|
|
a->value = nil;
|
|
any = 1;
|
|
}
|
|
if(any)
|
|
it->genattr = newgenattr(i, c, s, t, e);
|
|
}
|
|
ps->curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
|
|
ps->prelastit = ps->lastit;
|
|
ps->lastit->next = it;
|
|
ps->lastit = it;
|
|
}
|
|
|
|
// Make a text item out of s,
|
|
// using current font, foreground, vertical offset and underline state.
|
|
static Item*
|
|
textit(Pstate* ps, Rune* s)
|
|
{
|
|
assert(s != nil);
|
|
return newitext(s, ps->curfont, ps->curfg, ps->curvoff + Voffbias, ps->curul);
|
|
}
|
|
|
|
// Add text item or items for s, paying attention to
|
|
// current font, foreground, baseline offset, underline state,
|
|
// and literal mode. Unless we're in literal mode, compress
|
|
// whitespace to single blank, and, if curstate has a break,
|
|
// trim any leading whitespace. Whether in literal mode or not,
|
|
// turn nonbreaking spaces into spacer items with IFnobrk set.
|
|
//
|
|
// In literal mode, break up s at newlines and add breaks instead.
|
|
// Also replace tabs appropriate number of spaces.
|
|
// In nonliteral mode, break up the items every 100 or so characters
|
|
// just to make the layout algorithm not go quadratic.
|
|
//
|
|
// addtext assumes ownership of s.
|
|
static void
|
|
addtext(Pstate* ps, Rune* s)
|
|
{
|
|
int n;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
int col;
|
|
int c;
|
|
int nsp;
|
|
Item* it;
|
|
Rune* ss;
|
|
Rune* p;
|
|
Rune buf[SMALLBUFSIZE];
|
|
|
|
assert(s != nil);
|
|
n = runestrlen(s);
|
|
i = 0;
|
|
j = 0;
|
|
if(ps->literal) {
|
|
col = 0;
|
|
while(i < n) {
|
|
if(s[i] == '\n') {
|
|
if(i > j) {
|
|
// trim trailing blanks from line
|
|
for(k = i; k > j; k--)
|
|
if(s[k - 1] != ' ')
|
|
break;
|
|
if(k > j)
|
|
additem(ps, textit(ps, _Strndup(s+j, k-j)), nil);
|
|
}
|
|
addlinebrk(ps, 0);
|
|
j = i + 1;
|
|
col = 0;
|
|
}
|
|
else {
|
|
if(s[i] == '\t') {
|
|
col += i - j;
|
|
nsp = 8 - (col%8);
|
|
// make ss = s[j:i] + nsp spaces
|
|
ss = _newstr(i-j+nsp);
|
|
p = _Stradd(ss, s+j, i-j);
|
|
p = _Stradd(p, L(Ltab2space), nsp);
|
|
*p = 0;
|
|
additem(ps, textit(ps, ss), nil);
|
|
col += nsp;
|
|
j = i + 1;
|
|
}
|
|
else if(s[i] == NBSP) {
|
|
if(i > j)
|
|
additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
|
|
addnbsp(ps);
|
|
col += (i - j) + 1;
|
|
j = i + 1;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
if(i > j) {
|
|
if(j == 0 && i == n) {
|
|
// just transfer s over
|
|
additem(ps, textit(ps, s), nil);
|
|
}
|
|
else {
|
|
additem(ps, textit(ps, _Strndup(s+j, i-j)), nil);
|
|
free(s);
|
|
}
|
|
}
|
|
}
|
|
else { // not literal mode
|
|
if((ps->curstate&IFbrk) || ps->lastit == ps->items)
|
|
while(i < n) {
|
|
c = s[i];
|
|
if(c >= 256 || !isspace(c))
|
|
break;
|
|
i++;
|
|
}
|
|
p = buf;
|
|
for(j = i; i < n; i++) {
|
|
assert(p+i-j < buf+SMALLBUFSIZE-1);
|
|
c = s[i];
|
|
if(c == NBSP) {
|
|
if(i > j)
|
|
p = _Stradd(p, s+j, i-j);
|
|
if(p > buf)
|
|
additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
|
|
p = buf;
|
|
addnbsp(ps);
|
|
j = i + 1;
|
|
continue;
|
|
}
|
|
if(c < 256 && isspace(c)) {
|
|
if(i > j)
|
|
p = _Stradd(p, s+j, i-j);
|
|
*p++ = ' ';
|
|
while(i < n - 1) {
|
|
c = s[i + 1];
|
|
if(c >= 256 || !isspace(c))
|
|
break;
|
|
i++;
|
|
}
|
|
j = i + 1;
|
|
}
|
|
if(i - j >= 100) {
|
|
p = _Stradd(p, s+j, i+1-j);
|
|
j = i + 1;
|
|
}
|
|
if(p-buf >= 100) {
|
|
additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
|
|
p = buf;
|
|
}
|
|
}
|
|
if(i > j && j < n) {
|
|
assert(p+i-j < buf+SMALLBUFSIZE-1);
|
|
p = _Stradd(p, s+j, i-j);
|
|
}
|
|
// don't add a space if previous item ended in a space
|
|
if(p-buf == 1 && buf[0] == ' ' && ps->lastit != nil) {
|
|
it = ps->lastit;
|
|
if(it->tag == Itexttag) {
|
|
ss = ((Itext*)it)->s;
|
|
k = _Strlen(ss);
|
|
if(k > 0 && ss[k] == ' ')
|
|
p = buf;
|
|
}
|
|
}
|
|
if(p > buf)
|
|
additem(ps, textit(ps, _Strndup(buf, p-buf)), nil);
|
|
free(s);
|
|
}
|
|
}
|
|
|
|
// Add a break to ps->curstate, with extra space if sp is true.
|
|
// If there was a previous break, combine this one's parameters
|
|
// with that to make the amt be the max of the two and the clr
|
|
// be the most general. (amt will be 0 or 1)
|
|
// Also, if the immediately preceding item was a text item,
|
|
// trim any whitespace from the end of it, if not in literal mode.
|
|
// Finally, if this is at the very beginning of the item list
|
|
// (the only thing there is a null spacer), then don't add the space.
|
|
static void
|
|
addbrk(Pstate* ps, int sp, int clr)
|
|
{
|
|
int state;
|
|
Rune* l;
|
|
int nl;
|
|
Rune* r;
|
|
int nr;
|
|
Itext* t;
|
|
Rune* s;
|
|
|
|
state = ps->curstate;
|
|
clr = clr|(state&(IFcleft|IFcright));
|
|
if(sp && !(ps->lastit == ps->items))
|
|
sp = IFbrksp;
|
|
else
|
|
sp = 0;
|
|
ps->curstate = IFbrk|sp|(state&~(IFcleft|IFcright))|clr;
|
|
if(ps->lastit != ps->items) {
|
|
if(!ps->literal && ps->lastit->tag == Itexttag) {
|
|
t = (Itext*)ps->lastit;
|
|
_splitr(t->s, _Strlen(t->s), notwhitespace, &l, &nl, &r, &nr);
|
|
// try to avoid making empty items
|
|
// but not crucial f the occasional one gets through
|
|
if(nl == 0 && ps->prelastit != nil) {
|
|
ps->lastit = ps->prelastit;
|
|
ps->lastit->next = nil;
|
|
ps->prelastit = nil;
|
|
}
|
|
else {
|
|
s = t->s;
|
|
if(nl == 0) {
|
|
// need a non-nil pointer to empty string
|
|
// (_Strdup(L(Lempty)) returns nil)
|
|
t->s = emalloc(sizeof(Rune));
|
|
t->s[0] = 0;
|
|
}
|
|
else
|
|
t->s = _Strndup(l, nl);
|
|
if(s)
|
|
free(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add break due to a <br> or a newline within a preformatted section.
|
|
// We add a null item first, with current font's height and ascent, to make
|
|
// sure that the current line takes up at least that amount of vertical space.
|
|
// This ensures that <br>s on empty lines cause blank lines, and that
|
|
// multiple <br>s in a row give multiple blank lines.
|
|
// However don't add the spacer if the previous item was something that
|
|
// takes up space itself.
|
|
static void
|
|
addlinebrk(Pstate* ps, int clr)
|
|
{
|
|
int obrkstate;
|
|
int b;
|
|
int addit;
|
|
|
|
// don't want break before our null item unless the previous item
|
|
// was also a null item for the purposes of line breaking
|
|
obrkstate = ps->curstate&(IFbrk|IFbrksp);
|
|
b = IFnobrk;
|
|
addit = 0;
|
|
if(ps->lastit != nil) {
|
|
if(ps->lastit->tag == Ispacertag) {
|
|
if(((Ispacer*)ps->lastit)->spkind == ISPvline)
|
|
b = IFbrk;
|
|
addit = 1;
|
|
}
|
|
else if(ps->lastit->tag == Ifloattag)
|
|
addit = 1;
|
|
}
|
|
if(addit) {
|
|
ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|b;
|
|
additem(ps, newispacer(ISPvline), nil);
|
|
ps->curstate = (ps->curstate&~(IFbrk|IFbrksp))|obrkstate;
|
|
}
|
|
addbrk(ps, 0, clr);
|
|
}
|
|
|
|
// Add a nonbreakable space
|
|
static void
|
|
addnbsp(Pstate* ps)
|
|
{
|
|
// if nbsp comes right where a break was specified,
|
|
// do the break anyway (nbsp is being used to generate undiscardable
|
|
// space rather than to prevent a break)
|
|
if((ps->curstate&IFbrk) == 0)
|
|
ps->curstate |= IFnobrk;
|
|
additem(ps, newispacer(ISPhspace), nil);
|
|
// but definitely no break on next item
|
|
ps->curstate |= IFnobrk;
|
|
}
|
|
|
|
// Change hang in ps.curstate by delta.
|
|
// The amount is in 1/10ths of tabs, and is the amount that
|
|
// the current contiguous set of items with a hang value set
|
|
// is to be shifted left from its normal (indented) place.
|
|
static void
|
|
changehang(Pstate* ps, int delta)
|
|
{
|
|
int amt;
|
|
|
|
amt = (ps->curstate&IFhangmask) + delta;
|
|
if(amt < 0) {
|
|
if(warn)
|
|
fprint(2, "warning: hang went negative\n");
|
|
amt = 0;
|
|
}
|
|
ps->curstate = (ps->curstate&~IFhangmask)|amt;
|
|
}
|
|
|
|
// Change indent in ps.curstate by delta.
|
|
static void
|
|
changeindent(Pstate* ps, int delta)
|
|
{
|
|
int amt;
|
|
|
|
amt = ((ps->curstate&IFindentmask) >> IFindentshift) + delta;
|
|
if(amt < 0) {
|
|
if(warn)
|
|
fprint(2, "warning: indent went negative\n");
|
|
amt = 0;
|
|
}
|
|
ps->curstate = (ps->curstate&~IFindentmask)|(amt << IFindentshift);
|
|
}
|
|
|
|
// Push val on top of stack, and also return value pushed
|
|
static int
|
|
push(Stack* stk, int val)
|
|
{
|
|
if(stk->n == Nestmax) {
|
|
if(warn)
|
|
fprint(2, "warning: build stack overflow\n");
|
|
}
|
|
else
|
|
stk->slots[stk->n++] = val;
|
|
return val;
|
|
}
|
|
|
|
// Pop top of stack
|
|
static void
|
|
pop(Stack* stk)
|
|
{
|
|
if(stk->n > 0)
|
|
--stk->n;
|
|
}
|
|
|
|
//Return top of stack, using dflt if stack is empty
|
|
static int
|
|
top(Stack* stk, int dflt)
|
|
{
|
|
if(stk->n == 0)
|
|
return dflt;
|
|
return stk->slots[stk->n-1];
|
|
}
|
|
|
|
// pop, then return new top, with dflt if empty
|
|
static int
|
|
popretnewtop(Stack* stk, int dflt)
|
|
{
|
|
if(stk->n == 0)
|
|
return dflt;
|
|
stk->n--;
|
|
if(stk->n == 0)
|
|
return dflt;
|
|
return stk->slots[stk->n-1];
|
|
}
|
|
|
|
// Copy fromstk entries into tostk
|
|
static void
|
|
copystack(Stack* tostk, Stack* fromstk)
|
|
{
|
|
int n;
|
|
|
|
n = fromstk->n;
|
|
tostk->n = n;
|
|
memmove(tostk->slots, fromstk->slots, n*sizeof(int));
|
|
}
|
|
|
|
static void
|
|
popfontstyle(Pstate* ps)
|
|
{
|
|
pop(&ps->fntstylestk);
|
|
setcurfont(ps);
|
|
}
|
|
|
|
static void
|
|
pushfontstyle(Pstate* ps, int sty)
|
|
{
|
|
push(&ps->fntstylestk, sty);
|
|
setcurfont(ps);
|
|
}
|
|
|
|
static void
|
|
popfontsize(Pstate* ps)
|
|
{
|
|
pop(&ps->fntsizestk);
|
|
setcurfont(ps);
|
|
}
|
|
|
|
static void
|
|
pushfontsize(Pstate* ps, int sz)
|
|
{
|
|
push(&ps->fntsizestk, sz);
|
|
setcurfont(ps);
|
|
}
|
|
|
|
static void
|
|
setcurfont(Pstate* ps)
|
|
{
|
|
int sty;
|
|
int sz;
|
|
|
|
sty = top(&ps->fntstylestk, FntR);
|
|
sz = top(&ps->fntsizestk, Normal);
|
|
if(sz < Tiny)
|
|
sz = Tiny;
|
|
if(sz > Verylarge)
|
|
sz = Verylarge;
|
|
ps->curfont = sty*NumSize + sz;
|
|
}
|
|
|
|
static void
|
|
popjust(Pstate* ps)
|
|
{
|
|
pop(&ps->juststk);
|
|
setcurjust(ps);
|
|
}
|
|
|
|
static void
|
|
pushjust(Pstate* ps, int j)
|
|
{
|
|
push(&ps->juststk, j);
|
|
setcurjust(ps);
|
|
}
|
|
|
|
static void
|
|
setcurjust(Pstate* ps)
|
|
{
|
|
int j;
|
|
int state;
|
|
|
|
j = top(&ps->juststk, ALleft);
|
|
if(j != ps->curjust) {
|
|
ps->curjust = j;
|
|
state = ps->curstate;
|
|
state &= ~(IFrjust|IFcjust);
|
|
if(j == ALcenter)
|
|
state |= IFcjust;
|
|
else if(j == ALright)
|
|
state |= IFrjust;
|
|
ps->curstate = state;
|
|
}
|
|
}
|
|
|
|
// Do final rearrangement after table parsing is finished
|
|
// and assign cells to grid points
|
|
static void
|
|
finish_table(Table* t)
|
|
{
|
|
int ncol;
|
|
int nrow;
|
|
int r;
|
|
Tablerow* rl;
|
|
Tablecell* cl;
|
|
int* rowspancnt;
|
|
Tablecell** rowspancell;
|
|
int ri;
|
|
int ci;
|
|
Tablecell* c;
|
|
Tablecell* cnext;
|
|
Tablerow* row;
|
|
Tablerow* rownext;
|
|
int rcols;
|
|
int newncol;
|
|
int k;
|
|
int j;
|
|
int cspan;
|
|
int rspan;
|
|
int i;
|
|
|
|
rl = t->rows;
|
|
t->nrow = nrow = _listlen((List*)rl);
|
|
t->rows = (Tablerow*)emalloc(nrow * sizeof(Tablerow));
|
|
ncol = 0;
|
|
r = nrow - 1;
|
|
for(row = rl; row != nil; row = rownext) {
|
|
// copy the data from the allocated Tablerow into the array slot
|
|
t->rows[r] = *row;
|
|
rownext = row->next;
|
|
row = &t->rows[r];
|
|
r--;
|
|
rcols = 0;
|
|
c = row->cells;
|
|
|
|
// If rowspan is > 1 but this is the last row,
|
|
// reset the rowspan
|
|
if(c != nil && c->rowspan > 1 && r == nrow-2)
|
|
c->rowspan = 1;
|
|
|
|
// reverse row->cells list (along nextinrow pointers)
|
|
row->cells = nil;
|
|
while(c != nil) {
|
|
cnext = c->nextinrow;
|
|
c->nextinrow = row->cells;
|
|
row->cells = c;
|
|
rcols += c->colspan;
|
|
c = cnext;
|
|
}
|
|
if(rcols > ncol)
|
|
ncol = rcols;
|
|
}
|
|
t->ncol = ncol;
|
|
t->cols = (Tablecol*)emalloc(ncol * sizeof(Tablecol));
|
|
|
|
// Reverse cells just so they are drawn in source order.
|
|
// Also, trim their contents so they don't end in whitespace.
|
|
t->cells = (Tablecell*)_revlist((List*)t->cells);
|
|
for(c = t->cells; c != nil; c= c->next)
|
|
trim_cell(c);
|
|
t->grid = (Tablecell***)emalloc(nrow * sizeof(Tablecell**));
|
|
for(i = 0; i < nrow; i++)
|
|
t->grid[i] = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
|
|
|
|
// The following arrays keep track of cells that are spanning
|
|
// multiple rows; rowspancnt[i] is the number of rows left
|
|
// to be spanned in column i.
|
|
// When done, cell's (row,col) is upper left grid point.
|
|
rowspancnt = (int*)emalloc(ncol * sizeof(int));
|
|
rowspancell = (Tablecell**)emalloc(ncol * sizeof(Tablecell*));
|
|
for(ri = 0; ri < nrow; ri++) {
|
|
row = &t->rows[ri];
|
|
cl = row->cells;
|
|
ci = 0;
|
|
while(ci < ncol || cl != nil) {
|
|
if(ci < ncol && rowspancnt[ci] > 0) {
|
|
t->grid[ri][ci] = rowspancell[ci];
|
|
rowspancnt[ci]--;
|
|
ci++;
|
|
}
|
|
else {
|
|
if(cl == nil) {
|
|
ci++;
|
|
continue;
|
|
}
|
|
c = cl;
|
|
cl = cl->nextinrow;
|
|
cspan = c->colspan;
|
|
rspan = c->rowspan;
|
|
if(ci + cspan > ncol) {
|
|
// because of row spanning, we calculated
|
|
// ncol incorrectly; adjust it
|
|
newncol = ci + cspan;
|
|
t->cols = (Tablecol*)erealloc(t->cols, newncol * sizeof(Tablecol));
|
|
rowspancnt = (int*)erealloc(rowspancnt, newncol * sizeof(int));
|
|
rowspancell = (Tablecell**)erealloc(rowspancell, newncol * sizeof(Tablecell*));
|
|
k = newncol-ncol;
|
|
memset(t->cols+ncol, 0, k*sizeof(Tablecol));
|
|
memset(rowspancnt+ncol, 0, k*sizeof(int));
|
|
memset(rowspancell+ncol, 0, k*sizeof(Tablecell*));
|
|
for(j = 0; j < nrow; j++) {
|
|
t->grid[j] = (Tablecell**)erealloc(t->grid[j], newncol * sizeof(Tablecell*));
|
|
memset(t->grid[j], 0, k*sizeof(Tablecell*));
|
|
}
|
|
t->ncol = ncol = newncol;
|
|
}
|
|
c->row = ri;
|
|
c->col = ci;
|
|
for(i = 0; i < cspan; i++) {
|
|
t->grid[ri][ci] = c;
|
|
if(rspan > 1) {
|
|
rowspancnt[ci] = rspan - 1;
|
|
rowspancell[ci] = c;
|
|
}
|
|
ci++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove tail of cell content until it isn't whitespace.
|
|
static void
|
|
trim_cell(Tablecell* c)
|
|
{
|
|
int dropping;
|
|
Rune* s;
|
|
Rune* x;
|
|
Rune* y;
|
|
int nx;
|
|
int ny;
|
|
Item* p;
|
|
Itext* q;
|
|
Item* pprev;
|
|
|
|
dropping = 1;
|
|
while(c->content != nil && dropping) {
|
|
p = c->content;
|
|
pprev = nil;
|
|
while(p->next != nil) {
|
|
pprev = p;
|
|
p = p->next;
|
|
}
|
|
dropping = 0;
|
|
if(!(p->state&IFnobrk)) {
|
|
if(p->tag == Itexttag) {
|
|
q = (Itext*)p;
|
|
s = q->s;
|
|
_splitr(s, _Strlen(s), notwhitespace, &x, &nx, &y, &ny);
|
|
if(nx != 0 && ny != 0) {
|
|
q->s = _Strndup(x, nx);
|
|
free(s);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if(dropping) {
|
|
if(pprev == nil)
|
|
c->content = nil;
|
|
else
|
|
pprev->next = nil;
|
|
freeitem(p);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Caller must free answer (eventually).
|
|
static Rune*
|
|
listmark(uchar ty, int n)
|
|
{
|
|
Rune* s;
|
|
Rune* t;
|
|
int n2;
|
|
int i;
|
|
|
|
s = nil;
|
|
switch(ty) {
|
|
case LTdisc:
|
|
case LTsquare:
|
|
case LTcircle:
|
|
s = _newstr(1);
|
|
s[0] = (ty == LTdisc)? 0x2022 // bullet
|
|
: ((ty == LTsquare)? 0x220e // filled square
|
|
: 0x2218); // degree
|
|
s[1] = 0;
|
|
break;
|
|
|
|
case LT1:
|
|
t = _ltoStr(n);
|
|
n2 = _Strlen(t);
|
|
s = _newstr(n2+1);
|
|
t = _Stradd(s, t, n2);
|
|
*t++ = '.';
|
|
*t = 0;
|
|
break;
|
|
|
|
case LTa:
|
|
case LTA:
|
|
n--;
|
|
i = 0;
|
|
if(n < 0)
|
|
n = 0;
|
|
s = _newstr((n <= 25)? 2 : 3);
|
|
if(n > 25) {
|
|
n2 = n%26;
|
|
n /= 26;
|
|
if(n2 > 25)
|
|
n2 = 25;
|
|
s[i++] = n2 + (ty == LTa)? 'a' : 'A';
|
|
}
|
|
s[i++] = n + (ty == LTa)? 'a' : 'A';
|
|
s[i++] = '.';
|
|
s[i] = 0;
|
|
break;
|
|
|
|
case LTi:
|
|
case LTI:
|
|
if(n >= NROMAN) {
|
|
if(warn)
|
|
fprint(2, "warning: unimplemented roman number > %d\n", NROMAN);
|
|
n = NROMAN;
|
|
}
|
|
t = roman[n - 1];
|
|
n2 = _Strlen(t);
|
|
s = _newstr(n2+1);
|
|
for(i = 0; i < n2; i++)
|
|
s[i] = (ty == LTi)? tolower(t[i]) : t[i];
|
|
s[i++] = '.';
|
|
s[i] = 0;
|
|
break;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Find map with given name in di.maps.
|
|
// If not there, add one, copying name.
|
|
// Ownership of map remains with di->maps list.
|
|
static Map*
|
|
getmap(Docinfo* di, Rune* name)
|
|
{
|
|
Map* m;
|
|
|
|
for(m = di->maps; m != nil; m = m->next) {
|
|
if(!_Strcmp(name, m->name))
|
|
return m;
|
|
}
|
|
m = (Map*)emalloc(sizeof(Map));
|
|
m->name = _Strdup(name);
|
|
m->areas = nil;
|
|
m->next = di->maps;
|
|
di->maps = m;
|
|
return m;
|
|
}
|
|
|
|
// Transfers ownership of href to Area
|
|
static Area*
|
|
newarea(int shape, Rune* href, int target, Area* link)
|
|
{
|
|
Area* a;
|
|
|
|
a = (Area*)emalloc(sizeof(Area));
|
|
a->shape = shape;
|
|
a->href = href;
|
|
a->target = target;
|
|
a->next = link;
|
|
return a;
|
|
}
|
|
|
|
// Return string value associated with attid in tok, nil if none.
|
|
// Caller must free the result (eventually).
|
|
static Rune*
|
|
aval(Token* tok, int attid)
|
|
{
|
|
Rune* ans;
|
|
|
|
_tokaval(tok, attid, &ans, 1); // transfers string ownership from token to ans
|
|
return ans;
|
|
}
|
|
|
|
// Like aval, but use dflt if there was no such attribute in tok.
|
|
// Caller must free the result (eventually).
|
|
static Rune*
|
|
astrval(Token* tok, int attid, Rune* dflt)
|
|
{
|
|
Rune* ans;
|
|
|
|
if(_tokaval(tok, attid, &ans, 1))
|
|
return ans; // transfers string ownership from token to ans
|
|
else
|
|
return _Strdup(dflt);
|
|
}
|
|
|
|
// Here we're supposed to convert to an int,
|
|
// and have a default when not found
|
|
static int
|
|
aintval(Token* tok, int attid, int dflt)
|
|
{
|
|
Rune* ans;
|
|
|
|
if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
|
|
return dflt;
|
|
else
|
|
return toint(ans);
|
|
}
|
|
|
|
// Like aintval, but result should be >= 0
|
|
static int
|
|
auintval(Token* tok, int attid, int dflt)
|
|
{
|
|
Rune* ans;
|
|
int v;
|
|
|
|
if(!_tokaval(tok, attid, &ans, 0) || ans == nil)
|
|
return dflt;
|
|
else {
|
|
v = toint(ans);
|
|
return v >= 0? v : 0;
|
|
}
|
|
}
|
|
|
|
// int conversion, but with possible error check (if warning)
|
|
static int
|
|
toint(Rune* s)
|
|
{
|
|
int ans;
|
|
Rune* eptr;
|
|
|
|
ans = _Strtol(s, &eptr, 10);
|
|
if(warn) {
|
|
if(*eptr != 0) {
|
|
eptr = _Strclass(eptr, notwhitespace);
|
|
if(eptr != nil)
|
|
fprint(2, "warning: expected integer, got %S\n", s);
|
|
}
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
// Attribute value when need a table to convert strings to ints
|
|
static int
|
|
atabval(Token* tok, int attid, StringInt* tab, int ntab, int dflt)
|
|
{
|
|
Rune* aval;
|
|
int ans;
|
|
|
|
ans = dflt;
|
|
if(_tokaval(tok, attid, &aval, 0)) {
|
|
if(!_lookup(tab, ntab, aval, _Strlen(aval), &ans)) {
|
|
ans = dflt;
|
|
if(warn)
|
|
fprint(2, "warning: name not found in table lookup: %S\n", aval);
|
|
}
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
// Attribute value when supposed to be a color
|
|
static int
|
|
acolorval(Token* tok, int attid, int dflt)
|
|
{
|
|
Rune* aval;
|
|
int ans;
|
|
|
|
ans = dflt;
|
|
if(_tokaval(tok, attid, &aval, 0))
|
|
ans = color(aval, dflt);
|
|
return ans;
|
|
}
|
|
|
|
// Attribute value when supposed to be a target frame name
|
|
static int
|
|
atargval(Token* tok, int dflt)
|
|
{
|
|
int ans;
|
|
Rune* aval;
|
|
|
|
ans = dflt;
|
|
if(_tokaval(tok, Atarget, &aval, 0)){
|
|
ans = targetid(aval);
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
// special for list types, where "i" and "I" are different,
|
|
// but "square" and "SQUARE" are the same
|
|
static int
|
|
listtyval(Token* tok, int dflt)
|
|
{
|
|
Rune* aval;
|
|
int ans;
|
|
int n;
|
|
|
|
ans = dflt;
|
|
if(_tokaval(tok, Atype, &aval, 0)) {
|
|
n = _Strlen(aval);
|
|
if(n == 1) {
|
|
switch(aval[0]) {
|
|
case '1':
|
|
ans = LT1;
|
|
break;
|
|
case 'A':
|
|
ans = LTA;
|
|
break;
|
|
case 'I':
|
|
ans = LTI;
|
|
break;
|
|
case 'a':
|
|
ans = LTa;
|
|
break;
|
|
case 'i':
|
|
ans = LTi;
|
|
default:
|
|
if(warn)
|
|
fprint(2, "warning: unknown list element type %c\n", aval[0]);
|
|
}
|
|
}
|
|
else {
|
|
if(!_Strncmpci(aval, n, L(Lcircle)))
|
|
ans = LTcircle;
|
|
else if(!_Strncmpci(aval, n, L(Ldisc)))
|
|
ans = LTdisc;
|
|
else if(!_Strncmpci(aval, n, L(Lsquare)))
|
|
ans = LTsquare;
|
|
else {
|
|
if(warn)
|
|
fprint(2, "warning: unknown list element type %S\n", aval);
|
|
}
|
|
}
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
// Attribute value when value is a URL, possibly relative to base.
|
|
// FOR NOW: leave the url relative.
|
|
// Caller must free the result (eventually).
|
|
static Rune*
|
|
aurlval(Token* tok, int attid, Rune* dflt, Rune* base)
|
|
{
|
|
Rune* ans;
|
|
Rune* url;
|
|
|
|
USED(base);
|
|
ans = nil;
|
|
if(_tokaval(tok, attid, &url, 0) && url != nil)
|
|
ans = removeallwhite(url);
|
|
if(ans == nil)
|
|
ans = _Strdup(dflt);
|
|
return ans;
|
|
}
|
|
|
|
// Return copy of s but with all whitespace (even internal) removed.
|
|
// This fixes some buggy URL specification strings.
|
|
static Rune*
|
|
removeallwhite(Rune* s)
|
|
{
|
|
int j;
|
|
int n;
|
|
int i;
|
|
int c;
|
|
Rune* ans;
|
|
|
|
j = 0;
|
|
n = _Strlen(s);
|
|
for(i = 0; i < n; i++) {
|
|
c = s[i];
|
|
if(c >= 256 || !isspace(c))
|
|
j++;
|
|
}
|
|
if(j < n) {
|
|
ans = _newstr(j);
|
|
j = 0;
|
|
for(i = 0; i < n; i++) {
|
|
c = s[i];
|
|
if(c >= 256 || !isspace(c))
|
|
ans[j++] = c;
|
|
}
|
|
ans[j] = 0;
|
|
}
|
|
else
|
|
ans = _Strdup(s);
|
|
return ans;
|
|
}
|
|
|
|
// Attribute value when mere presence of attr implies value of 1,
|
|
// but if there is an integer there, return it as the value.
|
|
static int
|
|
aflagval(Token* tok, int attid)
|
|
{
|
|
int val;
|
|
Rune* sval;
|
|
|
|
val = 0;
|
|
if(_tokaval(tok, attid, &sval, 0)) {
|
|
val = 1;
|
|
if(sval != nil)
|
|
val = toint(sval);
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static Align
|
|
makealign(int halign, int valign)
|
|
{
|
|
Align al;
|
|
|
|
al.halign = halign;
|
|
al.valign = valign;
|
|
return al;
|
|
}
|
|
|
|
// Make an Align (two alignments, horizontal and vertical)
|
|
static Align
|
|
aalign(Token* tok)
|
|
{
|
|
return makealign(
|
|
atabval(tok, Aalign, align_tab, NALIGNTAB, ALnone),
|
|
atabval(tok, Avalign, align_tab, NALIGNTAB, ALnone));
|
|
}
|
|
|
|
// Make a Dimen, based on value of attid attr
|
|
static Dimen
|
|
adimen(Token* tok, int attid)
|
|
{
|
|
Rune* wd;
|
|
|
|
if(_tokaval(tok, attid, &wd, 0))
|
|
return parsedim(wd, _Strlen(wd));
|
|
else
|
|
return makedimen(Dnone, 0);
|
|
}
|
|
|
|
// Parse s[0:n] as num[.[num]][unit][%|*]
|
|
static Dimen
|
|
parsedim(Rune* s, int ns)
|
|
{
|
|
int kind;
|
|
int spec;
|
|
Rune* l;
|
|
int nl;
|
|
Rune* r;
|
|
int nr;
|
|
int mul;
|
|
int i;
|
|
Rune* f;
|
|
int nf;
|
|
int Tkdpi;
|
|
Rune* units;
|
|
|
|
kind = Dnone;
|
|
spec = 0;
|
|
_splitl(s, ns, L(Lnot0to9), &l, &nl, &r, &nr);
|
|
if(nl != 0) {
|
|
spec = 1000*_Strtol(l, nil, 10);
|
|
if(nr > 0 && r[0] == '.') {
|
|
_splitl(r+1, nr-1, L(Lnot0to9), &f, &nf, &r, &nr);
|
|
if(nf != 0) {
|
|
mul = 100;
|
|
for(i = 0; i < nf; i++) {
|
|
spec = spec + mul*(f[i]-'0');
|
|
mul = mul/10;
|
|
}
|
|
}
|
|
}
|
|
kind = Dpixels;
|
|
if(nr != 0) {
|
|
if(nr >= 2) {
|
|
Tkdpi = 100;
|
|
units = r;
|
|
r = r+2;
|
|
nr -= 2;
|
|
if(!_Strncmpci(units, 2, L(Lpt)))
|
|
spec = (spec*Tkdpi)/72;
|
|
else if(!_Strncmpci(units, 2, L(Lpi)))
|
|
spec = (spec*12*Tkdpi)/72;
|
|
else if(!_Strncmpci(units, 2, L(Lin)))
|
|
spec = spec*Tkdpi;
|
|
else if(!_Strncmpci(units, 2, L(Lcm)))
|
|
spec = (spec*100*Tkdpi)/254;
|
|
else if(!_Strncmpci(units, 2, L(Lmm)))
|
|
spec = (spec*10*Tkdpi)/254;
|
|
else if(!_Strncmpci(units, 2, L(Lem)))
|
|
spec = spec*15;
|
|
else {
|
|
if(warn)
|
|
fprint(2, "warning: unknown units %C%Cs\n", units[0], units[1]);
|
|
}
|
|
}
|
|
if(nr >= 1) {
|
|
if(r[0] == '%')
|
|
kind = Dpercent;
|
|
else if(r[0] == '*')
|
|
kind = Drelative;
|
|
}
|
|
}
|
|
spec = spec/1000;
|
|
}
|
|
else if(nr == 1 && r[0] == '*') {
|
|
spec = 1;
|
|
kind = Drelative;
|
|
}
|
|
return makedimen(kind, spec);
|
|
}
|
|
|
|
static void
|
|
setdimarray(Token* tok, int attid, Dimen** pans, int* panslen)
|
|
{
|
|
Rune* s;
|
|
Dimen* d;
|
|
int k;
|
|
int nc;
|
|
Rune* a[SMALLBUFSIZE];
|
|
int an[SMALLBUFSIZE];
|
|
|
|
if(_tokaval(tok, attid, &s, 0)) {
|
|
nc = _splitall(s, _Strlen(s), L(Lcommaspace), a, an, SMALLBUFSIZE);
|
|
if(nc > 0) {
|
|
d = (Dimen*)emalloc(nc * sizeof(Dimen));
|
|
for(k = 0; k < nc; k++) {
|
|
d[k] = parsedim(a[k], an[k]);
|
|
}
|
|
*pans = d;
|
|
*panslen = nc;
|
|
return;
|
|
}
|
|
}
|
|
*pans = nil;
|
|
*panslen = 0;
|
|
}
|
|
|
|
static Background
|
|
makebackground(Rune* imageurl, int color)
|
|
{
|
|
Background bg;
|
|
|
|
bg.image = imageurl;
|
|
bg.color = color;
|
|
return bg;
|
|
}
|
|
|
|
static Item*
|
|
newitext(Rune* s, int fnt, int fg, int voff, int ul)
|
|
{
|
|
Itext* t;
|
|
|
|
assert(s != nil);
|
|
t = (Itext*)emalloc(sizeof(Itext));
|
|
t->item.tag = Itexttag;
|
|
t->s = s;
|
|
t->fnt = fnt;
|
|
t->fg = fg;
|
|
t->voff = voff;
|
|
t->ul = ul;
|
|
return (Item*)t;
|
|
}
|
|
|
|
static Item*
|
|
newirule(int align, int size, int noshade, Dimen wspec)
|
|
{
|
|
Irule* r;
|
|
|
|
r = (Irule*)emalloc(sizeof(Irule));
|
|
r->item.tag = Iruletag;
|
|
r->align = align;
|
|
r->size = size;
|
|
r->noshade = noshade;
|
|
r->wspec = wspec;
|
|
return (Item*)r;
|
|
}
|
|
|
|
// Map is owned elsewhere.
|
|
static Item*
|
|
newiimage(Rune* src, Rune* altrep, int align, int width, int height,
|
|
int hspace, int vspace, int border, int ismap, Map* map)
|
|
{
|
|
Iimage* i;
|
|
int state;
|
|
|
|
state = 0;
|
|
if(ismap)
|
|
state = IFsmap;
|
|
i = (Iimage*)emalloc(sizeof(Iimage));
|
|
i->item.tag = Iimagetag;
|
|
i->item.state = state;
|
|
i->imsrc = src;
|
|
i->altrep = altrep;
|
|
i->align = align;
|
|
i->imwidth = width;
|
|
i->imheight = height;
|
|
i->hspace = hspace;
|
|
i->vspace = vspace;
|
|
i->border = border;
|
|
i->map = map;
|
|
i->ctlid = -1;
|
|
return (Item*)i;
|
|
}
|
|
|
|
static Item*
|
|
newiformfield(Formfield* ff)
|
|
{
|
|
Iformfield* f;
|
|
|
|
f = (Iformfield*)emalloc(sizeof(Iformfield));
|
|
f->item.tag = Iformfieldtag;
|
|
f->formfield = ff;
|
|
return (Item*)f;
|
|
}
|
|
|
|
static Item*
|
|
newitable(Table* tab)
|
|
{
|
|
Itable* t;
|
|
|
|
t = (Itable*)emalloc(sizeof(Itable));
|
|
t->item.tag = Itabletag;
|
|
t->table = tab;
|
|
return (Item*)t;
|
|
}
|
|
|
|
static Item*
|
|
newifloat(Item* it, int side)
|
|
{
|
|
Ifloat* f;
|
|
|
|
f = (Ifloat*)emalloc(sizeof(Ifloat));
|
|
f->_item.tag = Ifloattag;
|
|
f->_item.state = IFwrap;
|
|
f->item = it;
|
|
f->side = side;
|
|
return (Item*)f;
|
|
}
|
|
|
|
static Item*
|
|
newispacer(int spkind)
|
|
{
|
|
Ispacer* s;
|
|
|
|
s = (Ispacer*)emalloc(sizeof(Ispacer));
|
|
s->item.tag = Ispacertag;
|
|
s->spkind = spkind;
|
|
return (Item*)s;
|
|
}
|
|
|
|
// Free one item (caller must deal with next pointer)
|
|
static void
|
|
freeitem(Item* it)
|
|
{
|
|
Iimage* ii;
|
|
Genattr* ga;
|
|
|
|
if(it == nil)
|
|
return;
|
|
|
|
switch(it->tag) {
|
|
case Itexttag:
|
|
free(((Itext*)it)->s);
|
|
break;
|
|
case Iimagetag:
|
|
ii = (Iimage*)it;
|
|
free(ii->imsrc);
|
|
free(ii->altrep);
|
|
break;
|
|
case Iformfieldtag:
|
|
freeformfield(((Iformfield*)it)->formfield);
|
|
break;
|
|
case Itabletag:
|
|
freetable(((Itable*)it)->table);
|
|
break;
|
|
case Ifloattag:
|
|
freeitem(((Ifloat*)it)->item);
|
|
break;
|
|
}
|
|
ga = it->genattr;
|
|
if(ga != nil) {
|
|
free(ga->id);
|
|
free(ga->class);
|
|
free(ga->style);
|
|
free(ga->title);
|
|
freescriptevents(ga->events);
|
|
}
|
|
free(it);
|
|
}
|
|
|
|
// Free list of items chained through next pointer
|
|
void
|
|
freeitems(Item* ithead)
|
|
{
|
|
Item* it;
|
|
Item* itnext;
|
|
|
|
it = ithead;
|
|
while(it != nil) {
|
|
itnext = it->next;
|
|
freeitem(it);
|
|
it = itnext;
|
|
}
|
|
}
|
|
|
|
static void
|
|
freeformfield(Formfield* ff)
|
|
{
|
|
Option* o;
|
|
Option* onext;
|
|
|
|
if(ff == nil)
|
|
return;
|
|
|
|
free(ff->name);
|
|
free(ff->value);
|
|
for(o = ff->options; o != nil; o = onext) {
|
|
onext = o->next;
|
|
free(o->value);
|
|
free(o->display);
|
|
}
|
|
free(ff);
|
|
}
|
|
|
|
static void
|
|
freetable(Table* t)
|
|
{
|
|
int i;
|
|
Tablecell* c;
|
|
Tablecell* cnext;
|
|
|
|
if(t == nil)
|
|
return;
|
|
|
|
// We'll find all the unique cells via t->cells and next pointers.
|
|
// (Other pointers to cells in the table are duplicates of these)
|
|
for(c = t->cells; c != nil; c = cnext) {
|
|
cnext = c->next;
|
|
freeitems(c->content);
|
|
}
|
|
if(t->grid != nil) {
|
|
for(i = 0; i < t->nrow; i++)
|
|
free(t->grid[i]);
|
|
free(t->grid);
|
|
}
|
|
free(t->rows);
|
|
free(t->cols);
|
|
freeitems(t->caption);
|
|
free(t);
|
|
}
|
|
|
|
static void
|
|
freeform(Form* f)
|
|
{
|
|
if(f == nil)
|
|
return;
|
|
|
|
free(f->name);
|
|
free(f->action);
|
|
// Form doesn't own its fields (Iformfield items do)
|
|
free(f);
|
|
}
|
|
|
|
static void
|
|
freeforms(Form* fhead)
|
|
{
|
|
Form* f;
|
|
Form* fnext;
|
|
|
|
for(f = fhead; f != nil; f = fnext) {
|
|
fnext = f->next;
|
|
freeform(f);
|
|
}
|
|
}
|
|
|
|
static void
|
|
freeanchor(Anchor* a)
|
|
{
|
|
if(a == nil)
|
|
return;
|
|
|
|
free(a->name);
|
|
free(a->href);
|
|
free(a);
|
|
}
|
|
|
|
static void
|
|
freeanchors(Anchor* ahead)
|
|
{
|
|
Anchor* a;
|
|
Anchor* anext;
|
|
|
|
for(a = ahead; a != nil; a = anext) {
|
|
anext = a->next;
|
|
freeanchor(a);
|
|
}
|
|
}
|
|
|
|
static void
|
|
freedestanchor(DestAnchor* da)
|
|
{
|
|
if(da == nil)
|
|
return;
|
|
|
|
free(da->name);
|
|
free(da);
|
|
}
|
|
|
|
static void
|
|
freedestanchors(DestAnchor* dahead)
|
|
{
|
|
DestAnchor* da;
|
|
DestAnchor* danext;
|
|
|
|
for(da = dahead; da != nil; da = danext) {
|
|
danext = da->next;
|
|
freedestanchor(da);
|
|
}
|
|
}
|
|
|
|
static void
|
|
freearea(Area* a)
|
|
{
|
|
if(a == nil)
|
|
return;
|
|
free(a->href);
|
|
free(a->coords);
|
|
}
|
|
|
|
static void freekidinfos(Kidinfo* khead);
|
|
|
|
static void
|
|
freekidinfo(Kidinfo* k)
|
|
{
|
|
if(k->isframeset) {
|
|
free(k->rows);
|
|
free(k->cols);
|
|
freekidinfos(k->kidinfos);
|
|
}
|
|
else {
|
|
free(k->src);
|
|
free(k->name);
|
|
}
|
|
free(k);
|
|
}
|
|
|
|
static void
|
|
freekidinfos(Kidinfo* khead)
|
|
{
|
|
Kidinfo* k;
|
|
Kidinfo* knext;
|
|
|
|
for(k = khead; k != nil; k = knext) {
|
|
knext = k->next;
|
|
freekidinfo(k);
|
|
}
|
|
}
|
|
|
|
static void
|
|
freemap(Map* m)
|
|
{
|
|
Area* a;
|
|
Area* anext;
|
|
|
|
if(m == nil)
|
|
return;
|
|
|
|
free(m->name);
|
|
for(a = m->areas; a != nil; a = anext) {
|
|
anext = a->next;
|
|
freearea(a);
|
|
}
|
|
free(m);
|
|
}
|
|
|
|
static void
|
|
freemaps(Map* mhead)
|
|
{
|
|
Map* m;
|
|
Map* mnext;
|
|
|
|
for(m = mhead; m != nil; m = mnext) {
|
|
mnext = m->next;
|
|
freemap(m);
|
|
}
|
|
}
|
|
|
|
void
|
|
freedocinfo(Docinfo* d)
|
|
{
|
|
if(d == nil)
|
|
return;
|
|
free(d->src);
|
|
free(d->base);
|
|
freeitem((Item*)d->backgrounditem);
|
|
free(d->refresh);
|
|
freekidinfos(d->kidinfo);
|
|
freeanchors(d->anchors);
|
|
freedestanchors(d->dests);
|
|
freeforms(d->forms);
|
|
freemaps(d->maps);
|
|
// tables, images, and formfields are freed when
|
|
// the items pointing at them are freed
|
|
free(d);
|
|
}
|
|
|
|
// Currently, someone else owns all the memory
|
|
// pointed to by things in a Pstate.
|
|
static void
|
|
freepstate(Pstate* p)
|
|
{
|
|
free(p);
|
|
}
|
|
|
|
static void
|
|
freepstatestack(Pstate* pshead)
|
|
{
|
|
Pstate* p;
|
|
Pstate* pnext;
|
|
|
|
for(p = pshead; p != nil; p = pnext) {
|
|
pnext = p->next;
|
|
free(p);
|
|
}
|
|
}
|
|
|
|
static int
|
|
Iconv(Fmt *f)
|
|
{
|
|
Item* it;
|
|
Itext* t;
|
|
Irule* r;
|
|
Iimage* i;
|
|
Ifloat* fl;
|
|
int state;
|
|
Formfield* ff;
|
|
Rune* ty;
|
|
Tablecell* c;
|
|
Table* tab;
|
|
char* p;
|
|
int cl;
|
|
int hang;
|
|
int indent;
|
|
int bi;
|
|
int nbuf;
|
|
char buf[BIGBUFSIZE];
|
|
|
|
it = va_arg(f->args, Item*);
|
|
bi = 0;
|
|
nbuf = sizeof(buf);
|
|
state = it->state;
|
|
nbuf = nbuf-1;
|
|
if(state&IFbrk) {
|
|
cl = state&(IFcleft|IFcright);
|
|
p = "";
|
|
if(cl) {
|
|
if(cl == (IFcleft|IFcright))
|
|
p = " both";
|
|
else if(cl == IFcleft)
|
|
p = " left";
|
|
else
|
|
p = " right";
|
|
}
|
|
bi = snprint(buf, nbuf, "brk(%d%s)", (state&IFbrksp)? 1 : 0, p);
|
|
}
|
|
if(state&IFnobrk)
|
|
bi += snprint(buf+bi, nbuf-bi, " nobrk");
|
|
if(!(state&IFwrap))
|
|
bi += snprint(buf+bi, nbuf-bi, " nowrap");
|
|
if(state&IFrjust)
|
|
bi += snprint(buf+bi, nbuf-bi, " rjust");
|
|
if(state&IFcjust)
|
|
bi += snprint(buf+bi, nbuf-bi, " cjust");
|
|
if(state&IFsmap)
|
|
bi += snprint(buf+bi, nbuf-bi, " smap");
|
|
indent = (state&IFindentmask) >> IFindentshift;
|
|
if(indent > 0)
|
|
bi += snprint(buf+bi, nbuf-bi, " indent=%d", indent);
|
|
hang = state&IFhangmask;
|
|
if(hang > 0)
|
|
bi += snprint(buf+bi, nbuf-bi, " hang=%d", hang);
|
|
|
|
switch(it->tag) {
|
|
case Itexttag:
|
|
t = (Itext*)it;
|
|
bi += snprint(buf+bi, nbuf-bi, " Text '%S', fnt=%d, fg=%x", t->s, t->fnt, t->fg);
|
|
break;
|
|
|
|
case Iruletag:
|
|
r = (Irule*)it;
|
|
bi += snprint(buf+bi, nbuf-bi, "Rule size=%d, al=%S, wspec=", r->size, stringalign(r->align));
|
|
bi += dimprint(buf+bi, nbuf-bi, r->wspec);
|
|
break;
|
|
|
|
case Iimagetag:
|
|
i = (Iimage*)it;
|
|
bi += snprint(buf+bi, nbuf-bi,
|
|
"Image src=%S, alt=%S, al=%S, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%S",
|
|
i->imsrc, i->altrep? i->altrep : L(Lempty), stringalign(i->align), i->imwidth, i->imheight,
|
|
i->hspace, i->vspace, i->border, i->map?i->map->name : L(Lempty));
|
|
break;
|
|
|
|
case Iformfieldtag:
|
|
ff = ((Iformfield*)it)->formfield;
|
|
if(ff->ftype == Ftextarea)
|
|
ty = L(Ltextarea);
|
|
else if(ff->ftype == Fselect)
|
|
ty = L(Lselect);
|
|
else {
|
|
ty = _revlookup(input_tab, NINPUTTAB, ff->ftype);
|
|
if(ty == nil)
|
|
ty = L(Lnone);
|
|
}
|
|
bi += snprint(buf+bi, nbuf-bi, "Formfield %S, fieldid=%d, formid=%d, name=%S, value=%S",
|
|
ty, ff->fieldid, ff->form->formid, ff->name? ff->name : L(Lempty),
|
|
ff->value? ff->value : L(Lempty));
|
|
break;
|
|
|
|
case Itabletag:
|
|
tab = ((Itable*)it)->table;
|
|
bi += snprint(buf+bi, nbuf-bi, "Table tableid=%d, width=", tab->tableid);
|
|
bi += dimprint(buf+bi, nbuf-bi, tab->width);
|
|
bi += snprint(buf+bi, nbuf-bi, ", nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n",
|
|
tab->nrow, tab->ncol, tab->ncell, tab->totw, tab->toth);
|
|
for(c = tab->cells; c != nil; c = c->next)
|
|
bi += snprint(buf+bi, nbuf-bi, "Cell %d.%d, at (%d,%d) ",
|
|
tab->tableid, c->cellid, c->row, c->col);
|
|
bi += snprint(buf+bi, nbuf-bi, "End of Table %d", tab->tableid);
|
|
break;
|
|
|
|
case Ifloattag:
|
|
fl = (Ifloat*)it;
|
|
bi += snprint(buf+bi, nbuf-bi, "Float, x=%d y=%d, side=%S, it=%I",
|
|
fl->x, fl->y, stringalign(fl->side), fl->item);
|
|
bi += snprint(buf+bi, nbuf-bi, "\n\t");
|
|
break;
|
|
|
|
case Ispacertag:
|
|
p = "";
|
|
switch(((Ispacer*)it)->spkind) {
|
|
case ISPnull:
|
|
p = "null";
|
|
break;
|
|
case ISPvline:
|
|
p = "vline";
|
|
break;
|
|
case ISPhspace:
|
|
p = "hspace";
|
|
break;
|
|
}
|
|
bi += snprint(buf+bi, nbuf-bi, "Spacer %s ", p);
|
|
break;
|
|
}
|
|
bi += snprint(buf+bi, nbuf-bi, " w=%d, h=%d, a=%d, anchor=%d\n",
|
|
it->width, it->height, it->ascent, it->anchorid);
|
|
buf[bi] = 0;
|
|
return fmtstrcpy(f, buf);
|
|
}
|
|
|
|
// String version of alignment 'a'
|
|
static Rune*
|
|
stringalign(int a)
|
|
{
|
|
Rune* s;
|
|
|
|
s = _revlookup(align_tab, NALIGNTAB, a);
|
|
if(s == nil)
|
|
s = L(Lnone);
|
|
return s;
|
|
}
|
|
|
|
// Put at most nbuf chars of representation of d into buf,
|
|
// and return number of characters put
|
|
static int
|
|
dimprint(char* buf, int nbuf, Dimen d)
|
|
{
|
|
int n;
|
|
int k;
|
|
|
|
n = 0;
|
|
n += snprint(buf, nbuf, "%d", dimenspec(d));
|
|
k = dimenkind(d);
|
|
if(k == Dpercent)
|
|
buf[n++] = '%';
|
|
if(k == Drelative)
|
|
buf[n++] = '*';
|
|
return n;
|
|
}
|
|
|
|
void
|
|
printitems(Item* items, char* msg)
|
|
{
|
|
Item* il;
|
|
|
|
fprint(2, "%s\n", msg);
|
|
il = items;
|
|
while(il != nil) {
|
|
fprint(2, "%I", il);
|
|
il = il->next;
|
|
}
|
|
}
|
|
|
|
static Genattr*
|
|
newgenattr(Rune* id, Rune* class, Rune* style, Rune* title, SEvent* events)
|
|
{
|
|
Genattr* g;
|
|
|
|
g = (Genattr*)emalloc(sizeof(Genattr));
|
|
g->id = id;
|
|
g->class = class;
|
|
g->style = style;
|
|
g->title = title;
|
|
g->events = events;
|
|
return g;
|
|
}
|
|
|
|
static Formfield*
|
|
newformfield(int ftype, int fieldid, Form* form, Rune* name,
|
|
Rune* value, int size, int maxlength, Formfield* link)
|
|
{
|
|
Formfield* ff;
|
|
|
|
ff = (Formfield*)emalloc(sizeof(Formfield));
|
|
ff->ftype = ftype;
|
|
ff->fieldid = fieldid;
|
|
ff->form = form;
|
|
ff->name = name;
|
|
ff->value = value;
|
|
ff->size = size;
|
|
ff->maxlength = maxlength;
|
|
ff->ctlid = -1;
|
|
ff->next = link;
|
|
return ff;
|
|
}
|
|
|
|
// Transfers ownership of value and display to Option.
|
|
static Option*
|
|
newoption(int selected, Rune* value, Rune* display, Option* link)
|
|
{
|
|
Option *o;
|
|
|
|
o = (Option*)emalloc(sizeof(Option));
|
|
o->selected = selected;
|
|
o->value = value;
|
|
o->display = display;
|
|
o->next = link;
|
|
return o;
|
|
}
|
|
|
|
static Form*
|
|
newform(int formid, Rune* name, Rune* action, int target, int method, Form* link)
|
|
{
|
|
Form* f;
|
|
|
|
f = (Form*)emalloc(sizeof(Form));
|
|
f->formid = formid;
|
|
f->name = name;
|
|
f->action = action;
|
|
f->target = target;
|
|
f->method = method;
|
|
f->nfields = 0;
|
|
f->fields = nil;
|
|
f->next = link;
|
|
return f;
|
|
}
|
|
|
|
static Table*
|
|
newtable(int tableid, Align align, Dimen width, int border,
|
|
int cellspacing, int cellpadding, Background bg, Token* tok, Table* link)
|
|
{
|
|
Table* t;
|
|
|
|
t = (Table*)emalloc(sizeof(Table));
|
|
t->tableid = tableid;
|
|
t->align = align;
|
|
t->width = width;
|
|
t->border = border;
|
|
t->cellspacing = cellspacing;
|
|
t->cellpadding = cellpadding;
|
|
t->background = bg;
|
|
t->caption_place = ALbottom;
|
|
t->caption_lay = nil;
|
|
t->tabletok = tok;
|
|
t->tabletok = nil;
|
|
t->next = link;
|
|
return t;
|
|
}
|
|
|
|
static Tablerow*
|
|
newtablerow(Align align, Background bg, int flags, Tablerow* link)
|
|
{
|
|
Tablerow* tr;
|
|
|
|
tr = (Tablerow*)emalloc(sizeof(Tablerow));
|
|
tr->align = align;
|
|
tr->background = bg;
|
|
tr->flags = flags;
|
|
tr->next = link;
|
|
return tr;
|
|
}
|
|
|
|
static Tablecell*
|
|
newtablecell(int cellid, int rowspan, int colspan, Align align, Dimen wspec, int hspec,
|
|
Background bg, int flags, Tablecell* link)
|
|
{
|
|
Tablecell* c;
|
|
|
|
c = (Tablecell*)emalloc(sizeof(Tablecell));
|
|
c->cellid = cellid;
|
|
c->lay = nil;
|
|
c->rowspan = rowspan;
|
|
c->colspan = colspan;
|
|
c->align = align;
|
|
c->flags = flags;
|
|
c->wspec = wspec;
|
|
c->hspec = hspec;
|
|
c->background = bg;
|
|
c->next = link;
|
|
return c;
|
|
}
|
|
|
|
static Anchor*
|
|
newanchor(int index, Rune* name, Rune* href, int target, Anchor* link)
|
|
{
|
|
Anchor* a;
|
|
|
|
a = (Anchor*)emalloc(sizeof(Anchor));
|
|
a->index = index;
|
|
a->name = name;
|
|
a->href = href;
|
|
a->target = target;
|
|
a->next = link;
|
|
return a;
|
|
}
|
|
|
|
static DestAnchor*
|
|
newdestanchor(int index, Rune* name, Item* item, DestAnchor* link)
|
|
{
|
|
DestAnchor* d;
|
|
|
|
d = (DestAnchor*)emalloc(sizeof(DestAnchor));
|
|
d->index = index;
|
|
d->name = name;
|
|
d->item = item;
|
|
d->next = link;
|
|
return d;
|
|
}
|
|
|
|
static SEvent*
|
|
newscriptevent(int type, Rune* script, SEvent* link)
|
|
{
|
|
SEvent* ans;
|
|
|
|
ans = (SEvent*)emalloc(sizeof(SEvent));
|
|
ans->type = type;
|
|
ans->script = script;
|
|
ans->next = link;
|
|
return ans;
|
|
}
|
|
|
|
static void
|
|
freescriptevents(SEvent* ehead)
|
|
{
|
|
SEvent* e;
|
|
SEvent* nexte;
|
|
|
|
e = ehead;
|
|
while(e != nil) {
|
|
nexte = e->next;
|
|
free(e->script);
|
|
free(e);
|
|
e = nexte;
|
|
}
|
|
}
|
|
|
|
static Dimen
|
|
makedimen(int kind, int spec)
|
|
{
|
|
Dimen d;
|
|
|
|
if(spec&Dkindmask) {
|
|
if(warn)
|
|
fprint(2, "warning: dimension spec too big: %d\n", spec);
|
|
spec = 0;
|
|
}
|
|
d.kindspec = kind|spec;
|
|
return d;
|
|
}
|
|
|
|
int
|
|
dimenkind(Dimen d)
|
|
{
|
|
return (d.kindspec&Dkindmask);
|
|
}
|
|
|
|
int
|
|
dimenspec(Dimen d)
|
|
{
|
|
return (d.kindspec&Dspecmask);
|
|
}
|
|
|
|
static Kidinfo*
|
|
newkidinfo(int isframeset, Kidinfo* link)
|
|
{
|
|
Kidinfo* ki;
|
|
|
|
ki = (Kidinfo*)emalloc(sizeof(Kidinfo));
|
|
ki->isframeset = isframeset;
|
|
if(!isframeset) {
|
|
ki->flags = FRhscrollauto|FRvscrollauto;
|
|
ki->marginw = FRKIDMARGIN;
|
|
ki->marginh = FRKIDMARGIN;
|
|
ki->framebd = 1;
|
|
}
|
|
ki->next = link;
|
|
return ki;
|
|
}
|
|
|
|
static Docinfo*
|
|
newdocinfo(void)
|
|
{
|
|
Docinfo* d;
|
|
|
|
d = (Docinfo*)emalloc(sizeof(Docinfo));
|
|
resetdocinfo(d);
|
|
return d;
|
|
}
|
|
|
|
static void
|
|
resetdocinfo(Docinfo* d)
|
|
{
|
|
memset(d, 0, sizeof(Docinfo));
|
|
d->background = makebackground(nil, White);
|
|
d->text = Black;
|
|
d->link = Blue;
|
|
d->vlink = Blue;
|
|
d->alink = Blue;
|
|
d->target = FTself;
|
|
d->chset = ISO_8859_1;
|
|
d->scripttype = TextJavascript;
|
|
d->frameid = -1;
|
|
}
|
|
|
|
// Use targetmap array to keep track of name <-> targetid mapping.
|
|
// Use real malloc(), and never free
|
|
static void
|
|
targetmapinit(void)
|
|
{
|
|
targetmapsize = 10;
|
|
targetmap = (StringInt*)emalloc(targetmapsize*sizeof(StringInt));
|
|
memset(targetmap, 0, targetmapsize*sizeof(StringInt));
|
|
targetmap[0].key = _Strdup(L(L_top));
|
|
targetmap[0].val = FTtop;
|
|
targetmap[1].key = _Strdup(L(L_self));
|
|
targetmap[1].val = FTself;
|
|
targetmap[2].key = _Strdup(L(L_parent));
|
|
targetmap[2].val = FTparent;
|
|
targetmap[3].key = _Strdup(L(L_blank));
|
|
targetmap[3].val = FTblank;
|
|
ntargets = 4;
|
|
}
|
|
|
|
int
|
|
targetid(Rune* s)
|
|
{
|
|
int i;
|
|
int n;
|
|
|
|
n = _Strlen(s);
|
|
if(n == 0)
|
|
return FTself;
|
|
for(i = 0; i < ntargets; i++)
|
|
if(_Strcmp(s, targetmap[i].key) == 0)
|
|
return targetmap[i].val;
|
|
if(i >= targetmapsize) {
|
|
targetmapsize += 10;
|
|
targetmap = (StringInt*)erealloc(targetmap, targetmapsize*sizeof(StringInt));
|
|
}
|
|
targetmap[i].key = (Rune*)emalloc((n+1)*sizeof(Rune));
|
|
memmove(targetmap[i].key, s, (n+1)*sizeof(Rune));
|
|
targetmap[i].val = i;
|
|
ntargets++;
|
|
return i;
|
|
}
|
|
|
|
Rune*
|
|
targetname(int targid)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < ntargets; i++)
|
|
if(targetmap[i].val == targid)
|
|
return targetmap[i].key;
|
|
return L(Lquestion);
|
|
}
|
|
|
|
// Convert HTML color spec to RGB value, returning dflt if can't.
|
|
// Argument is supposed to be a valid HTML color, or "".
|
|
// Return the RGB value of the color, using dflt if s
|
|
// is nil or an invalid color.
|
|
static int
|
|
color(Rune* s, int dflt)
|
|
{
|
|
int v;
|
|
Rune* rest;
|
|
|
|
if(s == nil)
|
|
return dflt;
|
|
if(_lookup(color_tab, NCOLORS, s, _Strlen(s), &v))
|
|
return v;
|
|
if(s[0] == '#')
|
|
s++;
|
|
v = _Strtol(s, &rest, 16);
|
|
if(*rest == 0)
|
|
return v;
|
|
return dflt;
|
|
}
|
|
|
|
// Debugging
|
|
|
|
#define HUGEPIX 10000
|
|
|
|
// A "shallow" validitem, that doesn't follow next links
|
|
// or descend into tables.
|
|
static int
|
|
validitem(Item* i)
|
|
{
|
|
int ok;
|
|
Itext* ti;
|
|
Irule* ri;
|
|
Iimage* ii;
|
|
Ifloat* fi;
|
|
int a;
|
|
|
|
ok = (i->tag >= Itexttag && i->tag <= Ispacertag) &&
|
|
(i->next == nil || validptr(i->next)) &&
|
|
(i->width >= 0 && i->width < HUGEPIX) &&
|
|
(i->height >= 0 && i->height < HUGEPIX) &&
|
|
(i->ascent > -HUGEPIX && i->ascent < HUGEPIX) &&
|
|
(i->anchorid >= 0) &&
|
|
(i->genattr == nil || validptr(i->genattr));
|
|
// also, could check state for ridiculous combinations
|
|
// also, could check anchorid for within-doc-range
|
|
if(ok)
|
|
switch(i->tag) {
|
|
case Itexttag:
|
|
ti = (Itext*)i;
|
|
ok = validStr(ti->s) &&
|
|
(ti->fnt >= 0 && ti->fnt < NumStyle*NumSize) &&
|
|
(ti->ul == ULnone || ti->ul == ULunder || ti->ul == ULmid);
|
|
break;
|
|
case Iruletag:
|
|
ri = (Irule*)i;
|
|
ok = (validvalign(ri->align) || validhalign(ri->align)) &&
|
|
(ri->size >=0 && ri->size < HUGEPIX);
|
|
break;
|
|
case Iimagetag:
|
|
ii = (Iimage*)i;
|
|
ok = (ii->imsrc == nil || validptr(ii->imsrc)) &&
|
|
(ii->item.width >= 0 && ii->item.width < HUGEPIX) &&
|
|
(ii->item.height >= 0 && ii->item.height < HUGEPIX) &&
|
|
(ii->imwidth >= 0 && ii->imwidth < HUGEPIX) &&
|
|
(ii->imheight >= 0 && ii->imheight < HUGEPIX) &&
|
|
(ii->altrep == nil || validStr(ii->altrep)) &&
|
|
(ii->map == nil || validptr(ii->map)) &&
|
|
(validvalign(ii->align) || validhalign(ii->align)) &&
|
|
(ii->nextimage == nil || validptr(ii->nextimage));
|
|
break;
|
|
case Iformfieldtag:
|
|
ok = validformfield(((Iformfield*)i)->formfield);
|
|
break;
|
|
case Itabletag:
|
|
ok = validptr((Itable*)i);
|
|
break;
|
|
case Ifloattag:
|
|
fi = (Ifloat*)i;
|
|
ok = (fi->side == ALleft || fi->side == ALright) &&
|
|
validitem(fi->item) &&
|
|
(fi->item->tag == Iimagetag || fi->item->tag == Itabletag);
|
|
break;
|
|
case Ispacertag:
|
|
a = ((Ispacer*)i)->spkind;
|
|
ok = a==ISPnull || a==ISPvline || a==ISPhspace || a==ISPgeneral;
|
|
break;
|
|
default:
|
|
ok = 0;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// "deep" validation, that checks whole list of items,
|
|
// and descends into tables and floated tables.
|
|
// nil is ok for argument.
|
|
int
|
|
validitems(Item* i)
|
|
{
|
|
int ok;
|
|
Item* ii;
|
|
|
|
ok = 1;
|
|
while(i != nil && ok) {
|
|
ok = validitem(i);
|
|
if(ok) {
|
|
if(i->tag == Itabletag) {
|
|
ok = validtable(((Itable*)i)->table);
|
|
}
|
|
else if(i->tag == Ifloattag) {
|
|
ii = ((Ifloat*)i)->item;
|
|
if(ii->tag == Itabletag)
|
|
ok = validtable(((Itable*)ii)->table);
|
|
}
|
|
}
|
|
if(!ok) {
|
|
fprint(2, "invalid item: %I\n", i);
|
|
}
|
|
i = i->next;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
validformfield(Formfield* f)
|
|
{
|
|
int ok;
|
|
|
|
ok = (f->next == nil || validptr(f->next)) &&
|
|
(f->ftype >= 0 && f->ftype <= Ftextarea) &&
|
|
f->fieldid >= 0 &&
|
|
(f->form == nil || validptr(f->form)) &&
|
|
(f->name == nil || validStr(f->name)) &&
|
|
(f->value == nil || validStr(f->value)) &&
|
|
(f->options == nil || validptr(f->options)) &&
|
|
(f->image == nil || validitem(f->image)) &&
|
|
(f->events == nil || validptr(f->events));
|
|
// when all built, should have f->fieldid < f->form->nfields,
|
|
// but this may be called during build...
|
|
return ok;
|
|
}
|
|
|
|
// "deep" validation -- checks cell contents too
|
|
static int
|
|
validtable(Table* t)
|
|
{
|
|
int ok;
|
|
int i, j;
|
|
Tablecell* c;
|
|
|
|
ok = (t->next == nil || validptr(t->next)) &&
|
|
t->nrow >= 0 &&
|
|
t->ncol >= 0 &&
|
|
t->ncell >= 0 &&
|
|
validalign(t->align) &&
|
|
validdimen(t->width) &&
|
|
(t->border >= 0 && t->border < HUGEPIX) &&
|
|
(t->cellspacing >= 0 && t->cellspacing < HUGEPIX) &&
|
|
(t->cellpadding >= 0 && t->cellpadding < HUGEPIX) &&
|
|
validitems(t->caption) &&
|
|
(t->caption_place == ALtop || t->caption_place == ALbottom) &&
|
|
(t->totw >= 0 && t->totw < HUGEPIX) &&
|
|
(t->toth >= 0 && t->toth < HUGEPIX) &&
|
|
(t->tabletok == nil || validptr(t->tabletok));
|
|
// during parsing, t->rows has list;
|
|
// only when parsing is done is t->nrow set > 0
|
|
if(ok && t->nrow > 0 && t->ncol > 0) {
|
|
// table is "finished"
|
|
for(i = 0; i < t->nrow && ok; i++)
|
|
ok = validtablerow(t->rows+i);
|
|
for(j = 0; j < t->ncol && ok; j++)
|
|
ok = validtablecol(t->cols+j);
|
|
for(c = t->cells; c != nil && ok; c = c->next)
|
|
ok = validtablecell(c);
|
|
for(i = 0; i < t->nrow && ok; i++)
|
|
for(j = 0; j < t->ncol && ok; j++)
|
|
ok = validptr(t->grid[i][j]);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
validvalign(int a)
|
|
{
|
|
return a == ALnone || a == ALmiddle || a == ALbottom || a == ALtop || a == ALbaseline;
|
|
}
|
|
|
|
static int
|
|
validhalign(int a)
|
|
{
|
|
return a == ALnone || a == ALleft || a == ALcenter || a == ALright ||
|
|
a == ALjustify || a == ALchar;
|
|
}
|
|
|
|
static int
|
|
validalign(Align a)
|
|
{
|
|
return validhalign(a.halign) && validvalign(a.valign);
|
|
}
|
|
|
|
static int
|
|
validdimen(Dimen d)
|
|
{
|
|
int ok;
|
|
int s;
|
|
|
|
ok = 0;
|
|
s = d.kindspec&Dspecmask;
|
|
switch(d.kindspec&Dkindmask) {
|
|
case Dnone:
|
|
ok = s==0;
|
|
break;
|
|
case Dpixels:
|
|
ok = s < HUGEPIX;
|
|
break;
|
|
case Dpercent:
|
|
case Drelative:
|
|
ok = 1;
|
|
break;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
validtablerow(Tablerow* r)
|
|
{
|
|
return (r->cells == nil || validptr(r->cells)) &&
|
|
(r->height >= 0 && r->height < HUGEPIX) &&
|
|
(r->ascent > -HUGEPIX && r->ascent < HUGEPIX) &&
|
|
validalign(r->align);
|
|
}
|
|
|
|
static int
|
|
validtablecol(Tablecol* c)
|
|
{
|
|
return c->width >= 0 && c->width < HUGEPIX
|
|
&& validalign(c->align);
|
|
}
|
|
|
|
static int
|
|
validtablecell(Tablecell* c)
|
|
{
|
|
int ok;
|
|
|
|
ok = (c->next == nil || validptr(c->next)) &&
|
|
(c->nextinrow == nil || validptr(c->nextinrow)) &&
|
|
(c->content == nil || validptr(c->content)) &&
|
|
(c->lay == nil || validptr(c->lay)) &&
|
|
c->rowspan >= 0 &&
|
|
c->colspan >= 0 &&
|
|
validalign(c->align) &&
|
|
validdimen(c->wspec) &&
|
|
c->row >= 0 &&
|
|
c->col >= 0;
|
|
if(ok) {
|
|
if(c->content != nil)
|
|
ok = validitems(c->content);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static int
|
|
validptr(void* p)
|
|
{
|
|
// TODO: a better job of this.
|
|
// For now, just dereference, which cause a bomb
|
|
// if not valid
|
|
static char c;
|
|
|
|
c = *((char*)p);
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
validStr(Rune* s)
|
|
{
|
|
return s != nil && validptr(s);
|
|
}
|