将 Xalan 替换为 Saxon for XSL with Java-Extension

Replace Xalan with Saxon for XSL with Java-Extension

我在 Xalan 的业务流程中有几个 XSL 转换,其中这些步骤之一生成具有静态 Java 扩展函数的 XSL 样式表。

我想用 Saxon 替换 Xalan(不仅是为了性能问题,也是为了 XSLT2 的使用)。

我知道要在 XSL 中更改什么才能让 Saxon 使用这些功能。它工作得很好(使用 Xalan 优化的 XSL,我得到了 40 的加速并且只使用了一半的 RAM)。

我的问题是这些生成的 XSL 样式表 "cached"/存储量很大,"refresh" 它们会非常痛苦(或不可能)。

我的问题是我是否可以在不更改 XSL 或预处理 XML(修改 SAX-Parser 或 StringReplacing 等)的情况下设法让 XSL 与 Saxon 一起工作?

目前我需要更改命名空间和函数调用,因为在 Xalan 中我使用了命名空间中的包,而 Saxon(似乎)想要 class.

我完全控制了 "de.server.macro" 的包和 class 结构和代码。

(用于测试)我使用的是 Saxon-9B,但最终会是 Saxon-PE 或 Saxon-EE。

以下是我的最小化示例:

夏兰

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:macro="xalan://de.server.macro">

<xsl:template match="/">
    <output>
        <xsl:text>Hello World!</xsl:text>
        <mymacro>
            <xsl:variable name="foo">5</xsl:variable>
            <xsl:value-of select="macro:data.setVar('testdata', $foo)"/>
            <xsl:value-of select="macro:data.getVar('testdata')"/>
        </mymacro>
    </output>
</xsl:template>

撒克逊人

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:macro="de.server.macro.data">

<xsl:template match="/">
    <output>
        <xsl:text>Hello World!</xsl:text>
        <mymacro>
            <xsl:variable name="foo">5</xsl:variable>
            <xsl:value-of select="macro:setVar('testdata', $foo)"/>
            <xsl:value-of select="macro:getVar('testdata')"/>
        </mymacro>
    </output>
</xsl:template>

我知道 http://xml.apache.org/xalan-j/extensions.html and http://www.saxonica.com/documentation9.5/extensibility/functions/staticmethods.html 和其他几个关于 xslt-extensions 的来源(博客文章和书籍),但根据这些信息,答案似乎是 "no"。

但也许我错过了一些东西或 Saxon 中一些可能的映射机制来模拟正确的行为?

我认为有一部分格式可以跨 Xalan 和 Saxon 使用。如果您使用 Xalan 所称的 "class format" 命名空间,例如

xmlns:String="xalan://java.util.Hashtable"

那么您应该可以调用静态方法,例如

String:valueOf($x)

在任一产品中;对于 Saxon,您需要设置配置 属性 FeatureKeys.ALLOW_OLD_JAVA_URI_FORMAT

我针对我的(特定)问题使用了以下解决方案: 我 post 两个变体,因为 Saxon-B (v9.1.0.8) 和 Saxon-EE (v9.6.0.7) 之间存在一些差异。

在所有其他现有 FunctionLibrary 之前添加我的特殊 FunctionLibrary,以确保在未触发我的特殊情况时的默认处理:

撒克逊-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);

撒克逊-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);

我的特殊处理 FunctionLibrary 看起来像:

撒克逊-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;
    }

}

撒克逊-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 用于调用我的特定方法:

撒克逊-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);
     }
}

撒克逊-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);
     }

}

现在我不需要修改任何 (cached/saved) XSLT 样式表,它只捕获特定情况。所以我可以对新生成的样式表使用正常的撒克逊风格调用,但我是向下兼容的。我希望我没有明显的性能损失(我的测试到目前为止证实了这个估计)。