Hello,
First time posting!
So, for my first question:
For one of our client, I had to make tables and listing (statistics) in RTF.
However, that client needed a specific format for the output. Instead of having the footnote simply put at the "footnote" area on the page, he needs to have it as close to the table end.
i.e.
A "classic" output would be
______________________________________________________ title ------------------------------------------------------ Var1 | Var2 | ... | Varn ------------------------------------------------------ Results...
------------------------------------------------------ Footnote (program name, page x of y) ______________________________________________________
However, what I need is:
______________________________________________________ title ------------------------------------------------------ Var1 | Var2 | ... | Varn ------------------------------------------------------ Results...
------------------------------------------------------ Footnote (program name, page x of y) ______________________________________________________
With SAS Base 9.3, a colleague made for us a vb script that actually renumber the pages.
However, now we are switching to SAS Studio 3.8, that workaround is not valid anymore.
I'm therefore looking for another way to do that output.
Note that a team member managed to move the footnote itself right under the table, but still the page numbers are not created.
We are using ODS RTF and proc report, with a macro to initialize the page.
But I'm open to any other suggestion to solve this (installing a MS Office on the App Server is not an option, of course)
Thanks a lot in advance.
BR,
Lionel
1) Why is footnote line broken and displayed as 2 lines ?
2) The footnote is displayed immediately after the table-text, without space lines
to bring the footnote to the last line of the page.
3) Is the footnote the same that should be in production ?
Next code works but you may find some issues as a result of above notes.
%let txt1 = Source Data: ; /* unique string to search for footnote */
%let txt = \f2 ; /* rtf control to strat show text - in this RTF format */
data _NULL_;
retain pages 0 ;
infile flat(test_rtf.rtf) truncover end=eof; /* adapt to RTF file created by SAS */
length a_line $400 text $60;
input a_line $char400.;
if index(a_line,"&txt1") then pages+1;
if eof then call symput('pages',strip(pages));
run;
data _NULL_;
retain pages pageno 0 ;
pages = symget('pages');
infile flat(test_rtf.rtf) truncover end=eof; /* adapt to RTF file created by SAS */
length a_line $400 new_line $400 text $60;
input a_line $char400.;
if index(a_line,"&txt1") then do;
pageno+1;
ix1 = find(a_line,"&txt"||" ");
ix2 = find(substr(a_line,ix1+3),"&txt"||" ") + ix1;
put ix1= ix2=;
if ix1 then text = substr(a_line,ix1); else text='';
file log;
if ix1 then put _N_= pageno= text=;
new_line = substr(a_line,1,ix2 +4) || put(pageno,2.) || '/' ||
put(pages,2.) || " pages " || substr(a_line,ix2);
put new_line=; /* for test only */
end;
else new_line = a_line;
file flat(new_rtf.rtf); /* adapt to new RTF file changed */
put new_line ;
if eof then put pages= ;
run;
1) Read that .rtf file asa text file, search for New Line control character or for the footnote,
in order to count total number of pages and assign it to a macro variable.
2) Reread the file, search for the footnote and add page number and the total pages.
check the control character or are there many space lines preceding the footnot
and adjust it to be immediate after the results.
Can you post the .rtf file with at least 3 pages for a test, having the same format as the full one.
Hi,
Sure, it is T_12.rtf, the file is in attachment.
At the bottom right corner, under the data, you'll see a "/", that was where my page numbers (x/y) should have theoretically been outputted :-/. but for some unknown reason, I can't number the page
Thanks a lot.
Lionel
1) Why is footnote line broken and displayed as 2 lines ?
2) The footnote is displayed immediately after the table-text, without space lines
to bring the footnote to the last line of the page.
3) Is the footnote the same that should be in production ?
Next code works but you may find some issues as a result of above notes.
%let txt1 = Source Data: ; /* unique string to search for footnote */
%let txt = \f2 ; /* rtf control to strat show text - in this RTF format */
data _NULL_;
retain pages 0 ;
infile flat(test_rtf.rtf) truncover end=eof; /* adapt to RTF file created by SAS */
length a_line $400 text $60;
input a_line $char400.;
if index(a_line,"&txt1") then pages+1;
if eof then call symput('pages',strip(pages));
run;
data _NULL_;
retain pages pageno 0 ;
pages = symget('pages');
infile flat(test_rtf.rtf) truncover end=eof; /* adapt to RTF file created by SAS */
length a_line $400 new_line $400 text $60;
input a_line $char400.;
if index(a_line,"&txt1") then do;
pageno+1;
ix1 = find(a_line,"&txt"||" ");
ix2 = find(substr(a_line,ix1+3),"&txt"||" ") + ix1;
put ix1= ix2=;
if ix1 then text = substr(a_line,ix1); else text='';
file log;
if ix1 then put _N_= pageno= text=;
new_line = substr(a_line,1,ix2 +4) || put(pageno,2.) || '/' ||
put(pages,2.) || " pages " || substr(a_line,ix2);
put new_line=; /* for test only */
end;
else new_line = a_line;
file flat(new_rtf.rtf); /* adapt to new RTF file changed */
put new_line ;
if eof then put pages= ;
run;
Hi,
Wow, that's a pretty code !
To answer to your questions:
1) Why is footnote line broken and displayed as 2 lines ?
This is the default footnote and It is actually one line, but from client SOP, we had to display this default footnote as :
the "first" part of the default footnote is the "Source Data : Listings^nL_[...].sas" left justified, then it is added to "Data Version[...]" center justification, and finally Table name with date and time of run plus the page number over all pages "T_[...].sas DDMMMYYYY hh:mm x/y". This footnote is "footnote1"
After that default footnote, we has as many footnotes as necessary with a limit of 10, starting from footnote2.
3) Is the footnote the same that should be in production ?
The production footnote is the same, yes. This is for tables. For listing, the "Source Data" part is removed.
For the tables, we use:
%let SList = Source Data: Listings %sysfunc(Strip(&Source_Listing));
%let foot1 = j=l "&SList" j=c "Data Version: &dataver, &datadate " j=r " &prog_name..sas &runTime ^{thispage}/^{lastpage}";
footnote1 &foot1. ;
For the listings, we use:
%let foot1 = j=c "Data Version: &dataver, &datadate " j=r " &prog_name..sas &runTime ^{thispage}/^{lastpage}"; footnote1 &foot1. ;
Then we add the rest of the footnotes, with a loop, to set footnote2 to footnote10 if relevant.
I still wonder if the missing page number has something to do with the template ? I don't see anything wrong.
%MACRO set_ods_rtf(outPath=,tfl_name=,adDsn=, Source_Listing=, option=bodytitle, startpage=YES);
ods path(prepend) work.templat(update);
proc template ;
define style newstyle ;
parent = styles.journal ;
class Parskip /
font = fonts("headingFont")
cellpadding = 0 cellspacing = 0 /* Only for Measured */
frame= void
Rules = NONE
BorderWidth = 0
Color = _undef_
BackGroundColor = _undef_;
style byline / font_face="Courier New" font_style=Roman background = white;
style Body from Document / font_face="Courier New" font_style=Roman background = white;
style data / font_face="Courier New" font_style=Roman ;
style table / font_face="Courier New" font_style=Roman
bordercolor=black background = white borderwidth=1 ;
style cellcontents / font_face="Courier New" font_style=Roman ;
style TitleAndNoteContainer / font_face="Courier New" font_style=Roman background = white;
style ProcTitle / font_face="Courier New" font_style=Roman ;
style systemtitle / font_face="Courier New" font_style=Roman ;
style rowheader from headersandfooters / font_face="Courier New" font_style=Roman ;
style BodyDate / font_face="Courier New" font_style=Roman ;
style PageNo / font_face="Courier New" font_style=Roman ;
style SysTitleAndFooterContainer / font_face="Courier New" font_style=Roman ;
style header from headersandfooters / font_face="Courier New" font_style=Roman background = white;
style SystemFooter / font_face="Courier New" font_style=Roman bordercolor=black background = white borderwidth=1 ;
style NoteContent / font_face="Courier New" font_style=Roman font_size=8pt;
end;
run ;
/*** Set ODS Environment ***/
options papersize=letter leftmargin=3.65cm rightmargin=2.11cm topmargin=3.36cm bottommargin=3.3cm orientation=landscape;
%if %qupcase(%superq(option))=%quote(TAGSETS) %then %do;
ods tagsets.rtf file="&outPath.\&tfl_name..rtf" options(vspace='no') options(continue_tag="no") startpage=&startpage.;
ods tagsets.rtf style=newstyle ;
%end; %else %do;
ods rtf file="&outPath.\&tfl_name..rtf" &option startpage=&startpage.;
ods rtf style=newstyle ;
%end;
options nonumber nocenter nobyline nodate formdlim='' formchar="|_---|+|---+=|-/\<>*" MISSING=" " ;
ods escapechar='^' ;
%mend;
Another workaround was to use a macro i build with a colleague sometimes ago to force a page break after a certain number of lines, which ultimately gave us also a theoretical page number, it worked for a time. But it is not flawless...
Thanks a lot!
I'm gonna read the code and test it right away. I'll let you know 🙂
Just some more notes:
1) To analyze the RTF file as is I used NOTEPAD++ program.
2) I assumed that the footnote appears only once per page, thus counting number of footnotes
is same as counting pages.
3) You may drop FILE LOG and the PUT lines to log, as it served for debug only.
4) I found in the RTF file, that you sent, that each text is preceeded by "\f2 ".
It may change across different RTF formats.
5) I am not familiar enough, neither with RTF formats nor with SAS ODS and STYLE.
So may be there is some other solutions to your request.
6) When you define the macro variables (%let ...) - they are case sensitive.
7) You have not specified where, in the footnote, you want the page number, the total number of pages
and in what format to display them (how many digits ? 2/5 or 2 / 5 or p.2 / 5 ...).
Shall it override some spaces or be inserted in the footnote line ?
That will influence how to assign the NEW_LINE statement.
😎 I understand that the footnotes of a table differ from footnotes of listing.
Are they both in same RTF file ? That may affect searching for the footnote and counting pages.
@Shmuel wrote:Just some more notes:
1) To analyze the RTF file as is I used NOTEPAD++ program.
Oh, thanks, I'm not familiar RTF that much. I was sure that was as complicated as Word.
I indeed found \loch\f2 preceding the place where I need the page number to be placed.
2) I assumed that the footnote appears only once per page, thus counting number of footnotes
is same as counting pages.
3) You may drop FILE LOG and the PUT lines to log, as it served for debug only.
4) I found in the RTF file, that you sent, that each text is preceeded by "\f2 ".
It may change across different RTF formats.
You mean tagsets.rtf and ods rtf ? Yes, i'll generate both and search for the appropriate tag.
5) I am not familiar enough, neither with RTF formats nor with SAS ODS and STYLE.
So may be there is some other solutions to your request.
That solution suits me very well, it is fast and actually more reliable than the workaround in vbs from one of my colleage.
Thanks a lot again !
6) When you define the macro variables (%let ...) - they are case sensitive.
7) You have not specified where, in the footnote, you want the page number, the total number of pages
and in what format to display them (how many digits ? 2/5 or 2 / 5 or p.2 / 5 ...).
Shall it override some spaces or be inserted in the footnote line ?
That will influence how to assign the NEW_LINE statement.
Yes, Sorry, the page number should be after the run date and time at the extreme right. To solve that, I've added a keyword to look for, such as **pagenumber** and replaced it by "pagno/pages".
Thanks a lot again for your help. I was not aware of those possibilities.
I very much appreciate, you just saved me a lot of pain ^^ !
Kind regards,
Lionel.
By entering the string **pagenumber** into the footnote, code can be simplified into:
%let pagex = **pagenumber** ; /* unique string to search for footnote */
data _NULL_;
retain pages 0 ;
infile flat(test_rtf.rtf) truncover end=eof; /* adapt to RTF file created by SAS */
length a_line $400;
input a_line $char400.;
if index(a_line,"&pagex") then pages+1;
if eof then call symput('pages',strip(pages));
run;
data _NULL_;
retain pages pageno 0 ;
pages = symget('pages');
infile flat(test_rtf.rtf) truncover end=eof; /* adapt to RTF file created by SAS */
length a_line $400 ;
input a_line $char400.;
ix1 = index(a_line,"&pagex");
if ix1 then do;
pageno+1;
substr(a_line,ix1,length(&pagex)) = put(pageno,2.) || '/' || put(pages,2.);
end;
file flat(new_rtf.rtf); /* adapt to new RTF file changed */
put a_line ;
run;
The solution was actually much simpler than I expected at first 😕
Thanks you for your help ! It works at the perfection!
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 16. Read more here about why you should contribute and what is in it for you!
What’s the difference between SAS Enterprise Guide and SAS Studio? How are they similar? Just ask SAS’ Danny Modlin.
Find more tutorials on the SAS Users YouTube channel.
Select SAS Training centers are offering in-person courses. View upcoming courses for: