0
# XML Navigation
1
2
The GPathResult hierarchy provides XPath-like navigation through parsed XML documents from XmlSlurper, offering powerful querying and traversal capabilities with lazy evaluation.
3
4
## GPathResult
5
6
The base class for all XML navigation results, providing core navigation and querying functionality.
7
8
```java { .api }
9
public abstract class GPathResult implements Writable, Buildable, Iterable<GPathResult> {
10
// Navigation methods
11
public GPathResult parent();
12
public GPathResult children();
13
public GPathResult pop();
14
public String name();
15
public String lookupNamespace(String prefix);
16
17
// Content access
18
public String toString();
19
public abstract String text();
20
public abstract int size();
21
public boolean isEmpty();
22
23
// Type conversions
24
public Integer toInteger();
25
public Long toLong();
26
public Float toFloat();
27
public Double toDouble();
28
public BigDecimal toBigDecimal();
29
public BigInteger toBigInteger();
30
public URL toURL() throws MalformedURLException;
31
public URI toURI() throws URISyntaxException;
32
public Boolean toBoolean();
33
34
// Collection operations
35
public Object getAt(int index);
36
public Object getAt(IntRange range);
37
public void putAt(int index, Object newValue);
38
public List<GPathResult> list();
39
public Iterator<GPathResult> iterator();
40
public Iterator<GPathResult> depthFirst();
41
public Iterator<GPathResult> breadthFirst();
42
43
// Content modification (creates new structures)
44
public Object leftShift(Object newValue);
45
public Object plus(Object newValue);
46
public GPathResult declareNamespace(Map<String, String> newNamespaceMapping);
47
48
// Filtering and searching
49
public abstract GPathResult find(Closure closure);
50
public abstract GPathResult findAll(Closure closure);
51
52
// Utility methods
53
public void writeTo(Writer out) throws IOException;
54
public boolean asBoolean();
55
}
56
```
57
58
## Navigation Patterns
59
60
### Basic Element Navigation
61
62
```groovy
63
def slurper = new XmlSlurper()
64
def catalog = slurper.parseText('''
65
<catalog>
66
<books>
67
<book id="1">
68
<title>The Great Gatsby</title>
69
<author>F. Scott Fitzgerald</author>
70
<genres>
71
<genre>Classic</genre>
72
<genre>Fiction</genre>
73
</genres>
74
</book>
75
<book id="2">
76
<title>1984</title>
77
<author>George Orwell</author>
78
<genres>
79
<genre>Dystopian</genre>
80
<genre>Political</genre>
81
</genres>
82
</book>
83
</books>
84
</catalog>
85
''')
86
87
// Direct navigation
88
println catalog.books.book.title // All book titles
89
println catalog.books.book[0].title // First book title
90
println catalog.books.book.size() // Number of books
91
92
// Attribute access
93
println catalog.books.book.'@id' // All book IDs
94
println catalog.books.book[0].'@id' // First book's ID
95
96
// Nested navigation
97
println catalog.books.book.genres.genre // All genres from all books
98
println catalog.books.book[0].genres.genre.text() // Genres of first book
99
```
100
101
### Deep Navigation with ** Operator
102
103
```groovy
104
// Find all elements with a specific name anywhere in the tree
105
println catalog.'**'.findAll { it.name() == 'genre' } // All genre elements
106
println catalog.'**'.title // All titles at any level
107
println catalog.'**'.findAll { it.'@id' } // All elements with id attribute
108
109
// Complex deep searches
110
def allTextNodes = catalog.'**'.findAll {
111
it.text() && !it.children()
112
} // All leaf text nodes
113
114
def allElements = catalog.'**'.findAll {
115
it.name()
116
} // All elements (non-text nodes)
117
```
118
119
### Filtering and Searching
120
121
```groovy
122
// Find elements by attribute values
123
def book1 = catalog.books.book.find { it.'@id' == '1' }
124
def classicBooks = catalog.books.book.findAll {
125
it.genres.genre.find { it.text() == 'Classic' }
126
}
127
128
// Find by content
129
def orwellBooks = catalog.books.book.findAll {
130
it.author.text().contains('Orwell')
131
}
132
133
// Complex filtering
134
def longTitles = catalog.books.book.findAll {
135
it.title.text().length() > 10
136
}
137
138
// Combine filters
139
def fictionByFitzgerald = catalog.books.book.findAll { book ->
140
book.author.text().contains('Fitzgerald') &&
141
book.genres.genre.find { it.text() == 'Fiction' }
142
}
143
```
144
145
## GPathResult Implementations
146
147
### Node
148
149
Represents a single XML element in the navigation result.
150
151
```java { .api }
152
public class Node extends GPathResult {
153
public Node(Node parent, String name, Map<String, String> attributes,
154
Map<String, String> attributeNamespaces, Map<String, String> namespaceTagHints);
155
156
@Override
157
public String text();
158
@Override
159
public int size();
160
@Override
161
public GPathResult find(Closure closure);
162
@Override
163
public GPathResult findAll(Closure closure);
164
}
165
```
166
167
### NodeChild
168
169
Represents a child element accessed from a parent node.
170
171
```java { .api }
172
public class NodeChild extends GPathResult {
173
public NodeChild(Node node, GPathResult parent, String namespacePrefix,
174
Map<String, String> namespaceTagHints);
175
176
@Override
177
public String text();
178
@Override
179
public int size();
180
@Override
181
public GPathResult find(Closure closure);
182
@Override
183
public GPathResult findAll(Closure closure);
184
}
185
```
186
187
### NodeChildren
188
189
Represents a collection of child elements.
190
191
```java { .api }
192
public class NodeChildren extends GPathResult {
193
public NodeChildren(Node parent, String name, String namespacePrefix,
194
Map<String, String> namespaceTagHints);
195
196
@Override
197
public String text();
198
@Override
199
public int size();
200
@Override
201
public GPathResult find(Closure closure);
202
@Override
203
public GPathResult findAll(Closure closure);
204
}
205
```
206
207
### Attributes
208
209
Provides access to XML attributes as navigable results.
210
211
```java { .api }
212
public class Attributes extends GPathResult {
213
public Attributes(Node parent, String name, String namespacePrefix,
214
Map<String, String> namespaceTagHints);
215
216
@Override
217
public String text();
218
@Override
219
public int size();
220
@Override
221
public GPathResult find(Closure closure);
222
@Override
223
public GPathResult findAll(Closure closure);
224
}
225
```
226
227
## Advanced Navigation Examples
228
229
### Iterating Through Results
230
231
```groovy
232
// Iterate over all books
233
catalog.books.book.each { book ->
234
println "Book: ${book.title} by ${book.author}"
235
println "ID: ${book.'@id'}"
236
book.genres.genre.each { genre ->
237
println " Genre: ${genre}"
238
}
239
}
240
241
// Using iterator explicitly
242
def bookIterator = catalog.books.book.iterator()
243
while (bookIterator.hasNext()) {
244
def book = bookIterator.next()
245
println book.title.text()
246
}
247
248
// Depth-first traversal
249
catalog.depthFirst().each { node ->
250
if (node.text() && !node.children()) {
251
println "Leaf node: ${node.name()} = ${node.text()}"
252
}
253
}
254
```
255
256
### Content Type Conversions
257
258
```groovy
259
def prices = slurper.parseText('''
260
<products>
261
<product price="12.99">Book</product>
262
<product price="25.50">Magazine</product>
263
<product active="true">Subscription</product>
264
<product count="42">Bundle</product>
265
</products>
266
''')
267
268
// Convert to different types
269
prices.product.each { product ->
270
if (product.'@price') {
271
def price = product.'@price'.toDouble()
272
println "Price: \$${price}"
273
}
274
if (product.'@active') {
275
def active = product.'@active'.toBoolean()
276
println "Active: ${active}"
277
}
278
if (product.'@count') {
279
def count = product.'@count'.toInteger()
280
println "Count: ${count}"
281
}
282
}
283
```
284
285
### Working with Namespaces
286
287
```groovy
288
def nsXml = '''
289
<root xmlns:book="http://example.com/book"
290
xmlns:author="http://example.com/author">
291
<book:catalog>
292
<book:item>
293
<book:title>Sample Book</book:title>
294
<author:name>John Doe</author:name>
295
</book:item>
296
</book:catalog>
297
</root>
298
'''
299
300
def nsResult = slurper.parseText(nsXml)
301
302
// Access namespaced elements
303
println nsResult.'book:catalog'.'book:item'.'book:title'
304
println nsResult.'book:catalog'.'book:item'.'author:name'
305
306
// Declare namespace mappings for easier access
307
def mapped = nsResult.declareNamespace([
308
'b': 'http://example.com/book',
309
'a': 'http://example.com/author'
310
])
311
312
println mapped.'b:catalog'.'b:item'.'b:title'
313
println mapped.'b:catalog'.'b:item'.'a:name'
314
315
// Look up namespace URIs
316
println nsResult.lookupNamespace('book') // "http://example.com/book"
317
println nsResult.lookupNamespace('author') // "http://example.com/author"
318
```
319
320
### Complex Queries
321
322
```groovy
323
def library = slurper.parseText('''
324
<library>
325
<section name="Fiction">
326
<book rating="4.5" available="true">
327
<title>The Great Gatsby</title>
328
<author>F. Scott Fitzgerald</author>
329
<year>1925</year>
330
</book>
331
<book rating="4.8" available="false">
332
<title>To Kill a Mockingbird</title>
333
<author>Harper Lee</author>
334
<year>1960</year>
335
</book>
336
</section>
337
<section name="Science">
338
<book rating="4.2" available="true">
339
<title>A Brief History of Time</title>
340
<author>Stephen Hawking</author>
341
<year>1988</year>
342
</book>
343
</section>
344
</library>
345
''')
346
347
// Complex filtering with multiple conditions
348
def highRatedAvailableBooks = library.section.book.findAll { book ->
349
book.'@rating'.toDouble() > 4.0 &&
350
book.'@available'.toBoolean()
351
}
352
353
highRatedAvailableBooks.each { book ->
354
println "${book.title} (${book.'@rating'}) - Available"
355
}
356
357
// Find books published after a certain year
358
def modernBooks = library.'**'.book.findAll {
359
it.year.toInteger() > 1950
360
}
361
362
// Get all unique authors
363
def authors = library.'**'.author.text().unique()
364
println "Authors: ${authors.join(', ')}"
365
366
// Calculate average rating
367
def ratings = library.'**'.book.'@rating'*.toDouble()
368
def avgRating = ratings.sum() / ratings.size()
369
println "Average rating: ${avgRating}"
370
```
371
372
## Performance Considerations
373
374
GPathResult uses lazy evaluation, which provides several benefits:
375
376
```groovy
377
// Lazy evaluation - elements are only processed when accessed
378
def largeDoc = slurper.parseText(veryLargeXmlString)
379
def firstMatch = largeDoc.'**'.findAll { it.name() == 'target' }[0]
380
// Only processes until first match is found
381
382
// Memory efficient navigation
383
largeDoc.section.each { section ->
384
// Process one section at a time without loading entire document into memory
385
processSection(section)
386
}
387
388
// Avoid unnecessary iterations
389
def hasErrors = largeDoc.'**'.find { it.name() == 'error' }
390
if (hasErrors) {
391
// Only searches until first error is found
392
handleErrors()
393
}
394
```
395
396
## Working with Large Documents
397
398
```groovy
399
// Streaming-like processing with slurper
400
def processLargeDocument(xmlFile) {
401
def slurper = new XmlSlurper()
402
def doc = slurper.parse(xmlFile)
403
404
// Process in chunks to manage memory
405
doc.dataSection.records.record.each { record ->
406
if (record.'@type' == 'important') {
407
processImportantRecord(record)
408
}
409
}
410
}
411
412
// Selective loading
413
def loadOnlyRequired = { xmlFile ->
414
def slurper = new XmlSlurper()
415
def doc = slurper.parse(xmlFile)
416
417
// Only load what's needed
418
return doc.configuration.findAll { it.'@enabled' == 'true' }
419
}
420
```