0

从 Java(而不是 XSLT)调用 Saxon 中的 sort 函数时,如何使用它。例如,对于查询(在 Northwind 数据库上建模的数据),我可以使用以下方法获取未排序的数据:

/windward-studios/Employees/Employee

但我想让它按如下方式排序(在此处使用 SQL 语法):

/windward-studios/Employees/Employee order by City descending, LastName ascending

如何编写查询来完成此操作?

完整的代码在SaxonQuestions.zip(减去许可证密钥) - TestSort.java 中。

测试排序.java

import net.sf.saxon.s9api.*;

import java.io.*;
import java.util.ArrayList;

public class TestSort {
    public static void main(String[] args) throws Exception {

        XmlDatasource datasource = new XmlDatasource(
                new FileInputStream(new File("files", "SouthWind.xml").getCanonicalPath()),
                new FileInputStream(new File("files", "SouthWind.xsd").getCanonicalPath()));

        // what I want is sort like: "/windward-studios/Employees/Employee order by City descending, LastName ascending"
        XdmValue nodeSet = datasource.getxPathCompiler().evaluate("/windward-studios/Employees/Employee", datasource.getXmlRootNode());

        System.out.println(String.format("%10s    %10s    %10s", "firstName", "lastName", "city"));
        for (int i = 0; i < nodeSet.size(); i++) {
            XdmItem item = nodeSet.itemAt(i);
            String firstName = ((XdmNode)((ArrayList)((XdmNode) item).children("FirstName")).get(0)).getStringValue();
            String lastName = ((XdmNode)((ArrayList)((XdmNode) item).children("LastName")).get(0)).getStringValue();
            String city = ((XdmNode)((ArrayList)((XdmNode) item).children("City")).get(0)).getStringValue();
            System.out.println(String.format("%10s    %10s    %10s", firstName, lastName, city));
        }
    }
}

XMLDatasource.java

import com.saxonica.config.EnterpriseConfiguration;
import com.saxonica.ee.s9api.SchemaValidatorImpl;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.FeatureKeys;
import net.sf.saxon.s9api.*;
import net.sf.saxon.type.SchemaException;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

public class XmlDatasource {

    /** the DOM all searches are against */
    private XdmNode xmlRootNode;

    private XPathCompiler xPathCompiler;

    /** key == the prefix; value == the uri mapped to that prefix */
    private HashMap<String, String> prefixToUriMap = new HashMap<>();

    /** key == the uri mapped to that prefix; value == the prefix */
    private HashMap<String, String> uriToPrefixMap = new HashMap<>();


    public XmlDatasource (InputStream xmlData, InputStream schemaFile) throws SAXException, SchemaException, SaxonApiException, IOException {

        boolean haveSchema = schemaFile != null;

        // call this before any instantiation of Saxon classes.
        Configuration config = createEnterpriseConfiguration();

        if (haveSchema) {
            Source schemaSource = new StreamSource(schemaFile);
            config.addSchemaSource(schemaSource);
        }

        Processor processor = new Processor(config);

        DocumentBuilder doc_builder = processor.newDocumentBuilder();

        XMLReader reader = createXMLReader();

        InputSource xmlSource = new InputSource(xmlData);
        SAXSource saxSource = new SAXSource(reader, xmlSource);

        if (haveSchema) {
            SchemaValidator validator = new SchemaValidatorImpl(processor);
            doc_builder.setSchemaValidator(validator);
        }
        xmlRootNode = doc_builder.build(saxSource);

        xPathCompiler = processor.newXPathCompiler();
        if (haveSchema)
            xPathCompiler.setSchemaAware(true);

        declareNameSpaces();
    }

    public XdmNode getXmlRootNode() {
        return xmlRootNode;
    }

    public XPathCompiler getxPathCompiler() {
        return xPathCompiler;
    }

    /**
     * Create a XMLReader set to disallow XXE aattacks.
     * @return a safe XMLReader.
     */
    public static XMLReader createXMLReader() throws SAXException {

        XMLReader reader = XMLReaderFactory.createXMLReader();

        // stop XXE https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J
        reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
        reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

        return reader;
    }

    private void declareNameSpaces() throws SaxonApiException {

        // saxon has some of their functions set up with this.
        prefixToUriMap.put("saxon", "http://saxon.sf.net");
        uriToPrefixMap.put("http://saxon.sf.net", "saxon");

        XdmValue list = xPathCompiler.evaluate("//namespace::*", xmlRootNode);
        if (list == null || list.size() == 0)
            return;

        for (int index=0; index<list.size(); index++) {
            XdmNode node = (XdmNode) list.itemAt(index);
            String prefix = node.getNodeName() == null ? "" : node.getNodeName().getLocalName();

            // xml, xsd, & xsi are XML structure ones, not ones used in the XML
            if (prefix.equals("xml") || prefix.equals("xsd") || prefix.equals("xsi"))
                continue;

            // use default prefix if prefix is empty.
            if (prefix == null || prefix.isEmpty())
                prefix = "def";

            // this returns repeats, so if a repeat, go on to next.
            if (prefixToUriMap.containsKey(prefix))
                continue;

            String uri = node.getStringValue();
            if (uri != null && !uri.isEmpty()) {
                xPathCompiler.declareNamespace(prefix, uri);
                prefixToUriMap.put(prefix, uri);
                uriToPrefixMap.put(uri, prefix);            }
        }
    }

    public static EnterpriseConfiguration createEnterpriseConfiguration()
    {
        EnterpriseConfiguration configuration = new EnterpriseConfiguration();
        configuration.supplyLicenseKey(new BufferedReader(new java.io.StringReader(deobfuscate(key))));
        configuration.setConfigurationProperty(FeatureKeys.SUPPRESS_XPATH_WARNINGS, Boolean.TRUE);

        return configuration;
    }
}
4

1 回答 1

2

fn:sort就在具有多个排序键的 XPath 3.1中使用而言,XPath 表达式为

sort(/windward-studios/Employees/Employee, (), function($emp) { $emp/City, $emp/LastName })

要获得降序(完整结果),我认为您可以使用fn:reverse

sort(/windward-studios/Employees/Employee, (), function($emp) { $emp/City, $emp/LastName }) => reverse()

至于设置 XSLT 样式表定义函数以用作 XPath 3.1 和 Saxon 10 中的函数,在 XSLT 中,您需要提供要公开visibility="public"属性的函数,例如<xsl:function name="pf:foo" visibility="public">...</xsl:function>在样式表模块(例如,使用xsl:stylesheet根元素)或 XSLT 3包(例如,使用xsl:package,请参阅 XSLT 3 规范以获取示例)。

然后您需要使用XsltCompiler(我认为使用与 XPath 的其他编译器相同的处理器创建)将样式表编译为XsltPackage

    Processor processor = new Processor(true);
    
    XsltCompiler xsltCompiler = processor.newXsltCompiler();
    
    XsltPackage xpathLibrary = xsltCompiler.compilePackage(new StreamSource("my-functions.xsl"));

最后,在XPathCompiler你需要addXsltFunctionLibrary例如

    compiler = processor.newXPathCompiler();
    compiler.addXsltFunctionLibrary(xpathLibrary);

那么您的 XPath 表达式可以使用任何公共函数。当然,由于任何函数都需要在命名空间中,样式表需要为命名空间声明一个前缀,而 XPathCompiler 也需要为同一个命名空间声明一个前缀,使用相同的前缀可能是有意义的:

    compiler.declareNamespace("pf", "http://example.com/pf");

然后,您使用该编译器编译的任何 XPath 表达式都可以调用该函数pf:foo

使用 Saxon EE,在单独的步骤中编译和导出样式表并加载导出的样式表可能会更有效。可能最好在撒克逊支持网站上询问详细信息。

于 2020-07-28T23:12:31.010 回答