Flexible XML framework for Java providing comprehensive XML processing capabilities
—
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.
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.
import org.dom4j.XPath;
import org.dom4j.Node;
import org.dom4j.DocumentHelper;
import org.dom4j.InvalidXPathException;
import org.jaxen.VariableContext;
import org.jaxen.FunctionContext;
import org.jaxen.NamespaceContext;public interface XPath extends NodeFilter {
// Expression text
String getText();
// Node filtering (from NodeFilter)
boolean matches(Node node);
// Expression evaluation
Object evaluate(Object context);
// Node selection
List<Node> selectNodes(Object context);
List<Node> selectNodes(Object context, XPath sortXPath);
List<Node> selectNodes(Object context, XPath sortXPath, boolean distinct);
Node selectSingleNode(Object context);
// Value extraction
String valueOf(Object context);
Number numberValueOf(Object context);
boolean booleanValueOf(Object context);
// Result sorting
void sort(List<Node> list);
void sort(List<Node> list, boolean distinct);
// Context configuration
FunctionContext getFunctionContext();
void setFunctionContext(FunctionContext functionContext);
NamespaceContext getNamespaceContext();
void setNamespaceContext(NamespaceContext namespaceContext);
void setNamespaceURIs(Map<String, String> map);
VariableContext getVariableContext();
void setVariableContext(VariableContext variableContext);
}// Create XPath expressions
XPath elementXPath = DocumentHelper.createXPath("//element");
XPath attributeXPath = DocumentHelper.createXPath("//@attribute");
XPath specificXPath = DocumentHelper.createXPath("//book[@isbn='123-456-789']");
// With namespace support
XPath namespacedXPath = DocumentHelper.createXPath("//ns:element/@ns:attribute");
namespacedXPath.setNamespaceURIs(Map.of("ns", "http://example.com/namespace"));
// With variable context
VariableContext variables = new SimpleVariableContext();
variables.setVariableValue("id", "P123");
XPath variableXPath = DocumentHelper.createXPath("//product[@id=$id]", variables);Document document = createSampleDocument();
// Select multiple nodes
XPath booksXPath = DocumentHelper.createXPath("//book");
List<Node> books = booksXPath.selectNodes(document);
for (Node bookNode : books) {
Element book = (Element) bookNode;
System.out.println("Book: " + book.attributeValue("title"));
}
// Select single node
XPath firstBookXPath = DocumentHelper.createXPath("//book[1]");
Node firstBook = firstBookXPath.selectSingleNode(document);
// Extract values
XPath titleXPath = DocumentHelper.createXPath("//book[1]/title/text()");
String title = titleXPath.valueOf(document);
XPath priceXPath = DocumentHelper.createXPath("//book[1]/@price");
Number price = priceXPath.numberValueOf(document);
// Boolean evaluation
XPath hasIsbnXPath = DocumentHelper.createXPath("//book[@isbn]");
boolean hasIsbn = hasIsbnXPath.booleanValueOf(document);
// Filter nodes
XPath expensiveBooksXPath = DocumentHelper.createXPath("//book[@price > 50]");
List<Node> expensiveBooks = expensiveBooksXPath.selectNodes(document);DefaultXPath is the concrete implementation of the XPath interface, providing Jaxen-based XPath processing.
import org.dom4j.xpath.DefaultXPath;// Direct DefaultXPath creation
XPath xpath = new DefaultXPath("//book[position() > 1]");
// Complex XPath expressions
XPath complexXPath = new DefaultXPath(
"//book[author='John Doe' and @price < 100]/title/text()");
List<Node> results = complexXPath.selectNodes(document);
for (Node result : results) {
System.out.println("Title: " + result.getText());
}
// XPath with functions
XPath functionXPath = new DefaultXPath(
"//book[contains(title, 'XML') and string-length(@isbn) = 13]");
// Numerical operations
XPath mathXPath = new DefaultXPath("sum(//book/@price)");
Number totalPrice = mathXPath.numberValueOf(document);
XPath countXPath = new DefaultXPath("count(//book)");
Number bookCount = countXPath.numberValueOf(document);// Document with namespaces
String namespaceXML = """
<catalog xmlns:books="http://example.com/books"
xmlns:meta="http://example.com/metadata">
<books:book meta:id="1">
<books:title>XML Guide</books:title>
<books:author>Jane Smith</books:author>
</books:book>
</catalog>
""";
Document nsDocument = DocumentHelper.parseText(namespaceXML);
// Create namespace-aware XPath
XPath nsXPath = DocumentHelper.createXPath("//books:book[@meta:id='1']/books:title");
// Configure namespaces
Map<String, String> namespaces = Map.of(
"books", "http://example.com/books",
"meta", "http://example.com/metadata"
);
nsXPath.setNamespaceURIs(namespaces);
// Execute namespace-aware query
Node titleNode = nsXPath.selectSingleNode(nsDocument);
String title = titleNode.getText(); // "XML Guide"
// Alternative namespace configuration
DefaultNamespaceContext nsContext = new DefaultNamespaceContext();
nsContext.addNamespace("b", "http://example.com/books");
nsContext.addNamespace("m", "http://example.com/metadata");
XPath contextXPath = new DefaultXPath("//b:book[@m:id='1']");
contextXPath.setNamespaceContext(nsContext);// Axis-based selection
XPath childXPath = DocumentHelper.createXPath("/catalog/child::book");
XPath descendantXPath = DocumentHelper.createXPath("//descendant::title");
XPath followingXPath = DocumentHelper.createXPath("//book[1]/following-sibling::book");
XPath parentXPath = DocumentHelper.createXPath("//title/parent::book");
// Attribute selection
XPath allAttributesXPath = DocumentHelper.createXPath("//book/@*");
XPath specificAttrXPath = DocumentHelper.createXPath("//book/@isbn");
// Text node selection
XPath allTextXPath = DocumentHelper.createXPath("//text()");
XPath titleTextXPath = DocumentHelper.createXPath("//title/text()");
// Mixed content
XPath mixedXPath = DocumentHelper.createXPath("//description//text()");// Position-based predicates
XPath firstXPath = DocumentHelper.createXPath("//book[1]");
XPath lastXPath = DocumentHelper.createXPath("//book[last()]");
XPath secondToLastXPath = DocumentHelper.createXPath("//book[last()-1]");
XPath positionRangeXPath = DocumentHelper.createXPath("//book[position() > 2 and position() < 5]");
// Attribute-based predicates
XPath hasAttributeXPath = DocumentHelper.createXPath("//book[@isbn]");
XPath attributeValueXPath = DocumentHelper.createXPath("//book[@category='fiction']");
XPath attributeComparisonXPath = DocumentHelper.createXPath("//book[@price > 25.00]");
// Element content predicates
XPath hasElementXPath = DocumentHelper.createXPath("//book[author]");
XPath elementValueXPath = DocumentHelper.createXPath("//book[author='John Doe']");
XPath elementComparisonXPath = DocumentHelper.createXPath("//book[price > 20]");
// Complex logical predicates
XPath complexXPath = DocumentHelper.createXPath(
"//book[@category='technical' and (author='Smith' or author='Jones') and @price < 50]");// String functions
XPath containsXPath = DocumentHelper.createXPath("//book[contains(title, 'XML')]");
XPath startsWithXPath = DocumentHelper.createXPath("//book[starts-with(title, 'Advanced')]");
XPath substringXPath = DocumentHelper.createXPath("//book[substring(isbn, 1, 3) = '978']");
XPath normalizeXPath = DocumentHelper.createXPath("//book[normalize-space(title) != '']");
// Numeric functions
XPath sumXPath = DocumentHelper.createXPath("sum(//book/@price)");
XPath countXPath = DocumentHelper.createXPath("count(//book[@category='fiction'])");
XPath avgXPath = DocumentHelper.createXPath("sum(//book/@price) div count(//book)");
XPath minMaxXPath = DocumentHelper.createXPath("//book[@price = //book/@price[not(. > //book/@price)]]");
// Boolean functions
XPath notXPath = DocumentHelper.createXPath("//book[not(@out-of-print)]");
XPath trueXPath = DocumentHelper.createXPath("//book[true()]");
// Node set functions
XPath localNameXPath = DocumentHelper.createXPath("//book[local-name() = 'book']");
XPath namespaceUriXPath = DocumentHelper.createXPath("//*[namespace-uri() = 'http://example.com/books']");import org.jaxen.VariableContext;
import org.jaxen.SimpleVariableContext;
// Simple variable context
SimpleVariableContext variables = new SimpleVariableContext();
variables.setVariableValue("category", "fiction");
variables.setVariableValue("maxPrice", 30.0);
variables.setVariableValue("author", "Smith");
XPath variableXPath = DocumentHelper.createXPath(
"//book[@category=$category and @price <= $maxPrice and author=$author]");
variableXPath.setVariableContext(variables);
List<Node> results = variableXPath.selectNodes(document);
// Custom variable context
class CustomVariableContext implements VariableContext {
private final Map<String, Object> variables = new HashMap<>();
public CustomVariableContext() {
variables.put("today", LocalDate.now().toString());
variables.put("userId", getCurrentUserId());
}
@Override
public Object getVariableValue(String namespaceURI, String prefix, String localName) {
return variables.get(localName);
}
public void setVariable(String name, Object value) {
variables.put(name, value);
}
}
CustomVariableContext customVars = new CustomVariableContext();
customVars.setVariable("status", "active");
XPath customXPath = DocumentHelper.createXPath("//item[@status=$status and @lastUpdate >= $today]");
customXPath.setVariableContext(customVars);import org.jaxen.FunctionContext;
import org.jaxen.Function;
import org.jaxen.Context;
import org.jaxen.function.StringFunction;
// Custom function implementation
class UpperCaseFunction implements Function {
@Override
public Object call(Context context, List args) throws Exception {
if (args.size() != 1) {
throw new Exception("upper-case() requires exactly 1 argument");
}
String value = StringFunction.evaluate(args.get(0), context.getNavigator());
return value.toUpperCase();
}
}
// Register custom function
FunctionContext functionContext = XPathFunctionContext.getInstance();
functionContext = new CustomFunctionContext(functionContext);
((CustomFunctionContext) functionContext).registerFunction(null, "upper-case", new UpperCaseFunction());
// Use custom function in XPath
XPath functionXPath = DocumentHelper.createXPath("//book[upper-case(title) = 'XML GUIDE']");
functionXPath.setFunctionContext(functionContext);
// Custom function context class
class CustomFunctionContext implements FunctionContext {
private final FunctionContext delegate;
private final Map<String, Function> customFunctions = new HashMap<>();
public CustomFunctionContext(FunctionContext delegate) {
this.delegate = delegate;
}
public void registerFunction(String namespaceURI, String localName, Function function) {
String key = (namespaceURI != null ? namespaceURI + ":" : "") + localName;
customFunctions.put(key, function);
}
@Override
public Function getFunction(String namespaceURI, String prefix, String localName) throws UnresolvableException {
String key = (namespaceURI != null ? namespaceURI + ":" : "") + localName;
Function custom = customFunctions.get(key);
if (custom != null) {
return custom;
}
return delegate.getFunction(namespaceURI, prefix, localName);
}
}import org.dom4j.NodeFilter;
// Create filters from XPath expressions
NodeFilter elementFilter = DocumentHelper.createXPathFilter("self::element");
NodeFilter attributeFilter = DocumentHelper.createXPathFilter("self::attribute()");
NodeFilter textFilter = DocumentHelper.createXPathFilter("self::text()");
// Custom node filter
NodeFilter customFilter = new NodeFilter() {
@Override
public boolean matches(Node node) {
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
return "book".equals(element.getName()) &&
element.attributeValue("category") != null;
}
return false;
}
};
// Use filters
List<Node> allNodes = document.selectNodes("//node()");
List<Node> filteredNodes = allNodes.stream()
.filter(elementFilter::matches)
.collect(Collectors.toList());import org.dom4j.rule.Pattern;
// Create patterns for XSLT-style matching
Pattern bookPattern = DocumentHelper.createPattern("book");
Pattern authorPattern = DocumentHelper.createPattern("book/author");
Pattern isbnPattern = DocumentHelper.createPattern("book/@isbn");
// Test pattern matching
List<Element> elements = document.selectNodes("//element");
for (Element element : elements) {
if (bookPattern.matches(element)) {
System.out.println("Found book element: " + element.getName());
}
}
// Complex patterns
Pattern complexPattern = DocumentHelper.createPattern("book[@category='fiction' and author]");
Pattern positionPattern = DocumentHelper.createPattern("book[position() = 1]");// Build XPath expressions dynamically
public class XPathBuilder {
private final StringBuilder xpath = new StringBuilder();
public XPathBuilder element(String name) {
if (xpath.length() > 0 && !xpath.toString().endsWith("/")) {
xpath.append("/");
}
xpath.append(name);
return this;
}
public XPathBuilder descendant(String name) {
xpath.append("//").append(name);
return this;
}
public XPathBuilder attribute(String name, String value) {
xpath.append("[@").append(name).append("='").append(value).append("']");
return this;
}
public XPathBuilder hasAttribute(String name) {
xpath.append("[@").append(name).append("]");
return this;
}
public XPathBuilder position(int pos) {
xpath.append("[").append(pos).append("]");
return this;
}
public XPathBuilder text(String text) {
xpath.append("[text()='").append(text).append("']");
return this;
}
public XPath build() throws InvalidXPathException {
return DocumentHelper.createXPath(xpath.toString());
}
@Override
public String toString() {
return xpath.toString();
}
}
// Use dynamic builder
XPath dynamicXPath = new XPathBuilder()
.descendant("book")
.attribute("category", "fiction")
.hasAttribute("isbn")
.build();
List<Node> books = dynamicXPath.selectNodes(document);// Cache compiled XPath expressions
public class XPathCache {
private final Map<String, XPath> cache = new ConcurrentHashMap<>();
private final Map<String, String> namespaces;
public XPathCache(Map<String, String> namespaces) {
this.namespaces = namespaces != null ? Map.copyOf(namespaces) : Map.of();
}
public XPath getXPath(String expression) throws InvalidXPathException {
return cache.computeIfAbsent(expression, expr -> {
try {
XPath xpath = DocumentHelper.createXPath(expr);
if (!namespaces.isEmpty()) {
xpath.setNamespaceURIs(namespaces);
}
return xpath;
} catch (InvalidXPathException e) {
throw new RuntimeException(e);
}
});
}
public List<Node> selectNodes(String expression, Object context) throws InvalidXPathException {
return getXPath(expression).selectNodes(context);
}
public Node selectSingleNode(String expression, Object context) throws InvalidXPathException {
return getXPath(expression).selectSingleNode(context);
}
public String valueOf(String expression, Object context) throws InvalidXPathException {
return getXPath(expression).valueOf(context);
}
}
// Use cached XPath
Map<String, String> namespaces = Map.of("ns", "http://example.com/");
XPathCache cache = new XPathCache(namespaces);
// These expressions are compiled once and cached
List<Node> books1 = cache.selectNodes("//ns:book[@category='fiction']", document);
List<Node> books2 = cache.selectNodes("//ns:book[@category='technical']", document);
List<Node> books3 = cache.selectNodes("//ns:book[@category='fiction']", document); // Uses cached version// Sort nodes using XPath expressions
XPath booksXPath = DocumentHelper.createXPath("//book");
List<Node> books = booksXPath.selectNodes(document);
// Sort by title
XPath titleSortXPath = DocumentHelper.createXPath("title");
titleSortXPath.sort(books);
// Sort by price (numeric)
XPath priceSortXPath = DocumentHelper.createXPath("number(@price)");
priceSortXPath.sort(books);
// Complex sorting with selectNodes
XPath sortedBooksXPath = DocumentHelper.createXPath("//book");
XPath sortKeyXPath = DocumentHelper.createXPath("concat(author, '|', title)");
List<Node> sortedBooks = sortedBooksXPath.selectNodes(document, sortKeyXPath);
// Remove duplicates while sorting
List<Node> uniqueSortedBooks = sortedBooksXPath.selectNodes(document, titleSortXPath, true);// Handle XPath compilation errors
try {
XPath xpath = DocumentHelper.createXPath("//book[invalid syntax");
} catch (InvalidXPathException e) {
System.err.println("Invalid XPath syntax: " + e.getMessage());
// Provide user-friendly error message
String suggestion = suggestCorrection(e.getMessage());
System.err.println("Did you mean: " + suggestion);
}
// Handle runtime XPath errors
try {
XPath xpath = DocumentHelper.createXPath("//book/@price div //book/@quantity");
Number result = xpath.numberValueOf(document);
if (result.doubleValue() == Double.POSITIVE_INFINITY) {
System.err.println("Division by zero in XPath expression");
} else if (Double.isNaN(result.doubleValue())) {
System.err.println("Invalid numeric operation in XPath expression");
}
} catch (Exception e) {
System.err.println("XPath evaluation error: " + e.getMessage());
}
// Validate XPath expressions
public boolean isValidXPath(String expression) {
try {
DocumentHelper.createXPath(expression);
return true;
} catch (InvalidXPathException e) {
return false;
}
}
// Safe XPath execution with defaults
public String safeValueOf(String expression, Object context, String defaultValue) {
try {
XPath xpath = DocumentHelper.createXPath(expression);
String result = xpath.valueOf(context);
return result != null && !result.isEmpty() ? result : defaultValue;
} catch (Exception e) {
System.err.println("XPath error: " + e.getMessage());
return defaultValue;
}
}// Start with XPath, continue with DOM4J navigation
XPath authorXPath = DocumentHelper.createXPath("//author[1]");
Element firstAuthor = (Element) authorXPath.selectSingleNode(document);
// Continue with DOM4J navigation
Element book = firstAuthor.getParent();
String title = book.elementText("title");
String isbn = book.attributeValue("isbn");
// Navigate to siblings using DOM4J
Element nextBook = book.getParent().elements("book").stream()
.skip(book.getParent().elements("book").indexOf(book) + 1)
.findFirst()
.orElse(null);
// Use XPath on specific subtrees
if (nextBook != null) {
XPath relativeXPath = DocumentHelper.createXPath("./author");
Element nextAuthor = (Element) relativeXPath.selectSingleNode(nextBook);
}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.
Install with Tessl CLI
npx tessl i tessl/maven-org-dom4j--dom4j