CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-dom4j--dom4j

Flexible XML framework for Java providing comprehensive XML processing capabilities

Pending
Overview
Eval results
Files

xpath.mddocs/

DOM4J XPath Support

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.

XPath Interface

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.

Package and Import

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;

XPath Interface Methods

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);
}

Creating XPath Expressions

// 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);

Basic XPath Operations

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 Implementation

DefaultXPath is the concrete implementation of the XPath interface, providing Jaxen-based XPath processing.

Package and Import

import org.dom4j.xpath.DefaultXPath;

Advanced XPath Usage

// 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);

Namespace-Aware XPath

// 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);

XPath Expressions and Syntax

Node Selection Patterns

// 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()");

Predicates and Filtering

// 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]");

XPath Functions

// 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']");

Variable and Function Contexts

Variable Context

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);

Function Context

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);
    }
}

Pattern Matching and Filtering

NodeFilter Interface

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());

Pattern-Based Selection

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]");

Advanced XPath Techniques

Dynamic XPath Generation

// 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);

XPath Performance Optimization

// 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

Sorting with XPath

// 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);

Error Handling in XPath

// 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;
    }
}

Integration with Document Navigation

Combining XPath with DOM4J Navigation

// 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

docs

advanced-features.md

core-api.md

document-creation.md

index.md

io-operations.md

xpath.md

tile.json