0
# DOM4J XPath Support
1
2
DOM4J provides comprehensive XPath 1.0 support through integration with the Jaxen XPath library. This section covers XPath expressions, pattern matching, namespace support, and advanced querying capabilities for powerful document navigation and content selection.
3
4
## XPath Interface
5
6
The XPath interface represents compiled XPath expressions that can be executed against DOM4J documents and nodes. XPath expressions are compiled once and can be reused for efficient querying.
7
8
### Package and Import
9
```java { .api }
10
import org.dom4j.XPath;
11
import org.dom4j.Node;
12
import org.dom4j.DocumentHelper;
13
import org.dom4j.InvalidXPathException;
14
import org.jaxen.VariableContext;
15
import org.jaxen.FunctionContext;
16
import org.jaxen.NamespaceContext;
17
```
18
19
### XPath Interface Methods
20
```java { .api }
21
public interface XPath extends NodeFilter {
22
// Expression text
23
String getText();
24
25
// Node filtering (from NodeFilter)
26
boolean matches(Node node);
27
28
// Expression evaluation
29
Object evaluate(Object context);
30
31
// Node selection
32
List<Node> selectNodes(Object context);
33
List<Node> selectNodes(Object context, XPath sortXPath);
34
List<Node> selectNodes(Object context, XPath sortXPath, boolean distinct);
35
Node selectSingleNode(Object context);
36
37
// Value extraction
38
String valueOf(Object context);
39
Number numberValueOf(Object context);
40
boolean booleanValueOf(Object context);
41
42
// Result sorting
43
void sort(List<Node> list);
44
void sort(List<Node> list, boolean distinct);
45
46
// Context configuration
47
FunctionContext getFunctionContext();
48
void setFunctionContext(FunctionContext functionContext);
49
NamespaceContext getNamespaceContext();
50
void setNamespaceContext(NamespaceContext namespaceContext);
51
void setNamespaceURIs(Map<String, String> map);
52
VariableContext getVariableContext();
53
void setVariableContext(VariableContext variableContext);
54
}
55
```
56
57
### Creating XPath Expressions
58
```java { .api }
59
// Create XPath expressions
60
XPath elementXPath = DocumentHelper.createXPath("//element");
61
XPath attributeXPath = DocumentHelper.createXPath("//@attribute");
62
XPath specificXPath = DocumentHelper.createXPath("//book[@isbn='123-456-789']");
63
64
// With namespace support
65
XPath namespacedXPath = DocumentHelper.createXPath("//ns:element/@ns:attribute");
66
namespacedXPath.setNamespaceURIs(Map.of("ns", "http://example.com/namespace"));
67
68
// With variable context
69
VariableContext variables = new SimpleVariableContext();
70
variables.setVariableValue("id", "P123");
71
XPath variableXPath = DocumentHelper.createXPath("//product[@id=$id]", variables);
72
```
73
74
### Basic XPath Operations
75
```java { .api }
76
Document document = createSampleDocument();
77
78
// Select multiple nodes
79
XPath booksXPath = DocumentHelper.createXPath("//book");
80
List<Node> books = booksXPath.selectNodes(document);
81
82
for (Node bookNode : books) {
83
Element book = (Element) bookNode;
84
System.out.println("Book: " + book.attributeValue("title"));
85
}
86
87
// Select single node
88
XPath firstBookXPath = DocumentHelper.createXPath("//book[1]");
89
Node firstBook = firstBookXPath.selectSingleNode(document);
90
91
// Extract values
92
XPath titleXPath = DocumentHelper.createXPath("//book[1]/title/text()");
93
String title = titleXPath.valueOf(document);
94
95
XPath priceXPath = DocumentHelper.createXPath("//book[1]/@price");
96
Number price = priceXPath.numberValueOf(document);
97
98
// Boolean evaluation
99
XPath hasIsbnXPath = DocumentHelper.createXPath("//book[@isbn]");
100
boolean hasIsbn = hasIsbnXPath.booleanValueOf(document);
101
102
// Filter nodes
103
XPath expensiveBooksXPath = DocumentHelper.createXPath("//book[@price > 50]");
104
List<Node> expensiveBooks = expensiveBooksXPath.selectNodes(document);
105
```
106
107
## DefaultXPath Implementation
108
109
DefaultXPath is the concrete implementation of the XPath interface, providing Jaxen-based XPath processing.
110
111
### Package and Import
112
```java { .api }
113
import org.dom4j.xpath.DefaultXPath;
114
```
115
116
### Advanced XPath Usage
117
```java { .api }
118
// Direct DefaultXPath creation
119
XPath xpath = new DefaultXPath("//book[position() > 1]");
120
121
// Complex XPath expressions
122
XPath complexXPath = new DefaultXPath(
123
"//book[author='John Doe' and @price < 100]/title/text()");
124
125
List<Node> results = complexXPath.selectNodes(document);
126
for (Node result : results) {
127
System.out.println("Title: " + result.getText());
128
}
129
130
// XPath with functions
131
XPath functionXPath = new DefaultXPath(
132
"//book[contains(title, 'XML') and string-length(@isbn) = 13]");
133
134
// Numerical operations
135
XPath mathXPath = new DefaultXPath("sum(//book/@price)");
136
Number totalPrice = mathXPath.numberValueOf(document);
137
138
XPath countXPath = new DefaultXPath("count(//book)");
139
Number bookCount = countXPath.numberValueOf(document);
140
```
141
142
### Namespace-Aware XPath
143
```java { .api }
144
// Document with namespaces
145
String namespaceXML = """
146
<catalog xmlns:books="http://example.com/books"
147
xmlns:meta="http://example.com/metadata">
148
<books:book meta:id="1">
149
<books:title>XML Guide</books:title>
150
<books:author>Jane Smith</books:author>
151
</books:book>
152
</catalog>
153
""";
154
155
Document nsDocument = DocumentHelper.parseText(namespaceXML);
156
157
// Create namespace-aware XPath
158
XPath nsXPath = DocumentHelper.createXPath("//books:book[@meta:id='1']/books:title");
159
160
// Configure namespaces
161
Map<String, String> namespaces = Map.of(
162
"books", "http://example.com/books",
163
"meta", "http://example.com/metadata"
164
);
165
nsXPath.setNamespaceURIs(namespaces);
166
167
// Execute namespace-aware query
168
Node titleNode = nsXPath.selectSingleNode(nsDocument);
169
String title = titleNode.getText(); // "XML Guide"
170
171
// Alternative namespace configuration
172
DefaultNamespaceContext nsContext = new DefaultNamespaceContext();
173
nsContext.addNamespace("b", "http://example.com/books");
174
nsContext.addNamespace("m", "http://example.com/metadata");
175
176
XPath contextXPath = new DefaultXPath("//b:book[@m:id='1']");
177
contextXPath.setNamespaceContext(nsContext);
178
```
179
180
## XPath Expressions and Syntax
181
182
### Node Selection Patterns
183
```java { .api }
184
// Axis-based selection
185
XPath childXPath = DocumentHelper.createXPath("/catalog/child::book");
186
XPath descendantXPath = DocumentHelper.createXPath("//descendant::title");
187
XPath followingXPath = DocumentHelper.createXPath("//book[1]/following-sibling::book");
188
XPath parentXPath = DocumentHelper.createXPath("//title/parent::book");
189
190
// Attribute selection
191
XPath allAttributesXPath = DocumentHelper.createXPath("//book/@*");
192
XPath specificAttrXPath = DocumentHelper.createXPath("//book/@isbn");
193
194
// Text node selection
195
XPath allTextXPath = DocumentHelper.createXPath("//text()");
196
XPath titleTextXPath = DocumentHelper.createXPath("//title/text()");
197
198
// Mixed content
199
XPath mixedXPath = DocumentHelper.createXPath("//description//text()");
200
```
201
202
### Predicates and Filtering
203
```java { .api }
204
// Position-based predicates
205
XPath firstXPath = DocumentHelper.createXPath("//book[1]");
206
XPath lastXPath = DocumentHelper.createXPath("//book[last()]");
207
XPath secondToLastXPath = DocumentHelper.createXPath("//book[last()-1]");
208
XPath positionRangeXPath = DocumentHelper.createXPath("//book[position() > 2 and position() < 5]");
209
210
// Attribute-based predicates
211
XPath hasAttributeXPath = DocumentHelper.createXPath("//book[@isbn]");
212
XPath attributeValueXPath = DocumentHelper.createXPath("//book[@category='fiction']");
213
XPath attributeComparisonXPath = DocumentHelper.createXPath("//book[@price > 25.00]");
214
215
// Element content predicates
216
XPath hasElementXPath = DocumentHelper.createXPath("//book[author]");
217
XPath elementValueXPath = DocumentHelper.createXPath("//book[author='John Doe']");
218
XPath elementComparisonXPath = DocumentHelper.createXPath("//book[price > 20]");
219
220
// Complex logical predicates
221
XPath complexXPath = DocumentHelper.createXPath(
222
"//book[@category='technical' and (author='Smith' or author='Jones') and @price < 50]");
223
```
224
225
### XPath Functions
226
```java { .api }
227
// String functions
228
XPath containsXPath = DocumentHelper.createXPath("//book[contains(title, 'XML')]");
229
XPath startsWithXPath = DocumentHelper.createXPath("//book[starts-with(title, 'Advanced')]");
230
XPath substringXPath = DocumentHelper.createXPath("//book[substring(isbn, 1, 3) = '978']");
231
XPath normalizeXPath = DocumentHelper.createXPath("//book[normalize-space(title) != '']");
232
233
// Numeric functions
234
XPath sumXPath = DocumentHelper.createXPath("sum(//book/@price)");
235
XPath countXPath = DocumentHelper.createXPath("count(//book[@category='fiction'])");
236
XPath avgXPath = DocumentHelper.createXPath("sum(//book/@price) div count(//book)");
237
XPath minMaxXPath = DocumentHelper.createXPath("//book[@price = //book/@price[not(. > //book/@price)]]");
238
239
// Boolean functions
240
XPath notXPath = DocumentHelper.createXPath("//book[not(@out-of-print)]");
241
XPath trueXPath = DocumentHelper.createXPath("//book[true()]");
242
243
// Node set functions
244
XPath localNameXPath = DocumentHelper.createXPath("//book[local-name() = 'book']");
245
XPath namespaceUriXPath = DocumentHelper.createXPath("//*[namespace-uri() = 'http://example.com/books']");
246
```
247
248
## Variable and Function Contexts
249
250
### Variable Context
251
```java { .api }
252
import org.jaxen.VariableContext;
253
import org.jaxen.SimpleVariableContext;
254
255
// Simple variable context
256
SimpleVariableContext variables = new SimpleVariableContext();
257
variables.setVariableValue("category", "fiction");
258
variables.setVariableValue("maxPrice", 30.0);
259
variables.setVariableValue("author", "Smith");
260
261
XPath variableXPath = DocumentHelper.createXPath(
262
"//book[@category=$category and @price <= $maxPrice and author=$author]");
263
variableXPath.setVariableContext(variables);
264
265
List<Node> results = variableXPath.selectNodes(document);
266
267
// Custom variable context
268
class CustomVariableContext implements VariableContext {
269
private final Map<String, Object> variables = new HashMap<>();
270
271
public CustomVariableContext() {
272
variables.put("today", LocalDate.now().toString());
273
variables.put("userId", getCurrentUserId());
274
}
275
276
@Override
277
public Object getVariableValue(String namespaceURI, String prefix, String localName) {
278
return variables.get(localName);
279
}
280
281
public void setVariable(String name, Object value) {
282
variables.put(name, value);
283
}
284
}
285
286
CustomVariableContext customVars = new CustomVariableContext();
287
customVars.setVariable("status", "active");
288
289
XPath customXPath = DocumentHelper.createXPath("//item[@status=$status and @lastUpdate >= $today]");
290
customXPath.setVariableContext(customVars);
291
```
292
293
### Function Context
294
```java { .api }
295
import org.jaxen.FunctionContext;
296
import org.jaxen.Function;
297
import org.jaxen.Context;
298
import org.jaxen.function.StringFunction;
299
300
// Custom function implementation
301
class UpperCaseFunction implements Function {
302
@Override
303
public Object call(Context context, List args) throws Exception {
304
if (args.size() != 1) {
305
throw new Exception("upper-case() requires exactly 1 argument");
306
}
307
308
String value = StringFunction.evaluate(args.get(0), context.getNavigator());
309
return value.toUpperCase();
310
}
311
}
312
313
// Register custom function
314
FunctionContext functionContext = XPathFunctionContext.getInstance();
315
functionContext = new CustomFunctionContext(functionContext);
316
((CustomFunctionContext) functionContext).registerFunction(null, "upper-case", new UpperCaseFunction());
317
318
// Use custom function in XPath
319
XPath functionXPath = DocumentHelper.createXPath("//book[upper-case(title) = 'XML GUIDE']");
320
functionXPath.setFunctionContext(functionContext);
321
322
// Custom function context class
323
class CustomFunctionContext implements FunctionContext {
324
private final FunctionContext delegate;
325
private final Map<String, Function> customFunctions = new HashMap<>();
326
327
public CustomFunctionContext(FunctionContext delegate) {
328
this.delegate = delegate;
329
}
330
331
public void registerFunction(String namespaceURI, String localName, Function function) {
332
String key = (namespaceURI != null ? namespaceURI + ":" : "") + localName;
333
customFunctions.put(key, function);
334
}
335
336
@Override
337
public Function getFunction(String namespaceURI, String prefix, String localName) throws UnresolvableException {
338
String key = (namespaceURI != null ? namespaceURI + ":" : "") + localName;
339
Function custom = customFunctions.get(key);
340
if (custom != null) {
341
return custom;
342
}
343
return delegate.getFunction(namespaceURI, prefix, localName);
344
}
345
}
346
```
347
348
## Pattern Matching and Filtering
349
350
### NodeFilter Interface
351
```java { .api }
352
import org.dom4j.NodeFilter;
353
354
// Create filters from XPath expressions
355
NodeFilter elementFilter = DocumentHelper.createXPathFilter("self::element");
356
NodeFilter attributeFilter = DocumentHelper.createXPathFilter("self::attribute()");
357
NodeFilter textFilter = DocumentHelper.createXPathFilter("self::text()");
358
359
// Custom node filter
360
NodeFilter customFilter = new NodeFilter() {
361
@Override
362
public boolean matches(Node node) {
363
if (node.getNodeType() == Node.ELEMENT_NODE) {
364
Element element = (Element) node;
365
return "book".equals(element.getName()) &&
366
element.attributeValue("category") != null;
367
}
368
return false;
369
}
370
};
371
372
// Use filters
373
List<Node> allNodes = document.selectNodes("//node()");
374
List<Node> filteredNodes = allNodes.stream()
375
.filter(elementFilter::matches)
376
.collect(Collectors.toList());
377
```
378
379
### Pattern-Based Selection
380
```java { .api }
381
import org.dom4j.rule.Pattern;
382
383
// Create patterns for XSLT-style matching
384
Pattern bookPattern = DocumentHelper.createPattern("book");
385
Pattern authorPattern = DocumentHelper.createPattern("book/author");
386
Pattern isbnPattern = DocumentHelper.createPattern("book/@isbn");
387
388
// Test pattern matching
389
List<Element> elements = document.selectNodes("//element");
390
for (Element element : elements) {
391
if (bookPattern.matches(element)) {
392
System.out.println("Found book element: " + element.getName());
393
}
394
}
395
396
// Complex patterns
397
Pattern complexPattern = DocumentHelper.createPattern("book[@category='fiction' and author]");
398
Pattern positionPattern = DocumentHelper.createPattern("book[position() = 1]");
399
```
400
401
## Advanced XPath Techniques
402
403
### Dynamic XPath Generation
404
```java { .api }
405
// Build XPath expressions dynamically
406
public class XPathBuilder {
407
private final StringBuilder xpath = new StringBuilder();
408
409
public XPathBuilder element(String name) {
410
if (xpath.length() > 0 && !xpath.toString().endsWith("/")) {
411
xpath.append("/");
412
}
413
xpath.append(name);
414
return this;
415
}
416
417
public XPathBuilder descendant(String name) {
418
xpath.append("//").append(name);
419
return this;
420
}
421
422
public XPathBuilder attribute(String name, String value) {
423
xpath.append("[@").append(name).append("='").append(value).append("']");
424
return this;
425
}
426
427
public XPathBuilder hasAttribute(String name) {
428
xpath.append("[@").append(name).append("]");
429
return this;
430
}
431
432
public XPathBuilder position(int pos) {
433
xpath.append("[").append(pos).append("]");
434
return this;
435
}
436
437
public XPathBuilder text(String text) {
438
xpath.append("[text()='").append(text).append("']");
439
return this;
440
}
441
442
public XPath build() throws InvalidXPathException {
443
return DocumentHelper.createXPath(xpath.toString());
444
}
445
446
@Override
447
public String toString() {
448
return xpath.toString();
449
}
450
}
451
452
// Use dynamic builder
453
XPath dynamicXPath = new XPathBuilder()
454
.descendant("book")
455
.attribute("category", "fiction")
456
.hasAttribute("isbn")
457
.build();
458
459
List<Node> books = dynamicXPath.selectNodes(document);
460
```
461
462
### XPath Performance Optimization
463
```java { .api }
464
// Cache compiled XPath expressions
465
public class XPathCache {
466
private final Map<String, XPath> cache = new ConcurrentHashMap<>();
467
private final Map<String, String> namespaces;
468
469
public XPathCache(Map<String, String> namespaces) {
470
this.namespaces = namespaces != null ? Map.copyOf(namespaces) : Map.of();
471
}
472
473
public XPath getXPath(String expression) throws InvalidXPathException {
474
return cache.computeIfAbsent(expression, expr -> {
475
try {
476
XPath xpath = DocumentHelper.createXPath(expr);
477
if (!namespaces.isEmpty()) {
478
xpath.setNamespaceURIs(namespaces);
479
}
480
return xpath;
481
} catch (InvalidXPathException e) {
482
throw new RuntimeException(e);
483
}
484
});
485
}
486
487
public List<Node> selectNodes(String expression, Object context) throws InvalidXPathException {
488
return getXPath(expression).selectNodes(context);
489
}
490
491
public Node selectSingleNode(String expression, Object context) throws InvalidXPathException {
492
return getXPath(expression).selectSingleNode(context);
493
}
494
495
public String valueOf(String expression, Object context) throws InvalidXPathException {
496
return getXPath(expression).valueOf(context);
497
}
498
}
499
500
// Use cached XPath
501
Map<String, String> namespaces = Map.of("ns", "http://example.com/");
502
XPathCache cache = new XPathCache(namespaces);
503
504
// These expressions are compiled once and cached
505
List<Node> books1 = cache.selectNodes("//ns:book[@category='fiction']", document);
506
List<Node> books2 = cache.selectNodes("//ns:book[@category='technical']", document);
507
List<Node> books3 = cache.selectNodes("//ns:book[@category='fiction']", document); // Uses cached version
508
```
509
510
### Sorting with XPath
511
```java { .api }
512
// Sort nodes using XPath expressions
513
XPath booksXPath = DocumentHelper.createXPath("//book");
514
List<Node> books = booksXPath.selectNodes(document);
515
516
// Sort by title
517
XPath titleSortXPath = DocumentHelper.createXPath("title");
518
titleSortXPath.sort(books);
519
520
// Sort by price (numeric)
521
XPath priceSortXPath = DocumentHelper.createXPath("number(@price)");
522
priceSortXPath.sort(books);
523
524
// Complex sorting with selectNodes
525
XPath sortedBooksXPath = DocumentHelper.createXPath("//book");
526
XPath sortKeyXPath = DocumentHelper.createXPath("concat(author, '|', title)");
527
List<Node> sortedBooks = sortedBooksXPath.selectNodes(document, sortKeyXPath);
528
529
// Remove duplicates while sorting
530
List<Node> uniqueSortedBooks = sortedBooksXPath.selectNodes(document, titleSortXPath, true);
531
```
532
533
### Error Handling in XPath
534
```java { .api }
535
// Handle XPath compilation errors
536
try {
537
XPath xpath = DocumentHelper.createXPath("//book[invalid syntax");
538
} catch (InvalidXPathException e) {
539
System.err.println("Invalid XPath syntax: " + e.getMessage());
540
// Provide user-friendly error message
541
String suggestion = suggestCorrection(e.getMessage());
542
System.err.println("Did you mean: " + suggestion);
543
}
544
545
// Handle runtime XPath errors
546
try {
547
XPath xpath = DocumentHelper.createXPath("//book/@price div //book/@quantity");
548
Number result = xpath.numberValueOf(document);
549
550
if (result.doubleValue() == Double.POSITIVE_INFINITY) {
551
System.err.println("Division by zero in XPath expression");
552
} else if (Double.isNaN(result.doubleValue())) {
553
System.err.println("Invalid numeric operation in XPath expression");
554
}
555
556
} catch (Exception e) {
557
System.err.println("XPath evaluation error: " + e.getMessage());
558
}
559
560
// Validate XPath expressions
561
public boolean isValidXPath(String expression) {
562
try {
563
DocumentHelper.createXPath(expression);
564
return true;
565
} catch (InvalidXPathException e) {
566
return false;
567
}
568
}
569
570
// Safe XPath execution with defaults
571
public String safeValueOf(String expression, Object context, String defaultValue) {
572
try {
573
XPath xpath = DocumentHelper.createXPath(expression);
574
String result = xpath.valueOf(context);
575
return result != null && !result.isEmpty() ? result : defaultValue;
576
} catch (Exception e) {
577
System.err.println("XPath error: " + e.getMessage());
578
return defaultValue;
579
}
580
}
581
```
582
583
## Integration with Document Navigation
584
585
### Combining XPath with DOM4J Navigation
586
```java { .api }
587
// Start with XPath, continue with DOM4J navigation
588
XPath authorXPath = DocumentHelper.createXPath("//author[1]");
589
Element firstAuthor = (Element) authorXPath.selectSingleNode(document);
590
591
// Continue with DOM4J navigation
592
Element book = firstAuthor.getParent();
593
String title = book.elementText("title");
594
String isbn = book.attributeValue("isbn");
595
596
// Navigate to siblings using DOM4J
597
Element nextBook = book.getParent().elements("book").stream()
598
.skip(book.getParent().elements("book").indexOf(book) + 1)
599
.findFirst()
600
.orElse(null);
601
602
// Use XPath on specific subtrees
603
if (nextBook != null) {
604
XPath relativeXPath = DocumentHelper.createXPath("./author");
605
Element nextAuthor = (Element) relativeXPath.selectSingleNode(nextBook);
606
}
607
```
608
609
DOM4J's XPath support provides powerful and flexible document querying capabilities. The integration with Jaxen enables full XPath 1.0 compliance while maintaining the performance and ease of use that DOM4J is known for. Whether for simple node selection or complex document analysis, XPath expressions provide a declarative and efficient way to work with XML content.