com.deepoove.poi.xwpf.XWPFParagraphWrapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of poi-tl Show documentation
Show all versions of poi-tl Show documentation
Generate word(docx) with template
/*
* Copyright 2014-2021 Sayi
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.deepoove.poi.xwpf;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.xml.namespace.QName;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.xwpf.usermodel.IRunBody;
import org.apache.poi.xwpf.usermodel.IRunElement;
import org.apache.poi.xwpf.usermodel.XWPFFieldRun;
import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRelation;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFSDT;
import org.apache.xmlbeans.QNameSet;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBookmark;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTMarkupRange;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtBlock;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.impl.CTPImpl;
import com.deepoove.poi.exception.ReflectionException;
/**
* XWPFParagraph wrapper
*
* @author Sayi
*/
public class XWPFParagraphWrapper {
static final QName HYPER_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"hyperlink");
static final QName FLDSIMPLE_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"fldSimple");
static final QName R_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "r");
static final QName BOOKMARK_START_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"bookmarkStart");
static final QName BOOKMARK_END_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"bookmarkEnd");
static final QName COMMENT_START_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"commentRangeStart");
static final QName COMMENT_END_QNAME = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main",
"commentRangeEnd");
static final QNameSet RUN_QNAME_SET = QNameSet.forArray(new QName[] { HYPER_QNAME, FLDSIMPLE_QNAME, R_QNAME });
XWPFParagraph paragraph;
List sdts = null;
public XWPFParagraphWrapper(XWPFParagraph paragraph) {
this.paragraph = paragraph;
// clean structure when have duplicate XWPFHyperlinkRun
// because wrong structure lead to wrong position
List runs = getRuns();
List iRuns = getIRuns();
List caches = new ArrayList<>();
Iterator iterator = runs.iterator();
while (iterator.hasNext()) {
XWPFRun next = iterator.next();
if (next instanceof XWPFHyperlinkRun) {
CTHyperlink ctHyperlink = ((XWPFHyperlinkRun) next).getCTHyperlink();
if (caches.contains(ctHyperlink)) {
iterator.remove();
iRuns.remove(next);
} else {
caches.add(ctHyperlink);
}
}
}
}
public XWPFRun insertNewHyperLinkRun(XWPFRun run, String link) {
if (StringUtils.isBlank(link)) throw new IllegalArgumentException("Hyperlink must not be Empty!");
if (link.startsWith("anchor:")) {
return insertNewAnchor(getPosOfRun(run), link.substring("anchor:".length()));
}
return insertNewHyperLinkRun(getPosOfRun(run), link);
}
public XWPFHyperlinkRun insertNewHyperLinkRun(int pos, String link) {
if (pos >= 0 && pos <= paragraph.getRuns().size()) {
CTHyperlink hyperlink = insertNewHyperlink(pos);
// hyperlink.setAnchor(link.substring("anchor:".length()));
PackageRelationship relationship = paragraph.getPart()
.getPackagePart()
.addExternalRelationship(link, XWPFRelation.HYPERLINK.getRelation());
hyperlink.setId(relationship.getId());
CTR ctr = hyperlink.addNewR();
XWPFHyperlinkRun newRun = new XWPFHyperlinkRun(hyperlink, ctr, (IRunBody) paragraph);
updateRunsAndIRuns(pos, newRun);
return newRun;
}
return null;
}
public CTHyperlink insertNewHyperlink(int paramInt) {
CTP ctp = paragraph.getCTP();
synchronized (ctp.monitor()) {
// check_orphaned();
CTHyperlink localCTHyperlink = null;
localCTHyperlink = (CTHyperlink) ((CTPImpl) ctp).get_store()
.insert_element_user(RUN_QNAME_SET, HYPER_QNAME, paramInt);
return localCTHyperlink;
}
}
public XWPFFieldRun insertNewAnchor(int pos, String anchorName) {
if (pos >= 0 && pos <= paragraph.getRuns().size()) {
XWPFFieldRun insertNewField = insertNewField(pos);
insertNewField.setFieldInstruction("HYPERLINK \\l \"" + anchorName + "\"");
return insertNewField;
}
return null;
}
public CTBookmark insertNewBookmark(XWPFRun run) {
int pos = getPosOfRun(run);
CTMarkupRange end = insertNewBookmarkEnd(pos + 1);
CTBookmark start = insertNewBookmarkStart(pos);
start.setId(BigInteger.valueOf(500 + new Random().nextInt(50000)));
end.setId(start.getId());
return start;
}
public CTMarkupRange insertNewBookmarkEnd(int paramInt) {
CTP ctp = paragraph.getCTP();
synchronized (ctp.monitor()) {
// check_orphaned();
CTMarkupRange local = null;
local = (CTMarkupRange) ((CTPImpl) ctp).get_store()
.insert_element_user(RUN_QNAME_SET, BOOKMARK_END_QNAME, paramInt);
return local;
}
}
public CTBookmark insertNewBookmarkStart(int paramInt) {
CTP ctp = paragraph.getCTP();
synchronized (ctp.monitor()) {
// check_orphaned();
CTBookmark local = null;
local = (CTBookmark) ((CTPImpl) ctp).get_store()
.insert_element_user(RUN_QNAME_SET, BOOKMARK_START_QNAME, paramInt);
return local;
}
}
public void insertNewCommentRangeStart(XWPFRun run, BigInteger cId) {
int pos = getPosOfRun(run);
CTP ctp = paragraph.getCTP();
synchronized (ctp.monitor()) {
// check_orphaned();
CTMarkupRange mark = (CTMarkupRange) ((CTPImpl) ctp).get_store()
.insert_element_user(RUN_QNAME_SET, COMMENT_START_QNAME, pos);
mark.setId(cId);
}
}
public void insertNewCommentRangeEnd(XWPFRun run, BigInteger cId) {
int pos = getPosOfRun(run);
CTP ctp = paragraph.getCTP();
synchronized (ctp.monitor()) {
// check_orphaned();
CTMarkupRange mark = (CTMarkupRange) ((CTPImpl) ctp).get_store()
.insert_element_user(RUN_QNAME_SET, COMMENT_END_QNAME, pos);
mark.setId(cId);
}
}
public CTR insertNewR(int paramInt) {
CTP ctp = paragraph.getCTP();
synchronized (ctp.monitor()) {
// check_orphaned();
CTR localCTR = null;
localCTR = (CTR) ((CTPImpl) ctp).get_store().insert_element_user(RUN_QNAME_SET, R_QNAME, paramInt);
return localCTR;
}
}
public XWPFRun insertNewRun(int pos) {
if (pos >= 0 && pos <= paragraph.getRuns().size()) {
CTR ctRun = this.insertNewR(pos);
XWPFRun newRun = new XWPFRun(ctRun, (IRunBody) paragraph);
updateRunsAndIRuns(pos, newRun);
return newRun;
}
return null;
}
public XWPFRun insertNewRunAfter(int pos) {
if (pos == paragraph.getRuns().size()) {
return paragraph.createRun();
}
if (pos >= 0 && pos < paragraph.getRuns().size()) {
XWPFRun run = paragraph.getRuns().get(pos);
CTR ctr = run.getCTR();
XmlCursor newCursor = ctr.newCursor();
if (!isCursorInParagraph(newCursor)) {
// look up correct position for CTP -> XXX -> R array
newCursor.toParent();
}
if (isCursorInParagraph(newCursor)) {
boolean nextSibling = newCursor.toNextSibling();
if (!nextSibling) {
return paragraph.createRun();
}
String uri = CTR.type.getName().getNamespaceURI();
String localPart = "r";
newCursor.beginElement(localPart, uri);
newCursor.toParent();
CTR r = (CTR) newCursor.getObject();
XWPFRun newRun = new XWPFRun(r, (IRunBody) this.paragraph);
List iruns = getIRuns();
List runs = getRuns();
int iPos = iruns.size();
if (pos < runs.size()) {
XWPFRun oldAtPos = runs.get(pos);
int oldAt = iruns.indexOf(oldAtPos);
if (oldAt != -1) {
iPos = oldAt;
}
}
if (iPos + 1 >= iruns.size()) {
iruns.add(newRun);
} else {
iruns.add(iPos + 1, newRun);
}
// Runs itself is easy to update
if (pos + 1 >= runs.size()) {
runs.add(newRun);
} else {
runs.add(pos + 1, newRun);
}
newCursor.dispose();
return newRun;
}
newCursor.dispose();
}
return null;
}
private boolean isCursorInParagraph(XmlCursor cursor) {
XmlCursor verify = cursor.newCursor();
verify.toParent();
boolean result = (verify.getObject() == this.paragraph.getCTP());
verify.dispose();
return result;
}
public CTSimpleField insertNewFldSimple(int paramInt) {
CTP ctp = paragraph.getCTP();
synchronized (ctp.monitor()) {
// check_orphaned();
CTSimpleField localCTSimpleField = null;
localCTSimpleField = (CTSimpleField) ((CTPImpl) ctp).get_store()
.insert_element_user(RUN_QNAME_SET, FLDSIMPLE_QNAME, paramInt);
return localCTSimpleField;
}
}
public XWPFFieldRun insertNewField(int pos) {
if (pos >= 0 && pos <= paragraph.getRuns().size()) {
CTSimpleField ctSimpleField = this.insertNewFldSimple(pos);
CTR addNewR = ctSimpleField.addNewR();
XWPFFieldRun newRun = new XWPFFieldRun(ctSimpleField, addNewR, (IRunBody) paragraph);
// To update the iruns, find where we're going
// in the normal runs, and go in there
updateRunsAndIRuns(pos, newRun);
return newRun;
}
return null;
}
private void updateRunsAndIRuns(int pos, XWPFRun newRun) {
List iruns = getIRuns();
List runs = getRuns();
int iPos = iruns.size();
if (pos < runs.size()) {
XWPFRun oldAtPos = runs.get(pos);
int oldAt = iruns.indexOf(oldAtPos);
if (oldAt != -1) {
iPos = oldAt;
}
}
iruns.add(iPos, newRun);
// Runs itself is easy to update
runs.add(pos, newRun);
}
private int getPosOfRun(XWPFRun run) {
int pos = -1;
List runs = paragraph.getRuns();
for (int i = 0; i < runs.size(); i++) {
if (run == runs.get(i)) {
pos = i;
break;
}
}
return pos;
}
@SuppressWarnings("unchecked")
private List getRuns() {
try {
Field runsField = XWPFParagraph.class.getDeclaredField("runs");
runsField.setAccessible(true);
return (List) runsField.get(paragraph);
} catch (Exception e) {
throw new ReflectionException("runs", XWPFParagraph.class, e);
}
}
@SuppressWarnings("unchecked")
private List getIRuns() {
try {
Field runsField = XWPFParagraph.class.getDeclaredField("iruns");
runsField.setAccessible(true);
return (List) runsField.get(paragraph);
} catch (Exception e) {
throw new ReflectionException("iruns", XWPFParagraph.class, e);
}
}
public void setAndUpdateRun(XWPFRun xwpfRun, XWPFRun source, int insertPostionCursor) {
// body
// maybe need find correct position:rPos;
int rPos = 0;
List runs = getRuns();
if (insertPostionCursor >= 0 && insertPostionCursor <= runs.size()) {
// calculate the correct pos as our run/irun list contains
// hyperlinks
// and fields so it is different to the paragraph R array.
for (int i = 0; i < insertPostionCursor; i++) {
XWPFRun currRun = runs.get(i);
if (xwpfRun instanceof XWPFHyperlinkRun) {
if (currRun instanceof XWPFHyperlinkRun) {
rPos++;
}
} else if (xwpfRun instanceof XWPFFieldRun) {
if (currRun instanceof XWPFFieldRun) {
rPos++;
}
} else {
if (!(currRun instanceof XWPFHyperlinkRun || currRun instanceof XWPFFieldRun)) {
rPos++;
}
}
}
}
if (xwpfRun instanceof XWPFHyperlinkRun) {
paragraph.getCTP().setHyperlinkArray(rPos, ((XWPFHyperlinkRun) xwpfRun).getCTHyperlink());
} else if (xwpfRun instanceof XWPFFieldRun) {
paragraph.getCTP().setFldSimpleArray(rPos, ((XWPFFieldRun) xwpfRun).getCTField());
} else {
paragraph.getCTP().setRArray(rPos, xwpfRun.getCTR());
}
// runs
for (int i = 0; i < runs.size(); i++) {
XWPFRun ele = runs.get(i);
if (ele == source) {
runs.set(i, xwpfRun);
}
}
// iruns
List iruns = getIRuns();
for (int i = 0; i < iruns.size(); i++) {
IRunElement ele = iruns.get(i);
if (ele == source) {
iruns.set(i, xwpfRun);
}
}
}
public boolean removeRun(int pos) {
List iruns = getIRuns();
List runs = getRuns();
if (pos >= 0 && pos < runs.size()) {
// Remove the run from our high level lists
XWPFRun run = runs.get(pos);
runs.remove(pos);
iruns.remove(run);
// Remove the run from the low-level XML
// calculate the correct pos as our run/irun list contains
// hyperlinks and fields so is different to the paragraph R array.
int rPos = 0;
for (int i = 0; i < pos; i++) {
XWPFRun currRun = runs.get(i);
if (run instanceof XWPFHyperlinkRun) {
if (currRun instanceof XWPFHyperlinkRun) {
rPos++;
}
} else if (run instanceof XWPFFieldRun) {
if (currRun instanceof XWPFFieldRun) {
rPos++;
}
} else {
if (!(currRun instanceof XWPFHyperlinkRun || currRun instanceof XWPFFieldRun)) {
rPos++;
}
}
}
if (run instanceof XWPFHyperlinkRun) {
paragraph.getCTP().removeHyperlink(rPos);
} else if (run instanceof XWPFFieldRun) {
paragraph.getCTP().removeFldSimple(rPos);
} else {
paragraph.getCTP().removeR(rPos);
}
return true;
}
return false;
}
public List getSDTs() {
if (null != sdts) return sdts;
sdts = new ArrayList<>();
long count = paragraph.getIRuns().stream().filter(r -> r instanceof XWPFSDT).count();
if (0 == count) return sdts;
CTP ctp = paragraph.getCTP();
XmlCursor c = ctp.newCursor();
try {
c.selectPath("child::*");
while (c.toNextSelection()) {
XmlObject o = c.getObject();
if (o instanceof CTSdtBlock) {
XWPFStructuredDocumentTag cc = new XWPFStructuredDocumentTag((CTSdtBlock) o, paragraph.getBody());
sdts.add(cc);
}
if (o instanceof CTSdtRun) {
XWPFStructuredDocumentTag cc = new XWPFStructuredDocumentTag((CTSdtRun) o, paragraph.getBody());
sdts.add(cc);
}
}
} finally {
c.dispose();
}
return sdts;
}
public XWPFParagraph getParagraph() {
return paragraph;
}
}