DIRECTORY
Atom USING [GetPName],
CedarProcess USING [Priority, DoWithPriority],
FileOps USING [comment, endNode, endOfFile, IntBytes, LengthByte, look1, look2, look3, looks, looksFirst, LooksIndex, prop, PropIndex, propShort, rope, runs, startNode, startNodeFirst, terminalTextNode, terminalTextNodeFirst, ThirdByte, FormatIndex],
FileWriter USING [Close, OpenC, Ref, ToRope, ToStream, WriteChar, WriteRope],
FS USING [Create, OpenFile, StreamFromOpenFile, StreamOpen],
IO USING [Close, Error, GetIndex, PutChar, PutRope, RopeFromROS, ROS, SetLength, STREAM],
NodeProps USING [GetSpecs, MapProps],
PGSupport USING [CreatePGF, EnterLooks, EnterProp, EnterFormatName, FreePGF, PGF],
PrincOpsUtils USING [],
PutGet USING [],
Rope,
RopeEdit USING [BlankChar],
RopeIO USING [PutRope, ToFileC],
RopeReader USING [FreeRopeReader, GetRopeReader, Ref],
RunReader USING [FreeRunReader, GetRunReader, MergedGet, Ref, SetPosition],
TextLooks USING [CountRuns, Flatten, Look, Looks, LooksBytes, noLooks, Runs, RunsBody],
TextNode USING [FirstChild, Forward, IsLastSibling, NarrowToTextNode, Next, NodeProps, Parent, Ref, RefTextNode],
UserProfile USING [Number];
ROPE: TYPE = Rope.ROPE;
ToRope:
PUBLIC
PROC [node: TextNode.Ref, flatten, textOnly:
BOOL ←
FALSE]
RETURNS [dataLen, count:
INT, output:
ROPE] = {
control, comment, data: FileWriter.Ref;
simple: BOOL;
[output, simple] ← SimpleFile[node];
IF simple THEN { dataLen ← count ← Rope.Size[output]; RETURN };
[control, comment, data] ← FileWriter.ToRope[];
Finish[control, comment, data, node, flatten];
[dataLen, count, output] ← FileWriter.Close[control, comment, data, textOnly];
};
ToStream:
PUBLIC
PROC [stream:
IO.
STREAM, node: TextNode.Ref, flatten, textOnly:
BOOL ←
FALSE]
RETURNS [dataLen, count:
INT] = {
control, comment, data: FileWriter.Ref;
rope: ROPE;
simple: BOOL;
[rope, simple] ← SimpleFile[node];
IF simple
THEN {
RopeIO.PutRope[stream, rope];
dataLen ← count ← Rope.Size[rope];
RETURN
};
[control, comment, data] ← FileWriter.ToStream[stream];
Finish[control, comment, data, node, flatten];
[dataLen, count, ] ← FileWriter.Close[control, comment, data, textOnly];
};
ToFile:
PUBLIC
PROC [fileName:
ROPE, node: TextNode.Ref, start:
INT ← 0, flatten, textOnly:
BOOL ←
FALSE]
RETURNS [dataLen, count:
INT] = {
file: FS.OpenFile;
fileName ← fileName.Flatten[0, fileName.SkipTo[0, "!"]];
file ← FS.Create[name: fileName, keep: UserProfile.Number["Tioga.defaultKeep", 2]];
[dataLen, count] ← ToFileC[file, node, start, flatten, textOnly];
};
ToFileC:
PUBLIC
PROC [file:
FS.OpenFile, node: TextNode.Ref, start:
INT ← 0, flatten, textOnly:
BOOL ←
FALSE]
RETURNS [dataLen, count:
INT] = {
ropeToFile: PROC [rope: ROPE] = { RopeIO.ToFileC[file, rope, start] };
opener:
PROC
RETURNS [control, comment, data: FileWriter.Ref] = {
[control, comment, data] ← FileWriter.OpenC[file, start]
};
[dataLen, count] ← FileIt[ropeToFile, opener, node, flatten, textOnly];
};
savePriority: CedarProcess.Priority ← normal;
FileIt:
PROC [ropeToFile:
PROC [
ROPE],
opener:
PROC
RETURNS [control, comment, data: FileWriter.Ref],
node: TextNode.Ref, flatten, textOnly:
BOOL
]
RETURNS [dataLen, count:
INT] = {
innerFileIt:
PROC ~ {
rope: ROPE;
simple: BOOL;
[rope, simple] ← SimpleFile[node];
IF simple
THEN {
ropeToFile[rope];
dataLen ← count ← Rope.Size[rope];
}
ELSE {
control, comment, data: FileWriter.Ref;
[control, comment, data] ← opener[];
Finish[control, comment, data, node, flatten];
[dataLen, count, ] ← FileWriter.Close[control, comment, data, textOnly];
};
};
CedarProcess.DoWithPriority[savePriority, innerFileIt];
};
SimpleFile:
PROC [root: TextNode.Ref]
RETURNS [rope:
ROPE, simple:
BOOL] = {
SimpleNode:
PROC [node: TextNode.Ref]
RETURNS [
ROPE,
BOOL] = {
HasInterestingProp:
PROC
RETURNS [
BOOL] = {
Check:
PROC [name:
ATOM, value:
REF]
RETURNS [
BOOL] = {
RETURN [
SELECT name
FROM
$Viewer, $LockedViewer, $FromTiogaFile, $DocumentLock, $FileCreateDate, $FileExtension => FALSE,
When add a new "system" property that should not go on files, add registration at end of TEditDocuments2Impl so will not copy/write the property.
ENDCASE => TRUE]
};
RETURN [npI.MapProps[node, Check, FALSE, FALSE]]
};
text: TextNode.RefTextNode = node;
IF node.formatName=
NIL
AND
NOT text.comment
AND
NOT HasInterestingProp[]
THEN {
IF node.runs=NIL THEN RETURN [node.rope, TRUE]
ELSE
WITH node.runs
SELECT
FROM
r: REF TextLooks.RunsBody.base => IF r.length=1 AND r[0].looks=TextLooks.noLooks THEN
RETURN [node.rope, TRUE];
ENDCASE;
};
RETURN [NIL, FALSE]
};
IF root=NIL THEN RETURN [NIL, TRUE];
[rope, simple] ← SimpleNode[root];
IF ~simple THEN RETURN; -- not a simple root node
IF root.child=NIL THEN RETURN; -- simple root and no child
IF rope # NIL OR -- root has child and text, so not simple
~root.child.last OR root.child.child # NIL THEN RETURN [NIL, FALSE]; -- more than one child, so not simple
[rope, simple] ← SimpleNode[root.child];
};
Finish:
PROC [control, comment, data: FileWriter.Ref, root: TextNode.Ref, flatten:
BOOL] = {
WriteChar: PROC [c: CHAR, writer: FileWriter.Ref] = { FileWriter.WriteChar[c, writer] };
WriteAtom:
PROC [atom:
ATOM] = {
WriteControlRope[Atom.GetPName[atom]]
};
WriteRope:
PROC [r:
ROPE, writer: FileWriter.Ref] = {
WriteLen[Rope.Size[r]];
FileWriter.WriteRope[r, Rope.Size[r], writer, ropeReader];
WriteChar[15C, writer]
};
WriteControlRope:
PROC [r:
ROPE] = {
size: INT ← Rope.Size[r];
WriteLen[size];
FileWriter.WriteRope[r, size, control, ropeReader]
};
WriteLen:
PROC [len:
INT] = {
first, second, fourth: FileOps.LengthByte;
third: FileOps.ThirdByte;
lenBytes: FileOps.IntBytes ← LOOPHOLE[len];
IF lenBytes.fourth # 0
THEN {
fourth.data ← lenBytes.fourth;
first.others ← second.others ← third.others ←
TRUE
};
IF lenBytes.thirdTop # 0
OR lenBytes.thirdBottom # 0
THEN {
third.dataTop ← lenBytes.thirdTop;
third.dataBottom ← lenBytes.thirdBottom;
first.others ← second.others ←
TRUE
};
IF lenBytes.second # 0
THEN {
second.data ← lenBytes.second; first.others ←
TRUE
};
first.data ← lenBytes.first;
WriteChar[LOOPHOLE[first], control];
IF first.others
THEN {
WriteChar[LOOPHOLE[second], control];
IF second.others
THEN {
WriteChar[LOOPHOLE[third], control];
IF third.others
THEN {
WriteChar[LOOPHOLE[fourth], control]
}
}
}
};
WriteLooks:
PROC [lks: TextLooks.LooksBytes] = {
WriteChar[LOOPHOLE[lks.byte0], control];
WriteChar[LOOPHOLE[lks.byte1], control];
WriteChar[LOOPHOLE[lks.byte2], control];
WriteChar[LOOPHOLE[lks.byte3], control]
};
CountLookBits:
PROC [lks: TextLooks.Looks]
RETURNS [cnt:
NAT] = {
cnt ← 0;
FOR c: CHAR IN TextLooks.Look DO IF lks[c] THEN cnt ← cnt+1; ENDLOOP
};
WriteLookChars:
PROC [lks: TextLooks.Looks] = {
FOR c: CHAR IN TextLooks.Look DO IF lks[c] THEN WriteChar[c, control]; ENDLOOP
};
WriteProp:
PROC [name:
ATOM, value:
REF]
RETURNS [
BOOL] = {
-- write specs as a rope
specs: ROPE ← npI.GetSpecs[name, value];
IF specs=NIL THEN RETURN [FALSE];
[ok, propindex] ← PGSupport.EnterProp[name, pgf];
IF ok
THEN {
-- can use short form
WriteChar[FileOps.propShort, control];
WriteChar[LOOPHOLE[propindex], control]
}
ELSE {
-- must write full prop name
WriteChar[FileOps.prop, control];
WriteAtom[name]
};
WriteControlRope[specs];
RETURN [FALSE]
};
formatName: ATOM;
ok, terminal: BOOL;
formatindex: FileOps.FormatIndex;
looksindex: FileOps.LooksIndex;
propindex: FileOps.PropIndex;
node, nodeChild: TextNode.Ref;
pgf: PGSupport.PGF ← PGSupport.CreatePGF[];
runReader: RunReader.Ref ← RunReader.GetRunReader[];
ropeReader: RopeReader.Ref ← RopeReader.GetRopeReader[];
nameText: REF TEXT ← NEW[TEXT[32]];
node ← root;
DO
-- first write format
rope: ROPE;
size: INT;
runs: TextLooks.Runs;
terminal ← (nodeChild←TextNode.FirstChild[node])=NIL;
[ok, formatindex] ← PGSupport.EnterFormatName[formatName ← node.formatName, pgf];
IF ok
THEN {
WriteChar[formatindex+(IF ~terminal THEN FileOps.startNodeFirst ELSE FileOps.terminalTextNodeFirst), control]
}
ELSE {
WriteChar[IF ~terminal THEN FileOps.startNode ELSE FileOps.terminalTextNode, control];
WriteControlRope[IF formatName#NIL THEN Atom.GetPName[formatName] ELSE NIL]
};
write node props
IF node.props # NIL THEN [] ← npI.MapProps[node, WriteProp, FALSE, FALSE];
now write contents
first the rope, then the looks
IF flatten
THEN {
-- flatten rope and runs
node.rope ← Rope.Balance[node.rope];
node.runs ← TextLooks.Flatten[node.runs];
};
rope ← node.rope; size ← Rope.Size[rope];
IF (runs ← node.runs) #
NIL
THEN {
loc, cnt, numRuns: INT ← 0;
[numRuns, , ] ← TextLooks.CountRuns[runs, 0, size];
WriteChar[FileOps.runs, control];
WriteLen[numRuns];
RunReader.SetPosition[runReader, runs, 0];
WHILE (cnt𡤌nt+1) <= numRuns
DO
looks: TextLooks.Looks;
len: INT;
[len, looks] ← RunReader.MergedGet[runReader];
[ok, looksindex] ← PGSupport.EnterLooks[looks, pgf];
IF ok THEN
WriteChar[FileOps.looksFirst+looksindex, control]
ELSE {
-- must write out the looks
SELECT CountLookBits[looks]
FROM
1 => { WriteChar[FileOps.look1, control];
WriteLookChars[looks]
};
2 => { WriteChar[FileOps.look2, control];
WriteLookChars[looks]
};
3 => { WriteChar[FileOps.look3, control];
WriteLookChars[looks]
};
ENDCASE => {
WriteChar[FileOps.looks, control];
WriteLooks[LOOPHOLE[looks]]
}
};
WriteLen[len];
loc ← loc+len;
ENDLOOP;
IF loc # size THEN ERROR
};
IF node.comment
THEN {
-- put text in comment area of file
WriteChar[FileOps.comment, control];
WriteRope[rope, comment]
}
ELSE {
-- put text in data area of file
WriteChar[FileOps.rope, control];
WriteRope[rope, data]
};
move to the next node
IF ~terminal
THEN node ← nodeChild
ELSE {
-- node has no children
DO
IF node=root
THEN
GOTO Finis;
IF ~TextNode.IsLastSibling[node] THEN { node ← TextNode.Next[node]; EXIT };
node ← TextNode.Parent[node];
WriteChar[FileOps.endNode, control];
ENDLOOP
};
REPEAT Finis => {
WriteChar[FileOps.endOfFile, control];
RunReader.FreeRunReader[runReader];
RopeReader.FreeRopeReader[ropeReader];
PGSupport.FreePGF[pgf]
};
ENDLOOP
};
WriteMesaFilePlain:
PUBLIC
PROC [fileName:
ROPE, root: TextNode.Ref] = {
h: IO.STREAM ← FS.StreamOpen[fileName: fileName, accessOptions: $create, keep: UserProfile.Number["Tioga.defaultKeep", 2]];
WritePlain[h, root, TRUE];
IO.Close[h];
};
WriteFilePlain:
PUBLIC
PROC [fileName:
ROPE, root: TextNode.Ref] = {
h: IO.STREAM ← FS.StreamOpen[fileName: fileName, accessOptions: $create, keep: UserProfile.Number["Tioga.defaultKeep", 2]];
WritePlain[h, root];
IO.Close[h];
};
WriteFileCPlain:
PUBLIC
PROC [file:
FS.OpenFile, root: TextNode.Ref] = {
h: IO.STREAM = FS.StreamFromOpenFile[file, $write];
WritePlain[h, root];
IO.Close[h];
};
WriteRopePlain:
PUBLIC
PROC [root: TextNode.Ref, restoreDashes:
BOOL ←
FALSE]
RETURNS [output:
ROPE] = {
h: IO.STREAM = IO.ROS[];
WritePlain[h, root, restoreDashes];
RETURN [IO.RopeFromROS[h]]
};
WritePlain:
PUBLIC
PROC [h:
IO.
STREAM, root: TextNode.Ref, restoreDashes:
BOOL ←
FALSE] = {
HasInitialDashes:
PROC [r:
ROPE]
RETURNS [
BOOL] = {
loc: INT ← 0;
size: INT = Rope.Size[r];
c: CHAR;
WHILE loc < size
AND RopeEdit.BlankChar[c ← Rope.Fetch[r, loc]]
DO
loc ← loc+1; ENDLOOP;
IF loc > size-2 OR c # '- OR Rope.Fetch[r, loc+1] # '- THEN RETURN [FALSE];
RETURN [TRUE]
};
node: TextNode.Ref ← root;
level: INTEGER ← 0;
levelDelta: INTEGER;
first: BOOL ← TRUE;
DO
text: TextNode.RefTextNode;
[node, levelDelta] ← TextNode.Forward[node];
IF node=NIL THEN EXIT;
IF first
THEN first ←
FALSE
ELSE IO.PutChar[h, '\n]; -- carriage returns between nodes
level ← level+levelDelta;
IF (text ← TextNode.NarrowToTextNode[node])=NIL THEN LOOP;
THROUGH [1..level) DO IO.PutChar[h, '\t]; ENDLOOP; -- output level-1 tabs
IF restoreDashes AND text.comment AND ~HasInitialDashes[text.rope] THEN
IO.PutRope[h, "-- "]; -- restore the leading dashes for Mesa comments
IO.PutRope[h, text.rope];
ENDLOOP;
{
ENABLE
IO.Error =>
IF ec = NotImplementedForThisStream
THEN
GOTO Exit;
IO.SetLength[h, IO.GetIndex[h]]
}
EXITS Exit => RETURN
};
END.