📜 ⬆️ ⬇️

Xalan, Saxon and 8 Queens


Today I want to talk about XSLT . This very peculiar language can be very useful in cases where it is necessary to process XML data in some non-trivial way. I will talk about the two most popular (among Java developers) implementations of the XSLT processor, discuss in detail the issues of their use from Java code and try to compare their performance. As a test for comparing performance, I will use the well-known task of placing 8 queens on a chessboard.

Since the solution of such problems using XSLT can hardly be classified as a normal activity, the topic is placed in the appropriate section. At the same time, I hope that this article will be useful as a teaching material.

Xalan vs saxon


Despite the fairly large number of existing XSLT implementations, if we are talking about developing in the Java language, the choice (in my opinion) comes down to the two mentioned in the title. Indeed, the use of the implementation from Microsoft is made difficult by the fact that it comes in the form of a nested dll, while the implementation from Oracle is intended to be used directly in the database. Other implementations are much less known (I am really interested in this question and I will be happy to additionally consider any of the Java-compatible XSLT implementations proposed by Habr participants).

Saxon, in my opinion, is the most developed, to date, XSLT processor. The matrix of the opportunities it provides is truly impressive. Although the most interesting of these features are supported only by commercial distributions, supplied under the terms of the Mozilla Public License version 1.0, Saxon HE also provides full XPath and XSLT support for both version 1.0 and version 2.0. In order to understand how much XSLT 2.0 support can make life easier, just briefly familiarize yourself with the content of the popular XSLT Cookbook by Sal Mangano.
')
The main advantage of Xalan, unlike Saxon, is the use of the less restrictive Apache License Version 1.1 . This implementation provides full support for XSLT version 1.0. Another trump card, both Xalan and Saxon, is their declared full support for the TrAX industry standard. About what it is and why it is needed, I will tell in the next section.

In the name of TrAX and JAXP


TrAX is an API for performing XML transformations, developed by a number of interested organizations to unify interaction with XSLT processors. As far as the Java API is concerned, it was published by Sun as JAXP on February 6, 2001. To familiarize yourself with the possibilities of using JAXP, I recommend reading the following articles .

In short, JAXP allows you to work with XML documents in Java, using both DOM and SAX (including implementing XSLT transformations) without tying the code to the implementation and version of the handler being used. For correct work, it is enough to ensure the presence of the used jar in the classpath and set the corresponding System Property (this is not necessary if there is no difference which of the handlers mentioned in the classpath is used).

Let's see how it looks in practice. To begin with, let's try to load an XML file into memory and get some value from it using an XPath expression.

The XML file will look like this:

<?xml version="1.0"?> <size>5</size> 

There is not much data here, but you should always start with a simple one (in the future, we will use this value to set the size of the chessboard).

We get value using DOM
  private void test_xpath_dom() throws Exception { InputSource in = new InputSource(new FileInputStream("xml/data.xml")); DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); Document doc = df.newDocumentBuilder().parse(in); NodeIterator nl = XPathAPI.selectNodeIterator(doc, "/size"); Node n; while ((n = nl.nextNode())!= null) { if (n.getNodeType() == Node.TEXT_NODE) { System.out.println(n.getNodeValue()); } else { System.out.println(n.getTextContent()); } } } 


Here we get an iterator on a simple XPath query, go through a set of nodes (consisting of one node) and output its value (if it is a text node) or text content (if used, this option will always work). As we can see, everything is not difficult at all, but if we get a scalar value, we can proceed even more simply:

We get scalar value using DOM
  private void test_xpath_dom() throws Exception { System.setProperty(JAVAX_TRANSFORM_FACTORY, XALAN_TRANSFORM_FACTORY); InputSource in = new InputSource(new FileInputStream("xml/data.xml")); DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); Document doc = df.newDocumentBuilder().parse(in); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); XPathExpression xpathExpression = xpath.compile("/size"); System.out.println(xpathExpression.evaluate(doc)); } 


Sometimes (especially when processing large XML files), the use of the DOM is avoided (since data must be completely loaded into memory). In such cases, SAX is used to parse the XML. JAXP allows you to convert one format to another (for example, SAX to DOM) using the following transformation (if the style sheet is not specified when performing the transformation, the same transformation is performed, which is often quite convenient):

We use the SAX parser
  private void test_xpath_sax_file() throws Exception { TransformerFactory tf = TransformerFactory.newInstance(); if (tf.getFeature(SAXSource.FEATURE) && tf.getFeature(DOMResult.FEATURE)) { SAXTransformerFactory stf = (SAXTransformerFactory)tf; TransformerHandler h = stf.newTransformerHandler(); XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setContentHandler(h); reader.setProperty("http://xml.org/sax/properties/lexical-handler", h); DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); Document doc = df.newDocumentBuilder().newDocument(); Result out = new DOMResult(doc); h.setResult(out); reader.parse("xml/data.xml"); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); XPathExpression xpathExpression = xpath.compile("/size"); System.out.println(xpathExpression.evaluate(doc)); } else { throw new Exception("Can''t support SAXSource or DOMResult"); } } 


It is clear that in this way we will not get rid of loading the XML file into RAM, since we still use DOM for intermediate storage. How can we handle large files? For example, we can get a SAX stream when parsing a large XML file and convert it so that not one big file is loaded into memory, but many small files in turn. Let's try to generate SAX events manually:

SAX stream loading
  private void generateData(ContentHandler h, String data) throws SAXException { h.startDocument(); h.startElement("", "size", "size", new AttributesImpl()); h.characters(data.toCharArray(), 0, data.length()); h.endElement("", "size", "size"); h.endDocument(); } private void test_xpath_sax_stream(String size) throws Exception { TransformerFactory tf = TransformerFactory.newInstance(); if (tf.getFeature(SAXSource.FEATURE) && tf.getFeature(DOMResult.FEATURE)) { SAXTransformerFactory stf = (SAXTransformerFactory)tf; TransformerHandler h = stf.newTransformerHandler(); DocumentBuilderFactory df = DocumentBuilderFactory.newInstance(); Document doc = df.newDocumentBuilder().newDocument(); Result out = new DOMResult(doc); h.setResult(out); generateData(h, size); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); XPathExpression xpathExpression = xpath.compile("/size"); System.out.println(xpathExpression.evaluate(doc)); } else { throw new Exception("Can''t support SAXSource or DOMResult"); } } 


As we can see, there is also nothing complicated here. I just want to warn against a possible transfer to the uri parameter of the startElement and endElement calls of null values. For Xalan, this works fine, but Saxon throws a NullPointerException.

For completeness, it remains to add that the transformations can be linked into chains, forming a conveyor. At the output, the result can be saved, for example, to a file:

Conveyor
  private void test_solution(String size) throws Exception { TransformerFactory tf = TransformerFactory.newInstance(); if (tf.getFeature(SAXSource.FEATURE) && tf.getFeature(SAXResult.FEATURE)) { SAXTransformerFactory stf = (SAXTransformerFactory)tf; TransformerHandler solve = stf.newTransformerHandler(); TransformerHandler filter = stf.newTransformerHandler(); TransformerHandler view = stf.newTransformerHandler(); Result result = new StreamResult(new File("xml/result.xml")); solve.setResult(new SAXResult(filter)); filter.setResult(new SAXResult(view)); view.setResult(result); generateData(solve, size); } else { throw new Exception("Can''t support SAXSource or SAXResult"); } } 


So far, we see here three empty handlers that perform the same transformation. In the next section, we will start filling them with XSLT code.

The first approach to the projectile


So, we need to arrange queens on a chessboard so that they do not beat each other. To begin, we need to generate a list of positions that satisfy the condition of the problem. We will define coding positions. Since, according to the conditions of the problem, the figures can not be located on the same vertical, we can use a string of decimal digits to encode correct positions.

The digit position will mean the number of the vertical, and the value itself will mean the horizontal number on which the figure is located. Since we use decimal digits starting from one, we can solve the problem for a square board with a size from 1x1 to 9x9 cells. To make calculations faster, I recommend to set the size to less than 8 (5x5 cells will be just right). The code on XSLT (despite its excessive verbosity) is quite understandable:

solution.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="size"> <result> <xsl:call-template name="queens"> <xsl:with-param name="r"/> <xsl:with-param name="n" select="."/> <xsl:with-param name="s" select="."/> </xsl:call-template> </result> </xsl:template> <xsl:template name="queens"> <xsl:param name="r"/> <xsl:param name="n"/> <xsl:param name="s"/> <xsl:choose> <xsl:when test="$n = 0"> <position> <xsl:copy-of select="$r"/> </position> </xsl:when> <xsl:otherwise> <xsl:call-template name="step"> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="n" select="$n"/> <xsl:with-param name="v" select="$s"/> <xsl:with-param name="s" select="$s"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="step"> <xsl:param name="r"/> <xsl:param name="n"/> <xsl:param name="v"/> <xsl:param name="s"/> <xsl:if test="$v != 0"> <xsl:variable name="c"> <xsl:call-template name="check"> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="v" select="$v"/> </xsl:call-template> </xsl:variable> <xsl:if test="$c != 0"> <xsl:variable name="l"> <xsl:value-of select="concat($v,$r)"/> </xsl:variable> <xsl:call-template name="queens"> <xsl:with-param name="r" select="$l"/> <xsl:with-param name="n" select="$n - 1"/> <xsl:with-param name="s" select="$s"/> </xsl:call-template> </xsl:if> <xsl:call-template name="step"> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="n" select="$n"/> <xsl:with-param name="v" select="$v - 1"/> <xsl:with-param name="s" select="$s"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="check"> <xsl:param name="r"/> <xsl:param name="v"/> <xsl:if test="contains($r,$v)">0</xsl:if> </xsl:template> </xsl:stylesheet> 


Here we use a simplified check for the mutual battle of the figures, without performing a battle check on the diagonal. By running the following program to execute:

8 rooks
  private void test_solution(String size) throws Exception { TransformerFactory tf = TransformerFactory.newInstance(); if (tf.getFeature(SAXSource.FEATURE) && tf.getFeature(SAXResult.FEATURE)) { SAXTransformerFactory stf = (SAXTransformerFactory)tf; TransformerHandler solve = stf.newTransformerHandler(new StreamSource("xsl/solution.xsl")); TransformerHandler filter = stf.newTransformerHandler(); TransformerHandler view = stf.newTransformerHandler(); Result result = new StreamResult(new File("xml/result.xml")); solve.setResult(new SAXResult(filter)); filter.setResult(new SAXResult(view)); view.setResult(result); generateData(solve, size); } else { throw new Exception("Can''t support SAXSource or SAXResult"); } } 


... you can get a list of possible solutions to the 8 Rooks problem.

To exclude possible accusations of substitution of the conditions of the task, let’s make it difficult to check the mutual combat of the figures. To simplify code reuse, XSLT offers the option to import. We will take advantage of her:

queens.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="solution.xsl"/> <xsl:template name="check"> <xsl:param name="r"/> <xsl:param name="v"/> <xsl:choose> <xsl:when test="contains($r,$v)">0</xsl:when> <xsl:otherwise> <xsl:variable name="y"> <xsl:call-template name="additional_check"> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="v" select="$v"/> <xsl:with-param name="d" select="1"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$y"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="additional_check"> <xsl:param name="r"/> <xsl:param name="v"/> <xsl:param name="d"/> <xsl:if test="$d &lt;= string-length($r)"> <xsl:variable name="u" select="substring($r,$d,1)"/> <xsl:variable name="b"> <xsl:call-template name="abs"> <xsl:with-param name="x" select="$v - $u"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$b = $d">0</xsl:when> <xsl:otherwise> <xsl:call-template name="additional_check"> <xsl:with-param name="r" select="$r"/> <xsl:with-param name="v" select="$v"/> <xsl:with-param name="d" select="$d + 1"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:if> </xsl:template> <xsl:template name="abs"> <xsl:param name="x"/> <xsl:choose> <xsl:when test="$x &lt; 0"> <xsl:value-of select="$x * -1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$x"/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> 


I note that here it is necessary to use the import statement, and not the include statement, in order to ensure a higher priority of templates in the importing table as compared to the imported one (this is very similar to inheritance).

To make all this import work, you need to make another small change to the Java code. Define the URIResolver that will be called when a URI is requested during the XSLT parsing process:

Resolver.java
 import java.io.File; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.URIResolver; import javax.xml.transform.stream.StreamSource; public class Resolver implements URIResolver { public Source resolve(String href, String base) throws TransformerException { return new StreamSource(new File("xsl/" + href)); } } 


... and give it to the factory:

8 queens
  private void test_queens(String size) throws Exception { TransformerFactory tf = TransformerFactory.newInstance(); + tf.setURIResolver(new Resolver()); if (tf.getFeature(SAXSource.FEATURE) && tf.getFeature(SAXResult.FEATURE)) { SAXTransformerFactory stf = (SAXTransformerFactory)tf; TransformerHandler solve = stf.newTransformerHandler(new StreamSource("xsl/queens.xsl")); TransformerHandler filter = stf.newTransformerHandler(); TransformerHandler view = stf.newTransformerHandler(); Result result = new StreamResult(new File("xml/result.xml")); solve.setResult(new SAXResult(filter)); filter.setResult(new SAXResult(view)); view.setResult(result); generateData(solve, size); } else { throw new Exception("Can''t support SAXSource or SAXResult"); } } 


Of course, in our case, when all the xsl files are loaded from one disk directory, the XSLT processor will perfectly figure out where to load the file, but imagine, for example, that the XSLT code is loaded from the LOB field in the database!

Running the program for execution, we get a list of solutions to the problem.

What should we do with it?


The list of solutions in the form in which we made it is not very convenient for a person. It is compact, but does not give a visual representation of the positions (without some mental effort). It is also not very convenient to count the number of solutions manually using it. Fortunately, processing such data is exactly what XSLT is intended for. By adding another handler to the end of the transformation chain, we can easily calculate the number of solutions:

count.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="result"> <result> <xsl:value-of select="count(position)"/> </result> </xsl:template> </xsl:stylesheet> 


... either, show them in visual form:

boards.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:redirect="http://xml.apache.org/xalan/redirect" extension-element-prefixes="redirect" version="1.0"> <xsl:output method="html"/> <xsl:template match="/result/position"> <a href="{concat(. , '.html')}"> <xsl:value-of select="."/> </a><br/> <redirect:write select="concat('xml/', . , '.html')"> <style> table { display:block; margin:10px; border:0; border-collapse: collapse; } table tr { border:0; } table tr td { border:1px solid #999; width:15px; height:15px; padding: 0; } .active { background: #898989; } </style> <table border="1" style="border-collapse:collapse"> <xsl:call-template name="line"> <xsl:with-param name="r" select="."/> <xsl:with-param name="s" select="string-length(.)"/> </xsl:call-template> </table> </redirect:write> </xsl:template> <xsl:template name="line"> <xsl:param name="r"/> <xsl:param name="s"/> <xsl:if test="string-length($r) != 0"> <xsl:variable name="x" select="substring($r,1,1)"/> <tr> <xsl:call-template name="col"> <xsl:with-param name="x" select="$x"/> <xsl:with-param name="i" select="$s"/> </xsl:call-template> </tr> <xsl:call-template name="line"> <xsl:with-param name="r" select="substring($r,2)"/> <xsl:with-param name="s" select="$s"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="col"> <xsl:param name="x"/> <xsl:param name="i"/> <xsl:if test="$i != 0"> <xsl:choose> <xsl:when test="$x = $i"><td class="active"/></xsl:when> <xsl:otherwise><td/></xsl:otherwise> </xsl:choose> <xsl:call-template name="col"> <xsl:with-param name="x" select="$x"/> <xsl:with-param name="i" select="$i - 1"/> </xsl:call-template> </xsl:if> </xsl:template> </xsl:stylesheet> 


like this:

image

In this example, you should pay attention to the non-standard Xalan redirect: write extension, which allows you to redirect the output to another file (in XSLT 2.0, a standard instruction has been added for this purpose). Unfortunately, I was not able to redirect the result of such a redirect to an arbitrary SAX stream, which, in my opinion, somewhat reduces its value.

Reflection magic


We got solutions, but there are too many of them. This is because we do not screen out repetitions, taking into account possible rotations and reflections of the board. Let's get a list of unique solutions?

If you think a little, it becomes clear that all kinds of turns and reflections of the board are only 8 (given the initial version). We implement auxiliary templates to reflect the position (in our internal representation) vertically (flip), horizontal (reverse) and rotate the board by 90 degrees (rotate):

utils.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="result"> <result> <xsl:apply-templates/> </result> </xsl:template> <xsl:template match="position"> <position> <xsl:value-of select="."/> </position> <flip> <xsl:call-template name="flip"> <xsl:with-param name="x" select="."/> </xsl:call-template> </flip> <reverse> <xsl:call-template name="reverse"> <xsl:with-param name="x" select="."/> </xsl:call-template> </reverse> <rotate> <xsl:call-template name="rotate"> <xsl:with-param name="x" select="."/> </xsl:call-template> </rotate> </xsl:template> <xsl:template name="flip"> <xsl:param name="x"/> <xsl:call-template name="flip_internal"> <xsl:with-param name="x" select="$x"/> <xsl:with-param name="s" select="string-length($x) + 1"/> </xsl:call-template> </xsl:template> <xsl:template name="flip_internal"> <xsl:param name="x"/> <xsl:param name="s"/> <xsl:if test="string-length($x) != 0"> <xsl:value-of select="$s - substring($x,1,1)"/> <xsl:call-template name="flip_internal"> <xsl:with-param name="x" select="substring($x,2)"/> <xsl:with-param name="s" select="$s"/> </xsl:call-template> </xsl:if> </xsl:template> <!-- XSLT Cookbook By Sal Mangano http://shop.oreilly.com/product/9780596003722.do --> <xsl:template name="reverse"> <xsl:param name="x"/> <xsl:variable name="len" select="string-length($x)"/> <xsl:choose> <xsl:when test="$len &lt; 2"> <xsl:value-of select="$x"/> </xsl:when> <xsl:when test="$len = 2"> <xsl:value-of select="substring($x,2,1)"/> <xsl:value-of select="substring($x,1,1)"/> </xsl:when> <xsl:otherwise> <xsl:variable name="mid" select="floor($len div 2)"/> <xsl:call-template name="reverse"> <xsl:with-param name="x" select="substring($x,$mid+1,$mid+1)"/> </xsl:call-template> <xsl:call-template name="reverse"> <xsl:with-param name="x" select="substring($x,1,$mid)"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="rotate"> <xsl:param name="x"/> <xsl:call-template name="rotate_internal"> <xsl:with-param name="x" select="$x"/> <xsl:with-param name="i" select="1"/> <xsl:with-param name="r"/> </xsl:call-template> </xsl:template> <xsl:template name="rotate_internal"> <xsl:param name="x"/> <xsl:param name="i"/> <xsl:param name="r"/> <xsl:variable name="p"> <xsl:call-template name="index-of"> <xsl:with-param name="input" select="$x"/> <xsl:with-param name="substr" select="$i"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$p = 0"> <xsl:value-of select="$r"/> </xsl:when> <xsl:otherwise> <xsl:call-template name="rotate_internal"> <xsl:with-param name="x" select="$x"/> <xsl:with-param name="i" select="$i + 1"/> <xsl:with-param name="r" select="concat($p,$r)"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <!-- XSLT Cookbook By Sal Mangano http://shop.oreilly.com/product/9780596003722.do --> <xsl:template name="index-of"> <xsl:param name="input"/> <xsl:param name="substr"/> <xsl:choose> <xsl:when test="contains($input,$substr)"> <xsl:value-of select="string-length(substring-before($input,$substr))+1"/> </xsl:when> <xsl:otherwise>0</xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> 


Here, the implementations of the reverse and index-of functions from the excellent book by Sal Mangano, XSLT Collection of Recipes, are used. The filtering of unique values ​​is the classic task of using the axis preceding-sibling:

distinct.xsl
 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="utils.xsl"/> <xsl:template match="result"> <result> <xsl:apply-templates/> </result> </xsl:template> <xsl:template match="position"> <xsl:variable name="l" select="preceding-sibling::position"/> <xsl:variable name="a" select="."/> <xsl:variable name="b"> <xsl:call-template name="flip"> <xsl:with-param name="x" select="$a"/> </xsl:call-template> </xsl:variable> <xsl:variable name="c"> <xsl:call-template name="reverse"> <xsl:with-param name="x" select="$b"/> </xsl:call-template> </xsl:variable> <xsl:variable name="d"> <xsl:call-template name="flip"> <xsl:with-param name="x" select="$c"/> </xsl:call-template> </xsl:variable> <xsl:variable name="e"> <xsl:call-template name="rotate"> <xsl:with-param name="x" select="$a"/> </xsl:call-template> </xsl:variable> <xsl:variable name="f"> <xsl:call-template name="flip"> <xsl:with-param name="x" select="$e"/> </xsl:call-template> </xsl:variable> <xsl:variable name="g"> <xsl:call-template name="reverse"> <xsl:with-param name="x" select="$f"/> </xsl:call-template> </xsl:variable> <xsl:variable name="h"> <xsl:call-template name="flip"> <xsl:with-param name="x" select="$g"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$b = $l"></xsl:when> <xsl:when test="$c = $l"></xsl:when> <xsl:when test="$d = $l"></xsl:when> <xsl:when test="$e = $l"></xsl:when> <xsl:when test="$f = $l"></xsl:when> <xsl:when test="$g = $l"></xsl:when> <xsl:when test="$h = $l"></xsl:when> <xsl:otherwise> <position> <xsl:value-of select="$a"/> </position> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet> 


Unfortunately, in XSLT 1.0, we cannot solve this problem with a single XPath expression, but XSLT 2.0 adds the ability to "wrap" templates into user-defined functions.

That's all! We solved the problem.

It remains to measure ...

Who is faster?


Measurements will be carried out using the following code:

Measure performance
  private final static String JAVAX_TRANSFORM_FACTORY = "javax.xml.transform.TransformerFactory"; private final static String SAXON_TRANSFORM_FACTORY = "net.sf.saxon.TransformerFactoryImpl"; private final static String XALAN_TRANSFORM_FACTORY = "org.apache.xalan.processor.TransformerFactoryImpl"; private void test_full(String size) throws Exception { System.setProperty(JAVAX_TRANSFORM_FACTORY, SAXON_TRANSFORM_FACTORY); TransformerFactory tf = TransformerFactory.newInstance(); tf.setURIResolver(new Resolver()); if (tf.getFeature(SAXSource.FEATURE) && tf.getFeature(SAXResult.FEATURE)) { SAXTransformerFactory stf = (SAXTransformerFactory)tf; TransformerHandler solve = stf.newTransformerHandler(new StreamSource("xsl/queens.xsl")); TransformerHandler filter = stf.newTransformerHandler(new StreamSource("xsl/distinct.xsl")); TransformerHandler view = stf.newTransformerHandler(new StreamSource("xsl/count.xsl")); Result result = new StreamResult(new File("xml/result.xml")); solve.setResult(new SAXResult(filter)); filter.setResult(new SAXResult(view)); view.setResult(result); Long timestamp = System.currentTimeMillis(); generateData(solve, size); System.out.println("Elapsed Time: " + Long.toString(System.currentTimeMillis() - timestamp)); } else { throw new Exception("Can''t support SAXSource or SAXResult"); } } 


By replacing the SAXON_TRANSFORM_FACTORY constant with XALAN_TRANSFORM_FACTORY, you can switch from one XSLT processor to another.

Unfortunately (for some strange reason), I could not get any of the versions of the Saxon-HE branch to work normally. The code works, but each call works incredibly slowly. For example, the TransformerFactory.newInstance () call worked for a few minutes! At the same time, one of the CPUs was completely utilized, and the code (judging by the debugger) most of the time was somewhere in the SHA-2 implementation area.

Fortunately, the earlier version from the next branch works fine. Here are the final pictures:

image

image

You may notice that, in this task, both XSLT processors operate at approximately the same speed. Thus, the use of Saxon can be justified only when using the functionality of XSLT 2.0 or XSLT 3.0.

All source texts, as usual, are laid out on GitHub .

Finally, I want to thank jonic for the help provided to them in preparing the article.

Source: https://habr.com/ru/post/203004/


All Articles