• Print  |
  • Feedback  |

Knowledge Base


TS-548

Page 1 of n: Customizing Page Numbers


The SAS System prints page numbers at the upper right-hand corner of the page.
The PAGEOF macro allows you to print page numbers at the top or bottom of the
page, at the left, center, or right, and with your own phrasing (for example,
"Page 1 of 10"). You can also add blank lines between the page number and text.
The construction of PAGEOF shows how to make the macro portable across several
operating systems.
PAGEOF works at releases 6.07.03 (TS level 301) and later releases of the SAS
System.
Introduction
The first and second sections of this article show you how to use the PAGEOF
macro without going into programming details. A second portion of the article
discusses the construction of PAGEOF, which uses advanced DATA step and SAS
macro language programming.  References and instructions for downloading PAGEOF
appear at the end of the article.  Appendix 1 contains the text of PAGEOF, and
Appendix 2 contains the resolved text using the default parameter values.

Accessing PAGEOF
Access the PAGEOF macro in any of these four ways:
copy the code to the beginning of your program, include the code into your 
program with a %INCLUDE statement, copy the code into an autocall library 
on your system, or store the macro in compiled form (a stored compiled macro).

With the first two methods, be sure that the definition of PAGEOF appears before
its invocation.
To invoke PAGEOF, use the form shown here:

%PAGEOF(PGM=SAS program whose output is to be customized,
OUTPUT=file to contain customized output<,>



 )

Except for PAGETEXT=, the parameter values are not case sensitive.
This table defines the parameters:
Parameter    Default   Definition

PGM=         none      is the program whose output will receive customized page
                       numbers. The value can be a file name in quotes or a
                       fileref. PGM= is required.

OUTPUT=      none      is the file to contain the customized output. The
                       value can be a file name in quotes or a fileref.
                       OUTPUT= is required.

JUST=        CENTER    aligns the customized page numbers within the
                       line.

TOPBOT=      BOTTOM    places the customized page numbers on the first
                       or last line of the page.

PSADD=       2         adds lines to the page size for the customized
                       page numbers. The default value creates one new
                       line for the page numbers and leaves one blank
                       line between the page numbers and the output.  If
                       the customized phrase is long and you do not add
                       any lines, the customized page numbers may
                       overwrite part of the output.  If you plan to add
                       lines with PSADD=, make the page size in the
                       submitted program shorter than usual so the
                       result fits the page. For example, if you usually
                       use a page size of 60 and you plan to add two
                       lines with PSADD=, make the page size in your
                       program 58.

PAGETEXT=  PAGEX of PAGEN  is a phrase representing the customized page
number. It must contain the uppercase strings PAGEX (representing the number of the
current page) and PAGEN (representing the total number of pages in the output).
CC=         !          contains a character to be used in producing
                       temporary internal page divisions.  You do not
                       need to change the value unless a long series of
                       exclamation points already appears in your output
                       (for example, as the border of a box).


Using PAGEOF
The PAGEOF macro works by submitting a program and post-processing the output.
The user does not see any SAS code in the program or the post-processing stage.
This system eliminates the possibility of accidentally changing the output
during post processing because it requires no human intervention for the post
processing.
Caution: The PAGEOF macro depends on two PROC PRINTTO steps and two system
options (FORMDLIM= and NONUMBER).  Using PAGEOF with an application that
contains any of these features may cause unexpected results. Before using PAGEOF
with such an application, you should test PAGEOF thoroughly. In that case you
should also understand the overall construction of PAGEOF to discover how the
features may interact.
Examples
Suppose you want to change the placement of page numbers in some PROC PRINT
output to the default PAGEOF arrangement. Submit this code to produce Output 1:
%pageof(pgm='your-SAS-source',
output='new-output-file')


***OUTPUT IS IN /dept/pub/obs/issue15/features/Sharpe/output1 ***
To change the appearance of the page numbers and other characteristics as shown
in Output 2, invoke the macro with different values:
%pageof(pgm='your-SAS-source',
output='new-output-file',
just=RIGHT,
topbot=TOP,
psadd=0,
pagetext=PAGEX/PAGEN)


***OUTPUT IS IN /dept/pub/obs/issue15/features/Sharpe/output2 ***
Programming PAGEOF
PAGEOF is a long macro. This article discusses the highlights of programming it
but does not explain every line of code. For explanations of individual features,
see the references listed at the end of this article.
For clarity, Appendix 1 divides PAGEOF into eight sections:
1.	Pass information into the macro
2.	Initialize the programming environment
3.	Produce output with standard page numbers
4.	Restore option settings
5.	Find the ending page number of the output
6.	Create the template for the new output
7.	Read old output and write new output
8.	Clean up the programming environment.

/* Sections 1, 2, and 4 set up the programming environment and ensure the portability
of the code between different operating systems.  Sections 3, 5, 6, and 7 write new
page numbers in place of the old ones.  (However, part of what these sections do is
controlled by code that makes the program portable.) Section 8 cleans up the
programming environment for future use.  The remainder of this article discusses
each section in order.
1.	Pass information into the macro */

%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 */

      );



/*This section begins the macro definition and gives default values to some of
the parameters.
2.	Initialize the programming environment */

/* 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;

/* To illustrate dynamic file allocation and the portability of PAGEOF, this
section sets up a programming environment suitable for several different
operating systems.  The automatic macro variable SYSSCP detects the current
operating system.
The key to PAGEOF's portability is the use of the FORMDLIM= system option.
Different operating systems use different characters to indicate the beginning
of a new page, and they also place them in different locations.  FORMDLIM=
eliminates the original page-eject (carriage-control) codes. Instead, between
"pages" it writes a line of blanks, a line of the specified character (in this
case an exclamation point) and another line of blanks. Code that detects these
lines appears in other sections of the program.
Another technique for portability is the use of the STRT_COL macro variable.
The value of STRT_COL controls the column at which the SAS System starts reading
lines of the original output. Under most operating systems, the SAS System
starts at column 1. Under MVS and CMS, however, column 1 controls the printer or
the previewing software, and what you see as column 1 of the displayed output is
column 2 of the output file.  By making STRT_COL a macro variable, the program
can change the starting column to fit the current operating system.
3.	Produce output with standard page numbers */

/* 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;

/* In this section a %INCLUDE statement includes and executes the SAS program whose
output you want to customize.  The PROC PRINTTO step that surrounds the %INCLUDE
statement directs the output to a file identified as TEMP1 and then returns the
output destination to the default.
Output 3 shows a section of TEMP1.
*** OUTPUT IS IN /dept/pub/obs/issue15/features/Sharpe/output3 ***  
4.	Restore option settings  */

/* 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;
The previous section of code executed your SAS program and produced a temporary file
of output. This section restores the default carriage control. It also obtains the
line size and page size and stores them in macro variables for later use.
(In Release 6.07.03 and later releases, the current settings of SAS System options
are available through a SAS data view named VOPTION stored in the SASHELP library.)
5.	Find the ending page number of the output */

/* 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;

/* In section 5, the PROC PRINTTO step writes one page of output to the file
identified by TEMP2.  Since this page immediately follows the previous output,
its page number is one more than the number of the last page of the user's
output. The DATA _NULL_ step reads the first line of TEMP2, which contains the
page number.  Functions calculate the number of the previous page of output, and
the SYMPUT routine assigns the value to the macro variable LASTPAGE.  These
lines illustrate the stages in obtaining the number of the previous page of
output:
*** FIGURE IS IN /dept/pub/obs/issue15/features/Sharpe/figure1 ***

For a more detailed explanation of how to obtain the number of the last page of
output, see the I/O article "******need the name-by Russ Tyndall********" in the
previous issue of Observations.

6.	Create the template for the new output */

/* 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);

/* Section 6 first sets the options for the new output.  The new output does not
need an additonal date or page number (NODATE NONUMBER).  The page size of the
new output (PS=&PAGESIZE) will be the old page size plus any lines added as
the value of of PSADD= in the macro invocation.  (The default value of PSADD= is
2.)
The rest of the section analyzes the string representing the new page number
(the parameter PAGETEXT) and determines where in the string to put the current
page number and the ending page number (represented by PAGEX and PAGEN).
By the end of the section the complete string that will generate the customized
page numbers is the value of the macro variable MAKETEXT.
It is important to understand that MAKETEXT does not contain the customized page
numbers themselves. Page numbers change on each page of the new output and need
to be produced during iterations of a DATA step.
Instead, MAKETEXT contains a DATA step expression that forms a template
for the customized page numbers. (The expression becomes part of an
assignment statement in Section 7.) For example, using the default
values that produced Output 1, the value of macro variable MAKETEXT is
"Page "||trim(left(put(pagenum,6.)))||" of 16"

One final note: several macro quoting functions appear in this section 
(%NRBQUOTE, %QSUBSTR, and %NRSTR). You don't need to understand these in 
detail to use PAGEOF.  The point is that your customized text may contain 
characters that the macro processor interprets as operators. For example, 
the slash in PAGEX/PAGEN is a division operator to the macro processor.  
Using quoting functions prevents such characters from being interpreted 
as operators. They are a precaution that increases the variety of characters 
available to the customized text.
7.	Read the old output and write the new output */

/* Section 7 is the longest section in PAGEOF. It performs three tasks:
1.	Setting up the file for the new output.
2.	Reading and editing the original output. This includes reading the
original lines, eliminating the lines added by FORMDLIM=, obtaining the
number of the current page, and blanking out the original page number.
3.	Writing the new output. This includes writing lines of output to the
new file, writing extra lines if you requested them with the PSADD=
parameter, and writing the customized page number.

Set up the file for the new output */
/* 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;
      ;

/* The first part of section 7 sets up the destination file for the
customized output.  Specifying N=PS in the FILE statement allows
the output pointer to write on any line of the current output
page. It is important here because the customized page number
is the last thing written on each page, regardless of whether
it appears at the top or bottom of the page. If the destination
of the report is not the SAS procedure output file (PRINT), the
section also ensures that the destination file has printer-control
characters and that output is written to the beginning of the file
(PRINT OLD).
Using new-output-file as the destination for the new output produces
this FILE statement:
file new-output-file notitles n=ps line=cur_line print old;
Read and edit the original output */
      /* 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)) = ' ';


The INPUT statements in this section read lines from the original output file
and, if they were produced by the FORMDLIM= option, eliminate them.  An
assignment statement creates PAGENUM and assigns it the page number from the
record containing the number, as shown:
pagenum=reverse(scan(reverse(record),1));

The final editing task is to blank out the original page number. The
SUBSTR function, in addition to its more familiar uses, can place
characters into specified positions in a text string. In this situation
SUBSTR appears on the left side of the equal sign in an assignment
statement. Its syntax is
SUBSTR(target,start-position,length)=characters;
In the final assignment statement of the section, RECORD is the target, the
starting position to replace is the position of PAGENUM within RECORD, and the
length to replace is the length of the value of PAGENUM.
Note: this statement finds the position of PAGENUM by subtraction, not by using
the INDEX function.  If you are familiar with INDEX, you may wonder why it is
not appropriate. The answer is that INDEX locates the first occurrence of a
string, and if the title includes numbers, the page number may appear elsewhere
in the title. Consider this example:
Sales for 1995
19
When the value of PAGENUM is 19, the INDEX function locates the first two
characters of 1995. Calculating the position eliminates the problem.
Write the new output
/* 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 */

Each new output page contains the text from the original output, the customized
page number, and any additional lines you requested with the PSADD= parameter.
Since the first record of the original file is a FORMDLIM= record, writing a
record occurs only after the first iteration of the DATA step.  The PUT
statements write most records to the new output file. When the program detects a
series of exclamation points after writing other records, preparation begins for
writing the customized page number.
The new page number appears on either the first or last line of the page, based
on the value of TOPBOT= when you invoked PAGEOF.  It appears either at the left,
center, or right side of the page, depending on what you specified as the value
of JUST=.
The DATA step expression that will produce the customized page number is stored
in a macro variable named MAKETEXT that is referenced in an assignment
statement:
text=%unquote(&maketext);
It is not necessary to go into detail about the %UNQUOTE function, but it
appears as a precaution. The value of MAKETEXT was created with macro quoting
functions, and occasionally macro quoting functions alter a string so that,
although it looks normal, the DATA step parses it incorrectly. To avoid
potential problems, %UNQUOTE removes the effect of macro quoting functions.
Using the default values, the assignment statement becomes:
text="Page "||trim(left(put(pagenum,6.)))||" of 16";
The PUT statement that will write the customized page number is
put #&line @&col text;
Using the default values of the macro parameters, the PUT statement
becomes
put #23 @(floor((64 -length(text))/2)) text;

/* At the end of each page except the last, the _PAGE_ option in the PUT statement
forces a page eject.  When the program reads the last line of the original file,
it writes the final customized page number and the DATA step ceases execution.
8.	Clean up the programming environment */

/* 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;

This section shows the deletion of temporary files on operating systems that
require a deletion command. It also restores the line size and page size of the
SAS session to their original values and ends the macro definition.
Summary
The PAGEOF macro illustrates aportable solution to the common need to write the
total number of pages on each page of printout.  To achieve portability across
operating systems, it draws upon features of the DATA step and the SAS macro
facility.
Program Availability
The PAGEOF macro (pageof.sas) is available through Anonymous FTP. To use this
facility, connect to ftp.sas.com. Once connected, enter the following responses
as prompted:
Name (ftp.sas.com:userid): anonymous
Password: your E-mail address

You then enter
cd /observations/2q95/sharpe
get pageof.sas

Note that after you enter your password, all statements are case sensitive. If
you want to rename a file before downloading, you can enter a target file name
after the file name specified. To disconnect from Anonymous FTP, enter quit.
If you do not have a direct Internet connection, this file is available from SAS
Institute's Bulletin Board System (SIBBS), which you can access via modem at
(919) 677-8155.  To use the SIBBS service you must establish an account with SAS
Institute.  Once you have registered, from the SIBBS main menu select (D) for
download files.  From the Download Files Area select (F) for Observations(r)
article source code. After viewing the list of journal files select (D) to
download.  When prompted, enter the file name (pageof.sas).  After downloading
is complete, press RETURN to exit, then enter ! to logoff.
Appendix 1: The PAGEOF Macro
1.	Pass information into the macro

%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 */

      );


2.	Initialize the programming environment


/* 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;

3.	Produce output with standard page numbers


/* 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;


4.	Restore option settings


/* 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;

5.	Find the ending page number of the output


/* 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;

6.	Create the template for the new output


/* 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);

7.	Read the old output and write the new output

Set up the file for the new output
/* 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 and edit the original output
            /* 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)) = ' ';

Write the new output
/* 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 */
8.	Clean up the programming environment

/* 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;
Appendix 2: Program Generated By Default Parameter Settings
options number pageno=1 formdlim="!";
filename temp1 'temporary-file-1';
filename temp2 'temporary-file-2';
proc printto print=temp1 new;

%include 'your-input-program';
proc printto;
run;

options formdlim=";
data _null_;
set sashelp.voption;
where optname='LINESIZE' or optname='PAGESIZE';
	call symput(optname,setting); run;
proc printto print=temp2 new;
data _temp_;
x=1;

proc print;
options number;

proc printto;
run;

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;
options nodate nonumber ps#;
data _null_;
infile temp1 length=len truncover eof=eof noprint;

length text $ 200 next_cc $ 10;
retain pagenum;
file 'your-output-file' notitles n=ps line=cur_line
print old ;
input @1 record $varying200. len ;
if substr(record,2,10) eq "!!!!!!!!!!" then
do;
input; input @1 record $varying200. len ;
pagenum=reverse(scan(reverse(record),1));
substr(record, length(trim(record)) - length(pagenum) +1, length(pagenum)) = ' ';
if (2 gt 0) and ("b" eq "t") then
do;
do line_num = 1 to 2;
put #line_num @1 ' ';
end;
end;
end;
if _n_ gt 1 then
do;
put @1 record $varying200. len @; if cur_line lt 23 then put;
end;
input @2 next_cc $10. @@;
if next_cc eq "!!!!!!!!!!" and pagenum ge 1 then
do;
text="page " ||
trim(left(put(pagenum,6.))) || " of 16";
put #23 @(floor((64 -length(text))/2)) text;
put _page_;
end;
return;
eof: text="page " ||
trim(left(put(pagenum,6.))) || " of 16";
put #23 @(floor((64 -length(text))/2)) text; run;
x 'delete-temporary-files-if-needed';
options ps! pageno.;