如何使用多个排序键从 Java 中调用 Saxon 中的 fn:sort() [英] How do I call fn:sort() in Saxon from Java with multiple sort keys

查看:33
本文介绍了如何使用多个排序键从 Java 中调用 Saxon 中的 fn:sort()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How do I use the sort function in Saxon when calling it from Java (not from XSLT). For example, for the query (data modeled on the Northwind database) I can get unsorted data using:

/windward-studios/Employees/Employee

但我想按以下方式对其进行排序(此处使用 SQL 语法):

But I want to get it sorted like the following (using SQL syntax here):

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

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

How do I write the query to accomplish this?

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

The full code for this is in SaxonQuestions.zip (minus license key) - TestSort.java.

TestSort.java

TestSort.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

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;
    }
}

推荐答案

对于在 XPath 3.1 中使用 fn:sort 有多个排序键,XPath 表达式为

In terms of using fn:sort in XPath 3.1 with multiple sort keys, the XPath expression is

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

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

To get descending order (for the complete result) I think you can use fn:reverse:

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

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

As for setting up an XSLT stylesheet defining functions to be used as functions in XPath 3.1 with Saxon 10, in XSLT you need to give the functions to be exposed the visibility="public" attribute e.g. <xsl:function name="pf:foo" visibility="public">...</xsl:function> in a stylesheet module (e.g. with the xsl:stylesheet root element) or an XSLT 3 package (e.g. with xsl:package, see the XSLT 3 spec for an example).

然后你需要使用一个 XsltCompiler(我认为它是用与其他 XPath 编译器相同的处理器创建的)来将样式表编译成一个 XsltPackage:

Then you need to use an XsltCompiler (I think created with same Processor as the other compilers for XPath) to compile the stylesheet into an XsltPackage:

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

最后,在 XPathCompiler 你需要 addXsltFunctionLibrary 例如

finally, on the XPathCompiler you need to addXsltFunctionLibrary e.g.

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

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

then your XPath expressions can use any of the public functions. Of course, as any functions need to be in a namespace, the stylesheet needs to declare a prefix for the namespace and the XPathCompiler needs to declare a prefix too for the same namespace, it makes sense probably to use the same prefix:

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

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

Then any XPath expressions you compile with that compiler can call the function pf:foo.

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

With Saxon EE it might additionally be more efficient to compile and export the stylesheet in a separate step and to load the exported stylesheet. Probably better to ask on the Saxon support site for details.

这篇关于如何使用多个排序键从 Java 中调用 Saxon 中的 fn:sort()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆