Index: src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java =================================================================== --- src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java (revision 1143890) +++ src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java (working copy) @@ -114,10 +114,10 @@ null ); public static final XWPFRelation FOOTNOTE = new XWPFRelation( - null, + "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes", - null, - null + "/word/footnotes.xml", + XWPFFootnotes.class ); public static final XWPFRelation ENDNOTE = new XWPFRelation( null, Index: src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java =================================================================== --- src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java (revision 1143890) +++ src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java (working copy) @@ -100,10 +100,10 @@ protected List bodyElements = new ArrayList(); protected List pictures = new ArrayList(); protected Map> packagePictures = new HashMap>(); - protected Map footnotes = new HashMap(); protected Map endnotes = new HashMap(); protected XWPFNumbering numbering; protected XWPFStyles styles; + protected XWPFFootnotes footnotes; /** Handles the joy of different headers/footers for different pages */ private XWPFHeaderFooterPolicy headerFooterPolicy; @@ -216,9 +216,11 @@ String relation = p.getPackageRelationship().getRelationshipType(); if(relation.equals(XWPFRelation.FOOTNOTE.getRelation())){ FootnotesDocument footnotesDocument = FootnotesDocument.Factory.parse(p.getPackagePart().getInputStream()); + this.footnotes = (XWPFFootnotes)p; + this.footnotes.onDocumentRead(); for(CTFtnEdn ctFtnEdn : footnotesDocument.getFootnotes().getFootnoteList()) { - footnotes.put(ctFtnEdn.getId().intValue(), new XWPFFootnote(this, ctFtnEdn)); + footnotes.addFootnote(ctFtnEdn); } } else if (relation.equals(XWPFRelation.ENDNOTE.getRelation())){ EndnotesDocument endnotesDocument = EndnotesDocument.Factory.parse(p.getPackagePart().getInputStream()); @@ -349,15 +351,15 @@ } public XWPFFootnote getFootnoteByID(int id) { - return footnotes.get(id); + return footnotes.getFootnoteById(id); } public XWPFFootnote getEndnoteByID(int id) { return endnotes.get(id); } - public Collection getFootnotes() { - return Collections.unmodifiableCollection(footnotes == null ? new ArrayList() : footnotes.values()); + public List getFootnotes() { + return footnotes.getFootnotesList(); } public XWPFHyperlink[] getHyperlinks() { @@ -745,14 +747,30 @@ return styles; } + /** + * Creates an empty footnotes element for the document if one does not already exist + * @return footnotes + */ + public XWPFFootnotes createFootnotes() { + if(footnotes == null) { + FootnotesDocument footnotesDoc = FootnotesDocument.Factory.newInstance(); - public XWPFFootnote addEndnote(CTFtnEdn note) { - XWPFFootnote footnote = new XWPFFootnote(this, note); - footnotes.put(note.getId().intValue(), footnote); - return footnote; + XWPFRelation relation = XWPFRelation.FOOTNOTE; + int i = getRelationIndex(relation); + + XWPFFootnotes wrapper = (XWPFFootnotes)createRelationship(relation, XWPFFactory.getInstance(), i); + wrapper.setFootnotes(footnotesDoc.addNewFootnotes()); + footnotes = wrapper; + } + + return footnotes; } 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); return endnote; Index: src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java =================================================================== --- src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java (revision 0) +++ src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnotes.java (revision 0) @@ -0,0 +1,164 @@ +/* ==================================================================== + 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.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.namespace.QName; + +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.xmlbeans.XmlException; +import org.apache.xmlbeans.XmlOptions; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP; + +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdn; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFootnotes; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.FootnotesDocument; +/** + * @author Mike McEuen (mceuen@hp.com) + * + */ +public class XWPFFootnotes extends POIXMLDocumentPart { + + private List listFootnote = new ArrayList(); + private CTFootnotes ctFootnotes; + + protected XWPFDocument document; + + /** + * Construct XWPFFootnotes from a package part + * + * @param part the package part holding the data of the footnotes, + * @param rel the package relationship of type "http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes" + */ + + public XWPFFootnotes(PackagePart part, PackageRelationship rel) throws IOException, OpenXML4JException{ + super(part, rel); + } + + /** + * Construct XWPFFootnotes from scratch for a new document. + */ + public XWPFFootnotes() { + } + + /** + * Read document + */ + @Override + protected void onDocumentRead () throws IOException { + FootnotesDocument notesDoc; + try { + InputStream is = getPackagePart().getInputStream(); + notesDoc = FootnotesDocument.Factory.parse(is); + ctFootnotes = notesDoc.getFootnotes(); + + } catch (XmlException e) { + throw new POIXMLException(); + } + //get any Footnote + for(CTFtnEdn note : ctFootnotes.getFootnoteList()) { + listFootnote.add(new XWPFFootnote(note, this)); + } + } + + @Override + protected void commit() throws IOException { + XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); + xmlOptions.setSaveSyntheticDocumentElement(new QName(CTFootnotes.type.getName().getNamespaceURI(), "footnotes")); + Map map = new HashMap(); + map.put("http://schemas.openxmlformats.org/officeDocument/2006/relationships", "r"); + map.put("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "w"); + xmlOptions.setSaveSuggestedPrefixes(map); + PackagePart part = getPackagePart(); + OutputStream out = part.getOutputStream(); + ctFootnotes.save(out, xmlOptions); + 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 + * @param footnotes + */ + public void setFootnotes(CTFootnotes footnotes) { + ctFootnotes = footnotes; + } + + /** + * add an XWPFFootnote to the document + * @param footnote + * @throws IOException + */ + public void addFootnote(XWPFFootnote footnote){ + listFootnote.add(footnote); + ctFootnotes.addNewFootnote().set(footnote.getCTFtnEdn()); + } + + /** + * add a footnote to the document + * @param footnote + * @throws IOException + */ + public XWPFFootnote addFootnote(CTFtnEdn note){ + CTFtnEdn newNote = ctFootnotes.addNewFootnote(); + newNote.set(note); + XWPFFootnote xNote = new XWPFFootnote(newNote, this); + listFootnote.add(xNote); + return xNote; + } + + public void setXWPFDocument(XWPFDocument doc) { + document = doc; + } + + /** + * @see org.apache.poi.xwpf.usermodel.IBody#getPart() + */ + public XWPFDocument getXWPFDocument() { + if ( document != null) { + return document; + } else { + return (XWPFDocument)getParent(); + } + } + +}//end class + Index: src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java =================================================================== --- src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java (revision 1143890) +++ src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFFootnote.java (working copy) @@ -20,12 +20,35 @@ import java.util.Iterator; import java.util.List; +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; +import org.apache.xmlbeans.XmlObject; +import org.apache.xmlbeans.XmlCursor; + import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFtnEdn; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP; -public class XWPFFootnote implements Iterable { +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow; + +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; + + public XWPFFootnote(CTFtnEdn note, XWPFFootnotes xFootnotes) { + footnotes = xFootnotes; + ctFtnEdn = note; + for (CTP p : ctFtnEdn.getPList()) { + paragraphs.add(new XWPFParagraph(p, this)); + } + } + public XWPFFootnote(XWPFDocument document, CTFtnEdn body) { for (CTP p : body.getPList()) { paragraphs.add(new XWPFParagraph(p, document)); @@ -38,6 +61,280 @@ public Iterator iterator(){ return paragraphs.iterator(); + } + + public List getTables() { + return tables; } + public List getPictures() { + return pictures; + } + + public List getBodyElements() { + return bodyElements; + } + + public CTFtnEdn getCTFtnEdn() { + return ctFtnEdn; + } + + public void setCTFtnEdn(CTFtnEdn footnote) { + ctFtnEdn = footnote; + } + + /** + * @param position in table array + * @return The 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 XWPFTable to the arrays bodyElements and tables + * @param pos + * @param table + * @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; + for (i = 0; i < ctFtnEdn.getTblList().size(); i++) { + CTTbl tbl = ctFtnEdn.getTblArray(i); + if(tbl == table.getCTTbl()){ + break; + } + } + 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 + * if there is no corresponding {@link XWPFTable} the method will return null + * @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 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 + * @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 + * @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 paragraph that holds + * the text of the header or footer. + * @see org.apache.poi.xwpf.usermodel.IBody#getParagraphArray(int pos) + */ + public XWPFParagraph getParagraphArray(int pos) { + + return paragraphs.get(pos); + } + + /** + * get the TableCell which belongs to the TableCell + * @param cell + * @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(row == null){ + return null; + } + return tableRow.getTableCell(cell); + } + + /** + * verifies that cursor is on the right position + * @param cursor + */ + private boolean isCursorInFtn(XmlCursor cursor) { + XmlCursor verify = cursor.newCursor(); + verify.toParent(); + if(verify.getObject() == this.ctFtnEdn){ + return true; + } + return false; + } + + public POIXMLDocumentPart getOwner(){ + return footnotes; + } + + /** + * + * @param cursor + * @return the inserted table + * @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); + cursor = t.newCursor(); + cursor.toEndToken(); + return newT; + } + return null; + } + + /** + * add a new paragraph at position of the cursor + * @param cursor + * @return the inserted paragraph + * @see org.apache.poi.xwpf.usermodel.IBody#insertNewParagraph(XmlCursor cursor) + */ + public XWPFParagraph insertNewParagraph(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)) || (CTP)o == p){ + paragraphs.add(0, newP); + } + else{ + int pos = paragraphs.indexOf(getParagraph((CTP)o))+1; + paragraphs.add(pos,newP); + } + int i=0; + cursor.toCursor(p.newCursor()); + while(cursor.toPrevSibling()){ + o =cursor.getObject(); + if(o instanceof CTP || o instanceof CTTbl) + i++; + } + bodyElements.add(i, newP); + cursor.toCursor(p.newCursor()); + cursor.toEndToken(); + return newP; + } + return null; + } + + /** + * add a new table to the end of the footnote + * @param table + * @return the added 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 paragraph to the end of the footnote + * @param paragraph + * @return the added XWPFParagraph + */ + public XWPFParagraph addNewParagraph(CTP paragraph) { + CTP newPara = ctFtnEdn.addNewP(); + newPara.set(paragraph); + XWPFParagraph xPara = new XWPFParagraph(newPara, this); + paragraphs.add(xPara); + return xPara; + } + + /** + * @see org.apache.poi.xwpf.usermodel.IBody#getXWPFDocument() + */ + public XWPFDocument getXWPFDocument() { + return footnotes.getXWPFDocument(); + } + + /** + * returns the Part, to which the body belongs, which you need for adding relationship to other parts + * @see org.apache.poi.xwpf.usermodel.IBody#getPart() + */ + public POIXMLDocumentPart getPart() { + return footnotes; + } + + /** + * get the PartType of the body + * @see org.apache.poi.xwpf.usermodel.IBody#getPartType() + */ + public BodyType getPartType() { + return BodyType.FOOTNOTE; + } } + Index: src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFHeaderFooter.java =================================================================== --- src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFHeaderFooter.java (revision 1143890) +++ src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFHeaderFooter.java (working copy) @@ -498,6 +498,10 @@ return tableRow.getTableCell(cell); } + public void setXWPFDocument(XWPFDocument doc) { + document = doc; + } + public XWPFDocument getXWPFDocument() { if (document!=null) { return document;