Sometimes it is important to preserve XML data in certain order. Ordered data is easier to read and search, and some computer systems may require data they consume to be sorted. One of the most basic requirements that arise when dealing with XML is being able to sort nodes either by node value or attribute value. In this recipe, we are going to go through a couple of ways of achieving this with Groovy.
For this recipe, we will reuse the same XML document defined in the Searching in XML with GPath recipe.
import groovy.xml.XmlUtil def groovyMoviez = ''' <?xml version="1.0" encoding="UTF-8"?> ... ''' def movieList = new XmlParser().parseText(groovyMoviez) movieList.value = movieList.movie.sort { it.title.text() } println XmlUtil.serialize(movieList)
<?xml version="1.0" encoding="UTF-8"?> <movie-result> <movie> <title>Cool and Groovy</title> ... </movie> <movie> <title>Groovy Days</title> ... </movie> <movie> <title>Groovy: The Colors of Pacita Abad</title> ... </movie> </movie-result>
In step 1, we call the dynamic method, movie
, which returns a NodeList
(see the Searching in XML with GPath recipe). To sort the elements of the NodeList
, we invoke the sort
method. The groovy.util.NodeList
super type is, in fact, an ArrayList
and benefits of the multitude of great features that come with Groovy collections. The closure passed to the sort
method defines the expression used to sort elements of the collection. In this case, we used movie titles, which are sorted in lexicographical order. If we need to sort movie collection in a descending order, then we can either reverse a collection:
movieList.value = movieList.movie.sort { it.title.text() }.reverse()
Or pass a more verbose closure that operates on two compared elements, to the sort
method:
movieList.value = movieList.movie.sort { a, b -> b.title.text() > a.title.text() }
At the end of the script, we use the groovy.xml.XmlUtil
class, which is a handy utility that Groovy provides for serializing XML node trees. Another alternative to serializing XML is the XmlNodePrinter
class, which we touch in the Modifying XML content recipe. In fact, XmlUtil
is using XmlNodePrinter
under the hood.
We can also create more complex sort expressions, such as:
movieList.value = movieList.movie.sort { a, b -> a.year.text().toInteger() <=> b.year.text().toInteger() ?: a.country.text() <=> b.country.text() }
In this example, the Elvis operator (?:
) is used, which is a handy shortcut for a ternary operator. First we compare movie release years, and if they match, we sort based on country name. The spaceship operator (<=>
) it's just a shortcut for the compareTo
function. It returns -1 when the left side is smaller than the right side, 1 if the left side is greater, and 0 if they are equal.
For the sake of completeness, we will also demonstrate how to sort an XML tree using XSLT. This approach is not exactly easy on the eye, but can be used also as a general way to use XSLT transformations with Groovy.
The first step is to declare an XSLT transformation set. The sorting is applied to the title
tag:
def sortXSLT = '''<?xml version="1.0" encoding="ISO-8859-1"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"> <xsl:sort select="title"/> </xsl:apply-templates> </xsl:copy> </xsl:template> </xsl:stylesheet>'''
Next, you need to add a bunch of imports to your code:
import javax.xml.transform.TransformerFactory import javax.xml.transform.stream.StreamResult import javax.xml.transform.stream.StreamSource
Finally, here is the code to run the transformation on the original XML document:
def factory = TransformerFactory.newInstance() def xsltSource = new StreamSource(new StringReader(sortXSLT)) def transformer = factory.newTransformer(xsltSource) def source = new StreamSource(new StringReader(groovyMoviez)) def result = new StreamResult(new StringWriter()) transformer.transform(source, result) println result.writer.toString()