Example 18.1 Full-Screen Editing

The ability to form and submit statements dynamically provides a very powerful mechanism for making systems flexible. For example, consider the building of a data entry system for a file. It is straightforward to write a system by using WINDOW and DISPLAY statements for the data entry and data processing statements for the I/O, but once you get the system built, it is good only for that one file. With the ability to push statements dynamically, however, it is possible to make a system that dynamically generates the components that are customized for each file. For example, you can change your systems from static systems to dynamic systems.

To illustrate this point, consider an IML system to edit an arbitrary file, a system like the FSEDIT procedure in SAS/FSP software but programmed in IML. You cannot just write it with open code because the I/O statements hardcode the filenames and the WINDOW and DISPLAY statements must hardcode the fields. However, if you generate just these components dynamically, the problem is solved for any file, not just one. Here is the code:

   proc iml;
   /*                 FSEDIT                                    */
   /* This program defines and stores the modules FSEINIT,      */
   /* FSEDT, FSEDIT, and FSETERM in a storage catalog called    */
   /* FSED.  To use it, load the modules and issue the command  */
   /* RUN FSEDIT;  The system prompts or menus the files and    */
   /* variables to edit, then runs a full screen editing        */
   /* routine that behaves similar to PROC FSEDIT               */
   /*                                                           */
   /* These commands are currently supported:                   */
   /*                                                           */
   /* END         gets out of the system.  The user is prompted */
   /*             as to whether or not to close the files and   */
   /*             window.                                       */
   /* SUBMIT      forces current values to be written out,      */
   /*             either to append a new record or replace      */
   /*             existing ones                                 */
   /* ADD         displays a screen variable with blank values  */
   /*             for appending to the end of a file            */
   /* DUP         takes the current values and appends them to  */
   /*             the end of the file                           */
   /* number      goes to that line number                      */
   /* DELETE      deletes the current record after confirmation */
   /*             by a Y response                               */
   /* FORWARD1    moves to the next record, unless at eof       */
   /* BACKWARD1   moves to the previous record, unless at eof   */
   /* EXEC        executes any IML statement                    */
   /* FIND        finds records and displays them               */
   /*                                                           */
   /* Use: proc iml;                                            */
   /*         reset storage='fsed';                             */
   /*         load module=_all_;                                */
   /*         run fsedit;                                       */
   /*                                                           */
   /*---routine to set up display values for new problem---     */
   start fseinit;
      window fsed0 rows=15 columns=60 icolumn=18 color='GRAY'
      cmndline=cmnd group=title +30 'Editing a data set' color='BLUE';
      /*---get file name---                                     */
      _file="                 ";
      msg =
         'Please Enter Data Set Name or Nothing For Selection List';
      display  fsed0.title,
         fsed0 ( / @5  'Enter Data Set:'
                   +1  _file
                   +4  '(or nothing to get selection list)' );
      if _file=' ' then
         do;
            loop:
            _f=datasets(); _nf=nrow(_f); _sel=repeat("_",_nf,1);
            display fsed0.title,
               fsed0 (/ "Select? File Name"/) ,
               fsed0 (/ @5  _sel +1 _f protect=yes ) repeat ;
            _l = loc(_sel^='_');
            if nrow(_l)^=1 then
               do;
                  msg='Enter one S somewhere';
                  goto loop;
               end;
            _file = _f[_l];
         end;
      /*---open file, get number of records---                  */
      call queue(" edit ",_file,";
                   setin ",_file," NOBS _nobs; resume;"); pause *;
      /*---get variables---                                     */
      _var = contents();
      _nv = nrow(_var);
      _sel = repeat("_",_nv,1);
      display fsed0.title,
              fsed0 (/ "File:" _file) noinput,
              fsed0 (/ @10 'Enter S to select each var, or select none
                     to get all.'
                     // @3  'select?  Variable  '  ),
                     fsed0 ( / @5  _sel  +5 _var protect=yes ) repeat;
      /*---reopen if subset of variables---                     */
      if any(_sel^='_') then
         do;
            _var = _var[loc(_sel^='_')];
            _nv = nrow(_var);
            call push('close ',_file,'; edit ',_file,' var
            _var;resume;');pause *;
         end;
      /*---close old window---                                  */
      window close=fsed0;
      /*---make the window---*/
      call queue('window fsed columns=55 icolumn=25 cmndline=cmnd
                 msgline=msg ', 'group=var/@20 "Record " _obs
                 protect=yes');
      call queue( concat('/"',_var,': " color="YELLOW" ',
                 _var,' color="WHITE"'));
      call queue(';');
      /*---make a missing routine---*/
      call queue('start vmiss; ');
      do i=1 to _nv;
         val = value(_var[i]);
         if type(val)='N' then call queue(_var[i],'=.;');
         else call queue(_var[i],'="',
                         cshape(' ',1,1,nleng(val)),'";');
      end;
      call queue('finish; resume;');
      pause *;
      /*---initialize current observation---*/
      _obs = 1;
      msg = Concat('Now Editing File ',_file);
   finish;
     /*                                                         */
     /*---The Editor Runtime Controller---                      */
   start fsedt;
      _old = 0; go=1;
      do while(go);
      /*--get any needed data--*/
         if any(_obs^=_old) then do; read point _obs; _old = _obs;
      end;
      /*---display the record---*/
      display fsed.var repeat;
      cmnd = upcase(left(cmnd));
      msg=' ';
      if cmnd='END' then go=0;
      else if cmnd='SUBMIT' then
         do;
            if _obs<=_nobs then
               do;
                  replace point _obs; msg='replaced';
               end;
            else do;
                    append;
                    _nobs=_nobs+nrow(_obs);
                    msg='appended';
                 end;
         end;
      else if cmnd="ADD" then
          do;
             run vmiss;
             _obs = _nobs+1;
             msg='New Record';
          end;
      else if cmnd='DUP' then
         do;
            append;
            _nobs=_nobs+1;
            _obs=_nobs;
            msg='As Duplicated';
         end;
      else if cmnd>'0' & cmnd<'999999' then
         do;
            _obs = num(cmnd);
            msg=concat('record number ',cmnd);
         end;
      else if cmnd='FORWARD1' then _obs=min(_obs+1,_nobs);
      else if cmnd='BACKWARD1' then _obs=max(_obs-1,1);
      else if cmnd='DELETE' then
         do;
            records=cshape(char(_obs,5),1,1);
            msg=concat('Enter command Y to Confirm delete of'
                       ,records);
            display fsed.var repeat;
            if (upcase(cmnd)='Y') then
               do;
                  delete point _obs;
                  _obs=1;
                  msg=concat('Deleted Records',records);
               end;
            else msg='Not Confirmed, Not Deleted';
         end;
      else if substr(cmnd,1,4)='FIND' then
         do;
            call execute("find all where(",
                           substr(cmnd,5),
                           ") into _obs;" );
             _nfound=nrow(_obs);
             if _nfound=0 then
                do;
                   _obs=1;
                   msg='Not Found';
                end;
              else
                 do;
                    msg=concat("Found ",char(_nfound,5)," records");
                 end;
         end;
      else if substr(cmnd,1,4)='EXEC' then
         do;
            msg=substr(cmnd,5);
            call execute(msg);
         end;
      else msg='Unrecognized Command; Use END to exit.';
      end;
   finish;
     /*---routine to close files and windows, clean up---*/
   start fseterm;
        window close=fsed;
        call execute('close ',_file,';');
        free _q;
   finish;
      /*---main routine for FSEDIT---*/
   start fsedit;
     if (nrow(_q)=0) then
        do;
           run fseinit;
        end;
     else msg = concat('Returning to Edit File ',_file);
     run fsedt;
     _q='_';
     display fsed ( "Enter 'q' if you want to close files and windows"
                   _q " (anything else if you want to return later"
                   pause 'paused before termination';
     run fseterm;
   finish;
   reset storage='fsed';
   store module=_all_;