Jedi SAS Tricks: Custom PDF Bookmarks

1

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

Share

About Author

SAS Jedi

Principal Technical Training Consultant

Mark Jordan (a.k.a. SAS Jedi) grew up in northeast Brazil as the son of Baptist missionaries. He served 20 years as a US Navy submariner, pursuing his passion for programming as a hobby. Upon retiring from the Navy in 1994, he turned his hobby into a dream job and has been a SAS programmer ever since. Mark writes and teaches a broad spectrum of SAS Foundation programming classes, and is proud to announce his first book, "Mastering the SAS® DS2 Procedure: Advanced Data Wrangling Techniques". When he isn’t writing, teaching, or posting “Jedi SAS Tricks” here on the SAS Learning Post, Mark enjoys playing with his grand and great-grandchildren, hanging out at the beach and reading science fiction novels. His secret obsession is flying toys – kites, rockets, drones – and though he usually tries to convince Lori that they are for the grandkids, she isn't buying it. Mark lives in Toano, VA with his wife, Lori, and their cat, the amazing Tiger Man. To connect with Mark, check out his SAS Press Author page, follow him on Twitter @SASJedi or connect on Facebook or LinkedIn.

Related Posts

1 Comment

  1. 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.

Leave A Reply

Back to Top