Class Editor

java.lang.Object
eu.maveniverse.domtrip.Editor

public class Editor extends Object
High-level API for editing XML documents while preserving original formatting.

The Editor class provides a convenient, user-friendly interface for parsing, modifying, and serializing XML documents. It combines the functionality of Parser, and Serializer to offer a complete XML editing solution with lossless formatting preservation.

Capabilities:

  • Lossless Parsing - Preserves all formatting, whitespace, and comments
  • Simple Editing - Methods for common XML modifications
  • Format Preservation - Maintains original formatting for unchanged content
  • Flexible Output - Support for different serialization styles
  • Builder Pattern - Fluent API for document construction

Basic Usage:

// Parse existing XML
Document doc = Document.of(xmlString);
Editor editor = new Editor(doc);

// Make modifications
Element root = editor.root();
editor.addElement(root, "newChild", "content");
editor.setAttribute(root, "version", "2.0");  // Intelligently preserves formatting

// Serialize with preserved formatting
String result = editor.toXml();

Intelligent Formatting Preservation:

The Editor automatically preserves and infers appropriate formatting for new content:

// For XML with aligned attributes:
// <element attr1="value1"
//          attr2="value2"/>

editor.setAttribute(element, "attr3", "value3");
// Result: <element attr1="value1"
//                  attr2="value2"
//                  attr3="value3"/>  // Maintains alignment

Configuration Options:

// Use custom configuration
DomTripConfig config = DomTripConfig.prettyPrint()
    .withIndentation("  ")
    .withPreserveComments(true);

Document doc = Document.of(xmlString);
Editor editor = new Editor(doc, config);

// Different output styles
String pretty = editor.toXml(DomTripConfig.prettyPrint());
String minimal = editor.toXml(DomTripConfig.minimal());

Document Creation:

// Create new document
Editor editor = new Editor();
editor.createDocument("root");

// Build document structure
Element root = editor.root();
editor.addElement(root, "child", "value");

Working with Existing Documents:

// Use an existing Document object
Document existingDoc = Document.of(xmlString);
Editor editor = new Editor(existingDoc);

// Or with custom configuration
Editor editor = new Editor(existingDoc, DomTripConfig.prettyPrint());

// Work with programmatically created documents
Document doc = Document.withRootElement("project");
Editor editor = new Editor(doc);
See Also:
  • Constructor Details

    • Editor

      public Editor()
    • Editor

      public Editor(DomTripConfig config)
    • Editor

      public Editor(Document document)
      Creates a new editor with an existing Document.

      This constructor allows you to create an Editor instance from an existing Document object, which is useful when you already have a parsed document or when working with documents created programmatically.

      The editor will use default configuration settings. If you need custom configuration, use Editor(Document, DomTripConfig) instead.

      Usage Examples:

      // Working with an existing document
      Document existingDoc = Document.of(xmlString);
      Editor editor = new Editor(existingDoc);
      
      // Working with a programmatically created document
      Document doc = Document.withRootElement("project");
      Editor editor = new Editor(doc);
      
      // Continue editing
      Element root = editor.root();
      editor.addElement(root, "version", "1.0");
      
      Parameters:
      document - the existing Document to edit, may be null
      See Also:
    • Editor

      public Editor(Document document, DomTripConfig config)
      Creates a new editor with an existing Document and custom configuration.

      This constructor allows you to create an Editor instance from an existing Document object with custom configuration settings. This is useful when you need specific serialization or formatting behavior for an existing document.

      Usage Examples:

      // Working with existing document and custom config
      Document existingDoc = Document.of(xmlString);
      DomTripConfig config = DomTripConfig.prettyPrint()
          .withIndentString("  ")
          .withCommentPreservation(true);
      Editor editor = new Editor(existingDoc, config);
      
      // Working with builder-created document
      Document doc = Document.withRootElement("maven");
      Editor editor = new Editor(doc, DomTripConfig.minimal());
      
      Parameters:
      document - the existing Document to edit, may be null
      config - the configuration to use, or null for default configuration
      See Also:
  • Method Details

    • document

      public Document document()
      Gets the current XML document being edited.
      Returns:
      the current Document, or null if no document is loaded
    • toXml

      public String toXml()
      Serializes the current document back to XML
    • toXmlPretty

      public String toXmlPretty()
      Serializes with pretty printing
    • addElement

      public Element addElement(Element parent, String elementName) throws DomTripException
      Adds a new element as a child of the specified parent element
      Throws:
      DomTripException
    • addElement

      public Element addElement(Element parent, String elementName, String textContent) throws DomTripException
      Adds a new element with text content
      Throws:
      DomTripException
    • addElement

      public Element addElement(Element parent, QName qname) throws DomTripException
      Adds a new element using a QName.
      Parameters:
      parent - the parent element
      qname - the QName for the new element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the element cannot be added
    • addElement

      public Element addElement(Element parent, QName qname, String textContent) throws DomTripException
      Adds a new element using a QName with text content.
      Parameters:
      parent - the parent element
      qname - the QName for the new element
      textContent - the text content for the element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the element cannot be added
    • removeElement

      public boolean removeElement(Element element)
      Removes an element from its parent with intelligent whitespace handling.

      This method removes an element while preserving proper formatting by intelligently handling surrounding whitespace text nodes:

      • If removing the first element and there's no line before, removes following blank lines
      • If removing the last element and there's no line after, removes blank lines before
      • If removing a middle element, removes any following blank lines
      Parameters:
      element - the element to remove
      Returns:
      true if the element was successfully removed, false otherwise
    • setAttribute

      public void setAttribute(Element element, String name, String value) throws DomTripException
      Adds or updates an attribute on an element with intelligent formatting preservation.

      When updating an existing attribute, this method preserves the original formatting. When adding a new attribute, it analyzes existing attributes on the element to infer appropriate formatting patterns (quote style and whitespace alignment).

      Formatting Inference for New Attributes:

      • Quote Style - Uses the most common quote style from existing attributes
      • Whitespace - Analyzes existing attribute spacing patterns for alignment
      • Multi-line Support - Preserves newline-based attribute alignment

      Examples:

      // XML with aligned attributes:
      // <element attr1="value1"
      //          attr2="value2"/>
      
      editor.setAttribute(element, "attr3", "value3");
      // Result: <element attr1="value1"
      //                  attr2="value2"
      //                  attr3="value3"/>
      
      Parameters:
      element - the element to modify
      name - the attribute name
      value - the attribute value
      Throws:
      DomTripException - if element is null or name is invalid
      See Also:
    • removeAttribute

      public boolean removeAttribute(Element element, String name)
      Remove the specified attribute from the provided element.
      Parameters:
      element - the element from which to remove the attribute; if `null` no action is taken
      name - the name of the attribute to remove; if `null` no action is taken
      Returns:
      `true` if the attribute existed on the element and was removed, `false` otherwise
    • setTextContent

      public void setTextContent(Element element, String content) throws DomTripException
      Sets the text content of an element, preserving the node type (CDATA vs plain text) and surrounding whitespace of the existing content.

      If the element currently contains a CDATA section, the replacement text will also be wrapped in CDATA. If the element contains plain text, the replacement will be plain text. Surrounding whitespace patterns are preserved in both cases.

      When the element contains mixed content (multiple non-whitespace text nodes), only the first non-whitespace text node is updated and extra non-whitespace text nodes are removed.

      Examples:

      // CDATA preservation:
      // Before: <version><![CDATA[1.0]]></version>
      editor.setTextContent(versionElement, "2.0");
      // After:  <version><![CDATA[2.0]]></version>
      
      // Plain text:
      // Before: <version>1.0</version>
      editor.setTextContent(versionElement, "2.0");
      // After:  <version>2.0</version>
      
      Parameters:
      element - the element whose text content to set
      content - the new text content
      Throws:
      DomTripException - if element is null
    • addComment

      public Comment addComment(ContainerNode parent, String content) throws DomTripException
      Adds a comment as a child of the specified parent
      Throws:
      DomTripException
    • commentOutElement

      public Comment commentOutElement(Element element) throws DomTripException
      Comments out an element by wrapping it in an XML comment.

      This method replaces the element with a comment containing the element's XML representation. The original element is preserved within the comment and can be restored using uncommentElement(Comment).

      Example:

      // Before: <dependency><groupId>junit</groupId></dependency>
      editor.commentOutElement(dependencyElement);
      // After: <!-- <dependency><groupId>junit</groupId></dependency> -->
      
      Parameters:
      element - the element to comment out
      Returns:
      the comment that replaced the element
      Throws:
      DomTripException - if the element cannot be commented out
    • commentOutElements

      public Comment commentOutElements(Element... elements) throws DomTripException
      Comments out multiple elements as a single block comment.

      This method wraps multiple elements in a single XML comment block. All elements must have the same parent. The elements are replaced with a single comment containing all their XML representations.

      Example:

      // Before: <dep1/><dep2/><dep3/>
      editor.commentOutElements(dep1, dep2, dep3);
      // After: <!-- <dep1/><dep2/><dep3/> -->
      
      Parameters:
      elements - the elements to comment out as a block
      Returns:
      the comment that replaced the elements
      Throws:
      DomTripException - if the elements cannot be commented out
    • uncommentElement

      public Element uncommentElement(Comment comment) throws DomTripException
      Uncomments a previously commented element by parsing the comment content back to XML.

      This method attempts to parse the content of a comment as XML and replace the comment with the parsed elements. This is the reverse operation of commentOutElement(Element) and commentOutElements(Element...).

      Example:

      // Before: <!-- <dependency><groupId>junit</groupId></dependency> -->
      Element restored = editor.uncommentElement(comment);
      // After: <dependency><groupId>junit</groupId></dependency>
      
      Parameters:
      comment - the comment containing XML to uncomment
      Returns:
      the first element that was restored, or null if no elements were found
      Throws:
      DomTripException - if the comment content cannot be parsed as XML
    • insertElementAt

      public Element insertElementAt(Element parent, int index, String elementName) throws DomTripException
      Inserts a new element at the specified position within the parent.

      This method provides precise control over element positioning by allowing insertion at a specific index. The index is 0-based, where 0 inserts at the beginning and parent.nodeCount() appends at the end.

      Example:

      // Insert at position 1 (second position)
      Element newElement = editor.insertElementAt(parent, 1, "newChild");
      
      Parameters:
      parent - the parent element to insert into
      index - the position to insert at (0-based)
      elementName - the name of the new element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the insertion fails
    • insertElementAt

      public Element insertElementAt(Element parent, int index, String elementName, String textContent) throws DomTripException
      Inserts a new element with text content at the specified position.
      Parameters:
      parent - the parent element to insert into
      index - the position to insert at (0-based)
      elementName - the name of the new element
      textContent - the text content for the element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the insertion fails
    • insertElementBefore

      public Element insertElementBefore(Element referenceElement, String elementName) throws DomTripException
      Inserts a new element before the specified reference element.

      This method creates a new element and inserts it immediately before the reference element in the parent's child list. Both elements will have the same parent after insertion.

      Example:

      // Insert before existing element
      Element newElement = editor.insertElementBefore(existingElement, "newChild");
      
      Parameters:
      referenceElement - the element to insert before
      elementName - the name of the new element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the insertion fails
    • insertElementBefore

      public Element insertElementBefore(Element referenceElement, String elementName, String textContent) throws DomTripException
      Inserts a new element with text content before the specified reference element.
      Parameters:
      referenceElement - the element to insert before
      elementName - the name of the new element
      textContent - the text content for the element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the insertion fails
    • insertElementAfter

      public Element insertElementAfter(Element referenceElement, String elementName) throws DomTripException
      Inserts a new element after the specified reference element.

      This method creates a new element and inserts it immediately after the reference element in the parent's child list. Both elements will have the same parent after insertion.

      Example:

      // Insert after existing element
      Element newElement = editor.insertElementAfter(existingElement, "newChild");
      
      Parameters:
      referenceElement - the element to insert after
      elementName - the name of the new element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the insertion fails
    • insertElementAfter

      public Element insertElementAfter(Element referenceElement, String elementName, String textContent) throws DomTripException
      Inserts a new element with text content after the specified reference element.
      Parameters:
      referenceElement - the element to insert after
      elementName - the name of the new element
      textContent - the text content for the element
      Returns:
      the newly created element
      Throws:
      DomTripException - if the insertion fails
    • createDocument

      public void createDocument(String rootElementName) throws DomTripException
      Creates a new XML document with the specified root element.

      This method creates a new document with a default XML declaration and the specified root element. Any existing document will be replaced.

      Parameters:
      rootElementName - the name of the root element
      Throws:
      DomTripException - if the root element name is null or empty
    • root

      public Element root() throws DomTripException
      Gets the root element of the document.
      Returns:
      the root element of the document
      Throws:
      IllegalStateException - if no document is loaded or document has no root element
      DomTripException
    • select

      public List<Element> select(String expression)
      Evaluates a mini-XPath expression against the document root and returns all matching elements.

      Examples:

      List<Element> deps = editor.select("//dependency");
      List<Element> testDeps = editor.select("//dependency[@scope='test']");
      
      Parameters:
      expression - the mini-XPath expression
      Returns:
      a list of matching elements, never null
      Throws:
      DomTripException - if the expression is invalid or no document is loaded
      Since:
      1.3.0
      See Also:
    • selectFirst

      public Optional<Element> selectFirst(String expression)
      Evaluates a mini-XPath expression against the document root and returns the first match.

      Examples:

      Optional<Element> dep = editor.selectFirst("//dependency[@scope='test']");
      Optional<Element> ver = editor.selectFirst("project/version");
      
      Parameters:
      expression - the mini-XPath expression
      Returns:
      an Optional containing the first matching element, or empty if none found
      Throws:
      DomTripException - if the expression is invalid or no document is loaded
      Since:
      1.3.0
      See Also:
    • walk

      public DomTripVisitor.Action walk(DomTripVisitor visitor)
      Walks the entire document tree using the given visitor.

      This is a convenience method equivalent to document().accept(visitor).

      Parameters:
      visitor - the visitor to use for traversal
      Returns:
      the action indicating whether traversal completed or was stopped
      Throws:
      IllegalArgumentException - if visitor is null
      DomTripException - if no document is loaded
      Since:
      1.3.0
      See Also:
    • isWellFormed

      public boolean isWellFormed()
      Validates that the document is well-formed.

      Performs basic well-formedness validation including checking for the presence of a root element. Additional validation rules may be added in the future.

      Returns:
      true if the document is well-formed, false otherwise
    • documentStats

      public String documentStats()
      Gets statistics about the document structure.

      Returns a formatted string containing counts of different node types in the document including elements, text nodes, comments, and total nodes.

      Returns:
      a string containing document statistics
    • setAttributes

      public void setAttributes(Element element, Map<String,String> attributes) throws DomTripException
      Batch operation to set multiple attributes on an element.

      Sets multiple attributes at once with intelligent formatting preservation. Each attribute uses inferred formatting based on existing patterns on the element.

      Parameters:
      element - the element to modify
      attributes - a map of attribute names to values
      Throws:
      DomTripException - if any attribute operation is invalid
    • addElements

      public void addElements(Element parent, Map<String,String> nameValuePairs) throws DomTripException
      Batch operation to add multiple child elements with text content.

      Creates multiple child elements at once, each with the specified text content. This is more efficient than adding elements individually.

      Parameters:
      parent - the parent element to add children to
      nameValuePairs - a map of element names to text content values
      Throws:
      DomTripException - if any element creation is invalid
    • addQNameElements

      public void addQNameElements(Element parent, Map<QName,String> qnameValuePairs) throws DomTripException
      Batch operation to add multiple child elements with text content using QNames.
      Parameters:
      parent - the parent element
      qnameValuePairs - a map of QNames to text content
      Throws:
      DomTripException - if any element cannot be added
    • toXml

      public String toXml(DomTripConfig config)
      Serializes the document to XML string with custom configuration.

      Uses the provided configuration instead of the editor's default configuration for this serialization operation only.

      Parameters:
      config - the configuration to use for serialization
      Returns:
      the XML string, or empty string if no document is loaded
    • config

      public DomTripConfig config()
      Gets the configuration used by this editor.
      Returns:
      the DomTripConfig instance used by this editor
    • add

      public Editor.NodeBuilder add()
      Creates a fluent builder for adding nodes.
    • addBlankLineBefore

      public void addBlankLineBefore(Element element)
    • addBlankLineAfter

      public void addBlankLineAfter(Element element)