我如何使用多个排序键从 Java 在 Saxon 中调用 fn:sort()
How do I call fn:sort() in Saxon from Java with multiple sort keys
从 Java(而不是 XSLT)调用 Saxon 中的排序函数时如何使用它。例如,对于查询(基于 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;
}
}
就在具有多个排序键的 XPath 3.1 中使用 fn:sort
而言,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 表达式可以使用任何 public 函数。当然,由于任何函数都需要在命名空间中,样式表需要为命名空间声明一个前缀,而 XPathCompiler 也需要为相同的命名空间声明一个前缀,使用相同的前缀可能是有意义的:
compiler.declareNamespace("pf", "http://example.com/pf");
然后您使用该编译器编译的任何 XPath 表达式都可以调用函数 pf:foo
.
使用 Saxon EE,在单独的步骤中编译和导出样式表并加载导出的样式表可能会更加高效。可能最好在 Saxon 支持网站上询问详细信息。
从 Java(而不是 XSLT)调用 Saxon 中的排序函数时如何使用它。例如,对于查询(基于 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;
}
}
就在具有多个排序键的 XPath 3.1 中使用 fn:sort
而言,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 表达式可以使用任何 public 函数。当然,由于任何函数都需要在命名空间中,样式表需要为命名空间声明一个前缀,而 XPathCompiler 也需要为相同的命名空间声明一个前缀,使用相同的前缀可能是有意义的:
compiler.declareNamespace("pf", "http://example.com/pf");
然后您使用该编译器编译的任何 XPath 表达式都可以调用函数 pf:foo
.
使用 Saxon EE,在单独的步骤中编译和导出样式表并加载导出的样式表可能会更加高效。可能最好在 Saxon 支持网站上询问详细信息。