diff options
Diffstat (limited to 'appl/charon/build.b')
| -rw-r--r-- | appl/charon/build.b | 2862 |
1 files changed, 2862 insertions, 0 deletions
diff --git a/appl/charon/build.b b/appl/charon/build.b new file mode 100644 index 00000000..cf3a0b09 --- /dev/null +++ b/appl/charon/build.b @@ -0,0 +1,2862 @@ +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 = "1"; + 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) + CU->raisex("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,r) := 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 +{ + kind := Dnone; + spec := 0; + (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 +{ + 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; +} |
