Skip to content

Latest commit

 

History

History
202 lines (166 loc) · 7.4 KB

README.md

File metadata and controls

202 lines (166 loc) · 7.4 KB

EXPath Tools for Saxon

Build Status Maven Central License

Implementation of the tools for Saxon.

It is mainly two things now: the implementation of the data model from tools-java, and additional tools to ease writing extension functions for Saxon.

Function tools

A good example of the API for extension functions is the project file-saxon, implementing the EXPath File module, in the repository expath-file-java.

Let us have a look for instance at the class Exists:

public class Exists
        extends Function
{
    public Exists(Library lib)
    {
        super(lib);
    }

    @Override
    protected Definition makeDefinition()
            throws ToolsException
    {
        return library()
                .function(this, LOCAL_NAME)
                .returns(Types.SINGLE_BOOLEAN)
                .param(Types.SINGLE_STRING, PARAM_PATH)
                .make();
    }

    @Override
    public Sequence call(XPathContext ctxt, Sequence[] orig_params)
            throws XPathException
    {
        // the params
        Parameters params = checkParams(orig_params);
        String path = params.asString(0, false);
        // the actual call
        Properties props = new Properties();
        boolean res = props.exists(path);
        return Return.value(res);
    }

    private static final String LOCAL_NAME = "exists";
    private static final String PARAM_PATH = "path";
}

We can see interesting points:

  • an extension function extends the class Function
  • its constructor takes a Library object
  • the library is available through the library() method
  • it implements one method to return the function definition (describing its signature)
  • it implements a second method to actually implement the function behaviour
  • it uses several tools to deal with type descriptions, type conversions, parameters, etc.

Type description

The class Types contains static variables for all item types and sequence types, using a consistent naming scheme. For instance xs:integer+ is SEVERAL_INTEGER, and element()? is OPTIONAL_ELEMENT.

It also contains methods to construct more complex item types like element(name), which would be: singleElement("name", saxon) (requires the Saxon processor object).

Function definition

The function definition is described through a utility class, Definition. This class implements the Saxon's abstract class for function definitions, ExtensionFunctionDefinition, based on values passed to its constructor. But you never use it directly, you construct a definition by using a builder object, itself instantiated by the method Library.function(). You add the local name, return type, parameters, and then ask for the definition object.

Given the following example function (yes, there are 3 different functions, all with the same name, with different arities, which Saxon represents with the same function definition object):

my:hello() as xs:string
my:hello($name as xs:string) as xs:string
my:hello($name as xs:string, $lang as xs:string*) as xs:string

This function can be describe by the following code (assuming you already have a library object for this library, with the namespace URI and prefix):

library()
    .function(this, "hello")
    .returns(Types.SINGLE_STRING)
    .optional()
    .param(Types.SINGLE_STRING, "name")
    .param(Types.ANY_STRING, "lang")
    .make();

Parameters

The class Parameters provides helpers for dealing with Saxon's raw parameters. In particular, it helps accessing one specific parameter as a generic Java type, given its positional number. It reports the parameter name in the error message in case of an error. For instance (assuming the parameter is not optional):

String path = params.asString(0, false);

Return values

The class Return is like the class Parameters, but the other way around: it returns a Saxon sequence object from a java generic object. For instance, this will return an xs:boolean:

return Return.value(true);

Library

A library represents a set of functions in the same namespace. It contains a namespace URI, a namespace prefix, a set of functions and a way to create Saxon XPath errors (based on code names, typically bound to exceptions). The namespace prefix is used every time a function or error name has to be presented to the user, as in error messages. It looks typically like this (look at this example, EXPathFileLibrary):

public class SomeLibrary
        extends Library
{
    public SomeLibrary()
    {
        super(NS_URI, NS_PREFIX);
    }

    @Override
    protected Function[] functions()
            throws ToolsException
    {
        return new Function[] {
            new SomeFunction(this),
            new AnotherFunction(this),
            new YetAnotherOne(this)
        };
    }

    public XPathException error(SomeException ex)
    {
        switch ( ex.getType() ) {
            case NOT_FOUND:
                return error(ERR_NOT_FOUND, ex.getMessage(), ex);
            case FOOBAR:
                return error(ERR_FOOBAR, ex.getMessage(), ex);
            default:
                return error(ERR_DEFAULT, ex.getMessage(), ex);
        }
    }

    public static final String NS_URI    = "http://example.org/ns/my-library";
    public static final String NS_PREFIX = "my";

    // error codes, my:not-found, my:foobar and my:default-error
    private static final String ERR_NOT_FOUND = "not-found";
    private static final String ERR_FOOBAR    = "foobar";
    private static final String ERR_DEFAULT   = "default-error";
}

Given a library object, all the functions it contains can be registered against a Saxon Configuration object. This object is available regardless of the Saxon API you use. For instance, using S9API's Processor:

// the Saxon objects
Processor saxon = ...;
Configuration config = saxon.getUnderlyingConfiguration();
// the library
Library lib = new SomeLibrary();
lib.register(config);