NESUG 2009 Applications Big & Small

Visual Basic for Application with SAS® Software to Dynamically Collate Word Documents

Mei Li, ImClone Systems, Branchburg, NJ Zemin Zeng, Forest Research Institute, Inc., Jersey City, NJ

ABSTRACT This paper describes the process of developing an application to collate and print Microsoft Word documents with SAS output tables and charts by region. The authors’ need was to customize Word documents generated by SAS, collate the Word document with SAS created tables and charts for individual regions selected by the SAS code, then print and mail the set of Word and SAS output files to the individual regions. To automate the process, the authors first use SAS to create external BAS files, dynamically import BAS files into Microsoft for Application (VBA) projects, then execute the VBA projects to automatically collate and print Word documents in Microsoft Word.

INTRODUCTION Visual Basic for Applications (VBA) is a common development environment among all Office applications, SAS programmers have long taken advantage of VBA with Microsoft Office applications to perform post-process of SAS output in Microsoft Word. In the paper (Stetz, SUGI 25 Proceedings, 2000), Stetz built an elegant bridge between SAS and VBA. Stetz invented a Visual Basic Word macro called Run_VBA in Microsoft Word, which allows users to dynamically create new VBA procedures from SAS environment, and execute new Word application as instructed by the SAS statements. There are many publications applying Stetz’ technique in , but a very few in Microsoft Word. In the paper (Luo, SUGI 29 Proceedings, 2004), Lou attempted to apply Stetz’ technique to automatically allocate and print customized SAS output tables and charts, but did not succeed and had to use an alternative approach to achieve the goal. This paper will re-consider Luo’s allocating and printing tasks, and describes a process following Stetz’ technique to successfully complete the tasks. The authors first use SAS to create external BAS files, dynamically import BAS files into Microsoft Visual Basic for Application (VBA) projects via issuing Window DOS commands in SAS, then execute the VBA projects to automatically collate and print Word documents in Microsoft Word. Comparing to Luo’s approach, our application is much more dynamic, simple and straightforward.

COLLATING TASKES In the paper (Luo, SUGI 29 Proceedings, 2004), It was need to mail letters associated by SAS output tables and charts to hospitals. A desired application needs to be developed to automate the following: 1. Incorporate a well-formatted letter created in Microsoft Word. 2. Each letter should have the specific address selected by the SAS program. 3. Each letter should be followed by the table and chart produced by the SAS program for that specific location, post-process of the table and chart are required to be set as landscape. 4. The letter, table, and chart should be printed together. 5. The collating and printing process should continue to the next location until completed for all selected locations. 6. The automation should accept input from the user to specify parameters such as paths to files. 7. The whole process should not require intermittent manual interaction and afterward cleanup. In this paper, we will use the dummy data as presented in Luo’s paper and demonstrate how to develop an application to accomplish the above tasks by applying Stetz’ technique.

STETZ’ TECHNIQUE In the paper (SUGI 25 Proceedings, 2000), Stetz provided a Word macro Run_VBA to automate Word task. This Word macro creates a Word subroutine under the Normal project in Visual Basic by importing an external BAS file, invoke the Word subroutine, and the application defined by the Word subroutine will execute immediately, then delete the Word macro from the Normal project at the end. To meet authors’ need for the application in Microsoft Word, the authors modified it as below which is necessary to dynamically create new Word procedures for our application purpose (See the Appendix 1 for the modified version. Also see the paper Zeng and Li 2009 NESUG for other modified setup via VBA application of Stetz’ technique): 1. Delete the ‘kill bas file statement’ so that there is enough time to execute the new dynamically generated Word macro which is defined in the bas file in SAS.

1 NESUG 2009 Applications Big & Small

2. Set the Exit Word to ‘NO’, namely do not quit the Word application.

These two modifications are crucial, because if the bas file was deleted before the new macro execution, the application defined by the BAS file would be abnormally interrupted and results in a failure of executing the Word application. As for setting the Exit Word to ‘NO’, it would guarantee that there is enough time for the printing process to finish. If the Application.Quit command in VBA was issued, then the printing process would be cancelled, as a consequence, the printing job will not be continued.

The Run_VBA Word macro needs to be installed in the Microsoft Word. For the users of the Microsoft Word 2003, the authors refer to the set-up steps in Stetz’ paper (SUGI 25 Proceedings, 2000). The users need to strictly follow the 11 steps starting from the second page of Stetz’ paper, except copy the authors’ modified Run_VBA code in the Appendix 1 to the Visual Basic Editor (VBE). Then follow the 3 steps in the middle of the third page of Stetz’ paper to set-up the Word prompt for saving changes to the Normal Template.

For the users of Microsoft Word 2007, the 3 steps to set-up the Word prompt for saving changes to the Normal Template in Microsoft Word are not applicable, neither the first step of the Stetz’ 11 steps. Instead, the users first need to open the Microsoft Word and use shortcut ALT + F11 to open the VBE and then follow the remaining 10 steps as Stetz provided. Furthermore additional steps below need to follow as well: 1. Open Microsoft Word 2007 2. Click the Developer menu 3. Click the Macro Security at the left corner of the Menu bar 4. Select the Macro Settings 5. Check the Enable all macros and Trust access to VBA project object model

ALLOCATE WORD DOCUMENTS After the installation of the Word macro Run_VBA (see Appendix 1) in the Microsoft Word, it is ready to establish our allocating and printing process. Below are the steps (see the macro allocate.sas in Appendix 2 and refer to the codes there in the following steps):

Step I. Get the data and produce table and chart by region. For the illustration purpose, we will take the example as in Luo’s paper. The codes in this part were copied from Luo’s paper. Also assume we have a generic letter (say with file name letter.doc, in the document, hit Enter first, then make a centered Title and some normal-font content) which is going to be personalized and mailed out with the SAS output table and chart for a particular region. The address Excel file is as in the Appendix 3 and need to be saved, say in the same folder where the letter resides. Our focus now is no how to customize the letter and the SAS output table and chart (specifically, to add the mailing address of the manager in corresponding region in the beginning of the letter and set the table and chart to be landscape) and then print them out by region via dynamical VBA macros, which are going to be created in SAS, and then invoke them in the VBA and execute them to automate our Word tasks on the SAS output table and chart.

Step II. Define the BAS file for how to customize letter, SAS output table and chart by region in VBA languages. We create the bas file using the SAS put statements and save it to location where SAS output tables and charts reside. These codes belong to VBA languages, and mean the following actions in Microsoft Word: a. Open the letter file in Microsoft Word b. Insert the address lines to the beginning of the letter c. Print the letter document and then close it without saving the address line just inserted d. Open the SAS output Word document consisting of the table and chart e. Set the SAS output Word document to be landscape f. Print the Word document and close it without saving the landscape page setting Comparing to Luo’s approach, we integrate the inserting and page setting procedures together in a single VBA macro which is going to be generated by importing the BAS file to VBA in the next step. Such BAS file can be so flexibly created in SAS, while in Luo’s approach, such similar VBA procedures were needed to be recorded in the VBA environment separately. Step III. Set up the VBA running environment, import the BAS file and run it in VBA to generate VBA macro A batch file is to be defined by SAS. In the batch file, the VBA environment is set up and the system function is called to run the batch file. During the running time of the batch file, the following actions are taken place: • Invoke Word using the /mRun_VBA startup switch • Create a new module in the Normal project in VBA • Read in the BAS file defined in the Step II containing the VBA macro shoesi (where i is an integer and takes values from 1 to the total number of the regions) generated in the SAS data step • Execute the VBA macro shoesi • Delete the new model from the Normal project in VBA

During the execution of the VBA macro shoesi for the ith region, the address lines of the manager of the ith region are added to the beginning of the letter and print the letter and then open the ith region’s Word document and set the landscape page layout and print it. By this Step III, the allocating and printing tasks by region are accomplished. In the next step, we will do the file clear up.

2 NESUG 2009 Applications Big & Small

Step IV. Close the Word application and Clear up the BAS file Before close the Word application, it is very necessary to let the system sleep a few seconds. To close the Microsoft Word, we use Dynamic Data Exchange (DDE) and WordBasic commands. At the end, delete the BAS file.

Through the above four steps, we complete the customizing, allocating and printing process of the Word documents. It needs to point out that we should have all Word windows be closed before run the macro allocate.sas (Appendix 2), Otherwise an error message ‘This file is in use by another application or user’ in the Microsoft Word would appear due to the modifications to the Normal Template in the VBA during the running of allocate.sas. Also when ODS RFT invokes Microsoft Word to open the shoesi.rtf file, we need select Cancel to not open the rtf file due to the next immediately Word-process on the file.

CONCLUSIONS The process of developing an application to collate and print Microsoft Word documents with SAS output tables and charts by region is accomplished by the dynamical conversations between SAS software and VBA in Microsoft Word environment. The application, comparing to the one in the paper (Luo, SUGI 29 Proceedings, 2004), is much more straightforward, dynamic and simple. Furthermore, the demonstrated way to establish connections between SAS and VBA could be easily adapted for developing new applications.

REFERENCES Stetz, Mark (2000). Using SAS® Software and Visual Basic for Applications to Automate Tasks in Microsoft Word: An Alternative to Dynamic Data Exchange, Proceedings of the Twenty-Fifth Annual SAS Users Group International Conference, Paper 21-25.

Luo, Haiping (2004). Let SAS® Tell Microsoft World® to Collate, Proceedings of the Twenty-Ninth Annual SAS Users Group International Conference, Paper 034-29.

Zeng, Zemin and Li, Mei (2009). Using SAS® Software and Visual Basic for Application to Automatically Produce Customized RTF Tables from SAS List Files, Coder's Corner section, Proceedings of the NESUG 2009.

ACKNOWLEDGMENTS 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. ® indicates USA registration. CONTACT INFORMATION Your comments and questions are valued and encouraged. Contact the authors at: Mei Li, ImClone Systems, 33 ImClone Drive, Branchburg, New Jersey 08876. Email: [email protected]

Zemin Zeng, Forest Research Institute, Inc., Jersey City, NJ 07311. Email: [email protected] or [email protected]

APPENDIX 1: RUN_VBA WORD MACRO Sub Run_VBA() '*** Adapted from the R_VBA Word Macro, originally created by Mark Stetz *** '*** SUGI 25, Paper: 21-25, 2000 ***

'*** Declare local variables *** Dim vbcs As VBComponents Dim vbc As VBComponent Dim vba_filename As String Dim vba_path As String Dim vba_pgm As String

'*** Read environment variables (created in RUN_VBA.SAS) *** vba_path = Environ("VBA_PATH") vba_pgm = Environ("VBA_PGM")

'*** Get a reference to the Normal template component collection *** Set vbcs = VBE.VBProjects("Normal").VBComponents

'*** Construct the filename of the SAS generated VBA macro *** vba_filename = vba_path & "\" & vba_pgm & ".bas"

3 NESUG 2009 Applications Big & Small

'*** Add a new code module to the Normal template *** Set vbc = vbcs.Add(vbext_ct_StdModule) '*** Read in the SAS generated VBA macro *** vbc.CodeModule.AddFromFile FileName:=vba_filename

'*** Run the SAS generated VBA macro *** Application.Run MacroName:=vba_pgm

'*** Delete the code module from the Normal template *** vbcs.Remove VBComponent:=vbc

End Sub

APPENDIX 2: COLLATE.SAS %macro collate(letter=, outfile=); /************************************************************************** * MACRO NAME: collate * AUTHORS: Mei LI and Zemin ZENG * DATE: 05/26/2009 * * PARAMETERS: letter -- the path of the standard letter * outfile -- the path of the folder to store the report * and chart files * * CALL SAMPLE: %collate(letter=G:\vba_collate, outfile=G:\vba_collate\output) *------* CAUTIONS: 1. Need the set the R_VBA Word macro in Microsoft Word first * 2. Change the wordpath path if your Winword.exe file is not * in the folder: C:\Program Files\Microsoft Office\Office12 * 3. All Word window should be closed when run this macro *------*/ options noxwait noxsync mprint; %local wordpath vba_path vba_pgm h1 h2 h3 h4; %let wordpath=C:\Program Files\Microsoft Office\Office12; %let vba_path=&outfile; /*------* | Step I. Get the data and produce table and chart by region | /*------*/ proc sort data=sashelp.shoes out=shoes; by subsidiary product; where (substr(region,1,2) eq 'Ca') and (substr(subsidiary,2,1) eq 'o'); run; *** Count and assign a number to each selected subsidiary for looping purpose; proc sort data=shoes(keep=subsidiary) out=shoes0 NODUPKEY; by subsidiary; run;

data snumber; set shoes0 end=lastobs; by subsidiary; sn=_n_; if lastobs then call symput('subnumber',sn); Label sn = "Subsidiary Number"; run;

data shoes; merge shoes snumber; by subsidiary; run;

*to import the address excel file; proc import out=address datafile="&letter\SUBADDRESS.xls" dbms=excel97 replace; sheet="Sheet1$"; getnames=YES; run;

4 NESUG 2009 Applications Big & Small

data shoes; merge shoes(in=a) address; by subsidiary; ** Calculate some business statistics; ros=(returns/sales)*100; rov=(returns/inventory)*100; avgsales_sub=sales/stores; format ros rov 6.2 avgsales_sub dollar8.; Label ros="Returns on Sales" rov="Returns on Inventory" avgsales_sub="Average Store Sales at the Subsidiary"; if a; run;

%do i=1 %to &subnumber; *** Get regional data; data sales; set shoes(where=(sn=&i and Region^='')); by subsidiary; ** obtain the mailing headers from the regional data; if first.subsidiary then do; call symput('h1',manager); call symput('h2',address1); call symput('h3',address2); call symput('h4',address3); end; run; *** Output to a rtf file; ods listing close; ods rtf file="&outfile\shoes&i..rtf"; *** Print the regional data to a table in the rtf file; title1 "Sales Report"; proc print data=sales (drop=region manager address1 address2 address3 sn) label noobs style(header)={background=white}; by subsidiary; run; *** Output the regional data to a chart in the rtf file; goptions reset=all; proc gchart data=sales; vbar product / sumvar=ros patternid=midpoint raxis=axis1; title1 "Sales Report"; axis1 minor=none; by subsidiary; run; quit; ods rtf close; ods listing; /*------* | Step II. Define the BAS file for how to customized letter, SAS | | output table and chart by region in VBA languages | /*------*/ %let vba_pgm=shoes&i; data _null_; file "&vba_path\&vba_pgm..bas"; put "Sub &vba_pgm()" ; put 'ChangeFileOpenDirectory' '"' "&letter" '"' ; put 'Documents.Open FileName:=' '"' 'letter.doc' '"' ; put 'Selection.WholeStory' ; put 'With Selection' ; %if &h4 ne %then %do; put ' .InsertBefore chr(9) & ' '"' "&h4 " '"' '& chr(13)'; %end; %if &h3 ne %then %do; put ' .InsertBefore chr(9) & ' '"' "&h3 " '"' '& chr(13)'; %end; %if &h2 ne %then %do; put ' .InsertBefore chr(9) & ' '"' "&h2 " '"' '& chr(13)'; %end; %if &h1 ne %then %do; put ' .InsertBefore "To" & chr(9) & ' '"' "&h1 " '"' '& chr(13)'; %end; put ' .Collapse Direction :=wdCollapseEnd' ; put 'End With' ;

5 NESUG 2009 Applications Big & Small

put 'ActiveDocument.PrintOut' ; put 'ActiveDocument.Close SaveChanges:=False' ; put 'ChangeFileOpenDirectory' '"' "&outfile" '"' ; put 'Documents.Open FileName:=' '"' "&vba_pgm..rtf" '"' ; put 'Selection.WholeStory' ; put 'With ActiveDocument.PageSetup' ; put ' .Orientation = wdOrientLandscape' ; put 'End With' ; put 'ActiveDocument.PrintOut' ; put 'ActiveDocument.Close SaveChanges:=False' ; put 'End Sub' ; run; /*------* | Step III. Set up the VBA running environment, import the BAS | | file and run it in VBA to generate VBA macro | *------*/ data _null_ ; file "&vba_path\&vba_pgm..bat" ; put "set VBA_PATH=&vba_path" ; put "set VBA_PGM=&vba_pgm" ; put "cd &wordpath" ; put "start winword.exe /mrun_vba" ; put "del ""&vba_path\&vba_pgm..bat"" " ; run;

data _null_ ; call system("""&vba_path\&vba_pgm..bat""") ; run; /*------* | Step IV. Close the Word application and clear up the BAS file | /*------*/ data _null_; x=sleep(10); run;

filename cmds dde 'WinWord|System'; data _null_; file cmds; put '[AppClose]'; run; filename cmds clear;

x "del &vba_path\&vba_pgm..bas"; %end; %mend collate; *%collate(letter=G:\vba_collate, outfile=G:\vba_collate\output);

APPENDIX 3: SUBADDRESS.XLS Subsidiary manager address1 address2 address3 Calgary Jim Smith 123 M Street Calgary Montreal Brigitte Zuech 321 G Drive Suite E Montreal Ottawa Larry Watkins 987 T Ave Ottawa Toronto Nancy Green 346 H Lane Toronto Vancouver Don Ali 675 D Way Suite 3 ancouver

6