Finally I have resolved the problem using the techniques suggested in a 2010 PhUSE paper by Stephen Griffiths in http://www.lexjansen.com/phuse/2010/ts/TS04.pdf. This method creates bookmarked PDF files accepted by the FDA!
The final program is given here, but the full explanation of the code can be found in VIEWS News 65 (see https://hollandnumerics.org.uk/wordpress/2021/09/views-news-65-2021q3-has-been-published/).
* Final Location for Listings and PDF file *;
%LET filelist = ./output;
%LET filepdf = ./output;
%LET offset = 1;
LIBNAME listings "&filepdf.";
* Name of SAS dataset with Bookmarks *;
%LET bookmark_ds = bookmark_listings;
* Name of PS file *;
%LET bm_ds = osi_bookmarks;
DATA _bookmarks_;
LENGTH filen $30;
group = 'temp ';
lpage = 0;
ltotpage = 0;
opage = 0;
ototpage = 0;
filen = '__temp.xxx';
OUTPUT;
RUN;
%MACRO outpage(
dsout = /* bookmark list */
,fname = /* name of input text file */
,ftype = /* file type */
,title = /* file title */
,orient = landscape /* Orientation */
,prefix = Sex /* Group text */
);
OPTIONS ORIENTATION = &orient.
NODATE NONUMBER;
TITLE " ";
DATA &fname. (KEEP = order group lpage
ltotpage);
LENGTH line2 $150
group $6
;
* Page number *;
RETAIN lpage 1 ltotpage 1;
INFILE "&filelist./&fname..&ftype."
TRUNCOVER END = eof;
INPUT;
* variable to retain the ordering *;
order = _N_;
FILE PRINT;
* at each page break insert a new page *;
SELECT;
WHEN (SUBSTR(_INFILE_, 1, 1) EQ '0C'X) DO;
line2 = SUBSTR(_INFILE_, 2);
PUT _PAGE_ @&offset. line2;
lpage + 1;
END;
WHEN (_INFILE_ EQ '')
PUT @&offset. _INFILE_;
OTHERWISE PUT @&offset. _INFILE_;
END;
* Total pages *;
IF _N_ = 1 THEN DO;
ltotpage =
INPUT(SCAN(_INFILE_, -1), BEST8.);
CALL SYMPUT('lpages',
PUT(ltotpage, ??BEST.));
END;
IF INDEX(UPCASE(STRIP(_INFILE_)),
UPCASE("&prefix.")) EQ 1 THEN DO;
group = STRIP(SCAN(STRIP(_INFILE_),
2, ':'));
OUTPUT;
END;
RUN;
PROC SORT DATA = &fname. OUT = &fname.;
BY group order;
RUN;
DATA &fname. (DROP = order);
SET &fname.;
BY group order;
IF FIRST.group;
LENGTH filen $30 ltitle $200;
filen = "&fname.";
ltitle = "&title.";
RUN;
PROC SORT DATA = &dsout. OUT = _maxp;
BY DESCENDING ototpage;
RUN;
%LET totp=;
DATA _NULL_;
SET _maxp;
IF _N_ = 1
THEN CALL SYMPUT('totp', ototpage);
RUN;
DATA &fname.;
SET &fname.;
opage = lpage + &totp.;
ototpage = ltotpage + &totp.;
RUN;
DATA &dsout.
(WHERE = (group NOT IN ('temp' ' ')));
SET &dsout. &fname.;
* Update total overall pages *;
ototpage = &lpages. + &totp.;
RUN;
%MEND outpage;
%MACRO create_bmfile(
dsin = /* bookmarks data set */
,psout = /* PS file to hold bookmarks */
);
PROC SORT DATA = &dsin.
OUT = &dsin._listings
(KEEP = ltitle opage ototpage filen)
NODUPKEY;
BY ltitle;
RUN;
PROC SORT DATA = &dsin.
OUT = &dsin._groups (KEEP = group)
NODUPKEY;
BY group;
RUN;
PROC SQL;
CREATE TABLE &dsin._template AS
SELECT *
FROM &dsin._groups
,&dsin._listings
ORDER BY
ltitle
,group
;
QUIT;
DATA &dsin._template (DROP = opage ototpage);
SET &dsin._template;
order = opage;
nodatapage = ototpage + 1;
RUN;
PROC SORT DATA = &dsin. out=&dsin.1;
BY ltitle group;
RUN;
DATA &dsin.2;
MERGE &dsin._template (IN = a)
&dsin.1 (IN = b)
;
BY ltitle group;
* groups not in files *;
IF a AND NOT b THEN DO;
opage = nodatapage;
END;
RUN;
PROC SORT DATA = &dsin.2 OUT = &dsin.3;
BY group order opage;
RUN;
PROC FREQ DATA = &dsin.3 NOPRINT;
TABLE group / OUT = &dsin._group_sub
(DROP = percent
RENAME = (count = sub_bm));
RUN;
DATA &dsin.4;
MERGE &dsin.3
&dsin._group_sub
;
BY group;
RUN;
DATA &dsin.4 (KEEP = group ltitle order opage
sub_bm filen)
listings.&bookmark_ds
(KEEP = group ltitle filen opage
sub_bm filen)
;
SET &dsin.4;
* Make the number negative so that the
bookmark is closed *;
sub_bm = sub_bm * -1;
RUN;
DATA _NULL_;
FILE "&psout." LS = 1000;
SET &dsin.4;
BY group order;
IF FIRST.group THEN DO;
PUT "[/Count " sub_bm "/Title (" group
" ) /Page " opage " /OUT pdfmark";
END;
IF FIRST.order THEN DO;
PUT "[/Title (" ltitle " ) /Page "
opage " /OUT pdfmark";
END;
RUN;
%MEND create_bmfile;
PROC TEMPLATE;
DEFINE STYLE groupfile;
parent = styles.Printer;
STYLE fonts FROM fonts /
"BatchFixedFont" =
("Courier New, Courier", 9.5PT)
"docFont" =
("Courier New, Courier", 9.5PT)
"FixedFont" =
("Courier New, Courier", 9.5PT)
;
END;
RUN;
ODS PDF FILE = "&filepdf./osi_listings.pdf"
STYLE = groupfile;
ODS PDF NOBOOKMARKGEN;
%outpage(
dset = &bm_ds.
,fname = test_listings1
,ftype = txt
,title = %nrstr(Listing 99.1)
,prefix = Sex
);
%outpage(
dset = &bm_ds.
,fname = test_listings2
,ftype = txt
,title = %nrstr(Listing 99.2)
,prefix = Sex
);
%outpage(
dset = &bm_ds.
,fname = test_listings3
,ftype = txt
,title = %nrstr(Listing 99.3)
,prefix = Sex
);
DATA _NULL_;
FILE PRINT;
PUT _PAGE_ @&offset.
'There is no data available.';
RUN;
ODS PDF CLOSE;
DATA &bm_ds.;
SET _bookmarks_;
RUN;
ODS _ALL_ CLOSE;
%create_bmfile(
dsin = &bm_ds.
,psout = &filepdf./&bm_ds..ps);
FILENAME cmd PIPE 'gswin64c -dBATCH -dNOPAUSE
-sDEVICE=pdfwrite
-sOutputFile=osi_listings_bm.pdf
osi_listings.pdf osi_bookmarks.ps';
DATA _NULL_;
INFILE cmd;
INPUT;
PUT _INFILE_;
RUN;
Note that some of the filenames may require folder locations to be added when you run this code on your own SAS platform.
... View more