从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;
}
}
在XPath3.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
根元素(或XSLT3包(例如,带有xsl:package
,请参阅XSLT3规范中的示例(。
然后,您需要使用XsltCompiler
(我认为它与XPath的其他编译器使用相同的Processor创建(将样式表编译为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支持网站上查询详细信息。