%xytitles - an example client titles macro

Introduction

Client titles macros are what the %titles macro calls to complete the work of creating title lines and perhaps some footnote lines in the client's style. How this should be done will vary from client to client and will be an established layout that the client uses that is not open to negotiation. Spectre imposes no layouts but instead relies on your having written a client titles macro to do the work. The style is specified in the protocol.txt file so if you set "titlestyle: xy" then the %titles macro will make a call to the macro %xytitles and it expects it to be there.

The %xytitles macro explained here is not a recommendation for setting out titles. It is an example client macro, and a fairly complex one at that, which is useful in showing you how to approach the task of writing such a macro. If you can understand how this macro works then you should be well prepared to cope with writing any client titles macro. The person whose job it is to write it may be the Spectre Administrator or it may be given to a programmer or it may be me. As for getting advice on writing this macro as part of the Spectre support contract then this is the advice!! By talking you through this macro, this is all the advice I can give you. However, if I do write a client style macro as part of the support contract, then it will be on the condition that I can web it here. Preference will be given to the client style macros of the major pharmaceutical companies. Your own donations in this regard would be welcome but I might rewrite it. I will build up a collection and web it.

A word of caution

I have come across a case where a client's in-house system adds header titles and a standard footnote to the final text report at the same time as adding "Page x of Y" labels. This allows programmers to use all 10 titles and footnotes as user-defined text -- the limit in SAS -- plus to add extra standard header lines and footnote lines done automatically by the reporting system. It is important that you establish with the client whether they expect to be able to use all 10 title and footnote lines as user-defined text and have the extra standard header lines and perhaps footnote lines added by the system. A big clue as to their using all 10 lines as user-defined text is the page number labels appearing on the same line as the first logical "user" title such as "Table 12.1.3  (Page 1 of 2)" but you should not rely on clues alone.

Spectre will allow you to break the 10 titles 10 footnotes limit by calling an alternative to the %pagexofy macro, which you declare in the protocol.txt member, that could be called a "client pagexofy macro" and you can do this unusual adding of extra titles and footnotes in that macro plus anything else you need to do.

%xytitles

This is an example of a complex client titles macro. It is complex in that "tables" have one layout and "appendices" have a different layout. Whether it is a table or appendix is defined in the ".titles" member in the first title line such as "Table 1.2.1.1" or "Appendix 15.6.1.5" (there must always be this first title in the ".titles" member). This gets incorporated into the "titles" dataset and when these titles are read in by the %titles macro, the global macro variable _reptype_ gets set to either "TABLE" or "APPENDIX" (without quotes) depending on the report type defined. Since appendices are expected to run to many pages, a layout is chosen to make use of every possible line on a page so the main title (e.g. "Appendix 15.6.1.5") will be shown at the extreme right in the second header line and the population (if selected on a population) will be shown underneath it on the third header line at the extreme right. Both will be shown in mixed case and no matter in what case the word "appendix" was entered in a titles member, it will be shown with the first letter capitalized and the rest in lower case to make sure it is consistent across the study. For "tables", on the other hand, the main title (e.g. "TABLE 1.2.1.1") will be shown in the centre of the report in capitals, will have a blank line underneath it, then follows the rest of the titles and the last title will be the population (again, capitalized). This is probably about as complicated as they come but if it isn't, it probably won't get much worse. You can open this macro in a new page by clicking on the link below.
%xytitles

Here is the start of the macro and an explanation:
 
%macro xytitles(program=,label=);

%*- set up global macro variables -;
%global _pagescript_ _tline2_ _tline3_ _tline4_ _figbkmark_;
%let _pagescript_=pagexofy;
%let _tline2_=;
%let _tline3_=;
%let _tline4_=;
%let _figbkmark_=;
 

%*- set up needed local variables -;
%local popalert tcount;
 

%*- Alert flag for when the population in the form &_poplabel_ has -;
%*- been detected in one of the titles. If that is the case then for -;
%*- a table, do not put out a following population title. -;
%let popalert=0;

In the code above, first some global macro variables are set up. _pagescript_ is set to the name of the shell script, pagexofy, that %closerep will call to add the "Page X of Y" titles as this might vary from client to client. Unix is case sensitive so you might have a capitalized version of pagexofy named PAGEXOFY that creates capitalized page labels like "PAGE X OF Y". _figbkmark_ will be set to a bookmark that might be used for figures and it will be made up of titles that will also be written to the global macro variable _tline1_ _tline2_ etc. The value of _figbkmark_ will be displayed in the log as well as all the other. If you set up or possibly change a global macro variable then you must display these in the log so that programmers know they have been set up and can use them if they wish. This is done in all other Spectre macros and you should follow this convention in the client titles macro.

After the global macro variables have been declared, you can see that two local ones have been declared. The "popalert" one is set to "0" and explained.

Now for the next chunk of code.
 
%*- For tables, make sure the word "TABLE" is in upper case in first title -;
%*- and that the population label is also in upper case. -;
%if "&_reptype_" EQ "TABLE" %then %do;
  %let _repid_=%casestrmac(&_repid_,TABLE);
  %let _poplabel_=%upcase(&_poplabel_);
%end;
%else %if "&_reptype_" EQ "ATTACHMENT" %then %do;
  %let _repid_=%casestrmac(&_repid_,ATTACHMENT);
  %let _poplabel_=%upcase(&_poplabel_);
%end;
%*- For non-tables, make sure the first letter is upper case and the rest lower case -;
%else %do;
  %let _repid_=%casestrmac(&_repid_,
    %substr(&_reptype_,1,1)%lowcase(%substr(&_reptype_,2)));
%end;

The three global macros variables _reptype_ , _repid_ and _poplabel_ are set in the %proginfo macro that is called by %titles before the client titles macro is called. Here, the type of report defined to _reptype_ will be used to drive this client titles macro. The macro %casestrmac is used to force any mixed case form of a string to the one specified and you will see that for "TABLE" and "ATTACHMENT" (%proginfo sets _reptype_ values to upper case) the case for that string is set to upper case but for others, the first character is capitalized and the rest set to lower case. The value of _poplabel_ is set to upper case for "TABLE" and "ATTACHMENT" but is left as it is for other types of report. This value of _poplabel_ is set in protocol.txt and is not something the user has supplied, so there will be no conflict of case for the population label for other types of reports.

Now for the next piece of code.
 
*- set up and create the top three header lines of a standard report -;
data _null_;
  length text $ &_ls_ datestr $ 32;
  *- date string for draft report to add to the title -;
  datestr=put(date(),weekdate32.);
  datestr=substr(datestr,index(datestr,",")+2);
  text="&_drugname_";
  substr(text,&_ls_,1)='FF'x;
  call execute("title1"||' "'||text||'";');
  text="&_protocol_";
  %if "&_reptype_" NE "TABLE" and "&_reptype_" NE "ATTACHMENT" %then %do;
    substr(text,%eval(&_ls_-%length(&_repid_)+1))="&_repid_";
  %end;
  call execute("title2"||' "'||text||'";');
  text="&_report_";
  if index(upcase(text),'DRAFT') then text=trim(text)||" "||datestr;
  %if %length(&_poplabel_) %then %do;
    %if "&_reptype_" NE "TABLE" and "&_reptype_" NE "ATTACHMENT" %then %do;
      substr(text,%eval(&_ls_-%length(&_poplabel_)+1))="&_poplabel_";
    %end;
  %end;
  call execute("title3"||' "'||text||'";');
run;

The three "header" lines are set up and executed in this next piece of code. These header lines will become title1, title2 and title3 as far as sas knows them. The content of each title line is set up in turn the variable "text".

You will see that for the first header line it is set to the value of _drugname_ and the page label character, which must be "FF"x and nothing else, is put in the rightmost position that will fit on the page width by using the value of _ls_ that also gets set in the %proginfo macro. A call execute follows which will be activated after the data step ends.

The second header line has the value of _protocol_ on the left and if _reptype_ is not set to either "TABLE" or "ATTACHMENT" then the right is set to the value of _repid_ that had its case adjusted in the previous code. Then follows the call execute to activate title2.

The third header line is set to the value of _report_ that is set up in the %protinfo macro called earlier by %titles. If the word "draft" is detected in this string in any case then it will put the current date at the end that was set up in the variable "datestr" to tell you the date of the draft version of the output. If _reptype_ is neither "TABLE" or "ATTACHMENT" then the population label defined to _poplabel_ is put on the far right. Then follows the call execute to activate title3.

We have our three header lines now and they have been activated. They are done. More follows.
 
%*- For tables, throw a blank title line, put table title on -;
%*- following line and then throw another blank title line. -;
%*- For others, just throw a blank title line to leave a gap. -;
%if "&_reptype_" EQ "TABLE" or "&_reptype_" EQ "ATTACHMENT" %then %do;
  title4 "   ";
  title5 "&_repid_";
  title6 "   ";
%end;
%else %do;
  title4 "  ";
%end;
 

%*- do a quiet count of titles (i.e. do not display maximum values yet) -;
%maxtitle(quiet)
%let tcount=&_maxtitle_;

If _reptype_ is "TABLE" or "ATTACHMENT" we have yet to put out the report reference or main title. It has already been done on the far right of the second header line for other types of report. This case of this has already been adjusted so title4 is set to blank, title5 is set to the main title and a blank title line put under it to conform with the client style. For other types of reports, a blank title4 is created. The number of title lines created is determined using the %maxtitle(quiet) call and the value assigned to the local macro variable "tcount" for use in further logic as follows.
 
*- extract titles -;
data _titles;
  set der.titles(where=(program="&program" and label="&label"));
run;
 

%*- Only process the titles if there is more than one -;
%*- since the first title has already been dealt with. -;
%if %attrn(_titles,nobs) GT 1 %then %do;

  *- process the titles -;
  data _titles(keep=type number text);
    set _titles;
    *- drop this as we have already set up the first title -;
    if type="T" and number=1 then delete;
    if type="T" and number=2 then call symput('_tline2_',trim(text));
    if type="T" and number=3 then call symput('_tline3_',trim(text));
    if type="T" and number=4 then call symput('_tline4_',trim(text));
    *- increment the title number depending on how many titles already put out -;
    if type='T' then number=number-1+&_maxtitle_;
    *- Replace "&" character in titles and footnotes with a special character -;
    *- if not followed by an underscore so we do not get warning messages -;
    *- about unresolved macro variables. -;
    do while (index(text,'&') and (index(text,'&') NE index(text,'&_')));
      substr(text,index(text,'&'),1)='FD'x;
    end;
    *- Replace "%" character in titles and footnotes with a special character -;
    *- if not followed by a space so we do not get warning messages about -;
    *- macros not resolved. -;
    do while (index(text,'%') and (index(text,'%') NE index(text,'% ')));
      substr(text,index(text,'%'),1)='F8'x;
    end;
    *- Replace double quote character with a special character -;
    text=translate(text,'F0'x,'"');
    *- Set a flag to tell this macro not to add an extra titles population -;
    *- line if it detects a call to _poplabel_ in one of the titles. -;
    if type='T' and index(upcase(text),'&_POPLABEL_') then call symput('popalert',"1");
  run;
 

  *- generate the extra titles from the extracted titles -;
  %titlegen(_titles)

%end;

In the first data step, all the titles (and footnotes) are extracted for that program from the titles dataset. If we only find one observation then this will be the table/appendix/whatever reference number and this has already been put out in a header line or in a title so we have nothing further to do. This is why the number of observations is checked using %attrn(_titles,nobs) and the following data step is executed only if the number of observations is greater than one.

Assuming more than one observation was found then the processing continues as follows. In the titles dataset, the title numbers start at 1 and are numbered sequentially. It knows nothing about what titles line you are really going to use. We have already handled the title numbered 1 and so this is dropped. The second, third and fourth title lines are written to global macro variables for information purposes only, in case the programmer needs to use this. Next the real number of the title line must be adjusted by the number of titles put out already that is in the global macro variable _maxtitle_. This is only done for titles and not for footnotes.

Next in the data step comes the translation of special characters. You must use this same code in whatever client titles macro you write. The lines of code you must use are shown in bold. Note the substitution for the ampersand "&". It will not substitute if the ampersand is followed by an underscore. It allows for macro variables starting with an underscore to be substituted in titles and footnotes. If the underscore is not the first character in the macro variable name then it will show the ampersand as it is in the title or footnote. These characters will get substituted back to what they were originally in the script that adds the page labels which in this case is pagexofy.

Note that the local macro variable "popalert" gets set if it sees "&_poplabel_" in any of the titles in any case. This is to tell it not to add the population label later, if this has already been done.

Next comes the activation of these new titles and possibly footnotes. This is done using the %titlegen macro.

Now we move on. For tables, the XY client wants the population label as the last title line so long as it has not been put out already, as detected by the "popalert" setting, so this code follows.
 
%*- For a table, put the population title as last title unless it is -;
%*- blank or it has been detected in the title lines (i.e. &popalert=1) -;
%if "&_reptype_" EQ "TABLE" and %length(&_poplabel_) and (&popalert EQ 0) %then %do;
  %maxtitle(quiet)
  %*- Only add this population title if some extra -;
  %*- titles were generated in the previous step. -;
  %if &_maxtitle_ GT &tcount %then %do;
    title%eval(&_maxtitle_+1) "&_poplabel_";
  %end;
%end;

Next comes the bookmark that might be used for figures. I stress "might" because this bookmark will be set up in the global macro variable _figbkmark_ and will be used by default for figures but if the programmer doesn't like what is in it then they are free to change the value of _figbkmark_ to whatever they want. t is calculated for all types of report but will only be used for figures. There is no particular reason why it is done for types of report other than figures but it just seems logical to do it anyway.
 
%let _figbkmark_=&_repid_;
%if %length(&_tline2_) %then %do;
  %let _figbkmark_=%superq(_figbkmark_) %superq(_tline2_);
  %if %length(&_tline3_) %then %do;
    %let _figbkmark_=%superq(_figbkmark_) %superq(_tline3_);
    %if %length(&_tline4_) %then %do;
      %let _figbkmark_=%superq(_figbkmark_) %superq(_tline4_);
    %end;
  %end;
%end;
%let _figbkmark_=%superq(_figbkmark_) &_poplabel_;

Note that %superq is used above because there is no telling what is in _tline1_, _tline2_ etc. that might mess up the macro parser. With %superq you are always safe.

And now the last part. We display all the global macro variables we have set up and might have changed (you must do this) and after that we exit.
 
%put;
%put MSG: (xytitles) The following global macro variables have been set;
%put MSG: (xytitles) and can be used in your code. ;
%put _repid_=&_repid_;
%put _poplabel_=&_poplabel_;
%put _tline2_=%superq(_tline2_);
%put _tline3_=%superq(_tline3_);
%put _tline4_=%superq(_tline4_);
%put _figbkmark_=%superq(_figbkmark_);

%put;
 

*- tidy up -;
proc datasets nolist;
  delete _titles;
  run;
quit;

Conclusion

An example of a complex client titles macro has been explained to you. Using what you have learned from this example, it is hoped that you will be able to code any required client titles macro.
 
 

Use the "Back" button of your browser to return to the previous page

contact the author