%macro pageof(
/* Specify a file or fileref. */
/* Physical file names should be in quotes. */
/* Filerefs/DDnames should not be in quotes. */
pgm =, /* program file to execute */
output =, /* output file */
/* placement of page number text on print page */
just = center, /* left, center, or right */
topbot = bottom, /* top or bottom */
psadd = 2, /* number of lines from text */
pagetext = Page PAGEX of PAGEN, /* page number text */
cc = ! /* character to use as form feed */
);
/* Initialize global system options. */
options number pageno=1 formdlim="&cc";
/* Initialize column to begin reading */
/* original output. */
%let strt_col = 1;
/* Initialize system-specific files and options. */
/* Determine the operating system with automatic */
/* macro variable SYSSCP. */
%if &sysscp=HP 800 %then
%do;
filename temp1 'potemp1.txt';
filename temp2 'potemp2.txt';
%end;
%else
%if &sysscp=VMS or &sysscp=OS2 or &sysscp=WIN %then
%do;
/* Do not require the user to press enter to */
/* leave the X window brought up by an X */
/* statement. */
options noxwait;
filename temp1 'potemp1.txt';
filename temp2 'potemp2.txt';
%end;
%else
%if &sysscp=CMS %then
%do;
filename temp1 'potemp1 txt a';
filename temp2 'potemp2 txt a';
%let strt_col = %eval(&strt_col + 1);
%let psadd=%eval(&psadd + 2);
%end;
%else
%if &sysscp=OS %then
%do;
filename temp1 '&temp';
/* Cause a short time delay between */
/* allocations because the time value is used */
/* in the name of the temporary file. */
data _null_;
run;
filename temp2 '&temp';
%let strt_col = %eval(&strt_col + 1);
%let psadd=%eval(&psadd + 2);
%end;
/* Execute the user's program in a PRINTTO block */
/* to save the output for post processing. */
proc printto print=temp1 new;
%include &pgm;
proc printto;
run;
/* Set page eject back to the system default. */
options formdlim='';
/* Put the original LINESIZE= and PAGESIZE= */
/* settings into macro variables for later use */
/* by reading SASHELP.VOPTION and writing the */
/* values into macro variables. */
data _null_;
set sashelp.voption;
where optname='LINESIZE' or optname='PAGESIZE';
call symput(optname,setting);
run;
/* Route the output of a dummy PROC PRINT to the */
/* TEMP2 file to generate the next page number. */
proc printto print=temp2 new;
data _temp_;
x=1;
proc print;
options number;
proc printto;
run;
/* Read the first line of output to get the page */
/* number. Store the number in a macro variable. */
data _null_;
infile temp2 truncover;
input record $ 1-200;
call symput('lastpage',trim(left(put(input(reverse
(scan(reverse(record),1)),6.)-1,6.))));
stop;
run;
/* Adjust options. */
%let pagesize=%eval(&pagesize+&psadd);
options nodate nonumber ps=&pagesize;
/* Use macro logic to parse the text parameter */
/* and create the appropriate DATA step syntax */
/* to print the page number. */
/* Find the position of PAGEX in the string. */
%let xpos=%index(%nrbquote(&pagetext),PAGEX);
/* If PAGEX occurs at the beginning of the */
/* string, substitute this: */
%if &xpos=1 %then
%let maketext = trim(left(put(pagenum,6.))) ||
"%substr(&pagetext,6)";
%else
%do;
/* If PAGEX is not at the beginning of the */
/* string, substitute this: */
%let maketext = "%substr(&pagetext,1,&xpos-1)" ||
trim(left(put(pagenum,6.)));
/* If PAGEX is not at the end of the string, add */
/* the remainder of the string. */
%if &xpos+4 le %length(&pagetext) %then
%let maketext = &maketext ||
"%substr(&pagetext,&xpos+5)";
%end;
/* Find the position of PAGEN. */
%let npos = %index(&maketext,PAGEN);
/* Substitute the total page count for PAGEN. */
%let maketext = %qsubstr(&maketext,1,
&npos-1)%nrstr(&lastpage)%qsubstr(&maketext,
&npos+5);
/* Read in TEMP1 and check for carriage control. */
/* Each time carriage control is encountered, */
/* read the current page number and then rewrite */
/* it at the desired location along with the */
/* total page count. Otherwise write the original*/
/* record. */
data _null_;
infile temp1 length=len truncover eof=eof noprint;
length text $ 200 next_cc $ 10;
retain pagenum;
/* Use the option NOTITLES. Since the input */
/* file already has titles, do not repeat */
/* printing of the titles. */
file &output notitles n=ps line=cur_line
/* If the user does not want output to go to */
/* the OUTPUT window, then use these options: */
/* PRINT and OLD. */
%if "%upcase(&output)" ne "PRINT" %then %do;
print old
%end;
;
/* Read a line of output. Scan for carriage */
/* control and page number. */
input @&strt_col record $varying200. len ;
/* If the line begins with carriage control, */
/* read the page number and replace it */
/* with blanks. */
if substr(record,2,10) eq
"&cc&cc&cc&cc&cc&cc&cc&cc&cc&cc" then
do; /* Page eject detected. */
/* If the line is a carriage control */
/* line, read in the next (blank) line. */
input;
/* Read a line of output. */
input @&strt_col record $varying200. len ;
/* Scan for the current page number. */
pagenum=reverse(scan(reverse(record),1));
/* Replace the original page number with*/
/* blanks because the page number will */
/* be printed in a different place. */
/* To do this, use the SUBSTR function */
/* on the left side of the equal sign. */
/* In this case SUBSTR works as a text */
/* replacement function. So, replace the*/
/* text from the first character of the */
/* page number to the end of the line. */
substr(record,
length(trim(record)) - length(pagenum) +1,
length(pagenum)) = ' ';
/* If user requests page number text at */
/* top of page and requests to increase */
/* page size, then add &PSADD blank */
/* lines to the top of each page. */
if (&psadd gt 0) and
("%upcase(%substr(&topbot,1,1))" eq "T")
then
do; /* Add blank lines to top of page. */
do line_num = 1 to %eval(&psadd);
put #line_num @1 ' ';
end;
end; /* Add blank lines to top of page. */
end; /* Page eject detected. */
/* Write the record back to the file. */
if _n_ gt 1 then
do; /* Write regular record back to file.*/
put @1 record $varying200. len @;
if cur_line lt &pagesize then put;
end; /* Write regular record back to file.*/
/* Look ahead to the next input line to */
/* determine if it has a carriage control */
/* character. If it does, write the page */
/* number on the current page. */
input @2 next_cc $10. @@;
/* If look-ahead detects a page eject, write */
/* page number text. */
if next_cc eq "&cc&cc&cc&cc&cc&cc&cc&cc&cc&cc"
and pagenum ge 1 then
do; /* Write page number text. */
/* Use macro logic to set the line and */
/* column placement for the page-numbering */
/* text. */
/* Place text at top or bottom of the page.*/
/* If T, place text on line 1. */
%if %substr(%upcase(&topbot),1,1) eq T %then
%let line=1;
/* Otherwise place text on last line */
/* (same as page size). */
%else
%let line=&pagesize;
/* Place text at left, center, or right. */
%if %substr(%upcase(&just),1,1) eq L %then
%let col=1;
%else
%if %substr(%upcase(&just),1,1) eq C %then
%let col=(floor((&linesize-length(text))/2));
%else
%if %substr(%upcase(&just),1,1) eq R %then
%let col=(&linesize-length(text)-1);
/* Concatenate text for page numbering. */
text=%unquote(&maketext);
put #&line @&col text;
put _page_;
end; /* Write page number text. */
return; /* End of main part of DATA step */
/* To avoid a lost card problem, jump to end */
/* of data when eof is read in input file. */
eof:
/* Concatenate text for page numbering. */
text=%unquote(&maketext);
put #&line @&col text;
run; /* End of entire DATA step */
/* Delete temporary system files. */
%if &sysscp=HP 800 %then
%do;
X 'rm potemp1.txt potemp2.txt';
%end;
%else %if &sysscp=OS2 or &sysscp=WIN %then
%do;
X 'del potemp1.txt';
X 'del potemp2.txt';
%end;
%else %if &sysscp=VMS %then
%do;
X 'del potemp1.txt;*';
X 'del potemp2.txt;*';
%end;
%else %if &sysscp=CMS %then
%do;
X 'erase potemp1 txt a';
X 'erase potemp2 txt a';
%end;
/* No action is needed for &SYSSCP=OS. Files are */
/* deleted automatically when the SAS session */
/* ends. */
/* Set the page size and page number to the */
/* original settings. */
options ps=%eval(&pagesize-&psadd)
pageno=%eval(&lastpage+1);
%mend pageof;