On this page we will discuss, how to write implementations of XPath functions and XProc steps in Java and how to supply them to MorganaXProc. The following instructions apply to MorganaXProc from version 1.0.3 on.

Implementing XPath functions in Java:

The first step to implement an XPath function in Java is to write a Java class implementing the interface "com.xml_project.morganaxpath.functions.XPathFunction". Using a typical "hello world"-example, we will discuss a function, taking a string (xs:string) as a parameter and returning another string prefixing the parameter's value with the string "hello ". Here is the code:

public class GreetFunction implements XPathFunction{

/* Define the name of the implemented functions */
function QName getName(){return new QName("my-namespace", "greet");}

/* Define number of parameters, relative to XPath version (currently only 2.0) */
public boolean acceptsArity(int arity, float xpathVersion) {return arity == 1;}

/* Define the type via XPath sequence type syntax for each parameter */
public SequenceType[] getParameterDefinitions(){

return SequenceType[]{

SequenceType.makeSequenceType(XS_String.class)

};

}
/* Define the result type as XPath sequence type */
public SequenceType getResultType(List<SequenceType> parameter){

return SequenceType.STRING_ONE;

}
/* Now the function's action: */
public XdmSequence call(List<XdmSequence> arguments, DynamicXQueryContext context) throws XPathException {

return new XS_String("Hello "+ arguments.get(0).first().getStringValue());

}

}

Before you shake your had: The above code is for demonstration purposes only and does not seek any efficiency. To make the function faster and avoid wasting memory, one will declare the function's name, the parameter definition and the return type as static fields of the class and just return the values in the functions.

The second step now is to collect all functions share the same namespace in one class which then can be supplied to MorganaXProc. The relevant interface here is com.xml_project.morganaxproc.extensions.FunctionPackage. This interface has only two methods:

public String getNamespaceURI();
public List<XPathFunction> getFunctions();

Here you just have to return the namespace uri of all functions in the package and a list of the functions themselves. Please make sure the namespace uri returned by getNamespaceURI() is the same as the namespace uri used in the QNames of the contained functions. MorganaXProc will check this and raise an error, if the namespaces differ from each other.

Now the third and last step to take is to supply your implementation of FunctionPackage to MorganaXProc, so you can use the implemented functions in your XPath expressions. If you use MorganaXProc from a Java application, the easiest way is to call addXPathFunctions with the implementation of FunctionPackage as a parameter in XProcConfiguration. This will make the functions visible for XPath expressions used in XProc as well as in p:xquery (MorganaXQueryConnector and SaxonXQueryConnector only) and in p:xslt (SaxonXSLTConnector only). You do not need any import, because the functions are added to the XPath context. But you can use p:import with your function's namespace as uri as a declarative statement for both MorganaXProc (error XS0052 is raised, if no FunctionPackage with this namespace is found) and human readers.

If you want to use MorganaXProc from the command line interface or the graphical user interface, you obviously can not call a method in XProcConfiguration. Here is what to do in this case: Make sure you have your implementation of FunctionPackage and XPathFunction compiled and pack into a ".jar"-file. Put this file into the folder "Extensions" next to MorganaXProc.jar on your file system. Finally: Put the fully qualified class name of your FunctionPackage implementation in your configuration file for MorganaXProc using property "ExtensionLibrary". Now the function is visible in the same scope as if directly supplied to method addXPathFunctions.

And there is yet another way to make your function visible to XProc (and XProc only): Put a p:import into your pipeline, use the fully qualified package name of your implementation of XPathFunction as value for attribute "href" and use extension attribute mox:content-type="application/java-archive". Make sure you have set "JavaLoadAllowed" to true in your XProcSecuritySystem settings. Done! Now your function is imported by MorganaXProc's compiler and can be used in all XPath expressions in your XProc pipeline.

Implementing XProc steps in Java:

Please note that the following instructions will change with the transition to XProc 3.0 because ports in XProc 3.0 can have non-xml content and the Java interface will have to cope with this.

Although XProc defines a long list of supported steps and you can define your own steps by means of XProc itself, in some cases it might be useful to extend the step library of an XProc processor with a new step written in Java. Now, obviously this is a threat to interoperability because other XProc processors (or other configurations of the same XProc processor) might not be able to perform the used step. However keeping this in mind, in one scenario or the other one will be tempted to use steps implemented in Java. There are three ways to tell MorganaXProc to use such an extension step from a Java class:

  • Declare your step as an atomic one inside the pipeline and use the "java" attribute in MorganaXProc's namespace to indicate the Java class which should perform the step.
  • Use the "ExtensionLibrary" in your configuration document and put the Java class name of the StepPackage with the step implementations into the "value" attribute. Please mind, that all steps implemented in one Java package must have the same namespace.
  • Use p:import with the fully qualified class name of your StepPackage implementation as value for attribute href, add extension-attribute "mox:content-type="application/java-archive" to p:import and do not forget to allow Java classes to be loaded in XProcSecuritySystem.

All three ways have their pros and cons: The first one makes the use of an extension step explicit and the ports and options relevant for running this step are available to human readers of the pipeline. The obvious disadvantage of this approach is that it is susceptible for typing errors. Another is related to security: If any user of the XProc processor is allowed to run every step (s)he likes from a Java implementation, the security shield established by MorganaXProc can easily be bypassed. MorganaXProc's security management allows you to control whether loading Java implemented steps is allowed or not, but you cannot control which steps are loaded once "JavaLoadAllowed" is set to true. And third: If you have a longer list of steps implemented in Java, human readers of the pipeline will probably be bored.

The second way to incorporate Java written steps into your XProc pipelines does not have these disadvantages because the implementation of the steps does not appear literally in any pipeline. Only the use of the steps is visible to a human reader. This is obviously handy for large lists of Java implemented steps, prohibits misspelling of step declarations (because there are none) and also minimizes security issues. But the disadvantages of this methods are also obvious: You need to document the steps and their signatures in some way outside of the pipeline, to enable the user to write correct pipelines. A second problem will occur if you decide (for one reason or another) to modify the relevant Java classes. Pipelines running perfectly before the change might now fail in the compiler. And there is no hint whatsoever in the pipeline helping the user to understand what went wrong.

The third way can be seen as a compromise between the two other ways as it hides implementation complexity, but makes the use of the imported step and the source visible to human readers. On the side of the cons: You can not take the pipeline to another XProc processor, as it has literal elements in this way only understood by MorganaXProc.

Anyway, if you want (or need) to implement an XProc step in Java, you have to write a Java class, extending "com.xml_project.morganaxproc.core.steptypes.BuildInStep". Additionally you have to satisfy the loading framework by providing the two static methods "makeNewInstance()" and "implementedStep()." The basis framework for a Java written extension step looks like this:

public class MyStep extends BuildInStep{

private MyStep(CompilationContext context,String stepName, CompoundStep parent, int lineInSource, int columnInSource, Attribute[] extensionAttributes){

super(stepName, parent, parent.getURI(), lineInSource, columnInSource, extensionAttributes);
// declare input ports
// declare output ports
// declare options

}
public PortValueMap execute(

XProcRuntime runtime, PortValueMap init,
ValueMap options, String stepURI)
throws XProcRuntimeException, StepException {
PortValueMap res = init.makeNew();
Document resdoc;
try{

// write your algorithm here, creating value for resdoc;
return checkAndPutResult(res, nameOfYourResultPort, resdoc);

}
catch (Exception ex){

ex.printStackTrace();
Document errorDocument = XProcRuntimeException.makeErrorDocument();
String message = "Couldnt execute step '"+implementedStep.toString()+"': "+ex.getMessage()+" ("+ex.getClass().getName()+")";
StepException.addErrorElement(errorDocument, getName(), "MyStep", "XD0030", getURI().toString(), -1, -1, -1, message);
throw new StepException(XProcException.XD0030, errorDocument);

}

}
public static MyStep makeNewInstance(CompilationContext context, String stepname, CompoundStep parent,
Integer lineInSource, Integer columnInSource, Attribute[] extensionAttributes){

return new MyStep(context,stepname, parent, lineInSource, columnInSource, extensionAttributes);

}
public static QName implementedStep(){return new QName(MyNamespaceURI,MyStepName);}

}

In order to import the steps to the steps known by MorganaXProc, you have to write an implementation of com.xml_project.morganaxproc.extensions.StepPackage to collect all steps using the same namespace. This interface defines the methods you have to implement.

public String getNamespaceURI();
public List>Class>? extends BuildInStep<< getDeclaredSteps();

With this, your Java coding is done and you can supply the implementation of StepPackage to MorganaXProc either by using XProcConfiguration, by using the configuration file or by directly importing the steps using the overloaded version of p:import.

This may serve as a blueprint for you to implement XProc steps in Java. Please remember that the implementation of XProc steps in Java will significantly change, once XProc 2.0 becomes a Recommendation.