Oberon/A2/Oberon.HTMLDocs.Mod

From Wikibooks, open books for an open world
< Oberon‎ | A2
Jump to navigation Jump to search

(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)

MODULE HTMLDocs IN Oberon; (** portable *) (* ejz, Greek & Math support by afi *)
    (* Check that the index of ISOToOberon does not exceed 255. ple, 2005-05-08. *)

IMPORT Objects, Input, Strings, Display, Display3, Fonts, HyperDocs, Texts, Gadgets, Documents, HTTPDocs0, TextDocs,
Oberon, TextGadgets, Lists, Attributes, Desktops, Links, Streams, TextStreams;


CONST
(* class *)
WhiteSpace* = 0; OpenTag* = 1; OpenEndTag* = 2; CloseTag* = 3; CharRef* = 4; Character* = 5; Value* = 6; Undef* = 7;
(* state *)
TextPlain* = 1; TextHtml* = 2; InTag* = 3; End* = 4;
(* list types *)
DefList = 0; DescList = 1; OrderedList = 2;
Menu = "Desktops.Copy[Copy] HyperDocs.Back[Back] HyperDocs.Reload[Reload] TextDocs.Search[Search] Desktops.StoreDoc[Store]";
GreekCap = "0013143516173415192134222324252728202931323337372636180000000000";
GreekMin = "004546674849664751536654555657596052616364656969586850";


ASCIIBullets = TRUE;


TYPE
Integer = LONGINT;
DocURL* = POINTER TO DocURLDesc;
DocURLDesc* = RECORD (HyperDocs.DefURLDesc)
dockey*: LONGINT
END;
TextAttrs = POINTER TO TextAttrsDesc;
TextAttrsDesc = RECORD
lib: Objects.Library;
style: TextGadgets.Style;
col, voff: SHORTINT;
next: TextAttrs
END;
Page* = POINTER TO PageDesc;
Scanner* = RECORD
page*: Page;
S*: Streams.Stream;
class*, state*: INTEGER;
value*: ARRAY 1024 OF CHAR;
pre*, end*: BOOLEAN;
ch*, next*, char*: CHAR;
avail: LONGINT
END;
List = POINTER TO ListDesc;
ListDesc = RECORD
style: TextGadgets.Style;
kind, nesting, itemNr: INTEGER;
dtok: BOOLEAN;
next: List
END;
Form* = POINTER TO FormDesc;
FormDesc* = RECORD (TextGadgets.ControlDesc)
elems*: HTTPDocs0.ObjList
END;
PageDesc* = RECORD (Gadgets.ObjDesc)
W*, Ws*: Texts.Writer;
textAttrs: TextAttrs;
T*, source*: Texts.Text;
D*: Documents.Document;
alink*, clink: Objects.Object;
style: TextGadgets.Style;
base*: DocURL;
orgLabel: ARRAY 64 OF CHAR;
lists: List;
orgPos, linkkey*, headerLen, docKey*: LONGINT;
next*: Page;
task: Oberon.Task;
textC*, linkC*, oldLinkC*, textbackC*, lines*: INTEGER;
left*, blank*, head, cacheSource*: BOOLEAN
END;
Item* = POINTER TO ItemDesc;
ItemDesc* = RECORD (Lists.ItemDesc)
value*: ARRAY 64 OF CHAR;
oldSel*, hasVal*: BOOLEAN
END;
TagHandler* = PROCEDURE (VAR S: Scanner; on: BOOLEAN);
ExtTag* = POINTER TO ExtTagDesc;
ExtTagDesc* = RECORD
tag: ARRAY 32 OF CHAR;
handle*: TagHandler;
start*, stop*: PROCEDURE (P: Page);
next: ExtTag
END;
TagAttr* = POINTER TO TagAttrDesc;
TagAttrDesc* = RECORD
name: ARRAY 64 OF CHAR;
value*: ARRAY 512 OF CHAR;
next: TagAttr
END;
Task = POINTER TO TaskDesc;
TaskDesc = RECORD (Oberon.TaskDesc)
S: Scanner;
P: Page
END;


VAR
bullets: ARRAY 2 OF RECORD
f: Fonts.Font;
c: CHAR
END;
Wr*, Wq: Texts.Writer;
imgs*: BOOLEAN;
extTags, newTag*: ExtTag;
entities*: ARRAY 69, 7 OF CHAR;
entityEncoding*: ARRAY 69 OF CHAR;
pages*: Page;
searchAttr: ARRAY 32 OF CHAR;
mono: Fonts.Font;
found: BOOLEAN;
dispW: INTEGER;
GreekTab: ARRAY 128 OF CHAR;


PROCEDURE WriteLn*(P: Page);
BEGIN
INC(P.lines);
IF P.lines < 3 THEN
Texts.WriteLn(P.W); P.blank := FALSE
END
END WriteLn;


PROCEDURE WriteSpace*(P: Page);
BEGIN
IF P.blank THEN
Texts.Write(P.W, " "); P.blank := FALSE
END
END WriteSpace;


PROCEDURE WriteObj*(P: Page; obj: Objects.Object);
BEGIN
Texts.WriteObj(P.W, obj);
IF (obj IS Display.Frame) & (obj(Display.Frame).W >= (HyperDocs.docW-16)) THEN
INC(P.lines); P.blank := FALSE
ELSIF obj IS TextGadgets.Style THEN
P.blank := FALSE
ELSIF ~(obj IS TextGadgets.Control) THEN
P.lines := 0; P.blank := TRUE
END
END WriteObj;


PROCEDURE InitTabs(style: TextGadgets.Style);
VAR i: LONGINT;
BEGIN
style.W := ((dispW DIV 8) * 5)-24; style.width := style.W;
style.noTabs := 32;
FOR i := 0 TO 31 DO
style.tab[i] := SHORT(32+i*32)
END
END InitTabs;


PROCEDURE NewStyle(): TextGadgets.Style;
VAR style: TextGadgets.Style;
BEGIN
style := TextGadgets.newStyle();
InitTabs(style);
Attributes.SetBool(style, "FrameW", TRUE);
RETURN style
END NewStyle;


PROCEDURE Syntax(size: INTEGER; attr: CHAR): Fonts.Font;
VAR
name: ARRAY 32 OF CHAR;
sizeS: ARRAY 4 OF CHAR;
BEGIN
name := "Default";
IF dispW < 800 THEN DEC(size, 2) END;
IF size <= 8 THEN size := 8
ELSIF size <= 10 THEN size := 10
ELSIF size <= 12 THEN size := 12
ELSIF size <= 14 THEN size := 14
ELSIF size <= 16 THEN size := 16
ELSIF size <= 20 THEN size := 20
ELSE size := 24
END;
Strings.IntToStr(size, sizeS);
IF attr = 0X THEN (* skip *)
ELSIF attr = "i" THEN (* skip *)
ELSIF attr = "m" THEN attr := "b"
ELSIF attr = "b" THEN (* skip *)
ELSE attr := 0X
END;
Strings.Append(name, sizeS); Strings.AppendCh(name, attr); Strings.Append(name, ".Scn.Fnt");
RETURN Fonts.This(name)
END Syntax;


PROCEDURE PushTextAttrs*(P: Page);
VAR attr: TextAttrs;
BEGIN
NEW(attr);
attr.lib := P.W.lib; attr.col := P.W.col; attr.voff := P.W.voff;
attr.style := P.style;
attr.next := P.textAttrs; P.textAttrs := attr
END PushTextAttrs;


PROCEDURE PopTextAttrs*(P: Page);
VAR
style: TextGadgets.Style;
M: Objects.CopyMsg;
BEGIN
IF P.textAttrs # NIL THEN
Texts.SetFont(P.W, P.textAttrs.lib);
Texts.SetColor(P.W, P.textAttrs.col);
Texts.SetOffset(P.W, P.textAttrs.voff);
style := P.textAttrs.style;
IF (P.style.mode # style.mode) OR (P.style.leftM # style.leftM) OR (P.style.width # style.width) THEN
M.id := Objects.shallow; Objects.Stamp(M); M.obj := NIL; M.dlink := NIL;
style.handle(style, M); style := M.obj(TextGadgets.Style);
P.style := style; WriteObj(P, style)
END;
P.textAttrs := P.textAttrs.next
ELSE
Texts.SetFont(P.W, Syntax(12, 0X));
Texts.SetColor(P.W, SHORT(P.textC));
Texts.SetOffset(P.W, 0);
style := NewStyle();
IF (P.style.mode # style.mode) OR (P.style.leftM # style.leftM) OR (P.style.width # style.width) THEN
P.style := style; WriteObj(P, style)
END
END
END PopTextAttrs;


PROCEDURE SplitFontName(f: Fonts.Font; VAR family: ARRAY OF CHAR; VAR size: INTEGER; VAR attr: CHAR);
VAR
val: LONGINT;
i: INTEGER;
BEGIN
i := 0;
WHILE (f.name[i] # 0X) & ~Strings.IsDigit(f.name[i]) DO
INC(i)
END;
COPY(f.name, family); family[i] := 0X;
Strings.StrToIntPos(f.name, val, i); size := SHORT(val);
IF Strings.IsAlpha(f.name[i]) THEN
attr := f.name[i]
ELSE
attr := 0X
END
END SplitFontName;


PROCEDURE FontSize(f: Fonts.Font; VAR size: INTEGER);
VAR
family: ARRAY 64 OF CHAR;
attr: CHAR;
BEGIN
SplitFontName(f, family, size, attr)
END FontSize;


PROCEDURE GetFontSize*(P: Page): INTEGER;
VAR size: INTEGER;
BEGIN
FontSize(P.W.lib(Fonts.Font), size);
IF dispW < 800 THEN
INC(size, 2)
END;
IF size <= 8 THEN
size := 1
ELSIF size <= 10 THEN
size := 2
ELSIF size <= 12 THEN
size := 3
ELSIF size <= 14 THEN
size := 4
ELSIF size <= 16 THEN
size := 5
ELSIF size <= 20 THEN
size := 6
ELSE
size := 7
END;
RETURN size
END GetFontSize;


PROCEDURE ChangeFontAttr(f: Fonts.Font; attr: CHAR): Fonts.Font;
VAR
family: ARRAY 64 OF CHAR;
str: ARRAY 4 OF CHAR;
size: INTEGER;
oldattr: CHAR;
BEGIN
IF attr = "m" THEN attr := "b" END;
SplitFontName(f, family, size, oldattr);
IF oldattr # attr THEN
Strings.IntToStr(size, str); Strings.Append(family, str);
IF attr # 0X THEN
Strings.AppendCh(family, attr)
END;
Strings.Append(family, ".Scn.Fnt");
RETURN Fonts.This(family)
ELSE
RETURN f
END
END ChangeFontAttr;


PROCEDURE ChangeFontSize(f: Fonts.Font; size: INTEGER): Fonts.Font;
VAR
family: ARRAY 64 OF CHAR;
str: ARRAY 4 OF CHAR;
oldsize: INTEGER;
attr: CHAR;
BEGIN
IF dispW < 800 THEN
DEC(size, 2)
END;
SplitFontName(f, family, oldsize, attr);
IF oldsize # size THEN
Strings.IntToStr(size, str); Strings.Append(family, str);
IF attr # 0X THEN
Strings.AppendCh(family, attr)
END;
Strings.Append(family, ".Scn.Fnt");
RETURN Fonts.This(family)
ELSE
RETURN f
END
END ChangeFontSize;


PROCEDURE SetFontSize*(P: Page; size: INTEGER);
VAR fnt: Fonts.Font;
BEGIN
fnt := P.W.lib(Fonts.Font);
IF size < 1 THEN
size := 1
ELSIF size > 9 THEN
size := 9
END;
PushTextAttrs(P);
CASE size OF
1: Texts.SetFont(P.W, ChangeFontSize(fnt, 8))
|2: Texts.SetFont(P.W, ChangeFontSize(fnt, 10))
|3: Texts.SetFont(P.W, ChangeFontSize(fnt, 12))
|4: Texts.SetFont(P.W, ChangeFontSize(fnt, 14))
|5: Texts.SetFont(P.W, ChangeFontSize(fnt, 16))
|6: Texts.SetFont(P.W, ChangeFontSize(fnt, 20))
|7: Texts.SetFont(P.W, ChangeFontSize(fnt, 24))
|8: fnt := ChangeFontSize(fnt, 24);
Texts.SetFont(P.W, ChangeFontAttr(fnt, "m"))
|9: fnt := ChangeFontSize(fnt, 24);
Texts.SetFont(P.W, ChangeFontAttr(fnt, "b"))
END
END SetFontSize;


PROCEDURE HorzRule*(P: Page; w, h: INTEGER);
VAR obj: Objects.Object;
BEGIN
IF P.lines <= 0 THEN
WriteLn(P)
END;
h := 2*h;
IF h < 4 THEN
h := 4
END;
obj := Gadgets.CreateObject("BasicFigures.NewRect3D");
Attributes.SetBool(obj, "Filled", TRUE);
Attributes.SetInt(obj, "Color", P.textbackC);
Gadgets.ModifySize(obj(Display.Frame), w, h);
WriteObj(P, obj); P.lines := 1; WriteLn(P)
END HorzRule;


PROCEDURE TextAlign*(CONST align: ARRAY OF CHAR): TextGadgets.Style;
VAR style: TextGadgets.Style;
BEGIN
style := NewStyle(); EXCL(style.mode, TextGadgets.left);
CASE CAP(align[0]) OF
|"L": INCL(style.mode, TextGadgets.left)
|"R": INCL(style.mode, TextGadgets.right)
|"C", "J": INCL(style.mode, TextGadgets.middle)
ELSE (* BLEED... *)
CASE CAP(align[5]) OF
"L": INCL(style.mode, TextGadgets.left)
|"R": INCL(style.mode, TextGadgets.right)
|"C", "J": INCL(style.mode, TextGadgets.middle)
ELSE
RETURN NIL
END
END;
RETURN style
END TextAlign;


PROCEDURE CloseA*(P: Page);
BEGIN
IF P.alink # NIL THEN
WriteObj(P, P.alink); P.alink := NIL;
WHILE (P.W.col = SHORT(P.linkC)) OR (P.W.col = SHORT(P.oldLinkC)) DO
PopTextAttrs(P);
IF P.textAttrs = NIL THEN RETURN END
END
END
END CloseA;


PROCEDURE OpenList(P: Page; kind: INTEGER);
VAR list: List;
BEGIN
PushTextAttrs(P);
NEW(list); list.itemNr := 0;
list.style := NewStyle();
IF P.lists = NIL THEN
list.nesting := 0;
WriteLn(P); WriteLn(P)
ELSE
list.nesting := P.lists.nesting+1
END;
P.style := list.style; WriteObj(P, list.style);
list.dtok := FALSE; list.kind := kind;
list.next := P.lists; P.lists := list
END OpenList;


PROCEDURE CloseList(P: Page);
BEGIN
IF P.lists # NIL THEN
IF P.lists.itemNr = 0 THEN
P.lists.style.leftM := 32
END;
P.lists := P.lists.next
END;
PopTextAttrs(P);
IF P.lists = NIL THEN
WriteLn(P); WriteLn(P)
END
END CloseList;


PROCEDURE FindFormObj*(form: Form; CONST name: ARRAY OF CHAR): Objects.Object;
VAR
ol: HTTPDocs0.ObjList;
oname: ARRAY 64 OF CHAR;
obj: Objects.Object;
BEGIN
obj := NIL;
ol := form.elems;
WHILE ol # NIL DO
Attributes.GetString(ol.obj, "Name", oname);
IF oname = name THEN
obj := ol.obj
END;
ol := ol.next
END;
RETURN obj
END FindFormObj;


PROCEDURE RememberValue*(obj: Objects.Object);
VAR A: Objects.AttrMsg;
BEGIN
A.id := Objects.get; A.name := "Value"; A.class := Objects.Inval;
obj.handle(obj, A);
A.id := Objects.set; A.name := "IniValue";
obj.handle(obj, A)
END RememberValue;


PROCEDURE AddFormObj*(P: Page; form: Form; obj: Objects.Object; CONST name: ARRAY OF CHAR; storeVal, write: BOOLEAN);
VAR ol, op: HTTPDocs0.ObjList;
BEGIN
NEW(ol); ol.obj := obj; ol.next := NIL;
op := form.elems;
WHILE (op # NIL) & (op.next # NIL) DO
op := op.next
END;
IF op # NIL THEN
op.next := ol
ELSE
form.elems := ol
END;
IF storeVal THEN
RememberValue(obj)
END;
IF name # "" THEN
Gadgets.NameObj(obj, name)
END;
IF write THEN
WriteObj(P, obj)
END
END AddFormObj;


PROCEDURE FindA(CONST name: ARRAY OF CHAR);
BEGIN
IF name = searchAttr THEN
found := TRUE
END
END FindA;


PROCEDURE HasA(obj: Objects.Object; CONST name : ARRAY OF CHAR): BOOLEAN;
VAR A: Objects.AttrMsg;
BEGIN
found := FALSE; COPY(name, searchAttr);
A.id := Objects.enum; COPY(name, A.name); A.res := -1;
A.Enum := FindA;
obj.handle(obj, A);
RETURN found
END HasA;


PROCEDURE ResetValues(form: Form);
VAR
A: Objects.AttrMsg;
ol: HTTPDocs0.ObjList;
item: Item;
BEGIN
ol := form.elems;
WHILE ol # NIL DO
IF HasA(ol.obj, "IniValue") THEN
A.id := Objects.get; A.name := "IniValue"; A.class := Objects.Inval;
ol.obj.handle(ol.obj, A);
A.id := Objects.set; A.name := "Value";
ol.obj.handle(ol.obj, A);
Gadgets.Update(ol.obj)
ELSIF ol.obj IS Lists.List THEN
item := ol.obj(Lists.List).items(Item);
WHILE item # NIL DO
item.sel := item.oldSel;
IF item.next # NIL THEN
item := item.next(Item)
ELSE
item := NIL
END
END;
Gadgets.Update(ol.obj)
END;
ol := ol.next
END
END ResetValues;


PROCEDURE GetText*(view: Objects.Object): Texts.Text;
VAR model: Objects.Object;
BEGIN
IF view # NIL THEN
Links.GetLink(view, "Model", model);
IF (model # NIL) & (model IS Texts.Text) THEN
RETURN model(Texts.Text)
ELSE
RETURN NIL
END
ELSE
RETURN NIL
END
END GetText;


(** HTMLDocs.Locate label

Used for hyperlinks within the same page. *)
PROCEDURE Locate*;
VAR
S: Attributes.Scanner;
F: Texts.Finder;
pos: LONGINT;
obj: Objects.Object;
name: ARRAY 32 OF CHAR;
curDoc: Documents.Document;
node: HyperDocs.Node;
text: Texts.Text;
BEGIN
curDoc := Desktops.CurDoc(Gadgets.context);
node := HyperDocs.NodeByDoc(curDoc);
text := GetText(Gadgets.context);
IF (curDoc # NIL) & (text # NIL) THEN
Attributes.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos);
Attributes.Scan(S);
IF (S.class = Attributes.String) OR (S.class = Attributes.Name) THEN
Texts.OpenFinder(F, text, 0);
pos := F.pos; Texts.FindObj(F, obj);
WHILE ~F.eot DO
IF obj IS Gadgets.Frame THEN
Attributes.GetString(obj, "Name", name);
IF S.s = name THEN
IF node # NIL THEN
HyperDocs.RememberOrg(curDoc.dsc(TextGadgets.Frame).org, node, node);
HyperDocs.LinkNodeToDoc(curDoc, node)
END;
HyperDocs.ScrollTo(curDoc.dsc(TextGadgets.Frame), pos);
RETURN
END
END;
pos := F.pos; Texts.FindObj(F, obj)
END
END
END
END Locate;


(** HTMLDocs.ExecNext

Used by clickable images to activate the surrounding hyperlink. *)
PROCEDURE ExecNext*;
VAR
F: Texts.Finder;
exec, obj: Objects.Object;
text: Texts.Text;
BEGIN
exec := Gadgets.executorObj;
text := GetText(Gadgets.context);
IF (text # NIL) & (exec # NIL) THEN
Texts.OpenFinder(F, text, 0);
Texts.FindObj(F, obj);
WHILE ~F.eot DO
IF obj = exec THEN
exec := NIL
ELSIF (exec = NIL) & (obj IS TextGadgets.Control) THEN
Gadgets.ExecuteAttr(obj(TextGadgets.Control), "Cmd", Gadgets.context, NIL, NIL);
RETURN
END;
Texts.FindObj(F, obj)
END
END
END ExecNext;


PROCEDURE CurForm(context: Objects.Object): Form;
VAR
F: Texts.Finder;
exec, obj: Objects.Object;
ol: HTTPDocs0.ObjList;
text: Texts.Text;
BEGIN
IF context # NIL THEN
exec := Gadgets.executorObj; text := GetText(context);
IF text # NIL THEN
Texts.OpenFinder(F, text, 0);
Texts.FindObj(F, obj);
WHILE ~F.eot DO
IF obj IS Form THEN
ol := obj(Form).elems;
WHILE (ol # NIL) & (ol.obj # exec) DO
ol := ol.next
END;
IF ol # NIL THEN
RETURN obj(Form)
END
END;
Texts.FindObj(F, obj)
END
END;
RETURN CurForm(context.dlink)
END;
RETURN NIL
END CurForm;


(** HTMLDocs.Reset

Used by the "Reset" button in forms. *)
PROCEDURE Reset*;
BEGIN
ResetValues(CurForm(Gadgets.context))
END Reset;


PROCEDURE CopyForm(VAR M: Objects.CopyMsg; from, to: Form);
BEGIN
TextGadgets.CopyControl(M, from, to);

to.elems := from.elems

END CopyForm;


PROCEDURE FormHandler(F: Objects.Object; VAR M: Objects.ObjMsg);
VAR
ol: HTTPDocs0.ObjList;
obj, action: Objects.Object;
key: LONGINT;
F1: Form;
BEGIN
WITH F: Form DO
IF M IS Objects.AttrMsg THEN
WITH M: Objects.AttrMsg DO
IF (M.id = Objects.get) & (M.name = "Gen") THEN
M.class := Objects.String;
M.s := "HTMLDocs.NewForm";
M.res := 0
ELSE
TextGadgets.ControlHandler(F, M)
END
END
ELSIF M IS Objects.FileMsg THEN
WITH M: Objects.FileMsg DO
TextGadgets.ControlHandler(F, M);
IF M.id = Objects.load THEN
HyperDocs.LoadLink(M.R, key);
action := Gadgets.CreateObject("BasicGadgets.NewInteger");
Attributes.SetInt(action, "Value", key);
Gadgets.NameObj(action, "@ACTION");
NEW(F.elems);
F.elems.obj := action;
F.elems.next := NIL;
Gadgets.ReadRef(M.R, F.lib, obj);
WHILE obj # NIL DO
NEW(ol);
ol.next := F.elems;
F.elems := ol;
ol.obj := obj;
Gadgets.ReadRef(M.R, F.lib, obj)
END
ELSIF M.id = Objects.store THEN
action := FindFormObj(F, "@ACTION");
Attributes.GetInt(action, "Value", key);
HyperDocs.StoreLink(M.R, key);
ol := F.elems;
WHILE ol # NIL DO
IF ol.obj # action THEN
Gadgets.WriteRef(M.R, F.lib, ol.obj)
END;
ol := ol.next
END;
Gadgets.WriteRef(M.R, F.lib, NIL)
END
END
ELSIF M IS Objects.CopyMsg THEN
WITH M: Objects.CopyMsg DO
IF M.stamp = F.stamp THEN
M.obj := F.dlink
ELSE
NEW(F1);
F.stamp := M.stamp; F.dlink := F1;
CopyForm(M, F, F1);
M.obj := F1
END
END
ELSIF M IS Objects.BindMsg THEN
ol := F.elems;
WHILE ol # NIL DO
ol.obj.handle(ol.obj, M);
ol := ol.next
END
ELSE
TextGadgets.ControlHandler(F, M)
END
END
END FormHandler;


PROCEDURE NewForm*;
VAR form: Form;
BEGIN
NEW(form);
form.W := 0; form.H := 0;
form.elems := NIL;
form.handle := FormHandler;
Objects.NewObj := form
END NewForm;


PROCEDURE HexDigit(i: INTEGER): CHAR;
BEGIN
IF i < 10 THEN
RETURN CHR(i+ORD("0"))
ELSE
RETURN CHR(i-10+ORD("A"))
END
END HexDigit;


PROCEDURE MapCoord(obj: Objects.Object; VAR x, y: INTEGER);
VAR M: Display.LocateMsg;
BEGIN
M.F := NIL; M.loc := NIL; M.res := -1;
M.X := Oberon.Mouse.X; M.Y := Oberon.Mouse.Y;
Display.Broadcast(M);
IF M.loc = obj THEN
x := M.u; y := -M.v
ELSE
x := 0; y := 0
END
END MapCoord;


PROCEDURE ComposeQuery(form: Form; exec: Objects.Object; VAR query: Texts.Text);
VAR
ol: HTTPDocs0.ObjList;
x, y: INTEGER;
obj: Objects.Object;
text: Texts.Text;
name: Objects.Name;
str: ARRAY 64 OF CHAR; (* LEN(Objects.AttrMsg.s) *)
item: Item;
A: Objects.AttrMsg;
R: Texts.Reader;
ch: CHAR;
multisel, done: BOOLEAN;
PROCEDURE WriteEscCh(ch: CHAR);
BEGIN
ch := Strings.OberonToISO[ORD(ch)];
IF (ch < 020X) OR (ch = "+") OR(ch = "&") OR (ch = "=") OR (ch = "?") OR (ch = "%") OR (ch = "$") OR
(ch = ";") OR (ch = "/") OR (ch = "#") OR (ch = ":") THEN
Texts.Write(Wr, "%");
Texts.Write(Wr, HexDigit(ORD(ch) DIV 16));
Texts.Write(Wr, HexDigit(ORD(ch) MOD 16))
ELSIF ch = 020X THEN
Texts.Write(Wr, "+");
ELSIF ch >= CHR(128) THEN
Texts.Write(Wr, "%");
Texts.Write(Wr, HexDigit(ORD(ch) DIV 16));
Texts.Write(Wr, HexDigit(ORD(ch) MOD 16))
ELSE
Texts.Write(Wr, ch)
END
END WriteEscCh;
PROCEDURE IntAttr(i: LONGINT);
BEGIN
Texts.WriteString(Wr, name);
Texts.Write(Wr, "=");
Texts.WriteInt(Wr, i, 0);
Texts.Write(Wr, "&")
END IntAttr;
PROCEDURE StrAttr(CONST s: ARRAY OF CHAR);
VAR i: INTEGER;
BEGIN
Texts.WriteString(Wr, name);
Texts.Write(Wr, "=");
i := 0;
WHILE s[i] # 0X DO
WriteEscCh(s[i]);
INC(i)
END;
Texts.Write(Wr, "&")
END StrAttr;
PROCEDURE RealAttr(y: LONGREAL; l: INTEGER);
BEGIN
Texts.WriteString(Wr, name);
Texts.Write(Wr, "=");
Texts.WriteLongReal(Wr, y, l);
Texts.Write(Wr, "&")
END RealAttr;
BEGIN
NEW(query);
Texts.Open(query, "");
ol := form.elems;
WHILE ol # NIL DO
obj := ol.obj;
Gadgets.GetObjName(obj, name);
IF (name # "") & (name[0] # "@") THEN
IF HasA(obj, "FormElem") THEN
Attributes.GetString(obj, "FormElem", str);
IF (str = "IMAGE") & (exec = obj) THEN
MapCoord(obj, x, y);
Strings.Append(name, ".x");
IntAttr(x);
Gadgets.GetObjName(obj, name);
Strings.Append(name, ".y");
IntAttr(y)
ELSIF str = "SELECT" THEN
done := FALSE;
Attributes.GetBool(obj, "MultiSel", multisel);
item := obj(Lists.List).items(Item);
WHILE (item # NIL) & (~done OR multisel) DO
IF item.sel THEN
done := TRUE;
IF item.hasVal THEN
StrAttr(item.value)
ELSE
StrAttr(item.s)
END
END;
IF item.next # NIL THEN
item := item.next(Item)
ELSE
item := NIL
END
END;
IF ~done THEN
StrAttr("")
END
ELSIF str = "TEXTAREA" THEN
Texts.WriteString(Wr, name);
Texts.Write(Wr, "=");
text := GetText(obj);
IF text # NIL THEN
Texts.OpenReader(R, text, 0);
Texts.Read(R, ch);
WHILE ~R.eot DO
IF ch = Strings.CR THEN
(* SendText -> CRLF *)
Texts.WriteLn(Wr)
ELSE
WriteEscCh(ch)
END;
Texts.Read(R, ch)
END
END;
Texts.Write(Wr, "&")
ELSIF str = "PASSWORD" THEN
Attributes.GetString(obj, "Value", A.s);
StrAttr(A.s)
END
ELSIF HasA(obj, "YesVal") THEN
Attributes.GetBool(obj, "Value", A.b);
IF A.b THEN
Attributes.GetString(obj, "YesVal", str);
StrAttr(str)
ELSIF HasA(obj, "SubmitVal") & (obj = exec) THEN
Attributes.GetString(obj, "SubmitVal", str);
StrAttr(str)
END
ELSIF HasA(obj, "Value") THEN
IF HasA(obj, "MaxLen") THEN
Attributes.GetInt(obj, "MaxLen", A.i);
x := SHORT(A.i)
ELSE
x := -1
END;
A.id := Objects.get;
A.name := "Value";
A.class := Objects.Inval;
A.res := -1;
obj.handle(obj, A);
CASE A.class OF
Objects.Int: IntAttr(A.i);
|Objects.String: IF (x >= 0) & (x < LEN(A.s)) THEN
A.s[x] := 0X
END;
StrAttr(A.s)
|Objects.Real: IF x > 0 THEN
RealAttr(A.x, x)
ELSE
RealAttr(A.x, 15)
END
|Objects.LongReal: IF x > 0 THEN
RealAttr(A.y, x)
ELSE
RealAttr(A.y, 15)
END
ELSE
END
END
END;
ol := ol.next
END;
Texts.Append(query, Wr.buf);
Texts.Delete(query, query.len-1, query.len)
END ComposeQuery;


(** HTMLDocs.SubmitQuery

Used by the "Submit" button in forms. *)
PROCEDURE SubmitQuery*;
VAR
exec, obj: Objects.Object;
attr, qury: ARRAY 32 OF CHAR;
key, mkey: LONGINT;
docname: ARRAY 1024 OF CHAR;
query: Texts.Text;
form: Form;
x, y: INTEGER;
cont: HTTPDocs0.Context;
doc: Documents.Document;
clearCache: BOOLEAN;
BEGIN
query := NIL;
exec := Gadgets.executorObj;
IF HasA(exec, "Query") & HasA(exec, "Method") THEN
clearCache := TRUE;
form := CurForm(Gadgets.context);
obj := FindFormObj(form, "@ACTION");
Attributes.GetInt(obj, "Value", key);
HyperDocs.DocNameByKey(docname, key);
Attributes.GetString(exec, "Query", qury);
IF qury = "ISINDEX" THEN
obj := FindFormObj(CurForm(Gadgets.context), "QUERY");
Attributes.GetString(obj, "Value", attr);
NEW(query);
Texts.Open(query, "");
Texts.WriteString(Wq, attr);
Texts.Append(query, Wq.buf);
attr := "GET"
ELSIF qury = "ISMAP" THEN
IF HasA(exec, "UseMapKey") THEN
clearCache := FALSE;
query := NIL; Attributes.GetInt(exec, "UseMapKey", mkey)
ELSE
mkey := HyperDocs.UndefKey
END;
IF mkey = HyperDocs.UndefKey THEN
NEW(query);
Texts.Open(query, "");
MapCoord(exec, x, y);
Texts.WriteInt(Wq, x, 0);
Texts.Write(Wq, ",");
Texts.WriteInt(Wq, y, 0);
Texts.Append(query, Wq.buf)
ELSE
query := NIL; key := mkey
END;
attr := "GET"
ELSIF qury = "FORM" THEN
ComposeQuery(form, exec, query);
Attributes.GetString(exec, "Method", attr)
ELSE
Texts.WriteString(Wr, "unknown query type ");
Texts.WriteString(Wr, qury);
Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf);
RETURN
END;
IF (attr = "GET") OR (attr = "POST") THEN
NEW(cont);
cont.query := query;
COPY(attr, cont.method);
cont.user := ""; cont.passwd := "";
IF clearCache THEN
HyperDocs.CacheDoc(key, NIL);
HyperDocs.CacheText(key, NIL)
END;
HyperDocs.FollowKeyLink(cont, key)
ELSIF attr = "Authorization" THEN
NEW(cont); cont.query := NIL;
cont.method := "GET";
obj := FindFormObj(form, "Username");
Attributes.GetString(obj, "Value", cont.user);
obj := FindFormObj(form, "Password");
Attributes.GetString(obj, "Value", cont.passwd);
HyperDocs.FollowKeyLink(cont, key)
ELSIF attr = "MAILTO" THEN
NEW(cont);
cont.curDoc := NIL; cont.new := NIL;
cont.replace := FALSE; cont.history := FALSE;
cont.old := HyperDocs.NodeByDoc(Desktops.CurDoc(Gadgets.context));
HyperDocs.context := cont;
HyperDocs.RetrieveLink(key, docname);
doc := Documents.Open(docname);
HyperDocs.context := NIL;
IF (doc # NIL) & (doc.dsc # NIL) THEN
IF (query # NIL) & (doc.dsc IS TextGadgets.Frame) THEN
Texts.WriteLn(Wr); Texts.Insert(query, 0, Wr.buf);
Texts.WriteLn(Wr); Texts.Append(query, Wr.buf);
Texts.Save(query, 0, query.len, Wr.buf);
Texts.Append(doc.dsc(TextGadgets.Frame).text, Wr.buf)
END;
Desktops.ShowDoc(doc)
ELSIF query # NIL THEN
TextDocs.ShowText("mailto query", query, HyperDocs.docW, HyperDocs.docH)
END
ELSE
Texts.WriteString(Wr, "unknown query method ");
Texts.WriteString(Wr, attr);
Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf);
RETURN
END
END
END SubmitQuery;


PROCEDURE HREF(P: Page; VAR href, cmd: ARRAY OF CHAR; VAR key: LONGINT);
VAR i, j: LONGINT;
BEGIN
key := -1; COPY("", cmd); i := 0;
WHILE (href[i] # 0X) & (href[i] <= " ") DO
INC(i)
END;
IF href[i] = "#" THEN
COPY("HTMLDocs.Locate ", cmd); href[0] := " ";
Strings.Append(cmd, href); cmd[16] := 022X;
Strings.AppendCh(cmd, 022X)
ELSE
WHILE (href[i] # 0X) & (href[i] # "#") DO
INC(i)
END;
IF href[i] = "#" THEN
DEC(i); j := 0;
WHILE P.base.path[j] # 0X DO
INC(j)
END; DEC(j);
WHILE (i > 0) & (j > 0) & (href[i] = P.base.path[j]) DO
DEC(i); DEC(j)
END;
IF i = 0 THEN
COPY("HTMLDocs.Locate ", cmd);
j := 0;
WHILE cmd[j] # 0X DO
INC(j)
END;
cmd[j] := 022X; INC(j);
WHILE (href[i] # 0X) & (href[i] # "#") DO
INC(i)
END; INC(i);
WHILE href[i] # 0X DO
cmd[j] := href[i]; INC(j); INC(i)
END;
cmd[j] := 022X; cmd[j+1] := 0X;
RETURN
END
END;
key := HyperDocs.BuildKey(P.base, href);
IF key = HyperDocs.UndefKey THEN
Texts.WriteString(Wr, href);
Texts.WriteString(Wr, " link-typ not supported");
Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf)
END
END
END HREF;


PROCEDURE Read*(VAR S: Scanner);
VAR St: Streams.Stream;
BEGIN
St := S.S; S.ch := S.next;
IF St.eos & (St.Available(St) <= 0) THEN
S.avail := 0; S.next := 0X; S.end := TRUE
ELSE
TextStreams.Read(St, S.next); DEC(S.avail);
Texts.Write(S.page.Ws, S.next);
IF S.ch = Strings.CR THEN
IF S.next = Strings.LF THEN
TextStreams.Read(St, S.next); DEC(S.avail);
Texts.Write(S.page.Ws, S.next)
END;
S.ch := Strings.CR
ELSIF S.ch = Strings.LF THEN
S.ch := Strings.CR
END
END
END Read;


PROCEDURE ChangeFontFamily(f: Fonts.Font; CONST newfamily: ARRAY OF CHAR): Fonts.Font;
VAR
family: ARRAY 64 OF CHAR;
str: ARRAY 4 OF CHAR;
size: INTEGER;
attr: CHAR;
BEGIN
SplitFontName(f, family, size, attr);
IF family # newfamily THEN
COPY(newfamily, family);
Strings.IntToStr(size, str); Strings.Append(family, str);
IF attr # 0X THEN
Strings.AppendCh(family, attr)
END;
Strings.Append(family, ".Scn.Fnt");
RETURN Fonts.This(family)
ELSE
RETURN f
END
END ChangeFontFamily;


PROCEDURE WriteCharRef*(P: Page; VAR S: Scanner);
VAR
i, j, k: LONGINT;
entity: ARRAY 64 OF CHAR; istr: ARRAY 4 OF CHAR;
lib: Objects.Library;
obj: Objects.Object;
fnt: Fonts.Font;
ref: INTEGER;
BEGIN
IF S.ch = "#" THEN
(* Texts.WriteString(Wr, "Numeric character reference pending."); Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf); *)
Read(S);
IF Strings.IsDigit(S.ch) THEN
i := 0;
WHILE ~S.end & (i < 9100) & Strings.IsDigit(S.ch) DO
i := 10*i+ORD(S.ch)-ORD("0"); Read(S)
END;
IF S.ch = ";" THEN
Read(S)
END;
CASE i OF
169: Texts.WriteString(P.W, "(c)")
|174: Texts.WriteString(P.W, "(R)")
|188: Texts.WriteString(P.W, " 1/4")
|189: Texts.WriteString(P.W, " 1/2")
|190: Texts.WriteString(P.W, " 3/4")
ELSE
fnt := P.W.lib(Fonts.Font);
IF (i >= 913) & (i <= 982) THEN (* Greek character *)
Strings.IntToStr(i - 900, istr);
k := 0;
WHILE ((istr[0] # GreekTab[k]) OR (istr[1] # GreekTab[k+1])) & (k < 120) DO
k := k + 2
END;
k := k DIV 2;
Texts.SetFont (P.W, ChangeFontFamily(fnt, "Greek"));
Texts.Write(P.W, CHR(k + ORD("@")));
Texts.SetFont (P.W, fnt);
ELSIF (i >= 8501) & (i <= 9002) THEN (* Math character *)
Texts.SetFont (P.W, ChangeFontFamily(fnt, "Math"));
CASE i OF (* glyph TeX name *)
9001: Texts.Write(P.W, "a") (* a langle *)
(* b *) (* b vdash *)
(* c *) (* c models *)
(* d *) (* d dashv *)
| 8721: Texts.Write(P.W, "e") (* e sum *)
(* f *) (* f ll *)
(* g *) (* g gg *)
| 8968: Texts.Write(P.W, "h") (* h lceil *)
| 8969: Texts.Write(P.W, "i") (* i rceil *)
| 8970: Texts.Write(P.W, "j") (* j lfloor *)
| 8971: Texts.Write(P.W, "k") (* k rfloor *)
(* l *) (* l Vert *)
(* m *) (* m mp *)
(* n *) (* n prec *)
(* o *) (* o succ *)
| 8776: Texts.Write(P.W, "p") (* p simeq *)
| 9002: Texts.Write(P.W, "q") (* q rangle *)
(* r *) (* r not supset *)
| 8629: Texts.Write(P.W, "s") (* s (carriage return) *)
(* t *) (* t ?? *)
(* u *) (* u ?? *)
(* v - moved down *) (* v S *)
(* w *) (* w ?? *)
| 8747: Texts.Write(P.W, "x") (* x int *)
(* y *) (* y oint *)
(* z *) (* z because *)
(* C *) (* C wp *)
(* D *) (* D oslash *)
| 8853: Texts.Write(P.W, "E") (* E oplus *)
| 8709: Texts.Write(P.W, "F") (* F emptyset *)
| 8745: Texts.Write(P.W, "G") (* G cap *)
| 8746: Texts.Write(P.W, "H") (* H cup *)
| 8835: Texts.Write(P.W, "I") (* I supset *)
| 8839: Texts.Write(P.W, "J") (* J supseteq *)
| 8836: Texts.Write(P.W, "K") (* K not subset *)
| 8834: Texts.Write(P.W, "L") (* L subset *)
| 8838: Texts.Write(P.W, "M") (* M subseteq *)
| 8712: Texts.Write(P.W, "N") (* N in *)
| 8713: Texts.Write(P.W, "O") (* O not in *)
| 8736: Texts.Write(P.W, "P") (* P angle *)
| 8711: Texts.Write(P.W, "Q") (* Q nabla *)
| 8704: Texts.Write(P.W, "R") (* R forall *)
| 8707: Texts.Write(P.W, "S") (* S exists *)
| 8715: Texts.Write(P.W, "T") (* T owns *)
(* U *) (* U sqcap *)
| 8730: Texts.Write(P.W, "V") (* V surb *)
| 8901: Texts.Write(P.W, "W") (* W cdot *)
(* X *) (* X lnot *)
| 8869: Texts.Write(P.W, "Y") (* Y land *)
| 8870: Texts.Write(P.W, "Z") (* Z lor *)
(* 0 - moved down *) (* 0 deg *)
(* 1 - moved down *) (* 1 pm *)
| 8805: Texts.Write(P.W, "3") (* 3 geq *)
(* 4 *) (* 4 times *)
| 8733: Texts.Write(P.W, "5") (* 5 propto *)
| 8706: Texts.Write(P.W, "6") (* 6 partial *)
(* 7 - moved down *) (* 7 bullet *)
(* 8 - moved down *) (* 8 div *)
| 8800: Texts.Write(P.W, "9") (* 9 not= *)
(* ) *) (* ) swarrow *)
(* ! *) (* ! mathbb R ?? *)
| 8501: Texts.Write(P.W, "@") (* @ aleph *)
| 8804: Texts.Write(P.W, "#") (* # leq *)
(* $ - moved down *) (* $ / *)
| 8734: Texts.Write(P.W, "%") (* % infty *)
| 8658: Texts.Write(P.W, "^") (* ^ Rightarrow *)
(* & *) (* & >> *)
(* * *) (* * searrow *)
(* ( *) (* ( nwarrow *)
| 8592: Texts.Write(P.W, ",") (* , gets *)
| 8594: Texts.Write(P.W, ".") (* . to *)
| 8773: Texts.Write(P.W, ";") (* ; approx *)
(* ' *) (* ' nearrow *)
(* ` *) (* ` ?? *)
| 8593: Texts.Write(P.W, "-") (* - uparrow *)
(* = *) (* = vert *)
| 8595: Texts.Write(P.W, "/") (* / downarrow *)
| 8656: Texts.Write(P.W, "\") (* \ Leftarrow *)
| 8660: Texts.Write(P.W, "[") (* [ Leftrightarrow *)
| 8657: Texts.Write(P.W, "]") (* ] Uparrow *)
(* < - moved down *) (* < dots *)
| 8722: Texts.Write(P.W, ">") (* > (minus sign) *)
| 8801: Texts.Write(P.W, ":") (* : equiv *)
(* " - moved down *) (* " prime *)
(* ~ - moved down *) (* ~ cents *)
| 8659: Texts.Write(P.W, "_") (* _ Downarrow *)
| 8596: Texts.Write(P.W, "+") (* + leftrightarrow *)
ELSE
END;
Texts.SetFont (P.W, fnt);
ELSIF (i >= 8201) & (i <= 8500) THEN (* Math character *)
Texts.SetFont (P.W, ChangeFontFamily(fnt, "Math"));
CASE i OF (* glyph TeX name *)
  8465: Texts.Write(P.W, "A") (* A Im *)
| 8476: Texts.Write(P.W, "B") (* B Re *)
| 8243: Texts.Write(P.W, "2") (* 2 (double prime) *)
| 8226: Texts.Write(P.W, "7") (* 7 bullet *)
| 8260: Texts.Write(P.W, "$") (* $ / *)
| 8230: Texts.Write(P.W, "<") (* < dots *)
| 8242: Texts.Write(P.W, '"') (* " prime *)
| 8224: Texts.Write(P.W, "}") (* } dagger *)
ELSE
END;
Texts.SetFont (P.W, fnt);
(*
ELSIF (i >= 160) & (i <= 250) THEN (* Math character *)
*)
ELSIF (i = 167) OR (i = 176) OR (i = 177) OR (i = 162) OR (i = 247) THEN (* Math character *)
Texts.SetFont (P.W, ChangeFontFamily(fnt, "Math"));
CASE i OF (* glyph TeX name *)
  167: Texts.Write(P.W, "v") (* v S *)
| 176: Texts.Write(P.W, "0") (* 0 deg *)
| 177: Texts.Write(P.W, "1") (* 1 pm *)
| 247: Texts.Write(P.W, "8") (* 8 div *)
| 162: Texts.Write(P.W, "~") (* ~ cents *)
ELSE
END;
Texts.SetFont (P.W, fnt);

                        (* ISOToOberon is an array of 256 characters whereas HTML can
                        refer to any of 65536 characters, &#0; .. &#65535;. Check that i is
                        a valid index to ISOToOberon. *)

ELSIF (i < 256) THEN
Texts.Write(P.W, Strings.ISOToOberon[i])
ELSE (* The character is not available in ETH Oberon / PC Native. *)
Texts.Write(P.W, CHR(0)) (* Make a nul character. *)
END
END
ELSE (* Faulty HTML character reference. *)
Texts.WriteString(P.W, "&#")
END
ELSE
(* Texts.WriteString(Wr, "Named character entity pending."); Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf); *)
i := 0;
WHILE ~S.end & Strings.IsAlpha(S.ch) & (i < 63) DO
entity[i] := S.ch; INC(i); entity[i] := 0X;
j := 0;
WHILE (j < LEN(entities)) & (entities[j] # entity) DO
INC(j)
END;
IF j < LEN(entities) THEN
i := 63
END;
Read(S)
END;
entity[i] := 0X;
(* Texts.WriteString(Wr, "Entity name = "); Texts.WriteString(Wr, entity); Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf); *)
i := 0;
WHILE (i < LEN(entities)) & (entities[i] # entity) DO
INC(i)
END;
IF i < LEN(entities) THEN
Texts.Write(P.W, entityEncoding[i])
ELSIF entity = "trade" THEN
PushTextAttrs(P); Texts.SetOffset(P.W, 4);
Texts.WriteString(P.W, "TM");
PopTextAttrs(P)
ELSIF entity = "reg" THEN
Texts.WriteString(P.W, "(R)")
ELSIF entity = "copy" THEN
Texts.WriteString(P.W, "(c)")
ELSE
lib := Objects.ThisLibrary("HTMLIcons.Lib");
Objects.GetRef(lib.dict, entity, ref);
IF ref >= 0 THEN
lib.GetObj(lib, ref, obj); WriteObj(P, obj)
ELSE
Texts.Write(P.W, "&"); Texts.WriteString(P.W, entity); RETURN
END
END;
IF S.ch = ";" THEN
Read(S)
END
END
END WriteCharRef;


PROCEDURE CharRefStr*(P: Page; VAR S: Scanner; VAR str: ARRAY OF CHAR);
VAR
i, j: LONGINT;
entity: ARRAY 64 OF CHAR;
BEGIN
COPY("", str);
IF S.ch = "#" THEN
Read(S);
IF Strings.IsDigit(S.ch) THEN
i := 0;
WHILE ~S.end & (i < 256) & Strings.IsDigit(S.ch) DO
i := 10*i+ORD(S.ch)-ORD("0"); Read(S)
END;
IF S.ch = ";" THEN
Read(S)
END;
CASE i OF
 169: Strings.Append(str, "(c)")
|174: Strings.Append(str, "(R)")
|188: Strings.Append(str, " 1/4")
|189: Strings.Append(str, " 1/2")
|190: Strings.Append(str, " 3/4")
ELSE
    IF (i < 256) THEN
        Strings.AppendCh(str, Strings.ISOToOberon[i])
    ELSE (* The character is not available in ETH Oberon / PC Native 05.01.2003. *)
Strings.AppendCh(str, CHR(0)) (* Make a nul character. *)
    END
END
ELSE (* Faulty HTML character reference. *)
Strings.Append(str, "&#")
END
ELSIF Strings.IsAlpha(S.ch) THEN
i := 0;
WHILE ~S.end & Strings.IsAlpha(S.ch) & (i < 63) DO
entity[i] := S.ch; INC(i); entity[i] := 0X;
j := 0;
WHILE (j < LEN(entities)) & (entities[j] # entity) DO
INC(j)
END;
IF j < LEN(entities) THEN
i := 63
END;
Read(S)
END;
entity[i] := 0X;
i := 0;
WHILE (i < LEN(entities)) & (entities[i] # entity) DO
INC(i)
END;
IF i < LEN(entities) THEN
Strings.AppendCh(str, entityEncoding[i])
ELSIF entity = "trade" THEN
Strings.Append(str, "TM")
ELSIF entity = "reg" THEN
Strings.Append(str, "(R)")
ELSIF entity = "copy" THEN
Strings.Append(str, "(c)")
ELSE
Strings.AppendCh(str, "&"); Strings.Append(str, entity); RETURN
END;
IF S.ch = ";" THEN
Read(S)
END
ELSE
COPY("&", str)
END
END CharRefStr;


PROCEDURE Next*(VAR S: Scanner);
VAR i, l: LONGINT;
BEGIN
CASE S.state OF
TextHtml:
IF S.ch = "<" THEN
Read(S);
IF Strings.IsAlpha(S.ch) OR (S.ch = "/") OR (S.ch = "!") THEN
S.class := OpenTag
ELSE
S.char := S.ch; S.class := Character
END
ELSIF S.ch = "&" THEN
Read(S); S.class := CharRef
ELSIF (S.ch <= " ") & (S.ch # 0X) & ~S.pre THEN
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF (S.ch = "<") & (S.class = CloseTag) THEN
Read(S);
IF Strings.IsAlpha(S.ch) OR (S.ch = "/") OR (S.ch = "!") THEN
S.class := OpenTag
ELSE
S.char := S.ch; S.class := Character
END
ELSIF S.end THEN
S.class := Undef; S.state := End
ELSE
S.class := WhiteSpace
END
ELSIF ~S.end THEN
S.char := S.ch; Read(S); S.class := Character(*; S.skipWS := FALSE*)
ELSE
S.class := Undef; S.state := End
END
|TextPlain:
IF S.ch # 0X THEN
S.char := S.ch; Read(S); S.class := Character
ELSE
S.class := Undef; S.state := End
END
|InTag:
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF S.ch = "/" THEN
Read(S); S.class := OpenEndTag
ELSIF S.ch = ">" THEN
Read(S); S.class := CloseTag
ELSIF S.ch = "&" THEN
Read(S); S.class := CharRef
ELSIF Strings.IsAlpha(S.ch) THEN
i := 0; l := LEN(S.value)-1;
WHILE ~S.end & (Strings.IsAlpha(S.ch) OR Strings.IsDigit(S.ch)) DO
IF i < l THEN
S.value[i] := S.ch; INC(i)
END;
Read(S)
END;
S.value[i] := 0X; S.class := Value;
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF S.ch = "<" THEN
Read(S);
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF Strings.IsAlpha(S.ch) THEN
i := 0; l := LEN(S.value)-1;
WHILE ~S.end & (Strings.IsAlpha(S.ch) OR Strings.IsDigit(S.ch)) DO
IF i < l THEN
S.value[i] := S.ch; INC(i)
END;
Read(S)
END
END
END
ELSIF ~S.end THEN
S.char := S.ch; Read(S); S.class := Character
ELSE
S.class := Undef; S.state := End
END
ELSE
S.class := Undef; S.state := End
END
END Next;


PROCEDURE NextAttr*(VAR S: Scanner; VAR name: ARRAY OF CHAR): BOOLEAN;
VAR
i, l: LONGINT;
charRef: ARRAY 16 OF CHAR;
quoted: BOOLEAN;
BEGIN
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF (S.ch = ",") OR (S.ch = ";") THEN
Read(S);
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END
END;
i := 0; l := LEN(name)-1;
WHILE ~S.end & Strings.IsAlpha(S.ch) DO
IF i < l THEN
name[i] := CAP(S.ch); INC(i)
END;
Read(S)
END;
name[i] := 0X;
IF i > 0 THEN
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF S.ch = "=" THEN
Read(S);
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF S.ch = 022X THEN
Read(S); quoted := TRUE
ELSE
quoted := FALSE
END;
i := 0; l := LEN(S.value)-1;
WHILE ~S.end & ( (~quoted & (S.ch > " ") & (S.ch # ">")) OR (quoted & (S.ch # 022X)) ) DO
IF i < l THEN
IF S.ch = "&" THEN
Read(S); CharRefStr(S.page, S, charRef);
S.value[i] := 0X; Strings.Append(S.value, charRef);
i := Strings.Length(S.value)
ELSIF (S.ch >= " ") OR (S.ch = Strings.Tab) THEN
S.value[i] := S.ch; INC(i); Read(S)
ELSE
Read(S)
END
ELSE
Read(S)
END
END;
IF S.ch = 022X THEN
Read(S)
END;
WHILE ~quoted & (i > 0) & (S.value[i-1] <= " ") DO
S.value[i-1] := 0X; DEC(i)
END;
S.value[i] := 0X
ELSE
COPY("", S.value)
END;
S.class := Value; RETURN TRUE
END;
COPY("", name); COPY("", S.value); RETURN FALSE
END NextAttr;


PROCEDURE GetAttrs*(VAR S: Scanner; VAR attrs: TagAttr);
VAR attr: TagAttr;
BEGIN
attrs := NIL; NEW(attr);
WHILE NextAttr(S, attr.name) DO
attr.next := attrs; attrs := attr;
COPY(S.value, attr.value);
NEW(attr)
END
END GetAttrs;


PROCEDURE FindAttr*(attrs: TagAttr; CONST name: ARRAY OF CHAR): TagAttr;
VAR attr: TagAttr;
BEGIN
attr := attrs;
WHILE (attr # NIL) & (attr.name # name) DO
attr := attr.next
END;
RETURN attr
END FindAttr;


PROCEDURE OpenScanner*(VAR S: Scanner; St: Streams.Stream);
BEGIN
(*S.skipWS := FALSE; S.genWS := FALSE;*)
S.ch := 0X; S.next := 0X; S.end := FALSE;
S.S := St; S.class := Undef; S.state := TextHtml; S.pre := FALSE;
Read(S)
END OpenScanner;


PROCEDURE A(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
obj: Objects.Object;
isRef, newDoc: BOOLEAN;
BEGIN
P := S.page; CloseA(P); P.linkkey := HyperDocs.UndefKey;
IF on THEN
isRef := FALSE; newDoc := FALSE;
WHILE NextAttr(S, attr) DO
IF attr = "HREF" THEN
isRef := TRUE; HREF(P, S.value, attr, P.linkkey);
IF P.linkkey # HyperDocs.UndefKey THEN
P.alink := HyperDocs.LinkControl(P.linkkey)
ELSE
P.alink := Gadgets.CreateObject("TextGadgets.NewControl");
Attributes.SetString(P.alink, "Cmd", attr)
END
ELSIF (attr = "NAME") OR (attr = "ID") THEN
IF S.value = P.orgLabel THEN
Texts.Append(P.T, P.W.buf); P.orgPos := P.T.len
END;
obj := Gadgets.CreateObject("TextGadgets.NewControl");
WriteObj(P, obj); Attributes.SetString(obj, "Name", S.value)
ELSIF attr = "TARGET" THEN
newDoc := TRUE
END
END;
IF isRef THEN
IF newDoc THEN
Attributes.SetString(P.alink, "Opt", "New")
END;
IF ~HyperDocs.Visited(P.linkkey) THEN
PushTextAttrs(P); Texts.SetColor(P.W, SHORT(P.linkC))
ELSE
PushTextAttrs(P); Texts.SetColor(P.W, SHORT(P.oldLinkC))
END
END
END
END A;


PROCEDURE Address(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, Syntax(12, "i"))
ELSE
PopTextAttrs(P)
END
END Address;


PROCEDURE B(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, ChangeFontAttr(P.W.lib(Fonts.Font), "b"))
ELSE
PopTextAttrs(P)
END
END B;


PROCEDURE SplitHostPort*(VAR url, host: ARRAY OF CHAR; VAR port: INTEGER);
VAR
i, j: INTEGER;
val: LONGINT;
BEGIN
i := 0;
WHILE (url[i] # 0X) & (url[i] # "/") DO
INC(i)
END;
WHILE url[i] = "/" DO
INC(i)
END;
j := 0;
WHILE (url[j] # 0X) & (url[j] # "@") DO
INC(j)
END;
IF url[j] = "@" THEN
i := j+1
END;
j := 0;
WHILE (url[i] # 0X) & (url[i] # "/") & (url[i] # ":") DO
host[j] := url[i]; INC(i); INC(j)
END;
host[j] := 0X;
IF url[i] = ":" THEN
INC(i); Strings.StrToIntPos(url, val, i);
port := SHORT(val)
ELSE
port := 0
END
END SplitHostPort;


PROCEDURE BASE(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr, label: ARRAY 64 OF CHAR;
s: HyperDocs.LinkScheme;
key: LONGINT;
port: INTEGER;
BEGIN
IF on THEN
P := S.page;
WHILE NextAttr(S, attr) DO
IF attr = "HREF" THEN
P.base.key := HyperDocs.BuildKey(P.base, S.value);
s := HyperDocs.LinkSchemeByKey(P.base.key);
IF s = NIL THEN
s := HyperDocs.LinkSchemeByPrefix("file")
END;
COPY(s.prefix, P.base.prefix);
HyperDocs.RetrieveLink(P.base.key, S.value);
IF P.base.prefix = "http" THEN
P.base.key := HTTPDocs0.SplitHTTPAdr(S.value, P.base.host, P.base.path, label, P.base.port)
ELSIF P.base.prefix = "file" THEN
P.base.key := HyperDocs.SplitFileAdr(S.value, P.base.path, label);
P.base.host := ""; P.base.port := 0
ELSE (* proxy *)
SplitHostPort(S.value, P.base.host, P.base.port);
key := HTTPDocs0.SplitHTTPAdr(S.value, P.base.host, P.base.path, label, port)
END
END
END
END
END BASE;


PROCEDURE HexVal(ch: CHAR): INTEGER;
BEGIN
IF (ch >= "0") & (ch <= "9") THEN
RETURN ORD(ch)-ORD("0")
ELSIF (ch >= "A") & (ch <= "F") THEN
RETURN ORD(ch)-ORD("A")+10
ELSIF (ch >= "a") & (ch <= "f") THEN
RETURN ORD(ch)-ORD("a")+10
ELSE
RETURN 256
END
END HexVal;


PROCEDURE Color(CONST val: ARRAY OF CHAR; VAR col: INTEGER);
VAR
r, g, b, rr, gg, bb, i, bestC: INTEGER;
diff, mdiff: REAL;
BEGIN
IF val = "white" THEN
col := 0; RETURN
ELSIF val = "red" THEN
col := 1; RETURN
ELSIF val = "green" THEN
col := 2; RETURN
ELSIF val = "blue" THEN
col := 3; RETURN
ELSIF val = "black" THEN
col := 15; RETURN
ELSIF val[0] = "#" THEN
r := 16*HexVal(val[1])+HexVal(val[2]);
g := 16*HexVal(val[3])+HexVal(val[4]);
b := 16*HexVal(val[5])+HexVal(val[6])
ELSE
r := 16*HexVal(val[0])+HexVal(val[1]);
g := 16*HexVal(val[2])+HexVal(val[3]);
b := 16*HexVal(val[4])+HexVal(val[5])
END;
IF (r > 255) OR (g > 255) OR (b > 255) THEN
RETURN
END;
mdiff := MAX(REAL); bestC := 0;
i := 0;
WHILE (i < 256) & (mdiff > 0.0) DO
Display.GetColor(i, rr, gg, bb);
diff := LONG(rr-r)*LONG(rr-r);
diff := diff+LONG(gg-g)*LONG(gg-g);
diff := diff+LONG(bb-b)*LONG(bb-b);
IF diff < mdiff THEN
bestC := i; mdiff := diff
END;
INC(i)
END;
col := bestC
END Color;


PROCEDURE BASEFONT(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
val: LONGINT;
size, i: INTEGER;
col: BOOLEAN;
BEGIN
P := S.page;
IF on THEN
col := FALSE; size := GetFontSize(P);
WHILE NextAttr(S, attr) DO
IF attr = "SIZE" THEN
i := 1;
IF S.value[0] = "-" THEN
Strings.StrToIntPos(S.value, val, i);
size := size-SHORT(val)
ELSIF S.value[0] = "+" THEN
Strings.StrToIntPos(S.value, val, i);
size := size+SHORT(val)
ELSE
i := 0;
Strings.StrToIntPos(S.value, val, i);
size := SHORT(val)
END
ELSIF attr = "COLOR" THEN
Color(S.value, P.textC); col := TRUE
END
END;
SetFontSize(P, size);
IF col THEN
Texts.SetColor(P.W, SHORT(P.textC))
END
ELSE
PopTextAttrs(P)
END
END BASEFONT;


PROCEDURE BIG(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
SetFontSize(P, GetFontSize(P)+2)
ELSE
PopTextAttrs(P)
END
END BIG;


PROCEDURE BLINK(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetColor(P.W, SHORT(Display3.red))
ELSE
PopTextAttrs(P)
END
END BLINK;


PROCEDURE BLOCKQUOTE(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
P.style := NewStyle();
WriteObj(P, P.style);
Texts.SetFont(P.W, Syntax(12, "i"))
ELSE
PopTextAttrs(P)
END;
(*S.skipWS := TRUE*)
END BLOCKQUOTE;


PROCEDURE ChangeColors(P: Page; from, to: SHORTINT);
VAR tattr: TextAttrs;
BEGIN
IF P.W.col = from THEN
Texts.SetColor(P.W, to)
END;
tattr := P.textAttrs;
WHILE tattr # NIL DO
IF tattr.col = from THEN
tattr.col := to
END;
tattr := tattr.next
END
END ChangeColors;


PROCEDURE BODY(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
key: LONGINT;
doc: Documents.Document;
BackE: HTTPDocs0.Entry;
col: INTEGER;
BEGIN
P := S.page;
IF on THEN
Texts.Append(P.T, P.W.buf);
WHILE NextAttr(S, attr) DO
IF attr = "BGCOLOR" THEN
Color(S.value, P.textbackC); Attributes.SetInt(P.D.dsc, "Color", P.textbackC)
ELSIF attr = "TEXT" THEN
col := P.textC; Color(S.value, col);
ChangeColors(P, SHORT(P.textC), SHORT(col));
P.textC := col
ELSIF attr = "LINK" THEN (* hyper text link *)
col := P.linkC; Color(S.value, col);
ChangeColors(P, SHORT(P.linkC), SHORT(col));
P.linkC := col; Attributes.SetInt(P.D.dsc, "LinkColor", P.linkC)
ELSIF attr = "VLINK" THEN (* visited hyper text link *)
col := P.oldLinkC; Color(S.value, col);
ChangeColors(P, SHORT(P.oldLinkC), SHORT(col));
P.oldLinkC := col; Attributes.SetInt(P.D.dsc, "OldLinkColor", P.oldLinkC)
ELSIF attr = "BACKGROUND" THEN
key := HyperDocs.BuildKey(P.base, S.value);
IF imgs THEN
NEW(BackE);
BackE.basekey := P.base.dockey; BackE.ol := NIL; BackE.attrs := NIL; BackE.text := P.T;
BackE.pos := -1; BackE.key := HyperDocs.UndefKey; BackE.obj := P.D;
NEW(doc); HyperDocs.DocNameByKey(doc.name, key); doc.handle := NIL; doc.dsc := NIL;
HTTPDocs0.RequestDoc(doc, HTTPDocs0.httpProxy, key, "GET", FALSE, BackE, NIL)
END
END
END
END
END BODY;


PROCEDURE BR(VAR S: Scanner; on: BOOLEAN);
BEGIN
IF on THEN
WriteLn(S.page)(*; S.page.lines := 0*)
END;
(*S.skipWS := TRUE*)
END BR;


PROCEDURE ExecAttrs*;
VAR
exec, cont: Objects.Object;
name, nr: ARRAY 32 OF CHAR;
i: LONGINT;
BEGIN
cont := Gadgets.context; exec := Gadgets.executorObj;
IF exec # NIL THEN
i := 1; name := "Cmd";
Strings.IntToStr(i, nr);
Strings.Append(name, nr);
WHILE Attributes.FindAttr(name, exec(Gadgets.Frame).attr) # NIL DO
Gadgets.ExecuteAttr(exec(Gadgets.Frame), name, cont, NIL, NIL);
INC(i); name := "Cmd";
Strings.IntToStr(i, nr);
Strings.Append(name, nr)
END
END
END ExecAttrs;


PROCEDURE CALL(VAR S: Scanner; on: BOOLEAN);
(* ejz *)
VAR
P: Page;
name, attr: ARRAY 32 OF CHAR;
i: LONGINT;
BEGIN
P := S.page; CloseA(P);
IF on THEN
P.clink := Gadgets.CreateObject("TextGadgets.NewControl");
i := 0;
WHILE NextAttr(S, attr) DO
IF attr = "CMD" THEN
INC(i); name := "Cmd";
Strings.IntToStr(i, attr);
Strings.Append(name, attr);
Attributes.SetString(P.clink, name, S.value)
END
END;
IF i = 0 THEN
P.clink := NIL
ELSE
Attributes.SetString(P.clink, "Cmd", "HTMLDocs.ExecAttrs")
END;
PushTextAttrs(P);
Texts.SetColor(P.W, SHORT(Display3.red))
ELSE
IF P.clink # NIL THEN
WriteObj(P, P.clink)
END;
P.clink := NIL; PopTextAttrs(P)
END
END CALL;


PROCEDURE CENTER(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
P.style := NewStyle();
EXCL(P.style.mode, TextGadgets.left); INCL(P.style.mode, TextGadgets.middle);
WriteObj(P, P.style)
ELSE
PopTextAttrs(P)
END;
 (*S.skipWS := TRUE*)
END CENTER;


PROCEDURE CITE(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, ChangeFontAttr(P.W.lib(Fonts.Font), "i"))
ELSE
PopTextAttrs(P)
END
END CITE;


PROCEDURE CODEx(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono)
ELSE
PopTextAttrs(P)
END
END CODEx;


PROCEDURE DD(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
(* IF (P.lists.dtok) THEN
Texts.WriteString(Wr, "P.lists.dtok = TRUE"); Texts.WriteLn(Wr); Texts.Append(Oberon.Log, Wr.buf)
END; *)
(* <DL> and <DD> can exist with <DT> absent. *)
IF (P.lists # NIL) & (P.lists.kind = DescList) (* & P.lists.dtok *) THEN
Texts.Write(P.W, Strings.Tab)
ELSE
IF P.lists # NIL THEN
INC(P.lists.itemNr);
IF P.lists.itemNr > 1 THEN
WriteLn(P)
END
ELSE
WriteLn(P)
END
END
END;
P.blank := FALSE
(*S.skipWS := TRUE*)
END DD;


PROCEDURE DFN(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, ChangeFontAttr(P.W.lib(Fonts.Font), "i"))
ELSE
PopTextAttrs(P)
END
END DFN;


PROCEDURE DIR(VAR S: Scanner; on: BOOLEAN);
BEGIN
IF on THEN
OpenList(S.page, DefList)
ELSE
CloseList(S.page)
END;
(*S.skipWS := TRUE*)
END DIR;


PROCEDURE DIVI(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 32 OF CHAR;
style: TextGadgets.Style;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
style := NIL;
WHILE NextAttr(S, attr) DO
IF attr = "ALIGN" THEN
style := TextAlign(S.value)
END
END;
IF style # NIL THEN
P.style := style; WriteObj(P, P.style)
END
ELSE
PopTextAttrs(P)
END;
(*S.skipWS := TRUE*)
END DIVI;


PROCEDURE DL(VAR S: Scanner; on: BOOLEAN);
BEGIN
IF on THEN
OpenList(S.page, DescList)
ELSE
CloseList(S.page)
END;
(*S.skipWS := TRUE*)
END DL;


PROCEDURE DT(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
IF P.lists # NIL THEN
INC(P.lists.itemNr);
IF P.lists.itemNr > 1 THEN
WriteLn(P)
END;
IF P.lists.kind = DescList THEN
P.lists.dtok := TRUE;
Texts.Write(P.W, Strings.Tab)
END
ELSE
WriteLn(P)
END
END;
P.blank := FALSE
(*S.skipWS := TRUE*)
END DT;


PROCEDURE EM(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, ChangeFontAttr(P.W.lib(Fonts.Font), "i"))
ELSE
PopTextAttrs(P)
END
END EM;


PROCEDURE FRAME(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
BEGIN
P := S.page; CloseA(P); P.linkkey := HyperDocs.UndefKey;
IF on THEN
WHILE NextAttr(S, attr) DO
IF (attr = "SRC") OR (attr = "HREF") THEN
P.lines := 0; WriteLn(P); WriteLn(P); Texts.WriteString(P.W, "FRAME Reference: ");
HREF(P, S.value, attr, P.linkkey);
IF P.linkkey # HyperDocs.UndefKey THEN
P.alink := HyperDocs.LinkControl(P.linkkey);
HyperDocs.RetrieveLink(P.linkkey, S.value);
Attributes.SetString(P.alink, "Opt", "N")
ELSE
P.alink := Gadgets.CreateObject("TextGadgets.NewControl");
Attributes.SetString(P.alink, "Cmd", attr)
END;
PushTextAttrs(P); Texts.SetColor(P.W, SHORT(P.linkC));
Texts.WriteString(P.W, S.value); WriteObj(P, P.alink);
P.alink := NIL; P.linkkey := HyperDocs.UndefKey;
PopTextAttrs(P); WriteLn(P)
END
END
END
END FRAME;


PROCEDURE FONT(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
val: LONGINT;
size, i, textC: INTEGER;
col: BOOLEAN;
BEGIN
P := S.page;
IF on THEN
col := FALSE; size := GetFontSize(P);
WHILE NextAttr(S, attr) DO
IF attr = "SIZE" THEN
i := 1;
IF S.value[0] = "-" THEN
Strings.StrToIntPos(S.value, val, i);
size := size-SHORT(val)
ELSIF S.value[0] = "+" THEN
Strings.StrToIntPos(S.value, val, i);
size := size+SHORT(val)
ELSE
i := 0;
Strings.StrToIntPos(S.value, val, i);
size := SHORT(val)
END
ELSIF attr = "COLOR" THEN
Color(S.value, textC); col := TRUE
END
END;
SetFontSize(P, size);
IF col THEN
Texts.SetColor(P.W, SHORT(textC))
END
ELSE
PopTextAttrs(P)
END
END FONT;


PROCEDURE GREEN(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetColor(P.W, SHORT(Display3.green))
ELSE
PopTextAttrs(P)
END
END GREEN;


PROCEDURE H(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
style: TextGadgets.Style;
BEGIN
P := S.page;
IF on THEN
style := NIL; PushTextAttrs(P);
IF ~P.left THEN
WriteLn(P); WriteLn(P)
ELSE
P.left := FALSE
END;
CASE ORD(S.value[1])-ORD("0") OF
1: Texts.SetFont(P.W, Syntax(20, 0X))
|2: Texts.SetFont(P.W, Syntax(16, 0X))
|3: Texts.SetFont(P.W, Syntax(16, "i"))
|4: Texts.SetFont(P.W, Syntax(14, 0X))
|5: Texts.SetFont(P.W, Syntax(14, "i"))
|6: Texts.SetFont(P.W, Syntax(12, 0X))
ELSE
Texts.SetFont(P.W, Syntax(12, 0X))
END;
WHILE NextAttr(S, attr) DO
IF attr = "ALIGN" THEN
style := TextAlign(S.value)
ELSIF attr = "SRC" THEN
(* get image *)
ELSIF attr = "DINGBAT" THEN
(* get icon with name value *)
END
END;
IF style # NIL THEN
P.style := style; WriteObj(P, P.style)
END
ELSE
CloseA(P); PopTextAttrs(P);
WriteLn(P); WriteLn(P)
END;
(*S.skipWS := TRUE*)
END H;


PROCEDURE HEAD(VAR S: Scanner; on: BOOLEAN);
(* ignore *)
END HEAD;


PROCEDURE HP(VAR S: Scanner; on: BOOLEAN);
(* ignore *)
END HP;


PROCEDURE HR(VAR S: Scanner; on: BOOLEAN);
VAR
attr: ARRAY 64 OF CHAR;
P: Page;
l: LONGINT;
w, h, pos: INTEGER;
BEGIN
IF on THEN
P := S.page; CloseA(P);
w := dispW; h := 1;
WHILE NextAttr(S, attr) DO
IF attr = "SIZE" THEN
Strings.StrToInt(S.value, l); h := SHORT(l)
ELSIF attr = "WIDTH" THEN
pos := 0; Strings.StrToIntPos(S.value, l, pos);
w := SHORT(l);
IF S.value[pos] = "%" THEN
w := SHORT( (LONG((5*dispW) DIV 8) * w) DIV LONG(100) )
END
END
END;
HorzRule(P, w, h)
END;
(*S.skipWS := TRUE*)
END HR;


PROCEDURE HTML(VAR S: Scanner; on: BOOLEAN);

(*

BEGIN
IF ~on THEN
Texts.WriteLn(S.page.W);
(*S.skipWS := TRUE; *)S.state := TextPlain
END *)
END HTML;


PROCEDURE HTTP(VAR S: Scanner; on: BOOLEAN);
(* ignore *)
END HTTP;


PROCEDURE I(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, ChangeFontAttr(P.W.lib(Fonts.Font), "i"))
ELSE
PopTextAttrs(P)
END
END I;


PROCEDURE KBD(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono)
ELSE
PopTextAttrs(P)
END
END KBD;


PROCEDURE LI(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
i: INTEGER;
BEGIN
P := S.page;
IF on THEN
IF P.lists # NIL THEN
INC(P.lists.itemNr);
IF P.lists.itemNr > 1 THEN
WriteLn(P)
END;
FOR i := 1 TO P.lists.nesting DO
Texts.Write(P.W, Strings.Tab)
END
ELSE
WriteLn(P)
END;
IF (P.lists # NIL) & (P.lists.kind = OrderedList) THEN
Texts.WriteInt(P.W, P.lists.itemNr, 0)
ELSE
IF P.lists # NIL THEN
i := P.lists.nesting MOD LEN(bullets)
ELSE
i := 0
END;
PushTextAttrs(P);
Texts.SetFont(P.W, bullets[i].f);
Texts.Write(P.W, bullets[i].c);
PopTextAttrs(P)
END;
Texts.Write(P.W, Strings.Tab)
END;
P.blank := FALSE
(*S.skipWS := TRUE*)
END LI;


PROCEDURE LINK(VAR S: Scanner; on: BOOLEAN);

(* VAR

P: Page;
attr, cmd, caption: ARRAY 64 OF CHAR;
key: LONGINT;
obj: Objects.Object;
BEGIN
IF on THEN
P := S.page; caption := ""; cmd := "";
WHILE NextAttr(S, attr) DO
IF attr = "HREF" THEN
HREF(P, S.value, cmd, key);
IF key # HyperDocs.UndefKey THEN
cmd := "HyperDocs.FollowLink #Key"
END
ELSIF attr = "REV" THEN
IF caption = "" THEN
COPY(S.value, caption)
END
ELSIF attr = "REL" THEN
IF caption = "" THEN
COPY(S.value, caption)
END
ELSIF attr = "TITLE" THEN
COPY(S.value, caption)
END
END;
IF cmd # "" THEN
obj := Gadgets.CreateObject("BasicGadgets.NewButton");
Attributes.SetString(obj, "Caption", caption);
Attributes.SetString(obj, "Cmd", cmd);

(* ?! fixup on reload *)

Attributes.SetInt(obj, "Key", key);
WriteObj(P, obj)
END
END *)
END LINK;


PROCEDURE LISTING(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono);
S.pre := TRUE
ELSE
PopTextAttrs(P);
S.pre := FALSE
END
END LISTING;


PROCEDURE ISINDEX(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
objt, objb: Objects.Object;
form: Form;
attr: ARRAY 64 OF CHAR;
prompt: ARRAY 512 OF CHAR;
key: LONGINT;
BEGIN
IF on THEN
P := S.page;
key := P.base.key; NewForm(); form := Objects.NewObj(Form);
prompt := "This is a searchable index. Enter search keywords: ";
WHILE NextAttr(S, attr) DO
IF attr = "PROMPT" THEN
COPY(S.value, prompt)
ELSIF attr = "HREF" THEN
key := HyperDocs.BuildKey(P.base, S.value)
END
END;
WriteObj(P, form); HorzRule(P, dispW, 1);
Texts.WriteString(P.W, prompt);
objt := Gadgets.CreateObject("BasicGadgets.NewInteger");
Attributes.SetInt(objt, "Value", key);
AddFormObj(P, form, objt, "@ACTION", FALSE, FALSE);
objt := Gadgets.CreateObject("TextFields.NewTextField");
AddFormObj(P, form, objt, "QUERY", FALSE, TRUE);
objb := Gadgets.CreateObject("BasicGadgets.NewButton");
objb(Display.Frame).H := objt(Display.Frame).H;
Attributes.SetString(objb, "Query", "ISINDEX");
Attributes.SetString(objb, "Method", "GET");
Attributes.SetString(objb, "Caption", "Search");
Attributes.SetString(objb, "Cmd", "HTMLDocs.SubmitQuery");
AddFormObj(P, form, objb, "", FALSE, TRUE);
HorzRule(P, dispW, 1)
END
END ISINDEX;


PROCEDURE MENU(VAR S: Scanner; on: BOOLEAN);
BEGIN
IF on THEN
OpenList(S.page, DefList)
ELSE
CloseList(S.page)
END;
(*S.skipWS := TRUE*)
END MENU;


PROCEDURE META(VAR S: Scanner; on: BOOLEAN);
(* ignore *)
END META;


PROCEDURE NEXTID(VAR S: Scanner; on: BOOLEAN);
(* ignore *)
END NEXTID;


PROCEDURE NOFRAMES(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
HorzRule(P, dispW, 1)
END NOFRAMES;


PROCEDURE OL(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
OpenList(P, OrderedList)
ELSE
CloseList(P)
END;
(*S.skipWS := TRUE*)
END OL;


PROCEDURE P(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
style: TextGadgets.Style;
BEGIN
P := S.page;
IF on THEN
style := NIL; CloseA(P);
WriteLn(P); WriteLn(P); (*P.lines := 0;*)
WHILE NextAttr(S, attr) DO
IF attr = "ALIGN" THEN
style := TextAlign(S.value)
END
END;
IF style # NIL THEN
PushTextAttrs(P);
P.style := style; WriteObj(P, P.style)
END
ELSE
PopTextAttrs(P)
END;
(*S.skipWS := TRUE*)
END P;


PROCEDURE PLAINTEXT(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono);
S.state := TextPlain; S.pre := TRUE
ELSE
PopTextAttrs(P);
S.state := TextHtml; S.pre := FALSE
END
END PLAINTEXT;


PROCEDURE PRE(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono);
S.pre := TRUE
ELSE
PopTextAttrs(P);
S.pre := FALSE
END
END PRE;


PROCEDURE Q(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
Texts.WriteString(P.W, "<<")
ELSE
Texts.WriteString(P.W, ">>")
END
END Q;


PROCEDURE Range(VAR S: Scanner; on: BOOLEAN);
(* ignore *)
END Range;


PROCEDURE SAMP(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono)
ELSE
PopTextAttrs(P)
END
END SAMP;


PROCEDURE SMALL(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
SetFontSize(P, GetFontSize(P)-2)
ELSE
PopTextAttrs(P)
END
END SMALL;


PROCEDURE STRIKE(VAR S: Scanner; on: BOOLEAN);
(* strikethrough text, how ? *)
(* ignore *)
END STRIKE;


PROCEDURE STRONG(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, ChangeFontAttr(P.W.lib(Fonts.Font), "b"))
ELSE
PopTextAttrs(P)
END
END STRONG;


PROCEDURE STYLE(VAR S: Scanner; on: BOOLEAN);
(* ignore *)
END STYLE;


PROCEDURE SUB(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
SetFontSize(P, GetFontSize(P)-2);
Texts.SetOffset(P.W, P.W.voff-4)
ELSE
PopTextAttrs(P)
END
END SUB;


PROCEDURE SUP(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
SetFontSize(P, GetFontSize(P)-2);
Texts.SetOffset(P.W, P.W.voff+4)
ELSE
PopTextAttrs(P)
END
END SUP;


PROCEDURE TINY(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
SetFontSize(P, GetFontSize(P)-2)
ELSE
PopTextAttrs(P)
END
END TINY;


PROCEDURE TITLE(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF P.head THEN
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, Syntax(12, 0X));
Texts.WriteString(P.W, "Title: ")
ELSE
WriteLn(P); PopTextAttrs(P);
HorzRule(P, dispW, 2);
Texts.Append(P.T, P.W.buf);
P.headerLen := P.T.len
END
ELSIF ~on THEN
PushTextAttrs(P);
Texts.OpenWriter(P.W);
PopTextAttrs(P)
END
(*S.skipWS := TRUE*)
END TITLE;


PROCEDURE TT(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono)
ELSE
PopTextAttrs(P)
END
END TT;


PROCEDURE TAB(VAR S: Scanner; on: BOOLEAN);
VAR
P: Page;
attr: ARRAY 64 OF CHAR;
style: TextGadgets.Style;
BEGIN
P := S.page;
IF on THEN
Texts.Write(P.W, Strings.Tab);
style := NIL;
WHILE NextAttr(S, attr) DO
IF attr = "ALIGN" THEN
style := TextAlign(S.value)
END
END;
IF style # NIL THEN
PushTextAttrs(P);
P.style := style; WriteObj(P, P.style)
END
ELSE
PopTextAttrs(P)
END;
P.blank := FALSE
(*S.skipWS := TRUE*)
END TAB;


PROCEDURE U(VAR S: Scanner; on: BOOLEAN);
(* underline text, how ? *)
(* ignore *)
END U;


PROCEDURE UL(VAR S: Scanner; on: BOOLEAN);
BEGIN
IF on THEN
OpenList(S.page, DefList)
ELSE
CloseList(S.page)
END;
(*S.skipWS := TRUE*)
END UL;


PROCEDURE VARN(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, ChangeFontAttr(P.W.lib(Fonts.Font), "i"))
ELSE
PopTextAttrs(P)
END
END VARN;


PROCEDURE XMP(VAR S: Scanner; on: BOOLEAN);
VAR P: Page;
BEGIN
P := S.page;
IF on THEN
PushTextAttrs(P);
Texts.SetFont(P.W, mono);
S.pre := TRUE
ELSE
PopTextAttrs(P); S.pre := FALSE
END
END XMP;


PROCEDURE HandleTag(VAR S: Scanner);
VAR
Ss: Texts.Scanner;
gen: ARRAY 64 OF CHAR;
e: ExtTag;
i: INTEGER;
found, on: BOOLEAN;
prev: CHAR;
BEGIN
WHILE ~S.end & (S.ch <= " ") DO
Read(S)
END;
IF S.ch = "!" THEN
prev := S.ch; Read(S);
IF S.ch = "-" THEN
prev := S.ch; Read(S);
IF S.ch = "-" THEN
WHILE ~S.end & ((S.ch # ">") OR ((prev # "-") & (prev # "!"))) DO
IF S.ch > " " THEN
prev := S.ch
END;
Read(S)
END
ELSE
WHILE ~S.end & (S.ch # ">") DO
Read(S)
END
END
ELSE
WHILE ~S.end & (S.ch # ">") DO
Read(S)
END
END;
Read(S)
ELSIF ~Strings.IsAlpha(S.ch) & (S.ch # "/") THEN
Texts.Write(S.page.W, "<")
ELSE
S.state := InTag; Next(S);
IF S.class = OpenEndTag THEN
on := FALSE; Next(S)
ELSE
on := TRUE
END;
IF S.class = Value THEN
Strings.Upper(S.value, S.value); found := TRUE;
CASE S.value[0] OF
"A": IF S.value[1] = 0X THEN
A(S, on)
ELSIF (S.value = "ADDR") OR (S.value = "Address") THEN
Address(S, on)
ELSE
found := FALSE
END
|"B": IF S.value[1] = 0X THEN
B(S, on)
ELSIF S.value = "BASE" THEN
BASE(S, on)
ELSIF S.value = "BASEFONT" THEN
BASEFONT(S, on)
ELSIF S.value = "BIG" THEN
BIG(S, on)
ELSIF S.value = "BLINK" THEN
BLINK(S, on)
ELSIF (S.value = "BQ") OR (S.value = "BLOCKQUOTE") THEN
BLOCKQUOTE(S, on)
ELSIF S.value = "BODY" THEN
BODY(S, on)
ELSIF S.value = "BR" THEN
BR(S, on)
ELSE
found := FALSE
END
|"C": IF S.value = "CALL" THEN
CALL(S, on)
ELSIF S.value = "CENTER" THEN
CENTER(S, on)
ELSIF S.value = "CITE" THEN
CITE(S, on)
ELSIF S.value = "CODE" THEN
CODEx(S, on)
ELSE
found := FALSE
END
|"D": IF S.value = "DD" THEN
DD(S, on)
ELSIF S.value = "DFN" THEN
DFN(S, on)
ELSIF S.value = "DIR" THEN
DIR(S, on)
ELSIF S.value = "DIV" THEN
DIVI(S, on)
ELSIF S.value = "DL" THEN
DL(S, on)
ELSIF S.value = "DT" THEN
DT(S, on)
ELSE
found := FALSE
END
|"E": IF S.value = "EM" THEN
EM(S, on)
ELSE
found := FALSE
END
|"F": IF S.value = "FRAME" THEN
FRAME(S, on)
ELSIF S.value = "FONT" THEN
FONT(S, on)
ELSE
found := FALSE
END
|"G": IF S.value = "GREEN" THEN
GREEN(S, on)
ELSE
found := FALSE
END
|"H": IF (S.value = "HEAD") OR (S.value = "HEADER") THEN
HEAD(S, on)
ELSIF S.value = "HP" THEN
HP(S, on)
ELSIF S.value = "HR" THEN
HR(S, on)
ELSIF S.value = "HTML" THEN
HTML(S, on)
ELSIF S.value = "HTTP" THEN
HTTP(S, on)
ELSIF Strings.IsDigit(S.value[1]) THEN
H(S, on)
ELSE
found := FALSE
END
|"I": IF S.value[1] = 0X THEN
I(S, on)
ELSIF S.value = "ISINDEX" THEN
ISINDEX(S, on)
ELSE
found := FALSE
END
|"K": IF S.value = "KBD" THEN
KBD(S, on)
ELSE
found := FALSE
END
|"L": IF S.value = "LI" THEN
LI(S, on)
ELSIF S.value = "LINK" THEN
LINK(S, on)
ELSIF S.value = "LISTING" THEN
LISTING(S, on)
ELSE
found := FALSE
END
|"M": IF S.value = "MENU" THEN
MENU(S, on)
ELSIF S.value = "META" THEN
META(S, on)
ELSE
found := FALSE
END
|"N": IF S.value = "NEXTID" THEN
NEXTID(S, on)
ELSIF S.value = "NOFRAMES" THEN
NOFRAMES(S, on)
ELSE
found := FALSE
END
|"O": IF S.value = "OL" THEN
OL(S, on)
ELSE
found := FALSE
END
|"P": IF S.value[1] = 0X THEN
P(S, on)
ELSIF S.value = "PLAINTEXT" THEN
PLAINTEXT(S, on)
ELSIF S.value = "PRE" THEN
PRE(S, on)
ELSE
found := FALSE
END
|"Q": IF S.value[1] = 0X THEN
Q(S, on)
ELSE
found := FALSE
END
|"R": IF S.value = "RANGE" THEN
Range(S, on)
ELSE
found := FALSE
END
|"S": IF S.value[1] = 0X THEN
STRIKE(S, on)
ELSIF S.value = "SAMP" THEN
SAMP(S, on)
ELSIF S.value = "STRIKE" THEN
STRIKE(S, on)
ELSIF S.value = "STRONG" THEN
STRONG(S, on)
ELSIF S.value = "STYLE" THEN
STYLE(S, on)
ELSIF S.value = "SMALL" THEN
SMALL(S, on)
ELSIF S.value = "SUB" THEN
SUB(S, on)
ELSIF S.value = "SUP" THEN
SUP(S, on)
ELSE
found := FALSE
END
|"T": IF S.value = "TITLE" THEN
TITLE(S, on)
ELSIF S.value = "TT" THEN
TT(S, on)
ELSIF S.value = "TAB" THEN
TAB(S, on)
ELSIF S.value = "TINY" THEN
TINY(S, on)
ELSE
found := FALSE
END
|"U": IF S.value[1] = 0X THEN
U(S, on)
ELSIF S.value = "UL" THEN
UL(S, on)
ELSE
found := FALSE
END
|"V": IF S.value = "VAR" THEN
VARN(S, on)
ELSE
found := FALSE
END
|"X": IF S.value = "XMP" THEN
XMP(S, on)
ELSE
found := FALSE
END
ELSE
found := FALSE
END;
IF ~found THEN
e := extTags;
WHILE (e # NIL) & (e.tag # S.value) DO
e := e.next
END;
IF e = NIL THEN
gen := "HTMLTags."; Strings.Append(gen, S.value);
Oberon.OpenScanner(Ss, gen);
IF Ss.class IN {Texts.Name, Texts.String} THEN
COPY(Ss.s, gen);
newTag := NIL; Oberon.Call(gen, Oberon.Par, FALSE, i);
IF i # 0 THEN
Texts.WriteString(Wr, gen);
Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf)
END;
IF newTag # NIL THEN
e := newTag; COPY(S.value, e.tag);
e.next := extTags; extTags := e;
e.start(S.page)
END
END
END;
IF e # NIL THEN
e.handle(S, on); found := TRUE
END
END;
WHILE ~S.end & (S.class # CloseTag) DO
Next(S)
END;
IF ~found THEN


ELSE


END
END
END;
S.state := TextHtml
END HandleTag;


PROCEDURE DocHandler(D: Objects.Object; VAR M: Objects.ObjMsg);
BEGIN
WITH D: Documents.Document DO
IF M IS Objects.AttrMsg THEN
WITH M: Objects.AttrMsg DO
IF (M.id = Objects.get) & (M.name = "Gen") THEN
M.class := Objects.String; M.s := "HTMLDocs.NewDoc"; M.res := 0
ELSE
TextDocs.DocHandler(D, M)
END
END
ELSIF M IS Objects.LinkMsg THEN
WITH M: Objects.LinkMsg DO
IF M.id = Objects.get THEN
IF M.name = "DeskMenu" THEN
M.obj := Gadgets.CopyPublicObject("NetDocs.HTMLDeskMenu", TRUE);
IF M.obj = NIL THEN M.obj := Desktops.NewMenu(Menu) END;
M.res := 0
ELSIF M.name = "SystemMenu" THEN
M.obj := Gadgets.CopyPublicObject("NetDocs.HTMLSystemMenu", TRUE);
IF M.obj = NIL THEN M.obj := Desktops.NewMenu(Menu) END;
M.res := 0
ELSIF M.name = "UserMenu" THEN
M.obj := Gadgets.CopyPublicObject("NetDocs.HTMLUserMenu", TRUE);
IF M.obj = NIL THEN M.obj := Desktops.NewMenu(Menu) END;
M.res := 0
ELSE
TextDocs.DocHandler(D, M)
END
ELSE
TextDocs.DocHandler(D, M)
END
END
ELSE
TextDocs.DocHandler(D, M)
END
END
END DocHandler;


PROCEDURE TextColor(col: INTEGER): SHORTINT;
VAR r, g, b: INTEGER;
BEGIN
Display.GetColor(col, r, g, b);
IF (0.3*r+0.59*g+0.11*b) > 0.35 THEN
RETURN SHORT(Display3.black)
ELSE
RETURN SHORT(Display3.white)
END
END TextColor;


PROCEDURE EndPage(P: Page);
VAR
me: Oberon.Task;
e: ExtTag;
p: Page;
BEGIN
me := P.task; CloseA(P);
e := extTags;
WHILE e # NIL DO
e.stop(P); e := e.next
END;
Texts.Append(P.T, P.W.buf);
WITH me: Task DO
IF me.S.S # NIL THEN
me.S.S.Close(me.S.S); me.S.S := NIL
END
END;
P.D.handle := DocHandler;
Attributes.SetString(P.D, "Type", "HTML");
Attributes.SetInt(P.D.dsc, "Color", P.textbackC);
IF P.textbackC # Display3.textbackC THEN
Texts.ChangeLooks(P.T, 0, P.headerLen, {1}, NIL, TextColor(P.textbackC), 0)
END;
IF (P.orgPos > 0) & (P.D.dsc(TextGadgets.Frame).org = 0) THEN
HyperDocs.ScrollTo(P.D.dsc(TextGadgets.Frame), P.orgPos);
IF HTTPDocs0.curNode # NIL THEN
HyperDocs.RememberOrg(P.orgPos, HTTPDocs0.curNode, HTTPDocs0.curNode)
END
END;
Texts.Append(P.source, P.Ws.buf);
IF P.cacheSource THEN
HyperDocs.CacheText(HTTPDocs0.StripLoc(P.docKey), P.source)
END;
Oberon.Remove(me); p := pages;
WHILE (p # NIL) & (p.next # P) DO
p := p.next
END;
IF p # NIL THEN
p.next := P.next
ELSE
pages := P.next
END;
P.task := NIL; Oberon.Collect()
END EndPage;


PROCEDURE ParseNext(me: Oberon.Task);
VAR
P: Page;
St: Streams.Stream;
i, minSize, timeOut: LONGINT;
app: BOOLEAN;
BEGIN
WITH me: Task DO
P := me.S.page;
IF me.S.state # End THEN
i := Input.Time(); timeOut := i + Input.TimeUnit; me.time := i;
St := me.S.S; app := FALSE;
me.S.avail := St.Available(St); minSize := (*64*)1; (* workaround for hang problem after NetSystem.State change *)
IF (me.S.avail < minSize) & (me.S.avail > 0) THEN
IF St.State(St) = Streams.closed THEN
minSize := me.S.avail
ELSIF ~St.buffer THEN
minSize := 1
END
END;
WHILE (me.S.avail >= minSize) & (me.time < timeOut) DO
app := TRUE;
CASE me.S.class OF
WhiteSpace: WriteSpace(P)
|OpenTag: HandleTag(me.S)
|CharRef: WriteCharRef(P, me.S); P.lines := 0; P.blank := TRUE
|Character: Texts.Write(P.W, me.S.char); P.lines := 0; P.blank := TRUE
ELSE
(* ??? *)
END;
Next(me.S);
IF me.S.avail <= minSize THEN
me.S.avail := St.Available(St); me.time := Input.Time()
END;
IF (me.S.avail < minSize) & (me.S.avail > 0) THEN
IF St.State(St) = Streams.closed THEN
minSize := me.S.avail
ELSIF ~St.buffer THEN
minSize := 1
END
END
END;
me.time := Input.Time(); i := 0;
WHILE St.eos & (me.S.state # End) & (i < 5) DO (* :-) *)
app := TRUE;
CASE me.S.class OF
WhiteSpace: WriteSpace(P)
|OpenTag: HandleTag(me.S)
|CharRef: WriteCharRef(P, me.S); P.lines := 0; P.blank := TRUE
|Character: Texts.Write(P.W, me.S.char); P.lines := 0; P.blank := TRUE
ELSE
(* ??? *)
END;
Next(me.S); INC(i)
END;
me.S.avail := 0;
IF app THEN
Texts.Append(P.T, P.W.buf); INC(me.time, Input.TimeUnit DIV 5)
ELSE
INC(me.time, Input.TimeUnit)
END
END;
IF me.S.state = End THEN
EndPage(P)
END
END
END ParseNext;


(* Query a string setting in the NetSystem section in Registry. *)

PROCEDURE QueryString(CONST key: ARRAY OF CHAR; VAR s: ARRAY OF CHAR): BOOLEAN;
VAR lKey: ARRAY 32 OF CHAR; S: Texts.Scanner;
BEGIN
lKey := "NetSystem."; Strings.Append(lKey, key);
Oberon.OpenScanner(S, lKey);
IF S.class IN {Texts.Name, Texts.String} THEN
COPY(S.s, s)
ELSE
COPY("", s)
END;
RETURN s # ""
END QueryString;


PROCEDURE QueryBool(CONST key: ARRAY OF CHAR): BOOLEAN;
VAR
str: ARRAY 16 OF CHAR;
b: BOOLEAN;
BEGIN
IF QueryString(key, str) THEN
Strings.StrToBool(str, b);
RETURN b
ELSE
RETURN FALSE
END
END QueryBool;


PROCEDURE Parse*(D: Documents.Document; basekey: LONGINT; S: Streams.Stream; head, cache, blocking: BOOLEAN);
VAR
me: Task;
s: HyperDocs.LinkScheme;
e: ExtTag;
i, j: LONGINT;
BEGIN
imgs := QueryBool("HTMLImages");
NEW(me);
IF (D.handle = NIL) OR (D.Store = NIL) THEN
TextDocs.InitDoc(D); D.W := HyperDocs.docW; D.H := HyperDocs.docH
END;
D.dsc(TextGadgets.Frame).do := HyperDocs.linkMethods;
NEW(me.P); me.P.D := D; NEW(me.P.T); Texts.Open(me.P.T, ""); me.P.alink := NIL; me.P.clink := NIL;
me.P.cacheSource := cache; NEW(me.P.source); Texts.Open(me.P.source, ""); Texts.OpenWriter(me.P.Ws);
me.P.next := pages; pages := me.P; me.P.task := me; me.P.docKey := basekey;
me.P.handle := Gadgets.objecthandle; me.P.blank := FALSE;
Links.SetLink(D.dsc, "Model", me.P.T); me.P.lines := 0; me.P.linkkey := HyperDocs.UndefKey;
me.P.textAttrs := NIL; Texts.OpenWriter(me.P.W); me.P.left := FALSE;
me.P.linkC := HyperDocs.linkC; me.P.oldLinkC := HyperDocs.oldLinkC;
me.P.textC := Display3.textC; me.P.textbackC := Display3.textbackC;
Attributes.SetInt(me.P.D.dsc, "LinkColor", me.P.linkC);
Attributes.SetInt(me.P.D.dsc, "OldLinkColor", me.P.oldLinkC);
me.S.page := me.P; me.P.lists := NIL;
NEW(me.P.base); me.P.base.key := basekey; me.P.base.dockey := me.P.base.key;
me.P.style := NewStyle();
WriteObj(me.P, me.P.style); PopTextAttrs(me.P); me.P.orgPos := 0; me.P.head := head;
OpenScanner(me.S, S);
IF head THEN
Texts.WriteString(me.P.W, "URL: "); Texts.Write(me.P.W, 22X)
END;
s := HyperDocs.LinkSchemeByKey(me.P.base.key);
IF s = NIL THEN
s := HyperDocs.LinkSchemeByPrefix("file")
END;
COPY(s.prefix, me.P.base.prefix);
HyperDocs.RetrieveLink(me.P.base.key, me.S.value);
IF me.P.base.prefix = "http" THEN
me.P.base.key := HTTPDocs0.SplitHTTPAdr(me.S.value, me.P.base.host, me.P.base.path, me.P.orgLabel, me.P.base.port);
i := 0;
WHILE me.S.value[i] # 0X DO
INC(i)
END;
WHILE (i > 0) & (me.S.value[i] # "/") DO
DEC(i)
END;
IF me.S.value[i] = "/" THEN
me.S.value[i+1] := 0X
END
ELSIF me.P.base.prefix = "file" THEN
HyperDocs.RetrieveLink(HyperDocs.loadingKey, me.S.value);
i := HyperDocs.SplitFileAdr(me.S.value, me.P.base.path, me.P.orgLabel);
HyperDocs.RetrieveLink(me.P.base.key, me.S.value);
me.P.base.key := HyperDocs.SplitFileAdr(me.S.value, me.P.base.path, searchAttr);
me.P.base.host := ""; me.P.base.port := 0;
i := 0; j := 0;
WHILE me.P.base.path[i] # 0X DO
IF me.P.base.path[i] = "/" THEN j := i+1 END;
INC(i)
END;
me.P.base.path[j] := 0X
ELSE (* proxy *)
me.P.orgLabel := "";
SplitHostPort(me.S.value, me.P.base.host, me.P.base.port);
IF me.P.base.prefix = "ftp" THEN
i := 6;
WHILE (me.S.value[i] # 0X) & (me.S.value[i] # "/") DO
INC(i)
END;
j := 0;
WHILE me.S.value[i] # 0X DO
me.P.base.path[j] := me.S.value[i]; INC(j); INC(i)
END;
IF j > 0 THEN
me.P.base.path[j] := 0X
ELSE
me.P.base.path := "/"
END
ELSE
me.P.base.path := ""
END
END;
HyperDocs.RetrieveLink(me.P.base.key, me.S.value);
IF head THEN
Texts.WriteString(me.P.W, me.S.value);
Texts.Write(me.P.W, 22X); WriteLn(me.P)
END;
e := extTags;
WHILE e # NIL DO
e.start(me.P); e := e.next
END;
me.P.D.handle := DocHandler;
Attributes.SetString(me.P.D, "Type", "HTML");
Attributes.SetInt(me.P.D.dsc, "Color", me.P.textbackC);
Texts.Append(me.P.T, me.P.W.buf); Read(me.S); Next(me.S);
me.P.headerLen := me.P.T.len;
me.time := Oberon.Time(); me.safe := FALSE; me.handle := ParseNext; Oberon.Install(me);
IF blocking THEN
WHILE me.P.task = me DO
me.handle(me)
END
END
END Parse;


PROCEDURE Show*;
VAR
T: Texts.Text;
D: Objects.Object;
BEGIN
T := Oberon.MarkedText();
IF T # NIL THEN
HTTPDocs0.curNode := NIL;
D := Gadgets.CreateObject("TextDocs.NewDoc");
WITH D: Documents.Document DO
D.W := HyperDocs.docW; D.H := HyperDocs.docH;
D.handle := DocHandler;
Parse(D, HyperDocs.UndefKey, TextStreams.OpenReader(T, 0), TRUE, FALSE, TRUE);
Desktops.ShowDoc(D)
END
END
END Show;


(** HTMLDocs.Stop *

Stop all background task for the marked html document. *)
PROCEDURE Stop*;
VAR
S: Attributes.Scanner;
doc: Documents.Document;
P, nextP: Page;
BEGIN
Attributes.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos);
Attributes.Scan(S);
IF (S.class = Attributes.Char) & (S.c = "*") THEN
doc := Desktops.CurDoc(Gadgets.context)
ELSE
doc := NIL; P := NIL
END;
IF doc # NIL THEN
HTTPDocs0.StopDoc(doc);
P := pages;
WHILE (P # NIL) & (P.D # doc) DO
P := P.next
END;
IF P # NIL THEN
HTTPDocs0.StopDoc(P.D); EndPage(P)
END
ELSE
P := pages;
WHILE P # NIL DO
nextP := P.next;
HTTPDocs0.StopDoc(P.D); EndPage(P);
P := nextP
END
END
END Stop;


PROCEDURE LoadDoc(D: Documents.Document);
VAR
key: LONGINT;
s, so: HyperDocs.LinkScheme;
T: Texts.Text;
path, label: ARRAY 64 OF CHAR;
BEGIN
key := HyperDocs.BuildKey(NIL, D.name);
IF key # HyperDocs.UndefKey THEN
s := HyperDocs.LinkSchemeByKey(key);
IF s.prefix = "file" THEN
IF HyperDocs.context # NIL THEN
HTTPDocs0.curNode := HyperDocs.context.new
ELSE
HyperDocs.Remember(key, NIL, HTTPDocs0.curNode)
END;
key := HyperDocs.SplitFileAdr(D.name, path, label);
NEW(T); Texts.Open(T, path);
TextDocs.InitDoc(D); D.W := HyperDocs.docW; D.H := HyperDocs.docH;
Parse(D, key, TextStreams.OpenReader(T, 0), TRUE, FALSE, FALSE);
IF (D # NIL) & (D.dsc # NIL) THEN
IF HyperDocs.context = NIL THEN
HyperDocs.LinkNodeToDoc(D, HTTPDocs0.curNode)
ELSE
HyperDocs.context.history := TRUE
END;
IF D.name = "" THEN
HyperDocs.RetrieveLink(key, D.name)
END
END
ELSE

HALT(99)

END;
IF (HyperDocs.context # NIL) & (HyperDocs.context.old # NIL) THEN
so := HyperDocs.LinkSchemeByKey(HyperDocs.context.old.key);
IF (so.prefix = "file") OR (so.prefix = "http") THEN
HyperDocs.context.replace := TRUE
END
END
END
END LoadDoc;


PROCEDURE NewDoc*;
VAR doc: Objects.Object;
BEGIN
doc := Gadgets.CreateObject("TextDocs.NewDoc");
WITH doc: Documents.Document DO
doc.W := HyperDocs.docW; doc.H := HyperDocs.docH;
doc.Load := LoadDoc
END;
doc.handle := DocHandler;
Objects.NewObj := doc
END NewDoc;


PROCEDURE InitEntities;
VAR i: Integer;
BEGIN
(* entities determines the map from indices, {i}, to entity names.
entityEncoding determines the map from {i} into the font.
Display of an entity requires availability in a font and correct encoding into the font. *)
i := 0;
(* Control codes
Desktops.OpenDoc "https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes" *)
entityEncoding[i] := Strings.Tab; entities[i] := "tab"; INC(i); (* U+09, Hoizontal tab *)


(* Basic Latin
Desktops.OpenDoc "https://en.wikipedia.org/wiki/List_of_Unicode_characters#Basic_Latin" *)
entityEncoding[i] := 22X; entities[i] := "quot"; INC(i);
entityEncoding[i] := 22X; entities[i] := "quote"; INC(i);
entityEncoding[i] := 26X; entities[i] := "amp"; INC(i);
entityEncoding[i] := 3CX; entities[i] := "lt"; INC(i);
entityEncoding[i] := 3EX; entities[i] := "gt"; INC(i);


(* Latin-1Supplement.
Desktops.OpenDoc "https://en.wikipedia.org/wiki/List_of_Unicode_characters#Latin-1_Supplement" *)
entityEncoding[i] := " "; entities[i] := "nbsp"; INC(i); (* U+00 *)
entityEncoding[i] := 0C0X; entities[i] := "Agrave"; INC(i);
entityEncoding[i] := 0C1X; entities[i] := "Aacute"; INC(i);
entityEncoding[i] := 0C2X; entities[i] := "Acirc"; INC(i);
entityEncoding[i] := 0C3X; entities[i] := "Atilde"; INC(i);
entityEncoding[i] := 0C4X; entities[i] := "Auml"; INC(i);
entityEncoding[i] := 0C5X; entities[i] := "Aring"; INC(i);
entityEncoding[i] := 0C6X; entities[i] := "AElig"; INC(i);
entityEncoding[i] := 0C7X; entities[i] := "Ccedil"; INC(i);
entityEncoding[i] := 0C8X; entities[i] := "Egrave"; INC(i);
entityEncoding[i] := 0C9X; entities[i] := "Eacute"; INC(i);
entityEncoding[i] := 0CAX; entities[i] := "Ecirc"; INC(i);
entityEncoding[i] := 0CCX; entities[i] := "Euml"; INC(i);
entityEncoding[i] := 0CDX; entities[i] := "Igrave"; INC(i);
entityEncoding[i] := 0CEX; entities[i] := "Iacute"; INC(i);
entityEncoding[i] := 0CFX; entities[i] := "Icirc"; INC(i);
entityEncoding[i] := 0CBX; entities[i] := "Iuml"; INC(i);
entityEncoding[i] := 0D0X; entities[i] := "ETH"; INC(i);
entityEncoding[i] := 0D1X; entities[i] := "Ntilde"; INC(i);
entityEncoding[i] := 0D2X; entities[i] := "Ograve"; INC(i);
entityEncoding[i] := 0D3X; entities[i] := "Oacute"; INC(i);
entityEncoding[i] := 0D4X; entities[i] := "Ocirc"; INC(i);
entityEncoding[i] := 0D5X; entities[i] := "Otilde"; INC(i);
entityEncoding[i] := 0D6X; entities[i] := "Ouml"; INC(i);
entityEncoding[i] := 0D7X; entities[i] := "Oslash"; INC(i);
entityEncoding[i] := 0D9X; entities[i] := "Ugrave"; INC(i);
entityEncoding[i] := 0DAX; entities[i] := "Uacute"; INC(i);
entityEncoding[i] := 0DBX; entities[i] := "Ucirc"; INC(i);
entityEncoding[i] := 0DCX; entities[i] := "Uuml"; INC(i);
entityEncoding[i] := 0DDX; entities[i] := "Yacute"; INC(i);
entityEncoding[i] := 0DEX; entities[i] := "THORN"; INC(i);
entityEncoding[i] := 0DFX; entities[i] := "szlig"; INC(i);
entityEncoding[i] := 0E0X; entities[i] := "agrave"; INC(i);
entityEncoding[i] := 0E1X; entities[i] := "aacute"; INC(i);
entityEncoding[i] := 0E2X; entities[i] := "acirc"; INC(i);
entityEncoding[i] := 0E3X; entities[i] := "atilde"; INC(i);
entityEncoding[i] := 0E4X; entities[i] := "auml"; INC(i);
entityEncoding[i] := 0E5X; entities[i] := "aring"; INC(i);
entityEncoding[i] := 0E6X; entities[i] := "aelig"; INC(i);
entityEncoding[i] := 0E7X; entities[i] := "ccedil"; INC(i);
entityEncoding[i] := 0E8X; entities[i] := "egrave"; INC(i);
entityEncoding[i] := 0E9X; entities[i] := "eacute"; INC(i);
entityEncoding[i] := 0EAX; entities[i] := "ecirc"; INC(i);
entityEncoding[i] := 0EBX; entities[i] := "euml"; INC(i);
entityEncoding[i] := 0ECX; entities[i] := "igrave"; INC(i);
entityEncoding[i] := 0EDX; entities[i] := "iacute"; INC(i);
entityEncoding[i] := 0EEX; entities[i] := "icirc"; INC(i);
entityEncoding[i] := 0EFX; entities[i] := "iuml"; INC(i);
entityEncoding[i] := 0F0X; entities[i] := "eth"; INC(i);
entityEncoding[i] := 0F1X; entities[i] := "ntilde"; INC(i);
entityEncoding[i] := 0F2X; entities[i] := "ograve"; INC(i);
entityEncoding[i] := 0F3X; entities[i] := "oacute"; INC(i);
entityEncoding[i] := 0F4X; entities[i] := "ocirc"; INC(i);
entityEncoding[i] := 0F5X; entities[i] := "otilde"; INC(i);
entityEncoding[i] := 0F6X; entities[i] := "ouml"; INC(i);
entityEncoding[i] := 0F7X; entities[i] := "oslash"; INC(i);
entityEncoding[i] := 0F9X; entities[i] := "ugrave"; INC(i);
entityEncoding[i] := 0FAX; entities[i] := "uacute"; INC(i);
entityEncoding[i] := 0FBX; entities[i] := "ucirc"; INC(i);
entityEncoding[i] := 0FCX; entities[i] := "uuml"; INC(i);
entityEncoding[i] := 0FDX; entities[i] := "yacute"; INC(i);
entityEncoding[i] := 0FEX; entities[i] := "thorn"; INC(i);
entityEncoding[i] := 0FFX; entities[i] := "yuml"; INC(i);
Texts.WriteInt(Wr, i, 0); Texts.WriteString(Wr, " character entities initialized."); Texts.WriteLn(Wr);
Texts.Append(Oberon.Log, Wr.buf)
END InitEntities;


(** HTMLDocs.ShowHTML

Display the HTML-source of the marked page. *)

PROCEDURE ShowHTML*;

VAR
D: Documents.Document;
node: HyperDocs.Node;
P: Page;
key, pos: LONGINT;
T: Texts.Text;
R: Texts.Reader;
ch: CHAR;

BEGIN

D := Documents.MarkedDoc();
node := HyperDocs.NodeByDoc(D);
IF node # NIL THEN
Attributes.GetInt(D, "DocURL", key);
P := pages;
WHILE (P # NIL) & (P.docKey # key) DO
P := P.next
END;
IF P # NIL THEN
Texts.Append(P.source, P.Ws.buf); T := P.source
ELSE
key := HTTPDocs0.StripLoc(key);
T := HyperDocs.GetCachedText(key)
END;
IF T # NIL THEN
Texts.OpenReader(R, T, 0);
Texts.Read(R, ch); pos := 0;
WHILE ~R.eot DO
IF ch = Strings.LF THEN
Texts.WriteLn(Wr);
Texts.Delete(T, pos, pos+1);
Texts.Insert(T, pos, Wr.buf);
Texts.OpenReader(R, T, pos)
ELSE
INC(pos)
END;
Texts.Read(R, ch)
END;
TextDocs.ShowText("HTML", T, HyperDocs.docW, HyperDocs.docH)
END
END

END ShowHTML;

(** HTMLDocs.SetImages

Switch image loading on or off. *)

PROCEDURE SetImages*;

VAR
S: Attributes.Scanner;

BEGIN

Attributes.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos);
Attributes.Scan(S);
IF (S.class = Attributes.Name) OR (S.class = Attributes.String) THEN
Strings.StrToBool(S.s, imgs)
END;

END SetImages;

PROCEDURE Check;
VAR S: Texts.Scanner;
BEGIN

Oberon.OpenScanner(S, "HTMLTags");
IF S.class = Texts.Inval THEN
Texts.WriteString(Wr, "Oberon.Text - HTMLTags not found");
Texts.WriteLn(Wr); Texts.Append(Oberon.Log, Wr.buf)
END

END Check;

BEGIN

Texts.OpenWriter(Wr); Texts.OpenWriter(Wq);
imgs := TRUE; extTags := NIL; newTag := NIL;
pages := NIL; dispW := Display.Width;
COPY(GreekCap, GreekTab);
Strings.Append(GreekTab, GreekMin);
InitEntities(); mono := Fonts.This("Courier10.Scn.Fnt");
IF ASCIIBullets THEN
bullets[0].f := Fonts.This("Default12.Scn.Fnt"); bullets[0].c := "*";
bullets[1].f := bullets[0].f; bullets[1].c := "o"
ELSE
bullets[0].f := Fonts.This("Default10.Scn.Fnt"); bullets[0].c := CHR(29);
bullets[1].f := bullets[0].f; bullets[1].c := CHR(28)
END;
Check()

END HTMLDocs.