options topmargin=0.75 bottommargin=0.75 leftmargin=0.75 rightmargin=0.75; proc template; define Style styles.format; parent = styles.Printer; /* ----- */ replace fonts / "docFont" = ("Times", 9pt) "headingFont" = ("Times", 10pt, bold) "headingEmphasisFont" = ("Times", 10pt, bold italic) "FixedFont" = ("Courier New, Courier", 8pt) "BatchFixedFont" = ("SAS Monospace, Courier New, Courier", 6.7pt) "FixedHeadingFont" = ("Courier New, Courier", 8pt, bold) "FixedStrongFont" = ("Courier New, Courier", 8pt, bold) "FixedEmphasisFont" = ("Courier New, Courier", 8pt, italic) "EmphasisFont" = ("Times", 9pt, italic) "StrongFont" = ("Times", 9pt, bold) "TitleFont" = ("Times", 12pt, italic bold) "TitleFont2" = ("Times", 11pt, italic bold) ; end; run; data eg; length name $12; input name age height; cards; Jessie 12 57.3 James 12 59.8 Meowth 5 10.2 ; run; %inc format; %macro startup; %if &sysver < 8.2 %then %do; options rightmargin=4.25; ods printer style=format file="qual.ps"; %end; %else ods printer style=format columns=2 pdf file="qual.pdf"; %mend; %startup; %formatting_start; title "Presentation-Quality Tabular Output via ODS"; %section( "Abstract" ) %para( "This paper describes how to achieve presentation-quality tabular output using PROC REPORT, ODS Styles, and the ODS Printer destination for PS and PDF output. Covered are tricks for control of cell width and cell height, borders and shading, splitting and stacking cell values, and all those gnarly tricks you have to know to have (mostly) complete control over the look and feel of your tables. The techniques described can also be applied to PROC TABULATE and other SAS%super(%rtm) output as well. But hey, let's face it, REPORT is cool so why would you want to use anything else? I will also cover indenting and other secret formatting abilities of the ODS PRINTER code, and show a preview of coming 8.2 features." ) title2; /* Only put my name on the first page! */ %section( "Introduction" ) %para( "Now that many of you have had a chance to start using ODS, we are hearing more and more questions from people who are trying to customize the output in any number of ways, both in ways that we had anticipated and in ways that we had not anticipated when we designed the system. In this paper I will examine some of the table formats that people have asked us about and show how to achieve these using the ODS PRINTER destination." ) %para( "Based on the feedback we have received on these issues, we are designing new features to cover some of the ""holes"" in the original v8 interface for ODS, and at the end of the paper I will give you a preview of some of the features coming in v8.2 and beyond. However, the primary focus of this paper will be on what you can do right now in the latest production release of the SAS system (v8.1). Because this paper focuses squarely on the ODS PRINTER output destination, however, I will feel free to reveal tricks that would not work on other destinations. Whenever something is not portable across destinations, however, I will point that out, and tell you whether we've addressed it in a more robust way for v8.2 or not." ) %section( "Tables with fewer rules" ) %para( "One of the more common things that people want is a table with far fewer rules than the very ""boxy"" tables we produce by default. This one should be easy to do, since we have a pre-packaged style intended to do this very sort of thing. Unfortunately, it doesn't work quite right. Oops." ) %para( "Nonetheless, it makes a pretty decent start, and we're going to take a look at what it does and how it does it." ) %para( "First, this is the default appearance of a simple table in ODS PRINTER:" ) proc print; run; proc template; define style styles.fixeddocPrinter1; parent = styles.sasdocPrinter; replace color_list "Colors used in the default style" / "bg" = white "fg" = black "bgH" = white "link" = blue ; end; run; proc template; define style styles.fixeddocPrinter2; parent = styles.printer; replace HeadersAndFooters from Cell / font = fonts("HeadingFont"); style Table from Output / background = _undef_ frame = VOID cellpadding = 4pt cellspacing = 0.75pt; end; run; %para( "What we'd like instead is something more like this:" ) ods printer style=fixeddocPrinter2; proc print; run; ods printer style=format; %para( "If things worked as intended, all it would take is this:" ) %pre cards4; ods printer style=sasdocPrinter; ;;;; %pend %para( "However, what this produces instead (in v8.1) is this:" ) ods printer style=sasdocPrinter; proc print; run; ods printer style=format; %subsection( "How was that done?" ) %para( "Ok, how do we fix it? Well, first let's see how ""sasdocPrinter"" is (incorrectly) defined:" ) %pre cards4; proc template; source styles.sasdocPrinter; run; ;;;; %pend %para( "Here's what it shows (edited a bit for readability):" ) %pre cards4; define style Styles.sasdocPrinter; parent = styles.Printer; replace fonts / 'TitleFont2' = ("Arial, Helvetica",12pt,Bold) 'TitleFont' = ("Arial, Helvetica",13pt,Bold) 'StrongFont' = ("Bookman, Times",10pt,Bold) 'EmphasisFont' = ("Bookman, Times",10pt,Italic) 'FixedEmphasisFont' = ("Courier New, Courier",9pt,Italic) 'FixedStrongFont' = ("Courier New, Courier",9pt,Bold) 'FixedHeadingFont' = ("Courier New, Courier",9pt,Bold) 'BatchFixedFont' = ("SAS Monospace, Courier",7pt) 'FixedFont' = ("Courier New, Courier",9pt) 'headingEmphasisFont' = ("Bookman, Times",11pt,Bold Italic) 'headingFont' = ("Bookman, Times",11pt,Bold) 'docFont' = ("Bookman, Times",10pt); style Table from Output / background = _undef_ frame = HSIDES cellpadding = 4pt cellspacing = 0.75pt borderwidth = 0.75pt; end; ;;;; %pend %para( "So it resets the fonts to look more like the SAS documentation--a change we don't even especially desire here--and it resets the table to get rid of the extra rules. Note that this is a bit more involved than you might think at first blush: Not only must you do the obvious thing and get rid of the default borders (""%tt(%str(frame = HSIDES))""); you must also get rid of the internal rules." ) %para( "Now, you might expect to see RULES=GROUPS in order to get rules only between the groups and not around all of the cells. The reason you don't is that, as odd as it might seem, this is already the default (inherited from the Output style element). So why do there appear to be rules between all of the cells in the default style? That's because by default the background color of the %it(table) is black, and the table's background color ""shows through"" in the space between the cells. In order to avoid this, the code above sets ""%tt(%str(background = _undef_))."" (Please note that, in order to make things easier to work with in v8.2, we have changed this default.) ") %subsection( "Getting the background colors right" ) %para( "That is all well and good, but it fails to do anything to the background colors of the cells. There are two possible approaches for resetting the background color of the cells: we could change the definition used for the header and footer cells so that it no longer requests a background color, or we could change the color used for those cells. Here is the latter approach:" ) %pre cards4; proc template; define style styles.fixeddocPrinter1; parent = styles.sasdocPrinter; replace color_list / "bg" = white "fg" = black "bgH" = white "link" = blue ; end; run; ;;;; %pend %para( "And here are the results it produces:" ) ods printer style=fixeddocPrinter1; proc print; run; ods printer style=format; %subsection( "Knowing what to change" ) %para( "Now the obvious question here is: %it(how did I know to do that?) Well, in order to figure that out, we have to see how the default styles are defined. As you might already know, the default style for the printer destination is ""printer"" (this is documented in the description of the ODS PRINTER STYLE= option). So we examine the printer style definition:" ) %pre cards4; proc template; source styles.printer; run; ;;;; %pend %para( "This style is longer and more complex than the sasdocPrinter style reproduced previously, and thus too long to include in its entirety here, but the relevant part is pretty simple: ") %pre cards4; style Table from Output / rules = ALL cellpadding = 4pt cellspacing = 0.25pt borderwidth = 0.75pt; replace color_list "Colors used in the default style" / 'link' = blue 'bgH' = grayBB 'fg' = black 'bg' = white; ;;;; %pend %para( "In the example above, of course, I simply modified this color list. When we want to modify the definition of the header cells, however, it's a bit more difficult. We will search the printer style in vain for any sign of a definition of the header cells themselves. We will also find ourselves quite unable to find how the borders for the table are controlled, because these attributes are not specified in the printer style at all. Instead, they are %it(inherited) from the parent style, which is shown along with the other information about the printer style from the ""%tt(source)"" command above. The relevant line looks like this:" ) %pre cards4; parent = styles.default ;;;; %pend %para( "Now we can get the styles.default source in the same way that we got the styles.printer and styles.sasdocPrinter source. It is the base for almost all of the styles in ODS, and thus is enormous. However, you can search in the log window for ""Header"" (or, as I did, run in batch and use your favorite text editor to search the log file). You will soon discover that the base style for all the table headers and footers is called HeadersAndFooters, and is defined as follows:" ) %pre cards4; style HeadersAndFooters from Cell "Abstract. Controls table headers" / BackGround = colors("headerbg") ForeGround = colors("headerfg") font = fonts("HeadingFont") ; ;;;; %pend %para( "We can also find the definition for Output (from which table inherits) and thereby solve the mystery of the default borders and rules, but I'll leave that one as an exercise for the reader." ) %subsection( "Putting it all together" ) %para( "So we can change the definition of the headers and footers themselves. In this example, I have gone a bit further--I have also based the style directly off of ""printer"" rather than ""sasdocPrinter"" and redefined the table myself. This allows me to avoid the font change that sasdocPrinter introduces, and also to get rid of the rules at the top and bottom of the table, by specifying FRAME=VOID." ) %pre cards4; proc template; define style styles.fixeddocPrinter2; parent = styles.printer; replace HeadersAndFooters from Cell / font = fonts("HeadingFont"); style Table from Output / background = _undef_ frame = VOID cellpadding = 4pt cellspacing = 0.75pt; end; run; ;;;; %pend; %para( "This finally produces the desired results:" ) ods printer style=fixeddocPrinter2; proc print; run; ods printer style=format; %section( "Controlling cell sizes" ) %para( "Sometimes you need for the cells to be bigger or smaller than the default size that ODS will pick for you. Perhaps you are designing a form to print out that people will fill in, and you want to leave extra space, or perhaps you want to force the printer to try even harder than it usually would to avoid paneling (though it tries pretty hard without help!), or perhaps you simply want to make the columns equal widths to improve the aesthetics of your particular report." ) %para( "Whatever the reason, it's easy to accomplish. Just set the CELLWIDTH or CELLHEIGHT on the cells of interest. The subtlety here is that in ODS PRINTER the cell will be made %it(precisely) the size that you request, even if it's smaller than the other cells in the column, and the visual effect of this is pretty odd. Thus, you want to either make all cells in the column be the same width (in particular, be sure to make the header cell the same width as the data cells), or be sure that you are %it(increasing) the cell width from the default (the column is made as wide as the widest cell). " ) %para( "Changing the cell sizes (or other attributes of specific cells) differs from the proceeding section because rather than changing the overall document style, we want to affect different portions of the document in different ways. Since this paper is specific to PROC REPORT, it will cover how these are specified in PROC REPORT. There is similar syntax in PROC TABULATE; the output of most procedures is controlled by table templates handled through PROC TEMPLATE; and a few procedures do not allow this sort of control at all. (PROC PRINT will have similar syntax to PROC REPORT starting in v8.2.)" ) %subsection( "Making it wider and taller" ) %para( "Let us say that you wanted to create a small form for somebody to fill out. You want to leave enough room for them to fill in the blanks, but you'd prefer to use something more aesthetically pleasing than resorting to the listing tactic of using a string of underscores. You could do this:" ) %pre cards4; data; length left $20 right $20; input left right; cards; Name Age Address Sex City/State Phone run; proc report nowindows noheader style(column)={cellheight=1cm font_weight=bold just=r vjust=b posttext=":"} style(report)={background=_undef_ rules=rows}; columns left lv right rv; define lv / computed style={cellwidth=3in posttext=""}; define rv / computed style={cellwidth=1in posttext=""}; compute lv / char length=1; lv = " "; endcomp; compute rv / char length=1; rv = " "; endcomp; run; ;;;; %pend %para( "Note that what the STYLE(COLUMN) on the PROC REPORT statement itself really does it to provide a default for all of the table cells. We take advantage of this and even go so far as to specify a POSTTEXT= value there. We are forced to over-ride this default for the two blank columns, but it avoids the necessity to explicitly define either of the actual data columns at all. This produces results like those shown below (except that the output here is compressed horizontally so as to fit into the format of this paper)." ) data; length left $20 right $20; input left right; cards; Name Age Address Sex City/State Phone run; proc report nowindows noheader style(column)={cellheight=1cm font_weight=bold just=r vjust=b posttext=":"} style(report)={background=_undef_ rules=rows}; columns left lv right rv; define lv / computed style={cellwidth=1.5in posttext=""}; define rv / computed style={cellwidth=0.5in posttext=""}; compute lv / char length=1; lv = " "; endcomp; compute rv / char length=1; rv = " "; endcomp; run; %subsection( "Making it narrower" ) %para( "There are also times when you'd like to make a column narrower. For example, consider this table:" ) data; length a $ 200; input a b c; cards; abc 3 4 def 5 6 supercalifragilisticexpialadociouslyantidisestablishmentarianism 1 2 xyz 7 8 run; proc report nowd; run; %para( "In an effort to accommodate all of the data, ODS PRINTER has widened the table to the very edges of the margins. But the result is not very visually pleasing, since almost all of the data items are quite narrow, and the wide column winds up wrapping anyway. Now the most obvious fix (and probably the most sensible, in this case) would be to simply add a space about the middle of that preposterous word and be done with it. But this is an example in a paper, so we'll do it the hard way instead. Actually, it's not all that hard:" ) %pre cards4; proc report nowd; define a / style={cellwidth=2in}; run; ;;;; %pend proc report nowd; define a / style={cellwidth=2in}; run; %para( "That worked well, but I also want to illustrate the problems you can run into with cellwidth if you don't specify it for all of the cells in a column. I should think of some reason why you might run into this in real life, but I can't. PROC REPORT is a little too user-friendly; the only realistic examples I can think of would use a template. Nonetheless I want to show the effect of mis-matched cellwidth specifications in the hopes that it will help you recognize them if you encounter them while developing a report." ) %pre cards4; proc report nowd; define a / style(header)={cellwidth=1in}; run; ;;;; %pend proc report nowd; define a / style(header)={cellwidth=1in}; run; %subsection( "Double rules" ) %para( "A formatting convention sometimes encountered in tables is the double-rule; that is, at ""breaks"" in the data, or just after the header, the rule is doubled, like this (well, somewhat like this anyway; normally the gap between the lines would be rather smaller):" ) data; length team $ 25 player $25; format batavg f5.3; label batavg="Batting average"; input team $1-25 player $26-50 batavg; cards; Purple Sox Fred Jeffries 0.300 Purple Sox Jeff Fredrick 0.189 Purple Sox Cedric Halfonts 0.267 Green Sox Jeff O'Malley 0.289 Green Sox Jose Ng 0.333 run; proc report nowd; define team / order; break before team / summarize style={foreground=white font_size=1pt}; run; %para( "Now, in an ideal world, this would be quite simple; in troth, the BREAK BEFORE / SKIP option should probably do exactly this; however, it does not, so we have a little more work to do. The first thing to try is to use the summarize option but somehow make the output disappear. This easily enough done by making the foreground white; however, it is still full height. So we also shrink the cell, as shown below:" ) %pre cards4; proc report nowd; define team / order; break before team / summarize style={foreground=white cellheight=1pt}; run; ;;;; %pend %para( "Now, this seems logical enough, but it has this effect:" ) proc report nowd; define team / order; break before team / summarize style={foreground=white cellheight=1pt}; run; %para( "The problem is that the height of the %it(cell) is forced down by the %tt(cellheight) attribute, but the height of the text is not, so the white text partially over-writes the cell's rule. Definitely not the effect we were hoping for. So rather than using cellheight, in this case, we can use the %tt(font_size) attribute to shrink the cell, as follows:" ) %pre cards4; summarize style={foreground=white font_size=1pt}; ;;;; %pend %para( "And thus we get this table again:" ) proc report nowd; define team / order; break before team / summarize style={foreground=white font_size=1pt}; run; %para( "This is the best we can do, I fear. The gap is still a lot larger than the 1pt we requested, due to the cell padding. However, if we get rid of the cell padding, the results are perfectly hideous:" ) proc report style={cellpadding=0} nowd; define team / order; break before team / summarize style={ foreground=white font_size=1pt}; run; %para( "There are tricks we could do to add space at the start and finish of the cells, but nothing (that I can think of anyway!) to get vertical space on the cells where we desire them but not on the others. And for all that work, we can't make it as close as we'd like it anyway; font sizes smaller than 1pt aren't supported. Nonetheless, since this paper is supposed to cover all sorts of tricks, I will show how we can add horizontal space to the data cells. Note that just using %it(pretext) and/or %it(posttext) isn't sufficient; without the %it(asis) attribute, leading and trailing spaces are stripped." ) %pre cards4; proc report style={cellpadding=0} nowd style(column) = {pretext=" " posttext=" " asis=yes }; define team / order; ;;;; %pend %para( "Here is the result. Note that it's not really complete, even for what it intends to do; to complete it we'd need to mimic the column attributes for the header so that it didn't bump up against the left and right edges, either." ) proc report style={cellpadding=0} nowd style(column) = {pretext=" " posttext=" " asis=yes }; define team / order; break before team / summarize style={ foreground=white font_size=1pt}; run; %section( "Splitting or stacking" ) %para( "As you may already know, PROC REPORT supports stacked columns already, in a certain sense. But in the usual PROC REPORT terms, this is essentially a shorthand for defining a computed statistic on a variable without requiring a define statement, or at least I can't figure out anything else useful that can be done with it myself. But that's not what we are talking about here. Furthermore, PROC REPORT supports %tt(%str(split=)) to specify a split character to be used in PROC REPORT headers to specify where newlines go. That's a lot closer, but it doesn't work for the data cells, just the header cells." ) %para( "We can take advantage of secret internal features of ODS PRINTER, however, to get ""split"" or ""stacked"" values into the data cells of the REPORT output. As it turns out, the ""magical"" sequence" ) %pre cards4; '03'x || "n" ;;;; %pend %para( "will force a newline any time the ODS PRINTER output destination encounters it, even in the middle of a cell. Consider the following report:" ) %pre cards4; proc report data=sashelp.class split='*' nowd; columns age weight height ratio; define age / group; define ratio / computed "wt / ht*= ratio" style={just=r}; define weight / analysis mean noprint; define height / analysis mean noprint; compute ratio / character length=25; ratio = compress(put(weight.mean, f12.)) || " / " || compress(put(height.mean, f12.)) || '03'x || "n" || "= " || compress(put(weight.mean/ height.mean, f12.3)); endcomp; run; ;;;; %pend %para( "It produces this output:" ) proc report data=sashelp.class split='*' nowd; columns age weight height ratio; define age / group; define ratio / computed "wt / ht*= ratio" style={just=r}; define weight / analysis mean noprint; define height / analysis mean noprint; compute ratio / character length=25; ratio = compress(put(weight.mean, f12.)) || " / " || compress(put(height.mean, f12.)) || '03'x || "n" || "= " || compress(put(weight.mean/ height.mean, f12.3)); endcomp; run; %section( "Indenting and other secrets" ) %para( "As it turns out, the 03n sequence is not the only special sequence. There are a few other such special sequences. These are actually derived from sequences used internally in SAS code, designed primarily for use in notes. Please note that, even though you are reading it here, these are officially %it(undocumented.) They have not been through our usual QA process for 8.1, and there are some known ""quirks"" when using them. If you have trouble with them, please use the contact information at the end of this paper to ask about them; %it(do not) contact the official SAS technical support folks about them. They are swell folks who would probably try to help you anyway, but they are officially supposed to disavow all knowledge of these (for 8.1, anyway). Here is set of such marks that appear to work as expected in 8.1:" ) %def_start; %def( "'03'x||'_'", "Non-breakable space. The column width will be expanded in preference to breaking here." ) %def("'03'x||'m'", "Set mark. This position will be remembered, and if the line is too long and must be wrapped, it will wrap to here." ) %def("'03'x||'n'", "New-line. Forces a line-break here, as shown above." ) %def("'01'x", "Copyright symbol (%copyright). Actually present throughout SAS but, I believe, little-known."); %def("'02'x", "Registered trademark (%rtm). Actually present throughout SAS but, I believe, little-known."); %def("'04'x", "Trademark symbol (%tm). Actually present throughout SAS but, I believe, little-known."); %def_end; %para( "\3zThe '03'x||'n' sequence will behave differently in v8.2 than in v8.1 in the presence of a mark. To get consistent behavior, use the sequence '03'x||'-2n' instead." ) %subsection( "Examples" ) %para( "Here is a little example of what some of these do." ) %pre cards4; data; a = "Mark here: *" || '03'x || 'm' || "This text wraps around to where " || "the mark is rather than to the " || "very start of the line." || '03'x || '-2n' || "This silly example is " || '01'x || " 2000 SAS" || '02'x || " Institute Inc."; output; run; proc report noheader nowd; run; ;;;; %pend data; a = "Mark here: *" || '03'x || 'm' || "This text wraps around to where " || "the mark is rather than to the " || "very start of the line." || '03'x || '-2n' || "This silly example is " || '01'x || " 2000 SAS" || '02'x || " Institute Inc."; output; run; proc report noheader nowd; run; %subsection( "Indenting" ) %para( "One application of the marks, together with asis mode, is to have indented values in a cell. To be perfectly honest I don't really understand why somebody would want this effect, but it is apparently required by some FDA reports, so here we go. Note that leading spaces are normally stripped by ODS code, and using ASIS will prevent line wrapping entirely, so we have to put something innocuous at the start of the line. I chose a mark, which is invisible and harmless since it is superseded later by the intended real mark." ) %pre cards4; data; length a $ 255; a = "First obs, not indented, and " || "of course wrapping to the start"; b = "The other column, taking up space"; output; a = '03'x || "m " || '03'x || "m" || "Second obs, indented, and " || "forced to wrap to the indent point"; output; run; proc report noheader nowd; run; ;;;; %pend data; length a $ 255; a = "First obs, not indented, and " || "of course wrapping to the start"; b = "The other column, taking up space"; output; a = '03'x || "m " || '03'x || "m" || "Second obs, indented, and " || "forced to wrap to the indent point"; output; run; proc report noheader nowd; run; %subsection( "Controlling page-breaks" ) %para( "Sometimes you don't want each procedure to start a new page. You can use this option:" ) %pre cards4; options debug="pso_skip_explicit"; ;;;; %pend %para( "When this option is in effect, the implicit page breaks before each procedure are suppressed, but we will still go to a new page when we run out of room on the page." ) %section( "Other effects" ) %para( "There are numerous other effects that you can get using ODS PRINTER and PROC REPORT that are not covered in this paper. Here, we have covered only those features specific to ODS PRINTER and PROC REPORT, and I have stuck almost exclusively to features not covered in previous ODS papers. There are many style attributes not covered here. There is also traffic highlighting, for example." ) %para( "Traffic highlighting in PROC REPORT under ODS usually takes advantage of the embedded data-step computation code that is already used for special effects in v6 PROC REPORT. It also supports the SAS format lookup-table approach to traffic lighting, but I won't be covering either one in this paper. If you want to see how it works, though, there are numerous previous papers that go over this sort of thing in detail, available on the SAS web pages. (See the last section of this paper for web and contact information.)" ) %section( "What the future holds [8.2 preview]" ) %para( "There are a number of new features in the %it(next) release of SAS, version 8.2, that will simplify the sort of formatting we have covered in this report. I will also touch on a few other highlights of v8.2 enhancements for ODS PRINTER." ) %subsection( "ESCAPECHAR=" ) %para( "As you have seen, there is some embedded formatting you can already get, but specifying it is very tedious. To alleviate this, we will provide the ESCAPECHAR= option on the ODS PRINTER command. This allows you to specify a character that will be substituted for the %tt('03'x) above. The biggest advantage of this is that you need not be in an expression context in order to use these formatting features. The remaining examples in this section will assume that we have specified" ) %pre cards4; ods escapechar='\'; ;;;; %pend %subsection( "New formatting support" ) %para( "In addition to the codes listed above (which of course can be specified with the escape character if desired), these are supported in v8.2:" ) %def_start %def("\\w", "Preferred line-break. If the line breaks, it breaks there, but if there's enough room, it won't break." ) %def("\\z", "Error code. Formats the output like a SAS error message. Also available:%nl \\1z = error%nl \\2z = warning%nl \\3z = note%nl \\4z = fatal%nl" ) %def("\\%it([arg])n", "Arguments allowed for new-line:%nl -2 = same as \\n in v8.1: wrap to mark; otherwise, an explicit \\n forces a wrap to the left margin%nl Other numbers are taken as how much far to advance vertically; %it(e.g.), \\1.5n inserts an extra half-line of vertical space.") %def("\\S={%it(style-attributes)}", "Change style. This allows you to specify style attribute values that change in the middle of the cell or paragraph. An empty style reverts to the default." ) %def("\\{super %it(val)}", "Superscript.") %def("\\{sub %it(val)}", "Subscript.") %def("\\R/%it([tag])""%it(raw-text)""", "Insert raw text into the output. Allows a tag; if present, the raw text appears only in the output for the named destination." ) %def_end %para( "Here are some of these in action:" ) %pre cards4; data; a = "\\3zIn 8.2, you can put " || "\\S={font_weight=bold}bold\\S={} " || "and " || \\S={font_style=italic}italic\\S={} " || "text into your cells. It's " || "\\S={font_style=italic}n\\S={}" || "\\{super 2} the fun."; run; proc report noheader nowd; run; ;;;; %pend data; a = "\3zIn 8.2, you can put \S={font_weight=bold}bold\S={} " || "and \S={font_style=italic}italic\S={} " || "text into your cells. It makes for " || "\S={font_style=italic}n\S={}\{super 2} the fun."; run; proc report noheader nowd; run; %subsection( "Formatting macros" ) %para( "Even that's more difficult than one would hope. That's why we have available for downloading from our web page some formatting macros to make this easier. The above can now be reduced to this:" ) %pre cards4; data; a = "\\3zIn 8.2, you can put " || "%bo(bold) and %it(italic) " || "text into your cells. It's " || "%it(n)%super(2) the fun."; run; proc report noheader nowd; run; ;;;; %pend data; a = "\3zIn 8.2, you can put " || "%bo(bold) and %it(italic) " || "text into your cells. It's " || "%it(n)%super(2) the fun."; run; proc report noheader nowd; run; %para( "Actually, the macros work in 8.1, too, but many of them are ""dummy"" macros that don't do anything since they are interfaces to features that aren't in SAS v8.1." ) %subsection( "Images" ) %para( "In v8.2, ODS PRINTER supports images. They work just like in ODS HTML, except of course that the image must be available to SAS run-time rather than to the web server. For example, here is the new SAS logo:" ) %pre cards4; data; a = "The Power to Know."; run; proc report noheader nowd; define a / display style={preimage="saslogo.gif" pretext=" " font_style=italic font_weight=bold}; run; ;;;; %pend data; a = "The Power to Know."; run; proc report noheader nowd; define a / display style={preimage="saslogo.gif" font_style=italic font_weight=bold}; run; %subsection( "STARTPAGE=" ) %para( "There will be an official, documented replacement for %tt(pso_skip_explicit); namely, STARTPAGE=. Not only can it be turned ON or OFF, but it can be set to NOW, to force an immediate page break, or to NEVER to prevent page breaks even around graphs. (Normally, STARTPAGE=NO will still do page breaks around SAS/GRAPH output because SAS/GRAPH uses the entire page, but by using new [experimental] support to limit the graphs to only a portion of the page, and STARTPAGE=NEVER, you can put multiple graphs per page, or mixed graphs and text. You can even overlay text with graphs, though this is very, very rough in 8.2.)" ) %subsection( "And more secret stuff" ) %para( "Even in 8.2, there are experimental and undocumented features that may become ""official"" in later releases. For example, you use COLUMNS= and TEXT= on the ODS PRINTER statement to produce multi-column output and and to directly insert paragraphs, respectively." ) %para( "There's also a secret ability to move up an down or back and forth on a line; it's very rough and thus undocumented but could come in handy for special effects. The biggest drawback is that it works only in printer pixels, not in measured units, so the document might have to be altered if you go to another printer. If this proves a particularly popular pastime, it could be promoted to production and proper unit support provided. A short example follows:" ) %pre cards4; data; a = "In 8.2, you can format like " || "T\\7y\\-5xE\\-7y\\-5xX if you want" || "\\-73x------ need to."; run; proc report noheader nowd; run; ;;;; %pend data; a = "In 8.2, you can format like " || "T\7y\-5xE\-7y\-5xX if you want" || "\-73x------ need to."; run; proc report noheader nowd; run; %subsection( "Destinations" ) %para( "We will also increase the flexibility of the ODS output destinations. First of all, we have, in response to the feedback we've gotten from numerous users, re-thought the way that we present the format options for the printer. While of course the current syntax will continue to work, the ""new"" way of looking at is to think of each SAS-supported output format as a separate destination. Because they are separate destinations, it is very natural to produce multiple types of ""printer"" (page layout) output at once:" ) %pre cards4; ods printer; /* To physical printer */ ods ps file="foo.ps"; ods pdf file="foo.pdf"; ods pcl file="foo.pcl"; ods display; /* Pop up a window */ ;;;; %pend %para( "Note especially that PDF will become a production output destination in v8.2." ) %para( "Unfortunately, this new paradigm did not make it into the official 8.2 documentation [oddly, the documentations deadlines are much earlier than the code deadlines], but it works just fine, and is the way I recommend using the code in v8.2+." ) %para( "Also, in 8.2 you will be able to tag your destinations in order to get multiple copies of the same destination with different options. When you are done, you can close all the output destinations with a single statement." ) %pre cards4; ods ps file="foo.ps"; ods ps(2) file="foobw.ps" nocolor; : : ods _all_ close; ;;;; %pend %section( "Seeing is believing!" ) %para( "This is one of my favorite things about this paper. In SAS version 8.2, at least, the presentation-quality output abilities are strong enough that it's actually possible to format an entire paper in SAS itself. In fact, this paper was written in SAS, using the macros available in the ODS PRINTER section of the SAS web pages." ) %para( "Now, I'll be real about this. Would you really want to toss out your Microsoft Word%rtm or T\\7y\\-5xE\\-7y\\-5xX program in favor of formatting directly in SAS? No, you'd have to be nutty to do that. Primarily due to limitations in the SAS language itself, which was %it(not) designed for natural-language input, the input is rather awkward, and ODS PRINTER wasn't designed for this task, either, so it lacks (for example) full justification and hyphenation support." ) %para( "I, on the other hand, %it(am) a nut, so it doesn't bother me, and it sure makes it easy to include sample SAS input. Of course, the real reason that I did this paper in SAS was to prove that we had provided the tools that made it possible. Actually, it proved no such thing at the outset, but since I wrote and support the ODS PRINTER code, I have upgraded the ODS PRINTER code as necessary to get this paper written, and all of these enhancements will ship with SAS v8.2, though many of them will not be officially documented." ) %para( "The real reason, of course, that we have this support is to allow you to enhance and annotate your SAS output, not to replace your word processor. I figure that if it's possible to write an entire paper in the system, that's a pretty good indication of our capability to meet your needs." ) %para( "The formatting macros themselves actually create data sets and print them with PROC REPORT in order to render the paragraphs of the report. The implementation code is much too large to include in this paper, but it is available for download with other ODS PRINTER-related documentation. The source for this paper is also available. Here is a brief example of what the paper source (""%tt(qual.sas)"") looks like:" ) %pre cards4; %section( "Section header" ) %para( "A paragraph, with %it(italic) text. ""Quotes"" are a pain. And some example code:" ) %pre cards4; proc sample; some code; ;;;; %pend ;;;; %pend %section( "Still missing" ) %para( "The biggest feature still missing, in my opinion, having looked at the reports that you folks are trying to do and can't, is the ability in PROC REPORT to combine arbitrary cells, both horizontally and vertically. The second biggest feature is support for data step-based reports. This one we are working at but not in time for 8.2. (Actually, there's a %it(really) secret SCL interface shipping with 8.2 that could wind up being unofficially documented if there's sufficient demand.) We'd like to hear %it(your) opinions about what's missing, though. Please contact us at %tt(ods@sas.com) with questions and comments." ) %section( "Conclusion" ) %para( "I'd conclude that ODS PRINTER and PROC REPORT are the greatest thing since sliced bread, but I suppose that I need a more boring conclusion than %it(that), so let's try this:" ) %para( "There are a number of tricks you can do to get formatting output using PROC REPORT and ODS PRINTER in version 8.1 of the SAS system. (Most of them, incidentally, work in v8.0 as well.) Even more features are available in 8.2, and we welcome your feedback on what else is needed." ) %section( "Contact and Web information" ) %para( "To get to the web pages, you can simply start at %bo(www.sas.com) and navigate your way down to the Base SAS pages (as I write this, you get there by picking %it(Service & Support) and then %it(Base SAS) [listed under ""Communities""]. From there it's pretty straightforward; many branches lead to information on ODS. Most particularly, the %it(%str(Tips, Techniques, and SUGI Papers)) link leads to previous papers we've presented. To navigate directly to the Base SAS pages, just go directly to %nl""%tt(http://www.sas.com/rnd/base)"".%nl To navigate directly to the printer pages, go to %nl""%tt(http://www.sas.com/rnd/base/topics/odsprinter/)"".") %para( "There's a section of ODS PRINTER-oriented papers in there, in the %it(%str(Tips, Techniques, and SUGI Papers)) section, labeled %it(ODS PRINTER Information). In particular, if you use ODS PRINTER much, you should really read the faq. It's pretty hard to miss; there are a number of ways to navigate to it." ) %subsection( "Stuff nobody reads" ) %para( "SAS and all other SAS Institute Inc. product or service names are registered trademarks or trademarks of SAS Institute Inc. in the USA and other countries. %rtm indicates USA registration." ) %para( "Other brand and product names are registrered trademarks or trademarks of their respective companies." ) ods printer close;