MODULE StdCoder;
(**
	project	= "BlackBox 2.0"
	organization	= "www.oberon.ch, blackbox.oberon.org"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "The 2-Clause BSD License"
	changes	= "
	- 20160321, center #110, use mapped strings for labels in all forms
	- 20161010, center #133, bug fix in Encoding/Decoding Unicode filenames and paths
	- 20161216, center #144, inconsistent docu and usage of Files.Locator error codes
	- 20170225, center #148, StdCoder to support file names with spaces
	- 20190211, center #196, StdCoder to support both "\" & "/" in file paths
	"
	issues	= "
	- ...
	"

**)

	IMPORT
		Kernel, Files, Converters, Stores, Views, Controllers, Dialog, Documents, Windows,
		TextModels, TextViews, TextControllers, TextMappers, Utf, 
		StdCmds;

	CONST
		N = 16384;
		LineLength = 74;
		OldVersion = 0; ThisVersion = 1;
		Tag = "StdCoder.Decode";	(* first letter of Tag must not to appear within Tag again *)
		Separator = "/";
		Separator2 = "\";
		View = 1; File = 2; List = 3;

	TYPE
		FileList = POINTER TO RECORD
			next: FileList;
			file: Files.File;
			type: Files.Type;
			name: Dialog.String
		END;

		ParList* = RECORD
			list*: Dialog.Selection;
			storeAs*: Dialog.String;
			files: FileList
		END;

	VAR
		par*: ParList;
		askOption*: BOOLEAN;
		code: ARRAY 64 OF CHAR;
		revCode: ARRAY 256 OF BYTE;
		table: ARRAY N OF BYTE;
		stdDocuType: Files.Type;

	PROCEDURE NofSelections(IN list: Dialog.Selection): INTEGER;
		VAR i, n: INTEGER;
	BEGIN
		i := 0; n := 0;
		WHILE i # list.len DO
			IF list.In(i) THEN INC(n) END;
			INC(i)
		END;
		RETURN n
	END NofSelections;

	PROCEDURE ShowError(n: INTEGER; par: ARRAY OF CHAR);
	BEGIN
		Dialog.Beep;
		CASE n OF
		 1: Dialog.ShowParamMsg("#Std:bad characters", par, "", "")
		| 2: Dialog.ShowParamMsg("#Std:checksum error", par, "", "")
		| 3: Dialog.ShowParamMsg("#Std:incompatible version", par, "", "")
		| 4: Dialog.ShowParamMsg("#Std:filing error", par, "", "")
		| 5: Dialog.ShowParamMsg("#Std:directory ^0 not found", par, "", "")
		| 6: Dialog.ShowParamMsg("#Std:file ^0 not found", par, "", "")
		| 7: Dialog.ShowParamMsg("#Std:illegal path", par, "", "")
		| 8: Dialog.ShowParamMsg("#Std:no tag", par, "", "")
		| 9: Dialog.ShowParamMsg("#Std:disk write protected", par, "", "")
		| 10: Dialog.ShowParamMsg("#Std:io error", par, "", "")
		END
	END ShowError;

	PROCEDURE ShowSizeMsg(x: INTEGER);
		VAR i, j: INTEGER; ch: CHAR; s: ARRAY 20 OF CHAR;
	BEGIN
		ASSERT(x >= 0, 20);
		i := 0;
		REPEAT s[i] := CHR(ORD("0") + x MOD 10); INC(i); x := x DIV 10 UNTIL x = 0;
		s[i] := 0X;
		DEC(i); j := 0;
		WHILE j < i DO ch := s[j]; s[j] := s[i]; s[i] := ch; INC(j); DEC(i) END;
		Dialog.ShowParamStatus("#Std:^0 characters coded", s, "", "")
	END ShowSizeMsg;

	PROCEDURE Write(dest: TextModels.Writer; x: INTEGER; VAR n: INTEGER);
	BEGIN
		dest.WriteChar(code[x]); INC(n);
		IF n = LineLength THEN dest.WriteChar(0DX); dest.WriteChar(" "); n := 0 END
	END Write;

	PROCEDURE WriteHeader(dest: TextModels.Writer; VAR n: INTEGER;
		name: ARRAY OF CHAR; type: BYTE
	);
		VAR byte, bit, i, res: INTEGER; ch: CHAR; tag: ARRAY 16 OF CHAR; utf8: Kernel.Utf8Name;
	BEGIN
		tag := Tag; i := 0; ch := tag[0];
		WHILE ch # 0X DO dest.WriteChar(ch); INC(n); INC(i); ch := tag[i] END;
		dest.WriteChar(" "); INC(n);
		bit := 0; byte := 0; i := 0;
		Utf.StringToUtf8(name, utf8, res); ASSERT(res = 0);
		REPEAT
			ch := utf8[i]; INC(byte, ASH(ORD(ch), bit)); INC(bit, 8);
			WHILE bit >= 6 DO Write(dest, byte MOD 64, n); byte := byte DIV 64; DEC(bit, 6) END;
			INC(i)
		UNTIL ch = 0X;
		IF bit # 0 THEN Write(dest, byte, n) END;
		Write(dest, ThisVersion, n); Write(dest, type, n)
	END WriteHeader;

	PROCEDURE WriteFileType(dest: TextModels.Writer; VAR n: INTEGER; t: Files.Type);
		VAR byte, bit, i: INTEGER; ch: CHAR;
	BEGIN
		IF t = Files.docType THEN t := stdDocuType END;
		bit := 0; byte := 0; i := 0; dest.WriteChar(" ");
		REPEAT
			ch := t[i]; INC(byte, ASH(ORD(ch), bit)); INC(bit, 8);
			WHILE bit >= 6 DO Write(dest, byte MOD 64, n); byte := byte DIV 64; DEC(bit, 6) END;
			INC(i)
		UNTIL ch = 0X;
		IF bit # 0 THEN Write(dest, byte, n) END
	END WriteFileType;

	PROCEDURE WriteFile(dest: TextModels.Writer; VAR n: INTEGER; f: Files.File);
		VAR hash, byte, bit, i, j, sum, len: INTEGER; src: Files.Reader; b: BYTE;
	BEGIN
		len := f.Length(); j := len; i := 6;
		WHILE i # 0 DO Write(dest, j MOD 64, n); j := j DIV 64; DEC(i) END;
		i := 0;
		REPEAT table[i] := 0; INC(i) UNTIL i = N;
		hash := 0; bit := 0; byte := 0; sum := 0; src := f.NewReader(NIL);
		WHILE len # 0 DO
			src.ReadByte(b); DEC(len);
			sum := (sum + b MOD 256) MOD (16 * 1024);
			IF table[hash] = b THEN INC(bit)	(* 0 bit for correct prediction *)
			ELSE	(* Incorrect prediction -> 1'xxxx'xxxx bits *)
				table[hash] := b; INC(byte, ASH(1, bit)); INC(bit);
				INC(byte, ASH(b MOD 256, bit)); INC(bit, 8)
			END;
			WHILE bit >= 6 DO Write(dest, byte MOD 64, n); byte := byte DIV 64; DEC(bit, 6) END;
			hash := (16 * hash + b MOD 256) MOD N
		END;
		IF bit # 0 THEN Write(dest, byte, n) END;
		i := 6;
		WHILE i # 0 DO Write(dest, sum MOD 64, n); sum := sum DIV 64; DEC(i) END;
		IF n # 0 THEN dest.WriteChar(0DX); n := 0 END
	END WriteFile;

	PROCEDURE Read(src: TextModels.Reader; VAR x: INTEGER; VAR res: INTEGER);
		VAR ch: CHAR;
	BEGIN
		IF res = 0 THEN
			REPEAT src.ReadChar(ch); x := revCode[ORD(ch)] UNTIL (x >= 0) OR src.eot;
			IF src.eot THEN res := 1 END
		END;
		IF res # 0 THEN x := 0 END
	END Read;

	PROCEDURE Utf8ToString(IN in: ARRAY OF SHORTCHAR; OUT out: ARRAY OF CHAR);
		VAR res: INTEGER;
	BEGIN
		Utf.Utf8ToString(in, out, res);
		ASSERT(res IN {0, 2}) (* for backward compatibility: res = 2 allows non-encoded strings as well *)
	END Utf8ToString;
	
	PROCEDURE ReadHeader(src: TextModels.Reader; VAR res: INTEGER;
		VAR name: ARRAY OF CHAR; VAR type: BYTE
	);
		VAR x, bit, i, j: INTEGER; ch: CHAR; tag: ARRAY 16 OF CHAR; utf8: Kernel.Utf8Name;
	BEGIN
		tag := Tag; i := 0; name := "";
		WHILE ~src.eot & (tag[i] # 0X) DO
			src.ReadChar(ch);
			IF ch = tag[i] THEN INC(i) ELSIF ch = tag[0] THEN i := 1 ELSE i := 0 END
		END;
		IF ~src.eot THEN
			res := 0; i := 0; bit := 0; x := 0;
			REPEAT
				WHILE (res = 0) & (bit < 8) DO Read(src, j, res); INC(x, ASH(j, bit)); INC(bit, 6) END;
				IF res = 0 THEN
					ch := CHR(x MOD 256); x := x DIV 256; DEC(bit, 8); utf8[i] := SHORT(ch); INC(i)
				END
			UNTIL (res # 0) OR (ch = 0X);
			Read(src, j, res);
			IF res = 0 THEN
				Utf8ToString(utf8, name);
				IF (j = ThisVersion) OR (j = OldVersion) THEN
					Read(src, j, res); type := SHORT(SHORT(j))
				ELSE res := 3
				END
			END
		ELSE res := 8
		END
	END ReadHeader;

	PROCEDURE ReadFileType(src: TextModels.Reader; VAR res: INTEGER; VAR ftype: Files.Type);
		VAR x, bit, i, j: INTEGER; ch: CHAR;
	BEGIN
		res := 0; i := 0; bit := 0; x := 0;
		REPEAT
			WHILE (res = 0) & (bit < 8) DO Read(src, j, res); INC(x, ASH(j, bit)); INC(bit, 6) END;
			IF res = 0 THEN ch := CHR(x MOD 256); x := x DIV 256; DEC(bit, 8); ftype[i] := ch; INC(i) END
		UNTIL (res # 0) OR (ch = 0X);
		IF ftype = stdDocuType THEN ftype := Files.docType END
	END ReadFileType;

	PROCEDURE ReadFile(src: TextModels.Reader; VAR res: INTEGER; f: Files.File);
		VAR hash, x, bit, i, j, len, sum, s: INTEGER; byte: BYTE; dest: Files.Writer;
	BEGIN
		res := 0; i := 0; len := 0;
		REPEAT Read(src, x, res); len := len + ASH(x, 6 * i); INC(i) UNTIL (res # 0) OR (i = 6);
		i := 0;
		REPEAT table[i] := 0; INC(i) UNTIL i = N;
		bit := 0; hash := 0; sum := 0; dest := f.NewWriter(NIL);
		WHILE (res = 0) & (len # 0) DO
			IF bit = 0 THEN Read(src, x, res); bit := 6 END;
			IF ODD(x) THEN	(* Incorrect prediction -> 1'xxxx'xxxx *)
				x := x DIV 2; DEC(bit);
				WHILE (res = 0) & (bit < 8) DO Read(src, j, res); INC(x, ASH(j, bit)); INC(bit, 6) END;
				i := x MOD 256;
				IF i > MAX(BYTE) THEN i := i - 256 END;
				byte := SHORT(SHORT(i)); x := x DIV 256; DEC(bit, 8);
				table[hash] := byte
			ELSE byte := table[hash]; x := x DIV 2; DEC(bit)	(* correct prediction *)
			END;
			hash := (16 * hash + byte MOD 256) MOD N;
			dest.WriteByte(byte); sum := (sum + byte MOD 256) MOD (16 * 1024); DEC(len)
		END;
		IF res = 0 THEN
			i := 0; s := 0;
			REPEAT Read(src, x, res); s := s + ASH(x, 6 * i); INC(i) UNTIL (res # 0) OR (i = 6);
			IF (res = 0) & (s # sum) THEN res := 2 END
		END
	END ReadFile;

	PROCEDURE ShowText (t: TextModels.Model);
		VAR l: INTEGER; v: Views.View; wr: TextMappers.Formatter; conv: Converters.Converter;
	BEGIN
		l := t.Length();
		wr.ConnectTo(t); wr.SetPos(l); wr.WriteString(" --- end of encoding ---");
		ShowSizeMsg(l);
		v := TextViews.dir.New(t);
		conv := Converters.list;
		WHILE (conv # NIL) & (conv.imp # "StdTextConv.ImportText") DO conv := conv.next END;
		Views.Open(v, NIL, "", conv);
		Views.SetDirty(v)
	END ShowText;

	PROCEDURE EncodedView*(v: Views.View): TextModels.Model;
		VAR n: INTEGER; f: Files.File; wrs: Stores.Writer; t: TextModels.Model; wr: TextModels.Writer;
	BEGIN
		f := Files.dir.Temp(); wrs.ConnectTo(f); Views.WriteView(wrs, v);
		t := TextModels.dir.New(); wr := t.NewWriter(NIL);
		n := 0; WriteHeader(wr, n, "", View); WriteFileType(wr, n, f.type); WriteFile(wr, n, f);
		RETURN t
	END EncodedView;

	PROCEDURE EncodeDocument*;
		VAR v: Views.View; w: Windows.Window;
	BEGIN
		w := Windows.dir.First();
		IF w # NIL THEN
			v := w.doc.OriginalView();
			IF (v.context # NIL) & (v.context IS Documents.Context) THEN
				v := v.context(Documents.Context).ThisDoc()
			END;
			IF v # NIL THEN ShowText(EncodedView(v)) END
		END
	END EncodeDocument;

	PROCEDURE EncodeFocus*;
		VAR v: Views.View;
	BEGIN
		v := Controllers.FocusView();
		IF v # NIL THEN ShowText(EncodedView(v)) END
	END EncodeFocus;

	PROCEDURE EncodeSelection*;
		VAR beg, end: INTEGER; t: TextModels.Model; c: TextControllers.Controller;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			c.GetSelection(beg, end);
			t := TextModels.CloneOf(c.text); t.InsertCopy(0, c.text, beg, end);
			ShowText(EncodedView(TextViews.dir.New(t)))
		END
	END EncodeSelection;

	PROCEDURE EncodeFile*;
		VAR n: INTEGER; loc: Files.Locator; name: Files.Name; f: Files.File;
			t: TextModels.Model; wr: TextModels.Writer;
	BEGIN
		Dialog.GetIntSpec("", loc, name);
		IF loc # NIL THEN
			f := Files.dir.Old(loc, name, TRUE);
			IF f # NIL THEN
				t := TextModels.dir.New(); wr := t.NewWriter(NIL);
				n := 0; WriteHeader(wr, n, name, File); WriteFileType(wr, n, f.type); WriteFile(wr, n, f);
				ShowText(t)
			END
		END
	END EncodeFile;

	PROCEDURE GetFile(VAR path: ARRAY OF CHAR; VAR loc: Files.Locator; VAR name: Files.Name);
		VAR i, j: INTEGER; ch: CHAR;
	BEGIN
		i := 0; ch := path[0]; loc := Files.dir.This(""); name := "";
		WHILE (ch # 0X) & (loc.res = 0) DO
			j := 0;
			WHILE (ch # 0X) & (ch # Separator) & (ch # Separator2) DO name[j] := ch; INC(j); INC(i); ch := path[i] END;
			name[j] := 0X;
			IF (ch = Separator) OR (ch = Separator2) THEN loc := loc.This(name); INC(i); ch := path[i] END;
		END;
		IF loc.res # 0 THEN loc := NIL END;
		path[i] := 0X
	END GetFile;

	PROCEDURE ReadPath(rd: TextModels.Reader; VAR path: ARRAY OF CHAR; VAR len: INTEGER);
		VAR i, l: INTEGER; ch: CHAR; quote: CHAR;
	BEGIN
		i := 0; l := LEN(path) - 1;
		REPEAT rd.ReadChar(ch) UNTIL rd.eot OR (ch > " ");
		IF (ch = "'") OR (ch = '"') THEN quote := ch; rd.ReadChar(ch) ELSE quote := 0X END;
		WHILE ~rd.eot & (i < l) & ((quote = 0X) & (ch > " ") OR (quote # 0X) & (ch # quote) & (ch >= " ")) DO
			path[i] := ch; INC(i); rd.ReadChar(ch)
		END;
		path[i] := 0X; len := i
	END ReadPath;

	PROCEDURE WriteString(w: Files.Writer; IN str: ARRAY OF CHAR; len: INTEGER);
		VAR i, res: INTEGER; utf8: Kernel.Utf8Name;
	BEGIN
		Utf.StringToUtf8(str, utf8, res); ASSERT(res = 0); len:= LEN(utf8$) + 1;
		i := 0;
		WHILE i < len DO
			IF ORD(utf8[i]) > MAX(BYTE) THEN w.WriteByte(SHORT(SHORT(ORD(utf8[i]) - 256)))
			ELSE w.WriteByte(SHORT(ORD(utf8[i])))
			END;
			INC(i)
		END
	END WriteString;

	PROCEDURE EncodeFileList*;
		TYPE
			FileList = POINTER TO RECORD
				next: FileList;
				f: Files.File
			END;
		VAR
			beg, end, i, j, n: INTEGER; err: BOOLEAN;
			files, last: FileList;
			list, f: Files.File; w: Files.Writer; loc: Files.Locator;
			rd: TextModels.Reader; wr: TextModels.Writer; t: TextModels.Model;
			c: TextControllers.Controller;
			name: Files.Name; path, next: ARRAY 2048 OF CHAR;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN c.GetSelection(beg, end);
			rd := c.text.NewReader(NIL); rd.SetPos(beg); err := FALSE;
			list := Files.dir.Temp(); w := list.NewWriter(NIL); files := NIL; last := NIL;
			ReadPath(rd, path, i);
			WHILE (path # "") & (rd.Pos() - i < end) & ~err DO
				GetFile(path, loc, name);
				IF loc # NIL THEN
					f := Files.dir.Old(loc, name, TRUE); err := f = NIL;
					IF ~err THEN
						IF last = NIL THEN NEW(last); files := last ELSE NEW(last.next); last := last.next END;
						last.f := f;
						ReadPath(rd, next, j);
						IF (next = "=>") & (rd.Pos() - j < end) THEN
							ReadPath(rd, next, j);
							IF next # "" THEN WriteString(w, next, j + 1); ReadPath(rd, next, j)
							ELSE err := TRUE
							END
						ELSE WriteString(w, path, i + 1)
						END;
						path := next; i := j
					END
				ELSE err := TRUE
				END
			END;
			IF ~err & (files # NIL) THEN
				t := TextModels.dir.New(); wr := t.NewWriter(NIL);
				n := 0; WriteHeader(wr, n, "", List);
				WriteFileType(wr, n, list.type); WriteFile(wr, n, list);
				WHILE files # NIL DO
					WriteFileType(wr, n, files.f.type); WriteFile(wr, n, files.f); files := files.next
				END;
				ShowText(t)
			ELSIF err THEN
				IF path = "" THEN ShowError(7, path)
				ELSIF loc # NIL THEN ShowError(6, path)
				ELSE ShowError(5, path)
				END
			END
		END
	END EncodeFileList;

	PROCEDURE DecodeView(rd: TextModels.Reader; name: Files.Name);
		VAR res: INTEGER; f: Files.File; ftype: Files.Type; rds: Stores.Reader; v: Views.View;
	BEGIN
		ReadFileType(rd, res, ftype);
		IF res = 0 THEN
			f := Files.dir.Temp(); ReadFile(rd, res, f);
			IF res = 0 THEN
				rds.ConnectTo(f); Views.ReadView(rds, v); Views.Open(v, NIL, name, NIL);
				Views.SetDirty(v)
			ELSE ShowError(res, "")
			END
		ELSE ShowError(res, "")
		END
	END DecodeView;

	PROCEDURE DecodeFile(rd: TextModels.Reader; name: Files.Name);
		VAR res: INTEGER; ftype: Files.Type; loc: Files.Locator; f: Files.File;
	BEGIN
		ReadFileType(rd, res, ftype);
		IF res = 0 THEN
			Dialog.GetExtSpec(name, ftype, loc, name);
			IF loc # NIL THEN
				f := Files.dir.New(loc, askOption);
				IF f # NIL THEN
					ReadFile(rd, res, f);
					IF res = 0 THEN
						f.Register(name, ftype, askOption, res);
						IF res # 0 THEN ShowError(4, "") END
					ELSE ShowError(res, "")
					END
				ELSIF loc.res = 4 THEN ShowError(9, "")
				ELSIF loc.res = 5 THEN ShowError(10, "")
				END
			END
		ELSE ShowError(res, "")
		END
	END DecodeFile;

	PROCEDURE DecodeFileList (rd: TextModels.Reader; VAR files: FileList; VAR len, res: INTEGER);
		VAR i, n: INTEGER; b: BYTE; p: FileList;
			ftype: Files.Type; f: Files.File; frd: Files.Reader; utf8: Kernel.Utf8Name;
	BEGIN
		ReadFileType(rd, res, ftype);
		IF res = 0 THEN
			f := Files.dir.Temp(); ReadFile(rd, res, f);
			IF res = 0 THEN
				files := NIL; p := NIL; n := 0;
				frd := f.NewReader(NIL); frd.ReadByte(b);
				WHILE ~frd.eof & (res = 0) DO
					INC(n); i := 0;
					WHILE ~frd.eof & (b # 0) DO
						utf8[i] := SHORT(CHR(b MOD 256)); INC(i); frd.ReadByte(b)
					END;
					utf8[i] := 0X;
					(*
					IF (i > 4) & (utf8[i - 4] = ".") & (CAP(utf8[i - 3]) = "O")
						& (CAP(utf8[i - 2]) = "D") & (CAP(utf8[i - 1]) = "C")
					THEN utf8[i - 4] := 0X
					ELSE utf8[i] := 0X
					END;
					*)
					IF ~frd.eof THEN
						IF p = NIL THEN NEW(p); files := p ELSE NEW(p.next); p := p.next END;
						Utf8ToString(utf8, p.name);
						frd.ReadByte(b)
					ELSE res := 1
					END
				END;
				p := files; len := n;
				WHILE (res = 0) & (p # NIL) DO
					ReadFileType(rd, res, p.type);
					IF res = 0 THEN p.file := Files.dir.Temp(); ReadFile(rd, res, p.file) END;
					p := p.next
				END
			END
		END
	END DecodeFileList;

	PROCEDURE OpenDialog(files: FileList; len: INTEGER);
		VAR i: INTEGER; p: FileList;
	BEGIN
		par.files := files; par.list.SetLen(len);
		p := files; i := 0;
		WHILE p # NIL DO par.list.SetItem(i, p.name); INC(i); p := p.next END;
		par.storeAs := "";
		Dialog.Update(par); Dialog.UpdateList(par.list);
		StdCmds.OpenAuxDialog("Std/Rsrc/Coder", "#Std:DecodeDialog")
	END OpenDialog;

	PROCEDURE CloseDialog*;
	BEGIN
		par.files := NIL; par.list.SetLen(0); par.storeAs := "";
		Dialog.UpdateList(par.list); Dialog.Update(par)
	END CloseDialog;

	PROCEDURE Select*(op, from, to: INTEGER);
		VAR p: FileList; i: INTEGER;
	BEGIN
		IF (op = Dialog.included) OR (op = Dialog.excluded) OR (op = Dialog.set) THEN
			IF NofSelections(par.list) = 1 THEN
				i := 0; p := par.files;
				WHILE ~par.list.In(i) DO INC(i); p := p.next END;
				par.storeAs := p.name
			ELSE par.storeAs := ""
			END;
			Dialog.Update(par)
		END
	END Select;

	PROCEDURE CopyFile(from: Files.File; loc: Files.Locator; name: Files.Name; type: Files.Type);
		CONST BufSize = 4096;
		VAR res, k, l: INTEGER; f: Files.File; r: Files.Reader; w: Files.Writer;
			buf: ARRAY BufSize OF BYTE;
	BEGIN
		f := Files.dir.New(loc, askOption);
		IF f # NIL THEN
			r := from.NewReader(NIL); w := f.NewWriter(NIL); l := from.Length();
			WHILE l # 0 DO
				IF l <= BufSize THEN k := l ELSE k := BufSize END;
				r.ReadBytes(buf, 0, k); w.WriteBytes(buf, 0, k);
				l := l - k
			END;
			f.Register(name, type, askOption, res);
			IF res # 0 THEN ShowError(4, "") END
		ELSIF loc.res = 4 THEN ShowError(9, "")
		ELSIF loc.res = 5 THEN ShowError(10, "")
		END
	END CopyFile;

	PROCEDURE StoreSelection*;
		VAR i, n: INTEGER; p: FileList; loc: Files.Locator; name: Files.Name;
	BEGIN
		n := NofSelections(par.list);
		IF n > 1 THEN
			i := 0; p := par.files;
			WHILE n # 0 DO
				WHILE ~par.list.In(i) DO INC(i); p := p.next END;
				GetFile(p.name, loc, name); CopyFile(p.file, loc, name, p.type);
				DEC(n); INC(i); p := p.next
			END
		ELSIF (n = 1) & (par.storeAs # "") THEN
			i := 0; p := par.files;
			WHILE ~par.list.In(i) DO INC(i); p := p.next END;
			GetFile(par.storeAs, loc, name); CopyFile(p.file, loc, name, p.type)
		END
	END StoreSelection;

	PROCEDURE StoreSelectionGuard*(VAR p: Dialog.Par);
		VAR n: INTEGER;
	BEGIN
		n := NofSelections(par.list);
		p.disabled := (n = 0) OR ((n = 1) & (par.storeAs = ""))
	END StoreSelectionGuard;

	PROCEDURE StoreSingle*;
		VAR i: INTEGER; p: FileList; loc: Files.Locator; name: Files.Name;
	BEGIN
		IF NofSelections(par.list) = 1 THEN
			i := 0; p := par.files;
			WHILE ~par.list.In(i) DO INC(i); p := p.next END;
			GetFile(p.name, loc, name);
			Dialog.GetExtSpec(name, p.type, loc, name);
			IF loc # NIL THEN CopyFile(p.file, loc, name, p.type) END
		END
	END StoreSingle;

	PROCEDURE StoreSingleGuard*(VAR p: Dialog.Par);
	BEGIN
		p.disabled := NofSelections(par.list) # 1
	END StoreSingleGuard;

	PROCEDURE StoreAllFiles(files: FileList);
		VAR loc: Files.Locator; name: Files.Name;
	BEGIN
		WHILE files # NIL DO
			GetFile(files.name, loc, name); CopyFile(files.file, loc, name, files.type); files := files.next
		END
	END StoreAllFiles;

	PROCEDURE StoreAll*;
	BEGIN
		StoreAllFiles(par.files)
	END StoreAll;

	PROCEDURE DecodeAllFromText*(text: TextModels.Model; beg: INTEGER; ask: BOOLEAN);
		VAR res, i: INTEGER; type: BYTE; name: Files.Name; rd: TextModels.Reader; files: FileList;
	BEGIN
		CloseDialog;
		rd := text.NewReader(NIL); rd.SetPos(beg);
		ReadHeader(rd, res, name, type);
		(*
		i := 0;
		WHILE name[i] # 0X DO INC(i) END;
		IF (i > 4) & (name[i - 4] = ".") & (CAP(name[i - 3]) = "O")
			& (CAP(name[i - 2]) = "D") & (CAP(name[i - 1]) = "C")
		THEN name[i - 4] := 0X
		END;
		*)
		IF res = 0 THEN
			IF type = View THEN DecodeView(rd, name)
			ELSIF type = File THEN DecodeFile(rd, name)
			ELSIF type = List THEN
				DecodeFileList(rd, files, i, res);
				IF res = 0 THEN
					IF ask THEN OpenDialog(files, i) ELSE StoreAllFiles(files) END
				ELSE ShowError(res, "")
				END
			ELSE ShowError(3, "")
			END
		ELSE ShowError(res, "")
		END
	END DecodeAllFromText;

	PROCEDURE Decode*;
		VAR beg, end: INTEGER; c: TextControllers.Controller;
	BEGIN
		CloseDialog;
		c := TextControllers.Focus();
		IF c # NIL THEN
			IF c.HasSelection() THEN c.GetSelection(beg, end) ELSE beg := 0 END;
			DecodeAllFromText(c.text, beg, TRUE)
		END
	END Decode;

	PROCEDURE ListFiles(rd: TextModels.Reader; VAR wr: TextMappers.Formatter);
		VAR i, n, res: INTEGER; b: BYTE;
			ftype: Files.Type; f: Files.File; frd: Files.Reader; path: Dialog.String; utf8: Kernel.Utf8Name;
	BEGIN
		ReadFileType(rd, res, ftype);
		IF res = 0 THEN
			f := Files.dir.Temp(); ReadFile(rd, res, f);
			IF res = 0 THEN
				n := 0;
				frd := f.NewReader(NIL); frd.ReadByte(b);
				WHILE ~frd.eof & (res = 0) DO
					INC(n); i := 0;
					WHILE ~frd.eof & (b # 0) DO
						utf8[i] := SHORT(CHR(b MOD 256)); INC(i); frd.ReadByte(b)
					END;
					utf8[i] := 0X;
					(*
					IF (i > 4) & (utf8[i - 4] = ".") & (CAP(utf8[i - 3]) = "O")
						& (CAP(utf8[i - 2]) = "D") & (CAP(utf8[i - 1]) = "C")
					THEN utf8[i - 4] := 0X
					ELSE utf8[i] := 0X
					END;
					*)
					IF ~frd.eof THEN Utf8ToString(utf8, path); wr.WriteString(path); wr.WriteLn; frd.ReadByte(b) 
					ELSE res := 1
					END
				END
			ELSE ShowError(res, "")
			END
		ELSE ShowError(res, "")
		END
	END ListFiles;

	PROCEDURE ListSingleton(type, name: ARRAY OF CHAR; VAR wr: TextMappers.Formatter);
	BEGIN
		wr.WriteString(type);
		IF name # "" THEN wr.WriteString(": '"); wr.WriteString(name); wr.WriteChar("'") END;
		wr.WriteLn
	END ListSingleton;

	PROCEDURE EncodedInText*(text: TextModels.Model; beg: INTEGER): TextModels.Model;
		VAR res, i: INTEGER; type: BYTE; name: Files.Name;
			rd: TextModels.Reader; report: TextModels.Model; wr: TextMappers.Formatter;
	BEGIN
		report := TextModels.dir.New(); wr.ConnectTo(report);
		rd := text.NewReader(NIL); rd.SetPos(beg);
		ReadHeader(rd, res, name, type);
		(*
		i := 0;
		WHILE name[i] # 0X DO INC(i) END;
		IF (i > 4) & (name[i - 4] = ".") & (CAP(name[i - 3]) = "O")
			& (CAP(name[i - 2]) = "D") & (CAP(name[i - 1]) = "C")
		THEN name[i - 4] := 0X
		END;
		*)
		IF res = 0 THEN
			IF type = View THEN ListSingleton("View", name, wr)
			ELSIF type = File THEN ListSingleton("File", name, wr)
			ELSIF type = List THEN ListFiles(rd, wr)
			ELSE ShowError(3, "")
			END
		ELSE ShowError(res, "")
		END;
		RETURN report
	END EncodedInText;

	PROCEDURE ListEncodedMaterial*;
		VAR beg, end: INTEGER; c: TextControllers.Controller;
	BEGIN
		c := TextControllers.Focus();
		IF c # NIL THEN
			IF c.HasSelection() THEN c.GetSelection(beg, end) ELSE beg := 0 END;
			Views.OpenView(TextViews.dir.New(EncodedInText(c.text, beg)))
		END
	END ListEncodedMaterial;

	PROCEDURE InitCodes;
		VAR i: BYTE; j: INTEGER;
	BEGIN
		j := 0;
		WHILE j # 256 DO revCode[j] := -1; INC(j) END;
		code[0] := "."; revCode[ORD(".")] := 0; code[1] := ","; revCode[ORD(",")] := 1;
		i := 2; j := ORD("0");
		WHILE j <= ORD("9") DO code[i] := CHR(j); revCode[j] := i; INC(i); INC(j) END;
		j := ORD("A");
		WHILE j <= ORD("Z") DO code[i] := CHR(j); revCode[j] := i; INC(i); INC(j) END;
		j := ORD("a");
		WHILE j <= ORD("z") DO code[i] := CHR(j); revCode[j] := i; INC(i); INC(j) END;
		ASSERT(i = 64, 60)
	END InitCodes;

BEGIN
	InitCodes;
	askOption := TRUE;
	stdDocuType[0] := 3X; stdDocuType[1] := 3X; stdDocuType[2] := 3X; stdDocuType[3] := 0X
END StdCoder.
