Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Andrey Adamovich - Groovy 2 Cookbook - 2013.pdf
Скачиваний:
44
Добавлен:
19.03.2016
Размер:
26.28 Mб
Скачать

Working with XML in Groovy

See also

ff Reading XML using XmlSlurper ff Searching in XML with GPath

ff http://groovy.codehaus.org/api/groovy/util/XmlParser.html ff http://groovy.codehaus.org/api/groovy/util/Node.html

ff http://groovy.codehaus.org/api/groovy/util/NodeList.html ff http://groovy.codehaus.org/GPath

Reading XML content with namespaces

XML namespaces, in a way, are similar to Java packages because they allow creating an additional context for grouping a set of elements. We already noted some differences in namespace handling for the XmlParser and XmlSlurper classes in the Reading XML using XmlParser and Reading XML using XmlSlurper recipes.

In this recipe, we dig a bit deeper into the details of XML namespace support in Groovy.

Getting ready

Let's use the same shakespeare.xml file we used for the Reading XML using XmlParser and Reading XML using XmlSlurper recipes.

How to do it...

XmlParser requires you to specify an element name exactly as it appears in the parsed XML, including the name of the prefix used in the actual XML content. This makes the code fragile because the namespace prefixes have to match.

1.In order to make code that is based on XmlParser more reliable in respect to namespaces, we can resort to the groovy.xml.Namespace class as shown in the following code:

import groovy.xml.Namespace

def xmlSource = new File('shakespeare.xml')

def bibliography = new XmlParser().parse(xmlSource)

def bib = new Namespace('http://bibliography.org', 'bib') def lit = new Namespace('http://literature.org', 'lit')

println bibliography[bib.author].text() println bibliography[lit.play].findAll { it[lit.year].text().toInteger() > 1592

}.size()

172

www.it-ebooks.info

Chapter 5

2.XmlSlurper has a similar API for declaring the prefixes and namespaces required to navigate the nodes, shown in the following code:

def xmlSource = new File('shakespeare.xml')

def bibliography = new XmlSlurper().parse(xmlSource)

bibliography.declareNamespace( bib: 'http://bibliography.org', lit: 'http://literature.org')

println bibliography.'bib:author' println bibliography.'lit:play'.findAll {

it.'lit:year'.toInteger() > 1592 }.size()

3.The output of both scripts is indistinguishable:

William Shakespare 3

How it works...

Both of the previous code snippets extract the author's name and the number of plays written after 1592 from our reference bibliography data XML document.

In the case of XmlParser, we declare two instances of the groovy.xml.Namespace class. When we fetch a property (for example, bibliography[bib.author]) from the Namespace object, this is really what happens:

ff The bib.author expression returns a value of javax.xml.namespace.QName type.

ff The array reference bibliography[bib.author] is translated by Groovy into a call to the getAt method of the groovy.util.Node class. This method accepts a QName as an argument and returns a node if the QName is found, as shown next:

QName ns = bib.author

Node n = bibliography.getAt(ns)

In the case of XmlSlurper, the groovy.util.slurpersupport.GPathResult class instance (returned by the parse method) has an additional method to declare namespaces called, not surprisingly, declareNamespace.

173

www.it-ebooks.info

Working with XML in Groovy

Please note that unlike XmlParser, the XmlSlurper implementation (or more specifically GPathResult) does not force you to depend on

namespaces or prefixes at all. You can refer to elements and attributes using their local names, and only resort to using namespace prefixes if there are same local names under different namespaces.

If you try to use a fully qualified name (for example. bib:author) before declaring the namespace within an XmlSlurper instance, you'll get no result back. Also, namespace prefixes defined by declareNamespace do not have to match prefixes appearing in the actual XML file.

The declareNamespace method takes a map of prefixes and namespaces. When those are defined, you can use them to reference elements using their fully qualified names.

There's more...

If you plan to switch between XmlParser and XmlSlurper implementations and you need to parse XML that uses namespaces, then the safest approach is to use the *: prefix for element or attribute queries. For example:

println bibliography.'*:author'.text()

See also

ff Reading XML using XmlParser ff Reading XML using XmlSlurper ff Searching in XML with GPath

ff http://groovy.codehaus.org/api/groovy/util/XmlParser.html ff http://groovy.codehaus.org/api/groovy/util/XmlSlurper.html ff http://groovy.codehaus.org/api/groovy/util/Node.html

ff http://groovy.codehaus.org/api/groovy/xml/Namespace.html

174

www.it-ebooks.info

Chapter 5

Searching in XML with GPath

When using XmlSlurper or XmlParser with Groovy (see the Reading XML using XmlSlurper and Reading XML using XmlParser recipes), the returned parsed result can be queried

using GPath. GPath is a way to navigate nested data structures in Groovy. Sometimes GPath is called an expression language integrated into Groovy; but, in fact, GPath does not have

a separate compiler or interpreter, it's just the way Groovy language and core classes are designed to make data structure navigation and modification concise and easy-to-read.

GPath, in certain ways, is similar to XPath (http://www.w3.org/TR/xpath/) that is used for querying XML data. The main difference is that it uses dots instead of slashes to navigate the XML hierarchy and it can be used for navigating the hierarchy of objects (Plain Old Java

Objects (POJOs) and Plain Old Groovy Objects (POGOs) respectively).

The GPath syntax closely resembles E4X (ECMAScript for XML), which is an ECMAScript extension for accessing XML content.

This recipe will show how to parse an XML document and query its values and attributes through GPath.

Getting ready

As usual, we start by defining an XML snippet to test out our XML recipe. In this recipe,

we deal with movies. IMDB (Internet Movie Database) at http://www.imdb.com is the de facto standard for everything you want to know around the cinema universe. IMDB stores information about pretty much every movie ever made, along with actors and production data. IMDB is also a good citizen of the Internet and exposes API to retrieve the same data available on the site in XML. The API is not really documented, so we decide to roll out our own XML format based on the data we fetch from IMDB. Let's print the information about some movies having the word groovy in the title (admittedly not many!).

def groovyMoviez = '''<?xml version="1.0" ?> <movie-result>

<movie id="tt0116288"> <title>Groovy Days</title> <year>1996</year> <director>Peter Bay</director> <country>Denmark</country> <stars>

Ken Vedsegaard, Sofie Gråbøl,

175

www.it-ebooks.info

Working with XML in Groovy

Martin Brygmann</stars> </movie>

<movie id="tt1189088"> <title>Cool and Groovy</title> <year>1956</year> <director>Will Cowan</director> <country>USA</country> <stars>Anita Day,

Buddy De Franco and

Buddy DeFranco Quartet</stars> </movie>

<movie id="tt1492859">

<title>Groovy: The Colors of Pacita Abad</title> <year>2005</year>

<director>Milo Sogueco</director> <country>Philippines</country> <stars/>

</movie> </movie-result>

'''

How to do it...

Let's see how we can navigate the previous XML with GPath.

1.The first step is to process the XML using XmlSlurper so that we can access the GPath API:

def results = new XmlSlurper().parseText(groovyMoviez)

2.The rest is just Groovy's magic. The XML structure is magically converted into a navigable object structure based on the tags and attribute of the XML document:

for (flick in results.movie) {

println "Movie with id ${flick.@id} " + "is directed by ${flick.director}"

}

3.The code snippet yields the following output:

Movie with id tt0116288 is directed by Peter Bay Movie with id tt1189088 is directed by Will Cowan Movie with id tt1492859 is directed by Milo Sogueco

The code becomes extremely fluent and ceremony-free, just code and data.

176

www.it-ebooks.info

Chapter 5

4.Want to see something more awesome? Check out what we can do with the Groovy's spread-dot operator (*.):

results.movie*.title.each { println "- ${it}" } results.movie.findAll {

it.year.toInteger() > 1990 }*.title.each {

println "title: ${it}"

}

5.The output will be as follows: title: Groovy Days

title: Groovy: The Colors of Pacita Abad

6.Searching inside the XML document for specific content is also pretty simple:

results.movie.findAll { it.director.text().contains('Milo')

}.each {

println "- ${it.title}"

}

The findAll method is applied to the movie node and the closure passed to the findAll method checks for the presence of the word Milo in the director tag.

7.Should you need to search across all the nodes of the document (as opposed to a specific set of children, such as movie), it is possible to use the depthFirst method (or its shortcut **) on the root node:

results.'**'.findAll { it.director.text().contains('Milo')

}.each {

println "- ${it.title}"

}

8.The output will be as follows:

- Groovy: The Colors of Pacita Abad

How it works...

In the first step, we create a parser, an XmlSlurper in this case.

Please note how this time, we use the parseText method to read a string containing a valid XML document.

177

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]