2

I am posting to ask for help in getting my Saxon Extension functions to work in Oxygen XML Editor. I have created Java classes for the function definition, the function call, as well as an initializer class. I have the definition and function call in one JAR file and the initializer in a separate JAR file. I have placed the JAR files and a ‘lib’ folder containing all required libraries in the directory that the XSL is stored in:

Project folder

I have listed them in the ‘Extensions’ dialog of the Transformation Scenario window:

Extension libraries

And I have attempted to list the initializer:

Initializer

When I select the “Choose” button and then the “Detect” button on the following dialog, it does not detect the class:

Not detecting

When I attempt to transform I receive these problem messages:

Errors

Here is the code to the ExtensionDefinition class:

import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.value.SequenceType;

/**
 *
 * @author tfurst
 */
public class CreateXmlMapDefinition extends ExtensionFunctionDefinition{

    @Override
    public StructuredQName getFunctionQName() {
        return new StructuredQName("rcmt", "http://rcmt.com/saxon-extension", "create-map");
    }

    @Override
    public SequenceType[] getArgumentTypes() {
        return new SequenceType[]{SequenceType.SINGLE_STRING};
    }

    @Override
    public SequenceType getResultType(SequenceType[] sts) {
        return SequenceType.ANY_SEQUENCE;
    }

    @Override
    public ExtensionFunctionCall makeCallExpression() {
        return new CreateXmlExtension();
    }

}

Here is the code to the function call:

import com.rcmt.mapProcessor.ExcelMapProcessor;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.om.DocumentInfo;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.trans.XPathException;
import org.w3c.dom.Document;


public class CreateXmlExtension extends ExtensionFunctionCall{

    @Override
    public Sequence call(XPathContext xpc, Sequence[] args) throws XPathException
    {
        String excelPath = args[0].head().getStringValue();
        DocumentInfo doc = getXml(xpc, excelPath);
        return doc;
    }


    private DocumentInfo getXml(XPathContext context, String path)
    {
        DocumentInfo xml = null;
        try
        {
            Document d = ExcelMapProcessor.makeXml(path);
            xml = (DocumentInfo)context.getConfiguration().buildDocumentTree(makeSource(d));

        }
        catch (XPathException ex)
        {
            Logger.getLogger(CreateXmlExtension.class.getName()).log(Level.SEVERE, null, ex);
        }
        return xml;
    }

    private Source makeSource(Document doc)
    {
        Source result = null;
        try
        {
            TransformerFactory transformerFactory = new net.sf.saxon.TransformerFactoryImpl();

            Transformer trans = transformerFactory.newTransformer();
            DOMSource src = new DOMSource(doc);

            StreamResult res = new StreamResult(new ByteArrayOutputStream ());
            trans.transform(src, res);
            ByteArrayOutputStream so = (ByteArrayOutputStream)res.getOutputStream();
            result = new StreamSource(new ByteArrayInputStream(so.toByteArray()));

            so.close();
        }
        catch (TransformerException | IOException ex)
        {
            Logger.getLogger(CreateXmlExtension.class.getName()).log(Level.SEVERE, null, ex);
        }
        return result;
    }
}

This the code to my initializer:

import javax.xml.transform.TransformerException;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.Initializer;
import net.sf.saxon.om.NamePool;
import org.expath.httpclient.saxon.SendRequestFunction;
import com.rcmt.map.CreateXmlMapDefinition;


/**
 *
 * @author tfurst
 */
public class SaxonExtInitializer implements Initializer{

    @Override
    public void initialize(Configuration config) throws TransformerException {
        config.registerExtensionFunction(new CreateXmlMapDefinition());
        SendRequestFunction expathHttpClient = new SendRequestFunction();
        config.setNamePool(new NamePool());
        expathHttpClient.setConfiguration(config);
        config.registerExtensionFunction(expathHttpClient);
    }

}

And finally, the usage in the XSL:

XSL Function Call

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
twfurst
  • 165
  • 2
  • 13
  • You might get a better and more qualified answer by directly contacting oXygen support. At least add a tag for oXygen. The docs https://www.oxygenxml.com/doc/versions/22.1/ug-editor/topics/saxon_extension_functions-x1.html?hl=using%2Csaxon%2Cintegrated%2Cextension%2Cfunctions suggest the jar for the extension needs "Add a file called net.sf.saxon.lib.ExtensionFunctionDefinition that contains the fully qualified name of the Java class in the META-INF/services/ folder of the JAR file" – Martin Honnen Sep 24 '20 at 13:05
  • I sent them an email yesterday afternoon (US East Coast time). Patiently awaiting a response. I have read that line a number of times and I find it kind of confusing, seems like quite a strange name for a JAR file. – twfurst Sep 24 '20 at 13:18

1 Answers1

2

If you have a JAR file containing the ExtensionFunctionDefinition and ExtensionFunctionCall of your extension function(s), then, in that JAR file, in the META-INF folder, create a subfolder named services and inside of that folder create a text file named net.sf.saxon.lib.ExtensionFunctionDefinition that on each line lists the relevant classes, e.g., CreateXmlMapDefinition (prefixed by any namespace it's in).

It looks like this:

Enter image description here

Then provide that JAR file as an extension in the transformation scenario dialogue. You don't need any initializer that way. I have tested that Oxygen 22.1 and Saxon 9.9.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • I will review my JARs and attempt again. I am on oXygen 19.1 and Saxon 9.7.0.19. Should any libraries required by the function call (ex. POI as I making an XML file from an Excel spreadsheet) be in a "lib" folder as I have them now? Or should they be included in side the jar file? – twfurst Sep 24 '20 at 14:59
  • I have lost the one error related the initializer, but am still left with the "Cannot find a matching 1-argument function named {http://rcmt.com/saxon-extension}create-map()" error. My jar is constructed as you show above, just with my classes and folder structure. Could it be due to the fact that in my classes I refer to the saxon9he.jar, and in oXygen it is using Saxon PE? – twfurst Sep 24 '20 at 15:28
  • @twfurst, I can't tell what goes wrong, but I do not even know whether the approach I have tested works also with oXygen 19.1. I guess to start checking where things fail I would start with a simple setup not using different Saxon editions, then, once I have a way to have oXygen find my extension, I would start checking whether I can mix editions. Let's see whether the oXygen support can help you or someone here sees the oXygen tag and has some idea. – Martin Honnen Sep 24 '20 at 17:37
  • 2
    I received a response from Oxygen support and they indicated that it could be failing due to a mismatch between the Saxon compiled with and the Saxon in use by Oxygen. I will re-compile using the jar from the Oxygen lib folder to test. They also supplied a sample project with test extension and it works fine. – twfurst Sep 25 '20 at 12:36