I just started using the excelxp tagset and I've succeeded in having it create multiple worksheets within a workbook, with customized headers, and lots of other nifty features. Since I am trying to duplicate an existing Excel output format, now I need to force hard page breaks, within worksheets, when the value of a variable changes. The goal is to be able to print the worksheet and have each value of this variable start on a new page. I'm using proc report, and have tried using a compute block to at least add a line at the break point (I figure if I can output a line, I can then replace it with the actual XML code for a hard page break, once I find out what that is!) but I get an error message that there is a problem with the table when I try to open the file in Excel. Here's my code:
ods path work.cas(update) sasuser.templat(update) sashelp.tmplmst(read);
define style styles.CASXL; * just creating a copy;
%let title1 = "&campaigndesc - from &strtdt to &enddt";
%let title2 = "Data as of &asofdt";
When I manually go into an Excel sheet and insert "hard" page breaks and then save the file as an XML 2003 file, this is what I observe -- the XML has a PageBreaks tag like this:
And the <PageBreaks> </PageBreaks> tags enclose a <Row> tag that contains the row number (in this case, 10) where I wanted the "hard" page break.
The only way I know to insert XML or alter the XML that's created by TAGSETS.EXCELXP is to
1) change the tagset template using PROC TEMPLATE. This is not a trivial task. or
2) post-process the XML to manually or programmatically insert the XML that's needed. This is not a trivial task either.
I would suggest that you experiment with the FITTOPAGE or FITTOWIDTH suboptions to see whether you can force a page break the way you want or work with Tech Support on a tagset template solution. To open a track with Tech Support, fill out the form at this link: http://support.sas.com/ctx/supportform/createForm
Thanks for your reply - let me see if I understand this - I could post-process the XML, and add the PageBreaks tag to each worksheet, specifying at which Row I want each page break? That sounds do-able - I know where the breaks should be, after each change in one of my variables. I can count how many rows each group is, add the header rows, and generate a list of row locations for the page breaks. Then, I just have to write out the PageBreak tag block, at the end of every worksheet in the workbook. Can I do that by reading the XML back into SAS, find each end of worksheet tag, and write it back out, adding the page break blocks in? Sounds like a challenge, but possible.
If I do it this way, I don't need the 'Compute after' compute block, do I? I thought I'd have to put the page break physically where I wanted it to occur. Putting them all at the end of the worksheet is easier.
What you describe is do-able with a program. You can read an XML file with a SAS program and then write out an XML file.
If you are going to put more than 1 hard page break into the file, then you have to take into account whether embedded titles or more than 1 header rows are included, because this may have an impact on the row number that you use for the break. If I manually insert 2 hard page breaks into an Excel worksheet and then save the workbook/worksheet as Excel 2003 XML, the <PageBreaks> section looks like this:
Note the repetition of the <RowBreak> tags within one <PageBreaks> section.
Thanks, yes I will have to compensate for any additional lines used by titles and header rows.
Is there anything I should know about reading in the XML? Is it just a text file? Can you point me to a sample, to get me started?
This is great - once I get this working, it will allow me to eliminate the use of a very tedious Excel macro, that takes very long to run. SAS will produce my output, formatted beautifully, all ready to be sent to the client! Such an improvement since the old listing days! :-)
An XML file is just an ASCII text file. Basically, you're going to read a line and write a line until you encounter the </Worksheet> tag. At that point, you will insert the <PageBreaks> info for every worksheet.
illustrates how to read an ASCII text file (not an XML file -- but a .SAS program). However, tne concepts for reading the output are the same. What will be different is that the program in the above posting uses a SAS dataset to hold an entire .SAS program. You would not need to do that -- you can just read a line of XML and write a line of XML to a different file. So you could use DATA _NULL_ for your program.
As to how you would collect your break points, I would imagine that you can figure out what row in the final file should be the break point. If PROC REPORT is generating a detail report, it's probably easier than with a summary report, but in either case, I'd be tempted to use PROC REPORT to create an output dataset in order to collect the correct row numbers for creaing the <RowBreak> sets of tags.
The best thing to do would be for you to create your XML file without the page breaks and then to examine the XML file using Notepad. That will give you an idea of how the tags will look and some idea of what you should be looking for to distinguish one worksheet's markup XML from another worksheet's markup XML as you're reading through the file.
Then, I'd recommend writing a program that just reads a line and writes a line and get that logic working before you introduce other elements. Once that works, I'd figure out how to find the </Worksheet> tag using the INDEX or FIND functions and once you get that working, write the correct PUT statement logic to write out the <PageBreaks> set of tags.
It would be far easier/better/less hassle to create a worksheet for every logical group -- which automatically starts on every page. So that when they printed the entire workbook, every group would be on a page. When I used the code below, I got every table/page on a separate worksheet and then when I printed, I had to select "Entire Workbook" on the print window, but I got every "group" on a new page. Would something like that work??
proc sort data=sashelp.shoes out=shoes;
where region in ('Africa', 'Asia', 'Canada','Pacific');
** Create Excel 2003 XML file with a sheet for every table;
** this is the default TAGSETS.EXCELXP behavior;
** Have to print "Entire Workbook" to see separate pages;
ods tagsets.excelxp file='c:\temp\makegrp_page.xls' style=sasweb
ods tagsets.excelxp options(sheet_name="Shoes");
proc report data=shoes nowd;
column region product sales;
define region / group;
define product / group;
define sales / sum;
break after region / summarize page;
** make second sheet grouped by age;
ods tagsets.excelxp options(sheet_name="Class");
proc report data=sashelp.class nowd;
column age name sex height weight;
define age / order;
define name / order;
define sex / display;
break after age / summarize page;
ods _all_ close;
Just wanted to give you an update on this project. I went ahead with the task of post-processing the XML to add the hard page breaks.
After writing out the multisheet XML file by using a proc report step with a by group as the interval, I then read that XML file back in and add hard page breaks at a sub-sheet level. I build a dataset with the row break values, find where each sheet ends, add all of the pagebkeak/rowbreak blocks to the xml and then write it out again. As you mentioned, yes, this is tedious, but it works! I've even added a dde step at the end to open the xml, apply a password, and save it as a native XLS. All from within SAS - pretty neat.