Oberon/A2/Oberon.HTML.Mod

(* 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 : ftp://ftp.ethoberon.ethz.ch/ETHOberon/license.txt . *) MODULE HTML IN Oberon;	(** portable *) (* jm 26.8.94 *) IMPORT &#9;Files, Objects, Texts, Oberon, Out; CONST (* possible values of the variable typ denoting the paragraph type *) &#9;para &#61; 0;			(* Normal paragraph in xxx12.Scn.Fnt or xxx12i.Scn.Fnt *) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;(* Paragraphs are delimited by one or more empty lines *) &#9;title &#61; 1;			 (* Title when first character is red *) &#9;heading &#61; 2;	 (* Heading when in xxx12b.Scn.Fnt *) &#9;bullet &#61; 3;		 (* Bullet when "*" is first character on a line *) &#9;line &#61; 4;			 (* Horizontal ruler when "-" is first character on a line *) &#9;pre &#61; 5;			 (* pre-formatted when in xxx10.Scn.Fnt *) &#9;tab &#61; 09X; &#9;DocHeader &#61; TRUE;	(* include document header comment *) &#9;BodyColor &#61; FALSE;	(* set body color - HTML 4-specific *) &#9;(* One DTD should be uncommented. If necessary, add one.  Recompile after any change. &#9;&#9;Ref. https://www.w3.org/QA/2002/04/valid-dtd-list.html *) &#9;(* DTD &#61; "none"; This will suppress the DTD. *) &#9;(* DTD &#61; "HTML401Strict"; *) &#9;(* DTD &#61; "HTML401Trans"; *) &#9;(* DTD &#61; "HTML401Frameset; *) &#9;DTD &#61; "html"; (* HTML5" *) &#9;(* CharEncoding &#61; "iso-8859-1"; Obsolete. *) &#9;CharEncoding &#61; "UTF-8"; (* Ref. https://en.wikipedia.org/wiki/Character_encoding . *) &#9;lang &#61; "en"; (* Ref. https://www.w3.org/International/questions/qa-html-language-declarations#basics . *) VAR &#9;out : Files.Rider; &#9;italic : BOOLEAN; &#9;sep : CHAR; PROCEDURE S(s : ARRAY OF CHAR); VAR i : INTEGER; BEGIN &#9;i : &#61; 0; &#9;WHILE s&#91;i&#93; # 0X DO Files.Write(out, s&#91;i&#93;); INC(i) END END S; PROCEDURE C(ch : CHAR); BEGIN Files.Write(out, ch) END C; PROCEDURE L; BEGIN Files.Write(out, sep); END L; (* Check if font matches type. type &#61; digit &#123; digit &#125; ("." &#124; "b" &#124; "i"). *) PROCEDURE MatchFont(font : ARRAY OF CHAR; type : ARRAY OF CHAR) : BOOLEAN; VAR i, j : LONGINT; BEGIN &#9;i : &#61; 0; &#9;WHILE (font&#91;i&#93; # 0X) &#38; ((font&#91;i&#93; &#60; "0") OR (font&#91;i&#93; &#62; "9")) DO	(* skip name *) &#9;&#9;INC(i) &#9;END; &#9;j : &#61; 0; &#9;WHILE (font&#91;i&#93; # 0X) &#38; (font&#91;i&#93; &#62;&#61; "0") &#38; (font&#91;i&#93; &#60;&#61; "9") &#38; (font&#91;i&#93; &#61; type&#91;j&#93;) DO &#9;&#9;INC(i); INC(j) &#9;END; &#9;RETURN (font&#91;i&#93; &#61; type&#91;j&#93;) END MatchFont; (* Delimit a paragraph : begins at lastnl and ends at end *) PROCEDURE GetPara(T : Texts.Text; VAR R : Texts.Reader; VAR beg, end : LONGINT; VAR typ : SHORTINT); VAR ch, firstch : CHAR; firstfnt : Objects.Library; firstcol : INTEGER; lastnl : LONGINT; BEGIN &#9;beg : &#61; Texts.Pos(R); end : &#61; beg; lastnl : &#61; beg; &#9;(* skip empty lines *) &#9;Texts.Read(R, ch); &#9;WHILE &#126;R.eot &#38; (ch &#60;&#61; " ") DO &#9;&#9;INC(beg); &#9;&#9;IF ch &#61; 0DX THEN lastnl : &#61; beg END; &#9;&#9;Texts.Read(R, ch) &#9;END; &#9;IF &#126;R.eot THEN &#9;&#9;firstch : &#61; ch; firstfnt : &#61; R.lib; firstcol : &#61; R.col; &#9;&#9;LOOP &#9;&#9;&#9;WHILE &#126;R.eot &#38; (ch # 0DX) DO Texts.Read(R, ch) END; (* read till first nl *) &#9;&#9;&#9;IF R.eot THEN EXIT END; &#9;&#9;&#9;IF ch &#61; 0DX THEN &#9;&#9;&#9;&#9;end : &#61; Texts.Pos(R)-1; &#9;&#9;&#9;&#9;Texts.Read(R, ch); &#9;&#9;&#9;&#9;WHILE &#126;R.eot &#38; (ch &#61; " ") OR (ch &#61; tab) DO Texts.Read(R, ch) END; &#9;&#9;&#9;&#9;IF ch &#61; "*" THEN Texts.OpenReader(R, T, Texts.Pos(R)-1); EXIT END; &#9;&#9;&#9;&#9;IF ch &#61; 0DX THEN EXIT END &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;IF firstcol &#61; 1 THEN (* red *) typ : &#61; title &#9;&#9;ELSIF MatchFont(firstfnt.name, "12b") THEN typ : &#61; heading &#9;&#9;ELSIF MatchFont(firstfnt.name, "10.") THEN typ : &#61; pre; beg : &#61; lastnl; &#9;&#9;ELSIF firstch &#61; "*" THEN typ : &#61; bullet &#9;&#9;ELSIF firstch &#61; "-" THEN typ : &#61; line &#9;&#9;ELSE typ : &#61; para &#9;&#9;END &#9;END END GetPara; PROCEDURE WriteStretch(T : Texts.Text; beg, end : LONGINT); VAR R : Texts.Reader; ch : CHAR; lastlib : Objects.Library; BEGIN &#9;IF end &#62; beg THEN &#9;&#9;Texts.OpenReader(R, T, beg); &#9;&#9;Texts.Read(R, ch); lastlib : &#61; R.lib; &#9;&#9;WHILE beg &#60; end DO &#9;&#9;&#9;IF R.lib # lastlib THEN &#9;&#9;&#9;&#9;IF MatchFont(R.lib.name, "12i") THEN &#9;&#9;&#9;&#9;&#9;IF &#126;italic THEN S("&#60;i&#62;"); italic : &#61; TRUE END &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;IF italic THEN S("&#60;/i&#62;"); italic : &#61; FALSE END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;lastlib : &#61; R.lib &#9;&#9;&#9;END; &#9;&#9;&#9;IF ch &#61; 085X THEN S("&#38;uuml;") &#9;&#9;&#9;ELSIF ch &#61; 082X THEN S("&#38;Uuml;") &#9;&#9;&#9;ELSIF ch &#61; 08FX THEN S("&#38;ugrave;") &#9;&#9;&#9;ELSIF ch &#61; 083X THEN S("&#38;auml;") &#9;&#9;&#9;ELSIF ch &#61; 080X THEN S("&#38;Auml;") &#9;&#9;&#9;ELSIF ch &#61; 094X THEN S("&#38;aacute;") &#9;&#9;&#9;ELSIF ch &#61; 08BX THEN S("&#38;agrave;") &#9;&#9;&#9;ELSIF ch &#61; 091X THEN S("&#38;euml;") &#9;&#9;&#9;ELSIF ch &#61; 090X THEN S("&#38;eacute;") &#9;&#9;&#9;ELSIF ch &#61; 08CX THEN S("&#38;egrave;") &#9;&#9;&#9;ELSIF ch &#61; 084X THEN S("&#38;ouml;") &#9;&#9;&#9;ELSIF ch &#61; 081X THEN S("&#38;Ouml;") &#9;&#9;&#9;ELSIF ch &#61; 08EX THEN S("&#38;ograve;") &#9;&#9;&#9;ELSIF ch &#61; 092X THEN S("&#38;iuml;") &#9;&#9;&#9;ELSIF ch &#61; 08DX THEN S("&#38;igrave;") &#9;&#9;&#9;ELS IF ch &#61; 0DX THEN C(" "); C(sep) &#9;&#9;&#9;ELSIF ch &#61; tab THEN S("&#38;nbsp; &#38;nbsp; &#38;nbsp;") &#9;&#9;&#9;ELSIF (ch &#62;&#61; " ") OR (ch &#61; "-") THEN &#9;&#9;&#9;&#9;C(ch) &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.Read(R, ch); &#9;&#9;&#9;INC(beg) &#9;&#9;END &#9;END END WriteStretch; PROCEDURE WritePara(T : Texts.Text; beg, end : LONGINT); VAR R : Texts.Reader; ch : CHAR; col : INTEGER; &#9;pos, lstart : LONGINT; anchor : ARRAY 512 OF CHAR; apos : INTEGER; BEGIN col : &#61; -1; pos : &#61; beg; anchor : &#61; ""; &#9;Texts.OpenReader(R, T, beg); &#9;Texts.Read(R, ch); &#9;WHILE pos &#60; end DO &#9;&#9;IF (R.col &#61; 3) &#38; (col # 3) THEN (* start link *) &#9;&#9;&#9;WriteStretch(T, beg, pos); beg : &#61; pos &#9;&#9;END; &#9;&#9;col : &#61; R.col; &#9;&#9;IF (col &#61; 3) &#38; (ch &#61; "&#123;") THEN (* reading an anchor *) &#9;&#9;&#9;lstart : &#61; pos; &#9;&#9;&#9;INC(pos); Texts.Read(R, ch); &#9;&#9;&#9;apos : &#61; 0; &#9;&#9;&#9;WHILE &#126;R.eot &#38; (apos &#60; LEN(anchor)) &#38; (ch # "&#125;") DO &#9;&#9;&#9;&#9;anchor&#91;apos&#93; : &#61; ch; INC(apos); &#9;&#9;&#9;&#9;INC(pos); &#9;&#9;&#9;&#9;Texts.Read(R, ch) &#9;&#9;&#9;END; &#9;&#9;&#9;anchor&#91;apos&#93; : &#61; 0X; &#9;&#9;&#9;S("&#60;a href&#61;"); C(22X); S(anchor); C(22X); C("&#62;"); &#9;&#9;&#9;WriteStretch(T, beg, lstart); beg : &#61; pos+1; &#9;&#9;&#9;S("&#60;/a&#62;") &#9;&#9;ELSE INC(pos); Texts.Read(R, ch) &#9;&#9;END &#9;END; &#9;WriteStretch(T, beg, end) END WritePara; PROCEDURE GetPrefix(T : Texts.Text; VAR beg, end : LONGINT; VAR s : ARRAY OF CHAR); VAR R : Texts.Reader; old : LONGINT; ch : CHAR; i : INTEGER; BEGIN &#9;old : &#61; beg; i : &#61; 0; &#9;Texts.OpenReader(R, T, beg); &#9;Texts.Read(R, ch); &#9;WHILE &#126;R.eot &#38; (ch # " : ") &#38; (beg &#60; end) DO &#9;&#9;IF (ch &#62; " ") &#38; (i &#60; LEN(s) - 1) THEN s&#91;i&#93; : &#61; ch; INC(i) END; &#9;&#9;INC(beg); &#9;&#9;Texts.Read(R, ch) &#9;END; &#9;IF ch &#61; " : " THEN s&#91;i&#93; : &#61; 0X; INC(beg) &#9;ELSE s&#91;0&#93; : &#61; 0X; beg : &#61; old &#9;END END GetPrefix; PROCEDURE ConvertText(T : Texts.Text; start : LONGINT; VAR filename : ARRAY OF CHAR); VAR R : Texts.Reader; beg, end, nbeg, nend : LONGINT; typ, ntyp : SHORTINT; body : BOOLEAN; &#9;PROCEDURE StartBody; &#9;BEGIN &#9;&#9;S("&#60;/head&#62;"); L; &#9;&#9;IF BodyColor THEN &#9;&#9;&#9;S("&#60;body BGCOLOR&#61;"); C(22X); S("#FFFFFF"); C(22X); S("&#62;"); L &#9;&#9;ELSE &#9;&#9;&#9;S("&#60;body&#62;"); L &#9;&#9;END; &#9;&#9;body : &#61; TRUE &#9;END StartBody; BEGIN &#9;italic : &#61; FALSE; body : &#61; FALSE; &#9;Texts.OpenReader(R, T, start); &#9;GetPara(T, R, beg, end, typ); &#9; (* IF DocHeader THEN &#9;&#9;S("&#60;!DOCTYPE HTML PUBLIC "); C(22X); S("-//W3C//DTD HTML 4.01 Transitional//EN"); C(22X); S("&#62;"); L &#9;END; *) &#9; IF DTD &#61; "HTML401Strict" THEN &#9;&#9;S("&#60;!DOCTYPE HTML PUBLIC "); C(22X);S("-//W3C//DTD HTML 4.01//EN"); C(22X); &#9;&#9;S(" "); C(22X); S("http : //www.w3.org/TR/html4/strict.dtd"); S("&#62;"); L &#9;ELSIF DTD &#61; "HTML401Trans" THEN &#9;&#9;S("&#60;!DOCTYPE HTML PUBLIC "); C(22X);S("-//W3C//DTD HTML 4.01 Transitional//EN"); C(22X); &#9;&#9;S(" "); C(22X); S("http : //www.w3.org/TR/html4/loose.dtd"); S("&#62;"); L &#9;ELSIF DTD &#61; "HTML401Frameset" THEN &#9;&#9;S("&#60;!DOCTYPE HTML PUBLIC "); C(22X);S("-//W3C//DTD HTML 4.01 Frameset//EN"); C(22X); &#9;&#9;S(" "); C(22X); S("http : //www.w3.org/TR/html4/frameset.dtd"); S("&#62;"); L &#9;ELSIF DTD &#61; "html" THEN &#9;&#9;S("&#60;!DOCTYPE html&#62;"); L &#9;ELSE (* no DTD *) &#9;END; &#9; S("&#60;html lang&#61;"); C(22X); S(lang); C(22X); S("&#62;"); L; &#9;S("&#60;head&#62;"); L; &#9;S("&#60;title&#62;"); &#9;IF typ &#61; title THEN &#9;&#9;GetPrefix(T, beg, end, filename); (* Skip that file name, discarding it *) &#9;&#9;WritePara(T, beg, end); &#9;&#9;beg : &#61; end	(* title paragraph already written *) &#9;ELSE &#9;&#9;S("Untitled") &#9;END; &#9;S("&#60;/title&#62;"); L; &#9;S(&#39;&#60;meta http-equiv&#61;"Content-Type" content&#61;"text/html; charset&#61;&#39;);S(CharEncoding);S(&#39;"&#62;&#39;); L; &#9;WHILE &#126;R.eot DO &#9;&#9;IF &#126;body &#38; (typ # title) THEN StartBody END; (* first non-title paragraph starts body *) &#9;&#9;GetPara(T, R, nbeg, nend, ntyp); &#9;&#9;IF body &#38; (ntyp &#61; title) THEN ntyp : &#61; para END; (* treat a title paragraph in body like normal *) &#9;&#9;IF typ &#61; bullet THEN S("&#60;li&#62;"); INC(beg) &#9;&#9;ELSIF typ &#61; heading THEN S("&#60;h2&#62;") &#9;&#9;ELSE (* skip *) &#9;&#9;END; &#9;&#9;IF typ &#61; line THEN S("&#60;hr&#62;") (* Horizontal Ruler *) &#9;&#9; (* ELSIF typ &#61; title THEN *) (* skip *) &#9;&#9;ELSE &#9;&#9;&#9;WritePara(T, beg, end); (* write previous *) &#9;&#9;&#9;IF typ &#61; pre THEN C(0DX) END &#9;&#9;END; &#9;&#9;IF typ &#61; heading THEN S("&#60;/h2&#62;") END; &#9;&#9; IF beg # end THEN L END ; &#9;&#9;(* List *) &#9;&#9;IF (ntyp &#61; bullet) &#38; (typ # bullet) THEN (* open list *) &#9;&#9;&#9;S("&#60;ul&#62;"); L &#9;&#9;ELSIF (ntyp # bullet) &#38; (typ &#61; bullet) THEN (* close list *) &#9;&#9;&#9; S("&#60;/ul&#62;"); L &#9;&#9;END; &#9;&#9;(* Pre-formatted text *) &#9;&#9;IF (ntyp &#61; pre) &#38; (typ # pre) THEN (* start pre-formatted text *) &#9;&#9;&#9;IF &#126;body THEN StartBody END; &#9;&#9;&#9;S("&#60;pre&#62;") &#9;&#9;ELSIF (ntyp # pre) &#38; (typ &#61; pre) THEN &#9;&#9;&#9;S("&#60;/pre&#62;"); L &#9;&#9;END; &#9;&#9;(* Separate 2 consecutive "normal" paragraphs with a paragraph break, except two preformatteds *) &#9;&#9;IF (ntyp &#61; para) &#38; (typ &#61; para) THEN S("&#60;p&#62;"); L END; &#9;&#9;end : &#61; nend; beg : &#61; nbeg; typ : &#61; ntyp &#9;END; &#9; IF &#126;body &#38; (typ # title) THEN StartBody END; &#9;WritePara(T, beg, end); (* write previous *) &#9;IF (typ &#61; bullet) THEN (* close list *) &#9;&#9; S("&#60;/ul&#62;"); L &#9;END; &#9;IF (typ &#61; pre) THEN &#9;&#9;S("&#60;/pre&#62;"); L &#9;END; &#9;S("&#60;/body&#62;"); L; &#9;S("&#60;/html&#62;"); L END ConvertText; (** Show a preview of the HTML text in a text viewer - Processes ONLY one text! *) PROCEDURE Show*; VAR S : Texts.Scanner; T, t : Texts.Text; time, beg, end : LONGINT; &#9;filename : ARRAY 64 OF CHAR; f : Files.File; BEGIN &#9;sep : &#61; 0DX; &#9;beg : &#61; 0;		(* Process from the beginning of the text. Modified if "@" used *) &#9;Texts.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos); &#9;Texts.Scan(S); T : &#61; NIL; &#9;IF (S.class &#61; Texts.Char) &#38; (S.c &#61; "*") THEN &#9;&#9;T : &#61; Oberon.MarkedText &#9;ELSIF (S.class &#61; Texts.Char) &#38; (S.c &#61; "^") THEN &#9;&#9;Oberon.GetSelection(T, beg, end, time); &#9;&#9;IF time &#62;&#61; 0 THEN &#9;&#9;&#9;Texts.OpenScanner(S, T, beg); &#9;&#9;&#9;Texts.Scan(S); &#9;&#9;&#9;IF (S.class &#61; Texts.Name) THEN &#9;&#9;&#9;&#9;NEW(T); Texts.Open(T, S.s); &#9;&#9;&#9;&#9;IF T.len &#61; 0 THEN T : &#61; NIL END &#9;&#9;&#9;END &#9;&#9;ELSE T : &#61; NIL &#9;&#9;END &#9;ELSIF (S.class &#61; Texts.Char) &#38; (S.c &#61; "@") THEN &#9;&#9;Oberon.GetSelection(T, beg, end, time); &#9;&#9;IF time &#60; 0 THEN T : &#61; NIL END &#9;END; &#9;IF T # NIL THEN &#9;&#9;f : &#61; Files.New("Temp.HTML.tmp"); &#9;&#9;Files.Set(out, f, 0); &#9;&#9;ConvertText(T, beg, filename); &#9;&#9;Files.Register(f); &#9;&#9;NEW(t); Texts.Open(t, "Temp.HTML.tmp"); &#9;&#9;Oberon.OpenText(filename, t, 400, 200) &#9;END END Show; PROCEDURE Compile*; &#9;VAR S : Texts.Scanner; T : Texts.Text; filename : ARRAY 64 OF CHAR; f : Files.File; beg, end, time : LONGINT; &#9;PROCEDURE CompileT; &#9;&#9;VAR R : Texts.Reader; beg, end : LONGINT; typ : SHORTINT; &#9;&#9;&#9;&#9;res, i : INTEGER; bak : ARRAY 64 OF CHAR; &#9;BEGIN &#9;&#9;IF T.len &#62; 0 THEN &#9;&#9;(* Get the file name from the source text, at the beginning i.e. pos 0 *) &#9;&#9;&#9;Texts.OpenReader(R, T, 0); &#9;&#9;&#9;GetPara(T, R, beg, end, typ); &#9;&#9;&#9;IF typ &#61; title THEN &#9;&#9;&#9;&#9;GetPrefix(T, beg, end, filename) &#9;&#9;&#9;END; &#9;&#9;(* *) &#9;&#9;&#9;IF filename # "" THEN &#9;&#9;&#9;&#9;Out.String(filename); &#9;&#9;&#9;(* Rename the file &#39;fileName.Bak&#39; *) &#9;&#9;&#9;&#9;i : &#61; 0; &#9;&#9;&#9;&#9;WHILE filename&#91;i&#93; # 0X DO bak&#91;i&#93; : &#61; filename&#91;i&#93;; INC(i) END; &#9;&#9;&#9;&#9;bak&#91;i&#93; : &#61; "."; bak&#91;i+1&#93; : &#61; "B"; bak&#91;i+2&#93; : &#61; "a"; bak&#91;i+3&#93; : &#61; "k"; bak&#91;i+4&#93; : &#61; 0X; &#9;&#9;&#9;&#9;Files.Rename(filename, bak, res); &#9;&#9;&#9;(* *) &#9;&#9;&#9;&#9;f : &#61; Files.New(filename); &#9;&#9;&#9;&#9;Files.Set(out, f, 0); &#9;&#9;&#9;&#9;ConvertText(T, 0, filename); &#9;&#9;&#9;&#9;Files.Register(f); &#9;&#9;&#9;&#9;Out.Int(Files.Length(f), 10); &#9;&#9;&#9;ELSE Out.String("no destination file name in text") &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;Out.Ln &#9;END CompileT; BEGIN &#9;sep : &#61; 0AX; &#9;Out.String("HTML.Compile"); Out.Ln; &#9;Texts.OpenScanner(S, Oberon.Par.text, Oberon.Par.pos); &#9;Texts.Scan(S); &#9;IF (S.class &#61; Texts.Char) &#38; (S.c &#61; "*") THEN &#9;&#9;T : &#61; Oberon.MarkedText; &#9;&#9;IF T # NIL THEN &#9;&#9;&#9;CompileT &#9;&#9;END &#9;ELSE &#9;&#9;end : &#61; MAX(LONGINT) - 100; &#9;&#9;IF (S.class &#61; Texts.Char) &#38; (S.c &#61; "^") THEN &#9;&#9;&#9;Oberon.GetSelection(T, beg, end, time); &#9;&#9;&#9;IF time &#62;&#61; 0 THEN Texts.OpenScanner(S, T, beg); Texts.Scan(S) END &#9;&#9;END; &#9;&#9;WHILE (S.class &#61; Texts.Name) &#38; (Texts.Pos(S) &#60; end + S.len + 1) DO &#9;&#9;&#9;Out.String(S.s); Out.String(" &#61;&#62; "); &#9;&#9;&#9;NEW(T); Texts.Open(T, S.s); &#9;&#9;&#9;CompileT; &#9;&#9;&#9;Texts.Scan(S) &#9;&#9;END &#9;END END Compile; END HTML. System.Free HTML &#126; HTML.Compile ^	HTML.Compile * HTML.Show ^		HTML.Show *		HTML.Show @