XML processing utilities for Apache Groovy including markup builders, parsers, and navigation tools
—
The GPathResult hierarchy provides XPath-like navigation through parsed XML documents from XmlSlurper, offering powerful querying and traversal capabilities with lazy evaluation.
The base class for all XML navigation results, providing core navigation and querying functionality.
public abstract class GPathResult implements Writable, Buildable, Iterable<GPathResult> {
// Navigation methods
public GPathResult parent();
public GPathResult children();
public GPathResult pop();
public String name();
public String lookupNamespace(String prefix);
// Content access
public String toString();
public abstract String text();
public abstract int size();
public boolean isEmpty();
// Type conversions
public Integer toInteger();
public Long toLong();
public Float toFloat();
public Double toDouble();
public BigDecimal toBigDecimal();
public BigInteger toBigInteger();
public URL toURL() throws MalformedURLException;
public URI toURI() throws URISyntaxException;
public Boolean toBoolean();
// Collection operations
public Object getAt(int index);
public Object getAt(IntRange range);
public void putAt(int index, Object newValue);
public List<GPathResult> list();
public Iterator<GPathResult> iterator();
public Iterator<GPathResult> depthFirst();
public Iterator<GPathResult> breadthFirst();
// Content modification (creates new structures)
public Object leftShift(Object newValue);
public Object plus(Object newValue);
public GPathResult declareNamespace(Map<String, String> newNamespaceMapping);
// Filtering and searching
public abstract GPathResult find(Closure closure);
public abstract GPathResult findAll(Closure closure);
// Utility methods
public void writeTo(Writer out) throws IOException;
public boolean asBoolean();
}def slurper = new XmlSlurper()
def catalog = slurper.parseText('''
<catalog>
<books>
<book id="1">
<title>The Great Gatsby</title>
<author>F. Scott Fitzgerald</author>
<genres>
<genre>Classic</genre>
<genre>Fiction</genre>
</genres>
</book>
<book id="2">
<title>1984</title>
<author>George Orwell</author>
<genres>
<genre>Dystopian</genre>
<genre>Political</genre>
</genres>
</book>
</books>
</catalog>
''')
// Direct navigation
println catalog.books.book.title // All book titles
println catalog.books.book[0].title // First book title
println catalog.books.book.size() // Number of books
// Attribute access
println catalog.books.book.'@id' // All book IDs
println catalog.books.book[0].'@id' // First book's ID
// Nested navigation
println catalog.books.book.genres.genre // All genres from all books
println catalog.books.book[0].genres.genre.text() // Genres of first book// Find all elements with a specific name anywhere in the tree
println catalog.'**'.findAll { it.name() == 'genre' } // All genre elements
println catalog.'**'.title // All titles at any level
println catalog.'**'.findAll { it.'@id' } // All elements with id attribute
// Complex deep searches
def allTextNodes = catalog.'**'.findAll {
it.text() && !it.children()
} // All leaf text nodes
def allElements = catalog.'**'.findAll {
it.name()
} // All elements (non-text nodes)// Find elements by attribute values
def book1 = catalog.books.book.find { it.'@id' == '1' }
def classicBooks = catalog.books.book.findAll {
it.genres.genre.find { it.text() == 'Classic' }
}
// Find by content
def orwellBooks = catalog.books.book.findAll {
it.author.text().contains('Orwell')
}
// Complex filtering
def longTitles = catalog.books.book.findAll {
it.title.text().length() > 10
}
// Combine filters
def fictionByFitzgerald = catalog.books.book.findAll { book ->
book.author.text().contains('Fitzgerald') &&
book.genres.genre.find { it.text() == 'Fiction' }
}Represents a single XML element in the navigation result.
public class Node extends GPathResult {
public Node(Node parent, String name, Map<String, String> attributes,
Map<String, String> attributeNamespaces, Map<String, String> namespaceTagHints);
@Override
public String text();
@Override
public int size();
@Override
public GPathResult find(Closure closure);
@Override
public GPathResult findAll(Closure closure);
}Represents a child element accessed from a parent node.
public class NodeChild extends GPathResult {
public NodeChild(Node node, GPathResult parent, String namespacePrefix,
Map<String, String> namespaceTagHints);
@Override
public String text();
@Override
public int size();
@Override
public GPathResult find(Closure closure);
@Override
public GPathResult findAll(Closure closure);
}Represents a collection of child elements.
public class NodeChildren extends GPathResult {
public NodeChildren(Node parent, String name, String namespacePrefix,
Map<String, String> namespaceTagHints);
@Override
public String text();
@Override
public int size();
@Override
public GPathResult find(Closure closure);
@Override
public GPathResult findAll(Closure closure);
}Provides access to XML attributes as navigable results.
public class Attributes extends GPathResult {
public Attributes(Node parent, String name, String namespacePrefix,
Map<String, String> namespaceTagHints);
@Override
public String text();
@Override
public int size();
@Override
public GPathResult find(Closure closure);
@Override
public GPathResult findAll(Closure closure);
}// Iterate over all books
catalog.books.book.each { book ->
println "Book: ${book.title} by ${book.author}"
println "ID: ${book.'@id'}"
book.genres.genre.each { genre ->
println " Genre: ${genre}"
}
}
// Using iterator explicitly
def bookIterator = catalog.books.book.iterator()
while (bookIterator.hasNext()) {
def book = bookIterator.next()
println book.title.text()
}
// Depth-first traversal
catalog.depthFirst().each { node ->
if (node.text() && !node.children()) {
println "Leaf node: ${node.name()} = ${node.text()}"
}
}def prices = slurper.parseText('''
<products>
<product price="12.99">Book</product>
<product price="25.50">Magazine</product>
<product active="true">Subscription</product>
<product count="42">Bundle</product>
</products>
''')
// Convert to different types
prices.product.each { product ->
if (product.'@price') {
def price = product.'@price'.toDouble()
println "Price: \$${price}"
}
if (product.'@active') {
def active = product.'@active'.toBoolean()
println "Active: ${active}"
}
if (product.'@count') {
def count = product.'@count'.toInteger()
println "Count: ${count}"
}
}def nsXml = '''
<root xmlns:book="http://example.com/book"
xmlns:author="http://example.com/author">
<book:catalog>
<book:item>
<book:title>Sample Book</book:title>
<author:name>John Doe</author:name>
</book:item>
</book:catalog>
</root>
'''
def nsResult = slurper.parseText(nsXml)
// Access namespaced elements
println nsResult.'book:catalog'.'book:item'.'book:title'
println nsResult.'book:catalog'.'book:item'.'author:name'
// Declare namespace mappings for easier access
def mapped = nsResult.declareNamespace([
'b': 'http://example.com/book',
'a': 'http://example.com/author'
])
println mapped.'b:catalog'.'b:item'.'b:title'
println mapped.'b:catalog'.'b:item'.'a:name'
// Look up namespace URIs
println nsResult.lookupNamespace('book') // "http://example.com/book"
println nsResult.lookupNamespace('author') // "http://example.com/author"def library = slurper.parseText('''
<library>
<section name="Fiction">
<book rating="4.5" available="true">
<title>The Great Gatsby</title>
<author>F. Scott Fitzgerald</author>
<year>1925</year>
</book>
<book rating="4.8" available="false">
<title>To Kill a Mockingbird</title>
<author>Harper Lee</author>
<year>1960</year>
</book>
</section>
<section name="Science">
<book rating="4.2" available="true">
<title>A Brief History of Time</title>
<author>Stephen Hawking</author>
<year>1988</year>
</book>
</section>
</library>
''')
// Complex filtering with multiple conditions
def highRatedAvailableBooks = library.section.book.findAll { book ->
book.'@rating'.toDouble() > 4.0 &&
book.'@available'.toBoolean()
}
highRatedAvailableBooks.each { book ->
println "${book.title} (${book.'@rating'}) - Available"
}
// Find books published after a certain year
def modernBooks = library.'**'.book.findAll {
it.year.toInteger() > 1950
}
// Get all unique authors
def authors = library.'**'.author.text().unique()
println "Authors: ${authors.join(', ')}"
// Calculate average rating
def ratings = library.'**'.book.'@rating'*.toDouble()
def avgRating = ratings.sum() / ratings.size()
println "Average rating: ${avgRating}"GPathResult uses lazy evaluation, which provides several benefits:
// Lazy evaluation - elements are only processed when accessed
def largeDoc = slurper.parseText(veryLargeXmlString)
def firstMatch = largeDoc.'**'.findAll { it.name() == 'target' }[0]
// Only processes until first match is found
// Memory efficient navigation
largeDoc.section.each { section ->
// Process one section at a time without loading entire document into memory
processSection(section)
}
// Avoid unnecessary iterations
def hasErrors = largeDoc.'**'.find { it.name() == 'error' }
if (hasErrors) {
// Only searches until first error is found
handleErrors()
}// Streaming-like processing with slurper
def processLargeDocument(xmlFile) {
def slurper = new XmlSlurper()
def doc = slurper.parse(xmlFile)
// Process in chunks to manage memory
doc.dataSection.records.record.each { record ->
if (record.'@type' == 'important') {
processImportantRecord(record)
}
}
}
// Selective loading
def loadOnlyRequired = { xmlFile ->
def slurper = new XmlSlurper()
def doc = slurper.parse(xmlFile)
// Only load what's needed
return doc.configuration.findAll { it.'@enabled' == 'true' }
}Install with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-xml