BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.

I'm reading existing text files containing new-page characters into PROC DOCUMENT, but to paginate them correctly in ODS PDF I have to replay the individual pages separately.

 

In the file processing I've extracted the 2 title lines of each file into &line3 and &line4, and I'd really like those macro variables to be used in a bookmark for only the 1st page of each file:

%macro import_text(file=, orient=landscape);
  filename source catalog 'work.import';

  data _null_;
    length line $120;
    retain i prev_n 0;
    infile &file. truncover end = eof;
    input line $char120.;
    file source(import.source);
    if _N_ = 3 then call symput('line3', strip(line));
    else if _N_ = 4 then call symput('line4', strip(line));
    else if substr(line, 1, 1) = '0C'x then do;
      ** If first character is page break, then generate replay **;
      i + 1;
      if i = 1 then do;
        put 'replay textfile (where = (_obs_ lt ' _N_ '));';
      end;
      else do;
        put 'replay textfile (where = (' prev_n ' le _obs_ lt ' _N_ '));';
      end;
      prev_n = _N_;
    end;
    if eof then do;
      put 'replay textfile (where = (' prev_n ' le _obs_));';
    end;
  run;
    
  options orientation = &orient. nodate;

  title " ";

  proc document name = import(write);
    import textfile = &file. to ^;
    obpage \textfile / after;
    setlabel ^ "&line3.: &line4.";
    %include source(import.source) / source2;
  run;
  quit;
%mend import_text;

ods listing close;

ods pdf file = "~/osi_listings/output/osi_listings.pdf"
       pdftoc = 1 contents = yes;

%import_text(file="~/osi_listings/output/lo_hru.L10")
%import_text(file="~/osi_listings/output/lo_s_dth.L10")
%import_text(file="~/osi_listings/output/lo_s_ae_irr.L10")

ods pdf close;

However, when I generate the PDF files I'm getting bookmarks for every page with the full path name of that file:

SAS Communities - TOC 2021-05-26.png

How can I update my program to use &line3 and &line4 in the bookmarks instead?

 

Philip R Holland
Recent book (see my blog site): "SAS Programming Experiences: A How-To Guide from a Power SAS User"
1 ACCEPTED SOLUTION

Accepted Solutions
hollandnumerics
Pyrite | Level 9

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.

Philip R Holland
Recent book (see my blog site): "SAS Programming Experiences: A How-To Guide from a Power SAS User"

View solution in original post

7 REPLIES 7
jimbarbour
Meteorite | Level 14

Well, if someone like you doesn't know how to do it...

 

Well, let me think about it and see if anything bubbles up to the surface if I examine the problem but deliberately don't think about it for a while.

 

Jim

hollandnumerics
Pyrite | Level 9

Hi Jim,

 

I think the issue is centred around IMPORT TO, because ODS PROCLABEL and SETLABEL both fail to update the bookmark text.

 

What would be really nice would be being able to update/remove the bookmarks individually for each page, but I think that may not be possible in this release!  🙄

 

I'll look forward to your thoughts............Phil

Philip R Holland
Recent book (see my blog site): "SAS Programming Experiences: A How-To Guide from a Power SAS User"
hollandnumerics
Pyrite | Level 9

Version 2 of my macro copies the textfile into separate document folders and then uses REPLAY with WHERE to select each page. This gives me 2-level bookmarks, as I can now show the BY value from each page too, but I'm still seeing duplicate bookmarks with 1 bookmark for every page.

 

In version 3 I'm going to try splitting up the input file into pages before using IMPORT in PROC DOCUMENT, and then I'm hoping to be able to switch BOOKMARKGEN on or off for each page.

Philip R Holland
Recent book (see my blog site): "SAS Programming Experiences: A How-To Guide from a Power SAS User"
Tom
Super User Tom
Super User

Can you post the steps you are actually running without the macro to generate them?  For example for two files where one of them has at least two pages.

 

Once you figure out how to change the code the generate the PDF file you can then modify the macro to generate that code.

hollandnumerics
Pyrite | Level 9

Hi Tom,

 

The following steps are being done in version 3 of my program and macro for each input text file:

  1. Scan the listing file records to:
    • find '0C'x, which marks the 1st record of a new page (IMPORT TO in PROC DOCUMENT ignores new page control characters!) or the last record, and save the records of the beginning and end of page in WORK.PAGES.
    • extract the 3rd (line-3) and 4th (line-4) records of the file, which contain the identification number and title of the listing.
    • extract the records that begin with a specific string, in this case "sex:", into LINE6 and save in WORK.PAGES, along with the page number, too.
    • save each record of the input file in WORK.LISTINGS.
  2. Create a new DOCUMENT in WORK.IMPORT with a new folder labelled as "line-3: line-4".
  3. Copy the records for each page specified in WORK.PAGES from WORK.LISTINGS into individual temporary text files.
  4. Import each individual text files into a new folder in WORK.IMPORT using a separate PROC DOCUMENT step with IMPORT TO. If the page is the first with each LINE6 value, then, before starting PROC DOCUMENT, set ODS PDF BOOKMARKGEN, otherwise set ODS PDF NOBOOKMARKGEN.
  5. REPLAY each individual page in PROC DOCUMENT.

Repeat of these steps for each input text file.

 

The resulting PDF file looks like this, where text_listings1.txt has 2 pages of "sex: M", but only 1 page is seen in the bookmarks. This means that I now have the correct number of bookmarks, but the top level containing the listing number and title are duplicated. The 3 listing text files shown below are attached to this post.

 

osi_listingsv3 20210528.jpg

 

Using REPLAY to display the whole folder at the same time arranges the bookmarks as pages under the top level of the listing, but does not omit the duplicates. This is because the underlying data in the document has no information about whether a bookmark should be created or not, so a bookmark is created anyway.

 

osi_listingsv3 document 20210528.jpg

I suspect that the solution is probably post-processing of the full bookmark list to remove the duplicates using software other than SAS!

 

Does that make any sense at all?..................Phil

Philip R Holland
Recent book (see my blog site): "SAS Programming Experiences: A How-To Guide from a Power SAS User"
hollandnumerics
Pyrite | Level 9

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.

Philip R Holland
Recent book (see my blog site): "SAS Programming Experiences: A How-To Guide from a Power SAS User"
hollandnumerics
Pyrite | Level 9

The final PDF and bookmarks look like this:

hollandnumerics_0-1633126261190.png

 

Philip R Holland
Recent book (see my blog site): "SAS Programming Experiences: A How-To Guide from a Power SAS User"

SAS Innovate 2025: Call for Content

Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!

Submit your idea!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 7 replies
  • 3066 views
  • 0 likes
  • 3 in conversation