Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step3 #498

Merged
merged 12 commits into from
Oct 19, 2024
Merged

Step3 #498

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.docx4j.TextUtils;
import org.docx4j.wml.CommentRangeEnd;
import org.docx4j.wml.CommentRangeStart;
import org.docx4j.wml.Comments;
import org.docx4j.wml.R;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -41,16 +42,15 @@ static Map<BigInteger, Comment> collectComments(DocxPart docxPart) {
var allComments = new HashMap<BigInteger, Comment>();
new CommentCollectorWalker(docxPart, rootComments, allComments).walk();

var commentsPart = docxPart.commentsPart();
if (commentsPart == null)
return rootComments;
var comments = CommentUtil.getComments(commentsPart);

for (var comment : comments) {
var commentWrapper = allComments.get(comment.getId());
if (commentWrapper != null)
commentWrapper.setComment(comment);
}
var sourceDocument = docxPart.document();
CommentUtil.getCommentsPart(sourceDocument.getParts())
.map(CommentUtil::extractContent)
.map(Comments::getComment)
.stream()
.flatMap(Collection::stream)
.filter(comment -> allComments.containsKey(comment.getId()))
.forEach(comment -> allComments.get(comment.getId())
.setComment(comment));
return cleanMalformedComments(rootComments);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
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 pro.verron.officestamper.api.ExceptionResolver;
import pro.verron.officestamper.api.*;
import pro.verron.officestamper.utils.WmlFactory;

import java.math.BigInteger;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static pro.verron.officestamper.core.CommentCollectorWalker.collectComments;
import static pro.verron.officestamper.core.CommentUtil.getCommentString;

/**
* Allows registration of {@link CommentProcessor} objects. Each registered
Expand All @@ -36,22 +32,22 @@ public class CommentProcessorRegistry {

private static final Logger logger = LoggerFactory.getLogger(CommentProcessorRegistry.class);
private final DocxPart source;
private final Map<Class<?>, ?> commentProcessors;
private final CommentProcessors commentProcessors;
private final ExpressionResolver expressionResolver;
private final ExceptionResolver exceptionResolver;

/**
* Constructs a new CommentProcessorRegistry.
*
* @param source the source part of the Word document.
* @param source the source part of the Word document.
* @param expressionResolver the resolver for evaluating expressions.
* @param commentProcessors map of comment processor instances keyed by their respective class types.
* @param exceptionResolver the resolver for handling exceptions during processing.
* @param commentProcessors map of comment processor instances keyed by their respective class types.
* @param exceptionResolver the resolver for handling exceptions during processing.
*/
public CommentProcessorRegistry(
DocxPart source,
ExpressionResolver expressionResolver,
Map<Class<?>, ?> commentProcessors,
CommentProcessors commentProcessors,
ExceptionResolver exceptionResolver
) {
this.source = source;
Expand All @@ -63,89 +59,68 @@ public CommentProcessorRegistry(
public <T> void runProcessors(T expressionContext) {
var proceedComments = new ArrayList<Comment>();

source.streamParagraphs()
.map(P::getContent)
.flatMap(Collection::stream)
.map(XmlUtils::unwrap)
.filter(R.class::isInstance)
.map(R.class::cast)
source.streamRun()
.forEach(run -> {
var comments = collectComments(source);
var runParent = (P) run.getParent();
var runParent = StandardParagraph.from((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);
}
commentProcessors.commitChanges(source);
optional.ifPresent(proceedComments::add);
});

// 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 = collectComments(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());
}
var optional = runProcessorsOnParagraphComment(comments, expressionContext, p, p.paragraphContent());
commentProcessors.commitChanges(source);
optional.ifPresent(proceedComments::add);
});

source.streamParagraphs()
.forEach(paragraph -> runProcessorsOnInlineContent(expressionContext, paragraph));
for (Comment comment : proceedComments) {
CommentUtil.deleteComment(comment);
}

proceedComments.forEach(CommentUtil::deleteComment);
}

private <T> Optional<Comment> runProcessorsOnRunComment(
Map<BigInteger, Comment> comments,
T expressionContext,
R run,
P paragraph
Map<BigInteger, Comment> comments, T expressionContext, R run, Paragraph paragraph
) {
return CommentUtil
.getCommentAround(run, source.document())
.flatMap(c -> runCommentProcessors(
comments,
expressionContext,
c,
paragraph, run
));
return CommentUtil.getCommentAround(run, source.document())
.flatMap(c -> Optional.ofNullable(comments.get(c.getId())))
.flatMap(c -> {
commentProcessors.setContext(paragraph, run, c);
var comment = runCommentProcessors(expressionContext, c);
comments.remove(c.getComment()
.getId());
return comment;
});
}

/**
* 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 <T> the type of the context root object.
*/
private <T> Optional<Comment> runProcessorsOnParagraphComment(
WordprocessingMLPackage document,
Map<BigInteger, Comment> comments,
T expressionContext,
P paragraph
Paragraph paragraph,
List<Object> paragraphContent
) {
return CommentUtil
.getCommentFor(paragraph, document)
.flatMap(c -> runCommentProcessors(
comments,
expressionContext,
c,
paragraph,
null
));
return CommentUtil.getCommentFor(paragraphContent, source.document())
.flatMap(c -> Optional.ofNullable(comments.get(c.getId())))
.flatMap(c -> {
commentProcessors.setContext(paragraph, null, c);
var comment = runCommentProcessors(expressionContext, c);
comments.remove(c.getComment()
.getId());
return comment;
});
}

/**
Expand All @@ -156,62 +131,34 @@ private <T> Optional<Comment> runProcessorsOnParagraphComment(
* @param paragraph the paragraph to process.
* @param <T> type of the context root object
*/
private <T> void runProcessorsOnInlineContent(
T context,
P paragraph
) {
var paragraphWrapper = StandardParagraph.from(paragraph);
var text = paragraphWrapper.asString();
private <T> void runProcessorsOnInlineContent(T context, Paragraph paragraph) {
var text = paragraph.asString();
var placeholders = Placeholders.findProcessors(text);

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

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

private <T> Optional<Comment> runCommentProcessors(
Map<BigInteger, Comment> comments,
T context,
Comments.Comment comment,
P paragraph,
@Nullable R run
T context, Comment comment
) {
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);
}

var placeholder = comment.asPlaceholder();
try {
expressionResolver.setContext(context);
expressionResolver.resolve(placeholder);
comments.remove(comment.getId());
logger.debug("Comment '{}' successfully processed by a comment processor.", placeholder.expression());
return Optional.of(commentWrapper);
return Optional.of(comment);
} catch (SpelEvaluationException | SpelParseException e) {
var message = "Comment '%s' failed to process.".formatted(placeholder.expression());
exceptionResolver.resolve(placeholder, message, e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package pro.verron.officestamper.core;

import org.docx4j.wml.*;
import org.springframework.lang.Nullable;
import pro.verron.officestamper.api.*;
import pro.verron.officestamper.utils.WmlFactory;

import java.math.BigInteger;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Set;

public class CommentProcessors
extends AbstractMap<Class<?>, CommentProcessor> {
private final Map<Class<?>, CommentProcessor> processors;

public CommentProcessors(Map<Class<?>, CommentProcessor> processors) {
this.processors = processors;
}

void setContext(
DocxPart source,
Paragraph paragraph,
Placeholder placeholder
) {
var commentWrapper = new StandardComment(source.document());
commentWrapper.setComment(WmlFactory.newComment(placeholder.content()));
var commentRangeStart = new CommentRangeStart();
commentRangeStart.setId(BigInteger.TEN);
commentRangeStart.setParent(paragraph.getP());
commentWrapper.setCommentRangeStart(commentRangeStart);
var commentRangeEnd = new CommentRangeEnd();
commentRangeEnd.setId(BigInteger.TEN);
commentRangeEnd.setParent(paragraph.getP());
commentWrapper.setCommentRangeEnd(commentRangeEnd);
var commentReference = new R.CommentReference();
commentReference.setId(BigInteger.TEN);
commentReference.setParent(paragraph.getP());
commentWrapper.setCommentReference(commentReference);
var run = (R) paragraph.paragraphContent()
.get(0);
setContext(paragraph, run, commentWrapper);
}

public void setContext(Paragraph paragraph, @Nullable R run, Comment comment) {
for (var processor : processors.values()) {
processor.setProcessorContext(paragraph, run, comment);
}
}

void commitChanges(DocxPart source) {
for (var processor : processors.values()) {
processor.commitChanges(source);
processor.reset();
}
}

@Override public Set<Entry<Class<?>, CommentProcessor>> entrySet() {
return processors.entrySet();
}
}
Loading
Loading