Making my own XSLT transformer

I’ve tried to create my own xslt transformer that’s more lightweight than the built in service and that is also automatically embed the stringtobytes/bytetostring function.

Everything went fine until I tried a XSL file with Java extension, like :

<xsl:stylesheet xmlns:xsl = “http://www.w3.org/1999/XSL/Transform
xmlns:xalan = “http://xml.apache.org/xalan
xmlns:Output = “java://com.wm.pkg.xslt.extension.Output”
exclude-result-prefixes = “xalan xsl Output” version = “1.0”>

And then somewhere in the XML (just an exemple from an actual file, it runs OK in the built in transform service):
<xsl:value-of select = “Output:put($output, ‘mail’, string($mail))”/>

I get the exception:
"
java.lang.NoSuchMethodException: For extension function, could not find method java.lang.String.put([ExpressionContext,] #STRING, #STRING).
"

This is the java code I made so far:

//get input var
IDataHashCursor idc = pipeline.getHashCursor(); 

idc.first( "stylesheetSystemId" );
String stylesheetSystemId = (String)idc.getValue();

idc.first( "document" );
String document = (String)idc.getValue();

idc.first( "encoding" );
String encoding = (String)idc.getValue();

idc.first( "xslParamInput" );
IData xslParamInput = (IData)idc.getValue();
IDataHashCursor idcParamInput = null;
if(xslParamInput != null)
{
    idcParamInput = xslParamInput.getHashCursor(); 
}

try{

//transform input string into bytes and then into InputStream and then into Source
byte[] documentBytes = document.getBytes(encoding);
java.io.ByteArrayInputStream documentInputStream = new java.io.ByteArrayInputStream(documentBytes);
javax.xml.transform.stream.StreamSource documentSource = new javax.xml.transform.stream.StreamSource(documentInputStream);

//create the object that will hold the output
java.io.CharArrayWriter transformedDocWriter = new java.io.CharArrayWriter();
javax.xml.transform.stream.StreamResult transformedDocResult = new javax.xml.transform.stream.StreamResult(transformedDocWriter);

//create the XSL factory
javax.xml.transform.TransformerFactory fabrique = javax.xml.transform.TransformerFactory.newInstance();

//create the XSL template file
java.io.File stylesheet = new java.io.File(stylesheetSystemId);
javax.xml.transform.stream.StreamSource stylesource = new javax.xml.transform.stream.StreamSource(stylesheet);



javax.xml.transform.Templates template = fabrique.newTemplates(stylesource);


//get the transformer for this template
javax.xml.transform.Transformer transformer = template.newTransformer();
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, encoding);

//set input parameters (if applicable)
if(idcParamInput != null)
{
    idcParamInput.first();
    do
    {
        transformer.setParameter(idcParamInput.getKey(), idcParamInput.getValue());
    }
    while(idcParamInput.next());
    
    //destory the cursor, now useless:
    idcParamInput.destroy();
}

//transform
transformer.transform(documentSource, transformedDocResult);

//get output parameters (if any); create a webMethods document
//and set it in the pipeline
java.util.Properties p = transformer.getOutputProperties();
if(p!=null)
{
    java.util.Enumeration keys = p.keys();
    while (keys.hasMoreElements()) {
          Object key = keys.nextElement();

        //just for test we insert the key of the output object in the pipeline
        idc.first();
        idc.insertAfter("transformedDocument", (String)key);
    }
}

//access the result
String transformedDocument = ((java.io.CharArrayWriter)transformedDocResult.getWriter()).toString();
//set it in the pipeline
idc.first();
idc.insertAfter("transformedDocument", transformedDocument);

}
catch(Exception e){

idc.first();
idc.insertAfter("output", e.getMessage());

}

//destroy the pipeline cursor
idc.destroy();

Does someone know what the cause is?

Thanks!

Have you considered creating a wrapper service in FLOW? If what you’re after is a service that accepts and returns a string form of the input and output documents, you can easily create a service to accept the same parms as what you have above and do something like this:

stringToBytes
transformSerialXML
bytesToString

Is there more that you want/need to do? I offer this as an alternative because you’re replicating what transformSerialXML does, just with slightly different inputs and outputs.

That’s true. Well in fact I wanted to do this and then benchmark the 2 solutions.
There has to be a way to do this in Java anyway; so I’ll be glad to make this stuff work.

There are a couple of issues, which are obscured by the fact that the samples in the WmXSLT package work.

To help explain, we’ll look at two lines from the dateInt.xsl sample found in pub/samples/xdocs within the WmXSLT package.

xmlns:Output = “java://com.wm.pkg.xslt.extension.Output”

The line above declares a namespace and identifies the fully-qualified name of the Java class to use. Alas, in this case, this class does not exist. The sample still works, however, because the classname is never referenced within the XSL to create a new “Output” object. This leads us to the second line of interest.

xsl:value-of select=“Output:put($output, ‘year’, $year)”

This indicates to invoke the ‘put’ method on the $output object instance. The sample doesn’t create an instance of the Output class to assign to $output so how does this work?

It works because transformSerialXML creates an instance of com.wm.pkg.xslt.extension.OutputMap and sets the “output” parameter (declared in the XSL but never initialized) by calling transformer.setParameter.

The $output object is thus initialized with an OutputMap object. This class, which extends java.util.HashMap, has a put method. The XSL processor can successfully invoke the method (via reflection).

Lastly, after the transform method is called, transformSerialXML iterates over the output object that it passed to the transform and puts each output key/value pair into the pipeline under the xslParamOutput document.

Below is the Java code modified to create and process the output parameter.

[highlight=java]
IDataHashCursor idc = pipeline.getHashCursor();
idc.first( “stylesheetSystemId” );
String stylesheetSystemId = (String)idc.getValue();
idc.first( “document” );
String document = (String)idc.getValue();
idc.first( “encoding” );
String encoding = (String)idc.getValue();
idc.first( “xslParamInput” );
IData xslParamInput = (IData)idc.getValue();
IDataHashCursor idcParamInput = null;
if(xslParamInput != null)
{
idcParamInput = xslParamInput.getHashCursor();
}
try{
//transform input string into bytes and then into InputStream and then into Source
byte[] documentBytes = document.getBytes(encoding);
java.io.ByteArrayInputStream documentInputStream = new java.io.ByteArrayInputStream(documentBytes);
javax.xml.transform.stream.StreamSource documentSource = new javax.xml.transform.stream.StreamSource(documentInputStream);
//create the object that will hold the output
java.io.CharArrayWriter transformedDocWriter = new java.io.CharArrayWriter();
javax.xml.transform.stream.StreamResult transformedDocResult = new javax.xml.transform.stream.StreamResult(transformedDocWriter);

//create the XSL factory
javax.xml.transform.TransformerFactory fabrique = javax.xml.transform.TransformerFactory.newInstance();

//create the XSL template file
java.io.File stylesheet = new java.io.File(stylesheetSystemId);
javax.xml.transform.stream.StreamSource stylesource = new javax.xml.transform.stream.StreamSource(stylesheet);

javax.xml.transform.Templates template = fabrique.newTemplates(stylesource);

//get the transformer for this template
javax.xml.transform.Transformer transformer = template.newTransformer();
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, “yes”);
transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, encoding);
//set input parameters (if applicable)
if(idcParamInput != null)
{
idcParamInput.first();
do
{
transformer.setParameter(idcParamInput.getKey(), idcParamInput.getValue());
}
while(idcParamInput.next());

//destory the cursor, now useless:
idcParamInput.destroy();

}

// The next 2 lines are new
java.util.HashMap output = new java.util.HashMap();
transformer.setParameter(“output”, output);

//transform
transformer.transform(documentSource, transformedDocResult);

// This output handling block is new
if(!output.isEmpty())
{
IData xslParamOutput = IDataFactory.create();
IDataCursor paramCursor = xslParamOutput.getCursor();
java.util.Iterator keys = output.keySet().iterator();
Object name = null;
Object value = null;
for(; keys.hasNext(); paramCursor.insertAfter((String)name, value))
{
name = keys.next();
value = output.get(name);
}
idc.insertAfter(“xslParamOutput”, xslParamOutput);
}

//access the result
String transformedDocument = ((java.io.CharArrayWriter)transformedDocResult.getWriter()).toString();
//set it in the pipeline
idc.first();
idc.insertAfter(“transformedDocument”, transformedDocument);
}
catch(Exception e){
idc.first();
idc.insertAfter(“output”, e.getMessage());
}
//destroy the pipeline cursor
idc.destroy();[/highlight]
Now that we’ve explored how to make it work, can I talk you out of doing this? :slight_smile:

Reimplementing transformSerialXML in your own Java service seems really unnecessary. It isn’t any more “lightweight”–it’s doing the same thing with the same XSLT engine within IS. Working with strings as input and output may be more convenient, but that can be achieved as described above with a simple FLOW wrapper.

Are there other goals you’re looking to achieve?

Wow, thank you for your input. I would have never figured this out by myself.

Yes it is unnecessary; it was just for the sake it ;o
I’m running a little benchmark right now, I’ll post the results later.

I see. A rolling up of the sleeves to figure out what’s going on. Cool.

I forgot to mention that when modifying your code, why I didn’t use OutputMap and instead used HashMap. It’s because OutputMap isn’t visible outside of the WmXSLT package. Also, it isn’t documented and isn’t intended for our use. It’s a bummer that the sample had an error in the class reference in the ns decl. It should have been IOutputMap, not Output.