Element API
The Element class represents XML elements and provides comprehensive methods for manipulating element content, attributes, and whitespace while preserving formatting.
Overview
The Element class is the core building block for XML documents in DomTrip, providing:
- Element creation and manipulation
- Attribute management with formatting preservation
- Text content handling with whitespace preservation
- Child element navigation and modification
- Comprehensive whitespace control
- Namespace-aware operations
Element Creation
Factory Methods
// Create elements using factory methods
Element simple = Element.of("dependency");
Element withText = Element.of("groupId").textContent("com.example");
Element withNamespace = Element.of(QName.of("http://maven.apache.org/POM/4.0.0", "project"));
// Create empty element
Element empty = new Element("placeholder");
Builder Pattern
// Build complex elements with fluent API
Element dependency = Element.of("dependency")
.attribute("scope", "test")
.attribute("optional", "true")
.textContent("junit:junit:4.13.2");
// Chain multiple operations
Element project = Element.of("project").attribute("xmlns", "http://maven.apache.org/POM/4.0.0");
project.addNode(Element.of("groupId").textContent("com.example"));
project.addNode(Element.of("artifactId").textContent("my-app"));
project.addNode(Element.of("version").textContent("1.0.0"));
Attribute Management
Basic Attribute Operations
// Set attributes
element.attribute("groupId", "junit");
element.attribute("artifactId", "junit");
element.attribute("version", "4.13.2");
// Get attributes
String groupId = element.attribute("groupId"); // "junit"
String version = element.attribute("version"); // "4.13.2"
// Check if attribute exists
boolean hasScope = element.hasAttribute("scope"); // false
// Remove attribute
element.removeAttribute("version");
Attribute Formatting
Element element = doc.root();
// Attributes preserve their original quote style and whitespace
String scope = element.attribute("scope"); // "test"
String optional = element.attribute("optional"); // "true"
// When serialized, original formatting is maintained:
// scope='test' optional="true"
String result = editor.toXml();
Text Content
Basic Text Operations
// Set text content
element.textContent("This is the description");
// Get text content
String content = element.textContent(); // "This is the description"
// Get trimmed content (removes leading/trailing whitespace)
String trimmed = element.textContentTrimmed();
Whitespace-Preserving Text
// Original content with whitespace: " old value "
String original = item.textContent();
// Update content while preserving whitespace pattern
item.textPreservingWhitespace("new value");
// Result maintains whitespace: " new value "
String updated = item.textContent();
Whitespace Management
DomTrip provides fine-grained control over whitespace at multiple levels:
Node-Level Whitespace
// Whitespace before the element
String preceding = element.precedingWhitespace(); // " "
// Set whitespace programmatically
element.precedingWhitespace(" ");
Element Tag Whitespace
// Whitespace inside opening tag: <element >
String openTag = element.openTagWhitespace(); // " "
// Whitespace inside closing tag: </ element>
String closeTag = element.closeTagWhitespace(); // " "
// Set tag whitespace
element.openTagWhitespace(" ");
element.closeTagWhitespace("");
Inner Element Whitespace
For elements containing only whitespace (no child elements), DomTrip provides specialized handling:
Element element = doc.root();
// Whitespace immediately before closing tag: WHITESPACE</element>
String innerPreceding = element.innerPrecedingWhitespace(); // "\n \n"
// This field is used when an element contains only whitespace
// (no child elements), providing a cleaner model than Text nodes
Child Element Navigation
Finding Child Elements
// Find first child with name
Optional<Element> groupId = project.child("groupId");
String group = groupId.map(Element::textContent).orElse("unknown");
// Find descendant anywhere in tree
Optional<Element> version = project.descendant("version");
// Check if child exists
boolean hasArtifactId = project.child("artifactId").isPresent();
Element Streams
// Stream all child elements
var allDeps = dependencies.children().collect(Collectors.toList());
// Stream children with specific name
var depElements = dependencies.children("dependency").collect(Collectors.toList());
// Filter and transform
var testDeps = dependencies
.children("dependency")
.filter(dep -> "test".equals(dep.attribute("scope")))
.map(Element::textContent)
.collect(Collectors.toList());
Namespace Support
Namespace-Aware Operations
// Create element with namespace
QName qname = QName.of("http://maven.apache.org/POM/4.0.0", "project");
Element project = Element.of(qname);
// Declare namespace
project.namespaceDeclaration("", "http://maven.apache.org/POM/4.0.0");
project.namespaceDeclaration("xsi", "http://www.w3.org/2001/XMLSchema-instance");
// Access namespace information
String namespaceURI = project.namespaceURI();
String localName = project.localName();
String prefix = project.prefix();
QName Support
// Create QName with namespace
QName projectQName = QName.of("http://maven.apache.org/POM/4.0.0", "project");
Element project = Element.of(projectQName);
// Create QName with prefix
QName xsiQName = QName.of("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation", "xsi");
// Access QName components
String namespaceURI = projectQName.namespaceURI();
String localName = projectQName.localName();
String prefix = xsiQName.prefix();
Element Modification
Adding Child Elements
Element parent = Element.of("dependencies");
// Add child element
Element dependency = Element.of("dependency");
parent.addNode(dependency);
// Add multiple children
dependency.addNode(Element.of("groupId").textContent("junit"));
dependency.addNode(Element.of("artifactId").textContent("junit"));
dependency.addNode(Element.of("version").textContent("4.13.2"));
// Add child with text
Element scope = Element.of("scope").textContent("test");
dependency.addNode(scope);
Removing Elements
// Find and remove specific element
Optional<Element> toRemove = project.child("dependency");
toRemove.ifPresent(element -> project.removeNode(element));
// Remove all elements with specific name (collect to list first to avoid ConcurrentModificationException)
project.children("dependency").collect(Collectors.toList()).forEach(project::removeNode);
// Remove by condition (collect to list first to avoid ConcurrentModificationException)
project.children()
.filter(child -> "deprecated".equals(child.attribute("status")))
.collect(Collectors.toList())
.forEach(project::removeNode);
Advanced Features
Element Cloning
Element original = Element.of("dependency").attribute("scope", "test").attribute("optional", "true");
original.addNode(Element.of("groupId").textContent("junit"));
original.addNode(Element.of("artifactId").textContent("junit"));
// Clone the element (deep copy)
Element clone = original.clone();
// Modify clone without affecting original
clone.attribute("scope", "compile");
clone.child("groupId").ifPresent(g -> g.textContent("mockito"));
Modification Tracking
// Unmodified nodes use original formatting
Element unchanged = doc.root().child("groupId").orElseThrow();
Assertions.assertFalse(unchanged.isModified()); // false
// Modified nodes are rebuilt with inferred formatting
Element changed = doc.root().child("version").orElseThrow();
editor.setTextContent(changed, "2.0.0");
Assertions.assertTrue(changed.isModified()); // true
Best Practices
✅ Do:
- Use
textPreservingWhitespace()when updating text content to maintain formatting - Leverage the fluent API for method chaining
- Use Optional-returning methods for safe navigation
- Set inner whitespace fields for elements with only whitespace content
- Use QName-based methods for namespace-aware operations
❌ Avoid:
- Directly manipulating the nodes list (use provided methods instead)
- Ignoring namespace context when working with namespaced elements
- Using
textContent()when you need to preserve whitespace patterns - Creating elements without proper namespace declarations
Integration with Editor
Elements work seamlessly with the Editor API for document-level operations:
Element root = editor.root();
// Use Editor methods for modifications
Element dependency = editor.addElement(root, "dependency");
editor.setAttribute(dependency, "scope", "test");
editor.addElement(dependency, "groupId", "junit");
// Use Element methods for navigation
Optional<Element> groupId = dependency.child("groupId");
String value = groupId.map(Element::textContent).orElse("unknown");
// Combine both approaches
dependency.children().forEach(child -> {
editor.setAttribute(child, "modified", "true");
});
The Element API provides the foundation for precise XML element manipulation while maintaining formatting integrity and supporting advanced features like namespaces and whitespace preservation.