DocxHook.java

package pro.verron.officestamper.core;

import org.docx4j.wml.CTSmartTagRun;
import org.docx4j.wml.CommentRangeStart;
import org.docx4j.wml.ContentAccessor;
import pro.verron.officestamper.api.DocxPart;
import pro.verron.officestamper.api.Hook;
import pro.verron.officestamper.utils.iterator.ResetableIterator;
import pro.verron.officestamper.utils.wml.DocxIterator;

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

/// Interface for hooks that process specific parts of a DOCX document.
public interface DocxHook
        extends Hook {

    /// Creates an iterator over the hooks in the given content accessor.
    ///
    /// @param contentAccessor the content accessor to search for hooks.
    /// @param part the document part.
    ///
    /// @return an iterator over the found hooks.
    static ResetableIterator<DocxHook> ofHooks(ContentAccessor contentAccessor, DocxPart part) {
        return new DocxIterator(contentAccessor).filter(DocxHook::isPotentialHook)
                                                .map(o -> asHook(part, o));
    }

    /// Checks if the given object is a potential hook.
    ///
    /// @param o the object to check.
    ///
    /// @return `true` if it is a potential hook.
    static boolean isPotentialHook(Object o) {
        return o instanceof CTSmartTagRun tag && isTagElement(tag, "officestamper");
    }

    /// Converts an object to a hook.
    ///
    /// @param part the document part.
    /// @param o    the object to convert.
    /// @return the hook.
    static DocxHook asHook(DocxPart part, Object o) {
        return switch (o) {
            case CTSmartTagRun tag when isType(tag, "processor", "type") -> newCommentHook(part, tag);
            case CTSmartTagRun tag -> new TagHook(part, new Tag(part, tag));
            default -> throw new IllegalArgumentException("Unexpected value: " + o);
        };
    }

    /// Checks if the given tag is of the specified type.
    ///
    /// @param tag     the tag to check.
    /// @param type    the expected type value.
    /// @param typeKey the attribute name for the type.
    /// @return `true` if the tag matches the type.
    static boolean isType(CTSmartTagRun tag, String type, String typeKey) {
        return tag.getSmartTagPr()
                  .getAttr()
                  .stream()
                  .anyMatch(attr -> typeKey.equals(attr.getName()) && type.equals(attr.getVal()));
    }

    /// Creates a new comment hook.
    ///
    /// @param part the document part.
    /// @param tag  the tag.
    /// @return the comment hook.
    static DocxHook newCommentHook(DocxPart part, CTSmartTagRun tag) {
        var tagContent = tag.getContent();
        var commentRangeStart = (CommentRangeStart) tagContent.getFirst();
        var myTag = new Tag(part, tag);
        var comment = CommentUtil.comment(part, commentRangeStart, part.document(), part::content);
        return new CommentHook(part, myTag, comment);
    }

    /// Executes the hook's logic within the context of a document processing flow.
    ///
    /// @param engineFactory               a factory responsible for creating instances of the `Engine` class, which
    /// may be
    ///                                    used during the execution of the hook's logic
    /// @param contextTree                 the root of the context tree, representing the hierarchical structure of
    /// context
    ///                                    branches available during document processing
    /// @param officeStamperContextFactory a factory for creating evaluation contexts, which are used to
    ///                                    evaluate expressions and handle dynamic behavior during the document
    /// processing flow
    /// @return `true` if the execution of the hook was successful, otherwise `false`
    boolean run(
            EngineFactory engineFactory,
            ContextRoot contextTree,
            OfficeStamperEvaluationContextFactory officeStamperContextFactory
    );
}