Как вызвать fn: sort () в Saxon из Java с несколькими ключами сортировки

Как использовать функцию сортировки в Saxon при ее вызове из Java (не из XSLT). Например, для запроса (данные смоделированы в базе данных Northwind) я могу получить несортированные данные, используя:

/windward-studios/Employees/Employee

Но я хочу отсортировать его следующим образом (используя здесь синтаксис SQL):

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

Как мне написать запрос для этого?

Полный код для этого находится в SaxonQuestions.zip (без лицензионного ключа) - 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

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

1 ответ

Решение

С точки зрения использования 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 может быть более эффективным компилировать и экспортировать таблицу стилей на отдельном этапе и загружать экспортированную таблицу стилей. Возможно, лучше узнать подробности на сайте поддержки Saxon.

Другие вопросы по тегам