XSLT's extension architecture consists of two parts: within XPath any function name which is a qualified name is considered to be a call to an extension function. Within XSLT templates, certain literal result namespaces may be identified as being a call to an extension elements. The XSLT specification does not however define how either of these extension mechanisms' semantics.
LotusXSL supports both these extension mechanisms. For XPath extensions, a special extension namespace for interacting with Java objects is also provided. This namespace is described later in this document.
LotusXSL's extension architecture considers extension functions and extension elements as being two mechanisms for interacting with an implementation of an extension namespace handling object. That is, we consider an extension namespace in XSLT as providing a set of elements and functions to the stylesheet author.
LotusXSL implements an XSLT extension namespace using an LotusXSLT Component. LotusXSLT components may be embedded directly within the stylesheet itself or maintained separately. A component provides program code that implements the functions that will be invoked by the XPath and XSLT processors to perform the action requested by the user as a function or an element, respectively.
For extension namespaces implemented using Java, a special convenience is supported which allows the programmer to skip the component specification. This is described later in the semantics of how a component description is located and loaded.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0" xmlns:lxslt="http://xsl.lotus.com/" xmlns:extn1="URI of extension namespace1" extension-element-prefixes="extn1 extn2"> <xsl:template match="..."> ... <extn1:element1 att1="value1" att2="value2" ...> <whatever-the-contents/> <including-xsl-elements/> </extn1> ... <xsl:value-of select="extn1:function1(arg1,arg2,...,argn)"/> ... </xsl:template> <lxslt:component prefix="extn1" elements="element1" functions="function1"> <lxslt:script lang="javascript"> function element1 (xslProcContext, exensionElement) { code to do element1, including possibly evaluating the contents of the element by calling back to the processor } function function1 (arg1, arg2, ..., argn) { code to do function1 } </lxslt:script> </lxslt:component> </xsl:stylesheet>
<!ELEMENT lxslt:component (lxslt:script)> <!ATTLIST lxslt:component prefix CDATA #IMPLIED namespace-uri CDATA #IMPLIED elements NMTOKENS #REQUIRED functions NMTOKENS #REQUIRED> <!ELEMENT lxslt:script EMPTY)> <!ATTLIST lxslt:script lang CDATA #REQUIRED src CDATA #IMPLIED>
<lxslt:component prefix="prefix-being-searched-for"> <lxslt:script lang="javaclass" src="namespaceURI"/> </lxslt:component>
That is, a component implementation in Java with a source URL value of the namespace URI (taken as the fully qualified class name) is assumed. No elements and functions are defined - which means that if one were to test this namespace (via extension-element-available or extension-function-available) then the tests would fail. However, if a function or element is invoked on this extension namespace then they would succeed or fail based on whether the requisite functions (as defined below) were defined by the class or not.
Object <localPart> (com.lotus.xsl.XSLProcessorContext, org.w3c.dom.Element extensionElement)
where <localPart> is the local part of the extension element name. If the component is implemented in a loosely typed scripting language such as JavaScript, then these are untyped arguments to the function and the return type is unspecified as well. Note that for lang=="javaclass", methods may be static or not-static and must be public in order to be accessible.
Object <localPart> (Type1 arg1, Type2 arg2, ..., Typen argn)
where <localPart> is the local part of the extension function name. The type of each argument depends on what was given within XSLT in the invocation of the function. Here is the mapping from an XSLT data type to the Java data types that are used in invoking the methods:
XSLT Type | Java Type |
Node-Set | org.w3c.dom.NodeList |
String | java.lang.String |
Boolean | boolean or Boolean |
Number | double or Double |
Result Tree Fragment | org.w3c.dom.DocumentFragment |
variable | one of above based on type of variable's value |
For boolean and number XSLT types, first all such types are converted to the corresponding primitive type in Java and the method searched for. If the method is not found, then a method with the object types is searched for. For example foo:bar('a'='b', 1, 'Hello', 2) would first look for a method bar(boolean,double,String,boolean) and then for bar(Boolean,Double,String,Double).
If the component is implemented in a loosely typed scripting language such as JavaScript, then these are untyped arguments to the function and the return type is unspecified as well.
When a qualified function name is invoked in XPath, if the namespace is not already registered as an extension namespace (by the above mechanism), then the following is done:
The URI of the namespace is taken as a fully qualified class name and an attempt is made to load that class. If the URI starts with "class:", that part is first stripped before attempting to load the class. If class loading succeeds, then an extension namespace implemented in "javaclass" is assumed with the URI as the value of the src attribute. Note that no functions are defined - which means that if one were to test this namespace (via extension-function-available) then the tests would fail. However, if a function is invoked on this extension namespace then they would succeed or fail based on whether the requisite functions (as defined above) were defined by the class or not.
LotusXSL supports a special namespace for convenient interaction with Java from XPath. The special Java namespace's URI is "http://xsl.lotus.com/java". This namespace allows the stylesheet author to create new instances of Java classes and to invoke static and non-static methods on them. Assuming, the namespace declaration "xmlns:java='http://xsl.lotus.com/java'" is in scope, the following are supported:
java:FQCN.new (args)
where FQCN is the fully qualified class name of which a new instance is to be created with the given constructor arguments (if any).
java:FQCN.methodName (args)
where FQCN is the fully qualified class name whose static method "methodName" is to be invoked using the given arguments.
java:methodName (object, args)
where methodName is the name of the (static or non-static) method to invoke on object with the given arguments.
MyCounter.java:
import java.util.*; public class MyCounter { Hashtable counters = new Hashtable (); public MyCounter () { } // the context arg is actually a com.lotus.xsl.XSLProcessorContext public void init (Object context, org.w3c.dom.Element elem) { String name = elem.getAttribute ("name"); String value = elem.getAttribute ("value"); int val; try { val = Integer.parseInt (value); } catch (NumberFormatException e) { e.printStackTrace (); val = 0; } counters.put (name, new Integer (val)); } public int read (String name) { Integer cval = (Integer) counters.get (name); return (cval == null) ? 0 : cval.intValue (); } public void incr(Object context, org.w3c.dom.Element elem) { String name = elem.getAttribute ("name"); Integer cval = (Integer) counters.get (name); int nval = (cval == null) ? 0 : (cval.intValue () + 1); counters.put (name, new Integer (nval)); } }
MyFamily.xml:
<?xml version="1.0"?> <doc> <name first="Sanjiva" last="Weerawarana"/> <name first="Shahani" last="Weerawarana"/> <name first="Rukmal" last="Weerawarana"/> <name first="Sashi" last="Weerawarana"/> <name first="Kamal" last="Fernando"/> <name first="Ruby" last="Fernando"/> </doc>
Counter.xsl:
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0" xmlns:lxslt="http://xsl.lotus.com/" xmlns:counter="MyCounter" extension-element-prefixes="counter"> <!-- note that the component definition is optional because I used the fully qualified class name of the implementation as the URI for this namespace. --> <lxslt:component prefix="counter" elements="init incr" functions="read"> <lxslt:script lang="javaclass" src="MyCounter"/> </lxslt:component> <xsl:template match="/"> <HTML> <H1>Test for namespace specified Java extension.</H1> <counter:init name="index" value="1"/> <p>Here are the names in alphabetical order by last name:</p> <xsl:for-each select="doc/name"> <xsl:sort select="@last"/> <xsl:sort select="@first"/> <p> <xsl:text>[</xsl:text> <xsl:value-of select="counter:read('index')"/> <xsl:text>]. </xsl:text> <xsl:value-of select="@last"/> <xsl:text>, </xsl:text> <xsl:value-of select="@first"/> </p> <counter:incr name="index"/> </xsl:for-each> </HTML> </xsl:template> </xsl:stylesheet>
Output:
<HTML> <H1>Test for namespace specified Java extension.</H1> <p>Here are the names in alphabetical order by last name, first name:</p> <p>[1]. Fernando, Kamal</p> <p>[2]. Fernando, Ruby</p> <p>[3]. Weerawarana, Rukmal</p> <p>[4]. Weerawarana, Sanjiva</p> <p>[5]. Weerawarana, Sashi</p> <p>[6]. Weerawarana, Shahani</p> </HTML>
Counter.xsl:
<?xml version="1.0"?> <!-- named counter functionality done using java: --> <xsl:stylesheet xmlns:xsl="http://www.w3.org/XSL/Transform/1.0" xmlns:java="http://xsl.lotus.com/java"> <xsl:template match="/"> <HTML> <H1>Test for namespace specified Java extension.</H1> <xsl:variable name="counter-table" select="java:java.util.Hashtable.new ()"/> <!-- do following for side-effect --> <xsl:if test="java:put ($counter-table, 'index', 1)"/> <p>Here are the names in alphabetical order by last name, first name:</p> <xsl:for-each select="doc/name"> <xsl:sort select="@last"/> <xsl:sort select="@first"/> <p> <xsl:text>[</xsl:text> <xsl:value-of select="java:get ($counter-table, 'index')"/> <xsl:text>]. </xsl:text> <xsl:value-of select="@last"/> <xsl:text>, </xsl:text> <xsl:value-of select="@first"/> </p> <!-- do following for side-effect --> <xsl:if test="java:put ($counter-table, 'index', java:get($counter-table,'index')+1)"/> </xsl:for-each> </HTML> </xsl:template> </xsl:stylesheet>
Produces the same results.
[More examples are needed.]