I was building a nice little PDF report the other day. I love the way ODS PDF replicates the SAS Results window navigation structure as PDF bookmarks, but... I'd much rather write the text for the bookmarks myself. So, I decided to "use the SAS" and make ODS do my bidding. I was able to use ODS PROCLABEL to change the top-level titles, but those second level bookmarks had a mind of their own, and I couldn't seem to get rid of them. Cynthia Zender showed me how to trick PROC REPORT to produce the desired result, but I had to resort to ODS DOCUMENT trickery to bend SAS/Graph to my will. For my very first "Jedi SAS Tricks" blog, I thought I'd share the adventure (and the code) with you. Don't worry - we'll not dabble in the dark side...
My first attempt went something this:
%let ReportFile=!USERPROFILE\Desktop\test.pdf; %macro MakeReport; options nonumber nodate; %do i= 1 %to 3; %global Bookmark&i; %let Bookmark&i=%SCAN(%STR(Chevrolet,Ford,Honda),&i); ods proclabel="&&BookMark&i Table"; title "&&BookMark&i Automobiles"; proc report data=sashelp.cars (obs=10) nowd contents=''; where make = "&&BookMark&i" ; column Model Type Invoice MSRP; define MSRP / order descending ; run; %end; goptions dev=pdf; %do i= &i %to %eval(&i+3); %global Bookmark&i; %let Bookmark&i=%SCAN(%STR(Chevrolet,Ford,Honda),%eval(&i-3)); ods proclabel="&&BookMark&i Graph"; title "&&BookMark&i Automobiles"; proc gchart data=sashelp.cars (obs=10); where make = "&&BookMark&i" ; hbar3d Model / Sumvar=MSRP; run;quit; %end; options date number; goptions reset=all; %mend; ods _all_ close; ods PDF file="&ReportFile" style=journal pdftoc=1; %MakeReport ods pdf close; ods listing; title;
The PDF bookmarks looked like this:
I asked Cynthia Zender, a fellow SAS instructor (who also blogs here at sas.com) for a bit of help. She suggested adding a non-printing GROUP column to the PROC REPORT step, and setting the BREAK contents to "" (blank). Here's the modified code:
%macro MakeReport; options nonumber nodate; %do i= 1 %to 3; %global Bookmark&i; %let Bookmark&i=%SCAN(%STR(Chevrolet,Ford,Honda),&i); ods proclabel="&&BookMark&i Table"; title "&&BookMark&i Automobiles"; proc report data=sashelp.cars (obs=10) nowd contents=''; where make = "&&BookMark&i" ; /** Add Make to the column (the value of MAKE will be the same for all rows) **/ column Make Model Type Invoice MSRP; define MSRP / order descending ; /** Define Make as non-printing group**/ define Make / group noprint; /** Set a break for Make with CONTENTS option missing**/ break before Make / contents="" page; run; %end; goptions dev=pdf; %do i= &i %to %eval(&i+3); %global Bookmark&i; %let Bookmark&i=%SCAN(%STR(Chevrolet,Ford,Honda),%eval(&i-3)); ods proclabel="&&BookMark&i Graph"; title "&&BookMark&i Automobiles"; proc gchart data=sashelp.cars (obs=10); where make = "&&BookMark&i" ; hbar3d Model / Sumvar=MSRP; run; quit; %end; options date number; goptions reset=all; %mend; ods _all_ close; ods PDF file="&ReportFile" style=journal pdftoc=1; %MakeReport ods pdf close; ods listing; title;
The result was perfect for those portions of the PDF generated by PROC REPORT, but I still had an issue with the bookmarks generated by PROC GCHART.
Fortunately, my Jedi training included other facets of ODS. It was time to unleash the power of ODS DOCUMENT! I decided to use the original %MakeReport macro, but this time I'd send the output to ODS DOCUMENT. Then, I'd write a second macro to modifiy the bookmarks and replay the results to a PDF file. Here's the additional code:
%let ReportFile=!USERPROFILE\Desktop\test.pdf; %macro FixBookmarks(ReportFile); %local n; /** Find the bookmark names in the ODS document **/ ods all close; ods output properties=PropertiesData; proc document name=MyPdf; list / levels=all; run; quit; ods output close; /** How many bookmarks are we talking about? **/ proc sql noprint; select count(*) into :N from PropertiesData where type ne "Dir" ; %let N=&n; /** Get bookmark info into macro variables **/ select cats(path) into :B1-:B&n from PropertiesData where type ne "Dir" ; %put local; quit; /** Build the PDF **/ ods all close; ods pdf file="&ReportFile" style=Journal; proc document; doc name=MyPDF; /** for each bookmark in the document **/ /** First, work through the tables **/ %do i=1 %to 3; copy &&B&i to \G&i; /** set the Bookmark text **/ setlabel \G&i "%trim(&&Bookmark&i Table)"; /** replay that protion of the document **/ replay \G&i; %end; /** Next, work through the graphs **/ %do i=4 %to &N; copy &&B&i to \G&i; /** set the Bookmark text **/ setlabel \G&i "%trim(&&Bookmark&i Graph)"; /** replay that protion of the document **/ replay \G&i; %end; quit; ods pdf close; ods listing; %mend; /** First, use the MakeReport macro to create an ODS DOCUMENT **/ ods _all_ close; ods document name=MyPDF(write); %MakeReport ods document close; ods listing; title; /** Then, use the FixBookmarks macro to the PDF with custom bookmarks **/ %FixBookmarks(&ReportFile);
The final results were quite satisfactory:
Using the power of SAS, we have crushed the opposition and brought order to our PDF bookmarks. Until next time, may the SAS be with you.
Stay SASy
Mark
1 Comment
Glad to have found your post on support.sas.com. I had the same problem and the Proc Report trick suggested here worked perfectly!! Thanks.