

































|
XSP
(eXtensible Server Pages)
is Cocoon's technology for building web applications based on dynamic
XML content.
Beyond static content (i. e., hand-written documents produced
by web authors), web applications demand dynamic content
generation capabilities, where XML documents or fragments
are programmatically produced at request time.
In this context, content is the result of computations based on request
parameters and, often, on access to external data sources such as
databases or remote server processes.
This distinction in content origin extends the "traditional"
regions of web publishing (content and presentation)
to also encompass that of logic.
Dynamic web content generation has traditionally been addressed by
embedding procedural code into otherwise static markup.
This approach is fully supported by XSP. Consider the following example:
 |  |  |  | . . .
<p>
Good
<xsp:logic>
String timeOfDay = (
new SimpleDateFormat("aa")
).format(new Date());
if (timeOfDay.equals("AM")) {
<xsp:content>Morning</xsp:content>
} else {
<xsp:content>Afternoon</xsp:content>
}
</xsp:logic>!
</p>
. . . |  |  |  |  |
Upon XSP processing, this XML fragment will yield:
before noon and:
afterwards.
While the above may appear simple (to a Java developer,
that is!), XSP has been conceived to allow web authors
to generate dynamic content without forcing them to learn
a programming language.
Thus, XSP allows us to rephrase our example as:
 |  |  |  | ...
<p>Good <util:time-of-day/>!</p>
... |  |  |  |  |
where <util:time-of-day/> is a
library tag encapsulating dynamic content
in a simple, transparent way.
This feature promotes an ideal division of labor
where:
-
Application Developers encapsulate application
logic in a consistent, intuitive dynamic tagset
-
Content authors use an application-oriented
markup vocabulary (augmented with dynamic tags) to
produce XML documents
-
Presentation designers write XSLT stylesheets
to render the resulting content as visually appealing web
pages
Of course, for those of us subject to "real world" constraints,
XSP also supports the time-honored approach of first using
embedded logic and then incrementally evolving the resulting
pages into a well-structured web application.
These concepts are illustrated in the XSP Samples included in the
distribution.
|
An XSP page is a Cocoon XML document containing tag-based
directives that specify how to generate dynamic content
at request time.
Upon Cocoon processing, these directives are replaced by
generated content so that the resulting, augmented XML
document can be subject to further processing (typically
an XSLT transformation)
XSP pages are transformed into Cocoon producers,
typically as Java classes, though any scripting language
for which a Java-based processor exists could also be used.
Directives can be either XSP built-in processing tags or
user-defined library tags.
XSP built-in tags are used to embed procedural logic,
substitute expressions and dynamically build XML nodes.
User-defined library tags act as templates that dictate
how program code is generated from information encoded in
each dynamic tag.
|
 |  |  |  | A Simple Example: Embedded Logic |  |  |  |  |
In the following XSP page, the Java language is used to
generate some dynamic content:
 |  |  |  | <?xml version="1.0"?>
<?cocoon-process type="xsp"?>
<?cocoon-process type="xslt"?>
<?xml-stylesheet href="sample.xsl" type="text/xsl"?>
<xsp:page language="java" xmlns:xsp="http://www.apache.org/1999/XSP/Core">
<page title="Time of Day">
<xsp:logic>
// Define a variable to hold the time of day
Date now = new Date();
</xsp:logic>
<p>
To the best of my knowledge, it's now
<!-- Substitute time of day here -->
<xsp:expr>now</xsp:expr>
</p>
</page>
</xsp:page> |  |  |  |  |
Upon Cocoon processing, this page should yield something
like:
 |  |  |  | <html>
<head><title>Time of Day</title></head>
<body>
<h3 style="color: navy; text-align: center">Time of Day</h3>
<p>It's now Thu Dec 23 20:11:18 PST 1999</p>
</body>
</html> |  |  |  |  |
Let's dissect this example:
In our example, the "user" root element contains:
 |  |  |  | <page title="Time of Day">
<xsp:logic>
// Define a variable to hold the time of day
Date now = new Date();
</xsp:logic>
<p>
To the best of my knowledge, it's now
<!-- Substitute time of day here -->
<xsp:expr>now</xsp:expr>
</p>
</page> |  |  |  |  |
Here, we use the two essential XSP logic tags:
-
<xsp:logic> . This tag encloses
developer-supplied program logic. Such logic will be
transcribed verbatim into the generated Cocoon
producer.
-
<xsp:expr> . This tag evaluates
a program expression and substitutes its value as
a Text DOM node in the resulting
document.
In the fragment:
 |  |  |  | <xsp:logic>
// Define a variable to hold the time of day
Date now = new Date();
</xsp:logic> |  |  |  |  |
a new variable (now ) is declared that can
be subsequently referenced anywhere in the remaining
page content.
Note that, in this particular example, it's not necessary
to fully specify the Java type (java.util.Date )
because XSP automatically generates import
statements for the most commonly used Java libraries.
In the fragment:
 |  |  |  | <!-- Substitute time of day here -->
<xsp:expr>now</xsp:expr> |  |  |  |  |
the variable now is referenced so that its
value is inlined in the resulting XML document as a
Text node.
Note also that it's not necessary to explicitly cast
now to String .
<xsp:expr> takes care
of converting all Java types so that values are properly
converted depending on context.
In general, <xsp:expr> values are inlined
according to the following rules:
-
Primitive Java types (
int , long ,
etc) are converted to their String representation
and wrapped as Text
-
Java objects are wrapped as follows:
-
String s are directly wrapped as
Text
-
Arrays are wrapped as
DocumentFragments
and each element is recursively applied these same
transformation rules.
-
Non-string objects are converted to
String
(by means of their toString method)
and subsquently wrapped as Text .
Note that some types may not provide a suitable
string representation, so they may require a
more elaborate expression
A nice XSP feature (not present in other server pages
technologies) is that the <xsp:logic>
element allows for the arbitrary nesting of other markup
without the need to "prematurely" close it. For example:
 |  |  |  | <table>
<xsp:logic>
for (int i = 0; i < countries.length; i++) {
<tr>
<td>
<xsp:expr>countries[i].getName()</xsp:expr>
</td>
<td>
<xsp:expr>countries[i].getCurrency()</xsp:expr>
</td>
</tr>
}
</xsp:logic>
</table> |  |  |  |  |
If such nesting was not allowed, the purely programmatic
alternative would be considerably more complex:
 |  |  |  |
<table>
<xsp:logic>
for (int i = 0; i < countries.length; i++) {
// <tr>
xspParentNode = xspCurrentNode;
xspNodeStack.push(xspParentNode);
xspCurrentNode = document.createElement("tr");
xspParentNode.appendChild(xspCurrentNode);
// <td>name</td>
xspParentNode = xspCurrentNode;
xspNodeStack.push(xspParentNode);
xspCurrentNode = document.createElement("td");
xspParentNode.appendChild(xspCurrentNode);
xspCurrentNode.appendChild(
document.createTextNode(countries[i].getName())
);
xspCurrentNode = (Node) xspNodeStack.pop();
// <td>currency</td>
xspParentNode = xspCurrentNode;
xspNodeStack.push(xspParentNode);
xspCurrentNode = document.createElement("td");
xspParentNode.appendChild(xspCurrentNode);
xspCurrentNode.appendChild(
document.createTextNode(countries[i].getCurrency())
);
xspCurrentNode = (Node) xspNodeStack.pop();
// </tr>
xspCurrentNode = (Node) xspNodeStack.pop();
}
</xsp:logic>
</table>
|  |  |  |  |
Note, however, that it is not allowed to nest
<xsp:expr> tags directly inside
<xsp:logic> . For an expression
to be inlined inside an <xsp:logic>
element, it must be escaped by surrounding it with an
<xsp:content> tag. Example:
 |  |  |  |
<xsp:logic>
for (int i = 0; i < parameterValues.length; i++) {
<xsp:content>
<xsp:expr>parameterValues[i]</xsp:expr>
</xsp:content>
<br/>
}
</xsp:logic>
|  |  |  |  |
The observant reader may have noticed a rather
unpleasant feature in the above code: the
less-than sign (< )
must be represented as < !
This is an undesirable (but unavoidable) consequence
of the < and &
characters being special to XML parsers.
A workaround is to escape code chunks contaning the
less-than (< ) and
ampersand (& ) characters as
CDATA sections:
 |  |  |  | <table>
<xsp:logic><![CDATA[
for (int i = 0; i < countries.length; i++)]]>
<tr>
. . .
</tr>
}
</table>
</xsp:logic> |  |  |  |  |
Caution must be exercised, though, to avoid enclosing
static markup inside the <![CDATA]]>
section, as this will result in syntax errors upon
compiling the generated Cocoon producer!
|
|
 |  |  |  | Another Example: Tag Libraries |  |  |  |  |
In the following example, a developer-supplied tag
library is used instead of embedding procedural code:
 |  |  |  | <?xml version="1.0"?>
<?cocoon-process type="xsp"?>
<?cocoon-process type="xslt"?>
<?xml-stylesheet href="sample.xsl" type="text/xsl"?>
<xsp:page
language="java"
xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:example="http://www.plenix.com/DTD/XSP/Example"
>
<page title="Time of Day">
<p>
To the best of my knowledge, it's now
<!-- Substitute time of day here -->
<example:time-of-day format="yy/MM/dd hh:mm:ss aa"/>
</p>
</page>
</xsp:page> |  |  |  |  |
Here, the web author is shielded from programming
complexities at the expense of additional developer
effort in devising and implementing a proper dynamic
tagset.
As mentioned early, this is the "ideal" XSP scenario:
dynamic content generation requirements are identified
before hand and represented in reusable tag libraries.
Web authors can then focus on their their "true"
purpose in life: producing content.
XSP uses XSLT stylesheets for source code generation.
Thus, each dynamic tag in a library is supported by an
XSLT template containing the program logic to be
generated. Upon execution, generated logic will yield
the dynamic content encoded by its underlying dynamic
tag.
Without yet delving into the finer details of the XSP
object model, let's see how the example tag
library stylesheet looks like:
 |  |  |  | <?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/XSL/Transform/1.0"
xmlns:xsp="http://www.apache.org/1999/XSP/Core"
xmlns:example="http://www.plenix.com/DTD/XSP/Example"
>
<xsl:template match="xsp:page">
<xsp:page>
<xsl:copy>
<xsl:apply-templates select="@*"/>
</xsl:copy>
<xsp:structure>
<xsp:include>java.util.Date</xsp:include>
<xsp:include>java.text.SimpleDateFormat</xsp:include>
</xsp:structure>
<xsp:logic>
/* "Example" Class Level Logic */
private static String formatDate(Date date, String pattern) {
if (pattern == null || pattern.length() == 0) {
pattern = "yyyy/MM/dd hh:mm:ss aa";
}
return (new SimpleDateFormat(pattern)).format(date);
}
</xsp:logic>
<xsl:apply-templates/>
</xsp:page>
</xsl:template>
<xsl:template match="example:time-of-day">
<xsp:expr>
formatDate(new Date(), "<xsl:value-of select="@format"/>")
</xsp:expr>
</xsl:template>
<xsl:template match="@*|node()" priority="-1">
<xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
</xsl:template>
</xsl:stylesheet> |  |  |  |  |
Let's focus our attention on 4 key features of
this library:
- Namespace
- Structure
- Class-level logic
- Tag code templates
The <xsp:structure> section provides
the context for program-level declarations such
as the Java import directive.
 |  |  |  | <xsp:structure>
<xsp:include>java.util.Date</xsp:include>
<xsp:include>java.text.SimpleDateFormat</xsp:include>
</xsp:structure> |  |  |  |  |
The <xsp:structure> tag may contain
zero or more <xsp:include> directives,
each specifying an external program module to be made
available to the generated XSP program.
Note that the exact semantics of this elments is
language-dependent. For Java, it corresponds to
an import statement. For Fesi
Javascript, for example, it may be interpreted as
a load() function call.
In our example, we're importing the
java.util.Date and
java.text.SimpleDateFormat
class definitions.
In all honesty, though, including
java.util.Date is redundant because
XSP always imports java.util.* . It's
included here only to emphasize that the
example library depends on it.
java.text.SimpleDateFormat , on the other
hand, must be included, because it's not among
XSP's "preferred" Java packages.
|
Finally, each dynamic tag must provide an associated
XSLT template that dictates what source code must be
generated whenever the tag is encountered in an XSP
page.
Note that the source code to be generated must be
enclosed in either an <xsp:expr>
or an <xsp:logic> element. This
is so because these XSP tags will be later evaluated
by the XSP built-in library (itself an XSLT stylesheet!)
 |  |  |  | <xsl:template match="example:time-of-day">
<xsp:expr>
formatDate(new Date(), "<xsl:value-of select="@format"/>")
</xsp:expr>
</xsl:template> |  |  |  |  |
In this example, each reference to a
<example:time-of-day> tag
will be replaced by a corresponding
<xsp:expr> "call". Thus,
if an XSP page using the example
library contains:
 |  |  |  | <example:time-of-day format="hh:mm:ss"/> |  |  |  |  |
XSP will expand this reference to:
 |  |  |  | <xsp:expr>formatDate(new Date(), "hh:mm:ss")</xsp:expr> |  |  |  |  |
during library processing. Upon source program
generation this directive will be finally
expanded
to:
 |  |  |  |
xspCurrentNode.appendChild(
xspExpr(
formatDate(new Date(), "hh:mm:ss"),
document
)
);
|  |  |  |  |
where the xspExpr() built-in method is
overloaded to wrap all possible Java types as a
Text Node.
Finicky developers may prefer to map dynamic tags to
bean method calls, as opposed to inlining
"raw" Java code.
While some may consider this a matter of taste, using
bean properties and methods is certainly
advisable because it isolates library code from
implementation details. This would allow, for instance,
for a complex Enterprise Java Bean to be modified
without impacting existing XSP pages.
|
|
-
<xsp:page>
This element is the root of all XSP pages and
specifies the scripting language and the tag
libraries used by a particular XSP page
-
<xsp:structure>
This top-level element encloses source
program-level declarations such as
<xsp:include>
-
<xsp:include>
This element is used to import external
module definitions in a language-dependent
way
-
<xsp:logic>
This element is used to embed procedural
logic in an XSP page. Enclosed code is
transcribed verbatim into the generated
XSPPage producer. Other
XSP or user markup may be nested inside
this tag
-
<xsp:content>
This element is used to embed "regular"
XML content inside an
<xsp:logic> block
so that no nested additional
<xsp:logic>
sections are required
-
<xsp:expr>
This element inlines a program
expression as a Text node,
except when used directly inside another
<xsp:> element,
where it is substituted as an
expression, not a node. If you
want to substitute an
<xsp:expr> tag
as a node inside another XSP
tag, you must enclose it in an
<xsp:content> element.
-
<xsp:element>
This tag is used to dynamically build
an element when its attribute values
are not known at compile time
-
<xsp:attribute>
This element is used to dynamically provide
attribute values for a given element (which
can be specified statically as markup or
dynamically by means of
<xsp:element> ).
This tag is typically used in conjunction
with <xsp:expr> ,
where the substituted expression is
always cast to String
-
<xsp:pi>
This element is used to dynamically create
a processing instruction.
-
<xsp:comment>
This element is used to dynamically create
an XML comment.
|
|
A processor is a Cocoon Java type that
takes a DOM tree as input and produces another
(possibly modified) DOM tree as output. This
concept is similar to that of a Unix "filter"
in a command pipeline.
The XSP engine is implemented as a Cocooon processor
that accepts an XSP page as input. The first time a
given XSP page is processed, it is translated into an
equivalent source program which is then compiled, loaded
and executed. Subsequent requests for the same XSP page
result in the execution of the generated program.
As you may expect, the output DOM tree returned by the
XSP engine processor is actually built by the generated
program.
XSP pages are compiled into Cocoon producers.
A producer is a Cocoon Java type normally used to
"feed" the initial XML content to the Cocoon processing
pipeline.
Thus, for example, when Cocoon serves a static, "regular"
XML document, file contents are actually delivered by
Cocoon's built-in FileProducer .
Whereas other related server pages technologies (such as
JSP) generate servlets, XSP generates producers
instead. This is so because, among other reasons,
the servlet model does not yet provide a mechanism for
portably and efficiently post-processing XML content.
|
XSP defines an abstract producer (XSPPage )
as the base class for generated programs.
This class exposes a simple object model that can
be easily used by XSP developers to programmatically
control how XML content is generated.
The following objects are accesible inside an XSP
page:
-
request .
A Cocoon-supplied wrapper to the standard
HttpServletRequest object.
This wrapper provides all the functionality
defined for its JSDK interface counterpart.
This object is typically used to retrieve
Http form parameters as well as to get
header and cookie information.
-
response .
A Cocoon-supplied wrapper to the standard
HttpServletResponse object.
This wrapper provides most of the
functionality defined for its JSDK
interface counterpart,
except for access to the
ServletOutputStream object
and its associated writer.
This restriction
is necessary to ensure consistent Cocoon
servlet output and does not impose any
limitation to XML processing. Other suitable,
non-output operations (such as setting headers,
cookies or content types) are allowed
-
session .
The standard HttpSession servlet
object. This object is typically used to
store data associated with a user HTTP session
-
servletContext .
The standard ServletContext
object. This object is typically used to
store application-level data by means
of its setAttribute and
getAttribute methods. Other
uses include determining the real path
of a URL as dictated by the underlying web
server virtual directory structure. Note
that the application-level data sharing
capabilities offered by this object are
available only for JSDK version 2.2 and
higher. To circumvent this for JSDK prior
to 2.2, use the xspGlobal
object (explained below)
-
document .
An XSP-supplied org.w3c.Document
object. Initially empty, this object is populated
by the generated XSPPage producer
and is typically used as a node factory
(document.createElement() ,
document.createProcessingInstruction() ,
etc)
-
xspGlobal .
An XSP-supplied global dictionary offering
the same setAttribute and
getAttribute services supported
by the standard ServletContext
object. This surrogate exists only to provide
application-level data sharing for older
servlet engines (prior to 2.2). Note that,
while providing a means for application-level
data sharing for older servlet engines,
such global data cannot be shared with non-Cocoon
servlets or JSP pages. For this purpose, use
the standard servletContext object.
xspGlobal may be deprecated in future
XSP versions
-
xspNodeStack .
A java.util.Stack used to control
element nesting in the XSP page Document
object. Extreme caution must be exercised in
using this object as all DOM manipulations
take effect on its top element.
-
xspCurrentNode .
An org.w3c.Node object corresponding
to the node being currently populated
-
xspParentNode .
An org.w3c.Node object corresponding
to the parent of the node being currently
populated. This object is normally the top element
of the xspNodeStack
-
xspParser .
A Cocoon-supplied DOM parser which may be used
to create new documents and parse external
XML documents
In addition to the above objects, the XSPPage
contains an xspExpr() method than can be
used to wrap any Java value as an org.w3c.Text
object. Example:
 |  |  |  | xspCurrentElement.appendChild(
document.createTextNode("It's now ")
);
xspCurrentElement.appendChild(
xspExpr(new Date())
);
|  |  |  |  |
|
In addition to the above infrastructure objects and methods,
XSP provides a utility class offering a number of DOM, HTTP
and file manipulation services implemented as public
static methods:
-
Node cloneNode(Node node, Document factory) .
This method performs a deep copy of its node
argument using the factory document as the
creator for the copied nodes. This is typically used to
embed external XML documents into the XSP page
document object. This is required because
DOM Level 1 parsers do not allow for a node to be
appended as child to another node if the two belong
to different document instances
-
String toMarkup(Node node) .
This method generates an indented String representation
of its node argument. This is typically
used to embed a textual, non-DOM representation of
external XML files as well as for debugging purposes
|
-
String encodeMarkup(String string) .
This method scans its string argument
replacing occurrences of markup delimiters as
follows:
1) < is replaced by <
2) > is replaced by >
3) & is replaced by &
-
String formEncode(String text) .
This method converts its text argument
to x-www-form-urlencode format, as required
by HTTP form query strings. This method is actually
a wrapper for java.net.URLEncoder.encode
-
String formDecode(String text) .
This method converts its text argument
from x-www-form-urlencode format to a
String representation. This method is
actually a wrapper for
java.net.URLDecoder.decode (introduced
in Java2) and exists solely to provide this
functionality for JDK1.1
|
-
String pathComponent(String filename) .
This method interprets its filename
argument as a file name and removes the last
component to yield only the directory path
information.
This conversion is performed in an operating
system-independent manner
-
String fileComponent(String filename) .
This method interprets its filename
argument as a file name and removes the leading
path component to yield only the file name
portion, including the file name extension,
if any.
This conversion is performed in an operating
system-independent manner
-
String baseName(String filename) .
This method interprets its filename
argument as a file name and removes the leading
path component to yield only the base file name
excluding the last dot file
extension, if any.
This conversion is performed in an operating
system-independent manner
-
String baseName(String filename, String suffix) .
This method interprets its filename
argument as a file name and removes the leading
path component to yield only the base file name
excluding the last occurerence of the
extension given by its
suffix argument.
This conversion is performed in an operating
system-independent manner
-
String normalizedBaseName(String filename) .
This method interprets its filename
argument as a file name and removes the leading
path component to yield only the base file name
excluding the last dot file
extension, if any. The resulting filename is
then scanned for non-alphanumeric characters
which are replaced by underscore
(_ ). An underscore is also
preprended to each directory component.
This is used to map file names to valid Java
identifiers.
This conversion is performed in an operating
system-independent manner
-
String relativeFilename(String filename, HttpServletRequest request, ServletContext context) .
This method is used to build a fully qualified pathname
for a filename relative to the request
URI. This is typically used to open operating system files
given a name relative to the request's virtual path
-
String relativeFilename(String filename, HttpServletRequest request) .
This method is used to build a fully qualified pathname
for a filename relative to the request
URI. This is typically used to open operating system files
given a name relative to the request's virtual path. This
variant depends on the deprecated
HttpServletRequest.getRealPath method and
exists only for compatibility with older JSDK's in which
the ServletContext object did not provide
a getRealPath() method
|
-
String[] split(String line) .
This method converts its line string
argument to a String array using
whitespace (blanks, tabs, carriage returns and
newlines) as field separators
-
String[] split(String line, String delimiter) .
This method converts its line string
argument to a String array using
the characters in its delimiter
argument as field separators
-
boolean isAlphaNumeric(char c) .
This method tests its c argument to
assert whether it is an underscore, a lowercase or
uppercase letter or a digit
|
|
|
 |  |  |  | Other Scripting Languages |  |  |  |  |
XSP has been designed to support other scripting
languages, in addition to Java. In principle, any
programming language for which a Java-based
interpreter exists could be used to script
XSP pages.
In general, languages supporting compilation to
bytecodes, such as Java itself, Netscape's
Rhino Javascript
or IBM's
NetRexx,
perform significantly better than interpreted
languages like
FESI Javascript
or
WebL.
Interpreted languages, however, are expected to
play an important role in XSP scripting.
This is so because (in addition to their typical
ease of use) more and more field-specialized
languages are being developed for the JVM.
Such specialized scripting languages usually
offer more expressive power and conciseness
than strictly object-oriented languages.
For a fairly complete list of such languages
see
Programming Languages for the Java Virtual Machine.
Until recently, Java lacked a well-defined
scripting architecture that allowed applications
to incorporate scripting easily.
IBM's
Bean Scripting Framework
(BSF) is an Alphaworks project providing such architecture.
BSF supports a growing number of
scripting languages including
Rhino,
NetRexx,
Jacl
and
JPython.
On the Windows platform,
BSF also supports
JScript,
VBScript,
and
PerlScript.
Currently, XSP only supports Java as a scripting language,
though its design allows for an easy integration of
other languages.
In the near future, BSF will be integrated to XSP's own
language abstraction mechanism so that most (if not all)
of the above mentioned languages may become available
for XSP scripting.
XSP tag libraries are language-dependent. Thus, the
addition of a new language involves (at the very
least) porting the XSP built-in libraries. An XSP library is
composed of a code-generation XSLT sylesheet and an
optional library preprocessor class.
A library's code-generation stylesheet is simply a
"regular" XSLT stylesheet responsible for translating
user-defined dynamic tags into equivalent XSP tags
for a given scripting language (sometimes called logicsheet).
While a preprocessor is an optional library Java class
used to augment an XSP page DOM tree prior to
applying the library code-generation stylesheet.
Library preprocessors may be
language-dependent. Early experience
suggests, though, that the same preprocessor
class will be typically shared by different
languages for the same dynamic tagset.
XSLT extension functions and
extension elements may replace
library preprocessors in the future. In
this case, all code-generation logic would
reside in the library's stylesheet.
At this early development stage, we still don't know how the
implementation will evolve and we look forward for public feedback
before going any further along with the implementation details.
|
This initial implementation of XSP has
been developed by
Exolab
and is being donated to the
Apache project as part of Exoffice's
commitment to the open source movement.
Special thanks must be given to
Ismael Ghalimi
for sponsoring and supporting XSP development.
Last, but not least, cheers to
Stefano Mazzocchi,
founder and leader of the Cocoon project and original author of
the XSP specification. Thanks!!!
|
|
|