Factory Methods and Fluent APIs

DomTrip provides clean factory methods and fluent APIs that make creating and modifying XML structures intuitive and type-safe. This page covers all the creation patterns available in DomTrip.

Element Factory Methods

DomTrip provides convenient factory methods for creating elements:

Basic Element Creation

// Simple elements
Element version = Element.of("version");
Element textElement = Element.text("version", "1.0.0");

// Element with attributes using fluent API
Element dependency = Element.of("dependency").attribute("scope", "test").attribute("optional", "true");

// Element with multiple attributes at once
Element div = Element.withAttributes(
        "div",
        Map.of(
                "class", "container",
                "id", "main",
                "data-role", "content"));

Advanced Element Creation

// Element with complex structure using fluent API
Element project = Element.of("project").attribute("xmlns", "http://maven.apache.org/POM/4.0.0");

project.addNode(Element.text("modelVersion", "4.0.0"));
project.addNode(Element.text("groupId", "com.example"));
project.addNode(Element.text("artifactId", "my-project"));
project.addNode(Element.text("version", "1.0.0"));

// Element with CDATA content
Element script = Element.of("script").attribute("type", "text/javascript");
script.addNode(Text.cdata("function test() { return x < y && z > 0; }"));

Element with Namespaces

// Namespaced element using QName
QName soapEnvelope = QName.of("http://schemas.xmlsoap.org/soap/envelope/", "Envelope", "soap");
Element envelope = Element.of(soapEnvelope);

QName soapBody = QName.of("http://schemas.xmlsoap.org/soap/envelope/", "Body", "soap");
Element body = Element.text(soapBody, "Body content");
envelope.addNode(body);

Document Factory Methods

DomTrip provides convenient factory methods for creating documents:

Simple Document Creation

// Basic document
Document doc = Document.withRootElement("project");

// Document with XML declaration
Document docWithDecl = Document.withXmlDeclaration("1.0", "UTF-8");
docWithDecl.root(Element.of("project"));

// Parse existing XML from string
String xmlString = "<root><child>value</child></root>";
Document parsedDoc = Document.of(xmlString);

File-Based Document Loading

The Document.of(Path) method is the recommended way to load XML files as it handles encoding detection automatically:

// Basic file loading with automatic encoding detection
// Document doc = Document.of(Path.of("config.xml"));

// Error handling
try {
    String xmlContent = createConfigXml();
    Document doc = Document.of(xmlContent);
    Editor editor = new Editor(doc);
    // ... process document
    String result = editor.toXml();
    Assertions.assertNotNull(result);
} catch (Exception e) {
    System.err.println("Failed to parse XML: " + e.getMessage());
}

Benefits of Document.of(Path):

  • Automatic encoding detection: Properly handles UTF-8, UTF-16, ISO-8859-1, and other encodings
  • BOM handling: Correctly processes Byte Order Marks
  • Memory efficient: Streams the file content rather than loading entire string into memory first
  • Error handling: Provides better error messages for encoding and parsing issues
  • Convenience: Single method call for file-based XML parsing

Advanced Document Creation

// Document with processing instructions
Document doc = Document.withXmlDeclaration("1.0", "UTF-8");
doc.addNode(ProcessingInstruction.of("xml-stylesheet", "type=\"text/xsl\" href=\"style.xsl\""));
doc.root(Element.of("project"));

Text and Comment Factory Methods

Text Node Creation

// Simple text
Text text = Text.of("Hello World");

// CDATA text
Text cdata = Text.cdata("<script>alert('test');</script>");

// Text with explicit CDATA flag
Text explicitCdata = Text.of("content", true);

Comment Creation

// Simple comment
Comment comment = Comment.of("This is a comment");

// Modify comment content
Comment modified = Comment.of("Initial content");
modified.content("Updated content");

Processing Instruction Creation

// Processing instruction with target and data
ProcessingInstruction pi = ProcessingInstruction.of("xml-stylesheet", "type=\"text/css\" href=\"style.css\"");

// Processing instruction with target only
ProcessingInstruction simple = ProcessingInstruction.of("target");

// Modify processing instruction
ProcessingInstruction modified = ProcessingInstruction.of("target", "data");
modified.target("new-target");
modified.data("new data");

Editor Fluent API

The Editor provides a fluent API for adding content to existing documents:

Adding Elements

Editor editor = new Editor(Document.of(xmlString));
Element root = editor.root();

// Fluent element addition
editor.add()
    .element("dependency")
    .to(root)
    .withAttribute("scope", "test")
    .withText("junit")
    .build();

// Add comments and processing instructions
editor.add()
    .comment()
    .to(root)
    .withContent(" Configuration section ")
    .build();

editor.add()
    .text()
    .to(element)
    .withContent("text content")
    .asCData()
    .build();

Attribute Factory Methods

Attribute Creation

// Simple attribute
Attribute attr = Attribute.of("class", "important");

// Attribute with specific quote style
Attribute quoted = Attribute.of("id", "main", QuoteStyle.SINGLE);

// Apply attributes to element
Element element = Element.of("div");
element.attribute("class", attr.value());
element.attribute("id", quoted.value(), quoted.quoteStyle());

Best Practices

1. Use Factory Methods for Simple Cases

// ✅ Good - clean and direct
Element version = Element.text("version", "1.0.0");
Comment comment = Comment.of("Configuration");
Text cdata = Text.cdata("script content");

// ❌ Avoid - unnecessary complexity
Element version = new Element("version");
version.addNode(new Text("1.0.0"));

2. Chain Fluent Methods for Complex Structures

// ✅ Good - readable fluent chain
Element dependency = Element.of("dependency").attribute("scope", "test").attribute("optional", "true");

dependency.addNode(Element.text("groupId", "junit"));
dependency.addNode(Element.text("artifactId", "junit"));

3. Extract Complex Structures to Methods

// ✅ Good - readable and reusable
private Element createDependency(String groupId, String artifactId, String version) {
    Element dependency = Element.of("dependency");
    dependency.addNode(Element.text("groupId", groupId));
    dependency.addNode(Element.text("artifactId", artifactId));
    dependency.addNode(Element.text("version", version));
    return dependency;
}

// Usage
Element junitDep = createDependency("junit", "junit", "4.13.2");

4. Use Appropriate Factory Methods

// ✅ Good - use the right tool for the job
Element selfClosing = Element.selfClosing("br");
Element withAttrs = Element.withAttributes("div", attributes);
Text cdata = Text.cdata("script content");
Document withDecl = Document.withXmlDeclaration("1.0", "UTF-8");

// ❌ Avoid - manual setup when factory exists
Element br = Element.of("br").selfClosing(true);
Document doc = Document.of().version("1.0").encoding("UTF-8").withXmlDeclaration();

Performance Considerations

Factory methods and fluent APIs are optimized for both convenience and performance:

  • Memory efficient: No intermediate builder objects created
  • Validation: Early validation prevents invalid XML structures
  • Direct creation: Objects created immediately, not deferred
  • Method chaining: Returns this for zero-cost fluent chaining
// Efficient - direct object creation and modification
Element element = Element.of("dependency").attribute("scope", "test").attribute("optional", "true");

// All operations modify the same object instance
// No intermediate objects or copying involved