Macros for SAS Application Developers
https://github.com/sasjs/core
Loading...
Searching...
No Matches
mp_ds2pdf.sas
Go to the documentation of this file.
1/**
2 @file
3 @brief Create a PDF report from a dataset using ODS
4 @details Renders a dataset as a paginated PDF with ODS PDF, so the output
5 carries real typesetting - proportional fonts, repeated column headers on
6 every page, titles, footnotes and page numbers - rather than a picture of a
7 grid. Values are printed through PROC PRINT, so SAS formats and labels are
8 applied to what the reader sees.
9
10 This is the server-side half of a download pair: send the result with
11 `mp_streamfile` using `contenttype=PDF`.
12
13 Usage:
14
15 filename mypdf temp;
16 %mp_ds2pdf(sashelp.class, outref=mypdf
17 ,title=Class listing
18 ,footnote=Class table - generated &sysdate9);
19
20 %mp_streamfile(contenttype=PDF,inref=mypdf,outname=class.pdf)
21
22 Handling of the ODS session: the macro opens its own PDF destination, sets
23 titles and footnotes for the duration of the report and clears them on the
24 way out, so its own text does not leak into whatever the caller prints next.
25 It does not save and restore the caller's titles, even though they can be
26 read: SASHELP.VTITLE holds the current lines (TYPE T and F). Re-emitting
27 them means rebuilding SAS statements out of free text that may contain
28 quotes, semicolons or macro triggers, which is a separate problem this macro
29 does not take on - so a caller that needs its own titles back after the
30 report should capture them from SASHELP.VTITLE and set them again.
31
32 The page numbers are written with the `(*ESC*)` inline-function prefix
33 rather than with an escape character, so the report does not depend on the
34 caller having run `ods escapechar=`.
35
36 @param [in] ds The dataset to print. A name without a libref is read from
37 WORK.
38 @param [out] outref= (pdfout) Fileref to contain the PDF. Assigned as a TEMP
39 file if it is not already allocated.
40 @param [in] title= Optional report title, printed on every page.
41 @param [in] footnote= Optional footnote text. The page number is appended to
42 it (see `pagenum=`), so pass the provenance here - dataset, filter, user,
43 timestamp - and leave the numbering to the macro.
44 @param [in] by= Optional space separated BY variables. Each BY group starts a
45 new page, so the data must already be sorted by those variables.
46 @param [in] maxobs= (0) Maximum number of observations to print. 0 prints
47 every observation. Use it to cap very large tables, and say so in
48 `footnote=` when a cap has been applied.
49 @param [in] options= Additional options for the ODS PDF statement, for
50 example `fontsize=9`. The statement always carries `notoc`.
51 @param [in] style= Optional ODS style. Left to the site default when blank.
52 @param [in] orientation= (LANDSCAPE) Page orientation, one of LANDSCAPE or
53 PORTRAIT. This is a SAS system option rather than an ODS PDF attribute, so
54 the previous setting is restored on the way out.
55 @param [in] pagenum= (PAGE) Numbering appended to the footnote. Supported
56 values:
57 @li PAGE - "Page n"
58 @li PAGEOF - "Page n of m"
59 @li NO - no page number
60 The numbers come from the ODS inline functions `{thispage}` and
61 `{lastpage}`, written behind the `(*ESC*)` prefix so no ODS ESCAPECHAR has
62 to be set. SAS documents `{lastpage}` as preproduction, and warns that
63 images and URL links can be dropped from a title or footnote that uses it -
64 this macro prints a table only, so the total is safe here, but that is why
65 it is not the default.
66
67 @return The PDF is written to `outref`. No dataset is created.
68
69 <h4> SAS Macros </h4>
70 @li mp_abort.sas
71
72 @author 4GL Apps Ltd
73 @copyright 4GL Apps Ltd. This code may only be used within Data Controller
74 and may not be re-distributed or re-sold without the express permission of
75 4GL Apps Ltd.
76
77**/
78
79%macro mp_ds2pdf(
80 ds
81 ,outref=pdfout
82 ,title=
83 ,footnote=
84 ,by=
85 ,maxobs=0
86 ,options=
87 ,style=
88 ,orientation=LANDSCAPE
89 ,pagenum=PAGE
90)/*/STORE SOURCE*/;
91
92%local obsopt foot pagetxt odsopts prev_orient;
93
94%if %index(&ds,.)=0 %then %let ds=WORK.&ds;
95%let ds=%upcase(&ds);
96%let pagenum=%upcase(&pagenum);
97%let orientation=%upcase(&orientation);
98
99%if %sysfunc(exist(&ds))=0 %then %do;
100 %mp_abort(
101 msg=Dataset &ds does not exist
102 ,mac=&sysmacroname
103 )
104%end;
105
106/* the fileref is only assigned when the caller has not done so */
107%if %sysfunc(fileref(&outref)) > 0 %then %do;
108 filename &outref temp;
109%end;
110
111%let obsopt=;
112%if %eval(&maxobs)>0 %then %let obsopt=(obs=&maxobs);
113
114/* the caller's footnote keeps its own text; numbering is appended to it.
115 (*ESC*) is used rather than an escape character so the inline functions work
116 without the caller having set ods escapechar. */
117%if &pagenum=PAGE %then %let pagetxt=Page (*ESC*){thispage};
118%else %if &pagenum=PAGEOF %then
119 %let pagetxt=Page (*ESC*){thispage} of (*ESC*){lastpage};
120
121%let foot=&footnote;
122%if %length(&pagetxt)>0 %then %do;
123 %if %length(&foot)>0 %then %let foot=&foot &pagetxt;
124 %else %let foot=&pagetxt;
125%end;
126
127%let odsopts=notoc;
128%if %length(&options)>0 %then %let odsopts=&odsopts &options;
129
130%let prev_orient=%sysfunc(getoption(orientation));
131options orientation=&orientation;
132
133ods pdf file=&outref &odsopts
134 %if %length(&style)>0 %then style=&style;;
135
136/* each of these needs its own %do block: a bare "%then statement;" swallows
137 the semicolon as part of the %if, so the statements after it are read as a
138 continuation of the same statement */
139%if %length(&title)>0 %then %do;
140 title "&title";
141%end;
142%if %length(&foot)>0 %then %do;
143 footnote "&foot";
144%end;
145
146proc print data=&ds&obsopt noobs;
147 %if %length(&by)>0 %then %do;
148 by &by;
149 %end;
150run;
151
152ods pdf close;
153title;
154footnote;
155options orientation=&prev_orient;
156
157%mend mp_ds2pdf;