Как вызвать 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.