From: PJ Fanning Date: Tue, 24 Jul 2018 09:46:44 +0000 (+0000) Subject: [github-115] implement endnote. This closes #115 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e3ae88792bc4c413f36b252374cb2fbcef9953ff;p=poi.git [github-115] implement endnote. This closes #115 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1836538 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/AbstractXWPFFootnoteEndnote.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/AbstractXWPFFootnoteEndnote.java new file mode 100644 index 0000000000..7c30725ee9 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/AbstractXWPFFootnoteEndnote.java @@ -0,0 +1,499 @@ +package org.apache.poi.xwpf.usermodel; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.util.Internal; +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; + +/** + * Base class for both bottom-of-the-page footnotes {@link XWPFFootnote} and end + * notes {@link XWPFEndnote}). + *

The only significant difference between footnotes and + * end notes is which part they go on. Footnotes are managed by the Footnotes part + * {@link XWPFFootnotes} and end notes are managed by the Endnotes part {@link XWPFEndnotes}.

+ * @since 4.0.0 + */ +public abstract class AbstractXWPFFootnoteEndnote implements Iterable, IBody { + + private List paragraphs = new ArrayList<>(); + private List tables = new ArrayList<>(); + private List pictures = new ArrayList<>(); + private List bodyElements = new ArrayList<>(); + protected CTFtnEdn ctFtnEdn; + protected AbstractXWPFFootnotesEndnotes footnotes; + protected XWPFDocument document; + + public AbstractXWPFFootnoteEndnote() { + super(); + } + + @Internal + protected AbstractXWPFFootnoteEndnote(XWPFDocument document, CTFtnEdn body) { + ctFtnEdn = body; + this.document = document; + init(); + } + + @Internal + protected AbstractXWPFFootnoteEndnote(CTFtnEdn note, AbstractXWPFFootnotesEndnotes footnotes) { + this.footnotes = footnotes; + ctFtnEdn = note; + document = footnotes.getXWPFDocument(); + init(); + } + + protected void init() { + XmlCursor cursor = ctFtnEdn.newCursor(); + //copied from XWPFDocument...should centralize this code + //to avoid duplication + cursor.selectPath("./*"); + while (cursor.toNextSelection()) { + XmlObject o = cursor.getObject(); + if (o instanceof CTP) { + XWPFParagraph p = new XWPFParagraph((CTP) o, this); + bodyElements.add(p); + paragraphs.add(p); + } else if (o instanceof CTTbl) { + XWPFTable t = new XWPFTable((CTTbl) o, this); + bodyElements.add(t); + tables.add(t); + } else if (o instanceof CTSdtBlock) { + XWPFSDT c = new XWPFSDT((CTSdtBlock) o, this); + bodyElements.add(c); + } + + } + 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 {@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) { + if (pos >= 0 && pos < tables.size()) { + return tables.get(pos); + } + return null; + } + + /** + * Inserts an existing {@link XWPFTable) into the arrays bodyElements and tables. + * + * @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.getTblList()) { + if (tbl == table.getCTTbl()) { + break; + } + i++; + } + tables.add(i, table); + + } + + /** + * 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) + */ + public XWPFTable getTable(CTTbl ctTable) { + for (XWPFTable table : tables) { + if (table == null) + return null; + if (table.getCTTbl().equals(ctTable)) + return table; + } + 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 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) { + for (XWPFParagraph paragraph : paragraphs) { + if (paragraph.getCTP().equals(p)) + return paragraph; + } + return null; + } + + /** + * 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) + */ + public XWPFParagraph getParagraphArray(int pos) { + if(pos >=0 && pos < paragraphs.size()) { + return paragraphs.get(pos); + } + return null; + } + + /** + * 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) { + XmlCursor cursor = cell.newCursor(); + cursor.toParent(); + XmlObject o = cursor.getObject(); + if (!(o instanceof CTRow)) { + return null; + } + CTRow row = (CTRow) o; + cursor.toParent(); + o = cursor.getObject(); + cursor.dispose(); + if (!(o instanceof CTTbl)) { + return null; + } + CTTbl tbl = (CTTbl) o; + XWPFTable table = getTable(tbl); + if (table == null) { + return null; + } + XWPFTableRow tableRow = table.getRow(row); + if(tableRow == null){ + return null; + } + return tableRow.getTableCell(cell); + } + + /** + * 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(); + verify.toParent(); + if (verify.getObject() == this.ctFtnEdn) { + return true; + } + 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 {@link XWPFTable} + * @see org.apache.poi.xwpf.usermodel.IBody#insertNewTbl(XmlCursor cursor) + */ + public XWPFTable insertNewTbl(XmlCursor cursor) { + if (isCursorInFtn(cursor)) { + String uri = CTTbl.type.getName().getNamespaceURI(); + String localPart = "tbl"; + cursor.beginElement(localPart, uri); + cursor.toParent(); + CTTbl t = (CTTbl) cursor.getObject(); + XWPFTable newT = new XWPFTable(t, this); + cursor.removeXmlContents(); + XmlObject o = null; + while (!(o instanceof CTTbl) && (cursor.toPrevSibling())) { + o = cursor.getObject(); + } + if (!(o instanceof CTTbl)) { + tables.add(0, newT); + } else { + int pos = tables.indexOf(getTable((CTTbl) o)) + 1; + tables.add(pos, newT); + } + int i = 0; + cursor = t.newCursor(); + while (cursor.toPrevSibling()) { + o = cursor.getObject(); + if (o instanceof CTP || o instanceof CTTbl) + i++; + } + bodyElements.add(i, newT); + XmlCursor c2 = t.newCursor(); + cursor.toCursor(c2); + cursor.toEndToken(); + c2.dispose(); + return newT; + } + return null; + } + + /** + * Add a new {@link XWPFParagraph} at position of the cursor. + * + * @param cursor + * @return The inserted {@link XWPFParagraph} + * @see org.apache.poi.xwpf.usermodel.IBody#insertNewParagraph(XmlCursor cursor) + */ + public XWPFParagraph insertNewParagraph(final XmlCursor cursor) { + if (isCursorInFtn(cursor)) { + String uri = CTP.type.getName().getNamespaceURI(); + String localPart = "p"; + cursor.beginElement(localPart, uri); + cursor.toParent(); + CTP p = (CTP) cursor.getObject(); + XWPFParagraph newP = new XWPFParagraph(p, this); + XmlObject o = null; + while (!(o instanceof CTP) && (cursor.toPrevSibling())) { + o = cursor.getObject(); + } + if ((!(o instanceof CTP)) || o == p) { + paragraphs.add(0, newP); + } else { + int pos = paragraphs.indexOf(getParagraph((CTP) o)) + 1; + paragraphs.add(pos, newP); + } + int i = 0; + XmlCursor p2 = p.newCursor(); + cursor.toCursor(p2); + p2.dispose(); + while (cursor.toPrevSibling()) { + o = cursor.getObject(); + if (o instanceof CTP || o instanceof CTTbl) + i++; + } + bodyElements.add(i, newP); + p2 = p.newCursor(); + cursor.toCursor(p2); + cursor.toEndToken(); + p2.dispose(); + return newP; + } + return null; + } + + /** + * Add a new {@link XWPFTable} to the end of the footnote. + * + * @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(); + newTable.set(table); + XWPFTable xTable = new XWPFTable(newTable, this); + tables.add(xTable); + return xTable; + } + + /** + * Add a new {@link XWPFParagraph} to the end of the footnote. + * + * @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(); + newPara.set(paragraph); + XWPFParagraph xPara = new XWPFParagraph(newPara, this); + paragraphs.add(xPara); + return xPara; + } + + /** + * Get the {@link XWPFDocument} the footnote is part of. + * @see org.apache.poi.xwpf.usermodel.IBody#getXWPFDocument() + */ + public XWPFDocument getXWPFDocument() { + return document; + } + + /** + * 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() + */ + public POIXMLDocumentPart getPart() { + return footnotes; + } + + /** + * 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 + * @since 4.0.0 + */ + public BigInteger getId() { + return this.ctFtnEdn.getId(); + } + + /** + * Appends a new {@link XWPFParagraph} to this footnote. + * + * @return The new {@link XWPFParagraph} + * @since 4.0.0 + */ + 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 + * @since 4.0.0 + */ + public abstract void ensureFootnoteRef(XWPFParagraph p); + + /** + * Appends a new {@link XWPFTable} to this footnote + * + * @return The new {@link XWPFTable} + * @since 4.0.0 + */ + 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 + * @since 4.0.0 + */ + public XWPFTable createTable(int rows, int cols) { + XWPFTable table = new XWPFTable(ctFtnEdn.addNewTbl(), this, rows, cols); + bodyElements.add(table); + tables.add(table); + return table; + } + +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/AbstractXWPFFootnotesEndnotes.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/AbstractXWPFFootnotesEndnotes.java new file mode 100644 index 0000000000..a3560c5318 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/AbstractXWPFFootnotesEndnotes.java @@ -0,0 +1,74 @@ +package org.apache.poi.xwpf.usermodel; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackagePart; + +/** + * Base class for the Footnotes and Endnotes part implementations. + * @since 4.0.0 + */ +public abstract class AbstractXWPFFootnotesEndnotes extends POIXMLDocumentPart { + + protected XWPFDocument document; + protected List listFootnote = new ArrayList<>(); + private FootnoteEndnoteIdManager idManager; + + public AbstractXWPFFootnotesEndnotes(OPCPackage pkg) { + super(pkg); + } + + public AbstractXWPFFootnotesEndnotes(OPCPackage pkg, + String coreDocumentRel) { + super(pkg, coreDocumentRel); + } + + public AbstractXWPFFootnotesEndnotes() { + super(); + } + + public AbstractXWPFFootnotesEndnotes(PackagePart part) { + super(part); + } + + public AbstractXWPFFootnotesEndnotes(POIXMLDocumentPart parent, PackagePart part) { + super(parent, part); + } + + + public AbstractXWPFFootnoteEndnote getFootnoteById(int id) { + for (AbstractXWPFFootnoteEndnote note : listFootnote) { + if (note.getCTFtnEdn().getId().intValue() == id) + return note; + } + return null; + } + + /** + * @see org.apache.poi.xwpf.usermodel.IBody#getPart() + */ + public XWPFDocument getXWPFDocument() { + if (document != null) { + return document; + } else { + return (XWPFDocument) getParent(); + } + } + + public void setXWPFDocument(XWPFDocument doc) { + document = doc; + } + + public void setIdManager(FootnoteEndnoteIdManager footnoteIdManager) { + this.idManager = footnoteIdManager; + + } + + public FootnoteEndnoteIdManager getIdManager() { + return this.idManager; + } + +} \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/FootnoteEndnoteIdManager.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/FootnoteEndnoteIdManager.java new file mode 100644 index 0000000000..5104b487fa --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/FootnoteEndnoteIdManager.java @@ -0,0 +1,45 @@ +package org.apache.poi.xwpf.usermodel; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * Manages IDs for footnotes and endnotes. + *

Footnotes and endnotes are managed in separate parts but + * represent a single namespace of IDs.

+ */ +public class FootnoteEndnoteIdManager { + + private XWPFDocument document; + + public FootnoteEndnoteIdManager(XWPFDocument document) { + this.document = document; + } + + /** + * Gets the next ID number. + * + * @return ID number to use. + */ + public BigInteger nextId() { + + List ids = new ArrayList(); + for (AbstractXWPFFootnoteEndnote note : document.getFootnotes()) { + ids.add(note.getId()); + } + for (AbstractXWPFFootnoteEndnote note : document.getEndnotes()) { + ids.add(note.getId()); + } + int cand = ids.size(); + BigInteger newId = BigInteger.valueOf(cand); + while (ids.contains(newId)) { + cand++; + newId = BigInteger.valueOf(cand); + } + + return newId; + } + + +} 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 a45ed21972..fa252662b8 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java @@ -54,11 +54,9 @@ import org.apache.poi.openxml4j.opc.PackagingURIHelper; import org.apache.poi.openxml4j.opc.TargetMode; import org.apache.poi.poifs.crypt.HashAlgorithm; import org.apache.poi.util.IOUtils; -import org.apache.poi.ooxml.util.IdentifierManager; import org.apache.poi.util.Internal; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; -import org.apache.poi.ooxml.util.PackageHelper; import org.apache.poi.wp.usermodel.HeaderFooterType; import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy; import org.apache.xmlbeans.XmlCursor; @@ -100,6 +98,7 @@ import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; * http://www.ecma-international.org/publications/standards/Ecma-376.htm * at some point in your use.

*/ +@SuppressWarnings("unused") public class XWPFDocument extends POIXMLDocument implements Document, IBody { private static final POILogger LOG = POILogFactory.getLogger(XWPFDocument.class); @@ -113,7 +112,7 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { protected List bodyElements = new ArrayList<>(); protected List pictures = new ArrayList<>(); protected Map> packagePictures = new HashMap<>(); - protected Map endnotes = new HashMap<>(); + protected XWPFEndnotes endnotes; protected XWPFNumbering numbering; protected XWPFStyles styles; protected XWPFFootnotes footnotes; @@ -124,6 +123,9 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { * Keeps track on all id-values used in this document and included parts, like headers, footers, etc. */ private IdentifierManager drawingIdManager = new IdentifierManager(0L, 4294967295L); + + private FootnoteEndnoteIdManager footnoteIdManager = new FootnoteEndnoteIdManager(this); + /** * Handles the joy of different headers/footers for different pages */ @@ -286,12 +288,11 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { if (relation.equals(XWPFRelation.FOOTNOTE.getRelation())) { this.footnotes = (XWPFFootnotes) p; this.footnotes.onDocumentRead(); + this.footnotes.setIdManager(footnoteIdManager); } else if (relation.equals(XWPFRelation.ENDNOTE.getRelation())) { - EndnotesDocument endnotesDocument = EndnotesDocument.Factory.parse(p.getPackagePart().getInputStream(), DEFAULT_XML_OPTIONS); - - for (CTFtnEdn ctFtnEdn : endnotesDocument.getEndnotes().getEndnoteArray()) { - endnotes.put(ctFtnEdn.getId().intValue(), new XWPFFootnote(this, ctFtnEdn)); - } + this.endnotes = (XWPFEndnotes) p; + this.endnotes.onDocumentRead(); + this.endnotes.setIdManager(footnoteIdManager); } } } @@ -416,14 +417,14 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { if (footnotes == null) { return null; } - return footnotes.getFootnoteById(id); + return (XWPFFootnote)footnotes.getFootnoteById(id); } - public XWPFFootnote getEndnoteByID(int id) { + public XWPFEndnote getEndnoteByID(int id) { if (endnotes == null) { return null; } - return endnotes.get(id); + return endnotes.getFootnoteById(id); } public List getFootnotes() { @@ -898,19 +899,34 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { XWPFFootnotes wrapper = (XWPFFootnotes) createRelationship(relation, XWPFFactory.getInstance(), i); wrapper.setFootnotes(footnotesDoc.addNewFootnotes()); + wrapper.setIdManager(this.footnoteIdManager); footnotes = wrapper; } return footnotes; } + /** + * Add a CTFtnEdn footnote to the document. + * + * @param note CTFtnEnd to be added. + * @return New {@link XWPFFootnote} + */ + @Internal public XWPFFootnote addFootnote(CTFtnEdn note) { return footnotes.addFootnote(note); } - public XWPFFootnote addEndnote(CTFtnEdn note) { - XWPFFootnote endnote = new XWPFFootnote(this, note); - endnotes.put(note.getId().intValue(), endnote); + /** + * Add a CTFtnEdn endnote to the document. + * + * @param note CTFtnEnd to be added. + * @return New {@link XWPFEndnote} + */ + @Internal + public XWPFEndnote addEndnote(CTFtnEdn note) { + XWPFEndnote endnote = new XWPFEndnote(this, note); + endnotes.addEndnote(note); return endnote; } @@ -1658,12 +1674,10 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { } /** - * 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". + * Create a new footnote and add it to the document. * * @return New XWPFFootnote. + * @since 4.0.0 */ public XWPFFootnote createFootnote() { XWPFFootnotes footnotes = this.createFootnotes(); @@ -1675,8 +1689,9 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { /** * Remove the specified footnote if present. * - * @param pos + * @param pos Array position of the footnote to be removed. * @return True if the footnote was removed. + * @since 4.0.0 */ public boolean removeFootnote(int pos) { if (null != footnotes) { @@ -1685,4 +1700,62 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { return false; } } + + /** + * Create a new end note and add it to the document. + * + * @return New {@link XWPFEndnote}. + * @since 4.0.0 + */ + public XWPFEndnote createEndnote() { + XWPFEndnotes endnotes = this.createEndnotes(); + + XWPFEndnote endnote = endnotes.createEndnote(); + return endnote; + + } + + public XWPFEndnotes createEndnotes() { + if (endnotes == null) { + EndnotesDocument endnotesDoc = EndnotesDocument.Factory.newInstance(); + + XWPFRelation relation = XWPFRelation.ENDNOTE; + int i = getRelationIndex(relation); + + XWPFEndnotes wrapper = (XWPFEndnotes) createRelationship(relation, XWPFFactory.getInstance(), i); + wrapper.setEndnotes(endnotesDoc.addNewEndnotes()); + wrapper.setIdManager(footnoteIdManager); + endnotes = wrapper; + } + + return endnotes; + + } + + /** + * Gets the list of end notes for the document. + * + * @return List, possibly empty, of {@link XWPFEndnote}s. + */ + public List getEndnotes() { + if (endnotes == null) { + return Collections.emptyList(); + } + return endnotes.getEndnotesList(); + } + + /** + * Remove the specified end note if present. + * + * @param pos Array position of the end note to be removed. + * @return True if the end note was removed. + * @since 4.0.0 + */ + public boolean removeEndnote(int pos) { + if (null != endnotes) { + return endnotes.removeEndnote(pos); + } else { + return false; + } + } } diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFEndnote.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFEndnote.java new file mode 100644 index 0000000000..a6c3815b08 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFEndnote.java @@ -0,0 +1,76 @@ +package org.apache.poi.xwpf.usermodel; + +import org.apache.poi.util.Internal; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdn; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdnRef; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR; + +/** + * Represents an end note footnote. + *

End notes are collected at the end of a document or section rather than + * at the bottom of a page.

+ *

Create a new footnote using {@link XWPFDocument#createEndnote()} or + * {@link XWPFEndnotes#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(AbstractXWPFFootnoteEndnote)} + * method does this for you.

+ * @since 4.0.0 + */ +public class XWPFEndnote extends AbstractXWPFFootnoteEndnote { + + public XWPFEndnote() {} + + @Internal + public XWPFEndnote(XWPFDocument document, CTFtnEdn body) { + super(document, body); + } + + @Internal + public XWPFEndnote(CTFtnEdn note, AbstractXWPFFootnotesEndnotes footnotes) { + super(note, footnotes); + } + + /** + * Ensure that the specified paragraph has a reference marker for this + * end note 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(AbstractXWPFFootnoteEndnote))}. + *

+ *

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

+ * + * @param p The {@link XWPFParagraph} to ensure + * @since 4.0.0 + */ + 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.getEndnoteReferenceList()) { + if (getId().equals(ref.getId())) { + foundRef = true; + break; + } + } + if (!foundRef) { + ctr.addNewRPr().addNewRStyle().setVal("FootnoteReference"); + ctr.addNewEndnoteRef(); + } + + } + +} diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFEndnotes.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFEndnotes.java new file mode 100644 index 0000000000..0f5f2d6251 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFEndnotes.java @@ -0,0 +1,195 @@ +package org.apache.poi.xwpf.usermodel; + +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; + +import javax.xml.namespace.QName; + +import org.apache.poi.ooxml.POIXMLException; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.util.Internal; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlOptions; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTEndnotes; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdn; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.EndnotesDocument; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFtnEdn; + + +/** + * Looks after the collection of end notes for a document. + * Managed end notes ({@link XWPFEndnote}). + * @since 4.0.0 + */ +public class XWPFEndnotes extends AbstractXWPFFootnotesEndnotes { + + protected CTEndnotes ctEndnotes; + + public XWPFEndnotes() { + super(); + } + + /** + * Construct XWPFEndnotes from a package part + * + * @param part the package part holding the data of the footnotes, + * + * @since POI 3.14-Beta1 + */ + public XWPFEndnotes(PackagePart part) throws IOException, OpenXML4JException { + super(part); + } + + /** + * Set the end notes for this part. + * + * @param endnotes The endnotes to be added. + */ + @Internal + public void setEndnotes(CTEndnotes endnotes) { + ctEndnotes = endnotes; + } + + /** + * Create a new end note and add it to the document. + * + * @return New XWPFEndnote + * @since 4.0.0 + */ + public XWPFEndnote createEndnote() { + CTFtnEdn newNote = CTFtnEdn.Factory.newInstance(); + newNote.setType(STFtnEdn.NORMAL); + + XWPFEndnote footnote = addEndnote(newNote); + footnote.getCTFtnEdn().setId(getIdManager().nextId()); + return footnote; + + } + + /** + * Remove the specified footnote if present. + * + * @param pos + * @return True if the footnote was removed. + * @since 4.0.0 + */ + public boolean removeFootnote(int pos) { + if (ctEndnotes.sizeOfEndnoteArray() >= pos - 1) { + ctEndnotes.removeEndnote(pos); + listFootnote.remove(pos); + return true; + } else { + return false; + } + } + + /** + * Read document + */ + @Override + protected void onDocumentRead() throws IOException { + EndnotesDocument notesDoc; + InputStream is = null; + try { + is = getPackagePart().getInputStream(); + notesDoc = EndnotesDocument.Factory.parse(is, DEFAULT_XML_OPTIONS); + ctEndnotes = notesDoc.getEndnotes(); + } catch (XmlException e) { + throw new POIXMLException(); + } finally { + if (is != null) { + is.close(); + } + } + + for (CTFtnEdn note : ctEndnotes.getEndnoteList()) { + listFootnote.add(new XWPFEndnote(note, this)); + } + } + + @Override + protected void commit() throws IOException { + XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); + xmlOptions.setSaveSyntheticDocumentElement(new QName(CTEndnotes.type.getName().getNamespaceURI(), "endnotes")); + PackagePart part = getPackagePart(); + OutputStream out = part.getOutputStream(); + ctEndnotes.save(out, xmlOptions); + out.close(); + } + + /** + * add an {@link XWPFEndnote} to the document + * + * @param endnote + * @throws IOException + */ + public void addEndnote(XWPFEndnote endnote) { + listFootnote.add(endnote); + ctEndnotes.addNewEndnote().set(endnote.getCTFtnEdn()); + } + + /** + * Add an endnote to the document + * + * @param note Note to add + * @return New {@link XWPFEndnote} + * @throws IOException + */ + @Internal + public XWPFEndnote addEndnote(CTFtnEdn note) { + CTFtnEdn newNote = ctEndnotes.addNewEndnote(); + newNote.set(note); + XWPFEndnote xNote = new XWPFEndnote(newNote, this); + listFootnote.add(xNote); + return xNote; + } + + /** + * Get the end note with the specified ID, if any. + * @param id End note ID. + * @return The end note or null if not found. + */ + public XWPFEndnote getFootnoteById(int id) { + return (XWPFEndnote)super.getFootnoteById(id); + } + + /** + * Get the list of {@link XWPFEndnote} in the Endnotes part. + * + * @return List, possibly empty, of end notes. + */ + public List getEndnotesList() { + List resultList = new ArrayList(); + for (AbstractXWPFFootnoteEndnote note : listFootnote) { + resultList.add((XWPFEndnote)note); + } + return resultList; + } + + /** + * Remove the specified end note if present. + * + * @param pos Array position of the endnote to be removed + * @return True if the end note was removed. + * @since 4.0.0 + */ + public boolean removeEndnote(int pos) { + if (ctEndnotes.sizeOfEndnoteArray() >= pos - 1) { + ctEndnotes.removeEndnote(pos); + listFootnote.remove(pos); + return true; + } 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 9444dbf6e9..d4afd64512 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java @@ -16,22 +16,10 @@ ==================================================================== */ package org.apache.poi.xwpf.usermodel; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.apache.poi.ooxml.POIXMLDocumentPart; -import org.apache.xmlbeans.XmlCursor; -import org.apache.xmlbeans.XmlObject; +import org.apache.poi.util.Internal; 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. @@ -44,425 +32,21 @@ import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc; * 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)} + * The {@link XWPFParagraph#addFootnoteReference(AbstractXWPFFootnoteEndnote)} * method does this for you.

*/ -public class XWPFFootnote implements Iterable, IBody { - private List paragraphs = new ArrayList<>(); - private List tables = new ArrayList<>(); - private List pictures = new ArrayList<>(); - private List bodyElements = new ArrayList<>(); - - private CTFtnEdn ctFtnEdn; - private XWPFFootnotes footnotes; - private XWPFDocument document; - - public XWPFFootnote(CTFtnEdn note, XWPFFootnotes xFootnotes) { - footnotes = xFootnotes; - ctFtnEdn = note; - document = xFootnotes.getXWPFDocument(); - init(); +public class XWPFFootnote extends AbstractXWPFFootnoteEndnote { + + @Internal + public XWPFFootnote(CTFtnEdn note, AbstractXWPFFootnotesEndnotes xFootnotes) { + super(note, xFootnotes); } + @Internal public XWPFFootnote(XWPFDocument document, CTFtnEdn body) { - ctFtnEdn = body; - this.document = document; - init(); - } - - private void init() { - XmlCursor cursor = ctFtnEdn.newCursor(); - //copied from XWPFDocument...should centralize this code - //to avoid duplication - cursor.selectPath("./*"); - while (cursor.toNextSelection()) { - XmlObject o = cursor.getObject(); - if (o instanceof CTP) { - XWPFParagraph p = new XWPFParagraph((CTP) o, this); - bodyElements.add(p); - paragraphs.add(p); - } else if (o instanceof CTTbl) { - XWPFTable t = new XWPFTable((CTTbl) o, this); - bodyElements.add(t); - tables.add(t); - } else if (o instanceof CTSdtBlock) { - XWPFSDT c = new XWPFSDT((CTSdtBlock) o, this); - bodyElements.add(c); - } - - } - 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 {@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) { - if (pos >= 0 && pos < tables.size()) { - return tables.get(pos); - } - return null; - } - - /** - * Inserts an existing {@link XWPFTable) into the arrays bodyElements and tables. - * - * @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.getTblList()) { - if (tbl == table.getCTTbl()) { - break; - } - i++; - } - tables.add(i, table); - - } - - /** - * 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) - */ - public XWPFTable getTable(CTTbl ctTable) { - for (XWPFTable table : tables) { - if (table == null) - return null; - if (table.getCTTbl().equals(ctTable)) - return table; - } - 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 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) { - for (XWPFParagraph paragraph : paragraphs) { - if (paragraph.getCTP().equals(p)) - return paragraph; - } - return null; - } - - /** - * 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) - */ - public XWPFParagraph getParagraphArray(int pos) { - if(pos >=0 && pos < paragraphs.size()) { - return paragraphs.get(pos); - } - return null; - } - - /** - * 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) { - XmlCursor cursor = cell.newCursor(); - cursor.toParent(); - XmlObject o = cursor.getObject(); - if (!(o instanceof CTRow)) { - return null; - } - CTRow row = (CTRow) o; - cursor.toParent(); - o = cursor.getObject(); - cursor.dispose(); - if (!(o instanceof CTTbl)) { - return null; - } - CTTbl tbl = (CTTbl) o; - XWPFTable table = getTable(tbl); - if (table == null) { - return null; - } - XWPFTableRow tableRow = table.getRow(row); - if(tableRow == null){ - return null; - } - return tableRow.getTableCell(cell); + super(document, body); } - - /** - * 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(); - verify.toParent(); - if (verify.getObject() == this.ctFtnEdn) { - return true; - } - 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 {@link XWPFTable} - * @see org.apache.poi.xwpf.usermodel.IBody#insertNewTbl(XmlCursor cursor) - */ - public XWPFTable insertNewTbl(XmlCursor cursor) { - if (isCursorInFtn(cursor)) { - String uri = CTTbl.type.getName().getNamespaceURI(); - String localPart = "tbl"; - cursor.beginElement(localPart, uri); - cursor.toParent(); - CTTbl t = (CTTbl) cursor.getObject(); - XWPFTable newT = new XWPFTable(t, this); - cursor.removeXmlContents(); - XmlObject o = null; - while (!(o instanceof CTTbl) && (cursor.toPrevSibling())) { - o = cursor.getObject(); - } - if (!(o instanceof CTTbl)) { - tables.add(0, newT); - } else { - int pos = tables.indexOf(getTable((CTTbl) o)) + 1; - tables.add(pos, newT); - } - int i = 0; - cursor = t.newCursor(); - while (cursor.toPrevSibling()) { - o = cursor.getObject(); - if (o instanceof CTP || o instanceof CTTbl) - i++; - } - bodyElements.add(i, newT); - XmlCursor c2 = t.newCursor(); - cursor.toCursor(c2); - cursor.toEndToken(); - c2.dispose(); - return newT; - } - return null; - } - - /** - * Add a new {@link XWPFParagraph} at position of the cursor. - * - * @param cursor - * @return The inserted {@link XWPFParagraph} - * @see org.apache.poi.xwpf.usermodel.IBody#insertNewParagraph(XmlCursor cursor) - */ - public XWPFParagraph insertNewParagraph(final XmlCursor cursor) { - if (isCursorInFtn(cursor)) { - String uri = CTP.type.getName().getNamespaceURI(); - String localPart = "p"; - cursor.beginElement(localPart, uri); - cursor.toParent(); - CTP p = (CTP) cursor.getObject(); - XWPFParagraph newP = new XWPFParagraph(p, this); - XmlObject o = null; - while (!(o instanceof CTP) && (cursor.toPrevSibling())) { - o = cursor.getObject(); - } - if ((!(o instanceof CTP)) || o == p) { - paragraphs.add(0, newP); - } else { - int pos = paragraphs.indexOf(getParagraph((CTP) o)) + 1; - paragraphs.add(pos, newP); - } - int i = 0; - XmlCursor p2 = p.newCursor(); - cursor.toCursor(p2); - p2.dispose(); - while (cursor.toPrevSibling()) { - o = cursor.getObject(); - if (o instanceof CTP || o instanceof CTTbl) - i++; - } - bodyElements.add(i, newP); - p2 = p.newCursor(); - cursor.toCursor(p2); - cursor.toEndToken(); - p2.dispose(); - return newP; - } - return null; - } - - /** - * Add a new {@link XWPFTable} to the end of the footnote. - * - * @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(); - newTable.set(table); - XWPFTable xTable = new XWPFTable(newTable, this); - tables.add(xTable); - return xTable; - } - - /** - * Add a new {@link XWPFParagraph} to the end of the footnote. - * - * @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(); - newPara.set(paragraph); - XWPFParagraph xPara = new XWPFParagraph(newPara, this); - paragraphs.add(xPara); - return xPara; - } - - /** - * Get the {@link XWPFDocument} the footnote is part of. - * @see org.apache.poi.xwpf.usermodel.IBody#getXWPFDocument() - */ - public XWPFDocument getXWPFDocument() { - return document; - } - - /** - * 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() - */ - public POIXMLDocumentPart getPart() { - return footnotes; - } - - /** - * 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. @@ -474,7 +58,8 @@ public class XWPFFootnote implements Iterable, IBody { * contain a {@link CTFtnEdnRef} object.

* * @param p The {@link XWPFParagraph} to ensure - */ + * @since 4.0.0 + */ public void ensureFootnoteRef(XWPFParagraph p) { XWPFRun r = null; @@ -496,34 +81,6 @@ public class XWPFFootnote implements Iterable, IBody { 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 a4788673ed..4f3fa83b0d 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java @@ -28,27 +28,23 @@ import java.util.List; import javax.xml.namespace.QName; -import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ooxml.POIXMLException; import org.apache.poi.openxml4j.exceptions.OpenXML4JException; import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.util.Internal; 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. - * Manages both bottom-of-the-page footnotes and end notes. + * Manages bottom-of-the-page footnotes ({@link XWPFFootnote}). */ -public class XWPFFootnotes extends POIXMLDocumentPart { - protected XWPFDocument document; - private List listFootnote = new ArrayList<>(); - private CTFootnotes ctFootnotes; +public class XWPFFootnotes extends AbstractXWPFFootnotesEndnotes { + protected CTFootnotes ctFootnotes; /** * Construct XWPFFootnotes from a package part @@ -67,6 +63,49 @@ public class XWPFFootnotes extends POIXMLDocumentPart { public XWPFFootnotes() { } + /** + * Sets the ctFootnotes + * + * @param footnotes Collection of CTFntEdn objects. + */ + @Internal + public void setFootnotes(CTFootnotes footnotes) { + ctFootnotes = footnotes; + } + + /** + * Create a new footnote and add it to the document. + * + * @return New {@link XWPFFootnote} + * @since 4.0.0 + */ + public XWPFFootnote createFootnote() { + CTFtnEdn newNote = CTFtnEdn.Factory.newInstance(); + newNote.setType(STFtnEdn.NORMAL); + + XWPFFootnote footnote = addFootnote(newNote); + footnote.getCTFtnEdn().setId(getIdManager().nextId()); + return footnote; + + } + + /** + * Remove the specified footnote if present. + * + * @param pos Array position of the footnote to be removed + * @return True if the footnote was removed. + * @since 4.0.0 + */ + public boolean removeFootnote(int pos) { + if (ctFootnotes.sizeOfFootnoteArray() >= pos - 1) { + ctFootnotes.removeFootnote(pos); + listFootnote.remove(pos); + return true; + } else { + return false; + } + } + /** * Read document */ @@ -85,9 +124,8 @@ public class XWPFFootnotes extends POIXMLDocumentPart { is.close(); } } - - // Find our footnotes - for (CTFtnEdn note : ctFootnotes.getFootnoteArray()) { + + for (CTFtnEdn note : ctFootnotes.getFootnoteList()) { listFootnote.add(new XWPFFootnote(note, this)); } } @@ -102,31 +140,10 @@ public class XWPFFootnotes extends POIXMLDocumentPart { out.close(); } - public List getFootnotesList() { - return listFootnote; - } - - public XWPFFootnote getFootnoteById(int id) { - for (XWPFFootnote note : listFootnote) { - if (note.getCTFtnEdn().getId().intValue() == id) - return note; - } - return null; - } - /** - * Sets the ctFootnotes + * Add an {@link XWPFFootnote} to the document * - * @param footnotes - */ - public void setFootnotes(CTFootnotes footnotes) { - ctFootnotes = footnotes; - } - - /** - * add an XWPFFootnote to the document - * - * @param footnote + * @param footnote Footnote to add * @throws IOException */ public void addFootnote(XWPFFootnote footnote) { @@ -135,11 +152,12 @@ public class XWPFFootnotes extends POIXMLDocumentPart { } /** - * add a footnote to the document + * Add a CT footnote to the document * - * @param note + * @param note CTFtnEdn to add. * @throws IOException */ + @Internal public XWPFFootnote addFootnote(CTFtnEdn note) { CTFtnEdn newNote = ctFootnotes.addNewFootnote(); newNote.set(note); @@ -149,52 +167,18 @@ public class XWPFFootnotes extends POIXMLDocumentPart { } /** - * @see org.apache.poi.xwpf.usermodel.IBody#getPart() - */ - public XWPFDocument getXWPFDocument() { - if (document != null) { - return document; - } else { - return (XWPFDocument) getParent(); - } - } - - 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. + * Get the list of {@link XWPFFootnote} in the Footnotes part. * - * @param pos - * @return True if the footnote was removed. + * @return List, possibly empty, of footnotes. */ - public boolean removeFootnote(int pos) { - if (ctFootnotes.sizeOfFootnoteArray() >= pos - 1) { - ctFootnotes.removeFootnote(pos); - listFootnote.remove(pos); - return true; - } else { - return false; + public List getFootnotesList() { + List resultList = new ArrayList(); + for (AbstractXWPFFootnoteEndnote note : listFootnote) { + resultList.add((XWPFFootnote)note); } + return resultList; } + + + } 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 661e1c1abe..5fbc7a5225 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFParagraph.java @@ -76,21 +76,24 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para if (o instanceof CTFtnEdnRef) { CTFtnEdnRef ftn = (CTFtnEdnRef) o; footnoteText.append(" [").append(ftn.getId()).append(": "); - XWPFFootnote footnote = + AbstractXWPFFootnoteEndnote footnote = ftn.getDomNode().getLocalName().equals("footnoteReference") ? document.getFootnoteByID(ftn.getId().intValue()) : document.getEndnoteByID(ftn.getId().intValue()); - - boolean first = true; - for (XWPFParagraph p : footnote.getParagraphs()) { - if (!first) { - footnoteText.append("\n"); + if (null != footnote) { + boolean first = true; + for (XWPFParagraph p : footnote.getParagraphs()) { + if (!first) { + footnoteText.append("\n"); + } + first = false; + footnoteText.append(p.getText()); } - first = false; - footnoteText.append(p.getText()); + } else { + footnoteText.append("!!! End note with ID \"" + ftn.getId() + "\" not found in document."); } - footnoteText.append("] "); + } } c.dispose(); @@ -1674,11 +1677,16 @@ public class XWPFParagraph implements IBodyElement, IRunBody, ISDTContents, Para * The footnote reference run will have the style name "FootnoteReference". * * @param footnote Footnote to which to add a reference. + * @since 4.0.0 */ - public void addFootnoteReference(XWPFFootnote footnote) { + public void addFootnoteReference(AbstractXWPFFootnoteEndnote footnote) { XWPFRun run = createRun(); CTR ctRun = run.getCTR(); ctRun.addNewRPr().addNewRStyle().setVal("FootnoteReference"); - ctRun.addNewFootnoteReference().setId(footnote.getId()); + if (footnote instanceof XWPFEndnote) { + ctRun.addNewEndnoteReference().setId(footnote.getId()); + } else { + ctRun.addNewFootnoteReference().setId(footnote.getId()); + } } } diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java index d82eb5814b..cb1bba8e37 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java @@ -147,10 +147,10 @@ public final class XWPFRelation extends POIXMLRelation { XWPFFootnotes.class ); public static final XWPFRelation ENDNOTE = new XWPFRelation( - null, + "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes", - null, - null + "/word/endnotes.xml", + XWPFEndnotes.class ); /** * Supported image formats diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFEndnote.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFEndnote.java new file mode 100644 index 0000000000..0bc0fed736 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFEndnote.java @@ -0,0 +1,160 @@ +/* ==================================================================== + 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 TestXWPFEndnote { + + private XWPFDocument docOut; + private String p1Text; + private String p2Text; + private BigInteger endnoteId; + private XWPFEndnote endnote; + + @Before + public void setUp() { + docOut = new XWPFDocument(); + p1Text = "First paragraph in footnote"; + p2Text = "Second paragraph in footnote"; + + // NOTE: XWPFDocument.createEndnote() delegates directly + // to XWPFEndnotes.createEndnote() so this tests + // both creation of new XWPFEndnotes in document + // and XWPFEndnotes.createEndnote(); + + // NOTE: Creating the endnote does not automatically + // create a first paragraph. + endnote = docOut.createEndnote(); + endnoteId = endnote.getId(); + + } + + @Test + public void testAddParagraphsToFootnote() throws IOException { + + // Add a run to the first paragraph: + + XWPFParagraph p1 = endnote.createParagraph(); + p1.createRun().setText(p1Text); + + // Create a second paragraph: + + XWPFParagraph p = endnote.createParagraph(); + assertNotNull("Paragraph is null", p); + p.createRun().setText(p2Text); + + XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut); + + XWPFEndnote testEndnote = docIn.getEndnoteByID(endnoteId.intValue()); + assertNotNull(testEndnote); + + assertEquals(2, testEndnote.getParagraphs().size()); + XWPFParagraph testP1 = testEndnote.getParagraphs().get(0); + assertEquals(p1Text, testP1.getText()); + + XWPFParagraph testP2 = testEndnote.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 endnote reference in testP1", r1.getCTR().getEndnoteRefList().size() > 0); + assertNotNull("No endnote reference in testP1", r1.getCTR().getEndnoteRefArray(0)); + + XWPFRun r2 = testP2.getRuns().get(0); + assertNotNull("Expected a run in testP2", r2); + assertTrue("Found an endnote reference in testP2", r2.getCTR().getEndnoteRefList().size() == 0); + + } + + @Test + public void testAddTableToFootnote() throws IOException { + XWPFTable table = endnote.createTable(); + assertNotNull(table); + + XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut); + + XWPFEndnote testFootnote = docIn.getEndnoteByID(endnoteId.intValue()); + XWPFTable testTable = testFootnote.getTableArray(0); + assertNotNull(testTable); + + table = endnote.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, endnote.getBodyElements().size()); + IBodyElement testP1 = endnote.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().getEndnoteRefList().size() > 0); + assertNotNull("No footnote reference in testP1", r1.getCTR().getEndnoteRefArray(0)); + + } + + @Test + public void testRemoveEndnote() { + // NOTE: XWPFDocument.removeEndnote() delegates directly to + // XWPFEndnotes. + docOut.createEndnote(); + assertEquals("Expected 2 endnotes", 2, docOut.getEndnotes().size()); + assertNotNull("Didn't get second endnote", docOut.getEndnotes().get(1)); + boolean result = docOut.removeEndnote(0); + assertTrue("Remove endnote did not return true", result); + assertEquals("Expected 1 endnote after removal", 1, docOut.getEndnotes().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(endnote); + XWPFRun run = p.getRuns().get(0); + CTR ctr = run.getCTR(); + assertNotNull("Expected a run", run); + List endnoteRefList = ctr.getEndnoteReferenceList(); + assertNotNull(endnoteRefList); + CTFtnEdnRef ref = endnoteRefList.get(0); + assertNotNull(ref); + assertEquals("Endnote ID and reference ID did not match", endnote.getId(), ref.getId()); + + + } + +} diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFEndnotes.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFEndnotes.java new file mode 100644 index 0000000000..1195a1206e --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFEndnotes.java @@ -0,0 +1,62 @@ +/* ==================================================================== + 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 java.io.IOException; +import java.math.BigInteger; + +import org.apache.poi.xwpf.XWPFTestDataSamples; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STFtnEdn; + +import junit.framework.TestCase; + +public class TestXWPFEndnotes extends TestCase { + + public void testCreateEndnotes() throws IOException{ + XWPFDocument docOut = new XWPFDocument(); + + XWPFEndnotes footnotes = docOut.createEndnotes(); + + assertNotNull(footnotes); + + XWPFEndnotes secondFootnotes = docOut.createEndnotes(); + + assertSame(footnotes, secondFootnotes); + + docOut.close(); + } + + public void testAddEndnotesToDocument() throws IOException { + XWPFDocument docOut = new XWPFDocument(); + + // NOTE: XWPFDocument.createEndnote() delegates directly + // to XWPFFootnotes.createEndnote() so this tests + // both creation of new XWPFFootnotes in document + // and XWPFFootnotes.createEndnote(); + XWPFEndnote endnote = docOut.createEndnote(); + BigInteger noteId = endnote.getId(); + + XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut); + + XWPFEndnote note = docIn.getEndnoteByID(noteId.intValue()); + assertNotNull(note); + assertEquals(STFtnEdn.NORMAL, note.getCTFtnEdn().getType()); + } + +} + diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java index 8bb377fe5a..b875644cb5 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnote.java @@ -150,9 +150,11 @@ public class TestXWPFFootnote { assertNotNull("Expected a run", run); CTFtnEdnRef ref = ctr.getFootnoteReferenceList().get(0); assertNotNull(ref); + // FIXME: Verify that the footnote reference is w:endnoteReference, not w:footnoteReference 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 037bdad04f..39fb397442 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnotes.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFFootnotes.java @@ -31,11 +31,11 @@ public class TestXWPFFootnotes extends TestCase { public void testCreateFootnotes() throws IOException{ XWPFDocument docOut = new XWPFDocument(); - XWPFFootnotes footnotes = docOut.createFootnotes(); + AbstractXWPFFootnotesEndnotes footnotes = docOut.createFootnotes(); assertNotNull(footnotes); - XWPFFootnotes secondFootnotes = docOut.createFootnotes(); + AbstractXWPFFootnotesEndnotes secondFootnotes = docOut.createFootnotes(); assertSame(footnotes, secondFootnotes); diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFSDT.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFSDT.java index f1ecf560e1..39a3ac6751 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFSDT.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFSDT.java @@ -17,17 +17,15 @@ package org.apache.poi.xwpf.usermodel; +import static org.apache.poi.POITestCase.assertContains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.apache.poi.POITestCase.assertContains; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; import org.apache.poi.xwpf.XWPFTestDataSamples; -import org.junit.Ignore; import org.junit.Test; public final class TestXWPFSDT { @@ -168,8 +166,8 @@ public final class TestXWPFSDT { for (XWPFFootnote footnote : doc.getFootnotes()) { sdts.addAll(extractSDTsFromBodyElements(footnote.getBodyElements())); } - for (Map.Entry e : doc.endnotes.entrySet()) { - sdts.addAll(extractSDTsFromBodyElements(e.getValue().getBodyElements())); + for (XWPFEndnote footnote : doc.getEndnotes()) { + sdts.addAll(extractSDTsFromBodyElements(footnote.getBodyElements())); } return sdts; }