问题
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
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?
The full code for this is in SaxonQuestions.zip (minus license key) - 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:
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 })
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()
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).
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"));
finally, on the XPathCompiler
you need to addXsltFunctionLibrary
e.g.
compiler = processor.newXPathCompiler();
compiler.addXsltFunctionLibrary(xpathLibrary);
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");
Then any XPath expressions you compile with that compiler can call the function pf:foo
.
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.
来源:https://stackoverflow.com/questions/63100310/how-do-i-call-fnsort-in-saxon-from-java-with-multiple-sort-keys