Rapid Development Layer

In this tutorial you will learn how to take an existing HTML source and modify it on the fly while it is served through FIT.

Setup

We assume that you have successfully integrated a website with FIT as shown earlier.

We will base our work on the “Hello World” document from the RESS example and the corresponding URL map so that “Hello World” is accessible under http://local14.sevenval-fit.com/hello.html:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    ...
  </body>
</html>
<urlmap>
  <map path="/hello.html" source="fit://site/public/hello.html" />
  ...
</urlmap>

Content Modifications

Now we will modify the result. Let’s recap: When FIT receives a HTTP request from a browser (1), it in turn requests the corresponding resource from a remote source in a separate HTTP request (2). (Local resources (fit://) are just loaded from the file system.) A HTML response (3) is parsed into a DOM and later serialized back to a string before it is sent to the browser (4).

FIT request

The DOM makes it easy to modify the document. With the help of actions and control structures you can intervene in the normal request flow.

At first, create a conf/flow.xml with a default-request and a parse action:

<flow>
  <default-request />
  <parse />
</flow>

As this flow is identical to the built-in default, this changes nothing. Let’s enhance that flow with a set-attributes action and change the color of the greeting message:

<flow>
  <default-request />
  <parse />

  <set-attributes xpath="//h1" style="color: #67a92f" />
</flow>

Still not very dynamic. Let’s add some regex actions and change the greeting according to the preferred language of the user:

<flow>
  <default-request />

  <regex pattern="/Hello World/" replace="Hallo Welt" if="starts-with(request/language, 'de')"/>
  <regex pattern="/Hello World/" replace="Bonjour le monde" if="starts-with(request/language, 'fr')"/>

  <parse />

  <set-attributes xpath="//h1" style="color: #67a92f" />
</flow>

Note the if conditionals that check the request/language Delivery Context property. As regular expressions operate on plain text, regex actions are normally run before the parse action, i.e. before the HTML is parsed into a DOM.

Switch the preferred language in your browser to German (de) or French (fr) and see what happens.

Exercise

If the user prefers Spanish (es), the greeting shall be ¡Hola mundo!. For the solution, keep reading.

Advanced Content Juggling

The simplest solution would be to add yet another regex action:

<flow>
  ...
  <regex pattern="/Hello World/" replace="Hallo Welt" if="starts-with(request/language, 'de')"/>
  <regex pattern="/Hello World/" replace="Bonjour le monde" if="starts-with(request/language, 'fr')"/>
  <regex pattern="/Hello World/" replace="¡Hola mundo" if="starts-with(request/language, 'es')"/>
  ...
</flow>

To make it obvious that all three cases are mutually exclusive, you could instead use a Flow choose element:

<flow>
  ...
  <choose>
    <when test="starts-with(request/language, 'de')"><regex pattern="/Hello World/" replace="Hallo Welt"/></when>
    <when test="starts-with(request/language, 'fr')"><regex pattern="/Hello World/" replace="Bonjour le monde"/></when>
    <when test="starts-with(request/language, 'es')"><regex pattern="/Hello World/" replace="¡Hola mundo"/></when>
  </choose>
  ...
</flow>

These solutions work, but they violate the DRY principle. Moreover, they don’t scale. With each newly supported language these lists will grow.

So let’s try a different approach. Instead of changing the plain text with regular expressions (which can be a fragile thing, by the way) we will now manipulate the DOM with the help of XSLT. Change your conf/flow.xml to

<flow>
  <default-request />
  <parse />

  <set-attributes xpath="//h1" style="color: #67a92f" />
  <xslt src="transform.xsl"/>
</flow>

and create an XSLT style sheet conf/transform.xml:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" encoding="UTF-8" doctype-system="about:legacy-compat"/>

  <!-- Store the "request/language" property of the Delivery Context in a variable. -->
  <xsl:variable name="language" select="document('fit://request/dc')/dc/request/language"/>

  <xsl:template match="h1">
    <h1>
      <xsl:copy-of select="@*"/>
      <xsl:choose>
        <xsl:when test="starts-with($language, 'de')">Hallo Welt!</xsl:when>
        <xsl:when test="starts-with($language, 'fr')">Bonjour le monde!</xsl:when>
        <xsl:when test="starts-with($language, 'es')">¡Hola mundo!</xsl:when>
      </xsl:choose>
    </h1>
  </xsl:template>

  <!-- Recursively copy the rest of the DOM. -->
  <xsl:template match="node() | @*">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Okay, that looks even worse! But you get the idea how to transform a DOM with XSLT. Note, how the XSLT document function is used to access the Delivery Context. If you want to dig deeper into Sevenval FIT, XSLT is the way to go.

Anyway, let’s get this done properly. First, create an XML file conf/greetings.xml containing the greetings in all languages. This helps separating data from code. Adding more languages is now really easy.

<greetings>
  <greeting lang="en">Hello World!</greeting>
  <greeting lang="de">Hallo Welt!</greeting>
  <greeting lang="fr">Bonjour le monde!</greeting>
  <greeting lang="es">¡Hola mundo!</greeting>
</greetings>

Then, change conf/transform.xml to:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" encoding="UTF-8" doctype-system="about:legacy-compat"/>

  <!-- Store the "request/language" property of the Delivery Context in a variable. -->
  <xsl:variable name="language-header" select="document('fit://request/dc')/dc/request/language"/>

  <!-- Store the two-letter ISO 639-1 language code in a variable. -->
  <xsl:variable name="language" select="substring($language-header, 1, 2)"/>

  <!-- Store the "greetings" from "conf/greetings.xml" in a variable. -->
  <xsl:variable name="greetings" select="document('fit://site/conf/greetings.xml')/greetings"/>

  <xsl:template match="h1">
    <h1>
      <xsl:copy-of select="@*"/>
      <!-- Select the greeting for the given language. -->
      <xsl:value-of select="$greetings/greeting[@lang = $language]"/>
    </h1>
  </xsl:template>

  <!-- Recursively copy the rest of the DOM -->
  <xsl:template match="node() | @*">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Much more readable and scalable! Go and check the result in your browser.

Exercise

  • Sharpen your XSLT skills!

Response Header Manipulation

To add a header to the HTTP response use the set-header action:

<flow>
  ...
  <set-header name="X-Powered-By" value="Sevenval FIT" />
</flow>

Check the resulting HTTP response headers in the Network panel of your browser or call the site with curl on the command line:

$ curl --head 'http://local14.sevenval-fit.com/hello.html'

Exercise

Enhance the X-Powered-By header value so that it includes the version of the current FIT Server. Hint: The Delivery Context will help with that.

Solution

Use the server/fit-version property of the Delivery Context. Instead of the value attribute, use the xpath attribute of the set-header action to evaluate the given XPath expression. The XPath function concat combines both parts of the string:

<set-header name="X-Powered-By" xpath="concat('Sevenval FIT ', server/fit-version)" />

More Tutorials