org.wickedsource.docxstamper.util.ParagraphWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of docx-stamper Show documentation
Show all versions of docx-stamper Show documentation
Template engine for .docx documents.
The newest version!
package org.wickedsource.docxstamper.util;
import org.docx4j.wml.P;
import org.docx4j.wml.R;
import org.wickedsource.docxstamper.replace.IndexedRun;
import org.wickedsource.docxstamper.util.RunUtil;
import java.util.ArrayList;
import java.util.List;
/**
* A "Run" defines a region of text within a docx document with a common set of properties. Word processors are
* relatively free in splitting a paragraph of text into multiple runs, so there is no strict rule to say over how many
* runs a word or a string of words is spread.
*
* This class aggregates multiple runs so they can be treated as a single text, no matter how many runs the text spans.
* Call addRun() to add all runs that should be aggregated. Then, call methods to modify the aggregated text. Finally,
* call getText() or getRuns() to get the modified text or the list of modified runs.
*/
public class ParagraphWrapper {
private int currentPosition = 0;
private List runs = new ArrayList<>();
private P paragraph;
public ParagraphWrapper(P paragraph) {
this.paragraph = paragraph;
recalculateRuns();
}
public void recalculateRuns() {
currentPosition = 0;
this.runs.clear();
int index = 0;
for (Object contentElement : paragraph.getContent()) {
if (contentElement instanceof R && !"".equals(RunUtil.getText((R) contentElement))) {
this.addRun((R) contentElement, index);
}
index++;
}
}
/**
* Adds a run to the aggregation.
*
* @param run the run to add.
*/
private void addRun(R run, int index) {
int startIndex = currentPosition;
int endIndex = currentPosition + RunUtil.getText(run).length() - 1;
runs.add(new IndexedRun(startIndex, endIndex, index, run));
currentPosition = endIndex + 1;
}
/**
* Replaces the given placeholder String with the replacement object within the paragraph.
* The replacement object must be a valid DOCX4J Object.
*
* @param placeholder the placeholder to be replaced.
* @param replacement the object to replace the placeholder String.
*/
public void replace(String placeholder, Object replacement) {
String text = getText();
int matchStartIndex = text.indexOf(placeholder);
if (matchStartIndex == -1) {
// nothing to replace
return;
}
int matchEndIndex = matchStartIndex + placeholder.length() - 1;
List affectedRuns = getAffectedRuns(matchStartIndex, matchEndIndex);
boolean singleRun = affectedRuns.size() == 1;
if (singleRun) {
IndexedRun run = affectedRuns.get(0);
boolean placeholderSpansCompleteRun = placeholder.length() == RunUtil.getText(run.getRun()).length();
boolean placeholderAtStartOfRun = matchStartIndex == run.getStartIndex();
boolean placeholderAtEndOfRun = matchEndIndex == run.getEndIndex();
boolean placeholderWithinRun = matchStartIndex > run.getStartIndex() && matchEndIndex < run.getEndIndex();
if (placeholderSpansCompleteRun) {
this.paragraph.getContent().remove(run.getRun());
this.paragraph.getContent().add(run.getIndexInParent(), replacement);
recalculateRuns();
} else if (placeholderAtStartOfRun) {
run.replace(matchStartIndex, matchEndIndex, "");
this.paragraph.getContent().add(run.getIndexInParent(), replacement);
recalculateRuns();
} else if (placeholderAtEndOfRun) {
run.replace(matchStartIndex, matchEndIndex, "");
this.paragraph.getContent().add(run.getIndexInParent() + 1, replacement);
recalculateRuns();
} else if (placeholderWithinRun) {
String runText = RunUtil.getText(run.getRun());
int startIndex = runText.indexOf(placeholder);
int endIndex = startIndex + placeholder.length();
R run1 = RunUtil.create(runText.substring(0, startIndex), this.paragraph);
R run2 = RunUtil.create(runText.substring(endIndex), this.paragraph);
this.paragraph.getContent().add(run.getIndexInParent(), run2);
this.paragraph.getContent().add(run.getIndexInParent(), replacement);
this.paragraph.getContent().add(run.getIndexInParent(), run1);
this.paragraph.getContent().remove(run.getRun());
recalculateRuns();
}
} else {
IndexedRun firstRun = affectedRuns.get(0);
IndexedRun lastRun = affectedRuns.get(affectedRuns.size() - 1);
// remove the placeholder from first and last run
firstRun.replace(matchStartIndex, matchEndIndex, "");
lastRun.replace(matchStartIndex, matchEndIndex, "");
// remove all runs between first and last
for (IndexedRun run : affectedRuns) {
if (run != firstRun && run != lastRun) {
this.paragraph.getContent().remove(run.getRun());
}
}
// add replacement run between first and last run
this.paragraph.getContent().add(firstRun.getIndexInParent() + 1, replacement);
recalculateRuns();
}
}
private List getAffectedRuns(int startIndex, int endIndex) {
List affectedRuns = new ArrayList<>();
for (IndexedRun run : runs) {
if (run.isTouchedByRange(startIndex, endIndex)) {
affectedRuns.add(run);
}
}
return affectedRuns;
}
/**
* Returns the aggregated text over all runs.
*
* @return the text of all runs.
*/
public String getText() {
return getText(this.runs);
}
private String getText(List runs) {
StringBuilder builder = new StringBuilder();
for (IndexedRun run : runs) {
builder.append(RunUtil.getText(run.getRun()));
}
return builder.toString();
}
/**
* 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.
*
* @return the list of aggregated runs.
*/
public List getRuns() {
List resultList = new ArrayList<>();
for (IndexedRun run : runs) {
resultList.add(run.getRun());
}
return resultList;
}
@Override
public String toString() {
return getText();
}
public P getParagraph() {
return paragraph;
}
}