Monday, April 23, 2012

PROC STREAM News

Just a short posting for a couple of useful announcements about PROC STREAM.

  1. Many of the examples presented in my blog and in SAS® Server Pages: Generating Dynamic Content require the version of PROC STREAM that will ship with SAS 9.3M2. See Generating SAS Server Pages in different Releases of SAS for details about getting the updated version of PROC STREAM.
    NOTE:  This update can also be used in SAS 9.2.
  2. Check out Rick Langston's Super Demo on PROC STREAM. Presented at SAS Global Forum 2012.

Friday, April 20, 2012

A SAS Server Page UI for the Pie Chart

In my last post, A JavaScript Based Pie Chart, I showed a JavaScript based SAS Server Page that produced a pie chart using SAS data. That post illustrated how different pie charts could be produced by including parameters (also known as name/value pairs) in the URL. Clearly manually editing URLs to change the data or characteristics of the output from a web report is not desirable. Web pages should be created to provide options to the user that they can select.

This post describes using a SAS Server Page to present the user with the available choices. Try out this sample SAS Server Page based UI by clicking on either of the following links:
And here is the source for the SAS Server Page, interspersed with comments.

Assign a default for the data set to use. The data set could be selected in a prior page. To keep this example simple, a different data set can be specified by adding data to the URL as a name/value pair. The %sysfunc macro function uses the COALESCEC function to assign a default value. The macro variables lib and mem are used later in the page.

%global data;
%let data = %sysfunc(coalescec(&data,sashelp.shoes));
%let lib = %upcase(%scan(&data,1));
%let mem = %upcase(%scan(&data,2));


The initial HTML tags.

<html>
<head>
<title>UI for PieChart SSP Example</title>
</head>

When the page is initially loaded, submit the form with the default values.

<body onLoad="document.forms[0].submit();">

The FORM tag specifies that the output is displayed in another part of the page. It also uses the macro variable, _URL. This macro variable is created by the server and using this parameter is a sugggested Best Practice. Using _URL also allows for the same SAS Server Page to be processed by the both the Stored Process Server as well as the SAS/IntrNet Application Dispatcher.

<form action="&_url" target="results">

A standard header is defined by using %INCLUDE to dynamically include another input SAS Server Page. Using %INCLUDE ensures consistency and allows for easy updates/maintenance.

&streamDelim;%include srvrpgs(standardHeader.html);

Use an HTML table to arrange the select tags along the top of the page.

<table border="0">

Use the server created macro variable _program - both this UI and the PieChart SAS Server Page use the sasServerPage program. Using &_program is another Best Practice.

<input type="hidden" name="_program" value="&_program">
<input type="hidden" name="page" value="PieChart">
<input type="hidden" name="data" value="&data">

Use the generateOptionTag macro (a topic for a future posting), to create a select tag from data in a SAS Data Set. In this case, the data set is one that was created by a program that queried the provided SAS Styles to extract the color schemes.

<tr>
<td>ODS Color Pattern:
<td>%generateOptionTag
        (data=parms.colorList
        ,var=colorlist
        ,name=colors
        ,label=Style
        )

Use the generateOptionTag macro using the DATA step views of the dictionary tables. In this case the slice variables are limited to character variables (not a good approach in general - but done here for the sake of simplicity).

<td>Slice Variable:
<td>%generateOptionTag
        (data=sashelp.vcolumn
        ,var=name
        ,name=class
        ,selected=Product
        ,where=libname="&lib"
               and memname="&mem"
               and type="char"
        )

Use the generateOptionTag macro again, this time including numeric variables.

lt;td>Analysis Variable:
<td>%generateOptionTag
        (data=sashelp.vcolumn
        ,var=name
        ,name=var
        ,selected=Sales
        ,where=libname="&lib"
               and memname="&mem"
               and type="num"
        )

A hard-coded select tag for the statistic to use.

<td>Statistic:
<td>@select name="statistic">
    @option>Sum
    @option>Mean
    @option>Min
    @option>Max
</select>

The submit button and close the form.

<td>@input type="submit" value="Run">
<form>
</tr>
</table>

An iframe tag is used the embed the output into the same page as the UI. Note that the value of the name parameter matches the target value from the form tag.

<iframe name="results"
        width="100%"
        height="100%"
        frameborder="0">
</iframe>

Close the HTML page.

</body>
</html>

Wednesday, April 18, 2012

A JavaScript Based Pie Chart

This example illustrates the integration of the numerous resources available on the Web, which provides a rich source of samples and starting points for readers to create their own SAS Server Pages.

Try out any or all of the links below to see a pie chart of the SASHELP.SHOES data set. Once the pie chart displays, click on a slice or a table row to explode the slice.
The following URLs illustrate customizing the pie chart by passing parameters in the URL.
Note that this example uses the sasServerPage stored process (which doubles as a SAS/IntrNet Application Dispatcher program) and all of the processing, including creating the aggreates, is defined in the PieChart.html page.

The starting point for this example was found via a simple internet search and was modified so the data set, slice variable and analysis variable are parameters. The details can be found in Section 9.3 of the free preview copy of my eBook, SAS® Server Pages: Generating Dynamic Content. The download is scheduled to be available by SAS Global Forum on my SAS Press Author Page.

One of the key points to keep in mind here is that the use of PROC STREAM and SAS Server Pages enables a SAS programmer to leverage the broad range of tools available on the Internet to produce various output reports. You only need find samples that meet your needs or interest you, and then parameterize them by converting the input files to SAS Server Pages in order to customize the output. And, of course, it is also important to make sure that any tools you download and modify allow such use.

My next posting will illustrate creating a prompts page to allow the user to customize the generated pie chart.

Friday, April 13, 2012

Creating a simple UI

The point of this post is to illustrate how PROC STREAM and SAS Server Pages can be used to not only create the desired content, but also to create a user interface for a stored process (or SAS/IntrNet Application Dispatcher program). Following up on my previous posts, we need to allow the user to select which student letter is to be generated. Before describing how to do that, lets look at the results first:
We invoke the sasServerPage stored process (or SAS/IntrNet Application Dispatcher program) and just need to define the name of our input SAS Server Page which allows us to select the student for whom we'd like to generate the letter.

Here is the source for our input SAS Server Page, followed by a few notes and key points about it.

%let rc = %sysfunc(dosubl('%selectTag'));
<html>
<head>
<title>Select Letter to Generate
</head>
&streamDelim;%include srvrpgs(standardHeader.html);
<table style="width:100%; height:100%;">
<tr>
<td align="left" valign="top">
<form action="&_url" target="letter">
<input type="hidden" name="_program" value="&_program">
<input type="hidden" name="page" value="class_w_DoSub">
&selectTag
<p><input type="submit" value="Display Letter">
</form>
</td>
<td>
<iframe name="letter"
        style="width:100%; height:100%;"
        frameborder="0">
</iframe>
</td>
</tr>
</table>
</body>
</html>

Now a few notes about what is going on in the above SAS Server Page:
  • We are using DOSUBL to run some SAS code. In this case a macro (included below) that reads the SASHELP.CLASS data set and creates a macro variable that contains the text for the select tag of students that you see when you click on either of the links above to see the results.
    • The technique used here does require the M2 release of PROC STREAM/DOSUBL.
    • Note that the macro call is quoted with single quotes. Typically when functions are invoked with the %SYSFUNC macro, text is not quoted. However (again for the M2 release), for the DOSUBL function the decision was made to allow for the argument to be quoted - the rationale being that it might be desirable to allow for a single quoted string to provide a method to prevent macros froms being resolved in-line. Thus, starting with the M2 release, DOSUBL will recognize that there are leading/trailing quotes and will ignore them, thereby allowing a macro specification to be given and not expanded by %SYSFUNC.
    • Note however that the argument to DOSUBL does not need to be quoted.
    • Also note that there was no need to use a macro here. As seen in previous posts, the argument to DOSUBL could be the DATA Step code that the macro generates. I chose to use a macro here to highlight the fact that the argument to DOSUB could be a macro and also to highlight the quoting issue mentioned above.
  • The select tag is included in the output simply by using the macro variable reference &selectTag.
  • A %INCLUDE is used in the SAS Server Page to define a standard set of header text. In this example, is the the text on the grey backgound with the links to my book page on sasCommunity.org and to my web-site. I could have just as easily included trailer text, or both header and trailer text - simply by including the approapriate file.
  • Since both this user interface SAS Server Page and the SAS Server Page for the letter are self-contained (i.e., any needed SAS code is included instead of in separate programs to be run), we can simply chain these together using the same stored process, sasServerPage (which we can reference using the server created macro variable _program), and just specify the name of the input SAS Server Page using the page parameter.
  • We have complete control over how and where the output is displayed since we have complete control over what the SAS Server Pages do. In this case, we use the <iframe> tag so the user interface and the output appear in the same browser window. 
Here is the macro that we are invoking in the DOSUBL call. But before showing the code, a few disclaimers about it:
Here is the code with no explanation since it should be easy to undertand by most SAS programmers. And furthermore, it is not an approach I would want readers to think is a good idea. :-).

%macro selectTag;
 data _null_;
  length selectTag $32767;
  retain selectTag '<select name="letterObs">';
  set sashelp.class(keep=name) end=lr;
  selectTag = cats(selectTag
                  ,'<option value="'
                  ,_n_
                  ,'">'
                  ,name
                  ,'</option>'
                  );
  if lr;
  selectTag = cats(selectTag,'</select>');
  call symputx('selectTag',selectTag,'G');
 run;
%mend selectTag;

Wednesday, April 11, 2012

Running SAS Code within a SAS Server Page

As promised in my last post, A Sample Mail-Merge Application, embedding SAS code to be executed in a SAS Server Page is the topic of this post. That is, the format that creates the $gender format and the code to create the macro variables will be part of the input SAS Server Page instead of as a program that then invokes PROC STREAM. By doing that, we can use the macro and stored process discussed in A SAS Server Page macro. Our url uses the sasServerPage stored process which has as a parameter, page, the name of the input SAS Server Page to use.

So what happens if we just put the very same code, as is, into the macro?

%global letterObs;
%let letterObs = %verifyInteger
                    (value=&letterObs
                    ,min=1
                    ,max=19
                    ,default=1
                    );
proc format;
 /* create a format that maps the value
    of sex to daughter/son
 */
 value $gender 'F' = 'daughter'
               'M' = 'son'
;
run;
data _null_;
 /* read one observation and create macro variables.
    note the use of the vvalue function to cause
    the formatted values to be used.
 */
 set sashelp.class(firstobs=&letterObs obs=&letterObs);
 /* associate the desired formats with sex and age */
 format sex $gender. age words12.;
 call symputx('Name',vvalue(Name));
 call symputx('Sex',vvalue(Sex));
 call symputx('Age',vvalue(Age));
 call symputx('Height',vvalue(Height));
 call symputx('Weight',vvalue(Weight));
 stop;
run;

You can see the results using either of the following links:
It is pretty obvious that just including the code did not work as most of it (though not all of it), was just included in the output stream that PROC STREAM wrote (in this case) back to your browser. Note however that:
  • The /*  . . . */ comments were not included in the output. That is because those comments were eaten by the SAS Wordscanner. Note there is an option to cause those to be passed along. But that is a topic for later.
  • The %let statement and the verifyInteger macro call were also not included in the output. And that is because they were actually processed and executed by the macro facility.
  • None of the macro variables resolved correctly (because the code to create them was never executed).
So the question then becomes how do we cause text in a SAS Server Page to be seen/interpreted as SAS code to be executed instead of as text to be passed along to the output stream . . . and the answer is the DOSUB functions:
  • DOSUB takes a single argument which is the fileref that points to the desired code.
  • DOSUBL also has a single argument which is the line (or lines) of code to be run, including macro calls.
  • And both of these can be run using the %SYSFUNC macro function.
The following two links illustrate this. Note that the only difference in the URLs from the ones listed above is the name of the input SAS Server Page:
The only change I had to make was to wrap the PROC FORMAT and DATA Step code inside of a DOSUBL call (using the %SYSFUNC Macro function). Note that the %let statement and the verifyInteger function macros were already properly handled in the SAS Server Page. So here's the complete SAS Server Page (with the DOSUBL function invocation highlighted):

%global letterObs;
%let letterObs = %verifyInteger
                    (value=&letterObs
                    ,min=1
                    ,max=19
                    ,default=1
                    );
%let rc = %sysfunc(dosubl(
proc format;
/* create a format that maps the value
   of sex to daughter/son
*/
 value $gender 'F' = 'daughter'
               'M' = 'son'
 ;
run;
data _null_;
 /* read one observation and create macro variables.
    note the use of the vvalue function to cause
    the formatted values to be used.
 */
 set sashelp.class(firstobs=&letterObs obs=&letterObs);
 /* associate the desired formats with sex and age */
 format sex $gender. age words12.;
 call symputx('Name',vvalue(Name));
 call symputx('Sex',vvalue(Sex));
 call symputx('Age',vvalue(Age));
 call symputx('Height',vvalue(Height));
 call symputx('Weight',vvalue(Weight));
 stop;
run;
));
<html>
<head>
<title>&Name
</head>
<body>
%sysfunc(date(),worddate.)
<br><br>
<b>Dear Parents:
<p>As &name's teacher I wanted to make you
 aware of a project we are doing this year.
<p>Every month we will be collecting your &age
 year old &sex's height and weight and recording
 it. At year end, &name will be asked to produce
 a chart of their growth over the school year.
<p>For your information, here are the
 measurements that we just collected:
<ul><li><b>Height:</b> &height inches.
<li><b>Weight:</b> &weight pounds.
</ul>
<p>Please don't hesitate to contact me if
 you have any questions,
<p>Regards,
<br><br>
&name's teacher
</body>
</html>

The body of the actual HTML file is unchanged. All that we had to do was add the SAS code using DOSUB/DOSUBL.

The facility to run SAS code inside of a SAS Server Page is one of the features about PROC STREAM that I like a LOT. And future blog posts will show even more things that you can do with.

And next up: creating a simple UI to allow the user to select which observation to generate the letter for.

Monday, April 9, 2012

A Sample Mail-Merge Application

So far all of my postings about PROC STREAM and SAS Server Pages have been setting up some of the basics. This post is going to be about a real-world use case: a mail-merge example where the requirement is to generate custom content for each defined subset of data—usually each observation. This example is based on the example from Section 4.1 of SAS® Server Pages: Generating Dynamic Content (and Chapter 4 is included in the soon to be released free preview copy). It has been slightly modified so it can be run over the web. That way you can see the results using these links:
If no observation number is specified, the default is to generate a letter for observation 1. But you can supply an observation number as follows:
The value of the parameter letterObs is checked to make sure it is an integer as discussed in my last post, Simple Utility Macros and SAS Server Pages. Feel free to try different observation numbers by editing the URL.

Assuming you've clicked on the links to try these out, lets talk about how this works. The code is being run by my runMacro facility that runs the following macro.

%macro singleLetter;

%global letterObs;
%let letterObs = %verifyInteger
                    (value=&letterObs
                    ,min=1
                    ,max=19
                    ,default=1
                    );

proc format;
 /* create a format that maps the value
 of sex to daughter/son
 */
 value $gender 'F' = 'daughter'
               'M' = 'son'
 ;
run;

data _null_;
 /* read one observation and create macro variables.
    note the use of the vvalue function to
    cause the formatted values to be used.
 */
 set sashelp.class(firstobs=&letterObs obs=&letterObs);
 /* associate the desired formats with sex and age */
 format sex $gender. age words12.;
 call symputx('Name',vvalue(Name));
 call symputx('Sex',vvalue(Sex));
 call symputx('Age',vvalue(Age));
 call symputx('Height',vvalue(Height));
 call symputx('Weight',vvalue(Weight));
 stop;
run;

proc stream outfile=_Webout sqac;
/* %include the input SAS Server Page */
BEGIN
&streamDelim;%include srvrpgs(class.html);
;;;;
run;

%mend singleLetter;

The code is pretty simple at a conceptual level:
  • A format is created to map F/M to daughter/son so that the generated letter looks a little nicer.
  • DATA Step is used to subset the desired observations and load all the variables into like-name macro variables as well as to assign formats to both the Age and Sex variables. Note that the SYMPUTX function is used as it handles numeric to character conversion. The VVALUE function is used so the values of the macro variables are the formatted values.
  • Then  PROC STREAM is called and the input SAS Server Page which is the desired HTML letter is defined using %INCLUDE.
And, here is the input SAS Server Page that PROC STREAM processes:

<html>
<head>
<title>&Name</title>
</head>
<body>
%sysfunc(date(),worddate.)
<br><br>
<b>Dear Parents</b>:
<p>As &name's teacher I wanted to make you
 aware of a project we are doing this year.
<p>Every month we will be collecting your &age
 year old &sex's height and weight and recording
 it. At year end, &name will be asked to produce
 a chart of their growth over the school year.
<p>For your information, here are the
 measurements that we just collected:
<ul><li><b>Height:</b> &height inches.
<li><b>Weight:</b> &weight pounds.
</ul>
<p>Please don't hesitate to contact me if
 you have any questions,
<p>Regards,
<br><br>
&name's teacher
</body>
</html>

Just a few notes about this HTML file (which I refer to as a SAS Server Page due to the use of the Macro Language to customize it):
  • The %SYSFUNC Macro is used with the DATE function to insert the current date using the worddate format.
  • The desired text is intermixed with the needed HTML tags (e.g., P, BR, UL, LI, B, etc.).
  • The macro variables created in the DATA Step are simply referenced and thus resolved by PROC STREAM.
So, yes, it really can be that simple.

In Chapter 4 I build upon this example by next using the macro facility to generate a letter for each observation in the data set. I then show how to create an RTF document (that can be viewed and edited by word processing softtware like Microsoft Word). My next two blogs will take a different path by building on this example to illustrate:
  1. Embedding SAS code to be executed into a SAS Server Page. That is, the format that creates the $gender format and the code to create the macro variables will be part of the input SAS Server Page.
  2. Creating a SAS Server Page which is a user-interface to the letter generation process - allowing the user to select which letter to generate.

Thursday, April 5, 2012

Simple Utility Macros and SAS Server Pages

So much for plans . . .

Today's post was supposed to be a mail-merge example from Chapter 4 (included in the preview copy available at SAS Global Forum 2012) of SAS® Server Pages: Generating Dynamic Content.

In the book that example was not Web-based. I started with a simple example: generating a letter for a specified observation, and built on it. I had planned to something similar here but packaged for the Web - using it to illustrate some important features of PROC STREAM. My original plan was to just hard code the observation number - but I decided that was not a good idea and so I decided to:
  1. Allow the observation number from the SASHELP.CLASS data set for which the letter is to be generated to be passed in as a parameter.
  2. Have a default value used if no value is specified.
  3. That then led to needing to confirm that the value was an integer between 1 and the number of observations in the data set (19 in this case).
  4. Which then led to a simple utility macro that does that validation and assigns a default value.
Just as with any SAS application, I've discovered as I've used SAS Server Pages on numerous projects that its a good idea to create simple/short utility macros that perform a specific function. So here is the code for my macro that I can use in a SAS Server Page to validate the observation number.

%macro verifyInteger
  (value= /* the value to be verified as an integer */
  ,default=1 /* default if null */
  ,min= /* if specified, the minimum allowed value */
  ,max= /* if specified, the maximum allowed value */
  );

%let value =
     %sysfunc(coalescec(%superQ(value),&default));
%if %sysfunc(notdigit(%superQ(value)))
    %then %let value=1;
%if %length(&min) gt 0 and &value lt &min
    %then %let value = &min;
%if %length(&max) gt 0 and &value gt &max
    %then %let value = &max;

&value /* return the value to the input stack */

%mend verifyInteger;
 
In my code, I can just add the following statement:
 
%let letterObs = %verifyInteger(value=&letterObs
                               ,min=1
                               ,max=19
                               ,default=1
                               );
 
And before ending this post, just a few comments about this macro and how I am using it:
  1. Note the use of the NOTDIGIT function to validate that the value contains only integers.
  2. I've hard-coded the value of the max parameter on the call because I know the data set only has 19 observations. I could have used the SCL data access functions to get the value if it was unknown.
  3. While it is true that I could define a parameter for a stored process that forces the value to be an integer, since I want these to work for the SAS/IntrNet Application Dispatcher as well, that is not an option. In addition, since I will typically want to use my generic sasServerPage and runMacro stored processes (also runnable as SAS/IntrNet Application Dispatcher programs), having the constraints on the parameter value is not really an option.
And I'll get back on track with my next blog posting - the SAS Server Page mail-merge example. The revised schedule can be seen using the links from my last blog post:

Tuesday, April 3, 2012

Leveraging JQuery Widgets - An Events Calendar

This example is a bit of a teaser as I am going to illustrate a capability that I think is very cool - it illustrates the integration of a resource found on Web - but I am not going to include or discuss the code (which is actually a SAS Server Page). At least, not yet.

All of us programmers know about copying and modifying something that already exists instead of building something from scratch. And the Web, with the help of a Google search, provides a rich source of samples and starting points for readers to create their own SAS Server Pages.

So the idea for this example was born when a fellow consultant asked me (and a few others) a while back about whether I knew of a way to publish an events calendar on the SAS Portal. And before I had a chance to even think about it, Vince DelGobbo of SAS R&D suggested that maybe this could be done using SAS Server Pages (and PROC STREAM). So after a simple Google search, I found the FullCalendar plugin for JQuery.

And everyone reading this can probably guess that it was not too hard to create a calendar from data in a SAS Data Set. In fact, the hardest part was figuring out a set of events data I could use. Then it struck me - a calendar of blog postings about PROC STREAM which you can see here:
These both use the sasServerPage macro and the same source program (which can be run as both a Stored Process and a SAS/IntrNet Application Dispatcher program) as discussed in my last blog posting. The events data are my blog postings about PROC STREAM and SAS Server Pages. For blog postings I've already published, the events are hyperlinked; for future postings, the events are my best guess (at the time this blog was published) of what I plan to blog about between now and SAS Global Forum 2012.

The details of how I did this with PROC STREAM will be discussed in a future blog (sometime after SAS Global Forum 2012). But for now let me say that at the 10,000 foot level, it was pretty simple:
  1. Downloaded the code from the FullCalendar site (note that it can be used without restricion under the MIT License).
  2. Modified one of the demo programs replacing the hardcoded event data with a SAS macro that reads the data set of event data and generates JSON (JavaScript Object Notation).
  3. And thanks to the %let technique to handle HTML entities (&copy; in this case) discussed in my last blog posting I did not have to worry about SAS attempting to resolve Named HTML Entities as macro variable references.
That's all there was to it. I promise!

Wednesday, March 28, 2012

A SAS Server Page macro

In recent posts I've talked about a few things that can be a bit easier by packaging them in a macro. In Chapter 5 of SAS® Server Pages: Generating Dynamic Content, I describe a macro that I call sasServerPage that package functionality that I've found that I used repeatedly.

For example, in my blog post Processing External Files with PROC STREAM, I talked about using %let statements to deal with HTML Entities that might be used in your input SAS Server Pages. My sasServerPage macro includes the following %let statements for the most commonly used entities:

%local quot amp apos lt gt nbsp copy reg;
%let quot = &#034;
%let amp = &#038;
%let apol = &#039;
%let lt = &#060;
%let gt = &#062;
%let nbsp = &#160;
%let copy = &#169;
%let reg = &#174; 

Doing this for a complete list of HTML (and XML) Entities is propably overkill. But one of the advantages of sample macros is they can be edited/updated as you need to.

Likewise the same blog post also talks about using %include to define the input SAS Server Page. That is something else that can be packaged and simplified using macro parameters. For example, three of the parameters for my macro are used to specify:
  • srvrpgs: The fileref for the aggregate location where my input SAS Server Pages live
  • page: The filename in the aggregate location for the input SAS Server Page
  • defaultExtension: An optional parameter that specifies the default extension on the file name to use if no extension is specified. The default is html. This allows you, for example, to specify HelloWorld, instead of HelloWorld.html as the value of page.
And to address the possibility that you might have different maintenance levels of SAS on different boxes, you could use logic like:

   %if %index(&sysvlong,9.03.01M0)
    or %index(&sysvlong,9.03.01M1) %then
   %do; /* streamDelim not supported before M2 */
       %let streamDelim = %sysfunc(datetime(),z18.);
       %let streamDelim = _&streamDelim;
   %end; /* streamDelim not supported before M2 */ 

and then the following can be added to your PROC STREAM statement (again using macro logic):

   proc stream . . . . resetDelim="&streamDelim";

so the macro will work for 9.3 releases before TS1M2.

Chapter 5 also describe a SAS program that can be invoked as either a Stored Process or a SAS/IntrNet Application Dispatcher program to make it easier to invoke SAS Server Pages in Web environment. That SAS program calls the sasServerPage macro with the parameter values specified in the HTML form, URL (with default values assigned). This allows you to not have to have a distinct program for each and every SAS Server Page you want to produce. So, for example, the follow two URLs illustrate this for the HelloWorld SAS Server Page:
Note how each of these links passes as a parameter the name of the input SAS Server Page.

And now I suspect everyone wants to see the complete source. Unfortunately it is not quite ready for that. I am blogging about the sasServerPage macro and Stored Process/IntrNet program so I can start to use them in some real-world SAS Server Page examples that I will start blogging about shortly.

Friday, March 23, 2012

More on the streamDelim Macro Variable

Based on my last post (Processing External Files with PROC STREAM) you may now be curious about the rationale for the streamDelim macro variable as well as what else you can do with it.

But first, an announcement: The sasCommunity page SAS® Server Pages: Generating Dynamic Content has been updated to include:
  • The hopefully final title of the eBook
  • The list of chapters and topics covered
  • A link to the preview copy of Chapter 1 at the SAS press site (which provides more details on the book's content)
  • A list of my blog entries about SAS Server Pages (which will be regularly updated).
Please check it out.

Now back to streamDelim - why is it needed and what else can it be used for?

In working with Rick Langston of SAS R&D on this, one of the features we really wanted to support was the ability to use %include to include an external file into a SAS Server Page. In order to allow %include to be recognized and handled correctly it needs to be on a statement boundary - i.e., immediately after a semi-colon. So a way was needed to force a statement boundary without having the semi-colon appear in the output. The led to the resetDelim option (in the TS1M0 and TS1M1 releases of SAS 9.3):

proc stream . . . . resetDelim="a_SAS_name_token";

where you specify some text as a delimiter that does not occur in your input SAS Server Pages. This means that the following text could be used to allow %include to be recognized:

a_SAS_name_token; %include fileref-or-path-to-file;

but doing it that way allowed for the possibility of inconsistent values between the input SAS Server Page and the PROC STREAM statement (i.e., a SAS Server Page could be created or edited and not have the same value, or vice-versa). To address this, the TS1M2 release uses the value of a macro variable, streamDelim, as the delimiter. And if this macro variable does not exist, PROC STREAM creates it.

The resetDelim option still exists and that is why the following technique (mentioned in my last blog posting) works:

%let streamDelim = __&sysfunc(datetime(),z18.);
proc stream . . . . resetDelim = "&streamDelim";

So what else can you do with streamDelim?

You can use the following text in your SAS Server Page to force a new line or line break:

&streamDelim newline;

The tokenization process that PROC STREAM uses to resolve macro references ignores/loses line breaks (as many macro programmers know). There are any number of reasons you might want to force going to a new line, e.g.,:
  • readability of the generated text
  • to prevent line breaks forced by the output LRECL in places that might introduce errors (e.g., in a long select tag)
  • to deal with // style JavaScript comments (which says that all the rest of the text on the current line is a comment)
Another reason is that you might want to include a file - but you don't want it submit it to the resolution process. The readfile parameter will do this , e.g.:

&streamDelim readfile fileref-or-path-to-file;

And again, there are multiple reasons for not wanted resolution to occur:
  • no macro resolution is needed or desired
  • proper handling of JavaScript - && is the JavaScript AND operator. Macro resolution will convert && to & causing the JavaScript to not function correctly
Future blog posts will talk about when and how to use these features.

Monday, March 19, 2012

Processing External Files with PROC STREAM

In the examples posted in previous blog entries (A Gentle Introduction to SAS Server Pages and PROC STREAM: Extending the Macro Language to create more than just SAS code), the input text being processed was included in the SAS job stream and was delimited by:

• the token BEGIN (case insensitive)
• four semicolons with no intervening spaces starting in column 1 (;;;;)

PROC STREAM utilizes the SAS word-scanner and tokenization facilities to resolve and execute macro variable references, macro functions and macro calls for all the text delimited by BEGIN .... ;;;; and directs the output to a specified external file:

proc stream outfile= ... ;
BEGIN
/* Input text to be processed */
;;;;
run;

Quite often the text that you will want PROC STREAM to process is contained in an external file (what I would refer to as a SAS Server Page - text, e.g., HTML, along with commands interpreted by SAS to generate additional data-driven content). So the question becomes how to do that since there in no infile option. And the answer is to use a %INCLUDE statement. PROC STREAM recognizes the %INCLUDE statement and will use the contents of that file as its input. So our PROC STREAM statement looks like this:

proc stream outfile= ... ;
BEGIN
/* Include text file to be processed */
&streamDelim; %include fileref-or-path-to-file;
;;;;

Note that fileref-or-path-to-file can reference a fileref, a physical path or use SAS aggregate syntax. I prefer to use aggregate syntax as typically my SAS Server Pages are organized in one or more directories (and you can define your fileref so it points to concatenated directories).

The content of the file being included need not be SAS code, it can be any text (e.g., HTML, XML, CSV, SAS code, and more) - whatever text it contains will be processed by the SAS tokenizer.

But now you ask, what it &streamDelim; and why is it there? It is a delimiter that is needed because certain SAS statements must appear on statement boundaries (i.e., they must be the very first statement in your program or they must immediately follow a semi-colon). So in order for PROC STREAM to recognize the %INCLUDE, it needs to follow a semicolon. But since you typically don't want the ; in the output file, we need to have a way to tell PROC STREAM to ignore it - thus &streamDelim. There are a number of other things you can do with &streamDelim - and I'll have examples and blog postings between now and SAS Global Forum.

NOTE: In the SAS TS1M2 release, PROC STREAM will create the &streamDelim macro variable if it does not already exist. Until then, you can slightly modify the syntax above as follows to create a value for streamDelim that is a valid SAS name token but whose text value is not in your input SAS Server Page, for example:

%let streamDelim = __&sysfunc(datetime(),z18.)
proc stream . . . . resetDelim = "&streamDelim";
/* Include text file to be processed */
BEGIN
&streamDelim; %include srvrpgs(HelloWorld.html);
;;;;

Input HTML (and other) files may have an additional wrinkle - named HTML Entities. For example, the HelloWorld.html file contains &reg; for the registered trademark symbol in the text:

. . . input SAS&reg; Server Page . . .

When this text is processed we will likely get a warning, or depending on the context, an error message for &reg since the SAS tokenizer will interpret it as a macro variable reference. However, the comparable numeric HTML Entity (&#174;) for the registered trademark symbol does not have this problem since the SAS tokenizer does not see #174 as a macro variable reference. In order to avoid editing the input files to convert named to numeric HTML Entities, we can let the SAS tokenizer do the work for us. Since the content of the input file is being tokenized and macro variable references are replaced with their values, including a series of %let statements like the following before invoking PROC STREAM for the standard HTML entities

%let reg = &#174;

will allow the tokenizer to do the substitutions for us: &reg will be replaced by &#174 and so &reg; will be resolved to &#174;.

You can see both %include and this substitution in action on my server using:

• the SAS/IntrNet Application Dispatcher
• the Stored Process Server

both of which use the same code and the same input SAS Server Page (our Hello World example).

I will have more examples in future blog entries (and, of course, in the book) that take advantage of %INCLUDE, including input SAS Server Pages that have %INCLUDE statements.

Thursday, March 15, 2012

A Gentle Introduction to SAS Server Pages

My last post PROC STREAM: Extending the Macro Language to create more than just SAS code introduced the 9.3 experimental procedure, PROC STREAM, which provides direct support for SAS Server Pages. It also provided a very preliminary preview of a SAS Press eBook on PROC STREAM and SAS Server Pages. A free preview copy of selected chapters is targeted for the SAS Global Forum 2012 timeframe - look for a blog posting soon with more details on the topics covered in the eBook and the preview version.

Between now and SAS Global Forum I plan to write a number of blog entries on this topic - starting with an overview and a brief overview of what SAS Server Pages are. I will also be providing online demos so that even if you don't have access to SAS 9.3, you can see the generated output. I will be providing links the run the examples using both the SAS/IntrNet Application Dispatcher as well as the Stored Process Server. And note that the exact same programs are used by both the SAS/IntrNet Application Dispatcher and the Stored Process Server: one program for the DATA Step example; and one for the PROC STREAM example.

As discussed on sasCommunity.orgSAS Server Pages can be generated using the RESOLVE function in a DATA Step. So let's look at how to do that.

Here is a variation of the simple example in my last blog post:

data _null_;
file _webout;
infile datalines;
input;
_infile_ = resolve(_infile_);
put _infile_;
datalines4;
<html>
<head><title>The Obligatory Hello World Example</title></head>
<body>
<h1>Hello &_rmtuser..</h1>
<h2> This welcome note generated
at %sysfunc(time(),timeampm8.)<sup>1</sup>
on %sysfunc(date(),worddate.).</h2>
This HTML file was produced from an input
SAS Server Page and customized courtesy
of a DATA Step and the RESOLVE function
using SAS Release &sysver on &sysscp..
<p><sup>1</sup>The time listed is the server
time - the US Rocky Mountain time zone.
</body>
</html>
;;;;

The RESOLVE function is used to resolve macro variable references and execute macros (none included in this example) as well as macro functions (e.g., the %sysfunc macro fucntion). Try this out on my server:
Now lets look at the PROC STREAM version:

proc stream outfile=_webout quoting=both;
BEGIN
<html>
<head><title>The Obigatory Hello World Example</title></head>
<body>
<h1>Hello &_rmtuser..</h1>
<h2> This welcome note generated
at %sysfunc(time(),timeampm8.)<sup>1</sup>
on %sysfunc(date(),worddate.).</h2>
This HTML file was produced from an input
SAS Server Page and customized courtesy
of PROC STREAM
using SAS Release &sysver on &sysscp..
<p><sup>1</sup>The time listed is the server
time - the US Rocky Mountain time zone.
</body>
</html>
;;;;

You can run these out on my server as well:
The DATA Step and PROC STREAM both processed the same input SAS Server Page - and both produced the same results. PROC STREAM includes a number of features and capabilities that can't be done with the DATA Step approach. I'll be highlighting a number of those features in blog posts between now and SAS Global Forum.