Next Previous Contents

8. Programming in XSLT

8.1 The Big Picture

The fundamental design of an rm -d ms application spans the whole history of interactive computing. On one hand, it follows the good old 'dialog program'. Do you still have an old book on CICS, covered in dust? On the other hand it already makes heavy use of the new XML/apply-templates paradigm.

A dialog application offers a form. The user fills in the fields and presses one of the submit buttons or function buttons. The application either rejects the input or it processes it. In both cases the application offers the next form - that is all. Since rm -d ms is designed for the net and this model works very well for Internet applications, this chapter ignores other models.

Why yet another XML framework?

Nowadays most XSLT frameworks only use XSLT for rendering. Some frameworks offer a very limited processing model, others combine XSLT with an incompatible programming language, but all of them already include a powerful well-fitting programming language - XSLT. What will happen, if you use XSLT as a general purpose programming language? You will find some brain-dead limitations. But on the other hand XSLT has a powerful data type - XML. And it has a powerful control flow - apply-templates. Apply-templates gives you the power of virtual methods without the rigid corset of the object oriented model. And last but not least you are able to extend a XSLT processor with your own elements.

If you implement your applications in XSLT, you will encounter a small problem. Nobody teaches you how to do it and there are no appropriate case tools. Nowadays everything has to be object oriented. If it doesn't have the big OO, they can't sell it. So you have to construct your own software engendering model and your own tools. This chapter lists some starting points.

8.2 Separating Content, Style and Logic

You are interested in XML/XSL. So I assume separating content, style and logic is a high priority issue for you. Separating content and style is solved. We can ignore this point and concentrate on separating style and logic. There are hundreds of requirements like these ones:

Existing XML frameworks are very strong in this area, but they fail in others: rm -d ms doesn't offer a perfect solution, but is an environment for different approaches.

Chaining Stylesheets

One common approach chains two or more steps. One step computes the data, the other one renders the page. It turned out that this idea doesn't work very well. Most pages include information that isn't real data. Let's say your page consists of three areas: a logo, a navigation bar and the content area. The first step prepares the data of the content area, the second one renders this data and adds the logo and the navigation bar. Everything works fine until your customer wants one logo for each topic. Let's say he also want to highlight the button of the navigation bar that points to the current page. Now the second step needs additional information and some logic. So you have to change two stylesheets and the document type of the intermediate data. Life teaches: You cannot forsee what your customer will want you to do. On top of that, XSLT processors are really slow today. And two chained processors are twice as slow.

On the other hand it will be easy to use WYSIWYG editors as soon as they become available.

Extention Elements and a Glue Language

Another approach makes heavy use of the extention mechanism. You implement encapsulated objects in your favorite programming language and add an XML wrapper. The objects don't know anything about logos or navigation buttons. So there is a good change you don't have to change them. The objects are designed to be reusable and the XSLT glue code is cheap. So you are able to throw away the whole stylesheet when your customer has the style changed.

If you implement the whole application from scratch, this concept will add a lot of conversation code. But it might be a good choice if you have to combine existing components. One element may wrap an OLE/COM component, another one may wrap a Corba interface and the XSLT program brings the different data structures in line.

Your main problem is: there are no standards for extention element interfaces. Your extention elements may not even co-operate with the next release of your favorite processor! Due to this problem we run different versions of a processor on our servers.

Including Stylesheets

There are two ways to combine a stylesheet from different sources. If you use a validating parser you can use external entities and every XSLT processor is able to include stylesheets. You can place style and logic in different files, but XSLT has a really poor package concept. You have to take care, that templates from different files don't interfere. Let's hope, the next XSLT standard will address this problem.

The next chapter describes a layout for this approach.

Plan to Throw Away

This approach doesn't separate style and logic. If things will change very fast and you won't reuse an old Internet application it might be a good solution. Do you know what will be the hype of the next year?

Implement your stylesheets quick & dirty and plan to throw them away when the the next generation comes! XSLT is a good language for this approach. As soon as you get used to the crazy syntax, you will be able to write an XSLT program in a very short time. Usually, templates are not connected to other templates. So reusing them by copy & paste is easy. Last but not least, if you have to change your data, you can convert it with a simple stylesheet.

Since I don't know how things will evolve, the samples of rm -d ms follow this pattern.

Which One to Choose

Sorry if this list doesn't mention your favorite concept. There are just too many concepts. In my opinion we cannot choose one concept and ignore the other ones. So rm -d ms tries to support all of them. It will consist of a collection of tools, a library of extendable applications, a library of general purpose templates and a collection of database interfaces. This flexibility makes using rm -d ms more complicated then using an old fashioned content management system. But we are able to adopt new ideas. And I hope we will never have to deal with the brain-dead limitations of an ancient concept.

And remember: if it was that easy, the cleaning lady would do your job.

8.3 Connecting an URL to an Action

There is more than one way. An extensible layout can use one XML file for each action. The XML file lists the actions that have to be performed. Each item consists of two pieses of information, the name of the step and an action name, which matches a template. A common stylesheet dispatches the request to the appropriate templates. Included stylesheets can separate database logic from rendering.

If you don't want so many XML files you can add a todo parameter to your url - e.g. form.plxml?todo=insert&value=hallo-world. Apply-templates can match this parameter.


<xsl:apply-templates select="$request/parameter/todo"/>

<xsl:template match="todo[text()='insert']">
  ....
</xsl:template>

8.4 Connecting Input Fields and Database Records

The database interface of rm -d ms operates on one comprehensive document. It transfers document fragments instead of records. You can put this fragment into the body of of a pl:write element. The database interface needs to know where to place the fragment. If you insert new elements you have to pass the key of the parent element. If you update existing elements you have to add an attribute plid to the root element.

A Simple Insert Mask

This sample consists of two templates. The first template is very simple. It just renders a HTML form. After the user presses the submit button, the HTTP server calls the other template. It consists of two steps. The first step fetches the key of the parent element from the database, the second one stores a new element in the database.


<xsl:template match="update-mask">
  <!-- Render a HTML form -->
  <form action="insert.plxml">
    <input type="text" name="title"/>
    <input type="submit" value="OK"/>
  </form>
</xsl:template>

<xsl:template match="insert">
  <!-- The new element will become a child of the site element -->
  <pl:variable name="site" document="'tutorial'" select="/tutorial/site"/>
  <!-- We construct a new element and store it in the database -->
  <pl:write name="new-id" document="'tutorial'" owner="$site/@plid">
    <page>
      <title><xsl:value-of select="$request/parament/title"/></title>
      <content type="{$request/parament/type}"/>
    </page>
  </pl:write>
</xsl:template>

A Simple Update Mask

If you update an existing document fragment, the mask must fetch the old values from the database, but the template that performs the update will be easier. It needs the database key of the root element of the fragment and the new values. You can pass the key of the element in a hidden field - it becomes an ordinary HTTP parameter.


<xsl:template match="update-mask">
  <!-- Fetch the key of the element from the HTTP parameters -->
  <xsl:variable name="current" select="$request/parameter/current"/>
  <!-- Fetch the key of the parent element from the database -->
  <pl:variable name="title" document="'tutorial'" select="title[@plid=$current]"/>
  <!-- Render a HTML form -->
  <form action="update.plxml">
    <input type="text" name="title" value="{$title}"/>
    <!-- Pass the key of the record in a hidden field -->
    <input type="hidden" name="current" value="{$current}"/>
    <input type="submit" value="OK"/>
  </form>
</xsl:template>

<xsl:template match="update">
  <!-- Fetch the key of the element and the new value from the HTTP parameters -->
  <xsl:variable name="current" select="$request/parameter/current"/>
  <xsl:variable name="title" select="$request/parameter/title"/>
  <!-- We construct a new element and store it in the database -->
  <pl:write name="version-id" document="'tutorial'">
    <title plid="{$current}"><xsl:value-of select="$title"/></title>
  </pl:write>
</xsl:template>

A Template Based System

If you store XML in your database, you will be able to extend your data model in a very short time. But you also have to extend your masks. An extendable system should render the input masks from the doctype or from the data. This example constructs everything from the data. It doesn't use a doctype definition.

A template based system creates a new entry by copying a template or an existing entry. A more traditional system would use two kinds of entries. One kind describes the input mask, the other one contains the data. But this example uses only one kind. All elements include enough information to render an input mask.

The data of this example includes one template - templates/page and one element - pages/page.


<project>
  <templates>
    <page>
      <title>
        <edit type="text" cols="20"/>
      </title>
      <content>
        <edit type="html">
      </content>
    </page>
  </temlates>
  <pages>
    <page>
      <title>
        <edit type="text" cols="20">a title</edit>
      </title>
      <content>
        <edit type="html">a content</edit>
      </content>
    </page>
  </pages>
</project>

Insert an Arbitrary Entry

This insert code is very poor. It just copies the template. The next example shows how to process the template before writing it back.


<xsl:template name="insert">
  <!-- Fetch a template from the database, strip the primay keys -->
  <pl:variable name="entry" document="$document" select="templates/page" plid="no"/>
  <!-- Fetch the key of the parent element, don't fetch its childs -->
  <pl:variable name="owner" document="$document" select="pages" fetch-childs="''"/>
  <!-- Store the new entry -->
  <pl:write name="id" select="$entry" document="$document" owner="$owner/@plid"/>
</xsl:template>

Update an Arbitrary Entry

This example is more complicated then the previous ore. It consists of two parts. The first one renders a HTML input mask. It goes through the data and creates an HTML input field for each edit element. Since the edit fields don't include names, it constructs the necessary names from the database keys of the elements. After the user presses the submit button, the HTTP server calls the second part. It updates the database with the values that the user has just typed.


<!-- Render the mask  -->

<xsl:template match="update-mask">
  <!-- Fetch the key of the element from the HTTP parameters -->
  <xsl:variable name="id" select="$request/parameter/id">
  <!-- Fetch the document fragment from the database -->
  <pl:variable name="entry" document="$document" select="*[@plid = $id]/*"/>
  <!-- Render a HTML input mask -->
  <form action="update.plxml">
    <xsl:apply-templates select="$entry" mode="update-mask"/>
    <input type="hidden" name="id" value="{$id}"/>
    <input type="submit" value="OK"/>
  </form>
</xsl:template>

<xsl:template match="edit[@type='text']" mode="update-mask">
  <!-- Render an input field -->
  <input type="text" name="v{@plid}" size="{@cols}" value="{.}"/>
</xsl:template>

<!-- Process the input  -->

<xsl:template match="update">
  <!-- Fetch the key of the element from the HTTP parameters -->
  <xsl:variable name="id" select="$request/parameter/id">
  <!-- Fetch the old fragment from the database -->
  <pl:variable name="old-entry" document="$document" select="*[@plid = $id]"/>
  <!-- Merge the new values -->
  <xsl:variable name="entry">
    <xsl:apply-templates select="$old-entry" mode="copy-replace"/>
  </xsl:variable>
  <!-- Store the new entry -->
  <pl:write name="id" select="$entry" document="$document"/>
</xsl:template>

<!-- Copy a document fragment -->
<xsl:template match="*|text()" mode="copy-replace">
  <xsl:copy>
    <xsl:apply-templates select="*|@*|text()" mode="copy-replace"/>
  </xsl:copy>
</xsl:template>

<!-- Replace the text of an edit element -->
<xsl:template match="edit" mode="copy-replace">
  <xsl:copy>
    <!-- Copy attributes -->
    <xsl:apply-templates select="@*" mode="copy-replace"/>
    <!-- Names are constructed from the keys of the elements -->
    <xsl:variable name="vn">v<xsl:value-of select="@plid"/></xsl:variable>
    <!-- Fetch the value of the HTTP-Variable -->
    <xsl:value-of select="$request/parameter/*[name()=$vn]"/>
  </xsl:copy>
</xsl:template>

<!-- Copy attributes -->
<xsl:template match="@*" mode="copy-replace">
  <xsl:copy/>
</xsl:template>

8.5 Generating different formats.

An rm -d ms application has at least two XML documents. A large one that contains the data and a small one that controls rendering. So you can write a small XML document for each kind of output.

To be continued ...

If you would like to contribute your patterns, please send them to rmdms-devel@lists.sourceforge.net.


Next Previous Contents