I used the following solution for my (specific) problem:
I post two variants because there are several differences between Saxon-B (v9.1.0.8) and Saxon-EE (v9.6.0.7).
Adding my special FunctionLibrary before all other existing FunctionLibrarys to ensure the default handling when my special case is not triggered:
Saxon-B
net.sf.saxon.TransformerFactoryImpl saxonFactory = (net.sf.saxon.TransformerFactoryImpl)tFactory;
FunctionLibraryList fll = new FunctionLibraryList();
fll.addFunctionLibrary( new OldXalanFunctionLibrary() );
fll.addFunctionLibrary( saxonFactory.getConfiguration().getExtensionBinder("java") );
saxonFactory.getConfiguration().setExtensionBinder("java", fll);
Saxon-EE
net.sf.saxon.TransformerFactoryImpl saxonFactory = (net.sf.saxon.TransformerFactoryImpl)tFactory;
FunctionLibraryList fll = new FunctionLibraryList();
fll.addFunctionLibrary( new OldXalanFunctionLibrary() );
ProfessionalConfiguration conf = (ProfessionalConfiguration)saxonFactory.getConfiguration();
fll.addFunctionLibrary( conf.getExtensionBinder("java") );
conf.setExtensionBinder("java", fll);
My special handling FunctionLibrary looks like:
Saxon-B
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import de.server.MacroClasses;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.StaticContext;
import net.sf.saxon.functions.ExtensionFunctionCall;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trans.XPathException;
public class OldXalanFunctionLibrary implements FunctionLibrary {
private static final long serialVersionUID = 2216303509238422532L;
public OldXalanFunctionLibrary() {
}
@Override
public boolean isAvailable(StructuredQName functionName, int arity) {
String uri = functionName.getNamespaceURI();
String local = functionName.getLocalName();
if (uri.equals("xalan://de.server.macro") && (local.indexOf(".") > 0)) {
Class c = MacroClasses.class;
Method[] methods = c.getMethods();
String searchName = local.substring(local.lastIndexOf(".")+1);
for (int i=0; i< methods.length; i++) {
if (methods[i].getName().equals(searchName)) {
if (methods[i].getParameterTypes().length == arity) {
return true;
}
}
}
}
return false;
}
@Override
public Expression bind(StructuredQName functionName, Expression[] staticArgs, StaticContext env) throws XPathException {
String uri = functionName.getNamespaceURI();
String local = functionName.getLocalName();
if (uri.equals("xalan://de.server.macro") && (local.indexOf(".") > 0)) {
Class c = MacroClasses.class;
Method[] methods = c.getMethods();
Method m = null;
String searchName = local.substring(local.lastIndexOf(".")+1);
for (int i=0; i< methods.length; i++) {
//String name = methods[i].getName();
if (methods[i].getName().equals(searchName)) {
if (methods[i].getParameterTypes().length == staticArgs.length) {
m = methods[i];
break;
}
}
}
AccessibleObject accessObj = (AccessibleObject)m;
ExtensionFunctionCall fn;
try {
fn = (ExtensionFunctionCall)(c.newInstance());
} catch (InstantiationException e) {
throw new IllegalArgumentException(e.getMessage());
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e.getMessage());
}
fn.init(functionName, c, accessObj, env.getConfiguration());
fn.setArguments(staticArgs);
return fn;
}
return null;
}
@Override
public FunctionLibrary copy() {
OldXalanFunctionLibrary newLibrary = new OldXalanFunctionLibrary();
return newLibrary;
}
}
Saxon-EE
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import com.saxonica.expr.JavaExtensionFunctionCall;
import de.server.MacroClasses;
import net.sf.saxon.expr.Container;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.StaticContext;
import net.sf.saxon.functions.FunctionLibrary;
import net.sf.saxon.om.FunctionItem;
import net.sf.saxon.trans.SymbolicName;
import net.sf.saxon.trans.XPathException;
public class OldXalanFunctionLibrary implements FunctionLibrary {
public OldXalanFunctionLibrary() {
}
@Override
public boolean isAvailable(SymbolicName symName) {
String uri = symName.getComponentName().getNamespaceBinding().getURI();
String local = symName.getComponentName().getStructuredQName().getLocalPart();
if (uri.equals("xalan://de.server.macro") && (local.indexOf(".") > 0)) {
Class<MacroClasses> c = MacroClasses.class;
Method[] methods = c.getMethods();
String searchName = local.substring(local.lastIndexOf(".")+1);
for (int i=0; i< methods.length; i++) {
if (methods[i].getName().equals(searchName)) {
if (methods[i].getParameterTypes().length == symName.getArity()) {
return true;
}
}
}
}
return false;
}
@Override
public Expression bind(SymbolicName symName, Expression[] staticArgs, StaticContext context, Container cont) throws XPathException {
String uri = symName.getComponentName().getNamespaceBinding().getURI();
String local = symName.getComponentName().getStructuredQName().getLocalPart();
if (uri.equals("xalan://de.server.macro") && (local.indexOf(".") > 0)) {
Class<MacroClasses> c = MacroClasses.class;
Method[] methods = c.getMethods();
Method m = null;
String searchName = local.substring(local.lastIndexOf(".")+1);
for (int i=0; i< methods.length; i++) {
//String name = methods[i].getName();
if (methods[i].getName().equals(searchName)) {
if (methods[i].getParameterTypes().length == symName.getArity()) {
m = methods[i];
break;
}
}
}
AccessibleObject accessObj = (AccessibleObject)m;
JavaExtensionFunctionCall fn;
try {
fn = (JavaExtensionFunctionCall)(c.newInstance());
} catch (InstantiationException e) {
throw new IllegalArgumentException(e.getMessage());
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e.getMessage());
}
fn.init(symName.getComponentName().getStructuredQName(), c, accessObj);
fn.setArguments(staticArgs);
return fn;
}
return null;
}
@Override
public FunctionLibrary copy() {
OldXalanFunctionLibrary newLibrary = new OldXalanFunctionLibrary();
return newLibrary;
}
@Override
public FunctionItem getFunctionItem(SymbolicName symName, StaticContext context, Container cont) throws XPathException {
return null;
}
}
My Wrapper-Class for calling my specific methods:
Saxon-B
package de.server;
import net.sf.saxon.functions.ExtensionFunctionCall;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import de.macro.Format;
import de.macro.TrimLine;
import de.macro.data;
import de.macro.exception.JavaStaticTransformationException;
public class MacroClasses extends ExtensionFunctionCall {
// data
public static Document getXmlDoc() {
return data.getXmlDoc();
}
public static void setVar(String name, String value) {
data.setVar(name, value);
}
public static void setVar(String name, String value, String context) {
data.setVar(name, value, context);
}
public static Node getVar(String name) {
return data.getVar(name);
}
public static Node getVar(String name, String context) {
return data.getVar(name, context);
}
public static void flush() {
data.flush();
}
public static String countContextItems() {
return data.countContextItems();
}
public static String getContextsAsString() {
return data.getContextsAsString();
}
public static String countAllItems() {
return data.countAllItems();
}
public static void setOutdatedTime(long aInterval) {
data.setOutdatedTime(aInterval);
}
// Format
public static String format(String aVarName, String aDataType, String aInputString, String aFormatType, String aPrecision) throws Exception {
return Format.format(aVarName, aDataType, aInputString, aFormatType, aPrecision);
}
public static String format(String aVarName, String aDataType, String aInputString) throws Exception {
return Format.format(aVarName, aDataType, aInputString);
}
public static String format(String aVarName, String aDataType, String aInputString, String aSwitchSign,
String aFormatType, String aPrecision) throws Exception {
return Format.format(aVarName, aDataType, aInputString, aSwitchSign, aFormatType, aPrecision);
}
public static String convertToEnglish(String aInputString) {
return Format.convertToEnglish(aInputString);
}
public static String convertLastMonthsLast(String aInputString) {
return Format.convertLastMonthsLast(aInputString);
}
public static String getCountSelected(String aVarName, String aValue)
throws JavaStaticTransformationException {
return Format.getCountSelected(aVarName, aValue);
}
public static String fill(String aVarName, String aInputString, String aFillChar, String aLength,
String aDirection) throws JavaStaticTransformationException {
return Format.fill(aVarName, aInputString, aFillChar, aLength, aDirection);
}
// TrimLine
public static String process(String aInput, int aMaxLineLength) throws Exception {
return TrimLine.process(aInput, aMaxLineLength);
}
}
Saxon-EE
package de.server;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import com.saxonica.expr.JavaExtensionFunctionCall;
import de.macro.Format;
import de.macro.TrimLine;
import de.macro.data;
import de.macro.exception.JavaStaticTransformationException;
public class MacroClasses extends JavaExtensionFunctionCall {
// data
public static Document getXmlDoc() {
return data.getXmlDoc();
}
private static String convert4setVar(Object value) throws Exception {
String setValue;
if (value instanceof String)
{
setValue = (String)value;
}
else if (value instanceof net.sf.saxon.value.TextFragmentValue)
{
net.sf.saxon.value.TextFragmentValue newTextValue = (net.sf.saxon.value.TextFragmentValue)value;
setValue = newTextValue.getStringValue();
}
else if (value instanceof Integer)
{
setValue = Integer.toString((Integer)value);
}
else if (value instanceof Double)
{
setValue = Double.toString((Double)value);
}
else if (value instanceof java.math.BigInteger)
{
java.math.BigInteger newIntegerValue = (java.math.BigInteger)value;
setValue = newIntegerValue.toString();
}
else
{
throw new Exception("Type for data.setVar not implemented: " + value.getClass().getName());
}
return setValue;
}
public static void setVar(String name, Object value) throws Exception {
data.setVar(name, convert4setVar(value));
}
public static void setVar(String name, String value, String context) throws Exception {
data.setVar(name, convert4setVar(value), context);
}
public static Node getVar(String name) {
return data.getVar(name);
}
public static Node getVar(String name, String context) {
return data.getVar(name, context);
}
public static void flush() {
data.flush();
}
public static String countContextItems() {
return data.countContextItems();
}
public static String getContextsAsString() {
return data.getContextsAsString();
}
public static String countAllItems() {
return data.countAllItems();
}
public static void setOutdatedTime(long aInterval) {
data.setOutdatedTime(aInterval);
}
// Format
public static String format(String aVarName, String aDataType, String aInputString, String aFormatType, String aPrecision) throws Exception {
return Format.format(aVarName, aDataType, aInputString, aFormatType, aPrecision);
}
public static String format(String aVarName, String aDataType, String aInputString) throws Exception {
return Format.format(aVarName, aDataType, aInputString);
}
public static String format(String aVarName, String aDataType, String aInputString, String aSwitchSign,
String aFormatType, String aPrecision) throws Exception {
return Format.format(aVarName, aDataType, aInputString, aSwitchSign, aFormatType, aPrecision);
}
public static String convertToEnglish(String aInputString) {
return Format.convertToEnglish(aInputString);
}
public static String convertLastMonthsLast(String aInputString) {
return Format.convertLastMonthsLast(aInputString);
}
public static String getCountSelected(String aVarName, String aValue)
throws JavaStaticTransformationException {
return Format.getCountSelected(aVarName, aValue);
}
public static String fill(String aVarName, String aInputString, String aFillChar, String aLength,
String aDirection) throws JavaStaticTransformationException {
return Format.fill(aVarName, aInputString, aFillChar, aLength, aDirection);
}
// TrimLine
public static String process(String aInput, int aMaxLineLength) throws Exception {
return TrimLine.process(aInput, aMaxLineLength);
}
}
Now I don't need to modify any (cached/saved) XSLT-Stylesheets and it only catches the specific cases. So I can use the normal Saxon-Style calls for new generated Stylesheets but I'm downward compatible. And I hope I have no significant performance loss (my tests confirm this estimation till now).