]> source.dussan.org Git - poi.git/commitdiff
[github-114] Extend docx footnote support. Thanks to Eliot Kimber. This closes #114
authorPJ Fanning <fanningpj@apache.org>
Sat, 21 Jul 2018 21:30:52 +0000 (21:30 +0000)
committerPJ Fanning <fanningpj@apache.org>
Sat, 21 Jul 2018 21:30:52 +0000 (21:30 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1836415 13f79535-47bb-0310-9956-ffa450edef68

src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java
src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java
src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java
src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnotes.java

index 26f473088ed7e26bbab2f9ffabacebba4af0f0dd..a45ed219728c2e754158bc2381a219406b2f9276 100644 (file)
@@ -41,6 +41,8 @@ import org.apache.poi.ooxml.POIXMLDocumentPart;
 import org.apache.poi.ooxml.POIXMLException;
 import org.apache.poi.ooxml.POIXMLProperties;
 import org.apache.poi.ooxml.POIXMLRelation;
+import org.apache.poi.ooxml.util.IdentifierManager;
+import org.apache.poi.ooxml.util.PackageHelper;
 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
 import org.apache.poi.openxml4j.opc.OPCPackage;
@@ -84,6 +86,7 @@ import org.openxmlformats.schemas.wordprocessingml.x2006.main.STDocProtect;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.STHdrFtr;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.StylesDocument;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
 
 /**
  * <p>High(ish) level class for working with .docx files.</p>
@@ -1653,4 +1656,33 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
         charts.add(xwpfChart);
         return xwpfChart;
     }
+
+    /**
+     * Create a new footnote and add it to the document. 
+     * <p>The new note will have one paragraph with the style "FootnoteText"
+     * and one run containing the required footnote reference with the 
+     * style "FootnoteReference".
+     *
+     * @return New XWPFFootnote.
+     */
+    public XWPFFootnote createFootnote() {
+        XWPFFootnotes footnotes = this.createFootnotes();
+        
+        XWPFFootnote footnote = footnotes.createFootnote();
+        return footnote;
+    }
+
+    /**
+     * Remove the specified footnote if present.
+     *
+     * @param pos 
+     * @return True if the footnote was removed.
+     */
+    public boolean removeFootnote(int pos) {
+        if (null != footnotes) {
+            return footnotes.removeFootnote(pos);
+        } else {
+            return false;
+        }
+    }
 }
index a1be6a71a1302c4ac62b554624981b3ecc95ce2b..9444dbf6e96cfed1d873bdf85c40b92acee88824 100644 (file)
@@ -1,4 +1,3 @@
-/* ====================================================================
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -17,6 +16,7 @@
 ==================================================================== */
 package org.apache.poi.xwpf.usermodel;
 
+import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
@@ -25,12 +25,28 @@ import org.apache.poi.ooxml.POIXMLDocumentPart;
 import org.apache.xmlbeans.XmlCursor;
 import org.apache.xmlbeans.XmlObject;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdn;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdnRef;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSdtBlock;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
 
+/**
+ * Represents a bottom-of-the-page footnote.
+ * <p>Create a new footnote using {@link XWPFDocument#createFootnote()} or
+ * {@link XWPFFootnotes#createFootnote()}.</p>
+ * <p>The first body element of a footnote should (or possibly must) be a paragraph
+ * with the first run containing a CTFtnEdnRef object. The {@link XWPFFootnote#createParagraph()}
+ * and {@link XWPFFootnote#createTable()} methods do this for you.</p>
+ * <p>Footnotes have IDs that are unique across all footnotes in the document. You use
+ * the footnote ID to create a reference to a footnote from within a paragraph.</p>
+ * <p>To create a reference to a footnote within a paragraph you create a run
+ * with a CTFtnEdnRef that specifies the ID of the target paragraph. 
+ * The {@link XWPFParagraph#addFootnoteReference(XWPFFootnote)}
+ * method does this for you.</p>
+ */
 public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     private List<XWPFParagraph> paragraphs = new ArrayList<>();
     private List<XWPFTable> tables = new ArrayList<>();
@@ -78,37 +94,67 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
         cursor.dispose();
     }
 
+    /**
+     * Get the list of {@link XWPFParagraph}s in the footnote.
+     * @return List of paragraphs
+     */
     public List<XWPFParagraph> getParagraphs() {
         return paragraphs;
     }
 
+    /**
+     * Get an iterator over the {@link XWPFParagraph}s in the footnote.
+     * @return Iterator over the paragraph list.
+     */
     public Iterator<XWPFParagraph> iterator() {
         return paragraphs.iterator();
     }
 
+    /**
+     * Get the list of {@link XWPFTable}s in the footnote.
+     * @return List of tables
+     */
     public List<XWPFTable> getTables() {
         return tables;
     }
 
+    /**
+     * Gets the list of {@link XWPFPictureData}s in the footnote.
+     * @return List of pictures
+     */
     public List<XWPFPictureData> getPictures() {
         return pictures;
     }
 
+    /**
+     * Gets the body elements ({@link IBodyElement}) of the footnote.
+     * @return List of body elements.
+     */
     public List<IBodyElement> getBodyElements() {
         return bodyElements;
     }
 
+    /**
+     * Gets the underlying CTFtnEdn object for the footnote.
+     * @return CTFtnEdn object
+     */
     public CTFtnEdn getCTFtnEdn() {
         return ctFtnEdn;
     }
 
+    /**
+     * Set the underlying CTFtnEdn for the footnote.
+     * <p>Use {@link XWPFDocument#createFootnote()} to create new footnotes.</p> 
+     * @param footnote The CTFtnEdn object that will underly the footnote.
+     */
     public void setCTFtnEdn(CTFtnEdn footnote) {
         ctFtnEdn = footnote;
     }
 
     /**
+     * Gets the {@link XWPFTable} at the specified position from the footnote's table array.
      * @param pos in table array
-     * @return The table at position pos
+     * @return The {@link XWPFTable} at position pos, or null if there is no table at position pos.
      * @see org.apache.poi.xwpf.usermodel.IBody#getTableArray(int)
      */
     public XWPFTable getTableArray(int pos) {
@@ -119,16 +165,16 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * inserts an existing XWPFTable to the arrays bodyElements and tables
+     * Inserts an existing {@link XWPFTable) into the arrays bodyElements and tables.
      *
-     * @param pos
-     * @param table
+     * @param pos Position, in the bodyElements array, to insert the table
+     * @param table {@link XWPFTable) to be inserted
      * @see org.apache.poi.xwpf.usermodel.IBody#insertTable(int pos, XWPFTable table)
      */
     public void insertTable(int pos, XWPFTable table) {
         bodyElements.add(pos, table);
         int i = 0;
-        for (CTTbl tbl : ctFtnEdn.getTblArray()) {
+        for (CTTbl tbl : ctFtnEdn.getTblList()) {
             if (tbl == table.getCTTbl()) {
                 break;
             }
@@ -139,9 +185,10 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * if there is a corresponding {@link XWPFTable} of the parameter ctTable in the tableList of this header
-     * the method will return this table
-     * if there is no corresponding {@link XWPFTable} the method will return null
+     * if there is a corresponding {@link XWPFTable} of the parameter 
+     * ctTable in the tableList of this header
+     * the method will return this table, or null if there is no 
+     * corresponding {@link XWPFTable}.
      *
      * @param ctTable
      * @see org.apache.poi.xwpf.usermodel.IBody#getTable(CTTbl ctTable)
@@ -157,13 +204,12 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * if there is a corresponding {@link XWPFParagraph} of the parameter ctTable in the paragraphList of this header or footer
-     * the method will return this paragraph
-     * if there is no corresponding {@link XWPFParagraph} the method will return null
+     * if there is a corresponding {@link XWPFParagraph} of the parameter p in the paragraphList of this header or footer
+     * the method will return that paragraph, otherwise the method will return null.
      *
-     * @param p is instance of CTP and is searching for an XWPFParagraph
-     * @return null if there is no XWPFParagraph with an corresponding CTPparagraph in the paragraphList of this header or footer
-     * XWPFParagraph with the correspondig CTP p
+     * @param p The CTP paragraph to find the corresponding {@link XWPFParagraph} for.
+     * @return The {@link XWPFParagraph} that corresponds to the CTP paragraph in the paragraph
+     * list of this footnote or null if no paragraph is found.
      * @see org.apache.poi.xwpf.usermodel.IBody#getParagraph(CTP p)
      */
     public XWPFParagraph getParagraph(CTP p) {
@@ -175,8 +221,9 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * Returns the paragraph that holds
-     * the text of the header or footer.
+     * Returns the {@link XWPFParagraph} at position pos in footnote's paragraph array.
+     * @param pos Array position of the paragraph to get.
+     * @return the {@link XWPFParagraph} at position pos, or null if there is no paragraph at that position.
      *
      * @see org.apache.poi.xwpf.usermodel.IBody#getParagraphArray(int pos)
      */
@@ -188,9 +235,10 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * get the TableCell which belongs to the TableCell
+     * get the {@link XWPFTableCell} that belongs to the CTTc cell.
      *
      * @param cell
+     * @return {@link XWPFTableCell} that corresponds to the CTTc cell, if there is one, otherwise null.
      * @see org.apache.poi.xwpf.usermodel.IBody#getTableCell(CTTc cell)
      */
     public XWPFTableCell getTableCell(CTTc cell) {
@@ -220,9 +268,10 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * verifies that cursor is on the right position
+     * Verifies that cursor is on the right position.
      *
      * @param cursor
+     * @return true if the cursor is within a CTFtnEdn element.
      */
     private boolean isCursorInFtn(XmlCursor cursor) {
         XmlCursor verify = cursor.newCursor();
@@ -233,13 +282,19 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
         return false;
     }
 
+    /**
+     * The owning object for this footnote
+     *
+     * @return The {@link XWPFFootnotes} object that contains this footnote.
+     */
     public POIXMLDocumentPart getOwner() {
         return footnotes;
     }
 
     /**
+     * Insert a table constructed from OOXML table markup.
      * @param cursor
-     * @return the inserted table
+     * @return the inserted {@link XWPFTable}
      * @see org.apache.poi.xwpf.usermodel.IBody#insertNewTbl(XmlCursor cursor)
      */
     public XWPFTable insertNewTbl(XmlCursor cursor) {
@@ -279,10 +334,10 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * add a new paragraph at position of the cursor
+     * Add a new {@link XWPFParagraph} at position of the cursor.
      *
      * @param cursor
-     * @return the inserted paragraph
+     * @return The inserted {@link XWPFParagraph}
      * @see org.apache.poi.xwpf.usermodel.IBody#insertNewParagraph(XmlCursor cursor)
      */
     public XWPFParagraph insertNewParagraph(final XmlCursor cursor) {
@@ -323,10 +378,10 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * add a new table to the end of the footnote
+     * Add a new {@link XWPFTable} to the end of the footnote.
      *
-     * @param table
-     * @return the added XWPFTable
+     * @param table CTTbl object from which to construct the {@link XWPFTable}
+     * @return The added {@link XWPFTable}
      */
     public XWPFTable addNewTbl(CTTbl table) {
         CTTbl newTable = ctFtnEdn.addNewTbl();
@@ -337,10 +392,10 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * add a new paragraph to the end of the footnote
+     * Add a new {@link XWPFParagraph} to the end of the footnote.
      *
-     * @param paragraph
-     * @return the added XWPFParagraph
+     * @param paragraph CTP paragraph from which to construct the {@link XWPFParagraph}
+     * @return The added {@link XWPFParagraph}
      */
     public XWPFParagraph addNewParagraph(CTP paragraph) {
         CTP newPara = ctFtnEdn.addNewP();
@@ -351,6 +406,7 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
+     * Get the {@link XWPFDocument} the footnote is part of.
      * @see org.apache.poi.xwpf.usermodel.IBody#getXWPFDocument()
      */
     public XWPFDocument getXWPFDocument() {
@@ -358,7 +414,8 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * returns the Part, to which the body belongs, which you need for adding relationship to other parts
+     * Get the Part to which the footnote belongs, which you need for adding relationships to other parts
+     * @return {@link POIXMLDocumentPart} that contains the footnote.
      *
      * @see org.apache.poi.xwpf.usermodel.IBody#getPart()
      */
@@ -367,11 +424,106 @@ public class XWPFFootnote implements Iterable<XWPFParagraph>, IBody {
     }
 
     /**
-     * get the PartType of the body
+     * Get the part type  {@link BodyType} of the footnote.
+     * @return The {@link BodyType} value.
      *
      * @see org.apache.poi.xwpf.usermodel.IBody#getPartType()
      */
     public BodyType getPartType() {
         return BodyType.FOOTNOTE;
     }
+
+    /**
+     * Get the ID of the footnote.
+     * <p>Footnote IDs are unique across all bottom-of-the-page and
+     * end note footnotes.</p>
+     *
+     * @return Footnote ID
+     */
+    public BigInteger getId() {
+        return this.ctFtnEdn.getId();
+    }
+
+    /**
+     * Appends a new {@link XWPFParagraph} to this footnote.
+     *
+     * @return The new {@link XWPFParagraph}
+     */
+    public XWPFParagraph createParagraph() {
+        XWPFParagraph p = new XWPFParagraph(this.ctFtnEdn.addNewP(), this);
+        paragraphs.add(p);
+        bodyElements.add(p);
+
+        // If the paragraph is the first paragraph in the footnote, 
+        // ensure that it has a footnote reference run.
+        
+        if (p.equals(getParagraphs().get(0))) {
+            ensureFootnoteRef(p);
+        }
+        return p;
+    }
+
+    /**
+     * Ensure that the specified paragraph has a reference marker for this
+     * footnote by adding a footnote reference if one is not found.
+     * <p>This method is for the first paragraph in the footnote, not 
+     * paragraphs that will refer to the footnote. For references to
+     * the footnote, use {@link XWPFParagraph#addFootnoteReference(XWPFFootnote)}.
+     * </p>
+     * <p>The first run of the first paragraph in a footnote should
+     * contain a {@link CTFtnEdnRef} object.</p>
+     *
+     * @param p The {@link XWPFParagraph} to ensure
+     */
+    public void ensureFootnoteRef(XWPFParagraph p) {
+        
+        XWPFRun r = null;
+        if (p.getRuns().size() > 0) {
+            r = p.getRuns().get(0);
+        }
+        if (r == null) {
+            r = p.createRun();
+        }
+        CTR ctr = r.getCTR();
+        boolean foundRef = false;
+        for (CTFtnEdnRef ref : ctr.getFootnoteReferenceList()) {
+            if (getId().equals(ref.getId())) {
+                foundRef = true;
+                break;
+            }
+        }
+        if (!foundRef) {
+            ctr.addNewRPr().addNewRStyle().setVal("FootnoteReference");
+            ctr.addNewFootnoteRef();
+        }
+    }
+
+    /**
+     * Appends a new {@link XWPFTable} to this footnote
+     *
+     * @return The new {@link XWPFTable}
+     */
+    public XWPFTable createTable() {
+        XWPFTable table = new XWPFTable(ctFtnEdn.addNewTbl(), this);
+        if (bodyElements.size() == 0) {
+            XWPFParagraph p = createParagraph();
+            ensureFootnoteRef(p);
+        }
+        bodyElements.add(table);
+        tables.add(table);
+        return table;
+    }
+
+    /**
+     * Appends a new {@link XWPFTable} to this footnote
+     * @param rows Number of rows to initialize the table with
+     * @param cols Number of columns to initialize the table with 
+     * @return the new {@link XWPFTable} with the specified number of rows and columns
+     */
+    public XWPFTable createTable(int rows, int cols) {
+        XWPFTable table = new XWPFTable(ctFtnEdn.addNewTbl(), this, rows, cols);
+        bodyElements.add(table);
+        tables.add(table);
+        return table;
+    }
 }
index 7d7077b097d6fd1e700583798b4b9bc2bfb31314..a4788673eda898294e487f5e30eb7f4b0f5da31e 100644 (file)
@@ -22,6 +22,7 @@ import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -35,10 +36,14 @@ import org.apache.xmlbeans.XmlException;
 import org.apache.xmlbeans.XmlOptions;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFootnotes;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdn;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.FootnotesDocument;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFtnEdn;
 
 /**
- * Looks after the collection of Footnotes for a document
+ * Looks after the collection of Footnotes for a document.
+ * Manages both bottom-of-the-page footnotes and end notes.
  */
 public class XWPFFootnotes extends POIXMLDocumentPart {
     protected XWPFDocument document;
@@ -157,4 +162,39 @@ public class XWPFFootnotes extends POIXMLDocumentPart {
     public void setXWPFDocument(XWPFDocument doc) {
         document = doc;
     }
+
+    /**
+     * Create a new footnote and add it to the document. 
+     * <p>The new note will have one paragraph with the style "FootnoteText"
+     * and one run containing the required footnote reference with the 
+     * style "FootnoteReference".
+     * </p>
+     * @return New XWPFFootnote
+     */
+    public XWPFFootnote createFootnote() {
+        CTFtnEdn newNote = CTFtnEdn.Factory.newInstance(); 
+        newNote.setType(STFtnEdn.NORMAL);
+
+        XWPFFootnote footnote = addFootnote(newNote);
+        int id = ctFootnotes.sizeOfFootnoteArray();
+        footnote.getCTFtnEdn().setId(BigInteger.valueOf(id));
+        return footnote;
+        
+    }
+
+    /**
+     * Remove the specified footnote if present.
+     *
+     * @param pos 
+     * @return True if the footnote was removed.
+     */
+    public boolean removeFootnote(int pos) {
+        if (ctFootnotes.sizeOfFootnoteArray() >= pos - 1) {
+            ctFootnotes.removeFootnote(pos);
+            listFootnote.remove(pos);
+            return true;
+        } else {
+            return false;
+        }
+    }
 }
index 1c033f61b4f6f2c2b1c21f814c146b4bb918e069..661e1c1abeb793316e49c7d3a241932afe2fac47 100644 (file)
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.List;
 
 import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.ss.formula.eval.NotImplementedException;
 import org.apache.poi.util.Internal;
 import org.apache.poi.wp.usermodel.Paragraph;
 import org.apache.xmlbeans.XmlCursor;
@@ -1667,4 +1668,17 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para
         }
         return null;
     }
+
+    /**
+     * Add a new run with a reference to the specified footnote.
+     * The footnote reference run will have the style name "FootnoteReference".
+     *
+     * @param footnote Footnote to which to add a reference.
+     */
+    public void addFootnoteReference(XWPFFootnote footnote) {
+        XWPFRun run = createRun();
+        CTR ctRun = run.getCTR();
+        ctRun.addNewRPr().addNewRStyle().setVal("FootnoteReference");
+        ctRun.addNewFootnoteReference().setId(footnote.getId());      
+    }
 }
diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java
new file mode 100644 (file)
index 0000000..8bb377f
--- /dev/null
@@ -0,0 +1,158 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You 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 org.apache.poi.xwpf.usermodel;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.List;
+
+import org.apache.poi.xwpf.XWPFTestDataSamples;
+import org.junit.Before;
+import org.junit.Test;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdnRef;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
+
+public class TestXWPFFootnote {
+    
+    private XWPFDocument docOut;
+    private String p1Text;
+    private String p2Text;
+    private BigInteger footnoteId;
+    private XWPFFootnote footnote;
+
+    @Before
+    public void setUp() {
+        docOut = new XWPFDocument();
+        p1Text = "First paragraph in footnote";
+        p2Text = "Second paragraph in footnote";
+
+        // NOTE: XWPFDocument.createFootnote() delegates directly
+        //       to XWPFFootnotes.createFootnote() so this tests
+        //       both creation of new XWPFFootnotes in document
+        //       and XWPFFootnotes.createFootnote();
+        
+        // NOTE: Creating the footnote does not automatically
+        //       create a first paragraph.
+        footnote = docOut.createFootnote();
+        footnoteId = footnote.getId();
+        
+    }
+
+    @Test
+    public void testAddParagraphsToFootnote() throws IOException {
+
+        // Add a run to the first paragraph:    
+        
+        XWPFParagraph p1 = footnote.createParagraph();
+        p1.createRun().setText(p1Text);
+        
+        // Create a second paragraph:
+        
+        XWPFParagraph p = footnote.createParagraph();
+        assertNotNull("Paragraph is null", p);
+        p.createRun().setText(p2Text);
+
+        XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut);
+        
+        XWPFFootnote testFootnote = docIn.getFootnoteByID(footnoteId.intValue());
+        assertNotNull(testFootnote);
+        
+        assertEquals(2, testFootnote.getParagraphs().size());
+        XWPFParagraph testP1 = testFootnote.getParagraphs().get(0);
+        assertEquals(p1Text, testP1.getText());
+
+        XWPFParagraph testP2 = testFootnote.getParagraphs().get(1);
+        assertEquals(p2Text, testP2.getText());        
+        
+        // The first paragraph added using createParagraph() should
+        // have the required footnote reference added to the first
+        // run.
+        
+        // Verify that we have a footnote reference in the first paragraph and not
+        // in the second paragraph.
+        
+        XWPFRun r1 = testP1.getRuns().get(0);
+        assertNotNull(r1);
+        assertTrue("No footnote reference in testP1", r1.getCTR().getFootnoteRefList().size() > 0);
+        assertNotNull("No footnote reference in testP1", r1.getCTR().getFootnoteRefArray(0));
+
+        XWPFRun r2 = testP2.getRuns().get(0);
+        assertNotNull("Expected a run in testP2", r2);
+        assertTrue("Found a footnote reference in testP2", r2.getCTR().getFootnoteRefList().size() == 0);
+        
+    }
+    
+    @Test
+    public void testAddTableToFootnote() throws IOException {
+        XWPFTable table = footnote.createTable();
+        assertNotNull(table);
+        
+        XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut);
+        
+        XWPFFootnote testFootnote = docIn.getFootnoteByID(footnoteId.intValue());
+        XWPFTable testTable = testFootnote.getTableArray(0);
+        assertNotNull(testTable);
+        
+        table = footnote.createTable(2, 3);
+        assertEquals(2, table.getNumberOfRows());
+        assertEquals(3, table.getRow(0).getTableCells().size());
+        
+        // If the table is the first body element of the footnote then
+        // a paragraph with the footnote reference should have been
+        // added automatically.
+        
+        assertEquals("Expected 3 body elements", 3, footnote.getBodyElements().size());
+        IBodyElement testP1 = footnote.getBodyElements().get(0);
+        assertTrue("Expected a paragraph, got " + testP1.getClass().getSimpleName() , testP1 instanceof XWPFParagraph);
+        XWPFRun r1 = ((XWPFParagraph)testP1).getRuns().get(0);
+        assertNotNull(r1);
+        assertTrue("No footnote reference in testP1", r1.getCTR().getFootnoteRefList().size() > 0);
+        assertNotNull("No footnote reference in testP1", r1.getCTR().getFootnoteRefArray(0));
+
+    }
+    
+    @Test
+    public void testRemoveFootnote() {
+        // NOTE: XWPFDocument.removeFootnote() delegates directly to 
+        //       XWPFFootnotes.
+        docOut.createFootnote();
+        assertEquals("Expected 2 footnotes", 2, docOut.getFootnotes().size());
+        assertNotNull("Didn't get second footnote", docOut.getFootnotes().get(1));
+        boolean result = docOut.removeFootnote(0);
+        assertTrue("Remove footnote did not return true", result);
+        assertEquals("Expected 1 footnote after removal", 1, docOut.getFootnotes().size());
+    }
+
+    @Test
+    public void testAddFootnoteRefToParagraph() {
+        XWPFParagraph p = docOut.createParagraph();
+        List<XWPFRun> runs = p.getRuns();
+        assertEquals("Expected no runs in new paragraph", 0, runs.size());
+        p.addFootnoteReference(footnote);
+        XWPFRun run = p.getRuns().get(0);
+        CTR ctr = run.getCTR();
+        assertNotNull("Expected a run", run);
+        CTFtnEdnRef ref = ctr.getFootnoteReferenceList().get(0);
+        assertNotNull(ref);
+        assertEquals("Footnote ID and reference ID did not match", footnote.getId(), ref.getId());
+        
+        
+    }
+
+}
index 08e439310d85bd43f78200566699151cfd3538a2..037bdad04f8139865b40d8d267ad0941ac1d957b 100644 (file)
@@ -21,28 +21,42 @@ import java.io.IOException;
 import java.math.BigInteger;
 import java.util.List;
 
-import junit.framework.TestCase;
 import org.apache.poi.xwpf.XWPFTestDataSamples;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdn;
 import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFtnEdn;
 
+import junit.framework.TestCase;
+
 public class TestXWPFFootnotes extends TestCase {
+    
+    public void testCreateFootnotes() throws IOException{
+        XWPFDocument docOut = new XWPFDocument();
+
+        XWPFFootnotes footnotes = docOut.createFootnotes();
+        
+        assertNotNull(footnotes);
+        
+        XWPFFootnotes secondFootnotes = docOut.createFootnotes();
+        
+        assertSame(footnotes, secondFootnotes);
+        
+        docOut.close();
+    }
 
     public void testAddFootnotesToDocument() throws IOException {
         XWPFDocument docOut = new XWPFDocument();
 
-        BigInteger noteId = BigInteger.valueOf(1);
-
-        XWPFFootnotes footnotes = docOut.createFootnotes();
-        CTFtnEdn ctNote = CTFtnEdn.Factory.newInstance();
-        ctNote.setId(noteId);
-        ctNote.setType(STFtnEdn.NORMAL);
-        footnotes.addFootnote(ctNote);
+        // NOTE: XWPFDocument.createFootnote() delegates directly
+        //       to XWPFFootnotes.createFootnote() so this tests
+        //       both creation of new XWPFFootnotes in document
+        //       and XWPFFootnotes.createFootnote();
+        XWPFFootnote footnote = docOut.createFootnote();
+        BigInteger noteId = footnote.getId();
 
         XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut);
 
         XWPFFootnote note = docIn.getFootnoteByID(noteId.intValue());
-        assertEquals(note.getCTFtnEdn().getType(), STFtnEdn.NORMAL);
+        assertNotNull(note);
+        assertEquals(STFtnEdn.NORMAL, note.getCTFtnEdn().getType());
     }
 
     /**