/*

/ Program      : adddecodevars.sas
/ Version      : 1.0
/ Author       : Roland Rashleigh-Berry
/ Date         : 13-Feb-2007
/ SAS version  : 8.2
/ Purpose      : To add decode variables where a user format is specified
/ SubMacros    : %sysfmtlist
/ Notes        : Length of decode variable is determined from the format length.
/                Decode variable names will be same as the ones being decoded
/                but with the suffix defined to this macro added. The variables
/                will be reordered such that a decode variable will logically
/                follow the variable being decoded. System formats will not be
/                decoded and the list is maintained in the %sysfmtlist macro.
/ Usage        : %adddecodevars(dsin=ds1,dsout=ds2)
/ 
/===============================================================================
/ PARAMETERS:
/-------name------- -------------------------description------------------------
/ dsin=             Input dataset
/ dsout=            Output dataset (no modifiers)
/ suffix=__D        Suffix to use for the decode variable. This will be added
/                   at the end of the name of the variable being decoded.
/===============================================================================
/ AMENDMENT HISTORY:
/ init --date-- mod-id ----------------------description------------------------
/ rrb  13Feb07         "macro called" message added
/===============================================================================
/ This is public domain software. No guarantee as to suitability or accuracy is
/ given or implied. User uses this code entirely at their own risk.
/=============================================================================*/

%put MACRO CALLED: adddecodevars v1.0;

%macro adddecodevars(dsin=,dsout=,suffix="__D",killusrfmts=yes);

%local error;
%let error=0;


      /*----------------------------------*
          check that parameters are set
       *----------------------------------*/

%if not %length(&dsin) %then %do;
  %let error=1;
  %put ERROR: (adddecodevars) No input dataset defined to dsin=;
%end;

%if not %length(&dsout) %then %do;
  %let error=1;
  %put ERROR: (adddecodevars) No output dataset defined to dsout=;
%end;

%if not %length(&suffix) %then %do;
  %let error=1;
  %put ERROR: (adddecodevars) No characters defined to sufFix=;
%end;

%if &error %then %goto error;


      /*----------------------------------*
            prepare parameter settings
       *----------------------------------*/

%let suffix="%sysfunc(compress(&suffix,%str(%'%")))";

%if not %length(&killusrfmts) %then %let killusrfmts=yes;
%let killusrfmts=%upcase(%substr(&killusrfmts,1,1));


      /*----------------------------------*
           get contents of input dataset
       *----------------------------------*/

*- Option "fmtlen" is used to get lengths of user formats. -;
proc contents noprint fmtlen varnum data=&dsin mtype=data out=_adddecont;
run;

*- put in logical variable order -;
proc sort data=_adddecont;
  by memname varnum;
run;

      /*----------------------------------*
         create long strings of variables 
       *----------------------------------*/

data _adddecont;
  *- long lists of retain, length and user variables -;
  length retlist lenlist usrlist $ 32767;
  retain retlist "retain" lenlist "length" usrlist "format";
  set _adddecont end=last;
  *- this is trying to identify non-user formats -;
  if format in (" " %sysfmtlist) then _fmt="SYS";
  else _fmt="USR";
  *- add to the retain list for future reordering of variables -;
  retlist=trim(retlist)||" "||trim(name);
  if _fmt="USR" then do;
    *- add decode variable to the length statement list -;
    lenlist=trim(lenlist)||" "||trim(name)||&suffix||" $ "||compress(put(formatl,4.));
    *- add decode variable to the retain statement list -;
    retlist=trim(retlist)||" "||trim(name)||&suffix;
    *- add original variable to the format kill list -;
    usrlist=trim(usrlist)||" "||name;
  end;
  if last then do;
    *- write the long lists out to local macro variables -;
    call symput('retlist',trim(retlist));
    call symput('lenlist',trim(lenlist));
    call symput('usrlist',trim(usrlist));
  end;
  keep name type _fmt;
run;


      /*----------------------------------*
              generate call executes
       *----------------------------------*/

data _null_;
  set _adddecont end=last;
  *- start of generated data step -;
  if _n_=1 then do;
    call execute('data &dsout;');
    call execute('&lenlist;');
    call execute('set &dsin;');
  end;
  *- generate the decoding code and the label statement -;
  if _fmt="USR" then do;
    if type=2 then do;
      *- variable type is character -;
      call execute(trim(name)||&suffix||"=putc("||trim(name)||",vformat("||trim(name)||"));");
    end;
    else do;
      *- variable type is numeric -;
      call execute(trim(name)||&suffix||"=putn("||trim(name)||",vformat("||trim(name)||"));");
    end;
    call execute("label "||trim(name)||&suffix||"='Decode of variable "||trim(name)||"';");
  end;
  *- end of data step followed by reordering data step -;
  if last then do;
    call execute('run;');
    call execute('data &dsout;');
    call execute('&retlist;');
    call execute('set &dsout;');
    %if "&killusrfmts" EQ "Y" %then %do;
      call execute('&usrlist;');
    %end;
    call execute('run;');
  end;
run;


      /*----------------------------------*
                 tidy up and exit
       *----------------------------------*/

proc datasets nolist;
  delete _adddecont;
run;
quit;

%goto skip;
%error: %put ERROR: (adddecodevars) Leaving maco due to error(s) listed;
%skip:

%mend;