Subversion Repositories oidplus

Rev

Rev 747 | Rev 749 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. program OIDPLUS;
  2.  
  3. (************************************************)
  4. (* OIDPLUS.PAS                                  *)
  5. (* Author:   Daniel Marschall                   *)
  6. (* Revision: 2022-02-19                         *)
  7. (* License:  Apache 2.0                         *)
  8. (* This file contains:                          *)
  9. (* - "OIDplus for DOS" program                  *)
  10. (************************************************)
  11.  
  12. (* IMPORTANT:                                                  *)
  13. (* When you compile this code with Turbo Pascal 7.01,          *)
  14. (* it won't run on fast PCs (Runtime Error 200).               *)
  15. (* The built EXE file needs to be patched.                     *)
  16. (* The program "PatchCRT" by Kennedy Software                  *)
  17. (* WON'T work because it somehow breaks our "_Pause" function. *)
  18. (* Instead, use the tool "TPPATCH" by Andreas Bauer.           *)
  19.  
  20. uses
  21.   Dos, Crt, Drivers, StrList, VtsFuncs, VtsCui, OidFile, OidUtils,
  22.   Weid;
  23.  
  24. const
  25.   VERSIONINFO            = 'Revision: 2022-02-19';
  26.   DEFAULT_STATUSBAR      = '(C)2020-2022 ViaThinkSoft. Licensed under the terms of the Apache 2.0 license.';
  27.   TITLEBAR_LEFT_TEXT     = 'OIDplus';
  28.   DISKIO_SOUND_DEBUGGING = false;
  29.   DISKIO_SOUND_DELAY     = 500;
  30.   ASNEDIT_LINES          = 10;
  31.   DESCEDIT_LINES         = 10;
  32.   DESCEDIT_PADDING       = 3;
  33.   ACTIONMENU_SIZE        = 5;
  34.   MAINMENU_WIDTH         = 15;
  35.   MAINMENU_HEIGHT        = 3;
  36.   MAINMENU_ALLOW_ESC     = false;
  37.   TREEVIEW_INDENT        = 0;
  38.   TREEVIEW_INCLUDE_DESC  = true;
  39.   TREEVIEW_WIDTH         = 80;
  40.   OID_EXTENSION          = '.OID';
  41.   TREEVIEW_FILENAME      = 'OIDTREE.TXT';
  42.  
  43. procedure _Pause;
  44. begin
  45.   DrawStatusBar('Press any key to continue');
  46.   CursorOn;
  47.   ReadKey;
  48.   CursorOff;
  49.   DrawStatusBar(DEFAULT_STATUSBAR);
  50. end;
  51.  
  52. function _WriteOidFile(filename: string; oid: POid; ShowErrorMessage: boolean): boolean;
  53. var
  54.   res: boolean;
  55. begin
  56.   DrawStatusBar('Write file ' + filename + '...');
  57.   res := WriteOidFile(filename, oid);
  58.   if DISKIO_SOUND_DEBUGGING then
  59.   begin
  60.     Sound(70);
  61.     Delay(DISKIO_SOUND_DELAY - 10);
  62.     NoSound;
  63.     Delay(10);
  64.   end;
  65.   DrawStatusBar(DEFAULT_STATUSBAR);
  66.  
  67.   _WriteOidFile := res;
  68.  
  69.   if ShowErrorMessage and not res then
  70.   begin
  71.     ShowMessage('Cannot write to file ' + filename, 'ERROR', true);
  72.     _Pause;
  73.   end;
  74. end;
  75.  
  76. function _ReadOidFile(filename: string; oid: POid; ShowErrorMessage: boolean): boolean;
  77. var
  78.   res: boolean;
  79. begin
  80.   DrawStatusBar('Read file ' + filename + '...');
  81.   res := ReadOidFile(filename, oid);
  82.   if DISKIO_SOUND_DEBUGGING then
  83.   begin
  84.     Sound(50);
  85.     Delay(DISKIO_SOUND_DELAY - 10);
  86.     NoSound;
  87.     Delay(10);
  88.   end;
  89.   DrawStatusBar(DEFAULT_STATUSBAR);
  90.  
  91.   _ReadOidFile := res;
  92.  
  93.   if ShowErrorMessage and not res then
  94.   begin
  95.     ShowMessage('Cannot read file ' + filename, 'ERROR', true);
  96.     _Pause;
  97.   end;
  98. end;
  99.  
  100. function _ShowASNIds(childOID: POID): string;
  101. var
  102.   j, jmax: integer;
  103.   sTmp: string;
  104. begin
  105.   sTmp := '';
  106.   jmax := ListCount(childOID^.ASNIds)-1;
  107.   for j := 0 to jmax do
  108.   begin
  109.     if j = 0 then sTmp := sTmp + ' (';
  110.     sTmp := sTmp + ListGetElement(childOID^.ASNIds, j);
  111.     if j = jmax then
  112.       sTmp := sTmp + ')'
  113.     else
  114.       sTmp := sTmp + ', ';
  115.   end;
  116.   _ShowASNIds := sTmp;
  117. end;
  118.  
  119. function AsnAlreadyExisting(oid: POID; asnid: string): boolean;
  120. begin
  121.   AsnAlreadyExisting := ListContains(oid^.AsnIds, asnid);
  122. end;
  123.  
  124. function AsnEditor(oid: POID): boolean;
  125. var
  126.   asnList: PStringList;
  127.   i: integer;
  128.   x, y, w, h: integer;
  129.   res: integer;
  130.   sInput: string;
  131.   menuIdNew, menuIdSave, menuIdExit: integer;
  132. begin
  133.   AsnEditor := false;
  134.  
  135.   repeat
  136.     CreateList(asnList);
  137.  
  138.     for i := 0 to ListCount(oid^.ASNIds)-1 do
  139.     begin
  140.       ListAppend(asnList, ListGetElement(oid^.ASNIDs, i));
  141.     end;
  142.     menuIdNew  := ListAppend(asnList, '<NEW>');
  143.     menuIdSave := ListAppend(asnList, '<SAVE>');
  144.     menuIdExit := ListAppend(asnList, '<CANCEL>');
  145.  
  146.     DrawStatusBar(DEFAULT_STATUSBAR);
  147.     x := SINGLE_LINE_BOX_PADDING;
  148.     y := ScreenHeight div 2 - ASNEDIT_LINES div 2;
  149.     w := ScreenWidth - (SINGLE_LINE_BOX_PADDING-1)*2;
  150.     h := ASNEDIT_LINES;
  151.     res := DrawSelectionList(x, y, w, h,
  152.                              asnList, true,
  153.                              'EDIT ASN.1 IDENTIFIERS',
  154.                              2);
  155.     FreeList(asnList);
  156.  
  157.     (* Change double-border to thin-border *)
  158.     DrawThinBorder(x-1, y-1, w+2, h+2);
  159.     GoToXY(x+1, y-1);
  160.     Write('EDIT ASN.1 IDENTIFIERS');
  161.  
  162.     if res = -1 then
  163.     begin
  164.       exit;
  165.     end
  166.     else if res = menuIdNew then
  167.     begin
  168.       (* "NEW" item was selected *)
  169.       sInput := '';
  170.       CursorOn;
  171.       repeat
  172.         if QueryVal(sInput,
  173.                     SINGLE_LINE_BOX_PADDING_INNER,
  174.                     ScreenHeight div 2,
  175.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  176.                     1,
  177.                     'ADD SINGLE ASN.1 ID',
  178.                     2) then
  179.         begin
  180.           if sInput = '' then continue;
  181.           if not ASN1IDValid(sInput) then
  182.           begin
  183.             ShowMessage('Invalid ASN1.ID! (Require -, a..z, A..Z, 0..9, begin with a-z)', 'ERROR', true);
  184.             _Pause;
  185.           end
  186.           else if AsnAlreadyExisting(oid, sInput) then
  187.           begin
  188.             ShowMessage('ASN.1 identifier is already existing on this arc', 'ERROR', true);
  189.             _Pause;
  190.           end
  191.           else
  192.           begin
  193.             ListAppend(oid^.ASNIDs, sInput);
  194.             break;
  195.           end;
  196.         end
  197.         else break;
  198.       until false;
  199.       CursorOff;
  200.     end
  201.     else if res = menuIdSave then
  202.     begin
  203.       (* "SAVE" item was selected *)
  204.       AsnEditor := true;
  205.       Exit;
  206.     end
  207.     else if res = menuIdExit then
  208.     begin
  209.       (* "CANCEL" item was selected *)
  210.       AsnEditor := false;
  211.       Exit;
  212.     end
  213.     else
  214.     begin
  215.       DrawStatusBar('Note: Remove the text to delete the ASN.1 identifier');
  216.       sInput := ListGetElement(oid^.ASNIDs, res);
  217.           CursorOn;
  218.       repeat
  219.         if QueryVal(sInput,
  220.                     SINGLE_LINE_BOX_PADDING_INNER,
  221.                     ScreenHeight div 2,
  222.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  223.                     1,
  224.                     'EDIT SINGLE ASN.1 ID',
  225.                     2) then
  226.         begin
  227.           if sInput = '' then
  228.           begin
  229.             (* Empty input = Delete ASN.1 ID *)
  230.             ListDeleteElementByIndex(oid^.ASNIDs, res);
  231.             break;
  232.           end
  233.           else if not ASN1IDValid(sInput) then
  234.           begin
  235.             ShowMessage('Invalid ASN1.ID! (Require -, a..z, A..Z, 0..9, begin with a-z)', 'ERROR', true);
  236.             _Pause;
  237.           end
  238.           else if AsnAlreadyExisting(oid, sInput) and
  239.               not (ListGetElement(oid^.ASNIDs, res) = sInput) then
  240.           begin
  241.             ShowMessage('ASN.1 identifier is already existing on this arc', 'ERROR', true);
  242.             _Pause;
  243.           end
  244.           else
  245.           begin
  246.             ListSetElement(oid^.ASNIDs, res, sInput);
  247.             break;
  248.           end;
  249.         end
  250.         else break;
  251.       until false;
  252.           CursorOff;
  253.     end;
  254.   until false;
  255. end;
  256.  
  257. function UnicodeLabelAlreadyExisting(oid: POID; unicodeLabel: string): boolean;
  258. begin
  259.   UnicodeLabelAlreadyExisting := ListContains(oid^.UnicodeLabels, unicodeLabel);
  260. end;
  261.  
  262. function IriEditor(oid: POID): boolean;
  263. var
  264.   iriList: PStringList;
  265.   i: integer;
  266.   x, y, w, h: integer;
  267.   res: integer;
  268.   sInput: string;
  269.   menuIdNew, menuIdSave, menuIdExit: integer;
  270. begin
  271.   IriEditor := false;
  272.  
  273.   repeat
  274.     CreateList(iriList);
  275.  
  276.     for i := 0 to ListCount(oid^.UnicodeLabels)-1 do
  277.     begin
  278.       ListAppend(iriList, ListGetElement(oid^.UnicodeLabels, i));
  279.     end;
  280.     menuIdNew  := ListAppend(iriList, '<NEW>');
  281.     menuIdSave := ListAppend(iriList, '<SAVE>');
  282.     menuIdExit := ListAppend(iriList, '<CANCEL>');
  283.  
  284.     DrawStatusBar(DEFAULT_STATUSBAR);
  285.     x := SINGLE_LINE_BOX_PADDING;
  286.     y := ScreenHeight div 2 - ASNEDIT_LINES div 2;
  287.     w := ScreenWidth - (SINGLE_LINE_BOX_PADDING-1)*2;
  288.     h := ASNEDIT_LINES;
  289.     res := DrawSelectionList(x, y, w, h,
  290.                              iriList, true,
  291.                              'EDIT UNICODE LABELS',
  292.                              2);
  293.     FreeList(iriList);
  294.  
  295.     (* Change double-border to thin-border *)
  296.     DrawThinBorder(x-1, y-1, w+2, h+2);
  297.     GoToXY(x+1, y-1);
  298.     Write('EDIT UNICODE LABELS');
  299.  
  300.     if res = -1 then
  301.     begin
  302.       exit;
  303.     end
  304.     else if res = menuIdNew then
  305.     begin
  306.       (* "NEW" item was selected *)
  307.       sInput := '';
  308.       CursorOn;
  309.       repeat
  310.         if QueryVal(sInput,
  311.                     SINGLE_LINE_BOX_PADDING_INNER,
  312.                     ScreenHeight div 2,
  313.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  314.                     1,
  315.                     'ADD SINGLE UNICODE LABEL',
  316.                     2) then
  317.         begin
  318.           if sInput = '' then continue;
  319.           if not UnicodeLabelValid(sInput) then
  320.           begin
  321.             ShowMessage('Invalid Unicode Label!', 'ERROR', true);
  322.             _Pause;
  323.           end
  324.           else if UnicodeLabelAlreadyExisting(oid, sInput) then
  325.           begin
  326.             ShowMessage('Unicode Label is already existing on this arc', 'ERROR', true);
  327.             _Pause;
  328.           end
  329.           else
  330.           begin
  331.             ListAppend(oid^.UnicodeLabels, sInput);
  332.             break;
  333.           end;
  334.         end
  335.         else break;
  336.       until false;
  337.       CursorOff;
  338.     end
  339.     else if res = menuIdSave then
  340.     begin
  341.       (* "SAVE" item was selected *)
  342.       IriEditor := true;
  343.       Exit;
  344.     end
  345.     else if res = menuIdExit then
  346.     begin
  347.       (* "CANCEL" item was selected *)
  348.       IriEditor := false;
  349.       Exit;
  350.     end
  351.     else
  352.     begin
  353.       DrawStatusBar('Note: Remove the text to delete the Unicode Label');
  354.       sInput := ListGetElement(oid^.UnicodeLabels, res);
  355.           CursorOn;
  356.       repeat
  357.         if QueryVal(sInput,
  358.                     SINGLE_LINE_BOX_PADDING_INNER,
  359.                     ScreenHeight div 2,
  360.                     ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  361.                     1,
  362.                     'EDIT SINGLE UNICODE LABEL',
  363.                     2) then
  364.         begin
  365.           if sInput = '' then
  366.           begin
  367.             (* Empty input = Delete Unicode label *)
  368.             ListDeleteElementByIndex(oid^.UnicodeLabels, res);
  369.             break;
  370.           end
  371.           else if not UnicodeLabelValid(sInput) then
  372.           begin
  373.             ShowMessage('Invalid Unicode Label!', 'ERROR', true);
  374.             _Pause;
  375.           end
  376.           else if UnicodeLabelAlreadyExisting(oid, sInput) and
  377.               not (ListGetElement(oid^.UnicodeLabels, res) = sInput) then
  378.           begin
  379.             ShowMessage('Unicode Label is already existing on this arc', 'ERROR', true);
  380.             _Pause;
  381.           end
  382.           else
  383.           begin
  384.             ListSetElement(oid^.UnicodeLabels, res, sInput);
  385.             break;
  386.           end;
  387.         end
  388.         else break;
  389.       until false;
  390.           CursorOff;
  391.     end;
  392.   until false;
  393. end;
  394.  
  395. function DescEditor(oid: POID): boolean;
  396. var
  397.   sInput: string;
  398. begin
  399.   DescEditor := false;
  400.  
  401.   DrawStatusBar('Note: Press Ctrl+Return for a line-break.');
  402.   sInput := oid^.description;
  403.   CursorOn;
  404.   if QueryVal(sInput,
  405.               DESCEDIT_PADDING,
  406.               ScreenHeight div 2 - DESCEDIT_LINES div 2,
  407.               ScreenWidth - (DESCEDIT_PADDING-1)*2,
  408.               DESCEDIT_LINES,
  409.               'EDIT DESCRIPTION',
  410.               2) then
  411.   begin
  412.     oid^.description := sInput;
  413.     DescEditor := true; (* request caller to save <oid> *)
  414.   end;
  415.   CursorOff;
  416. end;
  417.  
  418. function NextPossibleFileID: string;
  419. var
  420.   DirInfo: SearchRec;
  421.   list: PStringList;
  422.   iId: LongInt;
  423.   sId: string;
  424. begin
  425.   (* Put all found files into a list *)
  426.   CreateList(list);
  427.   FindFirst(RepeatStr('?',8)+OID_EXTENSION, Archive, DirInfo);
  428.   while DosError = 0 do
  429.   begin
  430.     sId := Copy(DirInfo.Name, 1, 8);
  431.     ListAppend(list, sId);
  432.     FindNext(DirInfo);
  433.   end;
  434.  
  435.   (* Search for the first non existing item in the list *)
  436.   sId := '';
  437.   for iId := 0 to 99999999 do
  438.   begin
  439.     sId := ZeroPad(iId, 8);
  440.     if not ListContains(list, sId) then break;
  441.   end;
  442.   NextPossibleFileId := sId;
  443.   FreeList(list);
  444. end;
  445.  
  446. function NumIdAlreadyExisting(parentOID: POID; sInput: string): boolean;
  447. var
  448.   searchDotNotation: string;
  449.   sTmp: string;
  450.   i: integer;
  451. begin
  452.   if parentOID^.DotNotation = '' then
  453.     searchDotNotation := sInput
  454.   else
  455.     searchDotNotation := parentOID^.DotNotation + '.' + sInput;
  456.   for i := 0 to ListCount(parentOID^.SubIds)-1 do
  457.   begin
  458.     sTmp := ListGetElement(parentOID^.SubIds, i);
  459.     if DotNotationPart(sTmp) = searchDotNotation then
  460.     begin
  461.       NumIdAlreadyExisting := true;
  462.       exit;
  463.     end;
  464.   end;
  465.   NumIdAlreadyExisting := false;
  466. end;
  467.  
  468. function NumIdEditor(oid: POID; parentOID: POID): boolean;
  469. var
  470.   sInput: string;
  471. begin
  472.   NumIdEditor := false;
  473.   sInput := '';
  474.  
  475.   CursorOn;
  476.   repeat
  477.     if QueryVal(sInput,
  478.                 SINGLE_LINE_BOX_PADDING_INNER,
  479.                 ScreenHeight div 2,
  480.                 ScreenWidth - (SINGLE_LINE_BOX_PADDING_INNER-1)*2,
  481.                 1,
  482.                 'ENTER NUMERIC ID',
  483.                 2) then
  484.     begin
  485.       if sInput = '' then continue;
  486.       if not IsPositiveIntegerOrZero(sInput) then
  487.       begin
  488.         ShowMessage('Invalid numeric ID (must be a positive integer)', 'ERROR', true);
  489.         _Pause;
  490.       end
  491.       else if (parentOID^.DotNotation='') and (StrToInt(sInput) > 2) then
  492.       begin
  493.         ShowMessage('Invalid numeric ID (root arc can only be 0, 1, or 2)', 'ERROR', true);
  494.         _Pause;
  495.       end
  496.       else if ((parentOID^.DotNotation='0') or (parentOID^.DotNotation='1')) and (StrToInt(sInput) > 39) then
  497.       begin
  498.         ShowMessage('Invalid numeric ID (root 0 and 1 must have sub-arc of 0..39)', 'ERROR', true);
  499.         _Pause;
  500.       end
  501.       else if NumIdAlreadyExisting(parentOID, sInput) then
  502.       begin
  503.         ShowMessage('This numeric ID is already used in this arc', 'ERROR', true);
  504.         _Pause;
  505.       end
  506.       else
  507.       begin
  508.         if parentOID^.DotNotation = '' then
  509.           oid^.DotNotation := sInput
  510.         else
  511.           oid^.DotNotation := parentOID^.DotNotation + '.' + sInput;
  512.         NumIdEditor := true; (* request caller to save <oid> *)
  513.         Break;
  514.       end;
  515.     end
  516.     else
  517.     begin
  518.       Break;
  519.     end;
  520.   until false;
  521.   CursorOff;
  522. end;
  523.  
  524. function NewOidEditor(oid: POID): boolean;
  525. var
  526.   newfilename: string;
  527.   newOID: POID;
  528. begin
  529.   NewOidEditor := false;
  530.  
  531.   CreateOidDef(newOID);
  532.   newOID^.FileId := NextPossibleFileID;
  533.   newOID^.ParentFileId := oid^.FileId;
  534.   newOID^.ParentDotNotation := oid^.DotNotation;
  535.   if NumIdEditor(newOID, oid) and
  536.      AsnEditor(newOID) and
  537.      IriEditor(newOID) and
  538.      DescEditor(newOID) then
  539.   begin
  540.     newfilename := newOID^.FileId + OID_EXTENSION;
  541.     if _WriteOidFile(newfilename, newOID, true) then
  542.     begin
  543.       (* Add link to original file and enable the saving of it *)
  544.       ListAppend(oid^.SubIds, newOID^.FileId + newOID^.DotNotation);
  545.       NewOidEditor := true; (* request caller to save <oid> *)
  546.     end;
  547.   end;
  548.   FreeOidDef(newOID);
  549. end;
  550.  
  551. procedure DeleteChildrenRecursive(oid: POID);
  552. var
  553.   i: integer;
  554.   childOID: POID;
  555.   filenameChild: string;
  556. begin
  557.   for i := 0 to ListCount(oid^.SubIds)-1 do
  558.   begin
  559.     filenameChild := FileIdPart(ListGetElement(oid^.SubIds, i)) + OID_EXTENSION;
  560.     if FileExists(filenameChild) then
  561.     begin
  562.       CreateOidDef(childOID);
  563.       if _ReadOidFile(filenameChild, childOID, false) and
  564.          (childOID^.ParentFileId = oid^.FileId) and
  565.          (childOID^.ParentDotNotation = oid^.DotNotation) then
  566.       begin
  567.         DeleteChildrenRecursive(childOID);
  568.       end;
  569.       FreeOidDef(childOID);
  570.       DeleteFile(filenameChild);
  571.     end;
  572.   end;
  573.   ListClear(oid^.SubIds);
  574. end;
  575.  
  576. procedure DeleteOidRecursive(selfOID: POID);
  577. var
  578.   i: integer;
  579.   parentOID: POID;
  580.   filenameSelf, filenameParent: string;
  581. begin
  582.   (* Remove all children and their files recursively *)
  583.   DeleteChildrenRecursive(selfOID);
  584.  
  585.   (* Remove forward reference in parent OID *)
  586.   (* (this is the most important part)      *)
  587.   filenameParent := selfOID^.ParentFileId + OID_EXTENSION;
  588.   if FileExists(filenameParent) then
  589.   begin
  590.     CreateOidDef(parentOID);
  591.     if _ReadOidFile(filenameParent, parentOID, true) then
  592.     begin
  593.       if ListDeleteElementByValue(parentOID^.SubIds, selfOID^.FileId + selfOID^.DotNotation) then
  594.       begin
  595.         _WriteOidFile(filenameParent, parentOID, true);
  596.       end;
  597.     end;
  598.     FreeOidDef(parentOID);
  599.   end;
  600.  
  601.   (* Delete own file *)
  602.   filenameSelf := selfOID^.FileId + OID_EXTENSION;
  603.   if FileExists(filenameSelf) then
  604.   begin
  605.     DeleteFile(filenameSelf);
  606.   end;
  607. end;
  608.  
  609. function _DeleteConfirmation: boolean;
  610. var
  611.   sc: Char;
  612. begin
  613.   repeat
  614.     ShowMessage('Are you sure you want to delete this OID? (Y/N)', 'DELETE OID', true);
  615.     DrawStatusBar('Y=Yes, N=No');
  616.  
  617.     CursorOn;
  618.     sc := ReadKey;
  619.     CursorOff;
  620.     if sc = #0 then
  621.     begin
  622.       (* Extended key. Nothing we care about. *)
  623.       ReadKey;
  624.       continue;
  625.     end;
  626.  
  627.     if UpCase(sc) = 'Y' then
  628.     begin
  629.       _DeleteConfirmation := true;
  630.       break;
  631.     end
  632.     else if UpCase(sc) = 'N' then
  633.     begin
  634.       _DeleteConfirmation := false;
  635.       break;
  636.     end;
  637.   until false;
  638. end;
  639.  
  640. procedure _DrawOidTitleBar(filename: string; oid: POID);
  641. begin
  642.   if oid^.DotNotation = '' then
  643.     DrawTitleBar('OID ROOT', TITLEBAR_LEFT_TEXT, filename)
  644.   else
  645.     DrawTitleBar('OID ' + oid^.DotNotation, TITLEBAR_LEFT_TEXT, filename);
  646. end;
  647.  
  648. function DotNotation(oid: POid): string;
  649. var
  650.   res: string;
  651. begin
  652.   res := oid^.DotNotation;
  653.   if res = '' then res := '.'; (* root *)
  654.   DotNotation := res;
  655. end;
  656.  
  657. function OidLastArc(oid: POid): string;
  658. var
  659.   s: string;
  660.   p: integer;
  661. begin
  662.   s := oid^.DotNotation;
  663.  
  664.   while true do
  665.   begin
  666.     p := Pos('.', s);
  667.     if p = 0 then break;
  668.     Delete(s, 1, p);
  669.   end;
  670.  
  671.   OidLastArc := s;
  672. end;
  673.  
  674. function AsnNotation(oid: POid): string;
  675. var
  676.   prevOid, curOid: POid;
  677.   res: string;
  678. begin
  679.   CreateOidDef(curOid);
  680.   prevOid := oid;
  681.   res := '';
  682.  
  683.   while true do
  684.   begin
  685.     (* Note: BackRef is not checked yet! Infinite loop is possible! (TODO) *)
  686.     ReadOidFile(prevOid^.ParentFileId + '.OID', curOid);
  687.     if curOid^.ParentFileId = '' then break;
  688.     if curOid^.ParentFileId = curOid^.FileId then break;
  689.     if ListCount(curOid^.AsnIds) > 0 then
  690.       res := ListGetElement(curOid^.AsnIds, 0) + '('+OidLastArc(curOid)+') ' + res
  691.     else
  692.       res := OidLastArc(curOid) + ' ' + res;
  693.     prevOid := curOid;
  694.   end;
  695.   FreeOidDef(curOid);
  696.   if ListCount(oid^.AsnIds) > 0 then
  697.     res := res + ListGetElement(oid^.AsnIds, 0) + '('+OidLastArc(oid)+')'
  698.   else
  699.     res := res + OidLastArc(oid);
  700.   if res = '' then
  701.     AsnNotation := ''
  702.   else
  703.     AsnNotation := '{ ' + res + ' }';
  704. end;
  705.  
  706. function IriNotation(oid: POid): string;
  707. var
  708.   prevOid, curOid: POid;
  709.   res: string;
  710. begin
  711.   CreateOidDef(curOid);
  712.   prevOid := oid;
  713.   res := '';
  714.  
  715.   while true do
  716.   begin
  717.     (* Note: BackRef is not checked yet! Infinite loop is possible! (TODO) *)
  718.     ReadOidFile(prevOid^.ParentFileId + '.OID', curOid);
  719.     if curOid^.ParentFileId = '' then break;
  720.     if curOid^.ParentFileId = curOid^.FileId then break;
  721.     if ListCount(curOid^.UnicodeLabels) > 0 then
  722.       res := ListGetElement(curOid^.UnicodeLabels, 0) + '/' + res
  723.     else
  724.       res := OidLastArc(curOid) + '/' + res;
  725.     prevOid := curOid;
  726.   end;
  727.   FreeOidDef(curOid);
  728.   if ListCount(oid^.UnicodeLabels) > 0 then
  729.     res := res + ListGetElement(oid^.UnicodeLabels, 0)
  730.   else
  731.     res := res + OidLastArc(oid);
  732.   IriNotation := '/' + res;
  733. end;
  734.  
  735. function WeidNotation(oid: POid): string;
  736. begin
  737.   WeidNotation := OidToWeid(oid^.DotNotation);
  738. end;
  739.  
  740. procedure DisplayOIDFile(filename: string);
  741. var
  742.   isRoot: boolean;
  743.   oid, tmpOID: POID;
  744.   i: integer;
  745.   sTmp, subfile: string;
  746.   subsel, subfiles: PStringList;
  747.   subselres: integer;
  748.   exitRequest: boolean;
  749.   menuIdExit, menuIdAsnEdit, menuIdIriEdit, menuIdDescEdit, menuIdAdd, menuIdDelete: integer;
  750.   menuX, menuY: integer;
  751. begin
  752.   exitRequest := false;
  753.   repeat
  754.     CreateOidDef(oid);
  755.  
  756.     if not _ReadOidFile(filename, oid, true) then
  757.     begin
  758.       FreeOidDef(oid);
  759.       exit;
  760.     end;
  761.  
  762.     (* Print OID information *)
  763.  
  764.     ClrScr;
  765.     _DrawOidTitleBar(filename, oid);
  766.     DrawStatusBar(DEFAULT_STATUSBAR);
  767.     GotoXY(1,2);
  768.  
  769.     (*if oid^.DotNotation <> '' then*)
  770.     begin
  771.       Write('Dot notation:   ');
  772.       WriteLnKeepX(DotNotation(oid));
  773.       Write('IRI notation:   ');
  774.       WriteLnKeepX(IriNotation(oid));
  775.       Write('ASN.1 notation: ');
  776.       WriteLnKeepX(AsnNotation(oid));
  777.       Write('WEID notation:  ');
  778.       WriteLnKeepX(WeidNotation(oid));
  779.       WriteLn('');
  780.     end;
  781.  
  782.     if Trim(oid^.Description) <> '' then
  783.     begin
  784.       (* WriteLn('Description:'); *)
  785.       WriteLn(oid^.Description);
  786.       WriteLn('');
  787.     end
  788.     else
  789.     begin
  790.       WriteLn('(No description has been added to this OID.)');
  791.       WriteLn('');
  792.     end;
  793.  
  794.     (* Now prepare the menu entries *)
  795.  
  796.     CreateList(subsel);   (* Contains the human-readable OID name *)
  797.     CreateList(subfiles); (* Contains the file name               *)
  798.  
  799.     if oid^.ParentFileId = '' then
  800.     begin
  801.       isRoot := true;
  802.     end
  803.     else
  804.     begin
  805.       isRoot := oid^.ParentDotNotation = oid^.DotNotation;
  806.     end;
  807.  
  808.     if (oid^.ParentFileId <> '') and not isRoot then
  809.     begin
  810.       subfile := oid^.ParentFileId + OID_EXTENSION;
  811.       if FileExists(subfile) then
  812.       begin
  813.         CreateOidDef(tmpOID);
  814.         if not _ReadOidFile(subfile, tmpOID, true) then
  815.         begin
  816.           ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + ' (READ ERROR)');
  817.           ListAppend(subfiles, 'ERROR: '+subfile+' Read error or file invalid');
  818.         end
  819.         else
  820.         begin
  821.           ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + _ShowASNIds(tmpOID));
  822.           ListAppend(subfiles, subfile);
  823.         end;
  824.         FreeOidDef(tmpOID);
  825.       end
  826.       else
  827.       begin
  828.         ListAppend(subsel, 'Go to parent ' + oid^.ParentDotNotation + ' (FILE NOT FOUND)');
  829.         ListAppend(subfiles, 'ERROR: File '+subfile+' was not found');
  830.       end;
  831.     end;
  832.  
  833.     if isRoot then
  834.     begin
  835.       menuIdExit := ListAppend(subsel, 'Back to main menu');
  836.       ListAppend(subfiles, '');
  837.     end
  838.     else menuIdExit := -99;
  839.  
  840.     for i := 0 to ListCount(oid^.SubIds)-1 do
  841.     begin
  842.       sTmp := ListGetElement(oid^.SubIds, i);
  843.       subfile := FileIdPart(sTmp) + OID_EXTENSION;
  844.       if FileExists(subfile) then
  845.       begin
  846.         CreateOidDef(tmpOID);
  847.         if not _ReadOidFile(subfile, tmpOID, true) then
  848.         begin
  849.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (READ ERROR)');
  850.           ListAppend(subfiles, 'ERROR: Read error at file '+subfile+', or file is invalid.');
  851.         end
  852.         else if (tmpOID^.ParentFileId <> oid^.FileId) or
  853.                 (tmpOID^.ParentDotNotation <> oid^.DotNotation) then
  854.         begin
  855.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (BAD BACKREF)');
  856.           ListAppend(subfiles, 'ERROR: File '+subfile+' has a wrong back-reference.');
  857.         end
  858.         else
  859.         begin
  860.           ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + _ShowASNIds(tmpOID));
  861.           ListAppend(subfiles, subfile);
  862.         end;
  863.         FreeOidDef(tmpOID);
  864.       end
  865.       else
  866.       begin
  867.         ListAppend(subsel, 'Go to child  ' + DotNotationPart(sTmp) + ' (FILE NOT FOUND)');
  868.         ListAppend(subfiles, 'ERROR: File '+subfile+' was not found');
  869.       end;
  870.     end;
  871.  
  872.     if oid^.DotNotation <> '' then
  873.     begin
  874.       menuIdAsnEdit := ListAppend(subsel, 'View/Edit ASN.1 identifiers');
  875.       ListAppend(subfiles, '');
  876.     end
  877.     else menuIdAsnEdit := -99;
  878.  
  879.     if oid^.DotNotation <> '' then
  880.     begin
  881.       menuIdIriEdit := ListAppend(subsel, 'View/Edit Unicode Labels');
  882.       ListAppend(subfiles, '');
  883.     end
  884.     else menuIdIriEdit := -99;
  885.  
  886.     menuIdDescEdit := ListAppend(subsel, 'Edit description');
  887.     ListAppend(subfiles, '');
  888.  
  889.     menuIdAdd := ListAppend(subsel, 'Add child');
  890.     ListAppend(subfiles, '');
  891.  
  892.     if not isRoot then
  893.     begin
  894.       menuIdDelete := ListAppend(subsel, 'Delete OID');
  895.       ListAppend(subfiles, '');
  896.     end
  897.     else menuIdDelete := -99;
  898.  
  899.     (* Show menu *)
  900.  
  901.     menuX := WhereX + 1;
  902.     menuY := ScreenHeight - ACTIONMENU_SIZE - 1;
  903.     subselres := DrawSelectionList(menuX, menuY,
  904.                                    ScreenWidth-2,
  905.                                    ACTIONMENU_SIZE,
  906.                                    subsel,
  907.                                    true,
  908.                                    'SELECT ACTION',
  909.                                    1);
  910.  
  911.     (* Process user selection *)
  912.  
  913.     if subselres = -1 then
  914.     begin
  915.       exitRequest := true;
  916.     end
  917.     else if subselres = menuIdAsnEdit then
  918.     begin
  919.       if AsnEditor(oid) then
  920.         _WriteOidFile(filename, oid, true);
  921.     end
  922.     else if subselres = menuIdIriEdit then
  923.     begin
  924.       if IriEditor(oid) then
  925.         _WriteOidFile(filename, oid, true);
  926.     end
  927.     else if subselres = menuIdDescEdit then
  928.     begin
  929.       if DescEditor(oid) then
  930.         _WriteOidFile(filename, oid, true);
  931.     end
  932.     else if subselres = menuIdAdd then
  933.     begin
  934.       if NewOidEditor(oid) then
  935.         _WriteOidFile(filename, oid, true);
  936.     end
  937.     else if subselres = menuIdDelete then
  938.     begin
  939.       if _DeleteConfirmation then
  940.       begin
  941.         sTmp := oid^.ParentFileId + OID_EXTENSION;
  942.         DeleteOidRecursive(oid);
  943.         if FileExists(sTmp) then
  944.         begin
  945.           filename := sTmp;
  946.         end
  947.         else
  948.         begin
  949.           ShowMessage('Parent file ' + sTmp + ' was not found', 'ERROR', true);
  950.           _Pause;
  951.           exitRequest := true;
  952.         end;
  953.       end;
  954.     end
  955.     else if subselres = menuIdExit then
  956.     begin
  957.       exitRequest := true;
  958.     end
  959.     else
  960.     begin
  961.       (* Normal OID *)
  962.       (* Above we already checked if the files are valild and existing *)
  963.       sTmp := ListGetElement(subfiles, subselres);
  964.       if Copy(sTmp, 1, Length('ERROR: ')) = 'ERROR: ' then
  965.       begin
  966.         Delete(sTmp, 1, Length('ERROR: '));
  967.         ShowMessage(sTmp, 'ERROR', true);
  968.         _Pause;
  969.       end
  970.       else
  971.       begin
  972.         filename := sTmp;
  973.       end;
  974.     end;
  975.     FreeList(subsel);
  976.     FreeList(subfiles);
  977.  
  978.     FreeOidDef(oid);
  979.   until exitRequest;
  980. end;
  981.  
  982. function CreateRootOIDFile(filename: string; ShowErrorMessage: boolean): boolean;
  983. var
  984.   oid: POID;
  985. begin
  986.   CreateOidDef(oid);
  987.   oid^.Description  := 'This is the root of the OID tree.' +#13#10 +
  988.                        #13#10 +
  989.                        'Valid subsequent arcs are per definition:' + #13#10 +
  990.                        '- 0 (itu-t)' + #13#10 +
  991.                        '- 1 (iso)' + #13#10 +
  992.                        '- 2 (joint-iso-itu-t)';
  993.   oid^.FileId       := ZeroPad(0, 8);
  994.   oid^.DotNotation  := '';
  995.   oid^.ParentFileId := ZeroPad(0, 8);
  996.   oid^.ParentDotNotation := '';
  997.   CreateRootOIDFile := _WriteOidFile(filename, oid, ShowErrorMessage);
  998.   FreeOidDef(oid);
  999. end;
  1000.  
  1001. function _GetRootFile(ShowErrorMessage: boolean): string;
  1002. var
  1003.   rootFile: string;
  1004. begin
  1005.   rootFile := ZeroPad(0, 8) + OID_EXTENSION;
  1006.   _GetRootFile := rootFile;
  1007.   if not FileExists(rootFile) then
  1008.   begin
  1009.     if not CreateRootOIDFile(rootFile, ShowErrorMessage) then
  1010.     begin
  1011.       _GetRootFile := '';
  1012.     end;
  1013.   end;
  1014. end;
  1015.  
  1016. procedure OP_ManageOIDs;
  1017. var
  1018.   rootfile: string;
  1019. begin
  1020.   ClrScr;
  1021.   DrawTitleBar('Manage Object Identifiers', TITLEBAR_LEFT_TEXT, '');
  1022.   DrawStatusBar('Loading data... please wait...');
  1023.  
  1024.   (* This will try creating a new root file if it does not exist *)
  1025.   rootfile := _GetRootFile(true);
  1026.   if rootfile = '' then Exit;
  1027.  
  1028.   DisplayOIDFile(rootfile);
  1029. end;
  1030.  
  1031. procedure OP_ReturnToMSDOS;
  1032. begin
  1033.   (* Note: These two lines don't seem to be necessary if you use DoneVideo *)
  1034.   ResetDefaultDosColors;
  1035.   ClrScr; (*Important, so that the DOS command prompt is also LightGray *)
  1036.  
  1037.   WriteLn('Thank you for using OIDplus for DOS.');
  1038.   WriteLn('');
  1039. end;
  1040.  
  1041. function _GetTreeViewLine(oid: POID; indent: integer): string;
  1042. var
  1043.   i: integer;
  1044.   sTmp, sTmp2: string;
  1045. begin
  1046.   (* Build line *)
  1047.   sTmp := RepeatStr(' ', indent*TREEVIEW_INDENT);
  1048.   if oid^.DotNotation = '' then
  1049.     sTmp := sTmp + 'Object Identifiers'
  1050.   else
  1051.     sTmp := sTmp + oid^.DotNotation;
  1052.   sTmp := sTmp + _ShowAsnIds(oid);
  1053.   if TREEVIEW_INCLUDE_DESC then
  1054.   begin
  1055.     if Trim(oid^.Description) <> '' then
  1056.     begin
  1057.       sTmp := sTmp + ': ' + oid^.Description;
  1058.     end;
  1059.   end;
  1060.  
  1061.   sTmp := StringReplace(sTmp, #13#10, ' ');
  1062.   repeat
  1063.     sTmp2 := sTmp;
  1064.     sTmp := StringReplace(sTmp, '  ', ' ');
  1065.   until sTmp = sTmp2;
  1066.  
  1067.   sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1068.   _GetTreeViewLine := sTmp;
  1069. end;
  1070.  
  1071. procedure _RecTreeExport(oid: POID; var F: Text; indent: integer);
  1072. var
  1073.   i: integer;
  1074.   sTmp: string;
  1075.   suboid: POID;
  1076.   childFilename: string;
  1077. begin
  1078.   sTmp := _GetTreeViewLine(oid, indent);
  1079.   sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1080.   WriteLn(F, sTmp);
  1081.  
  1082.   (* Recursively call children *)
  1083.   for i := 0 to ListCount(oid^.SubIds)-1 do
  1084.   begin
  1085.     sTmp := ListGetElement(oid^.SubIds, i);
  1086.     CreateOidDef(suboid);
  1087.     childFilename := FileIdPart(sTmp) + OID_EXTENSION;
  1088.     if not FileExists(childFilename) then
  1089.     begin
  1090.       sTmp := 'ERROR: MISSING ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1091.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1092.       WriteLn(F, sTmp);
  1093.     end
  1094.     else if not _ReadOidFile(childFilename, suboid, false) then
  1095.     begin
  1096.       sTmp := 'ERROR: READ ERROR AT ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1097.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1098.       WriteLn(F, sTmp);
  1099.     end
  1100.     else if (suboid^.ParentFileId <> oid^.FileId) or
  1101.             (suboid^.ParentDotNotation <> oid^.DotNotation) then
  1102.     begin
  1103.       (* This can happen if a file is missing, and then another OID gets this filename since the number seems to be free *)
  1104.       sTmp := 'ERROR: BAD BACKREF AT ' + childFilename + ' (SHALL CONTAIN ' + DotNotationPart(sTmp) + ')!';
  1105.       sTmp := TrimLineToWidth(sTmp, TREEVIEW_WIDTH);
  1106.       WriteLn(F, sTmp);
  1107.     end
  1108.     else
  1109.     begin
  1110.       _RecTreeExport(suboid, F, indent+1);
  1111.       FreeOidDef(suboid);
  1112.     end
  1113.   end;
  1114. end;
  1115.  
  1116. procedure TreeViewPreview;
  1117. var
  1118.   list: PStringList;
  1119. begin
  1120.   ClrScr;
  1121.   DrawTitleBar('TreeView Export', TITLEBAR_LEFT_TEXT, TREEVIEW_FILENAME);
  1122.   DrawStatusBar('Press ESC to return to the main menu');
  1123.  
  1124.   CreateList(list);
  1125.  
  1126.   ListLoadFromFile(list, TREEVIEW_FILENAME);
  1127.   DrawSelectionList(2, 3, ScreenWidth-2, ScreenHeight-4,
  1128.                     list, true, 'PREVIEW OF '+TREEVIEW_FILENAME, 2);
  1129.   (* TODO: Jump to selected OID *)
  1130.  
  1131.   DrawStatusBar(DEFAULT_STATUSBAR);
  1132.  
  1133.   FreeList(list);
  1134. end;
  1135.  
  1136. procedure OP_TreeView;
  1137. var
  1138.   F: Text;
  1139.   rootoid: POID;
  1140.   rootfile: string;
  1141.   res: boolean;
  1142. begin
  1143.   ClrScr;
  1144.   DrawTitleBar('TreeView Export', TITLEBAR_LEFT_TEXT, '');
  1145.   DrawStatusBar('Exporting data... please wait...');
  1146.  
  1147.   (* This will try creating a new root file if it does not exist *)
  1148.   rootfile := _GetRootFile(true);
  1149.   if rootfile = '' then
  1150.   begin
  1151.     DrawStatusBar(DEFAULT_STATUSBAR);
  1152.     Exit;
  1153.   end;
  1154.  
  1155.   Assign(F, TREEVIEW_FILENAME);
  1156.   {$I-}
  1157.   Rewrite(F);
  1158.   {$I+}
  1159.   if IoResult <> 0 then
  1160.   begin
  1161.     (* Can happen if disk is read-only (Runtime Error 150) *)
  1162.     ShowMessage('Cannot open '+TREEVIEW_FILENAME+' for writing.', 'ERROR', true);
  1163.     _Pause;
  1164.     DrawStatusBar(DEFAULT_STATUSBAR);
  1165.     Exit;
  1166.   end;
  1167.  
  1168.   res := false;
  1169.   CreateOidDef(rootoid);
  1170.   if _ReadOidFile(rootfile, rootoid, true) then
  1171.   begin
  1172.     _RecTreeExport(rootoid, F, 0);
  1173.     res := true;
  1174.   end;
  1175.   FreeOidDef(rootoid);
  1176.  
  1177.   Close(F);
  1178.  
  1179.   DrawStatusBar(DEFAULT_STATUSBAR);
  1180.   if res then
  1181.   begin
  1182.     ShowMessage('TreeView successfully exported as '+TREEVIEW_FILENAME, 'TREEVIEW EXPORT', true);
  1183.     _Pause;
  1184.   end;
  1185.  
  1186.   TreeViewPreview;
  1187. end;
  1188.  
  1189. procedure OP_MainMenu;
  1190. var
  1191.   menu: PStringList;
  1192.   menuRes, menuLeft, menuTop: integer;
  1193.   menuIdOID, menuIdTree, menuIdExit: integer;
  1194. begin
  1195.   repeat
  1196.     ClrScr;
  1197.  
  1198.     DrawTitleBar('Welcome to OIDplus for DOS', '', '');
  1199.     DrawStatusBar(DEFAULT_STATUSBAR);
  1200.     GoToXY(ScreenWidth-Length(VERSIONINFO), ScreenHeight-1);
  1201.     Write(VERSIONINFO);
  1202.  
  1203.     CreateList(menu);
  1204.  
  1205.     menuIdOID  := ListAppend(menu, 'Manage OIDs');
  1206.     menuIdTree := ListAppend(menu, 'Export TreeView');
  1207.     menuIdExit := ListAppend(menu, 'Return to DOS');
  1208.  
  1209.     menuLeft := round(ScreenWidth/2 -MAINMENU_WIDTH/2);
  1210.     menuTop  := round(ScreenHeight/2-MAINMENU_HEIGHT/2);
  1211.     menuRes  := DrawSelectionList(menuLeft, menuTop,
  1212.                                   MAINMENU_WIDTH, MAINMENU_HEIGHT,
  1213.                                   menu, true, 'MAIN MENU', 2);
  1214.     FreeList(menu);
  1215.  
  1216.     if menuRes = menuIdOID then
  1217.     begin
  1218.       OP_ManageOIDs;
  1219.     end
  1220.     else if menuRes = menuIdTree then
  1221.     begin
  1222.       OP_Treeview;
  1223.     end;
  1224.   until (menuRes = menuIdExit) or (MAINMENU_ALLOW_ESC and (menuRes = -1));
  1225.  
  1226.   OP_ReturnToMSDOS;
  1227. end;
  1228.  
  1229. begin
  1230.   InitVideo; (* sets ScreenWidth and ScreenHeight *)
  1231.   CursorOff;
  1232.   OP_MainMenu;
  1233.   CursorOn;
  1234.   DoneVideo;
  1235. end.
  1236.