Sorting XML nodes

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.

How to do it...

For this recipe, we will reuse the same XML document defined in the Searching in XML with GPath recipe.

  1. The grooviest way to sort nodes is to replace the entire element tree with a sorted one:
    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)
  2. This will yield:
    <?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>

How it works...

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.

There's more...

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.

Note

Please note that, in the case of the year element, the value is converted to an integer value for the sorting algorithm to work correctly.

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()
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.20.90