Aaxsys Notes
  More articles

May 27 2009

HTML Templates in Aaxsys

(On using ASL for integrating HTML and XML)

Introduction. In a web-based environment, such as Aaxsys, using HTML is a natural way providing documents. The user already has a browser. No other viewing program (e.g., Acrobat Reader for PDF) is needed. Moreover, HTML is a completely open document format, it is directly legible and can be edited with any text editor. Using CSS and additional graphical elements, sophisticated styles can be achieved.

An immediate question is how to link the data from the various fields in client and property records to these documents? The solution in Aaxsys is to provide a special scripting method - ASL, "Aaxsys Scripting Language" - to bridge the gap between the database and the presentation (html). ASL is similar to PHP in that

The user's browser will not see any of these extra characters. They are "translated" into "pure" HTML by the server. The user only sees an end result such as
Dear John Smith welcome to your ...
Invisible to the user, ASL does not directly pull the data from the database, put utilizes an intermediate representation of data through XML. Unlike PHP that enable the user handle direct data connections to a database, with full database operation capabilities, ASL only works with predefined field names. The XML used is the same as in the XML downloads and feeds (of clients, properties, availability). Therefore, the templates provide views of these XML feeds as webpages.
Diagram 1. HTML and XML are integrated through ASL.
Database / property data
XML
Template (HTML + ASL)
(Server Translation)
HTML output
it mixes with the HTML by utilizing the processing instruction (PI) tags <?..?>. The most straightforward and - at the same time - perhaps the most necessary usage of this functionality is to embed simple data fields into an HTML document:
Dear 
  <? $Field(FirstName) $Field(LastName), ?>
welcome to your ...
Here $Field(LastName) refers to the field "LastName" of the client record, and is replaced by the value of that field when the template is processed in the Aaxsys server.
Then why not use XSLT, the designated way to transform XML into HTML? For achieving the results needed here, XSLT appears excessively verbose and technical. To learn to use ASL in its simplest form, using the $Field keyword, is trivial compared with the learning needed for utilizing XSLT. Secondly, apart from simple field value references, ASL provides some necessary functionality that would be difficult to achieve using XLST alone. The complexity and extent of the XSLT would overpower that of the HTML and would transform a simple document into a complicated example of XML stylesheet programming.
 
Conditional values. While using the keyword $Field to print record field values into the document is the simple (but major) first step, creating conditional content is the next one. Suppose that we want to print the unit bedroom type. It should be Studio when the number of bedrooms is zero. For other cases, it should read (for purposes of this example) (the number of bedrooms) with the tag BR. In ASL, this would be accomplished as follows:
  $IFEQ($Field(Bedrooms),0,Studio,$Field(Bedrooms) BR)
The keyword function $IFEQ takes 3 or 4 parameters (with 3 parameters, the 4th is implicitly taken as the empty string). However, this is not a classical IF..THEN..ELSE control structure, but rather a function. Indeed, ASL is a functional language that is string-oriented (it has only one "data type") and that has a "linear" parser in the sense that expressions are linearly executed as they are read instead of first parsing them into different data structures.

But how do we then do more complicated control structures? It is possible to embed $IFEQ functions into others, but the expressions quickly get unwieldy. In fact, ASL favors definitions to keep the templates looking simple.


Definitions. ASL is a macro language. To give an example of a macro, let us take the above situation and define a macro called Beds:

$DEF(Beds) 
  $IFEQ($Field(Bedrooms),0,Studio,$Field(Bedrooms) BR) 
$END(Beds)
Then, this new user-defined keyword can be used in the HTML simply as
  This property is a <? $Beds ?> unit and ...
A definition can take an arbitrary number of parameters. These are indicated by #1,#2,#3 etc. in the body of the definition. For example, consider the following example:
$DEF(Call) 
$IFEQ($Upper(#1),RESERVATIONS,call #1 at 1-414-447-2000)
$IFEQ($Upper(#1),MAINTENANCE,call #1 at 1-414-447-2007)
$IFEQ($Upper(#1),RECEPTION,call #1 at 1-414-447-2015)  
$END(Call)
This functional macro can then be used as ... to make a booking,<?$Call(Reservations)>?, for property maintenance, <?$Call(Maintenance)?>...

ASL prefers definitions instead of complex control structures. Due to its simple parsing, one cannot even use string arguments containg commas, because in ASL there is no such a thing as "string in quotes". Thus, given two strings "This is an example of ..." and "This is another example of ...", to compare them using the keyword $IFEQ one should first define the corresponding macros:

$DEF(Text1) This is an example ...$END(Text1)
$DEF(Text2) This is another example ...$END(Text2)

and continue with $IFEQ($Text1,$Text2,...).

In the above example, the definition keyword $DEF could have as well been replaced with $SET. The difference between $DEF and $SET is the following: While the former creates a genuine macro definition, the latter "sets" a permanent variable value. In case the value itself is constant (such as static text), then there is no difference between these two keywords.


Looping through XML. An XML file (or stream) typically consists of a series of records of the same structure. A listing of currently available units with four fields (address, bedrooms,bathrooms and monthly rent) is a good example. We need a way of iterating through the records, reading the essential fields from each. Otherwise, the keyword $Field - for example - would only refer to the first record in the series.

 

This is accomplished with the keyword $FOREACH. Let's say that the (top-level) element of the records is . Then the script block between $FOREACH(Unit) and $END(Unit) is iterated for each record in the XML. This can naturally be repeated recursively: In case the unit element itself contains a series of records (say, listing the various rental rates of the unit), then using $FOREACH(Rate) within the first will iterate through the rate elements.

So let us define a new keyword that creates a simple list of units and their monthly rates:

$DEF(DoUnits)
 <table>
 <tr><th>Address</th><th>Rate</th></tr>'
 $FOREACH(Unit)
   <tr>
   <td>$Field(Address)</td>
   <td>$Field(MRate)</td>
   </tr>
 $END(Unit)
 </table>
$END(DoUnits)


Macros with parameters. A standard way of defining a function in a programming language is to precede the function body by the parameter block. This is the case even in Javascript in which the types of the parameters are not defined. In ASL, however, functional macros are defined without parameter blocks. Instead, the parameters are referred to via the symbols #1,...,#9.

For example, let use define a macro that shows the status of a unit with respect to a given field. In case we only needed to show the smoking/non-smoking status, we could just define a "static" macro for this purpose:

$DEF(SmokingStatus)
(1)  $IFEQ($Field(Smoking),T,Smoking,Non-smoking)
$END(SmokingStatus)

However, by using parameters we can define a dynamic status macro:

$DEF(Status) 
(2)  $IFEQ($Field(#1),T,#2) $IFEQ($Field(#1),F,#3) 
$END(Status) 
Then we can use the macro with any suitable field. For example, $Status(Smoking,Smoking,Non-Smoking) and $Status(Pets,Pet-friendly,Pet-free) are both possible applications of the macro. (Also please note the subtle difference between 1) and 2). The former always gives a result, while the second only gives a (non-empty) result for field values T and F.)


Extension by Javascript Standard data manipulation capabilities missing in ASL can be obtained from Javascript. After the server has integrated (see Diagram 1.) the template and the XML, the resulting HTML is sent to the user's browser. Any Javascript contained in the template is processed first in the user's browser. Therefore, it cannot be used for intermediate calculations. Nevertheless, the use of Javascript helps the ASL template language to reach the expressive level of typical programming languages.


Invoice XML. Templates can be used when ever there is a corrsponding XML data stream. Currently, the latest addition is invoice XML that provides, in XML format, the data used for standard Aaxsys invoice statements. This makes it possible for the members not only to continue using their proprietary invoice formats, but also to provide simultaneous alternative formats to their users.

A classical initial test for a compiler is to be able to compile itself. In the same vein, an initial requirement for invoice templates would be to be able to re-produce the standard Aaxsys invoice. On the next page, we give a full listing for the standard default template. It can be used as a starting point for the user's own invoice templates, and also illustrates several ASL programming points.

 
Code Listing.
<? $STRICTON ?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>Guest Invoice for Client <? $Field(ClientNo)?></TITLE>
<STYLE>
<!--
TABLE {width:640;}
TD,TH,A.link,SPAN.text { font-family: Arial, Helvetica, sans-serif;
 font-size: 10pt;}
TH{font-family:arial,verdana,helvetica,sans-serif;}
TH.title{font-family:arial,verdana,helvetica,sans-serif;color:white;background:#6699cc;}
.header { font-family: Times New Roman, Times, Serif;
TH.simple {border:0px none #ffffff;
 font-size: 12pt;background: #9fc7c7;}
.text{font-family:arial,helvetica,sans-serif;font-size:10pt;}
.extra{font-family:arial,helvetica,sans-serif;font-size:10pt;color:blue;}
div {width:640;}
--> </STYLE>
<SCRIPT LANGUAGE="JavaScript1.2"><!--
function jsCloseFunction(e) {
  var ow = window.opener;
  var keyID = (window.event) ? event.keyCode : e.keyCode;
  if (keyID == 27) { window.close();
  if (ow) ow.focus();}
}
//--></SCRIPT>
</HEAD>
<BODY BGCOLOR=#FFFFFF onKeyPress="jsCloseFunction(event);">
<? $DEF(SetStandardHeader) 
     $BEGIN(VendorInformation)
     <H1> $Field(Company) </H1>
     <H3> $Field(Address) ($Field(Phone))</H3>
     <H3>$Field(City) $Field(State) $Field(Zip)</H3>
   $END(VendorInformation)
  $END(SetStandardHeader)
?> 

 

<? 
$DEF(SetHeader) 
        $IFNEQ($Field(HeaderFile),,$INSERTFILE($Field(HeaderFile)),$SetStandardHeader) 
$END(SetHeader) 
?>

<? $SetHeader ?>

<FORM $Field(EmailLink) method=post>
<TABLE BORDER = 0 CELLSPACING="0" CELLPADDING="0">
<TR><TH COLSPAN = "5" class="title"><? $Field(ReportTitle) ?></TH> </TR>
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<TR><TD colspan="5"><B><? $Field(InvoiceNo) ?></B></TD></TR>
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<TR><TH></TH><TH COLSPAN = "2" align = "left">Client/Billing address </TH>
<TH COLSPAN = 2 align = "left">Reservation information </TH></TR>
<TR><TD></TD><TD colspan="4" bgcolor="RoyalBlue"><img src="/pixel.gif" height="1"></TD></TR>
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<TR><TD></TD><TD COLSPAN = 2 valign = "top">
<B><? $Field(LastName),$Field(FirstName) ?></B><BR>
<B></B><BR>
<B></B>
<B></B>
<B></B><BR>
</TD>
<TD COLSPAN = 2 valign = "top">
<?
$DEF(ResInfo)
$BEGIN(ReservationInformation)
Guest No:  $Field(ClientNo)<BR>
Agent:     $Field(Agent)<BR>
Unit:   $Field(PropertyCode)<BR>
Address:    $Field(Address1)<BR>
$IFNEQ($Field(Address2),,  $Field(Address2)<BR>)
$IFNEQ($Field(Suite),,Suite:    $Field(Suite)<BR>)
Parking:  $Field(Parking)<BR>
Phone:      $Field(Phone)<BR>
Start date: $Field(StartDate)<BR>
End date:   $Field(EndDate)<BR>
Rent:   $Field(Rent)
$END(ReservationInformation)
$END(ResInfo)
?>

 

<? $IFEQ($Field(ClientType),Tenant,$ResInfo) ?>
<BR>
</TD></TR>
<TR><TD></TD></TR>
<TR> <TH COLSPAN=1 BGCOLOR=#F5F5DC align = "left" > Class</TH>
  <TH COLSPAN=1 BGCOLOR=#F5F5DC align = "left" > Reference 
#</TH><TH COLSPAN=1 BGCOLOR=#F5F5DC align = "left" > Description</TH>
  <TH COLSPAN=1 BGCOLOR=#F5F5DC align = "left" > 
Date</TH><TH COLSPAN=1 BGCOLOR=#F5F5DC align = "right"> Amount</TH>
</TR>
<TR><TD></TD></TR>

<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<TR><TD COLSPAN = "5" ALIGN = "left" BGCOLOR="#ECECEC"><B>CHARGES:</B></TD></TR>
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<?
$BEGIN(Charges)
  $FOREACH(ChargeItem)
<TR>
<TD WIDTH = 20%>  </TD>
<TD WIDTH = 20%>
$Field(TransactionNo)</TD>
<TD WIDTH = 30%>
$Field(Description)</TD>
<TD WIDTH = 15%>
$Field(Date)</TD>
<TD WIDTH = 15% ALIGN = "right">
$Field(Amount)</TD>
</TR>
  $END(ChargeItem)
<TR><TD colspan="5"><img src="/pixel.gif" height="2"></TD></TR>
<TR><TD></TD><TD colspan="4" bgcolor="RoyalBlue">
  <img src="/pixel.gif" height="1"></TD></TR>
<TR><TD colspan="5"\><img src="/pixel.gif" height="2"></TD></TR>
<TR><TD></TD><TD COLSPAN = 2><B>Total charges: </B></TD>
<TD></TD><TD ALIGN ="right"><B>$Field(TotalCharges)</B></TD></TR>
<TR><TD></TD></TR>
$END(Charges)
?>

 

<? $DEF(DoPayments)
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<TR><TD COLSPAN = "5" ALIGN = "left" BGCOLOR="#ECECEC"><B>PAYMENTS:</B></TD></TR>
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
$BEGIN(Payments)
  $FOREACH(PaymentItem)
<TR>
<TD WIDTH = 20%>  </TD>
<TD WIDTH = 20%>
$Field(TransactionNo)</TD>
<TD WIDTH = 30%>
$Field(Description)</TD>
<TD WIDTH = 15%>
$Field(Date)</TD>
<TD WIDTH = 15% ALIGN = "right">
$Field(Amount)</TD>
</TR>
  $END(PaymentItem)

<TR><TD></TD><TD COLSPAN = 2><B> Total payments: </B></TD>
<TD></TD><TD ALIGN ="right"><B>$Field(TotalPayments)</B></TD></TR>
$END(Payments)
$END(DoPayments)
?>
<? $IFEQ($Field(?HasPayments),T,$DoPayments) ?>
<TR><TD></TD></TR>
<TR BGCOLOR = #9FC7C7><TD></TD>
<TD COLSPAN = "2"> Balance due </TD>
<TD></TD><TD ALIGN = "right"> <? $Field(BalanceDue) + ?></TD>
</TR>
<? $DEF(DoCredits)
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<TR><TD COLSPAN = "5" ALIGN = "left" BGCOLOR="#ECECEC"><B>CREDITS:</B></TD></TR>
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
  $BEGIN(Credits)
    $FOREACH(CreditItem)
    <TR>
    <TD WIDTH = 20%>  </TD>
    <TD WIDTH = 20%>
    $Field(TransactionNo)</TD>
    <TD WIDTH = 30%>
    $Field(Description)</TD>
    <TD WIDTH = 15%>
    $Field(Date)</TD>
    <TD WIDTH = 15% ALIGN = "right">
    $Field(Amount)</TD>
    </TR>
  $END(CreditItem)

 

  <TR><TD colspan="5"><img src="/pixel.gif" height="2"></TD></TR>
  <TR><TD></TD><TD colspan="4" bgcolor="RoyalBlue">
    <img src="/pixel.gif" height="1"></TD></TR>
  <TR><TD colspan="5"\><img src="/pixel.gif" height="2"></TD></TR>
  <TR><TD></TD><TD COLSPAN = 2><B>Total credits: </B></TD>
  <TD></TD><TD ALIGN ="right"><B>$Field(TotalCredits)</B></TD></TR>
  <TR><TD></TD></TR>
  $END(Credits)
$END(DoCredits)
?>
<? $DEF(DoRefunds)
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
<TR><TD COLSPAN = "5" ALIGN = "left" BGCOLOR="#ECECEC"><B>REFUNDS:</B></TD></TR>
<TR><TD colspan="5"><img src="/pixel.gif" height="4"></TD></TR>
  $BEGIN(Refunds)
    $FOREACH(RefundItem)
    <TR>
    <TD WIDTH = 20%>  </TD>
    <TD WIDTH = 20%>
    $Field(TransactionNo)</TD>
    <TD WIDTH = 30%>
    $Field(Description)</TD>
    <TD WIDTH = 15%>
    $Field(Date)</TD>
    <TD WIDTH = 15% ALIGN = "right">
    $Field(Amount)</TD>
    </TR>
  $END(RefundItem)
  <TR><TD colspan="5"><img src="/pixel.gif" height="2"></TD></TR>
  <TR><TD></TD><TD colspan="4" bgcolor="RoyalBlue">
    <img src="/pixel.gif" height="1"></TD></TR>
  <TR><TD colspan="5"\><img src="/pixel.gif" height="2"></TD></TR>
  <TR><TD></TD><TD COLSPAN = 2><B>Total credits: </B></TD>
  <TD></TD><TD ALIGN ="right"><B>$Field(RefundItem)</B></TD></TR>
  <TR><TD></TD></TR>
  $END(Refunds)
$END(DoRefunds)
?>
<?
$IFEQ($Field(?HasCredits),T,$DoCredits)
$IFEQ($Field(?HasRefunds),T,$DoRefunds)
?>
</TABLE>
<?
$IFNEQ($Field(InvFile2),,<p><div class="text">$Field(InvFile2)</div>)
?>

</BODY>
</HTML>
Aaxsys Notes, May 27, 2009