allow add and remove a HyperlinkRun or a FieldRun

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1875862 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Alain Béarez 2020-03-29 22:21:35 +00:00
parent 3cb1a38d8e
commit ce544b8550
3 changed files with 314 additions and 45 deletions

View File

@ -27,6 +27,7 @@ import org.apache.poi.xwpf.usermodel.TextAlignment;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns; import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.apache.poi.xwpf.usermodel.VerticalAlign; import org.apache.poi.xwpf.usermodel.VerticalAlign;
import org.apache.poi.xwpf.usermodel.XWPFDocument; import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFHyperlinkRun;
import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFRun;
@ -78,6 +79,11 @@ public class SimpleDocument {
r3.setFontSize(20); r3.setFontSize(20);
r3.setSubscript(VerticalAlign.SUPERSCRIPT); r3.setSubscript(VerticalAlign.SUPERSCRIPT);
// hyperlink
XWPFHyperlinkRun hyperlink = p2.insertNewHyperlinkRun(0, "http://poi.apache.org/");
hyperlink.setUnderline(UnderlinePatterns.SINGLE);
hyperlink.setColor("0000ff");
hyperlink.setText("Apache POI");
XWPFParagraph p3 = doc.createParagraph(); XWPFParagraph p3 = doc.createParagraph();
p3.setWordWrapped(true); p3.setWordWrapped(true);

View File

@ -20,6 +20,7 @@ import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function;
import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
@ -1478,50 +1479,144 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para
} }
/** /**
* insert a new Run in RunArray * Appends a new field run to this paragraph
*
* @return a new field run
*/
public XWPFFieldRun createFieldRun() {
CTSimpleField ctSimpleField = paragraph.addNewFldSimple();
XWPFFieldRun newRun = new XWPFFieldRun(ctSimpleField, ctSimpleField.addNewR(), this);
runs.add(newRun);
iruns.add(newRun);
return newRun;
}
/**
* insert a new Run in all runs
* *
* @param pos The position at which the new run should be added. * @param pos The position at which the new run should be added.
* *
* @return the inserted run or null if the given pos is out of bounds. * @return the inserted run or null if the given pos is out of bounds.
*/ */
public XWPFRun insertNewRun(int pos) { public XWPFRun insertNewRun(int pos) {
if (pos >= 0 && pos <= runs.size()) { if (pos == runs.size()) {
// calculate the correct pos as our run/irun list contains return createRun();
// hyperlinks }
// and fields so it is different to the paragraph R array. return insertNewProvidedRun(pos, newCursor -> {
int rPos = 0; String uri = CTR.type.getName().getNamespaceURI();
for (int i = 0; i < pos; i++) { String localPart = "r";
XWPFRun currRun = runs.get(i); // creates a new run, cursor is positioned inside the new
if (!(currRun instanceof XWPFHyperlinkRun // element
|| currRun instanceof XWPFFieldRun)) { newCursor.beginElement(localPart, uri);
rPos++; // move the cursor to the START token to the run just created
} newCursor.toParent();
CTR r = (CTR) newCursor.getObject();
return new XWPFRun(r, (IRunBody)this);
});
}
/**
* insert a new hyperlink Run in all runs
*
* @param pos The position at which the new run should be added.
* @param uri hyperlink uri
*
* @return the inserted run or null if the given pos is out of bounds.
*/
public XWPFHyperlinkRun insertNewHyperlinkRun(int pos, String uri) {
if (pos == runs.size()) {
return createHyperlinkRun(uri);
}
XWPFHyperlinkRun newRun = insertNewProvidedRun(pos, newCursor -> {
String namespaceURI = CTHyperlink.type.getName().getNamespaceURI();
String localPart = "hyperlink";
newCursor.beginElement(localPart, namespaceURI);
// move the cursor to the START token to the hyperlink just created
newCursor.toParent();
CTHyperlink ctHyperLink = (CTHyperlink) newCursor.getObject();
return new XWPFHyperlinkRun(ctHyperLink, ctHyperLink.addNewR(), this);
});
String rId = getPart().getPackagePart().addExternalRelationship(
uri, XWPFRelation.HYPERLINK.getRelation()
).getId();
newRun.getCTHyperlink().setId(rId);
return newRun;
}
/**
* insert a new field Run in all runs
*
* @param pos The position at which the new run should be added.
*
* @return the inserted run or null if the given pos is out of bounds.
*/
public XWPFFieldRun insertNewFieldRun(int pos) {
if (pos == runs.size()) {
return createFieldRun();
}
return insertNewProvidedRun(pos, newCursor -> {
String uri = CTSimpleField.type.getName().getNamespaceURI();
String localPart = "fldSimple";
newCursor.beginElement(localPart, uri);
// move the cursor to the START token to the field just created
newCursor.toParent();
CTSimpleField ctSimpleField = (CTSimpleField) newCursor.getObject();
return new XWPFFieldRun(ctSimpleField, ctSimpleField.addNewR(), this);
});
}
/**
* insert a new run provided by in all runs
*
* @param <T> XWPFRun or XWPFHyperlinkRun or XWPFFieldRun
* @param pos The position at which the new run should be added.
* @param provider provide a new run at position of the given cursor.
* @return the inserted run or null if the given pos is out of bounds.
*/
private <T extends XWPFRun> T insertNewProvidedRun(int pos, Function<XmlCursor, T> provider) {
if (pos >= 0 && pos < runs.size()) {
XWPFRun run = runs.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)) {
// provide a new run
T newRun = provider.apply(newCursor);
CTR ctRun = paragraph.insertNewR(rPos); // To update the iruns, find where we're going
XWPFRun newRun = new XWPFRun(ctRun, (IRunBody) this); // in the normal runs, and go in there
int iPos = iruns.size();
// To update the iruns, find where we're going int oldAt = iruns.indexOf(run);
// in the normal runs, and go in there
int iPos = iruns.size();
if (pos < runs.size()) {
XWPFRun oldAtPos = runs.get(pos);
int oldAt = iruns.indexOf(oldAtPos);
if (oldAt != -1) { if (oldAt != -1) {
iPos = oldAt; iPos = oldAt;
} }
iruns.add(iPos, newRun);
// Runs itself is easy to update
runs.add(pos, newRun);
return newRun;
} }
iruns.add(iPos, newRun); newCursor.dispose();
// Runs itself is easy to update
runs.add(pos, newRun);
return newRun;
} }
return null; return null;
} }
// TODO Add methods to allow adding a HyperlinkRun or a FieldRun
/**
* verifies that cursor is on the right position
*
* @param cursor
* @return
*/
private boolean isCursorInParagraph(XmlCursor cursor) {
XmlCursor verify = cursor.newCursor();
verify.toParent();
boolean result = (verify.getObject() == this.paragraph);
verify.dispose();
return result;
}
/** /**
* this methods parse the paragraph and search for the string searched. * this methods parse the paragraph and search for the string searched.
@ -1643,31 +1738,67 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para
*/ */
public boolean removeRun(int pos) { public boolean removeRun(int pos) {
if (pos >= 0 && pos < runs.size()) { if (pos >= 0 && pos < runs.size()) {
// Remove the run from our high level lists
XWPFRun run = runs.get(pos); XWPFRun run = runs.get(pos);
if (run instanceof XWPFHyperlinkRun || // CTP -> CTHyperlink -> R array
run instanceof XWPFFieldRun) { if (run instanceof XWPFHyperlinkRun
// TODO Add support for removing these kinds of nested runs, && isTheOnlyCTHyperlinkInRuns((XWPFHyperlinkRun) run)) {
// which aren't on the CTP -> R array, but CTP -> XXX -> R array XmlCursor c = ((XWPFHyperlinkRun) run).getCTHyperlink()
throw new IllegalArgumentException("Removing Field or Hyperlink runs not yet supported"); .newCursor();
c.removeXml();
c.dispose();
runs.remove(pos);
iruns.remove(run);
return true;
} }
// CTP -> CTField -> R array
if (run instanceof XWPFFieldRun
&& isTheOnlyCTFieldInRuns((XWPFFieldRun) run)) {
XmlCursor c = ((XWPFFieldRun) run).getCTField().newCursor();
c.removeXml();
c.dispose();
runs.remove(pos);
iruns.remove(run);
return true;
}
XmlCursor c = run.getCTR().newCursor();
c.removeXml();
c.dispose();
runs.remove(pos); runs.remove(pos);
iruns.remove(run); 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(!(currRun instanceof XWPFHyperlinkRun || currRun instanceof XWPFFieldRun)) {
rPos++;
}
}
getCTP().removeR(rPos);
return true; return true;
} }
return false; return false;
} }
/**
* Is there only one ctHyperlink in all runs
*
* @param run
* hyperlink run
* @return
*/
private boolean isTheOnlyCTHyperlinkInRuns(XWPFHyperlinkRun run) {
CTHyperlink ctHyperlink = run.getCTHyperlink();
long count = runs.stream().filter(r -> (r instanceof XWPFHyperlinkRun
&& ctHyperlink == ((XWPFHyperlinkRun) r).getCTHyperlink()))
.count();
return count <= 1;
}
/**
* Is there only one ctField in all runs
*
* @param run
* field run
* @return
*/
private boolean isTheOnlyCTFieldInRuns(XWPFFieldRun run) {
CTSimpleField ctField = run.getCTField();
long count = runs.stream().filter(r -> (r instanceof XWPFFieldRun
&& ctField == ((XWPFFieldRun) r).getCTField())).count();
return count <= 1;
}
/** /**
* returns the type of the BodyElement Paragraph * returns the type of the BodyElement Paragraph
* *

View File

@ -327,6 +327,138 @@ public final class TestXWPFParagraph {
} }
} }
@Test
public void testCreateNewRuns() throws IOException {
try (XWPFDocument doc = new XWPFDocument()) {
XWPFParagraph p = doc.createParagraph();
XWPFHyperlinkRun h = p.createHyperlinkRun("http://poi.apache.org");
XWPFFieldRun fieldRun = p.createFieldRun();
XWPFRun r = p.createRun();
assertEquals(3, p.getRuns().size());
assertEquals(0, p.getRuns().indexOf(h));
assertEquals(1, p.getRuns().indexOf(fieldRun));
assertEquals(2, p.getRuns().indexOf(r));
assertEquals(3, p.getIRuns().size());
assertEquals(0, p.getIRuns().indexOf(h));
assertEquals(1, p.getIRuns().indexOf(fieldRun));
assertEquals(2, p.getIRuns().indexOf(r));
}
}
@Test
public void testInsertNewRuns() throws IOException {
try (XWPFDocument doc = new XWPFDocument()) {
XWPFParagraph p = doc.createParagraph();
XWPFRun r = p.createRun();
assertEquals(1, p.getRuns().size());
assertEquals(0, p.getRuns().indexOf(r));
XWPFHyperlinkRun h = p.insertNewHyperlinkRun(0, "http://poi.apache.org");
assertEquals(2, p.getRuns().size());
assertEquals(0, p.getRuns().indexOf(h));
assertEquals(1, p.getRuns().indexOf(r));
XWPFFieldRun fieldRun2 = p.insertNewFieldRun(2);
assertEquals(3, p.getRuns().size());
assertEquals(2, p.getRuns().indexOf(fieldRun2));
}
}
@Test
public void testRemoveRuns() throws IOException {
try (XWPFDocument doc = new XWPFDocument()) {
XWPFParagraph p = doc.createParagraph();
XWPFRun r = p.createRun();
p.createRun();
XWPFHyperlinkRun hyperlinkRun = p
.createHyperlinkRun("http://poi.apache.org");
XWPFFieldRun fieldRun = p.createFieldRun();
assertEquals(4, p.getRuns().size());
assertEquals(2, p.getRuns().indexOf(hyperlinkRun));
assertEquals(3, p.getRuns().indexOf(fieldRun));
p.removeRun(2);
assertEquals(3, p.getRuns().size());
assertEquals(-1, p.getRuns().indexOf(hyperlinkRun));
assertEquals(2, p.getRuns().indexOf(fieldRun));
p.removeRun(0);
assertEquals(2, p.getRuns().size());
assertEquals(-1, p.getRuns().indexOf(r));
assertEquals(1, p.getRuns().indexOf(fieldRun));
p.removeRun(1);
assertEquals(1, p.getRuns().size());
assertEquals(-1, p.getRuns().indexOf(fieldRun));
}
}
@Test
public void testRemoveAndInsertRunsWithOtherIRunElement()
throws IOException {
XWPFDocument doc = new XWPFDocument();
XWPFParagraph p = doc.createParagraph();
p.createRun();
// add other run element
p.getCTP().addNewSdt();
// add two CTR in hyperlink
XWPFHyperlinkRun hyperlinkRun = p
.createHyperlinkRun("http://poi.apache.org");
hyperlinkRun.getCTHyperlink().addNewR();
p.createFieldRun();
XWPFDocument doc2 = XWPFTestDataSamples.writeOutAndReadBack(doc);
XWPFParagraph paragraph = doc2.getParagraphArray(0);
assertEquals(4, paragraph.getRuns().size());
assertEquals(5, paragraph.getIRuns().size());
assertTrue(paragraph.getRuns().get(1) instanceof XWPFHyperlinkRun);
assertTrue(paragraph.getRuns().get(2) instanceof XWPFHyperlinkRun);
assertTrue(paragraph.getRuns().get(3) instanceof XWPFFieldRun);
assertTrue(paragraph.getIRuns().get(1) instanceof XWPFSDT);
assertTrue(paragraph.getIRuns().get(2) instanceof XWPFHyperlinkRun);
paragraph.removeRun(1);
assertEquals(3, paragraph.getRuns().size());
assertTrue(paragraph.getRuns().get(1) instanceof XWPFHyperlinkRun);
assertTrue(paragraph.getRuns().get(2) instanceof XWPFFieldRun);
assertTrue(paragraph.getIRuns().get(1) instanceof XWPFSDT);
assertTrue(paragraph.getIRuns().get(2) instanceof XWPFHyperlinkRun);
paragraph.removeRun(1);
assertEquals(2, paragraph.getRuns().size());
assertTrue(paragraph.getRuns().get(1) instanceof XWPFFieldRun);
assertTrue(paragraph.getIRuns().get(1) instanceof XWPFSDT);
assertTrue(paragraph.getIRuns().get(2) instanceof XWPFFieldRun);
paragraph.removeRun(0);
assertEquals(1, paragraph.getRuns().size());
assertTrue(paragraph.getRuns().get(0) instanceof XWPFFieldRun);
assertTrue(paragraph.getIRuns().get(0) instanceof XWPFSDT);
assertTrue(paragraph.getIRuns().get(1) instanceof XWPFFieldRun);
XWPFRun newRun = paragraph.insertNewRun(0);
assertEquals(2, paragraph.getRuns().size());
assertEquals(3, paragraph.getIRuns().size());
assertEquals(0, paragraph.getRuns().indexOf(newRun));
doc.close();
doc2.close();
}
@Test @Test
public void testPictures() throws IOException { public void testPictures() throws IOException {
try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("VariousPictures.docx")) { try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("VariousPictures.docx")) {