import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.util.Internal;
}
/**
- * 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.
*
* @return the inserted run or null if the given pos is out of bounds.
*/
public XWPFRun insertNewRun(int pos) {
- if (pos >= 0 && pos <= runs.size()) {
- // calculate the correct pos as our run/irun list contains
- // hyperlinks
- // and fields so it 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++;
- }
- }
+ if (pos == runs.size()) {
+ return createRun();
+ }
+ return insertNewProvidedRun(pos, newCursor -> {
+ String uri = CTR.type.getName().getNamespaceURI();
+ String localPart = "r";
+ // creates a new run, cursor is positioned inside the new
+ // element
+ newCursor.beginElement(localPart, uri);
+ // 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);
+ });
- CTR ctRun = paragraph.insertNewR(rPos);
- XWPFRun newRun = new XWPFRun(ctRun, (IRunBody) 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);
+ });
+ }
- // To update the iruns, find where we're going
- // 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);
+ /**
+ * 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);
+
+ // To update the iruns, find where we're going
+ // in the normal runs, and go in there
+ int iPos = iruns.size();
+ int oldAt = iruns.indexOf(run);
if (oldAt != -1) {
iPos = oldAt;
}
+ iruns.add(iPos, newRun);
+ // Runs itself is easy to update
+ runs.add(pos, newRun);
+ return newRun;
}
- iruns.add(iPos, newRun);
-
- // Runs itself is easy to update
- runs.add(pos, newRun);
-
- return newRun;
+ newCursor.dispose();
}
-
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.
*/
public boolean removeRun(int pos) {
if (pos >= 0 && pos < runs.size()) {
- // Remove the run from our high level lists
XWPFRun run = runs.get(pos);
- if (run instanceof XWPFHyperlinkRun ||
- run instanceof XWPFFieldRun) {
- // TODO Add support for removing these kinds of nested runs,
- // which aren't on the CTP -> R array, but CTP -> XXX -> R array
- throw new IllegalArgumentException("Removing Field or Hyperlink runs not yet supported");
+ // CTP -> CTHyperlink -> R array
+ if (run instanceof XWPFHyperlinkRun
+ && isTheOnlyCTHyperlinkInRuns((XWPFHyperlinkRun) run)) {
+ XmlCursor c = ((XWPFHyperlinkRun) run).getCTHyperlink()
+ .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);
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 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
*
}
}
+ @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
public void testPictures() throws IOException {
try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("VariousPictures.docx")) {