All Downloads are FREE. Search and download functionalities are using the official Maven repository.

pro.verron.officestamper.core.CommentProcessorRegistry Maven / Gradle / Ivy

Go to download

Office-stamper is a Java template engine for docx documents, forked from org.wickedsource.docx-stamper

There is a newer version: 2.7.0
Show newest version
package pro.verron.officestamper.core;

import org.docx4j.XmlUtils;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.Comments;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelParseException;
import org.springframework.lang.Nullable;
import pro.verron.officestamper.api.Comment;
import pro.verron.officestamper.api.CommentProcessor;
import pro.verron.officestamper.api.DocxPart;

import java.math.BigInteger;
import java.util.*;

import static pro.verron.officestamper.core.CommentUtil.getCommentString;
import static pro.verron.officestamper.core.CommentUtil.getComments;
import static pro.verron.officestamper.core.ExceptionUtil.treatException;

/**
 * Allows registration of {@link CommentProcessor} objects. Each registered
 * ICommentProcessor must implement an interface which has to be specified at
 * registration time. Provides several getter methods to access the registered
 * {@link CommentProcessor}.
 *
 * @author Joseph Verron
 * @author Tom Hombergs
 * @version ${version}
 * @since 1.0.0
 */
public class CommentProcessorRegistry {

    private static final Logger logger = LoggerFactory.getLogger(CommentProcessorRegistry.class);
    private final DocxPart source;
    private final Map, Object> commentProcessors;
    private final boolean failOnUnresolvedExpression;
    private final ExpressionResolver expressionResolver;

    /**
     * Creates a new CommentProcessorRegistry.
     *
     * @param expressionResolver         the expression resolver
     * @param commentProcessors          the comment processors
     * @param failOnUnresolvedExpression whether to fail on unresolved expressions
     */
    public CommentProcessorRegistry(
            DocxPart source,
            ExpressionResolver expressionResolver,
            Map, Object> commentProcessors,
            boolean failOnUnresolvedExpression
    ) {
        this.source = source;
        this.expressionResolver = expressionResolver;
        this.commentProcessors = commentProcessors;
        this.failOnUnresolvedExpression = failOnUnresolvedExpression;
    }

    public  void runProcessors(T expressionContext) {
        var proceedComments = new ArrayList();

        source.streamParagraphs()
              .map(P::getContent)
              .flatMap(Collection::stream)
              .map(XmlUtils::unwrap)
              .filter(R.class::isInstance)
              .map(R.class::cast)
              .forEach(run -> {
                  var comments = getComments(source);
                  var runParent = (P) run.getParent();
                  var optional = runProcessorsOnRunComment(comments, expressionContext, run, runParent);
                  if (optional.isPresent()) {
                      var comment = optional.get();
                      for (Object processor : commentProcessors.values()) {
                          var commentProcessor = (CommentProcessor) processor;
                          commentProcessor.commitChanges(source);
                          commentProcessor.reset();
                      }
                      proceedComments.add(comment);
                  }
              });
        // we run the paragraph afterward so that the comments inside work before the whole paragraph comments
        source.streamParagraphs()
              .forEach(p -> {
                  var document = source.document();
                  var comments = getComments(source);
                  var optional = runProcessorsOnParagraphComment(document, comments, expressionContext, p);
                  if (optional.isPresent()) {
                      for (Object processor : commentProcessors.values()) {
                          var commentProcessor = (CommentProcessor) processor;
                          commentProcessor.commitChanges(source);
                          commentProcessor.reset();
                      }
                      proceedComments.add(optional.get());
                  }
              });
        source.streamParagraphs()
              .forEach(paragraph -> runProcessorsOnInlineContent(expressionContext, paragraph));
        for (Comment comment : proceedComments) {
            CommentUtil.deleteComment(comment);
        }
    }

    private  Optional runProcessorsOnRunComment(
            Map comments,
            T expressionContext,
            R run,
            P paragraph
    ) {
        return CommentUtil
                .getCommentAround(run, source.document())
                .flatMap(c -> runCommentProcessors(
                        comments,
                        expressionContext,
                        c,
                        paragraph, run
                ));
    }

    /**
     * Takes the first comment on the specified paragraph and tries to evaluate
     * the string within the comment against all registered
     * {@link CommentProcessor}s.
     *
     * @param document          the Word document.
     * @param comments          the comments within the document.
     * @param expressionContext the context root object
     * @param                the type of the context root object.
     */
    private  Optional runProcessorsOnParagraphComment(
            WordprocessingMLPackage document,
            Map comments,
            T expressionContext,
            P paragraph
    ) {
        return CommentUtil
                .getCommentFor(paragraph, document)
                .flatMap(c -> runCommentProcessors(
                        comments,
                        expressionContext,
                        c,
                        paragraph, null
                ));
    }

    /**
     * Finds all processor expressions within the specified paragraph and tries
     * to evaluate it against all registered {@link CommentProcessor}s.
     *
     * @param context   the context root object against which evaluation is done
     * @param paragraph the paragraph to process.
     * @param        type of the context root object
     */
    private  void runProcessorsOnInlineContent(
            T context,
            P paragraph
    ) {
        var paragraphWrapper = new StandardParagraph(paragraph);
        var text = paragraphWrapper.asString();
        var placeholders = Placeholders.findProcessors(text);

        for (var placeholder : placeholders) {
            for (var processor : commentProcessors.values()) {
                ((CommentProcessor) processor).setParagraph(paragraph);
            }

            try {
                expressionResolver.setContext(context);
                expressionResolver.resolve(placeholder);
                paragraphWrapper.replace(placeholder, RunUtil.create(""));
                logger.debug("Placeholder '{}' successfully processed by a comment processor.", placeholder);
            } catch (SpelEvaluationException | SpelParseException e) {
                var msg = "Placeholder '%s' failed to process.".formatted(placeholder);
                treatException(e, failOnUnresolvedExpression, msg);
            }
            for (var processor : commentProcessors.values()) {
                ((CommentProcessor) processor).commitChanges(source);
            }
        }
    }

    private  Optional runCommentProcessors(
            Map comments,
            T context,
            Comments.Comment comment,
            P paragraph,
            @Nullable R run
    ) {
        Comment commentWrapper = comments.get(comment.getId());

        if (Objects.isNull(commentWrapper)) {
            // no comment to process
            return Optional.empty();
        }

        var placeholder = getCommentString(comment);

        for (final Object processor : commentProcessors.values()) {
            ((CommentProcessor) processor).setParagraph(paragraph);
            ((CommentProcessor) processor).setCurrentRun(run);
            ((CommentProcessor) processor).setCurrentCommentWrapper(commentWrapper);
        }

        try {
            expressionResolver.setContext(context);
            expressionResolver.resolve(placeholder);
            comments.remove(comment.getId());
            logger.debug("Comment '{}' successfully processed by a comment processor.", placeholder);
            return Optional.of(commentWrapper);
        } catch (SpelEvaluationException | SpelParseException e) {
            var msg = "Comment '%s' failed to process.".formatted(placeholder);
            treatException(e, failOnUnresolvedExpression, msg);
            return Optional.empty();
        }
    }

    /**
     * Resets all registered ICommentProcessors.
     */
    public void reset() {
        for (Object processor : commentProcessors.values()) {
            ((CommentProcessor) processor).reset();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy