RepeatProcessor.java

package pro.verron.officestamper.preset.processors.repeat;

import org.docx4j.TraversalUtil;
import org.docx4j.XmlUtils;
import org.docx4j.finders.ClassFinder;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.Comments;
import org.docx4j.wml.P;
import org.docx4j.wml.Tbl;
import org.docx4j.wml.Tr;
import org.springframework.lang.Nullable;
import pro.verron.officestamper.api.*;
import pro.verron.officestamper.core.CommentUtil;
import pro.verron.officestamper.core.StandardParagraph;
import pro.verron.officestamper.preset.CommentProcessorFactory;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;

/**
 * Repeats a table row for each element in a list.
 *
 * @author Joseph Verron
 * @author Tom Hombergs
 * @version ${version}
 * @since 1.0.0
 */
public class RepeatProcessor
        extends AbstractCommentProcessor
        implements CommentProcessorFactory.IRepeatProcessor {

    private final BiFunction<WordprocessingMLPackage, Tr, List<Tr>> nullSupplier;
    private Map<Tr, Iterable<Object>> tableRowsToRepeat = new HashMap<>();
    private Map<Tr, Comment> tableRowsCommentsToRemove = new HashMap<>();

    private RepeatProcessor(
            ParagraphPlaceholderReplacer placeholderReplacer,
            BiFunction<WordprocessingMLPackage, Tr, List<Tr>> nullSupplier1
    ) {
        super(placeholderReplacer);
        nullSupplier = nullSupplier1;
    }

    /**
     * Creates a new RepeatProcessor.
     *
     * @param pr The PlaceholderReplacer to use.
     *
     * @return A new RepeatProcessor.
     */
    public static CommentProcessor newInstance(ParagraphPlaceholderReplacer pr) {
        return new RepeatProcessor(pr, (document, row) -> emptyList());
    }

    /** {@inheritDoc} */
    @Override public void commitChanges(DocxPart source) {
        repeatRows(source);
    }

    private void repeatRows(DocxPart source) {
        for (Map.Entry<Tr, Iterable<Object>> entry : tableRowsToRepeat.entrySet()) {
            Tr row = entry.getKey();
            Iterable<Object> expressionContexts = entry.getValue();

            Tbl table = (Tbl) XmlUtils.unwrap(row.getParent());
            var content = table.getContent();
            int index = content.indexOf(row);
            content.remove(row);

            List<Tr> changes;
            if (expressionContexts == null) {
                changes = nullSupplier.apply(source.document(), row);
            }
            else {
                changes = new ArrayList<>();
                for (Object expressionContext : expressionContexts) {
                    Tr rowClone = XmlUtils.deepCopy(row);
                    Comment commentWrapper = requireNonNull(tableRowsCommentsToRemove.get(row));
                    Comments.Comment comment = requireNonNull(commentWrapper.getComment());
                    BigInteger commentId = comment.getId();
                    CommentUtil.deleteCommentFromElements(rowClone.getContent(), commentId);
                    var classFinder = new ClassFinder(P.class);
                    TraversalUtil.visit(rowClone, classFinder);
                    var objects = classFinder.results;
                    for (Object object : objects) {
                        P result = (P) object;
                        StandardParagraph paragraph = StandardParagraph.from(source, result);
                        placeholderReplacer.resolveExpressionsForParagraph(source, paragraph, expressionContext);
                    }
                    changes.add(rowClone);
                }
            }
            content.addAll(index, changes);
        }
    }

    /** {@inheritDoc} */
    @Override public void reset() {
        this.tableRowsToRepeat = new HashMap<>();
        this.tableRowsCommentsToRemove = new HashMap<>();
    }

    /** {@inheritDoc} */
    @Override public void repeatTableRow(@Nullable Iterable<Object> objects) {
        var tr = this.getParagraph()
                     .parent(Tr.class)
                     .orElseThrow(OfficeStamperException.throwing("This paragraph is not in a table row."));
        tableRowsToRepeat.put(tr, objects);
        tableRowsCommentsToRemove.put(tr, getCurrentCommentWrapper());
    }

}