ref: 09cf3c0ae6c0fed01e32a149f4668b7a7d52944b
dir: /appl/charon/build.b/
implement Build; include "common.m"; # local copies from CU sys: Sys; CU: CharonUtils; ByteSource, CImage, ImageCache, color, Nameval: import CU; D: Draw; Point, Rect, Image: import D; S: String; T: StringIntTab; C: Ctype; LX: Lex; RBRA, Token, TokenSource: import LX; U: Url; Parsedurl: import U; J: Script; ctype: array of byte; whitespace : con " \t\n\r"; notwhitespace : con "^ \t\n\r"; # These tables must be sorted align_tab := array[] of { T->StringInt ("baseline", int Abaseline), ("bottom", int Abottom), ("center", int Acenter), ("char", int Achar), ("justify", int Ajustify), ("left", int Aleft), ("middle", int Amiddle), ("right", int Aright), ("top", int Atop), }; input_tab := array[] of { T->StringInt ("button", Fbutton), ("checkbox", Fcheckbox), ("file", Ffile), ("hidden", Fhidden), ("image", Fimage), ("password", Fpassword), ("radio", Fradio), ("reset", Freset), ("submit", Fsubmit), ("text", Ftext), }; clear_tab := array[] of { T->StringInt ("all", IFcleft|IFcright), ("left", IFcleft), ("right", IFcright), }; fscroll_tab := array[] of { T->StringInt ("auto", FRhscrollauto|FRvscrollauto), ("no", FRnoscroll), ("yes", FRhscroll|FRvscroll), }; # 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. SPBefore: con byte 2; SPAfter: con byte 4; BL: con byte 1; BLBA: con BL|SPBefore|SPAfter; blockbrk := array[LX->Numtags] of { LX->Taddress => BLBA, LX->Tblockquote => BLBA, LX->Tcenter => BL, LX->Tdir => BLBA, LX->Tdiv => BL, LX->Tdd => BL, LX->Tdl => BLBA, LX->Tdt => BL, LX->Tform => BLBA, # headings and tables get breaks added manually LX->Th1 => BL, LX->Th2 => BL, LX->Th3 => BL, LX->Th4 => BL, LX->Th5 => BL, LX->Th6 => BL, LX->Thr => BL, LX->Tisindex => BLBA, LX->Tli => BL, LX->Tmenu => BLBA, LX->Tol => BLBA, LX->Tp => BLBA, LX->Tpre => BLBA, LX->Tul => BLBA, LX->Txmp => BLBA, * => byte 0 }; # attrinfo is information about attributes. # The AGEN value means that the attribute is generic (applies to almost all elements) AGEN: con byte 1; attrinfo := array[LX->Numattrs] of { LX->Aid => AGEN, LX->Aclass => AGEN, LX->Astyle => AGEN, LX->Atitle => AGEN, LX->Aonabort => AGEN, LX->Aonblur => AGEN, LX->Aonchange => AGEN, LX->Aonclick => AGEN, LX->Aondblclick => AGEN, LX->Aonerror => AGEN, LX->Aonfocus => AGEN, LX->Aonkeydown => AGEN, LX->Aonkeypress => AGEN, LX->Aonkeyup => AGEN, LX->Aonload => AGEN, LX->Aonmousedown => AGEN, LX->Aonmousemove => AGEN, LX->Aonmouseout => AGEN, LX->Aonmouseover => AGEN, LX->Aonmouseup => AGEN, LX->Aonreset => AGEN, LX->Aonresize => AGEN, LX->Aonselect => AGEN, LX->Aonsubmit => AGEN, LX->Aonunload => AGEN, * => byte 0 }; # Some constants FRKIDMARGIN: con 6; # default margin around kid frames IMGHSPACE: con 0; # default hspace for images (0 matches IE, Netscape) IMGVSPACE: con 0; # default vspace for images FLTIMGHSPACE: con 2; # default hspace for float images TABSP: con 2; # default cellspacing for tables TABPAD: con 2; # default cell padding for tables LISTTAB: con 1; # number of tabs to indent lists BQTAB: con 1; # number of tabs to indent blockquotes HRSZ: con 2; # thickness of horizontal rules SUBOFF: con 4; # vertical offset for subscripts SUPOFF: con 6; # vertical offset for superscripts NBSP: con ' '; # non-breaking space character dbg := 0; warn := 0; doscripts := 0; utf8 : Btos; latin1 : Btos; init(cu: CharonUtils) { CU = cu; sys = load Sys Sys->PATH; D = load Draw Draw->PATH; S = load String String->PATH;; T = load StringIntTab StringIntTab->PATH; U = load Url Url->PATH; if (U != nil) U->init(); C = cu->C; J = cu->J; LX = cu->LX; ctype = C->ctype; utf8 = CU->getconv("utf8"); latin1 = CU->getconv("latin1"); if (utf8 == nil || latin1 == nil) { sys->print("cannot load utf8 or latin1 charset converter\n"); raise "EXinternal:build init"; } dbg = int (CU->config).dbg['h']; warn = (int (CU->config).dbg['w']) || dbg; doscripts = (CU->config).doscripts && J != nil; } # Assume f has been reset, and then had any values from HTTP headers # filled in (e.g., base, chset). ItemSource.new(bs: ref ByteSource, f: ref Layout->Frame, mtype: int) : ref ItemSource { di := f.doc; # sys->print("chset = %s\n", di.chset); chset := CU->getconv(di.chset); if (chset == nil) chset = latin1; ts := TokenSource.new(bs, chset, mtype); psstk := list of { Pstate.new() }; if(mtype != CU->TextHtml) { ps := hd psstk; ps.curstate &= ~IFwrap; ps.literal = 1; pushfontstyle(ps, FntT); } return ref ItemSource(ts, mtype, di, f, psstk, 0, 0, 0, 0, nil, nil, nil, nil, nil, nil, nil); } ItemSource.getitems(is: self ref ItemSource) : ref Item { psstk := is.psstk; ps := hd psstk; # ps is always same as hd psstk curtab: ref Table = nil; # curtab is always same as hd is.tabstk if(is.tabstk != nil) curtab = hd is.tabstk; toks := is.toks; is.toks = nil; tokslen := len toks; toki := 0; di := is.doc; TokLoop: for(;; toki++) { if(toki >= tokslen) { outerps := lastps(psstk); if(outerps.items.next != nil) break; toks = is.ts.gettoks(); tokslen = len toks; if(dbg) sys->print("build: got %d tokens from token source\n", tokslen); if(tokslen == 0) break; toki = 0; } tok := toks[toki]; if(dbg > 1) sys->print("build: curstate %ux, token %s\n", ps.curstate, tok.tostring()); tag := tok.tag; brk := byte 0; brksp := 0; if(tag < LX->Numtags) { brk = blockbrk[tag]; if((brk&SPBefore) != byte 0) brksp = 1; } else if(tag < LX->Numtags+RBRA) { brk = blockbrk[tag-RBRA]; if((brk&SPAfter) != byte 0) brksp = 1; } if(brk != byte 0) { addbrk(ps, brksp, 0); if(ps.inpar) { popjust(ps); ps.inpar = 0; } } # check common case first (Data), then case statement on tag if(tag == LX->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; if(!ps.literal) { i := 0; j := len s; 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 < LX->Numtags && pt != LX->Timg && j>0 && s[0]=='\n') i++; } if(toki < tokslen-1) { nt := toks[toki+1].tag; if(nt >= RBRA && nt < LX->Numtags+RBRA && j>i && s[j-1]=='\n') j--; } if(i>0 || j <len s) s = s[i:j]; } if(ps.skipwhite) { s = S->drop(s, whitespace); if(s != "") ps.skipwhite = 0; } if(s != "") addtext(ps, s); } else case 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. LX->Ta => if(ps.curanchor != 0) { if(warn) sys->print("warning: nested <A> or missing </A>\n"); endanchor(ps, di.text); } name := aval(tok, LX->Aname); href := aurlval(tok, LX->Ahref, nil, di.base); target := astrval(tok, LX->Atarget, di.target); ga := getgenattr(tok); evl : list of Lex->Attr = nil; if(ga != nil) { evl = ga.events; if(evl != nil && doscripts) di.hasscripts = 1; } # ignore rel, rev, and title attrs if(href != nil) { di.anchors = ref Anchor(++is.nanchors, name, href, target, evl, 0) :: di.anchors; ps.curanchor = is.nanchors; ps.curfg = di.link; ps.fgstk = ps.curfg :: ps.fgstk; # underline, too ps.ulstk = ULunder :: ps.ulstk; ps.curul = ULunder; } if(name != nil) { # add a null item to be destination brkstate := ps.curstate & IFbrk; additem(ps, Item.newspacer(ISPnull, 0), tok); ps.curstate |= brkstate; # not quite right di.dests = ref DestAnchor(++is.nanchors, name, ps.lastit) :: di.dests; } LX->Ta+RBRA => endanchor(ps, di.text); # <!ELEMENT APPLET - - (PARAM | %text)* > # We can't do applets, so ignore PARAMS, and let # the %text contents appear for the alternative rep LX->Tapplet or LX->Tapplet+RBRA => if(warn && tag == LX->Tapplet) sys->print("warning: <APPLET> ignored\n"); # <!ELEMENT AREA - O EMPTY> LX->Tarea => map := is.curmap; if(map == nil) { if(warn) sys->print("warning: <AREA> not inside <MAP>\n"); continue; } map.areas = Area(S->tolower(astrval(tok, LX->Ashape, "rect")), aurlval(tok, LX->Ahref, nil, di.base), astrval(tok, LX->Atarget, di.target), dimlist(tok, LX->Acoords)) :: map.areas; # <!ELEMENT (B|STRONG) - - (%text)*> LX->Tb or LX->Tstrong => pushfontstyle(ps, FntB); LX->Tb+RBRA or LX->Tcite+RBRA or LX->Tcode+RBRA or LX->Tdfn+RBRA or LX->Tem+RBRA or LX->Tkbd+RBRA or LX->Ti+RBRA or LX->Tsamp+RBRA or LX->Tstrong+RBRA or LX->Ttt+RBRA or LX->Tvar+RBRA or LX->Taddress+RBRA => popfontstyle(ps); # <!ELEMENT BASE - O EMPTY> LX->Tbase => di.base = aurlval(tok, LX->Ahref, di.base, di.base); di.target = astrval(tok, LX->Atarget, di.target); # <!ELEMENT BASEFONT - O EMPTY> LX->Tbasefont => ps.adjsize = aintval(tok, LX->Asize, 3) - 3; # <!ELEMENT (BIG|SMALL) - - (%text)*> LX->Tbig or LX->Tsmall => sz := ps.adjsize; if(tag == LX->Tbig) sz += Large; else sz += Small; pushfontsize(ps, sz); LX->Tbig+RBRA or LX->Tsmall+RBRA => popfontsize(ps); # <!ELEMENT BLOCKQUOTE - - %body.content> LX->Tblockquote => changeindent(ps, BQTAB); LX->Tblockquote+RBRA => changeindent(ps, -BQTAB); # <!ELEMENT BODY O O %body.content> LX->Tbody => ps.skipping = 0; bg := Background(nil, color(aval(tok, LX->Abgcolor), di.background.color)); bgurl := aurlval(tok, LX->Abackground, nil, di.base); if(bgurl != nil) { pick ni := Item.newimage(di, bgurl, nil,"", Anone, 0, 0, 0, 0, 0, 0, 1, nil, nil, nil){ Iimage => bg.image = ni; } di.images = bg.image :: di.images; } di.background = ps.curbg = bg; ps.curbg.image = nil; di.text = color(aval(tok, LX->Atext), di.text); di.link = color(aval(tok, LX->Alink), di.link); di.vlink = color(aval(tok, LX->Avlink), di.vlink); di.alink = color(aval(tok, LX->Aalink), di.alink); if(doscripts) { ga := getgenattr(tok); if(ga != nil && ga.events != nil) { di.events = ga.events; di.hasscripts = 1; } } if(di.text != ps.curfg) { ps.curfg = di.text; ps.fgstk = nil; } LX->Tbody+RBRA => # HTML spec says ignore things after </body>, # but IE and Netscape don't # ps.skipping = 1; ; # <!ELEMENT BR - O EMPTY> LX->Tbr => addlinebrk(ps, atabval(tok, LX->Aclear, clear_tab, 0)); # <!ELEMENT CAPTION - - (%text;)*> LX->Tcaption => if(curtab == nil) { if(warn) sys->print("warning: <CAPTION> outside <TABLE>\n"); continue; } if(curtab.caption != nil) { if(warn) sys->print("warning: more than one <CAPTION> in <TABLE>\n"); continue; } ps = Pstate.new(); psstk = ps :: psstk; curtab.caption_place =atabbval(tok, LX->Aalign, align_tab, Atop); LX->Tcaption+RBRA => if(curtab == nil || tl psstk == nil) { if(warn) sys->print("warning: unexpected </CAPTION>\n"); continue; } curtab.caption = ps.items.next; psstk = tl psstk; ps = hd psstk; LX->Tcenter or LX->Tdiv => if(tag == LX->Tcenter) al := Acenter; else al = atabbval(tok, LX->Aalign, align_tab, ps.curjust); pushjust(ps, al); LX->Tcenter+RBRA or LX->Tdiv+RBRA => popjust(ps); # <!ELEMENT DD - O %flow > LX->Tdd => if(ps.hangstk == nil) { if(warn) sys->print("warning: <DD> not inside <DL\n"); continue; } h := hd ps.hangstk; if(h != 0) changehang(ps, -10*LISTTAB); else addbrk(ps, 0, 0); ps.hangstk = 0 :: ps.hangstk; #<!ELEMENT (DIR|MENU) - - (LI)+ -(%block) > #<!ELEMENT (OL|UL) - - (LI)+> LX->Tdir or LX->Tmenu or LX->Tol or LX->Tul => changeindent(ps, LISTTAB); if(tag == LX->Tol) tydef := LT1; else tydef = LTdisc; start := aintval(tok, LX->Astart, 1); ps.listtypestk = listtyval(tok, tydef) :: ps.listtypestk; ps.listcntstk = start :: ps.listcntstk; LX->Tdir+RBRA or LX->Tmenu+RBRA or LX->Tol+RBRA or LX->Tul+RBRA => if(ps.listtypestk == nil) { if(warn) sys->print("warning: %s ended no list\n", tok.tostring()); continue; } addbrk(ps, 0, 0); ps.listtypestk = tl ps.listtypestk; ps.listcntstk = tl ps.listcntstk; changeindent(ps, -LISTTAB); # <!ELEMENT DL - - (DT|DD)+ > LX->Tdl => changeindent(ps, LISTTAB); ps.hangstk = 0 :: ps.hangstk; LX->Tdl+RBRA => if(ps.hangstk == nil) { if(warn) sys->print("warning: unexpected </DL>\n"); continue; } changeindent(ps, -LISTTAB); if(hd ps.hangstk != 0) changehang(ps, -10*LISTTAB); ps.hangstk = tl ps.hangstk; # <!ELEMENT DT - O (%text)* > LX->Tdt => if(ps.hangstk == nil) { if(warn) sys->print("warning: <DT> not inside <DL>\n"); continue; } h := hd ps.hangstk; ps.hangstk = tl ps.hangstk; if(h != 0) changehang(ps, -10*LISTTAB); changehang(ps, 10*LISTTAB); ps.hangstk = 1 :: ps.hangstk; # <!ELEMENT FONT - - (%text)*> LX->Tfont => sz := stackhd(ps.fntsizestk, Normal); (szfnd, nsz) := tok.aval(LX->Asize); if(szfnd) { if(S->prefix("+", nsz)) sz = Normal + int (nsz[1:]) + ps.adjsize; else if(S->prefix("-", nsz)) sz = Normal - int (nsz[1:]) + ps.adjsize; else if(nsz != "") sz = Normal + ( int nsz - 3); } ps.curfg = color(aval(tok, LX->Acolor), ps.curfg); ps.fgstk = ps.curfg :: ps.fgstk; pushfontsize(ps, sz); LX->Tfont+RBRA => if(ps.fgstk == nil) { if(warn) sys->print("warning: unexpected </FONT>\n"); continue; } ps.fgstk = tl ps.fgstk; if(ps.fgstk == nil) ps.curfg = di.text; else ps.curfg = hd ps.fgstk; popfontsize(ps); # <!ELEMENT FORM - - %body.content -(FORM) > LX->Tform => if(is.curform != nil) { if(warn) sys->print("warning: <FORM> nested inside another\n"); continue; } action := aurlval(tok, LX->Aaction, di.base, di.base); name := astrval(tok, LX->Aname, aval(tok, LX->Aid)); target := astrval(tok, LX->Atarget, di.target); smethod := S->tolower(astrval(tok, LX->Amethod, "get")); method := CU->HGet; if(smethod == "post") method = CU->HPost; else if(smethod != "get") { if(warn) sys->print("warning: unknown form method %s\n", smethod); } (ecfnd, enctype) := tok.aval(LX->Aenctype); if(warn && ecfnd && enctype != "application/x-www-form-urlencoded") sys->print("form enctype %s not handled\n", enctype); ga := getgenattr(tok); evl : list of Lex->Attr = nil; if(ga != nil) { evl = ga.events; if(evl != nil && doscripts) di.hasscripts = 1; } frm := Form.new(++is.nforms, name, action, target, method, evl); di.forms = frm :: di.forms; is.curform = frm; LX->Tform+RBRA => if(is.curform == nil) { if(warn) sys->print("warning: unexpected </FORM>\n"); continue; } # put fields back in input order fields : list of ref Formfield = nil; for(fl := is.curform.fields; fl != nil; fl = tl fl) fields = hd fl :: fields; is.curform.fields = fields; is.curform.state = FormDone; is.curform = nil; # HTML 4 # <!ELEMENT FRAME - O EMPTY> LX->Tframe => if(is.kidstk == nil) { if(warn) sys->print("warning: <FRAME> not in <FRAMESET>\n"); continue; } ks := hd is.kidstk; kd := Kidinfo.new(0); kd.src = aurlval(tok, LX->Asrc, nil, di.base); kd.name = aval(tok, LX->Aname); if(kd.name == "") kd.name = "_fr" + string (++is.nframes); kd.marginw = aintval(tok, LX->Amarginwidth, 0); kd.marginh = aintval(tok, LX->Amarginheight, 0); kd.framebd = aintval(tok, LX->Aframeborder, ks.framebd); kd.flags = atabval(tok, LX->Ascrolling, fscroll_tab, kd.flags); norsz := aboolval(tok, LX->Anoresize); if(norsz) kd.flags |= FRnoresize; ks.kidinfos = kd :: ks.kidinfos; # HTML 4 # <!ELEMENT FRAMESET - - (FRAME|FRAMESET)+> LX->Tframeset => ks := Kidinfo.new(1); if(is.kidstk == nil) di.kidinfo = ks; else { pks := hd is.kidstk; pks.kidinfos = ks :: pks.kidinfos; } is.kidstk = ks :: is.kidstk; ks.framebd = aintval(tok, LX->Aborder, 1); ks.rows = dimlist(tok, LX->Arows); if(ks.rows == nil) ks.rows = array[] of {Dimen.make(Dpercent,100)}; ks.cols = dimlist(tok, LX->Acols); if(ks.cols == nil) ks.cols = array[] of {Dimen.make(Dpercent,100)}; if(doscripts) { ga := getgenattr(tok); if(ga != nil && ga.events != nil) { di.events = ga.events; di.hasscripts = 1; } } LX->Tframeset+RBRA => if(is.kidstk == nil) { if(warn) sys->print("warning: unexpected </FRAMESET>\n"); continue; } ks := hd is.kidstk; # put kids back in original order # and add blank frames to fill out cells n := (len ks.rows) * (len ks.cols); nblank := n - len ks.kidinfos; while(nblank-- > 0) ks.kidinfos = Kidinfo.new(0) :: ks.kidinfos; kids : list of ref Kidinfo = nil; for(kl := ks.kidinfos; kl != nil; kl = tl kl) kids = hd kl :: kids; ks.kidinfos= kids; is.kidstk = tl is.kidstk; if(is.kidstk == nil) { for(;;) { toks = is.ts.gettoks(); if(len toks == 0) break; } tokslen = 0; } # <!ELEMENT H1 - - (%text;)*>, etc. LX->Th1 or LX->Th2 or LX->Th3 or LX->Th4 or LX->Th5 or LX->Th6 => # don't want extra space if this is first addition # to this item list (BUG: problem if first of bufferful) bramt := 1; if(ps.items == ps.lastit) bramt = 0; addbrk(ps, bramt, IFcleft|IFcright); # assume Th2 = Th1+1, etc. sz := Verylarge - (tag - LX->Th1); if(sz < Tiny) sz = Tiny; pushfontsize(ps, sz); sty := stackhd(ps.fntstylestk, FntR); if(tag == LX->Th1) sty = FntB; pushfontstyle(ps, sty); pushjust(ps, atabbval(tok, LX->Aalign, align_tab, ps.curjust)); ps.skipwhite = 1; LX->Th1+RBRA or LX->Th2+RBRA or LX->Th3+RBRA or LX->Th4+RBRA or LX->Th5+RBRA or LX->Th6+RBRA => addbrk(ps, 1, IFcleft|IFcright); popfontsize(ps); popfontstyle(ps); popjust(ps); LX->Thead => # HTML spec says ignore regular markup in head, # but Netscape and IE don't # ps.skipping = 1; ; LX->Thead+RBRA => ps.skipping = 0; # <!ELEMENT HR - O EMPTY> LX->Thr => al := atabbval(tok, LX->Aalign, align_tab, Acenter); sz := aintval(tok, LX->Asize, HRSZ); wd := makedimen(tok, LX->Awidth); if(wd.kind() == Dnone) wd = Dimen.make(Dpercent, 100); nosh := aboolval(tok, LX->Anoshade); additem(ps, Item.newrule(al, sz, nosh, wd), tok); addbrk(ps, 0, 0); # <!ELEMENT (I|CITE|DFN|EM|VAR) - - (%text)*> LX->Ti or LX->Tcite or LX->Tdfn or LX->Tem or LX->Tvar or LX->Taddress => pushfontstyle(ps, FntI); # <!ELEMENT IMG - O EMPTY> LX->Timage or # common html error supported by other browsers LX->Timg => tok.tag = LX->Timg; map : ref Map = nil; usemap := aval(tok, LX->Ausemap); oldcuranchor := ps.curanchor; if(usemap != "") { # can't handle non-local maps if(!S->prefix("#", usemap)) { if(warn) sys->print("warning: can't handle non-local map %s\n", usemap); } else { map = getmap(di, usemap[1:]); if(ps.curanchor == 0) { # make an anchor so charon's easy test for whether # there's an action for the item works di.anchors = ref Anchor(++is.nanchors, "", nil, di.target, nil, 0) :: di.anchors; ps.curanchor = is.nanchors; } } } align := atabbval(tok, LX->Aalign, align_tab, Abottom); dfltbd := 0; if(ps.curanchor != 0) dfltbd = 2; src := aurlval(tok, LX->Asrc, nil, di.base); if(src == nil) { if(warn) sys->print("warning: <img> has no src attribute\n"); ps.curanchor = oldcuranchor; continue; } img := Item.newimage(di, src, aurlval(tok, LX->Alowsrc, nil, di.base), aval(tok, LX->Aalt), align, aintval(tok, LX->Awidth, 0), aintval(tok, LX->Aheight, 0), aintval(tok, LX->Ahspace, IMGHSPACE), aintval(tok, LX->Avspace, IMGVSPACE), aintval(tok, LX->Aborder, dfltbd), aboolval(tok, LX->Aismap), 0, # not a background image map, aval(tok, LX->Aname), getgenattr(tok)); if(align == Aleft || align == Aright) { additem(ps, Item.newfloat(img, align), tok); # if no hspace specified, use FLTIMGHSPACE (fnd,nil) := tok.aval(LX->Ahspace); if(!fnd) { pick ii := img { Iimage => ii.hspace = byte FLTIMGHSPACE; } } } else { ps.skipwhite = 0; additem(ps, img, tok); } if(!ps.skipping) di.images = img :: di.images; ps.curanchor = oldcuranchor; # <!ELEMENT INPUT - O EMPTY> LX->Tinput => if (ps.skipping) continue; ps.skipwhite = 0; if(is.curform ==nil) { if(warn) sys->print("<INPUT> not inside <FORM>\n"); continue; } field := Formfield.new(atabval(tok, LX->Atype, input_tab, Ftext), ++is.curform.nfields, # fieldid is.curform, # form aval(tok, LX->Aname), aval(tok, LX->Avalue), aintval(tok, LX->Asize, 0), aintval(tok, LX->Amaxlength, 1000)); if(aboolval(tok, LX->Achecked)) field.flags = FFchecked; case field.ftype { Ftext or Fpassword or Ffile => if(field.size == 0) field.size = 20; Fcheckbox => if(field.name == "") { if(warn) sys->print("warning: checkbox form field missing name\n"); # continue; } if(field.value == "") field.value = "on"; Fradio => if(field.name == "" || field.value == "") { if(warn) sys->print("warning: radio form field missing name or value\n"); # continue; } Fsubmit => if(field.value == "") field.value = "Submit"; if(field.name == "") field.name = "_no_name_submit_"; Fimage => src := aurlval(tok, LX->Asrc, nil, di.base); if(src == nil) { if(warn) sys->print("warning: image form field missing src\n"); # continue; } else { # width and height attrs aren't specified in HTML 3.2, # but some people provide them and they help avoid # a relayout field.image = Item.newimage(di, src, aurlval(tok, LX->Alowsrc, nil, di.base), astrval(tok, LX->Aalt, "Submit"), atabbval(tok, LX->Aalign, align_tab, Abottom), aintval(tok, LX->Awidth, 0), aintval(tok, LX->Aheight, 0), 0, 0, 0, 0, 0, nil, field.name, nil); di.images = field.image :: di.images; } Freset => if(field.value == "") field.value = "Reset"; Fbutton => if(field.value == "") field.value = " "; } is.curform.fields = field :: is.curform.fields; ffit := Item.newformfield(field); additem(ps, ffit, tok); if(ffit.genattr != nil) { field.events = ffit.genattr.events; if(field.events != nil && doscripts) di.hasscripts = 1; } # <!ENTITY ISINDEX - O EMPTY> LX->Tisindex => ps.skipwhite = 0; prompt := astrval(tok, LX->Aprompt, "Index search terms:"); target := astrval(tok, LX->Atarget, di.target); additem(ps, textit(ps, prompt), tok); frm := Form.new(++is.nforms, "", di.base, target, CU->HGet, nil); ff := Formfield.new(Ftext, 1, frm, "_ISINDEX_", "", 50, 1000); frm.fields = ff :: nil; frm.nfields = 1; di.forms = frm :: di.forms; additem(ps, Item.newformfield(ff), tok); addbrk(ps, 1, 0); # <!ELEMENT LI - O %flow> LX->Tli => if(ps.listtypestk == nil) { if(warn) sys->print("<LI> not in list\n"); continue; } ty := hd ps.listtypestk; ty2 := listtyval(tok, ty); if(ty != ty2) { ty = ty2; ps.listtypestk = ty2 :: tl ps.listtypestk; } v := aintval(tok, LX->Avalue, hd ps.listcntstk); if(ty == LTdisc || ty == LTsquare || ty == LTcircle) hang := 10*LISTTAB - 3; else hang = 10*LISTTAB - 1; changehang(ps, hang); addtext(ps, listmark(ty, v)); ps.listcntstk = (v+1) :: (tl ps.listcntstk); changehang(ps, -hang); ps.skipwhite = 1; # <!ELEMENT MAP - - (AREA)+> LX->Tmap => is.curmap = getmap(di, aval(tok, LX->Aname)); LX->Tmap+RBRA => map := is.curmap; if(map == nil) { if(warn) sys->print("warning: unexpected </MAP>\n"); continue; } # put areas back in input order areas : list of Area = nil; for(al := map.areas; al != nil; al = tl al) areas = hd al :: areas; map.areas = areas; is.curmap = nil; LX->Tmeta => if(ps.skipping) continue; (fnd, equiv) := tok.aval(LX->Ahttp_equiv); if(fnd) { v := aval(tok, LX->Acontent); case S->tolower(equiv) { "set-cookie" => if((CU->config).docookies > 0) { url := di.src; CU->setcookie(v, url.host, url.path); } "refresh" => di.refresh = v; "content-script-type" => if(v == "javascript" || v == "javascript1.1" || v == "jscript") di.scripttype = CU->TextJavascript; # TODO: other kinds else { if(warn) sys->print("unimplemented script type %s\n", v); di.scripttype = CU->UnknownType; } "content-type" => (nil, parms) := S->splitl(v, ";"); if (parms != nil) { nvs := Nameval.namevals(parms[1:], ';'); (got, s) := Nameval.find(nvs, "charset"); if (got) { # sys->print("HTTP-EQUIV charset: %s\n", s); btos := CU->getconv(s); if (btos != nil) is.ts.setchset(btos); else if (warn) sys->print("cannot set charset %s\n", s); } } } } # Nobr is NOT in HMTL 4.0, but it is ubiquitous on the web LX->Tnobr => ps.skipwhite = 0; ps.curstate &= ~IFwrap; LX->Tnobr+RBRA => ps.curstate |= IFwrap; # We do frames, so skip stuff in noframes LX->Tnoframes => ps.skipping = 1; LX->Tnoframes+RBRA => ps.skipping = 0; # We do scripts (if enabled), so skip stuff in noscripts LX->Tnoscript => if(doscripts) ps.skipping = 1; LX->Tnoscript+RBRA => if(doscripts) ps.skipping = 0; # <!ELEMENT OPTION - O (#PCDATA)> LX->Toption => if(is.curform == nil || is.curform.fields == nil) { if(warn) sys->print("warning: <OPTION> not in <SELECT>\n"); continue; } field := hd is.curform.fields; if(field.ftype != Fselect) { if(warn) sys->print("warning: <OPTION> not in <SELECT>\n"); continue; } val := aval(tok, LX->Avalue); option := ref Option(aboolval(tok, LX->Aselected), val, ""); field.options = option :: field.options; (option.display, toki) = getpcdata(toks, toki); option.display = optiontext(option.display); if(val == "") option.value = option.display; # <!ELEMENT P - O (%text)* > LX->Tp => pushjust(ps, atabbval(tok, LX->Aalign, align_tab, ps.curjust)); ps.inpar = 1; ps.skipwhite = 1; LX->Tp+RBRA => ; # <!ELEMENT PARAM - O EMPTY> # Do something when we do applets... LX->Tparam => ; # <!ELEMENT PRE - - (%text)* -(IMG|BIG|SMALL|SUB|SUP|FONT) > LX->Tpre => ps.curstate &= ~IFwrap; ps.literal = 1; ps.skipwhite = 0; pushfontstyle(ps, FntT); LX->Tpre+RBRA => ps.curstate |= IFwrap; if(ps.literal) { popfontstyle(ps); ps.literal = 0; } # <!ELEMENT SCRIPT - - CDATA> LX->Tscript => if(!doscripts) { if(warn) sys->print("warning: <SCRIPT> ignored\n"); ps.skipping = 1; break; } script := ""; scripttoki := toki; (script, toki) = getpcdata(toks, toki); # check language version lang := astrval(tok, LX->Alanguage, "javascript"); lang = S->tolower(lang); lang = trim_white(lang); # should give preference to type supported := 0; for (v := 0; v < len J->versions; v++) if (J->versions[v] == lang) { supported = 1; break; } if (!supported) break; di.hasscripts = 1; scriptsrc := aurlval(tok, LX->Asrc, nil, di.base); if(scriptsrc != nil && is.reqdurl == nil) { is.reqdurl = scriptsrc; toki = scripttoki; # is.reqddata will contain script next time round break TokLoop; } if (is.reqddata != nil) { script = CU->stripscript(string is.reqddata); is.reqddata = nil; is.reqdurl = nil; } if(script == "") break; #sys->print("SCRIPT (ver %s)\n%s\nENDSCRIPT\n", lang, script); (err, replace, nil) := J->evalscript(is.frame, script); if(err != "") { if(warn) sys->print("Javascript error: %s\n", err); } else { # First, worry about possible transfer back of new values if(di.text != ps.curfg) { # The following isn't nearly good enough # (if the fgstk isn't nil, need to replace bottom of stack; # and need to do similar things for all other pstates). # But Netscape 4.0 doesn't do anything at all if change # foreground in a script! if(ps.fgstk == nil) ps.curfg = di.text; } scripttoks := lexstring(replace); ns := len scripttoks; if(ns > 0) { # splice scripttoks into toks, replacing <SCRIPT>...</SCRIPT> if(toki+1 < tokslen && toks[toki+1].tag == LX->Tscript+RBRA) toki++; newtokslen := tokslen - (toki+1-scripttoki) + ns; newtoks := array[newtokslen] of ref Token; newtoks[0:] = toks[0:scripttoki]; newtoks[scripttoki:] = scripttoks; if(toki+1 < tokslen) newtoks[scripttoki+ns:] = toks[toki+1:tokslen]; toks = newtoks; tokslen = newtokslen; toki = scripttoki-1; scripttoks = nil; } } LX->Tscript+RBRA => ps.skipping = 0; # <!ELEMENT SELECT - - (OPTION+)> LX->Tselect => if(is.curform ==nil) { if(warn) sys->print("<SELECT> not inside <FORM>\n"); continue; } field := Formfield.new(Fselect, ++is.curform.nfields, # fieldid is.curform, # form aval(tok, LX->Aname), "", # value aintval(tok, LX->Asize, 1), 0); # maxlength if(aboolval(tok, LX->Amultiple)) field.flags = FFmultiple; is.curform.fields = field :: is.curform.fields; ffit := Item.newformfield(field); additem(ps, ffit, tok); if(ffit.genattr != nil) { field.events = ffit.genattr.events; if(field.events != nil && doscripts) di.hasscripts = 1; } # throw away stuff until next tag (should be <OPTION>) (nil, toki) = getpcdata(toks, toki); LX->Tselect+RBRA => if(is.curform == nil || is.curform.fields == nil) { if(warn) sys->print("warning: unexpected </SELECT>\n"); continue; } field := hd is.curform.fields; if(field.ftype != Fselect) continue; # put options back in input order opts : list of ref Option = nil; select := 0; for(ol := field.options; ol != nil; ol = tl ol) { o := hd ol; if (o.selected) select = 1; opts = o :: opts; } # Single-choice select fields preselect the first option if none explicitly selected if (!select && !int(field.flags & FFmultiple) && opts != nil) (hd opts).selected = 1; field.options = opts; # <!ELEMENT (STRIKE|U) - - (%text)*> LX->Tstrike or LX->Tu => if(tag == LX->Tstrike) ulty := ULmid; else ulty = ULunder; ps.ulstk = ulty :: ps.ulstk; ps.curul = ulty; LX->Tstrike+RBRA or LX->Tu+RBRA => if(ps.ulstk == nil) { if(warn) sys->print("warning: unexpected %s\n", tok.tostring()); continue; } ps.ulstk = tl ps.ulstk; if(ps.ulstk != nil) ps.curul = hd ps.ulstk; else ps.curul = ULnone; # <!ELEMENT STYLE - - CDATA> LX->Tstyle => if(warn) sys->print("warning: unimplemented <STYLE>\n"); ps.skipping = 1; LX->Tstyle+RBRA => ps.skipping = 0; # <!ELEMENT (SUB|SUP) - - (%text)*> LX->Tsub or LX->Tsup => if(tag == LX->Tsub) ps.curvoff += SUBOFF; else ps.curvoff -= SUPOFF; ps.voffstk = ps.curvoff :: ps.voffstk; sz := stackhd(ps.fntsizestk, Normal); pushfontsize(ps, sz-1); LX->Tsub+RBRA or LX->Tsup+RBRA => if(ps.voffstk == nil) { if(warn) sys->print("warning: unexpected %s\n", tok.tostring()); continue; } ps.voffstk = tl ps.voffstk; if(ps.voffstk != nil) ps.curvoff = hd ps.voffstk; else ps.curvoff = 0; popfontsize(ps); # <!ELEMENT TABLE - - (CAPTION?, TR+)> LX->Ttable => if (ps.skipping) continue; ps.skipwhite = 0; # Handle an html error (seen on deja.com) # ... sometimes see a nested <table> outside of a cell # imitate observed behaviour of IE/Navigator if (curtab != nil && curtab.cells == nil) { curtab.align = makealign(tok); curtab.width = makedimen(tok, LX->Awidth); curtab.border = aflagval(tok, LX->Aborder); curtab.cellspacing = aintval(tok, LX->Acellspacing, TABSP); curtab.cellpadding = aintval(tok, LX->Acellpadding, TABPAD); curtab.background = Background(nil, color(aval(tok, LX->Abgcolor), -1)); curtab.tabletok = tok; continue; } tab := Table.new(++is.ntables, # tableid makealign(tok), # align makedimen(tok, LX->Awidth), aflagval(tok, LX->Aborder), aintval(tok, LX->Acellspacing, TABSP), aintval(tok, LX->Acellpadding, TABPAD), # Background(nil, color(aval(tok, LX->Abgcolor), ps.curbg.color)), Background(nil, color(aval(tok, LX->Abgcolor), -1)), tok); is.tabstk = tab :: is.tabstk; di.tables = tab :: di.tables; curtab = tab; # HTML spec says: # don't add items to outer state (until </table>) # but IE and Netscape don't do that LX->Ttable+RBRA => if (ps.skipping) continue; if(curtab == nil) { if(warn) sys->print("warning: unexpected </TABLE>\n"); continue; } isempty := (curtab.cells == nil); if(isempty) { if(warn) sys->print("warning: <TABLE> has no cells\n"); } else { (ps, psstk) = finishcell(curtab, psstk); if(curtab.currows != nil) (hd curtab.currows).flags = byte 0; finish_table(curtab); } ps.skipping = 0; if(!isempty) { tabitem := Item.newtable(curtab); al := int curtab.align.halign; case al { int Aleft or int Aright => additem(ps, Item.newfloat(tabitem, byte al), tok); * => if(al == int Acenter) pushjust(ps, Acenter); addbrk(ps, 0, 0); if(ps.inpar) { popjust(ps); ps.inpar = 0; } additem(ps, tabitem, curtab.tabletok); if(al == int Acenter) popjust(ps); } } if(is.tabstk == nil) { if(warn) sys->print("warning: table stack is wrong\n"); } else is.tabstk = tl is.tabstk; if(is.tabstk == nil) curtab = nil; else curtab = hd is.tabstk; if(!isempty) { # the code at the beginning to add a break after table # changed the nested ps, not the current one addbrk(ps, 0, 0); } # <!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. LX->Ttd or LX->Tth => if (ps.skipping) continue; if(curtab == nil) { if(warn) sys->print("%s outside <TABLE>\n", tok.tostring()); continue; } if(ps.inpar) { popjust(ps); ps.inpar = 0; } (ps, psstk) = finishcell(curtab, psstk); tr : ref Tablerow = nil; if(curtab.currows != nil) tr = hd curtab.currows; if(tr == nil || tr.flags == byte 0) { if(warn) sys->print("%s outside row\n", tok.tostring()); tr = Tablerow.new(Align(Anone,Anone), curtab.background, TFparsing); curtab.currows = tr :: curtab.currows; } ps = cell_pstate(ps, tag == LX->Tth); psstk = ps :: psstk; flags := TFparsing; width := makedimen(tok, LX->Awidth); # nowrap only applies if no width has been specified if(width.kind() == Dnone && aboolval(tok, LX->Anowrap)) { flags |= TFnowrap; ps.curstate &= ~IFwrap; } if(tag == LX->Tth) flags |= TFisth; bg := Background(nil, color(aval(tok, LX->Abgcolor), tr.background.color)); c := Tablecell.new(len curtab.cells + 1, # cell id aintval(tok, LX->Arowspan, 1), aintval(tok, LX->Acolspan, 1), makealign(tok), width, aintval(tok, LX->Aheight, 0), bg, flags); bgurl := aurlval(tok, LX->Abackground, nil, di.base); if(bgurl != nil) { pick ni := Item.newimage(di, bgurl, nil,"", Anone, 0, 0, 0, 0, 0, 0, 1, nil, nil, nil){ Iimage => bg.image = ni; } di.images = bg.image :: di.images; } c.background = ps.curbg = bg; ps.curbg.image = nil; if(c.align.halign == Anone) { if(tr.align.halign != Anone) c.align.halign = tr.align.halign; else if(tag == LX->Tth) c.align.halign = Acenter; else c.align.halign = Aleft; } if(c.align.valign == Anone) { if(tr.align.valign != Anone) c.align.valign = tr.align.valign; else c.align.valign = Amiddle; } curtab.cells = c :: curtab.cells; tr.cells = c :: tr.cells; LX->Ttd+RBRA or LX->Tth+RBRA => if (ps.skipping) continue; if(curtab == nil || curtab.cells == nil) { if(warn) sys->print("unexpected %s\n", tok.tostring()); continue; } (ps, psstk) = finishcell(curtab, psstk); # <!ELEMENT TEXTAREA - - (#PCDATA)> LX->Ttextarea => if(is.curform ==nil) { if(warn) sys->print("<TEXTAREA> not inside <FORM>\n"); continue; } nrows := aintval(tok, LX->Arows, 3); ncols := aintval(tok, LX->Acols, 50); ft := Ftextarea; if (ncols == 0 || nrows == 0) ft = Fhidden; field := Formfield.new(ft, ++is.curform.nfields, # fieldid is.curform, # form aval(tok, LX->Aname), "", # value 0, 0); # size, maxlength field.rows = nrows; field.cols = ncols; is.curform.fields = field :: is.curform.fields; (field.value, toki) = getpcdata(toks, toki); if(warn && toki < tokslen-1 && toks[toki+1].tag != LX->Ttextarea+RBRA) sys->print("warning: <TEXTAREA> data ended by %s\n", toks[toki+1].tostring()); ffit := Item.newformfield(field); additem(ps, ffit, tok); if(ffit.genattr != nil) { field.events = ffit.genattr.events; if(field.events != nil && doscripts) di.hasscripts = 1; } # <!ELEMENT TITLE - - (#PCDATA)* -(%head.misc)> LX->Ttitle => (di.doctitle, toki) = getpcdata(toks, toki); if(warn && toki < tokslen-1 && toks[toki+1].tag != LX->Ttitle+RBRA) sys->print("warning: <TITLE> data ended by %s\n", toks[toki+1].tostring()); # <!ELEMENT TR - O (TH|TD)+> # rows are accumulated in reverse order in curtab.currows LX->Ttr => if (ps.skipping) continue; if(curtab == nil) { if(warn) sys->print("warning: <TR> outside <TABLE>\n"); continue; } if(ps.inpar) { popjust(ps); ps.inpar = 0; } (ps, psstk) = finishcell(curtab, psstk); if(curtab.currows != nil) (hd curtab.currows).flags = byte 0; tr := Tablerow.new(makealign(tok), Background(nil, color(aval(tok, LX->Abgcolor), curtab.background.color)), TFparsing); curtab.currows = tr :: curtab.currows; LX->Ttr+RBRA => if (ps.skipping) continue; if(curtab == nil || curtab.currows == nil) { if(warn) sys->print("warning: unexpected </TR>\n"); continue; } (ps, psstk) = finishcell(curtab, psstk); tr := hd curtab.currows; if(tr.cells == nil) { if(warn) sys->print("warning: empty row\n"); curtab.currows = tl curtab.currows; } else tr.flags = byte 0; # done parsing # <!ELEMENT (TT|CODE|KBD|SAMP) - - (%text)*> LX->Ttt or LX->Tcode or LX->Tkbd or LX->Tsamp => pushfontstyle(ps, FntT); # <!ELEMENT (XMP|LISTING) - - %literal > # additional support exists in LX to ignore character escapes etc. LX->Txmp => ps.curstate &= ~IFwrap; ps.literal = 1; ps.skipwhite = 0; pushfontstyle(ps, FntT); LX->Txmp+RBRA => ps.curstate |= IFwrap; if(ps.literal) { popfontstyle(ps); ps.literal = 0; } # Tags that have empty action LX->Tabbr or LX->Tabbr+RBRA or LX->Tacronym or LX->Tacronym+RBRA or LX->Tarea+RBRA or LX->Tbase+RBRA or LX->Tbasefont+RBRA or LX->Tbr+RBRA or LX->Tdd+RBRA or LX->Tdt+RBRA or LX->Tframe+RBRA or LX->Thr+RBRA or LX->Thtml or LX->Thtml+RBRA or LX->Timg+RBRA or LX->Tinput+RBRA or LX->Tisindex+RBRA or LX->Tli+RBRA or LX->Tlink or LX->Tlink+RBRA or LX->Tmeta+RBRA or LX->Toption+RBRA or LX->Tparam+RBRA or LX->Ttextarea+RBRA or LX->Ttitle+RBRA => ; # Tags not implemented LX->Tbdo or LX->Tbdo+RBRA or LX->Tbutton or LX->Tbutton+RBRA or LX->Tdel or LX->Tdel+RBRA or LX->Tfieldset or LX->Tfieldset+RBRA or LX->Tiframe or LX->Tiframe+RBRA or LX->Tins or LX->Tins+RBRA or LX->Tlabel or LX->Tlabel+RBRA or LX->Tlegend or LX->Tlegend+RBRA or LX->Tobject or LX->Tobject+RBRA or LX->Toptgroup or LX->Toptgroup+RBRA or LX->Tspan or LX->Tspan+RBRA => if(warn) { if(tag > RBRA) tag -= RBRA; sys->print("warning: unimplemented HTML tag: %s\n", LX->tagnames[tag]); } * => if(warn) sys->print("warning: unknown HTML tag: %s\n", tok.text); } } if (toki < tokslen) is.toks = toks[toki:]; if(tokslen == 0) { # we might have hit eof from lexer # some pages omit trailing </table> bs := is.ts.b; if(bs.eof && bs.lim == bs.edata) { while(curtab != nil) { if(warn) sys->print("warning: <TABLE> not closed\n"); if(curtab.cells != nil) { (ps, psstk) = finishcell(curtab, psstk); if(curtab.currows != nil) (hd curtab.currows).flags = byte 0; finish_table(curtab); ps.skipping = 0; additem(ps, Item.newtable(curtab), curtab.tabletok); addbrk(ps, 0, 0); } if(is.tabstk != nil) is.tabstk = tl is.tabstk; if(is.tabstk == nil) curtab = nil; else curtab = hd is.tabstk; } } } outerps := lastps(psstk); ans := outerps.items.next; # note: ans may be nil and di.kids not nil, if there's a frameset! outerps.items = Item.newspacer(ISPnull, 0); outerps.lastit = outerps.items; is.psstk = psstk; if(dbg) { if(ans == nil) sys->print("getitems returning nil\n"); else ans.printlist("getitems returning:"); } return ans; } endanchor(ps: ref Pstate, docfg: int) { if(ps.curanchor != 0) { if(ps.fgstk != nil) { ps.fgstk = tl ps.fgstk; if(ps.fgstk == nil) ps.curfg = docfg; else ps.curfg = hd ps.fgstk; } ps.curanchor = 0; if(ps.ulstk != nil) { ps.ulstk = tl ps.ulstk; if(ps.ulstk == nil) ps.curul = ULnone; else ps.curul = hd ps.ulstk; } } } lexstring(s: string) : array of ref Token { bs := ByteSource.stringsource(s); ts := TokenSource.new(bs, utf8, CU->TextHtml); ans : array of ref Token = nil; # gettoks might return answer in several hunks for(;;) { toks := ts.gettoks(); if(toks == nil) break; if(ans != nil) { newans := array[len ans + len toks] of ref Token; newans[0:] = ans; newans[len ans:] = toks; ans = newans; } else ans = toks; } return ans; } lastps(psl: list of ref Pstate) : ref Pstate { if(psl == nil) raise "EXInternal: empty pstate stack"; while(tl psl != nil) psl = tl psl; return hd psl; } # 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 (trimmed concatenation, last used toki). getpcdata(toks: array of ref Token, toki: int) : (string, int) { ans := ""; tokslen := len toks; toki++; for(;;) { if(toki >= tokslen) break; tok := toks[toki]; if(tok.tag == LX->Data) { toki++; ans = ans + tok.text; } else break; } return (trim_white(ans), toki-1); } optiontext(str : string) : string { ans := ""; lastc := 0; for (i := 0; i < len str; i++) { if (str[i] > 16r20) ans[len ans] = str[i]; else if (lastc > 16r20) ans[len ans] = ' '; lastc = str[i]; } return ans; } finishcell(curtab: ref Table, psstk: list of ref Pstate) : (ref Pstate, list of ref Pstate) { if(curtab.cells != nil) { c := hd curtab.cells; if((c.flags&TFparsing) != byte 0) { if(tl psstk == nil) { if(warn) sys->print("warning: parse state stack is wrong\n"); } else { ps := hd psstk; c.content = ps.items.next; c.flags &= ~TFparsing; psstk = tl psstk; } } } return (hd psstk, psstk); } Pstate.new() : ref Pstate { ps := ref Pstate ( 0, 0, DefFnt, # skipping, skipwhite, curfont CU->Black, # curfg Background(nil, CU->White), 0, # curvoff ULnone, Aleft, # curul, curjust 0, IFwrap, # curanchor, curstate 0, 0, 0, # literal, inpar, adjsize nil, nil, nil, # items, lastit, prelastit nil, nil, nil, nil, # fntstylestk, fntsizestk, fgstk, ulstk nil, nil, nil, nil, # voffstk, listtypestk, listcntstk, juststk nil); # hangstk ps.items = Item.newspacer(ISPnull, 0); ps.lastit = ps.items; ps.prelastit = nil; return ps; } cell_pstate(oldps: ref Pstate, ishead: int) : ref Pstate { ps := Pstate.new(); ps.skipwhite = 1; ps.curanchor = oldps.curanchor; ps.fntstylestk = oldps.fntstylestk; ps.fntsizestk = oldps.fntsizestk; ps.curfont = oldps.curfont; ps.curfg = oldps.curfg; ps.curbg = oldps.curbg; ps.fgstk = oldps.fgstk; ps.adjsize = oldps.adjsize; if(ishead) { # make bold sty := ps.curfont%NumSize; ps.curfont = FntB*NumSize + sty; } return ps; } trim_white(data: string): string { data = S->drop(data, whitespace); (l,nil) := S->splitr(data, notwhitespace); return l; } # 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. additem(ps: ref Pstate, it: ref Item, tok: ref LX->Token) { if(ps.skipping) { if(warn) { sys->print("warning: skipping item:\n"); it.print(); } return; } it.anchorid = ps.curanchor; it.state |= ps.curstate; if(tok != nil) it.genattr = getgenattr(tok); ps.curstate &= ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright); ps.prelastit = ps.lastit; ps.lastit.next = it; ps.lastit = it; } getgenattr(tok: ref LX->Token) : ref Genattr { any := 0; i, c, s, t: string; e: list of LX->Attr = nil; for(al := tok.attr; al != nil; al = tl al) { a := hd al; aid := a.attid; if(attrinfo[aid] == byte 0) continue; case aid { LX->Aid => i = a.value; any = 1; LX->Aclass => c = a.value; any = 1; LX->Astyle => s = a.value; any = 1; LX->Atitle => t = a.value; any = 1; * => CU->assert(aid >= LX->Aonabort && aid <= LX->Aonunload); e = a :: e; any = 1; } } if(any) return ref Genattr(i, c, s, t, e, 0); return nil; } textit(ps: ref Pstate, s: string) : ref Item { return Item.newtext(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. # # This code could be written much shorter using the String module # split and drop functions, but we want this part to go fast. addtext(ps: ref Pstate, s: string) { n := len 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, s[j:k]), nil); } addlinebrk(ps, 0); j = i+1; col = 0; } else { if(s[i] == '\t') { col += i-j; nsp := 8 - (col % 8); additem(ps, textit(ps, s[j:i] + " "[0:nsp]), nil); col += nsp; j = i+1; } else if(s[i] == NBSP) { if(i > j) additem(ps, textit(ps, s[j:i]), nil); addnbsp(ps); col += (i-j) + 1; j = i+1; } } i++; } if(i > j) additem(ps, textit(ps, s[j:i]), nil); } else { if((ps.curstate&IFbrk) || ps.lastit == ps.items) while(i < n) { c := s[i]; if(c >= C->NCTYPE || ctype[c] != C->W) break; i++; } ss := ""; j = i; for( ; i < n; i++) { c := s[i]; if(c == NBSP) { if(i > j) ss += s[j:i]; if(ss != "") additem(ps, textit(ps, ss), nil); ss = ""; addnbsp(ps); j = i + 1; continue; } if(c < C->NCTYPE && ctype[c] == C->W) { ss += s[j:i] + " "; while(i < n-1) { c = s[i+1]; if(c >= C->NCTYPE || ctype[c] != C->W) break; i++; } j = i + 1; } if(i - j >= 100) { ss += s[j:i+1]; j = i + 1; } if(len ss >= 100) { additem(ps, textit(ps, ss), nil); ss = ""; } } if(i > j && j < n) ss += s[j:i]; # don't add a space if previous item ended in a space if(ss == " " && ps.lastit != nil) { pick t := ps.lastit { Itext => sp := t.s; nsp := len sp; if(nsp > 0 && sp[nsp-1] == ' ') ss = ""; } } if(ss != "") additem(ps, textit(ps, ss), nil); } } # 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. addbrk(ps: ref Pstate, sp: int, clr: int) { 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 && tagof ps.lastit == tagof Item.Itext) { pick t := ps.lastit { Itext => (l,nil) := S->splitr(t.s, notwhitespace); # try to avoid making empty items # (but not crucial if the occasional one gets through) if(l == "" && ps.prelastit != nil) { ps.lastit = ps.prelastit; ps.lastit.next = nil; ps.prelastit = nil; } else t.s = l; } } } } # 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. [[ I think this is not what we want; see # MR inf983435. --Ravi ]] addlinebrk(ps: ref Pstate, clr: int) { # 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; if(ps.lastit != nil) { pick pit := ps.lastit { Ispacer => if(pit.spkind == ISPvline) b = IFbrk; } } ps.curstate = (ps.curstate & ~(IFbrk|IFbrksp)) | b; additem(ps, Item.newspacer(ISPvline, ps.curfont), nil); ps.curstate = (ps.curstate & ~(IFbrk|IFbrksp)) | obrkstate; addbrk(ps, 0, clr); } # Add a nonbreakable space addnbsp(ps: ref Pstate) { # 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, Item.newspacer(ISPhspace, ps.curfont), 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. changehang(ps: ref Pstate, delta: int) { amt := (ps.curstate&IFhangmask) + delta; if(amt < 0) { if(warn) sys->print("warning: hang went negative\n"); amt = 0; } ps.curstate = (ps.curstate&~IFhangmask) | amt; } # Change indent in ps.curstate by delta. changeindent(ps: ref Pstate, delta: int) { amt := ((ps.curstate&IFindentmask)>>IFindentshift) + delta; if(amt < 0) { if(warn) sys->print("warning: indent went negative\n"); amt = 0; } ps.curstate = (ps.curstate&~IFindentmask) | (amt<<IFindentshift); } stackhd(stk: list of int, dflt: int) : int { if(stk == nil) return dflt; return hd stk; } popfontstyle(ps: ref Pstate) { if(ps.fntstylestk != nil) ps.fntstylestk = tl ps.fntstylestk; setcurfont(ps); } pushfontstyle(ps: ref Pstate, sty: int) { ps.fntstylestk = sty :: ps.fntstylestk; setcurfont(ps); } popfontsize(ps: ref Pstate) { if(ps.fntsizestk != nil) ps.fntsizestk = tl ps.fntsizestk; setcurfont(ps); } pushfontsize(ps: ref Pstate, sz: int) { ps.fntsizestk = sz :: ps.fntsizestk; setcurfont(ps); } setcurfont(ps: ref Pstate) { sty := FntR; sz := Normal; if(ps.fntstylestk != nil) sty = hd ps.fntstylestk; if(ps.fntsizestk != nil) sz = hd ps.fntsizestk; if(sz < Tiny) sz = Tiny; if(sz > Verylarge) sz = Verylarge; ps.curfont = sty*NumSize + sz; } popjust(ps: ref Pstate) { if(ps.juststk != nil) ps.juststk = tl ps.juststk; setcurjust(ps); } pushjust(ps: ref Pstate, j: byte) { ps.juststk = j :: ps.juststk; setcurjust(ps); } setcurjust(ps: ref Pstate) { if(ps.juststk != nil) j := hd ps.juststk; else j = Aleft; if(j != ps.curjust) { ps.curjust = j; state := ps.curstate; state &= ~(IFrjust|IFcjust); if(j == Acenter) state |= IFcjust; else if(j == Aright) state |= IFrjust; ps.curstate = state; } } # Do final rearrangement after table parsing is finished # and assign cells to grid points finish_table(t: ref Table) { t.nrow = len t.currows; t.rows = array[t.nrow] of ref Tablerow; ncol := 0; r := t.nrow-1; for(rl := t.currows; rl != nil; rl = tl rl) { row := hd rl; t.rows[r--] = row; rcols := 0; cl := row.cells; # If rowspan is > 1 but this is the last row, # reset the rowspan if(cl != nil && (hd cl).rowspan > 1 && rl == t.currows) (hd cl).rowspan = 1; row.cells = nil; while(cl != nil) { c := hd cl; row.cells = c :: row.cells; rcols += c.colspan; cl = tl cl; } if(rcols > ncol) ncol = rcols; } t.currows = nil; t.ncol = ncol; t.cols = array[ncol] of { * => Tablecol(0, Align(Anone, Anone), (0,0)) }; # Reverse cells just so they are drawn in source order. # Also, trim their contents so they don't end in whitespace. cells : list of ref Tablecell = nil; for(cl := t.cells; cl != nil; cl = tl cl) { c := hd cl; trim_cell(c); cells = c :: cells; } t.cells = cells; t.grid = array[t.nrow] of { * => array[t.ncol] of ref 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 := array[t.ncol] of { * => 0}; rowspancell := array[t.ncol] of ref Tablecell; ri := 0; ci := 0; for(ri = 0; ri < t.nrow; ri++) { row := t.rows[ri]; cl = row.cells; for(ci = 0; ci < t.ncol || cl != nil; ) { if(ci < t.ncol && rowspancnt[ci] > 0) { t.grid[ri][ci] = rowspancell[ci]; rowspancnt[ci]--; ci++; } else { if(cl == nil) { ci++; continue; } c := hd cl; cl = tl cl; cspan := c.colspan; rspan := c.rowspan; if(ci+cspan > t.ncol) { # because of row spanning, we calculated # ncol incorrectly; adjust it newncol := ci+cspan; newcols := array[newncol] of Tablecol; newrowspancnt := array[newncol] of { * => 0}; newrowspancell := array[newncol] of ref Tablecell; newcols[0:] = t.cols; newrowspancnt[0:] = rowspancnt; newrowspancell[0:] = rowspancell; for(k := t.ncol; k < newncol; k++) newcols[k] = Tablecol(0, Align(Anone, Anone), (0,0)); t.cols = newcols; rowspancnt = newrowspancnt; rowspancell = newrowspancell; for(j := 0; j < t.nrow; j++) { newgrr := array[newncol] of ref Tablecell; newgrr[0:] = t.grid[j]; for(k = t.ncol; k < newncol; k++) newgrr[k] = nil; t.grid[j] = newgrr; } t.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++; } } } } t.flags |= Layout->Lchanged; } # Remove tail of cell content until it isn't whitespace. trim_cell(c: ref Tablecell) { dropping := 1; while(c.content != nil && dropping) { p := c.content; pprev : ref Item = nil; while(p.next != nil) { pprev = p; p = p.next; } dropping = 0; if(!(p.state&IFnobrk)) { pick q := p { Itext => s := q.s; (x,y) := S->splitr(s, notwhitespace); if(x == nil) dropping = 1; else if(y != nil) q.s = x; } } if(dropping) { if(pprev == nil) c.content = nil; else pprev.next = nil; } } } roman := array[] of {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV"}; listmark(ty: byte, n: int) : string { s := ""; case int ty { int LTdisc => s = "•"; int LTsquare => s = "∎"; int LTcircle => s = "∘"; int LT1 => s = string n + "."; int LTa or int LTA => n--; i := 0; if(n < 0) n = 0; if(n > 25) { n2 := n / 26; n %= 26; if(n2 > 25) n2 = 25; s[i++] = n2 + 'A'; } s[i++] = n + 'A'; s[i++] = '.'; if(ty == LTa) s = S->tolower(s); int LTi or int LTI => if(n >= len roman) { if(warn) sys->print("warning: unimplemented roman number > %d\n", len roman); n = len roman; } s = roman[n-1]; if(ty == LTi) s = S->tolower(s); s += "."; } return s; } # Find map with given name in di.maps. # If not there, add one. getmap(di: ref Docinfo, name: string) : ref Map { m : ref Map; for(ml := di.maps; ml != nil; ml = tl ml) { m = hd ml; if(m.name == name) return m; } m = Map.new(name); di.maps = m :: di.maps; return m; } # attrvalue, when "found" status doesn't matter # (because nil ans is sufficient indication) aval(tok: ref Token, attid: int) : string { (nil, ans) := tok.aval(attid); return ans; } # attrvalue, when ans is a string, but need default astrval(tok: ref Token, attid: int, dflt: string) : string { (fnd, ans) := tok.aval(attid); if(!fnd) return dflt; else return ans; } # attrvalue, when supposed to convert to int # and have default for when not found aintval(tok: ref Token, attid: int, dflt: int) : int { (fnd, ans) := tok.aval(attid); if(!fnd || ans == "") return dflt; else return toint(ans); } # Like int conversion, but with possible error check (if warning) toint(s: string) : int { if(warn) { ok := 0; for(i := 0; i < len s; i++) { c := s[i]; if(!(c < C->NCTYPE && ctype[c] == C->W)) break; } for(; i < len s; i++) { c := s[i]; if(c < C->NCTYPE && ctype[c] == C->D) ok = 1; else { ok = 0; break; } } if(!ok || i != len s) sys->print("warning: expected integer, got '%s'\n", s); } return int s; } # attrvalue when need a table to convert strings to ints atabval(tok: ref Token, attid: int, tab: array of T->StringInt, dflt: int) : int { (fnd, aval) := tok.aval(attid); ans := dflt; if(fnd) { name := S->tolower(aval); (fnd, ans) = T->lookup(tab, name); if(!fnd) { ans = dflt; if(warn) sys->print("warning: name not found in table lookup: %s\n", name); } } return ans; } # like atabval, but when want a byte answer atabbval(tok: ref Token, attid: int, tab: array of T->StringInt, dflt: byte) : byte { (fnd, aval) := tok.aval(attid); ans := dflt; if(fnd) { name := S->tolower(aval); ians : int; (fnd, ians) = T->lookup(tab, name); if(fnd) ans = byte ians; else if(warn) sys->print("warning: name not found in table lookup: %s\n", name); } return ans; } # special for list types, where "i" and "I" are different, # but "square" and "SQUARE" are the same listtyval(tok: ref Token, dflt: byte) : byte { (fnd, aval) := tok.aval(LX->Atype); ans := dflt; if(fnd) { case aval { "1" => ans = LT1; "A" => ans = LTA; "I" => ans = LTI; "a" => ans = LTa; "i" => ans = LTi; * => aval = S->tolower(aval); case aval { "circle" => ans = LTcircle; "disc" => ans = LTdisc; "square" => ans = LTsquare; * => if(warn) sys->print("warning: unknown list element type %s\n", aval); } } } return ans; } # attrvalue when value is a URL aurlval(tok: ref Token, attid: int, dflt, base: ref Parsedurl) : ref Parsedurl { ans := dflt; (fnd, url) := tok.aval(attid); if(fnd && url != nil) { url = S->drop(url, whitespace); ans = U->parse(url); case (ans.scheme) { "javascript" => ; # don't strip whitespace from the URL * => # sometimes people put extraneous whitespace in url = stripwhite(url); ans = U->parse(url); if(base != nil) ans = U->mkabs(ans, base); } } return ans; } # remove any whitespace characters from any part of s # up to a '#' (assuming s is a url and '#' begins a fragment # (can return s if there are no whitespace characters in it) stripwhite(s: string) : string { j := 0; n := len s; strip := 1; for(i := 0; i < n; i++) { c := s[i]; if(c == '#') strip = 0; if(strip && c < C->NCTYPE && ctype[c]==C->W) continue; s[j++] = c; } if(j < n) s = s[0:j]; return s; } # Presence of attribute implies true, omission implies false. # Boolean attributes can have a value equal to their attribute name. # HTML4.01 does not state whether the attribute is true or false # if a value is given that doesn't match the attribute name. aboolval(tok: ref Token, attid: int): int { (fnd, nil) := tok.aval(attid); return fnd; } # attrvalue when mere presence of attr implies value of 1 aflagval(tok: ref Token, attid: int) : int { val := 0; (fnd, sval) := tok.aval(attid); if(fnd) { val = 1; if(sval != "") val = toint(sval); } return val; } # Make an Align (two alignments, horizontal and vertical) makealign(tok: ref Token) : Align { h := atabbval(tok, LX->Aalign, align_tab, Anone); v := atabbval(tok, LX->Avalign, align_tab, Anone); return Align(h, v); } # Make a Dimen, based on value of attid attr makedimen(tok: ref Token, attid: int) : Dimen { (fnd, wd) := tok.aval(attid); if(fnd) return parsedim(wd); else return Dimen.make(Dnone, 0); } # Parse s as num[.[num]][unit][%|*] parsedim(s: string) : Dimen { kind := Dnone; spec := 0; (l,r) := S->splitl(s, "^0-9"); if(l != "") { # accumulate 1000 * value (to work in fixed point) spec = 1000 * toint(l); if(S->prefix(".", r)) { f : string; (f,r) = S->splitl(r[1:], "^0-9"); if(f != "") { mul := 100; for(i := 0; i < len f; i++) { spec = spec + mul * toint(f[i:i+1]); mul = mul / 10; } } } kind = Dpixels; if(r != "") { if(len r >= 2) { Tkdpi := 100; # hack, but matches current tk units := r[0:2]; r = r[2:]; case units { "pt" => spec = (spec*Tkdpi)/72; "pi" => spec = (spec*12*Tkdpi)/72; "in" => spec = spec*Tkdpi; "cm" => spec = (spec*100*Tkdpi)/254; "mm" => spec = (spec*10*Tkdpi)/254; "em" => spec = spec * 15; # hack, lucidasans 8pt is 15 pixels high * => if(warn) sys->print("warning: unknown units %s\n", units); } } if(r == "%") kind = Dpercent; else if(r == "*") kind = Drelative; } spec = spec / 1000; } else if(r == "*") { spec = 1; kind = Drelative; } return Dimen.make(kind, spec); } dimlist(tok: ref Token, attid: int) : array of Dimen { s := aval(tok, attid); if(s != "") { (nc, cl) := sys->tokenize(s, ", "); if(nc > 0) { d := array[nc] of Dimen; for(k := 0; k < nc; k++) { d[k] = parsedim(hd cl); cl = tl cl; } return d; } } return nil; } stringdim(d: Dimen) : string { ans := string d.spec(); k := d.kind(); if(k == Dpercent) ans += "%"; if(k == Drelative) ans += "*"; return ans; } stringalign(a: byte) : string { s := T->revlookup(align_tab, int a); if(s == nil) s = "none"; return s; } stringstate(state: int) : string { s := ""; if(state&IFbrk) { c := state&(IFcleft|IFcright); clr := ""; if(int c) { if(c == (IFcleft|IFcright)) clr = " both"; else if(c == IFcleft) clr = " left"; else clr = " right"; } amt := 0; if(state&IFbrksp) amt = 1; s = sys->sprint("brk(%d%s)", amt, clr); } if(state&IFnobrk) s += " nobrk"; if(!(state&IFwrap)) s += " nowrap"; if(state&IFrjust) s += " rjust"; if(state&IFcjust) s += " cjust"; if(state&IFsmap) s += " smap"; indent := (state&IFindentmask)>>IFindentshift; if(indent > 0) s += " indent=" + string indent; hang := state&IFhangmask; if(hang > 0) s += " hang=" + string hang; return s; } Item.newtext(s: string, fnt, fg, voff: int, ul: byte) : ref Item { return ref Item.Itext(nil, 0, 0, 0, 0, 0, nil, s, fnt, fg, byte voff, ul); } Item.newrule(align: byte, size, noshade: int, wspec: Dimen) : ref Item { return ref Item.Irule(nil, 0, 0, 0, 0, 0, nil, align, byte noshade, size, wspec); } Item.newimage(di: ref Docinfo, src: ref Parsedurl, lowsrc: ref Parsedurl, altrep: string, align: byte, width, height, hspace, vspace, border, ismap, isbkg: int, map: ref Map, name: string, genattr: ref Genattr) : ref Item { ci := CImage.new(src, lowsrc, width, height); state := 0; if(ismap) state = IFsmap; if (isbkg) state = IFbkg; return ref Item.Iimage(nil, 0, 0, 0, 0, state, genattr, len di.images, ci, width, height, altrep, map, name, -1, align, byte hspace, byte vspace, byte border); } Item.newformfield(ff: ref Formfield) : ref Item { return ref Item.Iformfield(nil, 0, 0, 0, 0, 0, nil, ff); } Item.newtable(t: ref Table) : ref Item { return ref Item.Itable(nil, 0, 0, 0, 0, 0, nil, t); } Item.newfloat(it: ref Item, side: byte) : ref Item { return ref Item.Ifloat(nil, 0, 0, 0, 0, IFwrap, nil, it, 0, 0, side, byte 0); } Item.newspacer(spkind, font: int) : ref Item { return ref Item.Ispacer(nil, 0, 0, 0, 0, 0, nil, spkind, font); } Item.revlist(itl: list of ref Item) : list of ref Item { ans : list of ref Item = nil; for( ;itl != nil; itl = tl itl) ans = hd itl :: ans; return ans; } Item.print(it: self ref Item) { s := stringstate(it.state); if(s != "") sys->print("%s\n",s); pick a := it { Itext => sys->print("Text '%s', fnt=%d, fg=%x", a.s, a.fnt, a.fg); Irule => sys->print("Rule wspec=%s, size=%d, al=%s", stringdim(a.wspec), a.size, stringalign(a.align)); Iimage => src := ""; if(a.ci.src != nil) src = a.ci.src.tostring(); map := ""; if(a.map != nil) map = a.map.name; sys->print("Image src=%s, alt=%s, al=%s, w=%d, h=%d hsp=%d, vsp=%d, bd=%d, map=%s, name=%s", src, a.altrep, stringalign(a.align), a.imwidth, a.imheight, int a.hspace, int a.vspace, int a.border, map, a.name); Iformfield => ff := a.formfield; if(ff.ftype == Ftextarea) ty := "textarea"; else if(ff.ftype == Fselect) ty = "select"; else ty = T->revlookup(input_tab, int ff.ftype); sys->print("Formfield %s, fieldid=%d, formid=%d, name=%s, value=%s", ty, ff.fieldid, int ff.form.formid, ff.name, ff.value); Itable => tab := a.table; sys->print("Table tableid=%d, width=%s, nrow=%d, ncol=%d, ncell=%d, totw=%d, toth=%d\n", tab.tableid, stringdim(tab.width), tab.nrow, tab.ncol, tab.ncell, tab.totw, tab.toth); for(cl := tab.cells; cl != nil; cl = tl cl) { c := hd cl; c.content.printlist(sys->sprint("Cell %d.%d, at (%d,%d)", tab.tableid, c.cellid, c.row, c.col)); } sys->print("End of Table %d", tab.tableid); Ifloat => sys->print("Float, x=%d y=%d, side=%s, it=", a.x, a.y, stringalign(a.side)); a.item.print(); sys->print("\n\t"); Ispacer => s = ""; case a.spkind { ISPnull => s = "null"; ISPvline => s = "vline"; ISPhspace => s = "hspace"; } sys->print("Spacer %s ", s); } sys->print(" w=%d, h=%d, a=%d, anchor=%d\n", it.width, it.height, it.ascent, it.anchorid); } Item.printlist(items: self ref Item, msg: string) { sys->print("%s\n", msg); il := items; while(il != nil) { il.print(); il = il.next; } } Formfield.new(ftype, fieldid: int, form: ref Form, name, value: string, size, maxlength: int) : ref Formfield { return ref Formfield(ftype, fieldid, form, name, value, size, maxlength, 0, 0, byte 0, nil, nil, -1, nil, 0); } Form.new(formid: int, name: string, action: ref Parsedurl, target: string, method: int, events: list of Lex->Attr) : ref Form { return ref Form(formid, name, action, target, method, events, 0, 0, nil, FormBuild); } Table.new(tableid: int, align: Align, width: Dimen, border, cellspacing, cellpadding: int, bg: Background, tok: ref Lex->Token) : ref Table { return ref Table(tableid, 0, 0, 0, # nrow, ncol, ncell align, width, border, cellspacing, cellpadding, bg, nil, Abottom, -1, # caption, caption_place, caption_lay nil, nil, nil, nil, # currows, cols, rows, cells 0, 0, 0, 0, # totw, toth, caph, availw nil, tok, byte 0); # grid, tabletok, flags } Tablerow.new(align: Align, bg: Background, flags: byte) : ref Tablerow { return ref Tablerow(nil, # cells 0, 0, # height, ascent align, # align bg, # background Point(0,0), # pos flags); } Tablecell.new(cellid, rowspan, colspan: int, align: Align, wspec: Dimen, hspec: int, bg: Background, flags: byte) : ref Tablecell { if(colspan < 0) colspan = 0; if(rowspan < 0) rowspan = 0; return ref Tablecell(cellid, nil, -1, # content, layid rowspan, colspan, align, flags, wspec, hspec, bg, 0, 0, 0, # minw, maxw, ascent 0, 0, # row, col Point(0,0)); # pos } Dimen.kind(d: self Dimen) : int { return (d.kindspec & Dkindmask); } Dimen.spec(d: self Dimen) : int { return (d.kindspec & Dspecmask); } Dimen.make(kind, spec: int) : Dimen { if(spec & Dkindmask) { if(warn) sys->print("warning: dimension spec too big: %d\n", spec); spec = 0; } return Dimen(kind | spec); } Map.new(name: string) : ref Map { return ref Map(name, nil); } Docinfo.new() : ref Docinfo { ans := ref Docinfo; ans.reset(); return ans; } Docinfo.reset(d: self ref Docinfo) { d.src = nil; d.base = nil; d.referrer = nil; d.doctitle = ""; d.backgrounditem = nil; d.background = (nil, CU->White); d.text = CU->Black; d.link = CU->Blue; d.vlink = CU->Blue; d.alink = CU->Blue; d.target = "_self"; d.refresh = ""; d.chset = (CU->config).charset; d.lastModified = ""; d.scripttype = CU->TextJavascript; d.hasscripts = 0; d.events = nil; d.evmask = 0; d.kidinfo = nil; d.frameid = -1; d.anchors = nil; d.dests = nil; d.forms = nil; d.tables = nil; d.maps = nil; d.images = nil; } Kidinfo.new(isframeset: int) : ref Kidinfo { ki := ref Kidinfo(isframeset, nil, # src "", # name 0, 0, 0, # marginw, marginh, framebd 0, # flags nil, nil, nil # rows, cols, kidinfos ); if(!isframeset) { ki.flags = FRhscrollauto|FRvscrollauto; ki.marginw = FRKIDMARGIN; ki.marginh = FRKIDMARGIN; ki.framebd = 1; } return ki; }