LotusXSL Extensibility Architecture

Sanjiva Weerawarana (sanjiva@watson.ibm.com)
Scott Boag (scott_boag@lotus.com)

Introduction

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.

LotusXSL Component Syntax

<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>

DTD for LotusXSLT Components

<!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>

Semantics of XSLT Extensions

  1. When the extension-element-prefixes attribute is seen on the stylesheet element, the components to handle those extension namespaces are searched for and installed in the processor. The components are location algorithm is as follows:
    1. First, components embedded within the current stylesheet itself are searched for: for each extension namespace, an <lxslt:component> element with a prefix attribute whose value matches the extension namespace prefix is searched for. If none is found, then proceed to step 2.
    2. 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 the following component description is assumed:
        <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.

    3. The URI of the namespace is taken as a URL and an attempt is made to load the component description form this URL. If found and if the lxslt:component has a "namespace-uri" atttribute present, then it must match with the namespace URI of the extension specified in the stylesheet. If no "namespace-uri" attribute is present then its assumed to be the component specification for this namespace. [NOT FULLY IMPLEMENTED YET - NAMESPACE USAGE ISN'T CORRECT.]
  2. The component specification identifies the valid local parts of the extension namespace. The element and function local parts are identified separately using the elements and functions attributes, respectively. The component specification presents the code that implements these local parts using a <lxslt:script> element. The script element can inline the code or refer to an external source for the code using the src attribute.
  3. The currently supported languages are: "javaclass" and "javascript", where "javaclass" is used to work with an implementation in Java and "javascript" refers to Netscape Rhino.
  4. Special case for lang="javaclass": The src attribute is treated as the fully qualified classname of the class which handles the extension namespace. If the name starts with "class:", then the class is not instantiated and the methods invoked to handle various local parts (see below) are assumed to be static. Otherwise, a new instance is created and used for invoking the appropriate methods as defined below.
  5. The src attribute is not yet implemented for languages other than "javaclass".
  6. For each extension element within the namespace, a function with the following signature must be defined in the implementation of the component:
      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.

  7. When an extension element is invoked, the appropriate function (as identified above) is invoked to handle the invocation. The entire extension element is given as the second argument to the function with content, if any, as-is. Thus, if within an extension element one were to place elements from the xsl: namespace, then those would have to processed by an explicit evaluation request from the extension element handler. Such invocations are done via the XSLProcessorContext object. [CALLING BACK INTO XSLProcessor.java NOT IMPLEMENTED YET.]
  8. For each extension function within the namespace, a function with the following signature must be defined in the implementation of the component:
      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.

  9. When an extension function is invoked, the appropriate function (as identified above) is invoked to handle the invocation. Any arguments to the function are first evaluated and then the resulting values are passed to the function by first converting them according to the table listed above.
  10. <xsl:fallback> is not yet implemented.

Special Handling for XPath Extensions into Java

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.

Predefined Extension Namespace for Java Access

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.

Example 1: Named Counter Component in Java

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>

Example 2: Named Counter Component Implemented Using Java XPath Extension Namespace

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.]