From c1fc95c5179333b6ae6a5be3ee213a7ea2ee8e14 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 21 Jul 2018 21:30:52 +0000 Subject: [PATCH] [github-114] Extend docx footnote support. Thanks to Eliot Kimber. This closes #114 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1836415 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/xwpf/usermodel/XWPFDocument.java | 32 +++ .../poi/xwpf/usermodel/XWPFFootnote.java | 212 +++++++++++++++--- .../poi/xwpf/usermodel/XWPFFootnotes.java | 42 +++- .../poi/xwpf/usermodel/XWPFParagraph.java | 14 ++ .../poi/xwpf/usermodel/TestXWPFFootnote.java | 158 +++++++++++++ .../poi/xwpf/usermodel/TestXWPFFootnotes.java | 34 ++- 6 files changed, 451 insertions(+), 41 deletions(-) create mode 100644 src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java index 26f473088..a45ed2197 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java @@ -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.*; /** *

High(ish) level class for working with .docx files.

@@ -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. + *

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; + } + } } diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java index a1be6a71a..9444dbf6e 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java @@ -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. + *

Create a new footnote using {@link XWPFDocument#createFootnote()} or + * {@link XWPFFootnotes#createFootnote()}.

+ *

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.

+ *

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.

+ *

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.

+ */ public class XWPFFootnote implements Iterable, IBody { private List paragraphs = new ArrayList<>(); private List tables = new ArrayList<>(); @@ -78,37 +94,67 @@ public class XWPFFootnote implements Iterable, IBody { cursor.dispose(); } + /** + * Get the list of {@link XWPFParagraph}s in the footnote. + * @return List of paragraphs + */ public List getParagraphs() { return paragraphs; } + /** + * Get an iterator over the {@link XWPFParagraph}s in the footnote. + * @return Iterator over the paragraph list. + */ public Iterator iterator() { return paragraphs.iterator(); } + /** + * Get the list of {@link XWPFTable}s in the footnote. + * @return List of tables + */ public List getTables() { return tables; } + /** + * Gets the list of {@link XWPFPictureData}s in the footnote. + * @return List of pictures + */ public List getPictures() { return pictures; } + /** + * Gets the body elements ({@link IBodyElement}) of the footnote. + * @return List of body elements. + */ public List 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. + *

Use {@link XWPFDocument#createFootnote()} to create new footnotes.

+ * @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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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. + *

Footnote IDs are unique across all bottom-of-the-page and + * end note footnotes.

+ * + * @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. + *

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)}. + *

+ *

The first run of the first paragraph in a footnote should + * contain a {@link CTFtnEdnRef} object.

+ * + * @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; + } } diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java index 7d7077b09..a4788673e 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java @@ -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. + *

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() { + 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; + } + } } diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java index 1c033f61b..661e1c1ab 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java @@ -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 index 000000000..8bb377fe5 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java @@ -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 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()); + + + } + +} diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnotes.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnotes.java index 08e439310..037bdad04 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnotes.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnotes.java @@ -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()); } /**