I have been creating a macro that creates some ODS output along with a Table of Contents.
My macro has a switch that allows users to select RTF, PDF or Word output (there are reasons for having the option of RTF and DOCX separately).
The macro has an option to insert custom text for the TOC title line.
If i'm using ODS RTF I can manipulate the template style I am using and change the title within ContentTitle:
style ContentTitle from ContentTitle / pretext = "" posttext="Line1(*ESC*){newline}Line2" color = dark blue;
Notice I want multiple lines in the title, so i use the ODS escape command {newline}.
However, when I try and do the same for ODS WORD, no formatting is honored
I have tried the following (creating the string as a macro variable to insert into the template):
data _null; *call symputx('testtitle', catx('0A'x, "Line1", "Line2")); call symputx('testtitle', "Line1(*ESC*){newline}Line2"); *call symputx('testtitle', "Line1\nLine2"); run; proc template; define style styles.test_docx; parent=styles.word; style ContentTitle from ContentTitle / content = "&testtitle." /*protectspecialcharacters=on*/ /*asis=on*/ /*contenttype="text/plain"*/ ; end; run;
ODS Escape sequences did not work, neither did ASCII like break characters or the simple "\n" (which weirdly printed out as "¥n"). I tried some other options I dug out of the log, like "protectspecialchars", "asis", etc but nothing seems to want to make a line break in an ODS WORD TOC.
Does anyone have a solution before I contact the Technical guys?
Full testing program attached.
Solved!
I had exhausted all the SAS tricks I knew, and focussed instead on manipulating the underlying XML of the resulting DOCX file.
After experimenting with several possible solutions, I discovered that the title that you set in the ODS template is written directly to the WORD XML, with no interpretation at all.
Line breaks are ignored because they have no meaning in XML, and so to insert a line break I had to use WORD XML languate instead:
proc template;
define style styles.test_docx;
parent=styles.word;
style ContentTitle from ContentTitle /
content = "Title1<w:br />Title2" ;
end;
run;
So I can add something to substutute "(*ESC*){newline}" with "<w:br />" in the input parameters, or use some keyword a user can specify to mean a line break that i replace as required.
Unless you are dealing with 1000's of lines of code it is best to just paste the code into a text box opened on the forum with the </> icon above the message window. That way no one has to download a file from an unknown source and we call all follow along:
*--- set outpout area ---; %let output_area=<Insert path here>; *--- Define the output tables ---; %macro outputstuff(type=); ods proclabel="Table 1"; proc print data=sashelp.class; run; ods &type. startpage=now; ods proclabel="Table 2"; proc print data=sashelp.class; run; ods &type. startpage=now; ods proclabel="Table 3"; proc print data=sashelp.class; run; %mend outputstuff; *---------* | ODS RTF | *---------*; *--- Change title of TOC ---; proc template; define style styles.test_rtf; parent=styles.rtf; style ContentTitle from ContentTitle / pretext = "" posttext="Line1(*ESC*){newline}Line2" color = dark blue ; end; run; *--- Generate output ---; ods rtf file="&output_area./sas_bug.rtf" contents toc_data style=styles.test_rtf; %outputstuff(type=rtf); ods rtf close; *----------* | ODS WORD | *----------*; *--- Change title of TOC ---; data _null; *call symputx('testtitle', catx('0A'x, "Line1", "Line2")); call symputx('testtitle', "Line1(*ESC*){newline}Line2"); *call symputx('testtitle', "Line1\nLine2"); run; proc template; define style styles.test_docx; parent=styles.word; style ContentTitle from ContentTitle / content = "&testtitle." /*protectspecialcharacters=on*/ /*asis=on*/ /*contenttype="text/plain"*/ ; end; run; *--- Generate output ---; ods word file="&output_area./sas_bug.docx" options(contents="on" toc_data="on") style=styles.test_docx; %outputstuff(type=word); ods word close;
Unless there is more manipulation involved you should consider using a %let instead of data _null_.
%let testtitle=Line1(*ESC*){newline}Line2;
May want to use Proc template to look closely at the Styles.word.
In my install it doesn't inherit much from Styles.default ( through Printer )as the Styles.RTF does. Which is likely why what works with RTF doesn't with Word, the "parent" values aren't there to modify.
Plus my install still shows ODS Word as "preproduction", which sometimes means all the features aren't quite stable yet. I know that I can't get Word to render lots of tables correctly that have no problems in RTF (or PDF or HTML)
Thanks @ballardw , I wasn't sure of the etiquette when wishing to post a program.
Regarding the %let over a symputx – I did it like that so I could construct test strings using catx (like the included one where I was trying to insert an ascii ‘0A’x which i dodn't know would work in a %let). I haven't tried the byte function yet, but suspect it may have the same effect (but i'll try that now!)
As for the PROC TEMPLATE, I have deconstructed all the styles in the past, and indeed built new ones entirely from scratch with no inheritance. What I couldn’t find in any of them, and indeed in any manual, is the right command that would trigger a line break within an ODS Word TOC title.
And it is a little frustrating that ODS WORD is still preproduction after over 5 years but it is odd it doesn’t seems to conform to the usual ODS commands.
Worst case scenario is I do post-processing on the DOCX to insert a line break where I want, but that’s like cracking a walnut with a hammer – I’d rather find a simpler, more elegant solution 😊
Solved!
I had exhausted all the SAS tricks I knew, and focussed instead on manipulating the underlying XML of the resulting DOCX file.
After experimenting with several possible solutions, I discovered that the title that you set in the ODS template is written directly to the WORD XML, with no interpretation at all.
Line breaks are ignored because they have no meaning in XML, and so to insert a line break I had to use WORD XML languate instead:
proc template;
define style styles.test_docx;
parent=styles.word;
style ContentTitle from ContentTitle /
content = "Title1<w:br />Title2" ;
end;
run;
So I can add something to substutute "(*ESC*){newline}" with "<w:br />" in the input parameters, or use some keyword a user can specify to mean a line break that i replace as required.
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
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.
Ready to level-up your skills? Choose your own adventure.