Tag.java

package pro.verron.officestamper.core;

import org.docx4j.wml.*;
import pro.verron.officestamper.api.Comment;
import pro.verron.officestamper.api.DocxPart;
import pro.verron.officestamper.api.Insert;
import pro.verron.officestamper.api.Paragraph;

import java.math.BigInteger;
import java.util.Optional;

import static pro.verron.officestamper.utils.wml.WmlUtils.asString;

/// Represents a Tag entity consisting of a DocxPart and a CTSmartTagRun. A Tag provides functionality to manipulate and
/// retrieve information related to smart tags embedded within a WordprocessingML-based document. This class offers
/// methods to create a new Tag instance, remove the tag from its parent content, and retrieve associated elements such
/// as Paragraph and Comment objects. Additionally, a placeholder representation of the tag can be accessed through the
/// appropriate method.
///
/// @param docxPart the DocxPart instance representing the part of the document associated with the tag.
/// @param tag the CTSmartTagRun representing the smart tag element in the document.
public record Tag(DocxPart docxPart, CTSmartTagRun tag) {

    /// Creates a new Tag instance using the provided DocxPart and CTSmartTagRun.
    ///
    /// @param docxPart the DocxPart instance representing the part of the document associated with the new Tag.
    /// @param tag the CTSmartTagRun representing the smart tag element in the document.
    ///
    /// @return a new Tag instance initialized with the given DocxPart and CTSmartTagRun.
    public static Tag of(DocxPart docxPart, CTSmartTagRun tag) {
        return new Tag(docxPart, tag);
    }


    /// Removes the current tag from its parent's content list.
    ///
    /// This method locates the parent content accessor of the tag, retrieves its sibling elements, and removes the tag
    /// from the sibling list, detaching it from its parent content.
    public void remove() {
        var parent = (ContentAccessor) tag.getParent();
        var siblings = parent.getContent();
        siblings.remove(tag);
    }

    /// Retrieves the paragraph associated with the smart tag's parent element.
    ///
    /// @return the Paragraph object representing the parent element of the smart tag
    public Paragraph getParagraph() {
        return StandardParagraph.from(docxPart, tag.getParent());
    }

    /// Converts the current tag entity into a Comment representation.
    ///
    /// This method creates a new Comment instance associated with the parent paragraph of the smart tag, using its
    /// placeholder representation, and a predefined position value.
    ///
    /// @return a Comment object representing the current tag
    public Comment asComment() {
        return StandardComment.create(docxPart, (ContentAccessor) tag.getParent(), expression(), BigInteger.ZERO);
    }

    /// Retrieves the expression of the tag.
    ///
    /// @return the expression.
    public String expression() {
        return asString(tag.getContent());
    }

    /// Replaces the current tag with the provided Insert object in the parent's content list. It sets the Run
    /// Properties
    /// [RPr] of the provided Insert object and then removes the current tag and inserts the elements from the Insert
    /// object at the appropriate position.
    ///
    /// @param insert the Insert object containing elements to replace the current tag. It also provides the
    ///         ability to set Run Properties [RPr] for styling purposes.
    public void replace(Insert insert) {
        var optionalRun = getFirst(tag, R.class);
        optionalRun.ifPresent(firstRun -> insert.setRPr(firstRun.getRPr()));
        var parent = (ContentAccessor) tag.getParent();
        var siblings = parent.getContent();
        var index = siblings.indexOf(tag);
        siblings.remove(index);
        siblings.addAll(index, insert.elements());
    }

    private static <T> Optional<T> getFirst(CTSmartTagRun tagRun, Class<T> clazz) {
        return tagRun.getContent()
                     .stream()
                     .filter(clazz::isInstance)
                     .map(clazz::cast)
                     .findFirst();
    }

    /// Retrieves the type of the tag.
    ///
    /// @return the type.
    public Optional<String> type() {
        return tag.getSmartTagPr()
                  .getAttr()
                  .stream()
                  .filter(a -> a.getName()
                                .equals("type"))
                  .map(CTAttr::getVal)
                  .findFirst();
    }

    /// Retrieves the context key of the tag.
    ///
    /// @return the context key.
    public String getContextKey() {
        var smartTagPr = tag.getSmartTagPr();
        if (smartTagPr == null) return String.valueOf(0);
        var smartTagPrAttr = smartTagPr.getAttr();
        if (smartTagPrAttr == null) return String.valueOf(0);
        for (CTAttr attribute : smartTagPrAttr) {
            if ("context".equals(attribute.getName())) try {
                return String.valueOf(Integer.parseInt(attribute.getVal()));
            } catch (NumberFormatException _) {
                return String.valueOf(0);
            }
        }
        return String.valueOf(0);
    }
}