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.
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).
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.
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
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
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>
<!-- Content: (lsp:when+, lsp:otherwise?) -->
</lsp:choose>
<lsp:when
test = boolean-expression>
<!-- Content: any (whitespace stripping) -->
</lsp:when>
<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
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:
index
- a number the position in the list, starting from 1first
- a boolean indicating if this is the first elementlast
- a boolean indicating if this is the last elementeven
- a boolean indicating if this is an even elementodd
- a boolean indicating if this is an odd element
<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.
The lsp:import
includes a file when the page is
compiled. Any LSP elements and templates in the included file are
processed.
<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 import
ed and in
an enclose. The lsp:root
element can be nested.
<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
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
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
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:
html
(in any case) with
null namespace URI, the output method is html
.html
(in lower case) with
namespace URI http://www.w3.org/1999/xhtml
,
the output method is html
or xhtml
depending
on the "html" parameter to the LSP compiler.xml
.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.
The lsp:include
element is used to include a part.
May only be used in an enclose.
<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.
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
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>
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.
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.
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 ']'
LSPName ::= NCName not containing any '.' Literal ::= '"' [^"]* '"' | "'" [^']* "'" Number ::= Digits ('.' Digits?)? | '.' Digits NodeType ::= 'comment' | 'text' | 'processing-instruction' | 'node' Digits ::= [0-9]+
Non-namespaced functions are the core functions, defined as in XPath:
true
if object is null
,
false
otherwisenull
,
return object2 if object1 is null
Namespaced functions are extension functions.
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.
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.
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.
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 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.
LSP type | Java type |
---|---|
boolean | java.lang.Boolean |
number | java.lang.Number (e.g. java.lang.Integer or java.lang.Dobule ) |
string | java.lang.String java.lang.CharSequence char[] byte[] (which is assumed to be encoded using ISO-8859-1)java.lang.Enum |
list | java.util.Collection (only the iterator() , size() and isEmpty() methods are used)Object[] int[] short[] long[] double[] float[] boolean[] |
tuple | java.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 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.