home *** CD-ROM | disk | FTP | other *** search
/ The Devil's Doorknob BBS Capture (1996-2003) / devilsdoorknobbbscapture1996-2003.iso / Dloads / OTHERUTI / TPASCAL3.ZIP / TVDEMOS.ZIP / TVHC.PAS < prev    next >
Pascal/Delphi Source File  |  1991-06-11  |  29KB  |  1,015 lines

  1. {************************************************}
  2. {                                                }
  3. {   Turbo Pascal 6.0                             }
  4. {   Turbo Vision Demo                            }
  5. {   Copyright (c) 1990 by Borland International  }
  6. {                                                }
  7. {************************************************}
  8.  
  9. {===== TVHC ============================================================}
  10. {  Turbo Vision help file compiler documentation.                       }
  11. {=======================================================================}
  12. {                                                                       }
  13. {    Refer to DEMOHELP.TXT for an example of a help source file.        }
  14. {                                                                       }
  15. {    This program takes a help script and produces a help file (.HLP)   }
  16. {    and a help context file (.PAS).  The format for the help file is   }
  17. {    very simple.  Each context is given a symbolic name (i.e FileOpen) }
  18. {    which is then put in the context file (i.e. hcFileOpen).  The text }
  19. {    following the topic line is put into the help file.  Since the     }
  20. {    help file can be resized, some of the text will need to be wrapped }
  21. {    to fit into the window.  If a line of text is flush left with      }
  22. {    no preceeding white space, the line will be wrapped.  All adjacent }
  23. {    wrappable lines are wrapped as a paragraph.  If a line begins with }
  24. {    a space it will not be wrapped. For example, the following is a    }
  25. {    help topic for a File|Open menu item.                              }
  26. {                                                                       }
  27. {       |.topic FileOpen                                                }
  28. {       |  File|Open                                                    }
  29. {       |  ---------                                                    }
  30. {       |This menu item will bring up a dialog...                       }
  31. {                                                                       }
  32. {    The "File|Open" will not be wrapped with the "----" line since     }
  33. {    they both begin with a space, but the "This menu..." line will     }
  34. {    be wrapped.                                                        }
  35. {      The syntax for a ".topic" line is:                               }
  36. {                                                                       }
  37. {        .topic symbol[=number][, symbol[=number][...]]                 }
  38. {                                                                       }
  39. {    Note a topic can have multiple symbols that define it so that one  }
  40. {    topic can be used by multiple contexts.  The number is optional    }
  41. {    and will be the value of the hcXXX context in the context file     }
  42. {    Once a number is assigned all following topic symbols will be      }
  43. {    assigned numbers in sequence.  For example,                        }
  44. {                                                                       }
  45. {       .topic FileOpen=3, OpenFile, FFileOpen                          }
  46. {                                                                       }
  47. {    will produce the follwing help context number definitions,         }
  48. {                                                                       }
  49. {        hcFileOpen  = 3;                                               }
  50. {        hcOpenFile  = 4;                                               }
  51. {        hcFFileOpen = 5;                                               }
  52. {                                                                       }
  53. {    Cross references can be imbedded in the text of a help topic which }
  54. {    allows the user to quickly access related topics.  The format for  }
  55. {    a cross reference is as follows,                                   }
  56. {                                                                       }
  57. (*        {text[:alias]}                                               *)
  58. {                                                                       }
  59. {    The text in the brackets is highlighted by the help viewer.  This  }
  60. {    text can be selected by the user and will take the user to the     }
  61. {    topic by the name of the text.  Sometimes the text will not be     }
  62. {    the same as a topic symbol.  In this case you can use the optional }
  63. {    alias syntax.  The symbol you wish to use is placed after the text }
  64. {    after a ':'. The following is a paragraph of text using cross      }
  65. {    references,                                                        }
  66. {                                                                       }
  67. (*      |The {file open dialog:FileOpen} allows you specify which      *)
  68. {       |file you wish to view.  If it also allow you to navigate       }
  69. {       |directories.  To change to a given directory use the           }
  70. (*      |{change directory dialog:ChDir}.                              *)
  71. {                                                                       }
  72. {    The user can tab or use the mouse to select more information about }
  73. {    the "file open dialog" or the "change directory dialog". The help  }
  74. {    compiler handles forward references so a topic need not be defined }
  75. {    before it is referenced.  If a topic is referenced but not         }
  76. {    defined, the compiler will give a warning but will still create a  }
  77. {    useable help file.  If the undefined reference is used, a message  }
  78. {    ("No help available...") will appear in the help window.           }
  79. {=======================================================================}
  80.  
  81. program TVHC;
  82.  
  83. {$S-}
  84.  
  85. {$M 8192,8192,655360}
  86.  
  87. uses Drivers, Objects, Dos, HelpFile;
  88.  
  89. procedure UpStr(var S: String);
  90. var
  91.   I: Integer;
  92. begin
  93.   for I := 1 to Length(S) do
  94.     S[I] := UpCase(S[I]);
  95. end;
  96.  
  97. {======================= File Management ===============================}
  98.  
  99. procedure Error(Text: String); forward;
  100.  
  101. type
  102.   PProtectedStream = ^TProtectedStream;
  103.   TProtectedStream = object(TBufStream)
  104.     FileName: FNameStr;
  105.     Mode: Word;
  106.     constructor Init(AFileName: FNameStr; AMode, Size: Word);
  107.     destructor Done; virtual;
  108.     procedure Error(Code, Info: Integer); virtual;
  109.   end;
  110.  
  111. var
  112.   TextStrm,
  113.   SymbStrm: TProtectedStream;
  114.   HelpStrm: PProtectedStream;
  115.  
  116. constructor TProtectedStream.Init(AFileName: FNameStr; AMode, Size: Word);
  117. begin
  118.   TBufStream.Init(AFileName, AMode, Size);
  119.   FileName := AFileName;
  120.   Mode := AMode;
  121. end;
  122.  
  123. destructor TProtectedStream.Done;
  124. var
  125.   F: File;
  126. begin
  127.   TBufStream.Done;
  128.   if (Mode = stCreate) and ((Status <> stOk) or (ExitCode <> 0)) then
  129.   begin
  130.     Assign(F, FileName);
  131.     Erase(F);
  132.   end;
  133. end;
  134.  
  135. procedure TProtectedStream.Error(Code, Info: Integer);
  136. begin
  137.   case Code of
  138.     stError:
  139.       TVHC.Error('Error encountered in file ' + FileName);
  140.     stInitError:
  141.       if Mode = stCreate then
  142.         TVHC.Error('Could not create ' + FileName)
  143.       else
  144.         TVHC.Error('Could not find ' + FileName);
  145.     stReadError: Status := Code; {EOF is "ok"}
  146.     stWriteError:
  147.       TVHC.Error('Disk full encountered writting file '+ FileName);
  148.   else
  149.       TVHC.Error('Internal error.');
  150.   end;
  151. end;
  152.  
  153. {----- ReplaceExt(FileName, NExt, Force) -------------------------------}
  154. {  Replace the extension of the given file with the given extension.    }
  155. {  If the an extension already exists Force indicates if it should be   }
  156. {  replaced anyway.                                                     }
  157. {-----------------------------------------------------------------------}
  158.  
  159. function ReplaceExt(FileName: PathStr; NExt: ExtStr; Force: Boolean):
  160.   PathStr;
  161. var
  162.   Dir: DirStr;
  163.   Name: NameStr;
  164.   Ext: ExtStr;
  165. begin
  166.   FSplit(FileName, Dir, Name, Ext);
  167.   if Force or (Ext = '') then
  168.     ReplaceExt := Dir + Name + NExt else
  169.     ReplaceExt := FileName;
  170. end;
  171.  
  172. {----- FExist(FileName) ------------------------------------------------}
  173. {  Returns true if the file exists false otherwise.                     }
  174. {-----------------------------------------------------------------------}
  175.  
  176. function FExists(FileName: PathStr): Boolean;
  177. var
  178.   F: file;
  179.   Attr: Word;
  180. begin
  181.   Assign(F, FileName);
  182.   GetFAttr(F, Attr);
  183.   FExists := DosError = 0;
  184. end;
  185.  
  186.  
  187. {======================== Line Management ==============================}
  188.  
  189. {----- GetLine(S) ------------------------------------------------------}
  190. {  Return the next line out of the stream.                              }
  191. {-----------------------------------------------------------------------}
  192.  
  193. const
  194.   Line: String = '';
  195.   LineInBuffer: Boolean = False;
  196.   Count: Integer = 0;
  197.  
  198. function GetLine(var S: TStream): String;
  199. var
  200.   C, I: Byte;
  201. begin
  202.   if S.Status <> stOk then
  203.   begin
  204.     GetLine := #26;
  205.     Exit;
  206.   end;
  207.   if not LineInBuffer then
  208.   begin
  209.     Line := '';
  210.     C := 0;
  211.     I := 0;
  212.     while (Line[I] <> #13) and (I < 254) and (S.Status = stOk) do
  213.     begin
  214.       Inc(I);
  215.       S.Read(Line[I], 1);
  216.     end;
  217.     Dec(I);
  218.     S.Read(C, 1); { Skip #10 }
  219.     Line[0] := Char(I);
  220.   end;
  221.   Inc(Count);
  222.   GetLine := Line;
  223.   LineInBuffer := False;
  224. end;
  225.  
  226. {----- UnGetLine(S) ----------------------------------------------------}
  227. {  Return given line into the stream.                                   }
  228. {-----------------------------------------------------------------------}
  229.  
  230. procedure UnGetLine(S: String);
  231. begin
  232.   Line := S;
  233.   LineInBuffer := True;
  234.   Dec(Count);
  235. end;
  236.  
  237. {========================= Error routines ==============================}
  238.  
  239. {----- PrntMsg(Text) ---------------------------------------------------}
  240. {  Used by Error and Warning to print the message.                      }
  241. {-----------------------------------------------------------------------}
  242.  
  243. procedure PrntMsg(Pref: String; var Text: String);
  244. var
  245.   S: String;
  246.   L: array[0..3] of Longint;
  247. begin
  248.   L[0] := Longint(@Pref);
  249.   L[1] := Longint(@HelpStrm^.FileName);
  250.   L[2] := Count;
  251.   L[3] := Longint(@Text);
  252.   if Count > 0 then FormatStr(S, '%s: %s(%d): %s'#13#10, L)
  253.   else FormatStr(S, '%s: %s %#3%s', L);
  254.   PrintStr(S);
  255. end;
  256.  
  257. {----- Error(Text) -----------------------------------------------------}
  258. {  Used to indicate an error.  Terminates the program                   }
  259. {-----------------------------------------------------------------------}
  260.  
  261. procedure Error(Text: String);
  262. begin
  263.   PrntMsg('Error', Text);
  264.   Halt(1);
  265. end;
  266.  
  267. {----- Warning(Text) ---------------------------------------------------}
  268. {  Used to indicate an warning.                                         }
  269. {-----------------------------------------------------------------------}
  270.  
  271. procedure Warning(Text: String);
  272. begin
  273.   PrntMsg('Warning', Text);
  274. end;
  275.  
  276. {====================== Topic Reference Management =====================}
  277.  
  278. type
  279.   PFixUp = ^TFixUp;
  280.   TFixUp = record
  281.     Pos: Longint;
  282.     Next: PFixUp;
  283.   end;
  284.  
  285.   PReference = ^TReference;
  286.   TReference = record
  287.     Topic: PString;
  288.     case Resolved: Boolean of
  289.       True:  (Value: Word);
  290.       False: (FixUpList: PFixUp);
  291.   end;
  292.  
  293.   PRefTable = ^TRefTable;
  294.   TRefTable = object(TSortedCollection)
  295.     function Compare(Key1, Key2: Pointer): Integer; virtual;
  296.     procedure FreeItem(Item: Pointer); virtual;
  297.     function GetReference(var Topic: String): PReference;
  298.     function KeyOf(Item: Pointer): Pointer; virtual;
  299.   end;
  300.  
  301. const
  302.   RefTable: PRefTable = nil;
  303.  
  304. procedure DisposeFixUps(P: PFixUp);
  305. var
  306.   Q: PFixUp;
  307. begin
  308.   while P <> nil do
  309.   begin
  310.     Q := P^.Next;
  311.     Dispose(P);
  312.     P := Q;
  313.   end;
  314. end;
  315.  
  316. {----- TRefTable -------------------------------------------------------}
  317. {  TRefTable is a collection of PReference's used as a symbol table.    }
  318. {  If the topic has not been seen, a forward reference is inserted and  }
  319. {  a fix-up list is started.  When the topic is seen all forward        }
  320. {  references are resolved.  If the topic has been seen already the     }
  321. {  value it has is used.                                                }
  322. {-----------------------------------------------------------------------}
  323.  
  324. function TRefTable.Compare(Key1, Key2: Pointer): Integer;
  325. var
  326.   K1,K2: String;
  327. begin
  328.   K1 := PString(Key1)^;
  329.   K2 := PString(Key2)^;
  330.   UpStr(K1); UpStr(K2);
  331.   if K1 > K2 then Compare := 1
  332.   else if K1 < K2 then Compare := -1
  333.   else Compare := 0;
  334. end;
  335.  
  336. procedure TRefTable.FreeItem(Item: Pointer);
  337. var
  338.   Ref: PReference absolute Item;
  339.   P, Q: PFixUp;
  340. begin
  341.   if not Ref^.Resolved then DisposeFixUps(Ref^.FixUpList);
  342.   DisposeStr(Ref^.Topic);
  343.   Dispose(Ref);
  344. end;
  345.  
  346. function TRefTable.GetReference(var Topic: String): PReference;
  347. var
  348.   Ref: PReference;
  349.   I: Integer;
  350. begin
  351.   if Search(@Topic, I) then
  352.     Ref := At(I)
  353.   else
  354.   begin
  355.     New(Ref);
  356.     Ref^.Topic := NewStr(Topic);
  357.     Ref^.Resolved := False;
  358.     Ref^.FixUpList := nil;
  359.     Insert(Ref);
  360.   end;
  361.   GetReference := Ref;
  362. end;
  363.  
  364. function TRefTable.KeyOf(Item: Pointer): Pointer;
  365. begin
  366.   KeyOf := PReference(Item)^.Topic;
  367. end;
  368.  
  369. {----- InitRefTable ----------------------------------------------------}
  370. {  Make sure the reference table is initialized.                        }
  371. {-----------------------------------------------------------------------}
  372.  
  373. procedure InitRefTable;
  374. begin
  375.   if RefTable = nil then
  376.     RefTable := New(PRefTable, Init(5,5));
  377. end;
  378.  
  379. {----- RecordReference -------------------------------------------------}
  380. {  Record a reference to a topic to the given stream.  This routine     }
  381. {  handles forward references.                                          }
  382. {-----------------------------------------------------------------------}
  383.  
  384. procedure RecordReference(var Topic: String; var S: TStream);
  385. var
  386.   I: Integer;
  387.   Ref: PReference;
  388.   FixUp: PFixUp;
  389. begin
  390.   InitRefTable;
  391.   Ref := RefTable^.GetReference(Topic);
  392.   if Ref^.Resolved then
  393.     S.Write(Ref^.Value, SizeOf(Ref^.Value))
  394.   else
  395.   begin
  396.     New(FixUp);
  397.     FixUp^.Pos := S.GetPos;
  398.     I := -1;
  399.     S.Write(I, SizeOf(I));
  400.     FixUp^.Next := Ref^.FixUpList;
  401.     Ref^.FixUpList := FixUp;
  402.   end;
  403. end;
  404.  
  405. {----- ResolveReference ------------------------------------------------}
  406. {  Resolve a reference to a topic to the given stream.  This routine    }
  407. {  handles forward references.                                          }
  408. {-----------------------------------------------------------------------}
  409.  
  410. procedure ResolveReference(var Topic: String; Value: Word; var S: TStream);
  411. var
  412.   I: Integer;
  413.   Ref: PReference;
  414.  
  415. procedure DoFixUps(P: PFixUp);
  416. var
  417.   Pos: LongInt;
  418. begin
  419.   Pos := S.GetPos;
  420.   while P <> nil do
  421.   begin
  422.     S.Seek(P^.Pos);
  423.     S.Write(Value, SizeOf(Value));
  424.     P := P^.Next;
  425.   end;
  426.   S.Seek(Pos);
  427. end;
  428.  
  429. begin
  430.   InitRefTable;
  431.   Ref := RefTable^.GetReference(Topic);
  432.   if Ref^.Resolved then
  433.     Error('Redefinition of ' + Ref^.Topic^)
  434.   else
  435.   begin
  436.     DoFixUps(Ref^.FixUpList);
  437.     DisposeFixUps(Ref^.FixUpList);
  438.     Ref^.Resolved := True;
  439.     Ref^.Value := Value;
  440.   end;
  441. end;
  442.  
  443. {======================== Help file parser =============================}
  444.  
  445. {----- GetWord ---------------------------------------------------------}
  446. {   Extract the next word from the given line at offset I.              }
  447. {-----------------------------------------------------------------------}
  448.  
  449. function GetWord(var Line: String; var I: Integer): String;
  450. var
  451.   J: Integer;
  452. const
  453.   WordChars = ['A'..'Z','a'..'z','0'..'9','_'];
  454.  
  455. procedure SkipWhite;
  456. begin
  457.   while (I <= Length(Line)) and (Line[I] = ' ') or (Line[I] = #8) do
  458.     Inc(I);
  459. end;
  460.  
  461. procedure SkipToNonWord;
  462. begin
  463.   while (I <= Length(Line)) and (Line[I] in WordChars) do Inc(I);
  464. end;
  465.  
  466. begin
  467.   SkipWhite;
  468.   J := I;
  469.   if J > Length(Line) then GetWord := ''
  470.   else
  471.   begin
  472.     Inc(I);
  473.     if Line[J] in WordChars then SkipToNonWord;
  474.     GetWord := Copy(Line, J, I - J);
  475.   end;
  476. end;
  477.  
  478. {----- TopicDefinition -------------------------------------------------}
  479. {  Extracts the next topic definition from the given line at I.         }
  480. {-----------------------------------------------------------------------}
  481.  
  482. type
  483.   PTopicDefinition = ^TTopicDefinition;
  484.   TTopicDefinition = object(TObject)
  485.     Topic: PString;
  486.     Value: Word;
  487.     Next: PTopicDefinition;
  488.     constructor Init(var ATopic: String; AValue: Word);
  489.     destructor Done; virtual;
  490.   end;
  491.  
  492. constructor TTopicDefinition.Init(var ATopic: String; AValue: Word);
  493. begin
  494.   Topic := NewStr(ATopic);
  495.   Value := AValue;
  496.   Next := nil;
  497. end;
  498.  
  499. destructor TTopicDefinition.Done;
  500. begin
  501.   DisposeStr(Topic);
  502.   if Next <> nil then Dispose(Next, Done);
  503. end;
  504.  
  505. function TopicDefinition(var Line: String; var I: Integer): PTopicDefinition;
  506. var
  507.   J,K: Integer;
  508.   TopicDef: PTopicDefinition;
  509.   Value: Word;
  510.   Topic, W: String;
  511. const
  512.   HelpCounter: Integer = 2; {1 is hcDragging}
  513. begin
  514.   Topic := GetWord(Line, I);
  515.   if Topic = '' then
  516.   begin
  517.     Error('Expected topic definition');
  518.     TopicDefinition := nil;
  519.   end
  520.   else
  521.   begin
  522.     J := I;
  523.     W := GetWord(Line, J);
  524.     if W = '=' then
  525.     begin
  526.       I := J;
  527.       W := GetWord(Line, I);
  528.       Val(W, J, K);
  529.       if K <> 0 then Error('Expected numeric')
  530.       else HelpCounter := J;
  531.     end else Inc(HelpCounter);
  532.     TopicDefinition := New(PTopicDefinition, Init(Topic, HelpCounter));
  533.   end;
  534. end;
  535.  
  536. {----- TopicDefinitionList----------------------------------------------}
  537. {  Extracts a list of topic definitions from the given line at I.       }
  538. {-----------------------------------------------------------------------}
  539.  
  540. function TopicDefinitionList(var Line: String; var I: Integer):
  541.   PTopicDefinition;
  542. var
  543.   J: Integer;
  544.   W: String;
  545.   TopicList, P: PTopicDefinition;
  546. begin
  547.   J := I;
  548.   TopicList := nil;
  549.   repeat
  550.     I := J;
  551.     P := TopicDefinition(Line, I);
  552.     if P = nil then
  553.     begin
  554.       if TopicList <> nil then Dispose(TopicList, Done);
  555.       TopicDefinitionList := nil;
  556.       Exit;
  557.     end;
  558.     P^.Next := TopicList;
  559.     TopicList := P;
  560.     J := I;
  561.     W := GetWord(Line, J);
  562.   until W <> ',';
  563.   TopicDefinitionList := TopicList;
  564. end;
  565.  
  566. {----- TopicHeader -----------------------------------------------------}
  567. {  Parse a the Topic header                                             }
  568. {-----------------------------------------------------------------------}
  569.  
  570. const
  571.   CommandChar = '.';
  572.  
  573. function TopicHeader(var Line: String): PTopicDefinition;
  574. var
  575.   I,J: Integer;
  576.   W: String;
  577.   TopicDef: PTopicDefinition;
  578.  
  579. begin
  580.   I := 1;
  581.   W := GetWord(Line, I);
  582.   if W <> CommandChar then
  583.   begin
  584.     TopicHeader := nil;
  585.     Exit;
  586.   end;
  587.   W := GetWord(Line, I);
  588.   UpStr(W);
  589.   if W = 'TOPIC' then
  590.     TopicHeader := TopicDefinitionList(Line, I)
  591.   else
  592.   begin
  593.     Error('TOPIC expected');
  594.     TopicHeader := nil;
  595.   end;
  596. end;
  597.  
  598. {----- ReadParagraph ---------------------------------------------------}
  599. { Read a paragraph of the screen.  Returns the paragraph or nil if the  }
  600. { paragraph was not found in the given stream.  Searches for cross      }
  601. { references and updates the XRefs variable.                            }
  602. {-----------------------------------------------------------------------}
  603. type
  604.   PCrossRefNode = ^TCrossRefNode;
  605.   TCrossRefNode = record
  606.     Topic: PString;
  607.     Offset: Integer;
  608.     Length: Byte;
  609.     Next: PCrossRefNode;
  610.   end;
  611. const
  612.   BufferSize = 1024;
  613. var
  614.   Buffer: array[0..BufferSize-1] of Byte;
  615.   Ofs: Integer;
  616.  
  617. function ReadParagraph(var TextFile: TStream; var XRefs: PCrossRefNode;
  618.  var Offset: Integer): PParagraph;
  619. var
  620.   Line: String;
  621.   State: (Undefined, Wrapping, NotWrapping);
  622.   P: PParagraph;
  623.  
  624. procedure AddToBuffer(var Line: String; Wrapping: Boolean); assembler;
  625. asm
  626.         PUSH    DS
  627.         CLD
  628.         PUSH    DS
  629.         POP     ES
  630.         MOV     DI,OFFSET Buffer
  631.         ADD     DI,Ofs
  632.         LDS     SI,Line
  633.         LODSB
  634.         XOR     AH,AH
  635.         ADD     ES:Ofs,AX
  636.         XCHG    AX,CX
  637.         REP     MOVSB
  638.         XOR     AL,AL
  639.         TEST    Wrapping,1      { Only add a #13, line terminator, if not }
  640.         JE      @@1             { currently wrapping the text. Otherwise  }
  641.         MOV     AL,' '-13       { add a ' '.                              }
  642. @@1:    ADD     AL,13
  643. @@2:    STOSB
  644.         POP     DS
  645.         INC     Ofs
  646. end;
  647.  
  648. procedure ScanForCrossRefs(var Line: String);
  649. var
  650.   I, BegPos, EndPos, Alias: Integer;
  651. const
  652.   BegXRef = '{';
  653.   EndXRef = '}';
  654.   AliasCh = ':';
  655.  
  656. procedure AddXRef(XRef: String; Offset: Integer; Length: Byte);
  657. var
  658.   P: PCrossRefNode;
  659.   PP: ^PCrossRefNode;
  660. begin
  661.   New(P);
  662.   P^.Topic := NewStr(XRef);
  663.   P^.Offset := Offset;
  664.   P^.Length := Length;
  665.   P^.Next := nil;
  666.   PP := @XRefs;
  667.   while PP^ <> nil do
  668.     PP := @PP^^.Next;
  669.   PP^ := P;
  670. end;
  671.  
  672. procedure ReplaceSpacesWithFF(var Line: String; Start: Integer;
  673.   Length: Byte);
  674. var
  675.   I: Integer;
  676. begin
  677.   for I := Start to Start + Length do
  678.     if Line[I] = ' ' then Line[I] := #$FF;
  679. end;
  680.  
  681. begin
  682.   I := 1;
  683.   repeat
  684.     BegPos := Pos(BegXRef, Copy(Line, I, 255));
  685.     if BegPos = 0 then I := 0
  686.     else
  687.     begin
  688.       Inc(I, BegPos);
  689.       if Line[I + 1] = BegXRef then
  690.       begin
  691.         Delete(Line, I, 1);
  692.         Inc(I);
  693.       end
  694.       else
  695.       begin
  696.         EndPos := Pos(EndXRef, Copy(Line, I, 255));
  697.         if EndPos = 0 then
  698.         begin
  699.           Error('Unterminated topic reference.');
  700.           Inc(I);
  701.         end
  702.         else
  703.         begin
  704.           Alias := Pos(AliasCh, Copy(Line, I, 255));
  705.           if (Alias = 0) or (Alias > EndPos) then
  706.             AddXRef(Copy(Line, I, EndPos - 1), Offset + Ofs + I - 1, EndPos - 1)
  707.           else
  708.           begin
  709.             AddXRef(Copy(Line, I + Alias, EndPos - Alias - 1),
  710.               Offset + Ofs + I - 1, Alias - 1);
  711.             Delete(Line, I + Alias - 1, EndPos - Alias);
  712.             EndPos := Alias;
  713.           end;
  714.           ReplaceSpacesWithFF(Line, I, EndPos-1);
  715.           Delete(Line, I + EndPos - 1, 1);
  716.           Delete(Line, I - 1, 1);
  717.           Inc(I, EndPos - 2);
  718.         end;
  719.       end;
  720.     end;
  721.   until I = 0;
  722. end;
  723.  
  724. function IsEndParagraph: Boolean;
  725. begin
  726.   IsEndParagraph :=
  727.      (Line = '') or
  728.      (Line[1] = CommandChar) or
  729.      (Line = #26) or
  730.      ((Line[1] =  ' ') and (State = Wrapping)) or
  731.      ((Line[1] <> ' ') and (State = NotWrapping));
  732. end;
  733.  
  734. begin
  735.   Ofs := 0;
  736.   ReadParagraph := nil;
  737.   State := Undefined;
  738.   Line := GetLine(TextFile);
  739.   while Line = '' do
  740.   begin
  741.     AddToBuffer(Line, State = Wrapping);
  742.     Line := GetLine(TextFile);
  743.   end;
  744.  
  745.   if IsEndParagraph then
  746.   begin
  747.     ReadParagraph := nil;
  748.     UnGetLine(Line);
  749.     Exit;
  750.   end;
  751.   while not IsEndParagraph do
  752.   begin
  753.     if State = Undefined then
  754.       if Line[1] = ' ' then State := NotWrapping
  755.       else State := Wrapping;
  756.     ScanForCrossRefs(Line);
  757.     AddToBuffer(Line, State = Wrapping);
  758.     Line := GetLine(TextFile);
  759.   end;
  760.   UnGetLine(Line);
  761.   GetMem(P, SizeOf(P^) + Ofs);
  762.   P^.Size := Ofs;
  763.   P^.Wrap := State = Wrapping;
  764.   Move(Buffer, P^.Text, Ofs);
  765.   Inc(Offset, Ofs);
  766.   ReadParagraph := P;
  767. end;
  768.  
  769. {----- ReadTopic -------------------------------------------------------}
  770. { Read a topic from the source file and write it to the help file       }
  771. {-----------------------------------------------------------------------}
  772. var
  773.   XRefs: PCrossRefNode;
  774.  
  775. procedure HandleCrossRefs(var S: TStream; XRefValue: Integer); far;
  776. var
  777.   P: PCrossRefNode;
  778. begin
  779.   P := XRefs;
  780.   while XRefValue > 1 do
  781.   begin
  782.     if P <> nil then P := P^.Next;
  783.     Dec(XRefValue);
  784.   end;
  785.   if P <> nil then RecordReference(P^.Topic^, S);
  786. end;
  787.  
  788. procedure ReadTopic(var TextFile: TStream; var HelpFile: THelpFile);
  789. var
  790.   Line: String;
  791.   P: PParagraph;
  792.   Topic: PHelpTopic;
  793.   TopicDef: PTopicDefinition;
  794.   I, J, Offset: Integer;
  795.   Ref: TCrossRef;
  796.   RefNode: PCrossRefNode;
  797.  
  798. procedure SkipBlankLines(var S: TStream);
  799. var
  800.   Line: String;
  801. begin
  802.   Line := '';
  803.   while Line = '' do
  804.     Line := GetLine(S);
  805.   UnGetLine(Line);
  806. end;
  807.  
  808. function XRefCount: Integer;
  809. var
  810.   I: Integer;
  811.   P: PCrossRefNode;
  812. begin
  813.   I := 0;
  814.   P := XRefs;
  815.   while P <> nil do
  816.   begin
  817.     Inc(I);
  818.     P := P^.Next;
  819.   end;
  820.   XRefCount := I;
  821. end;
  822.  
  823. procedure DisposeXRefs(P: PCrossRefNode);
  824. var
  825.   Q: PCrossRefNode;
  826. begin
  827.   while P <> nil do
  828.   begin
  829.     Q := P;
  830.     P := P^.Next;
  831.     Dispose(Q);
  832.   end;
  833. end;
  834.  
  835. procedure RecordTopicDefinitions(P: PTopicDefinition);
  836. begin
  837.   while P <> nil do
  838.   begin
  839.     ResolveReference(P^.Topic^, P^.Value, HelpFile.Stream^);
  840.     HelpFile.RecordPositionInIndex(P^.Value);
  841.     P := P^.Next;
  842.   end;
  843. end;
  844.  
  845. begin
  846.   { Get Screen command }
  847.   SkipBlankLines(TextFile);
  848.   Line := GetLine(TextFile);
  849.  
  850.   TopicDef := TopicHeader(Line);
  851.  
  852.   Topic := New(PHelpTopic, Init);
  853.  
  854.   { Read paragraphs }
  855.   XRefs := nil;
  856.   Offset := 0;
  857.   P := ReadParagraph(TextFile, XRefs, Offset);
  858.   while P <> nil do
  859.   begin
  860.     Topic^.AddParagraph(P);
  861.     P := ReadParagraph(TextFile, XRefs, Offset);
  862.   end;
  863.  
  864.   I := XRefCount;
  865.   Topic^.SetNumCrossRefs(I);
  866.   RefNode := XRefs;
  867.   for J := 1 to I do
  868.   begin
  869.     Ref.Offset := RefNode^.Offset;
  870.     Ref.Length := RefNode^.Length;
  871.     Ref.Ref := J;
  872.     Topic^.SetCrossRef(J, Ref);
  873.     RefNode := RefNode^.Next;
  874.   end;
  875.  
  876.   RecordTopicDefinitions(TopicDef);
  877.  
  878.   CrossRefHandler := HandleCrossRefs;
  879.   HelpFile.PutTopic(Topic);
  880.  
  881.   if Topic <> nil then Dispose(Topic, Done);
  882.   if TopicDef <> nil then Dispose(TopicDef, Done);
  883.   DisposeXRefs(XRefs);
  884.  
  885.   SkipBlankLines(TextFile);
  886. end;
  887.  
  888. {----- WriteSymbFile ---------------------------------------------------}
  889. { Write the .PAS file containing all screen titles as constants.        }
  890. {-----------------------------------------------------------------------}
  891.  
  892. procedure WriteSymbFile(var SymbFile: TProtectedStream);
  893. const
  894.   HeaderText1 =
  895.     'unit ';
  896.   HeaderText2 =
  897.     ';'#13#10 +
  898.     #13#10 +
  899.     'interface'#13#10 +
  900.     #13#10 +
  901.     'const'#13#10 +
  902.     #13#10;
  903.   FooterText =
  904.     #13#10 +
  905.     'implementation'#13#10 +
  906.     #13#10 +
  907.     'end.'#13#10;
  908.   Header1: array[1..Length(HeaderText1)] of Char = HeaderText1;
  909.   Header2: array[1..Length(HeaderText2)] of Char = HeaderText2;
  910.   Footer: array[1..Length(FooterText)] of Char = FooterText;
  911. var
  912.   I, Count: Integer;
  913.   Dir: DirStr;
  914.   Name: NameStr;
  915.   Ext: ExtStr;
  916.  
  917. procedure DoWriteSymbol(P: PReference); far;
  918. var
  919.   L: array[0..1] of LongInt;
  920.   Line: String;
  921. begin
  922.   if P^.Resolved then
  923.   begin
  924.     L[0] := Longint(P^.Topic);
  925.     L[1] := P^.Value;
  926.     FormatStr(Line, '  hc%-20s = %d;'#13#10, L);
  927.     SymbFile.Write(Line[1], Length(Line));
  928.   end
  929.   else Warning('Unresolved forward reference "' + P^.Topic^ + '"');
  930. end;
  931.  
  932. begin
  933.   SymbFile.Write(Header1, SizeOf(Header1));
  934.   FSplit(SymbFile.FileName, Dir, Name, Ext);
  935.   SymbFile.Write(Name[1], Length(Name));
  936.   SymbFile.Write(Header2, SizeOf(Header2));
  937.  
  938.   RefTable^.ForEach(@DoWriteSymbol);
  939.  
  940.   SymbFile.Write(Footer, SizeOf(Footer));
  941. end;
  942.  
  943. {----- ProcessText -----------------------------------------------------}
  944. { Compile the given stream, and output a help file.                     }
  945. {-----------------------------------------------------------------------}
  946.  
  947. procedure ProcessText(var TextFile, HelpFile, SymbFile: TProtectedStream);
  948. var
  949.   HelpRez: THelpFile;
  950. begin
  951.   HelpRez.Init(@HelpFile);
  952.   while TextFile.Status = stOk do
  953.     ReadTopic(TextFile, HelpRez);
  954.   WriteSymbFile(SymbFile);
  955.   HelpRez.Done;
  956. end;
  957.  
  958. {========================== Program Block ==========================}
  959.  
  960. var
  961.   TextName,
  962.   HelpName,
  963.   SymbName: PathStr;
  964.  
  965. procedure ExitClean; far;
  966. begin
  967.   { Print a message if an out of memory error encountered }
  968.   if ExitCode = 201 then
  969.   begin
  970.     Writeln('Error: Out of memory.');
  971.     ErrorAddr := nil;
  972.     ExitCode := 1;
  973.   end;
  974.  
  975.   { Clean up files }
  976.   TextStrm.Done;
  977.   SymbStrm.Done;
  978. end;
  979.  
  980. begin
  981.   { Banner messages }
  982.   PrintStr('Help Compiler  Version 1.0  Copyright (c) 1990 Borland International.'#13#10);
  983.   if ParamCount < 1 then
  984.   begin
  985.     PrintStr(
  986.       #13#10 +
  987.       '  Syntax:  TVHC <Help text>[.TXT] [<Help file>[.HLP] [<Symbol file>[.PAS]]'#13#10 +
  988.       #13#10+
  989.       '     Help text   = Help file source'#13#10 +
  990.       '     Help file   = Compiled help file'#13#10 +
  991.       '     Symbol file = A Pascal file containing all the screen names as CONST''s'#13#10);
  992.     Halt(0);
  993.   end;
  994.  
  995.   { Calculate file names }
  996.   TextName := ReplaceExt(ParamStr(1), '.TXT', False);
  997.   if not FExists(TextName) then
  998.     Error('File ' + TextName + ' not found.');
  999.   if ParamCount >= 2 then
  1000.     HelpName := ReplaceExt(ParamStr(2), '.HLP', False) else
  1001.     HelpName := ReplaceExt(TextName, '.HLP',  True);
  1002.   if ParamCount >= 3 then
  1003.     SymbName := ReplaceExt(ParamStr(3), '.PAS', False) else
  1004.     SymbName := ReplaceExt(HelpName, '.PAS', True);
  1005.  
  1006.   ExitProc := @ExitClean;
  1007.  
  1008.   RegisterHelpFile;
  1009.  
  1010.   TextStrm.Init(TextName, stOpenRead, 1024);
  1011.   SymbStrm.Init(SymbName, stCreate,   1024);
  1012.   HelpStrm := New(PProtectedStream, Init(HelpName, stCreate, 1024));
  1013.   ProcessText(TextStrm, HelpStrm^, SymbStrm);
  1014. end.
  1015.