LSP User Guide

Overview

LSP is an advanced web template language based on XML technology. LSP provides powerful and easy to use presentation logic, but keeps business logic and technical details out of templates. LSP is compiled into Java bytecode for efficient execution.

LSP is fully based on XML and namespaces. This means that it's easy to efficiently use LSP together with other XML technologies, such as XSLT (for example, you can process the output of LSP with XSLT).

LSP can be used as an alternative to JSP in a Java Servlet based web application. See here.

System requirements

LSP requires Java Runtime Environment (JRE) version 1.5 (also known as 5.0). You might experience problems with XSLT transforms when using the initial release of Java 1.5, change to Java 1.5 update 4 to avoid those problems. LSP also works with Java 6 (also known as 1.6).

LSP basics

An LSP page is a well-formed XML document, composed of elements from the LSP namespace and from other namespaces (including the null namespace). The document root element may, but doesn't have to, be in the LSP namespace.

The namespace URI for LSP is http://staldal.nu/LSP/core. In this document, it's assumed that this namespace is mapped to the prefix lsp, however any prefix may be used.

An LSP page is processed by interpreting all LSP elements, and sending all non-LSP elements (unless they are extension elements), all character data and all processing instructions to the output (subject to template processing). However, some LSP element can alter the output of parts of the LSP page. Comments in the LSP page are ignored. Any DTD in the LSP page is not sent to the output.

To ensure high performance, LSP pages are compiled into bytecode, which can then be executed several times, possibly with a different set of parameters. Unless otherwise stated, all processing occurs when the page is executed.

It's simple to start using LSP in a static HTML (or some XML based markup language) page. Just add a declaration for the LSP namespace (xmlns:lsp="http://staldal.nu/LSP/core") in the root element and add LSP elements. However, you must ensure that the HTML page is in well-formed XML format.

LSP elements

This section describes all elements in the LSP namespace. An attribute name in bold means this attribute is required. An attribute value specification surrounded by curly braces ({}) means that the attribute value is processed as a template.

In elements marked with (whitespace stripping), any whitespace node immediatly after the start-tag and before the end-tag is stripped. This stripping can be inhibited by setting the xml:space attribute to preserve. The prupose of this whitespace stripping is to make it possible to format the LSP source in a readable way without getting a lot of unnessecary whitespace in the output.

lsp:value-of

<lsp:value-of
  select = string-expression
  disable-output-escaping = boolean>
  <!-- Content: any -->
</lsp:value-of>

The select expression is evaluated, and converted to string (as if by a call to the string() function). That string is outputted. Any content of the lsp:value-of element is ignored (may be used as sample data during development).

If the disable-output-escaping attribute is specified and set to "yes" or "disable-output-escaping", the normal escaping of characters &, < and characters not possible in the current character encoding is disabled. This allows you to e.g. include a XML or HTML fragment as a string. Note: This may cause the output to be ill-formed.

lsp:if

<lsp:if
  test = boolean-expression>
  <!-- Content: any (whitespace stripping) -->
</lsp:if>

The test expression is evaluated, and converted to boolean (as if by a call to the boolean() function). If the result is true, the content of the lsp:if element is outputted, otherwise nothing outputted.

lsp:choose

<lsp:choose>
  <!-- Content: (lsp:when+, lsp:otherwise?) -->
</lsp:choose>

lsp:when

<lsp:when
  test = boolean-expression>
  <!-- Content: any (whitespace stripping) -->
</lsp:when>

lsp:otherwise

<lsp:otherwise>
  <!-- Content: any (whitespace stripping) -->
</lsp:otherwise>

The test expression for each lsp:when element is evaluated in turn, and converted to boolean (as if by a call to the boolean() function). The content of the first, and only the first, lsp:when element whose test is true is outputted. If no lsp:when element is true, the content of the lsp:otherwise element is outputted. If no lsp:when element is true and if there is no lsp:otherwise element, nothing is outputted.

lsp:for-each

<lsp:for-each
  select = list-expression
  var = variable name>
  status = variable name>
  <!-- Content: any (whitespace stripping) -->
</lsp:for-each>

The select expression is evaulated, and is expected to be of type list, otherwise an error occurs. The content of the lsp:for-each element is outputted once for each object in the list, with the variable specified in the var attribute bound to the current object in the list. If the status attribute is specified, a variable with that name is bound to an interator status object. Any variable with the same name in the enclosing scope is shadowed.

An iterator status object is a tuple with the following elements:

lsp:let

<lsp:let
  var1 = expression
  var2 = expression
  ...
  varn = expression>
  <!-- Content: any (whitespace stripping) -->
</lsp:let>

Each expression is evaulated, and the value is bound to a variable with the same name as the attribute within the body of the lsp:let element. Any variables with the same name in the enclosing scope are shadowed.

lsp:import

<lsp:import
  file = URL />

The lsp:import includes a file when the page is compiled. Any LSP elements and templates in the included file are processed.

lsp:root

<lsp:root>
  <!-- Content: any -->
</lsp:root>

The lsp:root element simply outputs all of its content. Useful as document root element in files to be imported and in an enclose. The lsp:root element can be nested.

lsp:processing-instruction

<lsp:processing-instruction
  name = { ncname }>
  <!-- Content: any   -(lsp:processing-instruction, lsp:element, lsp:attribute) -->
</lsp:processing-instruction>

The lsp:processing-instruction element creates a processing instruction in the output. The name attribute is used as the processing instruction target, and the character data of the content is used as the processing instruction data. lsp:processing-instruction may not be nested.

lsp:element

<lsp:element
  name = { ncname }
  namespace = { URI }>
  <!-- Content: any -->
</lsp:element>

The lsp:element element creates an element with a computed name in the output. The name attribute is used as the local name of the element. The namespace attribute is used as the namespace URI of the element (if omitted, the generated element will be in the default namespace in effect, set namespace attribute to the empty string to explicitly place the element in the null namespace).

Note: unlike the similar element in XSLT, the value of the name attribute may not be a QName.

lsp:attribute

<lsp:attribute
  name = { ncname }
  namespace = { URI }
  value = { any }/>

The lsp:attribute element creates an attribute with a computed name in the output. The attribute is attached to the enclosing element (which may be either an lsp:element or a normal literal element). The name attribute is used as the local name of the attribute. The namespace attribute is used as the namespace URI of the atribute (if omitted, the created attribute will be in the null namespace). May not be used to create namespace declarations, the value of the name attribute may not be "xmlns". It's an error to add an attribute with the same name as an already existing attribute, this error cannot be detected at compile time, and will result in undefined behaviour at runtime. If the name expression evaluates to the empty string, no attribute will be created.

Any whitespace immediately before an lsp:attribute element, and any whitespace immediately after an lsp:attribute element if there are no more children of the parent element, will be stripped. Unless the xml:space attribute is set to preserve.

Note: unlike the similar element in XSLT, the value of the name attribute may not be a QName.

lsp:output

<lsp:output
  method = "xml" | "html" | "xhtml" | "text" | "html-fragment" | "xhtml-fragment"
  version = nmtoken
  encoding = string
  omit-xml-declaration = "yes" | "no"
  standalone = "yes" | "no"
  doctype-public = string
  doctype-system = string
  indent = "yes" | "no"
  media-type = string 
  stylesheet = string />

The lsp:output specifies how the LSP page should be serialized to a byte stream. The parameters has the same meaning as in XSLT 1.0, with the addition of the xhtml output method. The xhtml output method works as the xml output method, but uses the applicable HTML compatibility guidelines published in Appendix C of the XHTML 1.0 specification. In addition, the methods html-fragment and xhtml-fragment are provided which doesn't output any XML declaration or DOCTYPE declaration, they are useful to generate fragments for inclusion in a page within an AJAX application.

The xhtml output method attempt to omit the XML declaration by default, set omit-xml-declaration="no" to force generation of XML declaration.

An LSP page may contain at most one lsp:output element.

Any whitespace immediately before an lsp:output element will be stripped, unless the xml:space attribute is set to preserve.

If an LSP page contains no lsp:output element with a method parameter, the output method is choosen as follows at compile time:

If the stylesheet parameter is specified, an XSLT stylesheet is used to transform the output of the LSP page. All other output parameters are ignored and the output parameters in the XSLT stylesheet are used instead. Note that XSLT might use an other algorithm to determine default output method, so be sure to always specify output method in the XSLT stylesheet explicitly.

lsp:include

<lsp:include
  part = name />

The lsp:include element is used to include a part. May only be used in an enclose.

lsp:part

<lsp:part
  name = name>
  <!-- Content: any (whitespace stripping) -->
</lsp:part>

The lsp:part element is used to define a part to be included in an enclose.

Enclose

A web application usually consists of several pages with similar structure. All pages may have a header and menu in common, but different content in the "middle" of the page. You probably want a separate LSP file for each page, so you might end up with several LSP files with the common structure repeated in all of them. This is inconvenient since you need to change several files when the common structure changes.

Some common parts could be extracted to files you import, but that strategy tends to be cumbersome due to the requirement of each imported file to have well-balanced markup. Another way is to process the output with an XSLT stylesheet, but the syntax is cumbersome and XSLT may degrade the performance. A better way to handle this is to use the enclose feature of LSP.

An enclose is a LSP file which defines the common structure of several pages. The enclose uses the <lsp:include> element to include the parts that are different in each page. The main LSP files will contain a list of <lsp:part> elements (use <lsp:root> to achieve a well-formed file if there are several parts) to define the unique parts.

An enclose file may use all LSP features, and have access to page parameters. The enclose processing is done by the LSP compiler, with no runtime performance penalty.

Example:

enclose.lsp

<html xmlns:lsp="http://staldal.nu/LSP/core"
      xmlns="http://www.w3.org/1999/xhtml">
  <lsp:output encoding="UTF-8" indent="no"/>
  <head>
    <title>My web app</title>
    <lsp:include part="head"/>    
  </head>
  <body>
    <h1>My web app</h1>
    <div>
      <lsp:include part="body"/>    
    </div>        
  </body>
</html>

page1.lsp

<lsp:root xmlns:lsp="http://staldal.nu/LSP/core"
          xmlns="http://www.w3.org/1999/xhtml">

  <lsp:part name="head">
    <meta name="foo" content="bar"/>
  </lsp:part>

  <lsp:part name="body">
    <p>Hello, world!</p>
  </lsp:part>

</lsp:root>

page2.lsp

<lsp:root xmlns:lsp="http://staldal.nu/LSP/core"
          xmlns="http://www.w3.org/1999/xhtml">

  <lsp:part name="head">
    <meta name="foo" content="whatever"/>
  </lsp:part>

  <lsp:part name="body">
    <p>Another page</p>
  </lsp:part>

</lsp:root>
# lspc -enclose enclose.lsp page1.lsp page2.lsp

Templates

All attribute values to non-LSP elements and the values of some attributes to LSP elements are processed as templates. This means that it may contain LSP expressions surrounded by curly braces ({}). A such expression is evaluated and the expression (together with the curly braces) is replaced with its string value. To actually include a literal curly brace, use a double curly brace.

If the attribute value of an non-LSP element consists of an expression yeilding a boolean value, it will be handled specially. If the expression evaulates to false the attribute will be removed, if the expression evaulates to true the attribute will get its name as value. This is useful for handling the boolean attributes in HTML, such as checked and selected. Note: the check if the expression yeilds a boolean value is done at compile time, it won't work if the expression is a variable reference, a tuple expression or an extension function call. Wrap the expression with the boolean function to force handling as a boolean. Wrap the expression with the string function to inhibit handling as a boolean.

Example:

<select>
  <lsp:for-each select="$theList" var="ent">
    <option value="{$ent.value}" selected="{$ent.value=$currentVal}">
      <lsp:value-of select="$ent.text"/>
    </option>
  </lsp:for-each>
</select>

LSP Expressions

The expression language used in LSP is based on XPath. It's essentially XPath 1.0 without nodesets, with the conditional expression "if ... then ... else ..." from XPath 2.0.

Types

The expression language has five types:

The boolean, number and string types can be implicitly converted into each other, as if using the boolean(), number() and string() functions. A list can be implicitly converted into boolean, the result will be true if the list has at least one element.

An object can be extracted from a tuple either by appending an '.' and a symbol, e.g.
$tupleVar.foo
or
functionReturningTuple().bar
. Or by appending '[]' with an expression, e.g.
$tupleVar[$myKey]
or
functionReturningTuple()[getKey()]
.

With the '[]' notation, a tuple can be used like an hashtable, and is most likley implemented with an hashtable. Note that the value will be converted to a string before used as key for lookup, this means that a mapping with another type of key will not be found by LSP (e.g. you cannot use a Java Number).

It's a runtime error to attempt to reference a non-existing tuple element. Unless the acceptUnbound parameter is passed to the compiler, then an non-existing tuple element will result in a special value convertible to the empty string, the number 0.0, the boolean value false, the empty list or the empty tuple. The haselement() function can be used to test if a tuple has a given element, it will never generate runtime error. A tuple element set to null is not considered non-existing.

Grammar

Expr ::= OrExpr

PrimaryExpr ::= VariableReference
              | '(' Expr ')'
              | Literal
              | Number
              | FunctionCall

VariableReference ::= '$' LSPName

FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument )* )? ')'

FunctionName ::= LSPName ( ':' LSPName )?  /* not NodeType */

Argument ::= Expr

OrExpr ::= AndExpr
         | OrExpr 'or' AndExpr

AndExpr ::= IfExpr
          | AndExpr 'and' IfExpr

IfExpr ::= EqualityExpr
         | 'if' '(' Expr ')' 'then' Expr 'else' EqualityExpr 

EqualityExpr ::= RelationalExpr
               | EqualityExpr '=' RelationalExpr
               | EqualityExpr '!=' RelationalExpr

RelationalExpr ::= AdditiveExpr
                 | RelationalExpr '<' AdditiveExpr
                 | RelationalExpr '>' AdditiveExpr
                 | RelationalExpr '<=' AdditiveExpr
                 | RelationalExpr '>=' AdditiveExpr

AdditiveExpr ::= MultiplicativeExpr
               | AdditiveExpr '+' MultiplicativeExpr
               | AdditiveExpr '-' MultiplicativeExpr

MultiplicativeExpr ::= UnaryExpr
                     | MultiplicativeExpr '*' UnaryExpr
                     | MultiplicativeExpr 'div' UnaryExpr
                     | MultiplicativeExpr 'mod' UnaryExpr

UnaryExpr ::= TupleExpr
            | '-' UnaryExpr

TupleExpr ::= PrimaryExpr
            | TupleExpr '.' LSPName
            | TupleExpr '[' Expr ']'

Lexical

LSPName ::= NCName not containing any '.'

Literal ::= '"' [^"]* '"'
          | "'" [^']* "'"

Number ::= Digits ('.' Digits?)?
         | '.' Digits


NodeType ::= 'comment'
           | 'text'
           | 'processing-instruction'
           | 'node'

Digits ::= [0-9]+

Functions

Non-namespaced functions are the core functions, defined as in XPath:

Namespaced functions are extension functions.

Variables

Unlike XPath, variable name may not be namespaced.

The parameters passed to the LSP page are bound to variables in the outermost scope.

It's a runtime error to attempt to reference an unbound variable. Unless the acceptUnbound parameter is passed to the compiler, then an unbound variable will result in a special value convertible to the empty string, the number 0.0, the boolean value false, the empty list or the empty tuple. The isset() function can be used to test if a variable is bound, it will never generate runtime error. A variable bound to null is not considered unbound.

The LSP compiler

LSP comes with a compiler which compiles LSP pages into Java .class files. The LSP compiler is contained in lspc.jar.

For a programmatic Java interface to the LSP compiler, look at the nu.staldal.lsp.compiler package.

The LSP compiler uses the third-party library BCEL. The BCEL library is only needed when compiling LSP pages, not when executing the compiled code. lsprt.jar is also needed by the compiler.

Running LSP compiler from command line

The LSP compiler is invoked by the application class nu.staldal.lsp.compiler.LSPCompilerCLI. The syntax is:

lspc [-verbose] [-force] [-html] [-acceptUnbound] [-sourcepath sourcepath] [-d destdir] [-enclose encloseFile] inputFile ...

sourcepath specifies where to look for imported files with relative URL:s, may contain multiple directories separated by ';' (will search the directory where the source file is as well). destdir specifies where place generated files, default is current directory. Multiple input files can be specified. If -force is not specified, files are only compiled if the source file is newer than the target file (imported files and encloses are also checked, changing an imported file or enclose will trigger recompile).

If -html is specified, the default output method for HTML pages will be html, otherwise it will be xhtml.

If -acceptUnbound is specified, the LSP page will accept non-existing variables and tuple entries without runtime error. This may make it easier to develop the application, but may make it harder to debug.

If -verbose is specified, the name of compiled files will be displayed.

You have to put bcel.jar and lsprt.jar in CLASSPATH. If you use any extension libraries, you have to include them in the CLASSPATH as well.

Using LSP compiler from within Apache Ant

Define the LSP compiler Ant task in the Ant build file like this:

<taskdef name="lspc" classname="nu.staldal.lsp.compiler.LSPCompilerAntTask">
  <classpath>
    <pathelement location="locationOfLSPJars/lspc.jar" />
    <pathelement location="locationOfLSPJars/lsprt.jar" />
    <pathelement location="locationOfBCELJars/bcel.jar" />
  </classpath>
</taskdef>  

If you use any extension libraries, you have to include them in the classpath as well.

and use the following syntax:

<lspc sourcepath="sourcepath"
         destdir="where to place compiled code"
         enclose="enclose file"
         force="force recompiling of all files"
         html="use html as default output method"
         acceptUnbound="allow non-existing variables and tuple entries">
      <fileset dir="lsp">
        <include name="*.lsp" />
      </fileset>
</lspc>

sourcepath specifies where to look for imported files with relative URL:s, may contain multiple directories separated by ';' or ':' (will search the directory where the source file is as well). If force is not set, files are only compiled if the source file is newer than the target file (imported files and encloses are also checked, changing an imported file or enclose will trigger recompile).

If html is set, the default output method for HTML pages will be html, otherwise it will be xhtml.

If acceptUnbound is set, the LSP page will accept non-existing variables and tuple entries without runtime error. This may make it easier to develop the application, but may make it harder to debug.

The LSP runtime

The LSP runtime is contained in lsprt.jar.

For a general Java interface to the LSP runtime, look at the nu.staldal.lsp package, especially the LSPHelper class.

For a Java Servlet interface to the LSP runtime, look at the nu.staldal.lsp.servlet package. A description how to setup LSP for use with Java Servlets. Extension libraries will get an nu.staldal.lsp.servlet.LSPServletContext (containing the javax.servlet.ServletContext, the javax.servlet.http.HttpServletRequest and the javax.servlet.http.HttpServletResponse) as external context.

Passing parameters from Java to LSP

LSP typeJava type
booleanjava.lang.Boolean
numberjava.lang.Number
(e.g. java.lang.Integer or java.lang.Dobule)
stringjava.lang.String
java.lang.CharSequence
char[]
byte[] (which is assumed to be encoded using ISO-8859-1)
java.lang.Enum
listjava.util.Collection (only the iterator(), size() and isEmpty() methods are used)
Object[]
int[]
short[]
long[]
double[]
float[]
boolean[]
tuplejava.util.Map (only the get(Object) and containsKey(Object) methods with java.lang.String keys are used)
java.util.ResourceBundle

A java.sql.ResultSet will be used as a list of tuples. However, it can only be traversed once, and the count function can only be used after traversion.

If a Java object of another type is used as a tuple, it is considered to be a JavaBean, and an attempt is made to use its properties as tuple values.

The Java object java.lang.Void.TYPE is a special value convertible to the empty string, the number 0.0, the boolean value false, the empty list or the a tuple giving java.lang.Void.TYPE this value for every possible key (implemented with FullMap).

An object of any type, even null, can be passed as a variable and used as parameter to an extension function.

Extension libraries

Extension function and extension elements are implemented in Java with extension libraries. An extension library is a Java class implementing the nu.staldal.lsp.LSPExtLib interface, possibly by extending the nu.staldal.lsp.SimpleExtLib class.

Refer to an extension library by using a namespace URI starting with "lsp:extlib:" followed by the fully qualified class name of the implementation class.

The extension library implementation class must be available to both compiler and runtime.