using ods object oriented features to produce a formatted ... · footer1 through footer10, if they...

17
1 Using ODS Object Oriented Features To Produce A Formatted Record Layout Suzanne M. Dorinski, US Census Bureau, Washington DC ABSTRACT This paper uses the Fiscal Year 2005 State Library Agencies Survey to demonstrate the usefulness of storing the survey metadata in an Excel spreadsheet. The metadata were originally stored in a Word document, and not easily available to the programmer. When the metadata are stored in a spreadsheet, we can read it with SAS® and have it available to produce the required database documentation. We can also produce the formatted record layout as RTF, which looks almost identical to the original Word document. To produce the formatted record layout, we use the new data _null_ ODS object oriented features experimentally available in SAS 9.1.3. We note that when changes are made to the metadata in the Excel spreadsheet, they easily flow to both the formatted record layout and the database documentation. INTRODUCTION The U.S. Census Bureau is the data collection agent for the State Library Agencies Survey (StLA). For fiscal year 2005 (FY 2005), the sponsor was the National Center for Education Statistics (NCES). The survey collects data about staffing, collections, revenue, and expenditures. The first data collection was FY 1994. The record layout was stored in a Word document, formatted with tab stops. For FY 2005, 103 variables were removed from the survey. This is when we noticed that storing the record layout in a Word document was not very efficient. The survey analyst had to remove variables by hand, and then update the start positions for the variables in the sections following the removed variables. Frank DiIorio and Jeff Abolafia presented a paper at SESUG in 2006 (“The Design and Use of Metadata: Part Fine Art, Part Black Art”) that discussed why file layouts should not be stored in Word. We were intrigued by their arguments, but we were stymied by the requirement that the file layout in future years have the exact same format as the FY 2005 file layout contained in the public use data file documentation. Daniel O’Connor presented a paper at SUGI 28 (“Next Generation Data _NULL_ Report Writing Using ODS OO Features”) that showed how to use ODS object oriented features to produce custom output. We studied his paper in detail to figure out how we could use those techniques to produce a file layout matching the format in the FY 2005 data file documentation. We created an Excel spreadsheet with all the information shown in the file layout. The main advantage of using an Excel spreadsheet is that if we add or remove survey items in the future, the start position can be updated automatically in Excel. We used the FY 2005 record layout for this paper, which is shown in Appendix A of the public use data file documentation, available at http://harvester.census.gov/imls/pubs/Publications/2007321.pdf . DISCUSSION OF THE PARTS OF THE FILE LAYOUT The first page of the record layout from the data file documentation is shown in Figure 1. I’ve noted the parts of the file layout in blue text in Figure 1. * * * * * * * * * * * * * * * Programming Beyond the Basics NESUG 2008

Upload: others

Post on 01-Aug-2020

1 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

1

Using ODS Object Oriented Features

To Produce A Formatted Record Layout

Suzanne M. Dorinski, US Census Bureau, Washington DC

ABSTRACT

This paper uses the Fiscal Year 2005 State Library Agencies Survey to demonstrate the usefulness of storing the

survey metadata in an Excel spreadsheet. The metadata were originally stored in a Word document, and not

easily available to the programmer. When the metadata are stored in a spreadsheet, we can read it with SAS®

and have it available to produce the required database documentation. We can also produce the formatted record

layout as RTF, which looks almost identical to the original Word document. To produce the formatted record

layout, we use the new data _null_ ODS object oriented features experimentally available in SAS 9.1.3. We note

that when changes are made to the metadata in the Excel spreadsheet, they easily flow to both the formatted

record layout and the database documentation.

INTRODUCTION

The U.S. Census Bureau is the data collection agent for the State Library Agencies Survey (StLA). For fiscal year

2005 (FY 2005), the sponsor was the National Center for Education Statistics (NCES). The survey collects data

about staffing, collections, revenue, and expenditures.

The first data collection was FY 1994. The record layout was stored in a Word document, formatted with tab

stops. For FY 2005, 103 variables were removed from the survey. This is when we noticed that storing the

record layout in a Word document was not very efficient. The survey analyst had to remove variables by hand,

and then update the start positions for the variables in the sections following the removed variables.

Frank DiIorio and Jeff Abolafia presented a paper at SESUG in 2006 (“The Design and Use of Metadata: Part

Fine Art, Part Black Art”) that discussed why file layouts should not be stored in Word. We were intrigued by their

arguments, but we were stymied by the requirement that the file layout in future years have the exact same format

as the FY 2005 file layout contained in the public use data file documentation.

Daniel O’Connor presented a paper at SUGI 28 (“Next Generation Data _NULL_ Report Writing Using ODS OO

Features”) that showed how to use ODS object oriented features to produce custom output. We studied his paper

in detail to figure out how we could use those techniques to produce a file layout matching the format in the FY

2005 data file documentation.

We created an Excel spreadsheet with all the information shown in the file layout. The main advantage of using

an Excel spreadsheet is that if we add or remove survey items in the future, the start position can be updated

automatically in Excel. We used the FY 2005 record layout for this paper, which is shown in Appendix A of the

public use data file documentation, available at http://harvester.census.gov/imls/pubs/Publications/2007321.pdf.

DISCUSSION OF THE PARTS OF THE FILE LAYOUT

The first page of the record layout from the data file documentation is shown in Figure 1. I’ve noted the parts of

the file layout in blue text in Figure 1.

* * * * * * * * * * * * * * *

Programming Beyond the BasicsNESUG 2008

Page 2: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

2

Figure 1. First page of record layout from data file documentation

Programming Beyond the BasicsNESUG 2008

Page 3: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

3

The file layout consists of the following parts:

Page header, which is the Appendix A text and the line below it.

Table header, which shows the names of the columns in the table.

Explanatory text, which describes the file (source, number of records, number of fields, record size).

Variable name, which is column 1 in the table.

Survey part, which is column 2 in the table.

Data item, which is column 3 in the table.

Data type, which is column 4 in the table.

Field length, which is column 5 in the table.

Start position, which is column 6 in the table.

Description, which is column 7 in the table.

Part name, which is the section of the questionnaire where the survey variable is located.

Description header, which is information that prints in the row above a survey variable, if there is

information to print.

Footer information, which is information that prints in the rows below a survey variable, if there is

information to print.

A variable in the data collection must have a value for columns 1 through 7 in the table. If a data item has been

removed from the survey, it is now reserved for future use. Columns 1 through 6 in the table are all blank for such

items, but we want a special note in column 7 that states that the item is reserved for future use.

The ODS object oriented features allow us to make the part name span the entire page (from columns 1 through

7), and force the first 6 columns to be blank when we insert a special note in column 7. We are also printing two

variables in column 7, description header (if it exists) and description.

* * * * * * * * * * * * * * *

Programming Beyond the BasicsNESUG 2008

Page 4: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

4

DISCUSSION OF THE FILE LAYOUT SPREADSHEET

We developed a spreadsheet that contains two sheets with all the information needed for the record layout. The

file_layout sheet contains the survey metadata for each variable. See Figure 2 for an example.

Figure 2. Survey metadata for BRANCH

The file_layout sheet contains the following variables:

database_doc_output_exclude is a flag with a default value of blank. If the analyst wants to exclude

the variable from the database documentation tables, the analyst sets this variable to X. This variable

was created for use in other SAS programs, which will be included in the online proceedings version of

this paper.

variable_name is the name of the survey variable. This variable is printed in column 1 in the formatted

record layout in Figure 1.

survey_part is the section of the questionnaire in which the survey variable is located. This variable is

printed in column 2 in the formatted record layout in Figure 1.

data_item is a sequence code for the survey variable response. This variable is printed in column 3 in

the formatted record layout in Figure 1.

Programming Beyond the BasicsNESUG 2008

Page 5: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

5

data_type indicates whether the survey variable is alphanumeric or numeric. This variable is printed in

column 4 in the formatted record layout in Figure 1.

field_length is the length of the survey variable. This variable is printed in column 5 in the formatted

record layout in Figure 1.

start_position is the column in the ASCII file where the survey variable begins. This variable is printed in

column 6 in the formatted record layout in Figure 1.

description_header is text that should be printed above a survey variable row, in column 7, if it exists.

description is the text that explains the survey variable. This variable is printed in column 7 in the

formatted record layout in Figure 1.

footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1,

after the survey variable row. The footer variables are generally used to explain in further detail what the

values of the survey variable represent.

The spreadsheet is designed to require no updating from year to year if the variables have not changed. The

description_header for BRANCH, “Location in state government as of October 1, &cy, ^n whom the agency

reports to, and selection methods”, has a macro variable, &cy. The analyst will set the value for &cy in the

program that creates the formatted record layout.

The program uses ^ as the ODS escape character, and the ^n in the description_header for BRANCH causes

“whom the agency reports to, and selection methods” to be printed on the line below “Location in state

government as of October 1, &cy,”. If you specify text that is longer than the column, the text will wrap. Forcing

the new line is simply a way to control where the line breaks.

We allow up to 10 footnotes, simply for ease of data entry. If we force new lines with ^n, it should be possible to

have all the footnotes stored in one footnote field. The footnotes are used to produce other parts of the data file

documentation.

The other sheet in the file layout spreadsheet is the survey_part_description sheet, which contains two variables:

survey_part and survey_part_description. See Figure 3 for the screen shot of the survey_part_description sheet.

Figure 3. Screen shot of survey_part_description sheet

Programming Beyond the BasicsNESUG 2008

Page 6: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

6

DISCUSSION OF O’CONNOR’S SUGI PAPER

O’Connor briefly covered quite a few techniques in his SUGI paper. He started off with an example that shows

tabular data, similar to the kind of output you get from PROC PRINT. Then he discussed using the object

oriented technique to create a report one item at a time. His examples for non-tabular output include creating

customer account statements, where the format of the account statement is non-tabular, and you want to repeat

that output for each customer in your data set.

O’Connor has posted more information at http://support.sas.com/rnd/base/datastep/dsobject/index.html. The

examples from his SUGI paper are at http://support.sas.com/rnd/base/datastep/dsobject/sugi28-22examples.zip.

I downloaded the examples and worked with those programs before figuring out what I needed to do to produce

the formatted record layout.

GETTING STARTED ON THE FORMATTED RECORD LAYOUT

The SAS program discussed in this paper will be part of the online proceedings, along with the spreadsheet that

serves as input for the program. See Figure 4 for the beginning of the data _null_ that produces the formatted

record layout.

We read the spreadsheet into a SAS data set called file_layout. We process the data set by survey_part,

because we want blank lines between each part of the survey in the record layout. Before we print the table, we

set up a two-line title with the obj.title function. When you open the output in Word, the two-line title is the first

header in the document. We have specified ^ as the ODS escape character, so the ^n causes the title to take up

two lines.

The obj.head_start function is the beginning of the table header. Anything specified here will be a table header

that repeats across the pages of the table. Note that we can specify the column width in the obj.format_cell

function. We use ^n to cause the column header to take up two lines in the output.

There are several lines of explanatory text at the top of the record layout that we do not want to repeat across the

pages of the table. The obj.head_end function is the end of the table header. Our table has 7 columns, and we

want the explanatory text to start in column 3 (Data item) and end in column 7 (Description). We specify that the

first two cells in that row are blank with the obj.format_cell(' ', column_span: 2)statement. Then the

following obj.format_cell function specifies the text, and column_span is set to 5 to indicate that the text starts

in column 3 and continues through column 7.

* * * * * * * * * * * * * * *

Programming Beyond the BasicsNESUG 2008

Page 7: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

7

data _null_;

length description_header_text description_text footer1_text footer2_text

footer3_text footer4_text footer5_text footer6_text footer7_text footer8_text

footer9_text footer10_text $ &long_text_length ;

set file_layout end=eof;

by survey_part;

if _n_=1 then

do;

dcl odsout obj();

obj.open_dir();

obj.title(text:

"Appendix A—Record Layout of State Library Agencies Data File, FY &CY^n

(istla&cy_2_digits.a.mdb and istla&cy_2_digits.a.txt)",

overrides: "font_style= " );

obj.table_start();

obj.head_start();

obj.row_start(type: "Header");

obj.format_cell(text: "Variable^n name", overrides: "cellwidth=1in

just=l");

obj.format_cell(text: "Survey^n part", overrides: "cellwidth=0.5in

just=l");

obj.format_cell(text: "Data^n item", overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Data^n type", overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Field^n length", overrides: "cellwidth=0.5in

just=l");

obj.format_cell(text: "Start^n position",

overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Description", overrides: "cellwidth=3.3in just=l");

obj.row_end();

obj.head_end();

/* now add notes at very top of record layout! */

%print_blank_line

obj.row_start();

obj.format_cell(' ', column_span: 2);

obj.format_cell(text: "Data Source: State Library Agencies Survey, Fiscal

Year &cy", column_span: 5, overrides: "just=l");

obj.row_end();

obj.row_start();

obj.format_cell(' ', column_span: 2);

obj.format_cell(text: "Number of records = %trim(&num_records) (one record

per observation)", column_span: 5, overrides: "just=l");

obj.row_end();

obj.row_start();

obj.format_cell(' ', column_span: 2);

obj.format_cell(text: "Number of fields = %trim(&num_fields)",

column_span: 5, overrides: "just=l");

obj.row_end();

obj.row_start();

obj.format_cell(' ', column_span: 2);

obj.format_cell(text: "ASCII file (istla&cy_2_digits.a.txt) is fixed width;

record size = %trim(&rec_size) ", column_span: 5, overrides: "just=l");

obj.row_end();

%print_blank_line

end;

Figure 4. Beginning of the data _null_

Programming Beyond the BasicsNESUG 2008

Page 8: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

8

if first.survey_part then

do;

obj.row_start();

obj.format_cell(text: survey_part_description, column_span: 7,

overrides: "just=l");

obj.row_end();

end;

* if description_header has text, print it out in column 7 after printing

* a blank line. ;

if description_header ne '' then

do;

%print_blank_line

description_header_text=resolve(description_header);

obj.row_start();

obj.format_cell(' ' , column_span: 6);

obj.format_cell(text: description_header_text,

overrides: "just=l font_weight=medium");

obj.row_end();

end;

description_text=resolve(description);

* if variable_name is blank, there should be a note in the description

* field explaining that the item is reserved for future use. want a

* blank line before and after that note. ;

if variable_name='' then

do;

%print_blank_line

obj.row_start();

obj.format_cell(' ', column_span: 6);

obj.format_cell(text: description_text,

overrides: "just=l font_weight=medium");

obj.row_end();

%print_blank_line

end;

PRODUCING ONE SURVEY PART IN THE RECORD LAYOUT

See Figure 5 for the code. As we encounter each distinct value of survey_part for the first time, we want to print

out the description of that survey part and have it take up the entire line. Then we check the description_header

variable for the record. If it has text, we print it in column 7 (Description). We use the resolve function here

because the value of description_header may contain macro variables. The resolve function causes the macro

variable &cy to display as 2005. Then we check variable_name. If variable_name is blank, we print a note in the

Description column stating that the item or items are blank and reserved for future use.

Figure 5. Processing each survey part

PRINTING ALL THE DETAILS FOR A SURVEY VARIABLE

See Figure 6 for the code. We use the obj.format_cell function repeatedly to print the variable_name,

survey_part, data_item, data_type, field_length, start_position, and description_text. In Figure 5, description_text

is the result of the resolve function on the description variable. We use the resolve function because description

may contain macro variables. Once we have printed all the details for the survey variable, we need to check for

footnotes, called footer1 through footer10 in the spreadsheet. The %check_footers macro is checking each

footer variable. If the footer variable is not blank, it gets printed in column 7 (Description). If the next footer

variable is blank, we print a blank line. If we have finished processing the last record for that survey_part, we print

a blank line. If there are more survey_parts to process, we continue. If we have reached the end of the file, we

use the appropriate functions to close our table.

Programming Beyond the BasicsNESUG 2008

Page 9: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

9

* otherwise the observation in the data set is an entire row that should

* be printed out. ;

else

obj.row_start();

obj.format_cell(text: variable_name, overrides: "just=l font_weight=medium");

obj.format_cell(text: survey_part, overrides: "just=l font_weight=medium");

obj.format_cell(text: data_item, overrides: "just=l font_weight=medium");

obj.format_cell(text: data_type, overrides: "just=l font_weight=medium");

obj.format_cell(text: trim(put(field_length, 10.)),

overrides: "just=l font_weight=medium");

obj.format_cell(text: trim(put(start_position, 10.)),

overrides: "just=l font_weight=medium");

obj.format_cell(text: description_text,

overrides: "just=l font_weight=medium");

obj.row_end();

* check to see if footer1, footer2, footer3, etc exist. if they do,

* print in column 7 ("description") in output, and put a blank line

* after last bit of text. ;

%check_footers

if footer10 ne '' then

do;

footer10_text=resolve(footer10);

obj.row_start();

obj.format_cell(' ', column_span: 6);

obj.format_cell(text: footer10_text,

overrides: "just=l font_weight=medium");

obj.row_end();

end;

if last.survey_part then

do;

%print_blank_line ;

end;

if eof then

do;

obj.table_end();

obj.title(clear: 2);

obj.close_dir();

end;

run;

Figure 6. Printing all the details for a variable

NOTES AT THE VERY END OF THE RECORD LAYOUT

At the end of the record layout, we want to print four notes. The first note gives the definition for N in the record

layout, the second note gives the definition for A in the record layout, the third note gives the definition for the

daggers in the record layout, and the fourth note tells the reader that the survey instrument is in another appendix.

We use RTF text statements to place these notes after the table created with ODS object oriented programming.

ODS RTF TEXT="N\tab Numeric field.";

ODS RTF TEXT="A\tab Alpha character field.";

ODS RTF TEXT="†\tab Not applicable.";

ODS RTF TEXT="NOTE: The survey instrument is in appendix C.";

Programming Beyond the BasicsNESUG 2008

Page 10: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

10

A FEW PROBLEMS STILL REMAIN

The formatted record layout is one appendix in the data file documentation. The RTF output from this program is

added to a longer Word document that contains the user’s guide, state codes, survey instrument, imputation flag

frequencies, and frequencies and distributions of the variables. We have had problems with the page number

code on other RTF outputs when inserted into existing Word documents, so we do not handle page numbering in

the SAS program. This may not be an issue in SAS 9.2.

We used ODS RTF output in this program to create the output file. ODS RTF output lets Word handle page

breaks. The original record layout in Word was manually edited to prevent variable groups from splitting across

pages, and to ensure that the survey part description was not stranded on one page with the first variable in that

section on the following page. Variable groups do split across pages and survey part descriptions are sometimes

stranded in the output from this program. SAS 9.2 with the RTF tagset may be able to avoid these problems.

OTHER PROBLEMS NOW SOLVED

Now that we have the survey metadata in an Excel spreadsheet, it is available for use in other programs.

Specifically, we can now use the footer1 through footer10 variables when we produce PROC FREQ output for the

database documentation. The online proceedings will contain the other programs used to produce some of the

appendices for the public use file database documentation.

WARNING ON EXPERIMENTAL METHOD

Let me emphasize that the object oriented features are experimental in SAS 9.1.3. Figure 7 is a screen shot from

the log when you run this program. Note that the message in green is important, so I spotlighted it here.

Figure 7. Warning that method is experimental in SAS 9.1.3

* * * * * * * * * * * * * * *

Programming Beyond the BasicsNESUG 2008

Page 11: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

11

REFERENCES

DiIorio, Frank and Abolafia, Jeff. 2006. “The Design and Use of Metadata: Part Fine Art, Part Black Art”,

Proceedings of the 14th Annual Southeast SAS Users Group Conference. Atlanta, GA. Available at

http://analytics.ncsu.edu/sesug/2006/ST01_06.PDF.

O’Connor, Daniel. 2003. “Next Generation Data _NULL_ Report Writing Using ODS OO Features”, Proceedings

of the Twenty-Eighth Annual SAS® Users Group International Conference. Seattle, WA. Available at

http://www2.sas.com/proceedings/sugi28/022-28.pdf. O’Connor has posted more information at

http://support.sas.com/rnd/base/datastep/dsobject/index.html and

http://support.sas.com/rnd/base/datastep/dsobject/sugi28-22examples.zip.

ACKNOWLEDGEMENTS

SAS is a Registered Trademark of the SAS Institute, Inc. of Cary, North Carolina.

Other brand and product names are trademarks of their respective companies.

The author wishes to thank Cindy Sheckells for asking if SAS could produce this type of output. The author

wishes to thank Chris Boniface, James Clement, Carma Hogue, Johnny Monaco, Rita Petroni, Cindy Sheckells,

and Freda Spence for reading the draft of this paper and providing helpful comments.

DISCLAIMER

This report is released to inform interested parties of research and to encourage discussion. The views expressed

on technical issues are those of the author and not necessarily those of the U.S. Census Bureau.

CONTACT INFORMATION

Your comments and questions are valued and encouraged. Contact the author at:

Suzanne M. Dorinski

U.S. Census Bureau

OSMREP, Room 7K042E

Washington, DC 20233

(301)-763-4869

[email protected]

* * * * * * * * * * * * * * *

Programming Beyond the BasicsNESUG 2008

Page 12: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

12

APPENDIX – COMPLETE PROGRAM dm 'cle log; cle out';

*********************************************************************************;

* this is create_StLA_file_layout_document_from_spreadsheet_OBJECT_ORIENTED.sas *;

* *;

* Program creates file layout from spreadsheet, using ODS object oriented *;

* features. See SUGI paper 22-28 by Daniel O'Connor of SAS for more details. *;

* Output is RTF. TECHNIQUE IS EXPERIMENTAL FOR SAS 9.1.3. *;

* *;

* suzanne m. dorinski 7/6/08 *;

*********************************************************************************;

**************** MACRO VARIABLES **************************************;

* cy is the current year in 4 digit format;

* py is the prior year in 4 digit format;

* cy_2_digits is the last 2 digits of current year;

* long_text_length is used in length and attrib statements so that we can deal with

* really long text fields;

* num_records is the number of states that responded for StLA;

* num_fields is the number of fields shown in the record layout;

* rec_size is physical size of the fixed-width file;

* input_dir_spreadsheet is the directory where the spreadsheet is stored;

* spreadsheet_name is the name of the file layout spreadsheet -- do NOT include

.xls extension;

* dsn_dir is the directory where the permanent data set will be stored;

* output_dir is the directory where the RTF will be stored;

* output_file_name is the name of the RTF output -- do NOT include .rtf extension;

%let cy=2005;

%let py=2004;

%let cy_2_digits=05;

%let long_text_length=1000;

%let num_records=51;

%let num_fields=468;

%let rec_size=103 KB;

%let input_dir_spreadsheet=C:\Users\Suzanne\Documents\NESUG_2008_StLA;

%let spreadsheet_name=StLA_2005_file_layout_in_Excel_OBJECT_ORIENTED;

%let output_dir=C:\Users\Suzanne\Documents\NESUG_2008_StLA;

%let output_file_name=&cy._example_file_layout_from_spreadsheet_OBJECT_ORIENTED;

**************** END MACRO VARIABLES **********************************;

options missing=' ' linesize=132 pagesize=60 nodate nonumber mprint;

title;

footnote;

ODS ESCAPECHAR='^';

ODS LISTING CLOSE; * do not show output in listing window ;

ODS RESULTS OFF; * do not open up Word viewer in SAS session ;

ODS PATH WORK.TEMPLAT(UPDATE) SASHELP.Tmplmst(READ);

proc template;

define style styles.newprinter;

parent=styles.printer;

replace color_list

Programming Beyond the BasicsNESUG 2008

Page 13: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

13

"Colors used in the default style" /

'link'= blue

'bgH'= white /* bgH was graybb */

'fg' = black

'bg' = white;

replace fonts /

'TitleFont2' = ("Arial",12pt,Bold)

/* this is font used for titles in ODS OO output! */

'TitleFont' = ("Arial",12pt,Bold )

/* default is Times Roman 13pt bold italic */

'StrongFont' = ("Times Roman",12pt,Bold) /* default is 10 pt */

'EmphasisFont' = ("Times Roman",10pt,Italic)

'FixedEmphasisFont' = ("Courier",9pt,Italic)

'FixedStrongFont' = ("Courier",9pt,Bold)

'FixedHeadingFont' = ("Courier",9pt,Bold)

'BatchFixedFont' = ("SAS Monospace, Courier",6.7pt)

'FixedFont' = ("Courier",9pt)

'headingEmphasisFont' = ("Times Roman",12pt,Bold Italic)

/* default is 11 pt */

'headingFont' = ("Arial",9pt,Bold)

/* default is Times Roman 11 pt bold */

'docFont' = ("Arial",9pt); /* default is Times Roman 10 pt */

style body from document /

leftmargin=0.8in

rightmargin=0.5in

topmargin=0.8in

bottommargin=1in;

style rowheader from rowheader /

background=_undef_

font=fonts('headingFont')

just=left

protectspecialchars=off;

/* allow me to insert RTF control words */

style table from table /

rules=groups /* only line inside table is below header */

frame=above /* put line above header */

cellpadding=0pt /* minimize space inside cells */

outputwidth=100%;

/* outputwidth=100% forces all tables to use entire width of page */

style data from data /

protectspecialchars=off;

/* allow me to use RTF control words in data cells */

style UserText from UserText

"Controls the TEXT= style" /

outputwidth=100%

/* force ODS RTF TEXT= cells to use entire width of page */

protectspecialchars=off;

style systemtitle from systemtitle /

protectspecialchars=off;

/* allow me to insert RTF control words */

end;

run;

%macro print_blank_line;

obj.row_start();

obj.format_cell(' ', column_span: 7);

obj.row_end();

%mend print_blank_line;

%macro check_footers;

Programming Beyond the BasicsNESUG 2008

Page 14: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

14

%do i=1 %to 9;

if footer&i ne '' then

do;

footer&i._text=resolve(footer&i);

obj.row_start();

obj.format_cell(' ', column_span: 6);

obj.format_cell(text: footer&i._text,

overrides: "just=l font_weight=medium");

obj.row_end();

if footer%eval(&i+1)='' then

do;

%print_blank_line

end;

end;

%end;

%mend check_footers;

PROC IMPORT OUT= WORK.file_layout

DATAFILE= "&input_dir_spreadsheet\&spreadsheet_name..xls"

DBMS=EXCEL REPLACE;

SHEET="file_layout";

GETNAMES=YES;

MIXED=NO;

SCANTEXT=YES;

USEDATE=YES;

SCANTIME=YES;

RUN;

PROC IMPORT OUT= WORK.part_description

DATAFILE= "&input_dir_spreadsheet\&spreadsheet_name..xls"

DBMS=EXCEL REPLACE;

SHEET="survey_part_description";

GETNAMES=YES;

MIXED=NO;

SCANTEXT=YES;

USEDATE=YES;

SCANTIME=YES;

RUN;

* at the moment, we assume that the file layout spreadsheet

* DOES have survey_part for items reserved for future use. need to

* do that to make sure that the merge works properly. ;

data file_layout;

merge file_layout(in=f)

part_description(in=p);

by survey_part;

run;

* description_header is currently longer than description field,

* so we need to use ATTRIB statement below, because description

* is the column where description_header will be printed.

* we also have to add extra length to account for any special

* RTF control words and style formats applied inside description.

* remember that ATTRIB statement needs to be done before set

* statement, and you have to specify both the length and the format! ;

data file_layout;

attrib description length=$&long_text_length format=$&long_text_length..;

set file_layout;

Programming Beyond the BasicsNESUG 2008

Page 15: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

15

run;

options orientation=portrait center;

ODS RTF FILE="&output_dir\&output_file_name..rtf"

STYLE=newprinter;

* RESOLVE FUNCTION! need it here to get SAS to resolve the macro variables that

* are used in description_header and description variables. as far as i can tell,

* this is the only way to get the &cy in the Excel spreadsheet to show up as the

* value specified in the %let statement at the top of this program. ;

* also using resolve function to resolve macro variables used in footer variables;

data _null_;

length description_header_text description_text footer1_text footer2_text

footer3_text footer4_text footer5_text footer6_text footer7_text footer8_text

footer9_text footer10_text $ &long_text_length ;

set file_layout end=eof;

by survey_part;

if _n_=1 then

do;

dcl odsout obj();

obj.open_dir();

obj.title(text: "Appendix A—Record Layout of State Library Agencies Data

File, FY &CY^n (istla&cy_2_digits.a.mdb and istla&cy_2_digits.a.txt)",

overrides: "font_style= " );

obj.table_start();

obj.head_start();

obj.row_start(type: "Header");

obj.format_cell(text: "Variable^n name", overrides: "cellwidth=1in just=l");

obj.format_cell(text: "Survey^n part", overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Data^n item", overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Data^n type", overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Field^n length", overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Start^n position",

overrides: "cellwidth=0.5in just=l");

obj.format_cell(text: "Description", overrides: "cellwidth=3.3in just=l");

obj.row_end();

obj.head_end();

/* now add notes at very top of record layout! */

%print_blank_line

obj.row_start();

obj.format_cell(' ', column_span: 2);

obj.format_cell(text: "Data Source: State Library Agencies Survey, Fiscal

Year &cy", column_span: 5, overrides: "just=l");

obj.row_end();

obj.row_start();

obj.format_cell(' ', column_span: 2);

obj.format_cell(text: "Number of records = %trim(&num_records) (one record

per observation)", column_span: 5, overrides: "just=l");

obj.row_end();

obj.row_start();

obj.format_cell(' ', column_span: 2);

obj.format_cell(text: "Number of fields = %trim(&num_fields)",

column_span: 5, overrides: "just=l");

obj.row_end();

obj.row_start();

obj.format_cell(' ', column_span: 2);

Programming Beyond the BasicsNESUG 2008

Page 16: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

16

obj.format_cell(text: "ASCII file (istla&cy_2_digits.a.txt) is fixed width;

record size = %trim(&rec_size) ", column_span: 5, overrides: "just=l");

obj.row_end();

%print_blank_line

end;

if first.survey_part then

do;

obj.row_start();

obj.format_cell(text: survey_part_description, column_span: 7,

overrides: "just=l");

obj.row_end();

end;

* if description_header has text, print it out in column 7 after printing a blank

* line. ;

if description_header ne '' then

do;

%print_blank_line

description_header_text=resolve(description_header);

obj.row_start();

obj.format_cell(' ' , column_span: 6);

obj.format_cell(text: description_header_text,

overrides: "just=l font_weight=medium");

obj.row_end();

end;

description_text=resolve(description);

* if variable_name is blank, there should be a note in the description field

* explaining that the item is reserved for future use. want a blank line before

* and after that note. ;

if variable_name='' then

do;

%print_blank_line

obj.row_start();

obj.format_cell(' ', column_span: 6);

obj.format_cell(text: description_text,

overrides: "just=l font_weight=medium" );

obj.row_end();

%print_blank_line

end;

* otherwise the observation in the data set is an entire row that should be printed

* out. ;

else

obj.row_start();

obj.format_cell(text: variable_name, overrides: "just=l font_weight=medium");

obj.format_cell(text: survey_part, overrides: "just=l font_weight=medium");

obj.format_cell(text: data_item, overrides: "just=l font_weight=medium");

obj.format_cell(text: data_type, overrides: "just=l font_weight=medium");

obj.format_cell(text: trim(put(field_length, 10.)),

overrides: "just=l font_weight=medium");

obj.format_cell(text: trim(put(start_position, 10.)),

overrides: "just=l font_weight=medium");

obj.format_cell(text: description_text,

overrides: "just=l font_weight=medium");

obj.row_end();

* check to see if footer1, footer2, footer3, etc exist. if they do, print in

* column 7 ("description") in output, and put blank line after last bit of text. ;

%check_footers

if footer10 ne '' then

do;

footer10_text=resolve(footer10);

obj.row_start();

Programming Beyond the BasicsNESUG 2008

Page 17: Using ODS Object Oriented Features To Produce A Formatted ... · footer1 through footer10, if they exist, are printed in column 7 in the formatted record layout in Figure 1, after

17

obj.format_cell(' ', column_span: 6);

obj.format_cell(text: footer10_text, overrides: "just=l font_weight=medium");

obj.row_end();

end;

if last.survey_part then

do;

%print_blank_line ;

end;

if eof then

do;

obj.table_end();

obj.title(clear: 2);

obj.close_dir();

end;

run;

ODS RTF TEXT="N\tab Numeric field.";

ODS RTF TEXT="A\tab Alpha character field.";

ODS RTF TEXT="†\tab Not applicable.";

ODS RTF TEXT="NOTE: The survey instrument is in appendix C.";

ODS RTF CLOSE;

proc template;

delete styles.newprinter;

run;

* now open the listing window again ;

ODS LISTING;

Programming Beyond the BasicsNESUG 2008