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

pro.verron.officestamper.core.StandardParagraph 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.6.0
Show newest version
package pro.verron.officestamper.core;

import jakarta.xml.bind.JAXBElement;
import org.docx4j.wml.*;
import pro.verron.officestamper.api.Paragraph;
import pro.verron.officestamper.api.Placeholder;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import static java.util.stream.Collectors.joining;

/**
 * 

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 {@link #add(R, int)} to add all runs that should be aggregated. Then, call * methods to modify the aggregated text. Finally, call {@link #asString()} to get the modified text. * * @author Joseph Verron * @author Tom Hombergs * @version ${version} * @since 1.0.8 */ public class StandardParagraph implements Paragraph { private final List runs = new ArrayList<>(); private final List contents; private final PPr paragraphPr; private final P paragraph; private int currentPosition = 0; /** * Constructs a new ParagraphWrapper for the given paragraph. * * @param paragraph the paragraph to wrap. */ public StandardParagraph(P paragraph) { this.paragraph = paragraph; this.contents = this.paragraph.getContent(); this.paragraphPr = paragraph.getPPr(); recalculateRuns(); } /** * Recalculates the runs of the paragraph. This method is called automatically by the constructor, but can also be * called manually to recalculate the runs after a modification to the paragraph was done. */ private void recalculateRuns() { currentPosition = 0; this.runs.clear(); add(0, contents); } private int add(int index, List objects) { for (Object object : objects) index = add(index, object); return index; } private int add(int index, Object object) { if (object instanceof R r) return add(r, index); else if (object instanceof SdtRun sdtRun) return add(index, sdtRun); else if (object instanceof JAXBElement jaxbElement) return add(index, jaxbElement.getValue()); else return index + 1; } private int add(int index, SdtRun sdtRun) { return add(index, sdtRun.getSdtContent()); } private int add(int index, SdtContent sdtContent) { return this.add(index, sdtContent.getContent()); } /** * Adds a run to the aggregation. * * @param run the run to add. */ private int add(R run, int index) { int endPosition = currentPosition + RunUtil.getLength(run); runs.add(new IndexedRun(currentPosition, endPosition, index, run)); currentPosition = endPosition; return index + 1; } /** * Replaces the given expression with the replacement object within * the paragraph. * The replacement object must be a valid DOCX4J Object. * * @param placeholder the expression to be replaced. * @param replacement the object to replace the expression. */ @Override public void replace(Placeholder placeholder, Object replacement) { if (replacement instanceof R run) { replaceWithRun(placeholder, run); } else if (replacement instanceof Br br) { replaceWithBr(placeholder, br); } else { throw new AssertionError("replacement must be a R"); } } private void replaceWithRun(Placeholder placeholder, R replacement) { var text = asString(); String full = placeholder.expression(); int matchStartIndex = text.indexOf(full); if (matchStartIndex == -1) { // nothing to replace return; } int matchEndIndex = matchStartIndex + full.length(); List affectedRuns = getAffectedRuns(matchStartIndex, matchEndIndex); boolean singleRun = affectedRuns.size() == 1; if (singleRun) { IndexedRun run = affectedRuns.get(0); boolean expressionSpansCompleteRun = full.length() == run.length(); boolean expressionAtStartOfRun = matchStartIndex == run.startIndex(); boolean expressionAtEndOfRun = matchEndIndex == run.endIndex(); boolean expressionWithinRun = matchStartIndex > run.startIndex() && matchEndIndex <= run.endIndex(); replacement.setRPr(run.getPr()); if (expressionSpansCompleteRun) { contents.remove(run.run()); contents.add(run.indexInParent(), replacement); recalculateRuns(); } else if (expressionAtStartOfRun) { run.replace(matchStartIndex, matchEndIndex, ""); contents.add(run.indexInParent(), replacement); recalculateRuns(); } else if (expressionAtEndOfRun) { run.replace(matchStartIndex, matchEndIndex, ""); contents.add(run.indexInParent() + 1, replacement); recalculateRuns(); } else if (expressionWithinRun) { int startIndex = run.indexOf(full); int endIndex = startIndex + full.length(); R run1 = RunUtil.create(run.substring(0, startIndex), paragraphPr); R run2 = RunUtil.create(run.substring(endIndex), paragraphPr); contents.add(run.indexInParent(), run2); contents.add(run.indexInParent(), replacement); contents.add(run.indexInParent(), run1); contents.remove(run.run()); recalculateRuns(); } } else if (affectedRuns.get(0) .run() .getParent() == paragraph) { IndexedRun firstRun = affectedRuns.get(0); IndexedRun lastRun = affectedRuns.get(affectedRuns.size() - 1); replacement.setRPr(firstRun.getPr()); // remove the expression 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 (!Objects.equals(run, firstRun) && !Objects.equals(run, lastRun)) { contents.remove(run.run()); } } // add replacement run between first and last run contents.add(firstRun.indexInParent() + 1, replacement); recalculateRuns(); } else { IndexedRun firstRun = affectedRuns.get(0); IndexedRun lastRun = affectedRuns.get(affectedRuns.size() - 1); var siblings = ((ContentAccessor) firstRun.run() .getParent()).getContent(); replacement.setRPr(firstRun.getPr()); // remove the expression 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 (!Objects.equals(run, firstRun) && !Objects.equals(run, lastRun)) { siblings.remove(run.run()); } } // add replacement run between first and last run siblings.add(siblings.indexOf(firstRun) + 1, replacement); recalculateRuns(); } } private void replaceWithBr(Placeholder placeholder, Br br) { for (IndexedRun indexedRun : runs) { var run = indexedRun.run(); var content = run.getContent(); var iterator = content.listIterator(); while (iterator.hasNext()) { Object element = iterator.next(); if (element instanceof JAXBElement jaxbElement) { element = jaxbElement.getValue(); } if (element instanceof Text text) { var value = text.getValue(); if (value.contains(placeholder.expression())) { iterator.remove(); var iterator1 = Arrays.stream(value.split(placeholder.expression())) .iterator(); while (iterator1.hasNext()) { var next = iterator1.next(); var text1 = new Text(); text1.setValue(next); iterator.add(text1); if (iterator1.hasNext()) iterator.add(br); } } } } } } /** * Returns the aggregated text over all runs. * * @return the text of all runs. */ @Override public String asString() { return runs.stream() .map(IndexedRun::run) .map(RunUtil::getText) .collect(joining()); } private List getAffectedRuns(int startIndex, int endIndex) { return runs.stream() .filter(run -> run.isTouchedByRange(startIndex, endIndex)) .toList(); } /** * {@inheritDoc} */ @Override public String toString() { return asString(); } }