]> source.dussan.org Git - poi.git/commitdiff
allow add and remove a HyperlinkRun or a FieldRun
authorAlain Béarez <abearez@apache.org>
Sun, 29 Mar 2020 22:21:35 +0000 (22:21 +0000)
committerAlain Béarez <abearez@apache.org>
Sun, 29 Mar 2020 22:21:35 +0000 (22:21 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1875862 13f79535-47bb-0310-9956-ffa450edef68

src/examples/src/org/apache/poi/xwpf/usermodel/examples/SimpleDocument.java
src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java
src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFParagraph.java

index 9de12c5aa453e5a25c24d0cd45fd7048a1bdc563..26990154459eff4b7d7270af5b2f5a5255af69f5 100644 (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.VerticalAlign;
 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.XWPFRun;
 
@@ -78,6 +79,11 @@ public class SimpleDocument {
             r3.setFontSize(20);
             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();
             p3.setWordWrapped(true);
index 9b1176be6a857d4216c031f8c504aa2021147c0d..cdb2f786c749bb2eb067d581fdd0bbdf7dd173a1 100644 (file)
@@ -20,6 +20,7 @@ import java.math.BigInteger;
 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;
@@ -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.
      *
      * @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.
@@ -1643,31 +1738,67 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para
      */
     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
      *
index eae2754527e5f0894c9d15d353e881c1f11fd9b3..3ce354b089cd168587d294c258af53a5139ff02c 100644 (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
     public void testPictures() throws IOException {
         try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("VariousPictures.docx")) {