Skip to content
Open
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
@@ -1,12 +1,11 @@
package org.wickedsource.docxstamper.replace;

import java.util.List;

import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.Br;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.docx4j.wml.RPr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.expression.spel.SpelEvaluationException;
Expand All @@ -24,119 +23,155 @@
import org.wickedsource.docxstamper.util.walk.BaseCoordinatesWalker;
import org.wickedsource.docxstamper.util.walk.CoordinatesWalker;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PlaceholderReplacer<T> {

private Logger logger = LoggerFactory.getLogger(PlaceholderReplacer.class);
private Logger logger = LoggerFactory.getLogger(PlaceholderReplacer.class);

private ExpressionUtil expressionUtil = new ExpressionUtil();

private ExpressionUtil expressionUtil = new ExpressionUtil();
private ExpressionResolver expressionResolver = new ExpressionResolver();

private ExpressionResolver expressionResolver = new ExpressionResolver();
private TypeResolverRegistry typeResolverRegistry;

private TypeResolverRegistry typeResolverRegistry;
private String lineBreakPlaceholder;

private String lineBreakPlaceholder;
private boolean leaveEmptyOnExpressionError = false;

private boolean leaveEmptyOnExpressionError = false;
private boolean replaceNullValues = false;

private boolean replaceNullValues = false;
public PlaceholderReplacer(TypeResolverRegistry typeResolverRegistry) {
this.typeResolverRegistry = typeResolverRegistry;
}

public PlaceholderReplacer(TypeResolverRegistry typeResolverRegistry) {
this.typeResolverRegistry = typeResolverRegistry;
}
public PlaceholderReplacer(TypeResolverRegistry typeResolverRegistry, String lineBreakPlaceholder) {
this.typeResolverRegistry = typeResolverRegistry;
this.lineBreakPlaceholder = lineBreakPlaceholder;
}

public PlaceholderReplacer(TypeResolverRegistry typeResolverRegistry, String lineBreakPlaceholder) {
this.typeResolverRegistry = typeResolverRegistry;
this.lineBreakPlaceholder = lineBreakPlaceholder;
}
public boolean isLeaveEmptyOnExpressionError() {
return leaveEmptyOnExpressionError;
}

public boolean isLeaveEmptyOnExpressionError() {
return leaveEmptyOnExpressionError;
}
public void setLeaveEmptyOnExpressionError(boolean leaveEmptyOnExpressionError) {
this.leaveEmptyOnExpressionError = leaveEmptyOnExpressionError;
}

public void setLeaveEmptyOnExpressionError(boolean leaveEmptyOnExpressionError) {
this.leaveEmptyOnExpressionError = leaveEmptyOnExpressionError;
}
public void setReplaceNullValues(boolean replaceNullValues) {
this.replaceNullValues = replaceNullValues;
}

public void setReplaceNullValues(boolean replaceNullValues) {
this.replaceNullValues = replaceNullValues;
}

public void setExpressionResolver(ExpressionResolver expressionResolver) {
this.expressionResolver = expressionResolver;
}
public void setExpressionResolver(ExpressionResolver expressionResolver) {
this.expressionResolver = expressionResolver;
}

/**
* Finds expressions in a document and resolves them against the specified context object. The expressions in the
* document are then replaced by the resolved values.
*
* @param document the document in which to replace all expressions.
* @param proxyBuilder builder for a proxy around the context root to customize its interface
*/
public void resolveExpressions(final WordprocessingMLPackage document, ProxyBuilder<T> proxyBuilder) {
try {
final T expressionContext = proxyBuilder.build();
CoordinatesWalker walker = new BaseCoordinatesWalker(document) {
@Override
protected void onParagraph(ParagraphCoordinates paragraphCoordinates) {
resolveExpressionsForParagraph(paragraphCoordinates.getParagraph(), expressionContext, document);
/**
* Finds expressions in a document and resolves them against the specified context object. The expressions in the
* document are then replaced by the resolved values.
*
* @param document the document in which to replace all expressions.
* @param proxyBuilder builder for a proxy around the context root to customize its interface
*/
public void resolveExpressions(final WordprocessingMLPackage document, ProxyBuilder<T> proxyBuilder) {
try {
final T expressionContext = proxyBuilder.build();
CoordinatesWalker walker = new BaseCoordinatesWalker(document) {
@Override
protected void onParagraph(ParagraphCoordinates paragraphCoordinates) {
resolveExpressionsForParagraph(paragraphCoordinates.getParagraph(), expressionContext, document);
}
};
walker.walk();
} catch (ProxyException e) {
throw new DocxStamperException("could not create proxy around context root!", e);
}
};
walker.walk();
} catch (ProxyException e) {
throw new DocxStamperException("could not create proxy around context root!", e);
}
}

@SuppressWarnings("unchecked")
public void resolveExpressionsForParagraph(P p, T expressionContext, WordprocessingMLPackage document) {
ParagraphWrapper paragraphWrapper = new ParagraphWrapper(p);
List<String> placeholders = expressionUtil.findVariableExpressions(paragraphWrapper.getText());
for (String placeholder : placeholders) {
try {
Object replacement = expressionResolver.resolveExpression(placeholder, expressionContext);
if (replacement != null) {
ITypeResolver resolver = typeResolverRegistry.getResolverForType(replacement.getClass());
Object replacementObject = resolver.resolve(document, replacement);
replace(paragraphWrapper, placeholder, replacementObject);
logger.debug(String.format("Replaced expression '%s' with value provided by TypeResolver %s", placeholder, resolver.getClass()));
} else if(replaceNullValues) {
ITypeResolver resolver = typeResolverRegistry.getDefaultResolver();
Object replacementObject = resolver.resolve(document, replacement);
replace(paragraphWrapper, placeholder, replacementObject);
logger.debug(String.format("Replaced expression '%s' with value provided by TypeResolver %s", placeholder, resolver.getClass()));

@SuppressWarnings("unchecked")
public void resolveExpressionsForParagraph(P p, T expressionContext, WordprocessingMLPackage document) {
ParagraphWrapper paragraphWrapper = new ParagraphWrapper(p);
List<String> placeholders = expressionUtil.findVariableExpressions(paragraphWrapper.getText());
LinkedHashMap<String, RPr> placeholdersAndStyle = paragraphWrapper.getPlaceholderAndStyle();
Map<String, Integer> indexMap = new HashMap<String, Integer>();
RPr rPr;
for (String placeholder : placeholders) {
if(!indexMap.containsKey(placeholder)){
indexMap.put(placeholder, 0);
} else {
indexMap.put(placeholder, indexMap.get(placeholder) + 1);
}
List<RPr> collect = placeholdersAndStyle.entrySet().stream()
.filter(m -> m.getKey().replaceAll("_.*", "").equals(placeholder))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
if(collect != null && collect.size() > indexMap.get(placeholder))
rPr = collect.get(indexMap.get(placeholder));
else
rPr = null;
try {
Object replacement = expressionResolver.resolveExpression(placeholder, expressionContext);
if (replacement != null) {
ITypeResolver resolver = typeResolverRegistry.getResolverForType(replacement.getClass());
Object replacementObject = resolver.resolve(document, replacement);
replaceRstyle(paragraphWrapper, placeholder, rPr, replacementObject);
logger.debug(String.format("Replaced expression '%s' with value provided by TypeResolver %s", placeholder, resolver.getClass()));
} else if (replaceNullValues) {
ITypeResolver resolver = typeResolverRegistry.getDefaultResolver();
Object replacementObject = resolver.resolve(document, replacement);
replace(paragraphWrapper, placeholder, replacementObject);
logger.debug(String.format("Replaced expression '%s' with value provided by TypeResolver %s", placeholder, resolver.getClass()));
}
} catch (SpelEvaluationException | SpelParseException e) {
logger.warn(String.format(
"Expression %s could not be resolved against context root of type %s. Reason: %s. Set log level to TRACE to view Stacktrace.",
placeholder, expressionContext.getClass(), e.getMessage()));
logger.trace("Reason for skipping expression:", e);

if (isLeaveEmptyOnExpressionError()) {
replace(paragraphWrapper, placeholder, null);
}
}

}
} catch (SpelEvaluationException | SpelParseException e) {
logger.warn(String.format(
"Expression %s could not be resolved against context root of type %s. Reason: %s. Set log level to TRACE to view Stacktrace.",
placeholder, expressionContext.getClass(), e.getMessage()));
logger.trace("Reason for skipping expression:", e);

if(isLeaveEmptyOnExpressionError()) {
replace(paragraphWrapper,placeholder,null);
if (this.lineBreakPlaceholder != null) {
replaceLineBreaks(paragraphWrapper);
}
}
}
if (this.lineBreakPlaceholder != null) {
replaceLineBreaks(paragraphWrapper);

}
}

private void replaceLineBreaks(ParagraphWrapper paragraphWrapper) {
Br lineBreak = Context.getWmlObjectFactory().createBr();
R run = RunUtil.create(lineBreak);
while (paragraphWrapper.getText().contains(this.lineBreakPlaceholder)) {
replace(paragraphWrapper, this.lineBreakPlaceholder, run);
private void replaceLineBreaks(ParagraphWrapper paragraphWrapper) {
Br lineBreak = Context.getWmlObjectFactory().createBr();
R run = RunUtil.create(lineBreak);
while (paragraphWrapper.getText().contains(this.lineBreakPlaceholder)) {
replace(paragraphWrapper, this.lineBreakPlaceholder, run);
}
}
}

public void replace(ParagraphWrapper p, String placeholder, Object replacementObject) {
if (replacementObject == null) {
replacementObject = RunUtil.create("");
public void replace(ParagraphWrapper p, String placeholder, Object replacementObject) {
if (replacementObject == null) {
replacementObject = RunUtil.create("");
}
if (replacementObject instanceof R) {
RunUtil.applyParagraphStyle(p.getParagraph(), (R) replacementObject);

}
p.replace(placeholder, replacementObject);
}
if (replacementObject instanceof R) {
RunUtil.applyParagraphStyle(p.getParagraph(), (R) replacementObject);

private void replaceRstyle(ParagraphWrapper p, String placeholder, RPr rPr, Object replacementObject) {
if (replacementObject == null) {
replacementObject = RunUtil.create("");
}
if (replacementObject instanceof R) {
RunUtil.applyRunStyle(rPr, (R) replacementObject);

}
p.replace(placeholder, replacementObject);
}
p.replace(placeholder, replacementObject);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.docx4j.wml.RPr;
import org.wickedsource.docxstamper.replace.IndexedRun;
import org.wickedsource.docxstamper.util.RunUtil;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* A "Run" defines a region of text within a docx document with a common set of properties. Word processors are
Expand Down Expand Up @@ -148,6 +151,15 @@ public String getText() {
return getText(this.runs);
}

/**
* Returns each Placeholder and its run.
*
* @return the placeholder and its run as linkedHashMap
*/
public LinkedHashMap<String, RPr> getPlaceholderAndStyle() {
return getPlaceholderAndStyle(this.runs);
}

private String getText(List<IndexedRun> runs) {
StringBuilder builder = new StringBuilder();
for (IndexedRun run : runs) {
Expand All @@ -156,6 +168,32 @@ private String getText(List<IndexedRun> runs) {
return builder.toString();
}

private LinkedHashMap<String, RPr> getPlaceholderAndStyle(List<IndexedRun> runs) {
LinkedHashMap<String, RPr> placeholderRpr = new LinkedHashMap<>();
StringBuilder builder = new StringBuilder();

for (IndexedRun run : runs) {
builder.append(RunUtil.getText(run.getRun()));
Pattern p = Pattern.compile("\\$\\{.*?}");
Matcher m = p.matcher(builder.toString());
RPr rPr = new RPr();
String placeholder = "";
int index = 0;
while (m.find(index)) {
builder = new StringBuilder();
rPr = run.getRun().getRPr();
placeholder = m.group();
index = m.end();
}
if (!"".equals(placeholder)) {
placeholderRpr.put(placeholder+"_"+run.getStartIndex(), rPr);
}

}

return placeholderRpr;
}

/**
* Returns the list of runs that are aggregated. Depending on what modifications were done to the aggregated text
* this list may not return the same runs that were initially added to the aggregator.
Expand Down
18 changes: 16 additions & 2 deletions src/main/java/org/wickedsource/docxstamper/util/RunUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static String getText(R run) {
text = text.trim();
}
result += text;
}else if (element.getValue() instanceof R.Tab){
} else if (element.getValue() instanceof R.Tab) {
result += "\t";
}
} else if (content instanceof Text) {
Expand All @@ -58,6 +58,19 @@ public static void applyParagraphStyle(P p, R run) {
}
}

/**
* Applies the style of the given run to the given content object (if the content object is a Run).
*
* @param rPr the run whose style to use.
* @param run the Run to which the style should be applied.
*/
public static void applyRunStyle(RPr rPr, R run) {
RPr runProperties = new RPr();
StyleUtil.apply(rPr, runProperties);
run.setRPr(runProperties);

}

/**
* Sets the text of the given run to the given value.
*
Expand Down Expand Up @@ -87,10 +100,11 @@ public static R create(String text) {

/**
* Creates a new run with the given object as content.
*
* @param content the content of the run.
* @return the newly created run.
*/
public static R create(Object content){
public static R create(Object content) {
R run = factory.createR();
run.getContent().add(content);
return run;
Expand Down
Loading