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 = "
%let amp = &
%let apol = '
%let lt = <
%let gt = >
%let nbsp =  
%let copy = ©
%let reg = ® 

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 ® for the registered trademark symbol in the text:

. . . input SAS® 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 (®) 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 = ®

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

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.