Element Selection
DomTrip provides two complementary APIs for finding elements in an XML document: mini-XPath expressions for concise string-based queries, and the ElementQuery API for programmatic, type-safe queries.
Mini-XPath Expressions
The select() and selectFirst() methods accept mini-XPath expression strings, providing a familiar and concise way to locate elements.
Basic Usage
Editor editor = new Editor(Document.of(xml));
Element root = editor.root();
// Find all dependencies
List<Element> deps = root.select("dependencies/dependency");
// Find first matching element
Optional<Element> junit = root.selectFirst("//dependency[groupId='junit']");
// Or query from the editor (evaluates against root)
List<Element> allDeps = editor.select("//dependency");
Optional<Element> version = editor.selectFirst("project/version");
Path Navigation
Navigate the document tree using path expressions:
// Direct child path
root.select("dependencies/dependency/groupId");
// Descendant search (anywhere below)
root.select("//groupId");
// Mixed: child then descendant
root.select("dependencies//groupId");
// Wildcard: any element
root.select("dependencies/*/groupId");
// Self and parent
element.select("."); // the element itself
element.select("dependency[1]/.."); // parent of first dependency
Predicates
Filter elements using predicates inside square brackets:
// Attribute presence
root.select("//item[@scope]");
// Attribute value (single or double quotes)
root.select("//dependency[@scope='test']");
root.select("//dependency[@scope=\"test\"]");
// Child text content
root.select("//dependency[groupId='org.junit']");
// Positional (1-based)
root.select("dependencies/dependency[1]"); // first
root.select("dependencies/dependency[last()]"); // last
// Multiple predicates (AND logic)
root.select("//dependency[scope='test'][artifactId='junit-api']");
Namespace Support
Expressions match both prefixed and local names:
// Given: <soap:Envelope xmlns:soap="..."><soap:Body>...
Element envelope = editor.root();
// Match by prefix
envelope.select("soap:Body/soap:Fault");
// Match by local name (also works)
envelope.select("Body");
// Descendant search with prefix
envelope.select("//soap:Fault");
Compiled Expressions
For repeated evaluation, compile once and reuse:
// Compile once
XPathExpression expr = XPathExpression.compile("//dependency[@scope='test']");
// Evaluate many times against different contexts
List<Element> results1 = expr.select(root1);
List<Element> results2 = expr.select(root2);
Optional<Element> first = expr.selectFirst(root1);
Supported Expression Syntax
| Expression | Description |
|---|---|
foo/bar/baz |
Direct child path |
//foo |
Descendant search (anywhere below) |
foo//bar |
bar anywhere under foo children |
. |
Current element |
.. |
Parent element |
* |
Any element (wildcard) |
foo[@attr] |
Element with attribute present |
foo[@attr='val'] |
Element with attribute value |
foo[bar='text'] |
Element with child text content |
foo[1] |
First element (1-based) |
foo[last()] |
Last element |
What is NOT Supported
This is a practical subset of XPath, not a full implementation:
- Full axis specifiers (
preceding-sibling::,ancestor::) - XPath functions (
contains(),normalize-space()) - Boolean operators (
and,or) - Arithmetic operators
- Union operator (
|)
ElementQuery API
The ElementQuery API provides a programmatic, fluent interface for finding elements with compile-time type safety.
Basic Usage
// Start a query from any element
Optional<Element> result = root.query()
.withName("dependency")
.withAttribute("scope", "test")
.first();
// Get all matches
List<Element> matches = root.query()
.withName("dependency")
.toList();
// Check existence
boolean hasTests = root.query()
.withName("dependency")
.withAttribute("scope", "test")
.exists();
Available Filters
root.query()
.withName("dependency") // match by element name
.withQName(qname) // match by qualified name
.withNamespace("http://...") // match by namespace URI
.withAttribute("scope") // has attribute
.withAttribute("scope", "test") // attribute equals value
.withTextContent("4.0.0") // exact text content
.containingText("example") // text contains substring
.atDepth(2) // at specific nesting depth
.withChildren() // has child elements
.withoutChildren() // leaf elements only
.where(e -> customCheck(e)) // custom predicate
.first(); // terminal: get first match
Terminal Operations
ElementQuery query = root.query().withName("dependency");
Optional<Element> first = query.first(); // first match
Stream<Element> stream = query.all(); // stream of matches
List<Element> list = query.toList(); // list of matches
long count = query.count(); // count matches
boolean found = query.exists(); // any match exists?
Choosing Between XPath and ElementQuery
| Use Case | Recommended API |
|---|---|
| Quick one-off queries | select("//dependency") |
| Queries from configuration/user input | select(userExpression) |
| Complex multi-criteria filtering | query().withName(...).withAttribute(...) |
| Custom predicate logic | query().where(e -> ...) |
| Repeated evaluation of same query | XPathExpression.compile(...) |
| Depth-limited searches | query().atDepth(2) |
Both APIs can be used together:
// Find dependencies with XPath, then refine with ElementQuery
List<Element> testDeps = root.select("//dependency[@scope='test']");
// Or use ElementQuery for complex conditions XPath can't express
Optional<Element> match = root.query()
.withName("dependency")
.where(dep -> {
String groupId = dep.child("groupId").map(Element::textContentTrimmed).orElse("");
return groupId.startsWith("org.junit");
})
.first();
Full XPath 1.0 via Jaxen Module
For applications that need full XPath 1.0 support -- including boolean operators (and, or), functions (contains(), starts-with(), not()), inequality (!=), union (|), and full axis navigation -- DomTrip provides an optional Jaxen module.
Setup
Add the domtrip-jaxen dependency alongside domtrip-core:
<dependency>
<groupId>eu.maveniverse.maven.domtrip</groupId>
<artifactId>domtrip-jaxen</artifactId>
<version>1.3.0</version>
</dependency>
Quick Queries
Use the XPath utility class for one-shot queries:
import eu.maveniverse.domtrip.jaxen.XPath;
// Boolean operators
List<Element> testJunit = XPath.select(root,
"//dependency[scope='test' and groupId='junit']");
// String functions
List<Element> springDeps = XPath.select(root,
"//dependency[contains(groupId, 'spring')]");
List<Element> orgDeps = XPath.select(root,
"//dependency[starts-with(groupId, 'org.')]");
// Negation
List<Element> nonOptional = XPath.select(root,
"//dependency[not(@optional)]");
// Inequality
List<Element> nonTest = XPath.select(root,
"//dependency[@scope!='test']");
// Union (combine results from multiple paths)
List<Element> ids = XPath.select(root,
"//groupId | //artifactId");
// First match
Optional<Element> first = XPath.selectFirst(root,
"//dependency[scope='test']");
Compiled Expressions
For repeated evaluation, compile the expression once:
import eu.maveniverse.domtrip.jaxen.DomTripXPath;
import eu.maveniverse.domtrip.jaxen.XPath;
DomTripXPath expr = XPath.compile("//dependency[scope='test']");
List<Element> results1 = expr.selectElements(root1);
List<Element> results2 = expr.selectElements(root2);
Full Axis Navigation
Jaxen supports all XPath axes that mini-XPath does not:
// Following siblings
XPath.select(element, "following-sibling::dependency");
// Preceding siblings
XPath.select(element, "preceding-sibling::*");
// Ancestors
XPath.select(element, "ancestor::project");
// Descendants (explicit axis)
XPath.select(root, "descendant::groupId");
Namespace-Aware Queries
Register namespace prefixes for namespace-aware queries:
DomTripXPath xpath = XPath.compile("//soap:Body/soap:Fault");
xpath.addNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
List<Element> results = xpath.selectElements(root);
Mini-XPath vs Jaxen
| Aspect | Mini-XPath (core) | Jaxen Module |
|---|---|---|
| Dependencies | None (built into core) | Requires jaxen:jaxen |
| Boolean operators | Not supported | and, or |
| Functions | last() only |
contains(), starts-with(), not(), string-length(), etc. |
| Inequality | Not supported | != |
| Union | Not supported | | |
| Full axes | .. only |
ancestor::, following-sibling::, preceding::, etc. |
| Use case | Simple queries, zero-dep environments | Complex queries, full XPath 1.0 |
Use mini-XPath for simple queries where you want zero dependencies. Use the Jaxen module when you need the full power of XPath 1.0.