From 14babbb97085a6f8996fd8c11258c138e35e548e Mon Sep 17 00:00:00 2001 From: Mark Murphy Date: Sun, 30 Oct 2016 18:49:16 +0000 Subject: [PATCH] 57366: XWPFTable to Header / Footer Task-Url: https://bz.apache.org/bugzilla/show_bug.cgi?id=57366 This update contains a breaking change git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1767175 13f79535-47bb-0310-9956-ffa450edef68 --- .../usermodel/BetterHeaderFooterExample.java | 8 +- .../poi/xwpf/usermodel/HeaderFooterTable.java | 97 +++++++++++++++++++ .../xwpf/model/XWPFHeaderFooterPolicy.java | 26 ++--- .../poi/xwpf/usermodel/XWPFHeaderFooter.java | 89 ++++++++++++++++- .../poi/xwpf/usermodel/TestXWPFHeader.java | 54 ++++++++--- .../xwpf/usermodel/TestXWPFPictureData.java | 2 +- 6 files changed, 237 insertions(+), 39 deletions(-) create mode 100644 src/examples/src/org/apache/poi/xwpf/usermodel/HeaderFooterTable.java diff --git a/src/examples/src/org/apache/poi/xwpf/usermodel/BetterHeaderFooterExample.java b/src/examples/src/org/apache/poi/xwpf/usermodel/BetterHeaderFooterExample.java index bb67c5094..99e7dd1ba 100644 --- a/src/examples/src/org/apache/poi/xwpf/usermodel/BetterHeaderFooterExample.java +++ b/src/examples/src/org/apache/poi/xwpf/usermodel/BetterHeaderFooterExample.java @@ -38,14 +38,10 @@ public class BetterHeaderFooterExample { // create header/footer functions insert an empty paragraph XWPFHeader head = doc.createHeader(HeaderFooterType.DEFAULT); - p = head.getParagraphArray(0); - r = p.createRun(); - r.setText("header"); + head.createParagraph().createRun().setText("header"); XWPFFooter foot = doc.createFooter(HeaderFooterType.DEFAULT); - p = foot.getParagraphArray(0); - r = p.createRun(); - r.setText("footer"); + foot.createParagraph().createRun().setText("footer"); try { OutputStream os = new FileOutputStream(new File("header2.docx")); diff --git a/src/examples/src/org/apache/poi/xwpf/usermodel/HeaderFooterTable.java b/src/examples/src/org/apache/poi/xwpf/usermodel/HeaderFooterTable.java new file mode 100644 index 000000000..cd1d21f48 --- /dev/null +++ b/src/examples/src/org/apache/poi/xwpf/usermodel/HeaderFooterTable.java @@ -0,0 +1,97 @@ +/* ==================================================================== + 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.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; + +import org.apache.poi.wp.usermodel.HeaderFooterType; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGrid; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblGridCol; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblLayoutType; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblPr; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblLayoutType; + +public class HeaderFooterTable { + + public static void main(String[] args) throws IOException { + XWPFDocument doc = new XWPFDocument(); + + // Create a header with a 1 row, 3 column table + // changes made for issue 57366 allow a new header or footer + // to be created empty. This is a change. You will have to add + // either a paragraph or a table to the header or footer for + // the document to be considered valid. + XWPFHeader hdr = doc.createHeader(HeaderFooterType.DEFAULT); + XWPFTable tbl = hdr.createTable(1, 3); + + // Set the padding around text in the cells to 1/10th of an inch + int pad = (int) (.1 * 1440); + tbl.setCellMargins(pad, pad, pad, pad); + + // Set table width to 6.5 inches in 1440ths of a point + tbl.setWidth((int)(6.5 * 1440)); + // Can not yet set table or cell width properly, tables default to + // autofit layout, and this requires fixed layout + CTTbl ctTbl = tbl.getCTTbl(); + CTTblPr ctTblPr = ctTbl.addNewTblPr(); + CTTblLayoutType layoutType = ctTblPr.addNewTblLayout(); + layoutType.setType(STTblLayoutType.FIXED); + + // Now set up a grid for the table, cells will fit into the grid + // Each cell width is 3120 in 1440ths of an inch, or 1/3rd of 6.5" + BigInteger w = new BigInteger("3120"); + CTTblGrid grid = ctTbl.addNewTblGrid(); + for (int i = 0; i < 3; i++) { + CTTblGridCol gridCol = grid.addNewGridCol(); + gridCol.setW(w); + } + + // Add paragraphs to the cells + XWPFTableRow row = tbl.getRow(0); + XWPFTableCell cell = row.getCell(0); + XWPFParagraph p = cell.getParagraphArray(0); + XWPFRun r = p.createRun(); + r.setText("header left cell"); + + cell = row.getCell(1); + p = cell.getParagraphArray(0); + r = p.createRun(); + r.setText("header center cell"); + + cell = row.getCell(2); + p = cell.getParagraphArray(0); + r = p.createRun(); + r.setText("header right cell"); + + // Create a footer with a Paragraph + XWPFFooter ftr = doc.createFooter(HeaderFooterType.DEFAULT); + p = ftr.createParagraph(); + + r = p.createRun(); + r.setText("footer text"); + + OutputStream os = new FileOutputStream(new File("headertable.docx")); + doc.write(os); + doc.close(); + } + +} diff --git a/src/ooxml/java/org/apache/poi/xwpf/model/XWPFHeaderFooterPolicy.java b/src/ooxml/java/org/apache/poi/xwpf/model/XWPFHeaderFooterPolicy.java index ac3d7d5e0..8ff59713a 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/model/XWPFHeaderFooterPolicy.java +++ b/src/ooxml/java/org/apache/poi/xwpf/model/XWPFHeaderFooterPolicy.java @@ -271,19 +271,19 @@ public class XWPFHeaderFooterPolicy { ftr.setPArray(i, paragraphs[i].getCTP()); } } else { - CTP p = ftr.addNewP(); - CTBody body = doc.getDocument().getBody(); - if (body.sizeOfPArray() > 0) { - CTP p0 = body.getPArray(0); - if (p0.isSetRsidR()) { - byte[] rsidr = p0.getRsidR(); - byte[] rsidrdefault = p0.getRsidRDefault(); - p.setRsidP(rsidr); - p.setRsidRDefault(rsidrdefault); - } - } - CTPPr pPr = p.addNewPPr(); - pPr.addNewPStyle().setVal(pStyle); +// CTP p = ftr.addNewP(); +// CTBody body = doc.getDocument().getBody(); +// if (body.sizeOfPArray() > 0) { +// CTP p0 = body.getPArray(0); +// if (p0.isSetRsidR()) { +// byte[] rsidr = p0.getRsidR(); +// byte[] rsidrdefault = p0.getRsidRDefault(); +// p.setRsidP(rsidr); +// p.setRsidRDefault(rsidrdefault); +// } +// } +// CTPPr pPr = p.addNewPPr(); +// pPr.addNewPStyle().setVal(pStyle); } return ftr; } diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFHeaderFooter.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFHeaderFooter.java index f4a13e454..a0a6fd0b7 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFHeaderFooter.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFHeaderFooter.java @@ -43,10 +43,10 @@ import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc; * Parent of XWPF headers and footers */ public abstract class XWPFHeaderFooter extends POIXMLDocumentPart implements IBody { - List paragraphs = new ArrayList(1); - List tables = new ArrayList(1); + List paragraphs = new ArrayList(); + List tables = new ArrayList(); List pictures = new ArrayList(); - List bodyElements = new ArrayList(1); + List bodyElements = new ArrayList(); CTHdrFtr headerFooter; XWPFDocument document; @@ -323,13 +323,75 @@ public abstract class XWPFHeaderFooter extends POIXMLDocumentPart implements IBo /** * Adds a new paragraph at the end of the header or footer + * + * @return new {@link XWPFParagraph} object */ public XWPFParagraph createParagraph() { XWPFParagraph paragraph = new XWPFParagraph(headerFooter.addNewP(), this); paragraphs.add(paragraph); + bodyElements.add(paragraph); return paragraph; } + /** + * Adds a new table at the end of the header or footer + * + * @param rows - number of rows in the table + * @param cols - number of columns in the table + * @return new {@link XWPFTable} object + */ + public XWPFTable createTable(int rows, int cols) { + XWPFTable table = new XWPFTable(headerFooter.addNewTbl(), this, rows, cols); + tables.add(table); + bodyElements.add(table); + return table; + } + + /** + * Removes a specific paragraph from this header / footer + * + * @param paragraph - {@link XWPFParagraph} object to remove + */ + public void removeParagraph(XWPFParagraph paragraph) { + if (paragraphs.contains(paragraph)) { + CTP ctP = paragraph.getCTP(); + XmlCursor c = ctP.newCursor(); + c.removeXml(); + c.dispose(); + paragraphs.remove(paragraph); + bodyElements.remove(paragraph); + } + } + + /** + * Removes a specific table from this header / footer + * + * @param table - {@link XWPFTable} object to remove + */ + public void removeTable(XWPFTable table) { + if (tables.contains(table)) { + CTTbl ctTbl = table.getCTTbl(); + XmlCursor c = ctTbl.newCursor(); + c.removeXml(); + c.dispose(); + tables.remove(table); + bodyElements.remove(table); + } + } + + /** + * Clears all paragraphs and tables from this header / footer + */ + public void clearHeaderFooter() { + XmlCursor c = headerFooter.newCursor(); + c.removeXmlContents(); + c.dispose(); + paragraphs.clear(); + tables.clear(); + bodyElements.clear(); + CTP ctp = CTP.Factory.newInstance(); + } + /** * add a new paragraph at position of the cursor * @@ -538,4 +600,25 @@ public abstract class XWPFHeaderFooter extends POIXMLDocumentPart implements IBo public POIXMLDocumentPart getPart() { return this; } + + @Override + protected void prepareForCommit() { + // must contain at least an empty paragraph + if (bodyElements.size() == 0) { + createParagraph(); + } + + // Cells must contain at least an empty paragraph + for (XWPFTable tbl : tables) { + for (XWPFTableRow row : tbl.tableRows) { + for (XWPFTableCell cell : row.getTableCells()) { + if (cell.getBodyElements().size() == 0) { + cell.addParagraph(); + } + } + } + } + super.prepareForCommit(); + + } } diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFHeader.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFHeader.java index 0efb679fa..371c9da06 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFHeader.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFHeader.java @@ -17,16 +17,22 @@ package org.apache.poi.xwpf.usermodel; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + import java.io.IOException; -import junit.framework.TestCase; import org.apache.poi.xwpf.XWPFTestDataSamples; import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy; +import org.junit.Test; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText; -public final class TestXWPFHeader extends TestCase { +public final class TestXWPFHeader { + + @Test public void testSimpleHeader() throws IOException { XWPFDocument sampleDoc = XWPFTestDataSamples.openSampleDocument("headerFooter.docx"); @@ -38,6 +44,7 @@ public final class TestXWPFHeader extends TestCase { assertNotNull(footer); } + @Test public void testImageInHeader() throws IOException { XWPFDocument sampleDoc = XWPFTestDataSamples.openSampleDocument("headerPic.docx"); @@ -49,6 +56,7 @@ public final class TestXWPFHeader extends TestCase { assertEquals(1, header.getRelations().size()); } + @Test public void testSetHeader() throws IOException { XWPFDocument sampleDoc = XWPFTestDataSamples.openSampleDocument("SampleDoc.docx"); // no header is set (yet) @@ -56,6 +64,7 @@ public final class TestXWPFHeader extends TestCase { assertNull(policy.getDefaultHeader()); assertNull(policy.getFirstPageHeader()); assertNull(policy.getDefaultFooter()); + assertNull(policy.getFirstPageFooter()); CTP ctP1 = CTP.Factory.newInstance(); CTR ctR1 = ctP1.addNewR(); @@ -95,29 +104,31 @@ public final class TestXWPFHeader extends TestCase { XWPFHeader headerD = policy.createHeader(XWPFHeaderFooterPolicy.DEFAULT, pars); XWPFHeader headerF = policy.createHeader(XWPFHeaderFooterPolicy.FIRST); // Set a default footer and capture the returned XWPFFooter object. - XWPFFooter footer = policy.createFooter(XWPFHeaderFooterPolicy.DEFAULT, pars2); + XWPFFooter footerD = policy.createFooter(XWPFHeaderFooterPolicy.DEFAULT, pars2); + XWPFFooter footerF = policy.createFooter(XWPFHeaderFooterPolicy.FIRST); // Ensure the headers and footer were set correctly.... assertNotNull(policy.getDefaultHeader()); assertNotNull(policy.getFirstPageHeader()); assertNotNull(policy.getDefaultFooter()); + assertNotNull(policy.getFirstPageFooter()); // ....and that the footer object captured above contains two // paragraphs of text. - assertEquals(2, footer.getParagraphs().size()); + assertEquals(2, footerD.getParagraphs().size()); + assertEquals(0, footerF.getParagraphs().size()); // Check the header created with the paragraph got them, and the one - // created without got an empty one + // created without got none assertEquals(1, headerD.getParagraphs().size()); - assertEquals(1, headerF.getParagraphs().size()); - assertEquals(tText, headerD.getParagraphs().get(0).getText()); - assertEquals("", headerF.getParagraphs().get(0).getText()); + + assertEquals(0, headerF.getParagraphs().size()); // As an additional check, recover the defauls footer and // make sure that it contains two paragraphs of text and that // both do hold what is expected. - footer = policy.getDefaultFooter(); - XWPFParagraph[] paras = footer.getParagraphs().toArray(new XWPFParagraph[0]); + footerD = policy.getDefaultFooter(); + XWPFParagraph[] paras = footerD.getParagraphs().toArray(new XWPFParagraph[0]); assertEquals(2, paras.length); assertEquals("First paragraph for the footer", paras[0].getText()); @@ -126,12 +137,15 @@ public final class TestXWPFHeader extends TestCase { // Add some text to the empty header String fText1 = "New Text!"; - headerF.getParagraphs().get(0).insertNewRun(0).setText(fText1); - // TODO Add another paragraph and check - + String fText2 = "More Text!"; + headerF.createParagraph().insertNewRun(0).setText(fText1); + headerF.createParagraph().insertNewRun(0).setText(fText2); +// headerF.getParagraphs().get(0).insertNewRun(0).setText(fText1); + // Check it assertEquals(tText, headerD.getParagraphs().get(0).getText()); assertEquals(fText1, headerF.getParagraphs().get(0).getText()); + assertEquals(fText2, headerF.getParagraphs().get(1).getText()); // Save, re-open, ensure it's all still there @@ -141,7 +155,7 @@ public final class TestXWPFHeader extends TestCase { assertNotNull(policy.getFirstPageHeader()); assertNull(policy.getEvenPageHeader()); assertNotNull(policy.getDefaultFooter()); - assertNull(policy.getFirstPageFooter()); + assertNotNull(policy.getFirstPageFooter()); assertNull(policy.getEvenPageFooter()); // Check the new headers still have their text @@ -149,16 +163,20 @@ public final class TestXWPFHeader extends TestCase { headerF = policy.getFirstPageHeader(); assertEquals(tText, headerD.getParagraphs().get(0).getText()); assertEquals(fText1, headerF.getParagraphs().get(0).getText()); + assertEquals(fText2, headerF.getParagraphs().get(1).getText()); // Check the new footers have their new text too - footer = policy.getDefaultFooter(); - paras = footer.getParagraphs().toArray(new XWPFParagraph[0]); + footerD = policy.getDefaultFooter(); + paras = footerD.getParagraphs().toArray(new XWPFParagraph[0]); + footerF = policy.getFirstPageFooter(); assertEquals(2, paras.length); assertEquals("First paragraph for the footer", paras[0].getText()); assertEquals("Second paragraph for the footer", paras[1].getText()); + assertEquals(1, footerF.getParagraphs().size()); } + @Test public void testSetWatermark() throws IOException { XWPFDocument sampleDoc = XWPFTestDataSamples.openSampleDocument("SampleDoc.docx"); @@ -183,18 +201,22 @@ public final class TestXWPFHeader extends TestCase { assertNotNull(policy.getEvenPageHeader()); } + @Test public void testAddPictureData() { // TODO } + @Test public void testGetAllPictures() { // TODO } + @Test public void testGetAllPackagePictures() { // TODO } + @Test public void testGetPictureDataById() { // TODO } diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFPictureData.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFPictureData.java index 02ab71adc..f4c0a49c0 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFPictureData.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFPictureData.java @@ -73,7 +73,7 @@ public class TestXWPFPictureData extends TestCase { // Add a default header policy = doc.createHeaderFooterPolicy(); XWPFHeader header = policy.createHeader(XWPFHeaderFooterPolicy.DEFAULT); - header.getParagraphs().get(0).createRun().setText("Hello, Header World!"); + header.createParagraph().createRun().setText("Hello, Header World!"); header.createParagraph().createRun().setText("Paragraph 2"); assertEquals(0, header.getAllPictures().size()); assertEquals(2, header.getParagraphs().size());