Reputation: 1
I'm creating a word document with a table using Java and Apache POI.
I can create the table easily enough, set each column with different widths and then merge the cells to produce the desired effect (see images below) however when I open the word document some of the cells have been adjusted so that their edges snap together. I have found that adding an additional row to the beginning of the table and leaving all cells unmerged keeps the rest of the rows intact, but removing this row later using table.removeRow(0); affects the rest of the rows. If I open the word document and manually delete the row, the cells stay where they are. Is there anything I can do to preserve the layout of the cells?
correct layout with an additional unmerged top row
the result after removing the top row
This is the function that creates the word doc and table:
public static void createWord() {
// Blank Document
XWPFDocument document = new XWPFDocument();
CTSectPr sectPr = document.getDocument().getBody().addNewSectPr();
CTPageMar pageMar = sectPr.addNewPgMar();
pageMar.setLeft(BigInteger.valueOf(300L));
pageMar.setTop(BigInteger.valueOf(300L));
pageMar.setRight(BigInteger.valueOf(300L));
pageMar.setBottom(BigInteger.valueOf(300L));
XWPFParagraph paragraph = document.createParagraph();
paragraph.setSpacingBefore(0);
paragraph.setSpacingAfter(0);
// determine the number of rows and columns required
int rows = 3;
int cols = 6;
// create table
XWPFTable table = document.createTable(rows+1, cols);
CTTblPr tblPr = table.getCTTbl().getTblPr();
if (null == tblPr) {
tblPr = table.getCTTbl().addNewTblPr();
}
// set table width
CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
width.setType(STTblWidth.PCT);
width.setW(BigInteger.valueOf(5000)); // 5000 * 1/50 = 100%
//set row height
for(XWPFTableRow row:table.getRows()) {
row.setHeight(22);
}
// set width of each column
for (int row = 0; row <= rows; row++) {
setCellWidthPercentage(table, row, 0, 0.188);
setCellWidthPercentage(table, row, 1, 0.125);
setCellWidthPercentage(table, row, 2, 0.063);
setCellWidthPercentage(table, row, 3, 0.25);
setCellWidthPercentage(table, row, 4, 0.25);
setCellWidthPercentage(table, row, 5, 0.125);
}
mergeCellHorizontally(table, 1, 0, 2);
mergeCellHorizontally(table, 2, 0, 1);
mergeCellHorizontally(table, 2, 2, 4);
mergeCellHorizontally(table, 3, 1, 3);
// remove first row (comment out this line to see issue)
table.removeRow(0);
// Write the Document in file system
try {
File docFile = new File("C:\\doc.docx");
docFile.createNewFile();
FileOutputStream out = new FileOutputStream(docFile, false);
document.write(out);
out.close();
document.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
I'm using the code below to merge cells horizontally:
static void mergeCellHorizontally(XWPFTable table, int row, int fromCol, int toCol) {
for(int colIndex = fromCol; colIndex <= toCol; colIndex++){
XWPFTableCell cell = table.getRow(row).getCell(colIndex);
CTHMerge hmerge = CTHMerge.Factory.newInstance();
if(colIndex == fromCol) {
// The first merged cell is set with RESTART merge value
hmerge.setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
hmerge.setVal(STMerge.CONTINUE);
}
// Try getting the TcPr. Not simply setting an new one every time.
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr != null) {
tcPr.setHMerge(hmerge);
} else {
// only set an new TcPr if there is not one already
tcPr = CTTcPr.Factory.newInstance();
tcPr.setHMerge(hmerge);
cell.getCTTc().setTcPr(tcPr);
}
}
}
and this function to assign width values to the columns before merging:
private static void setCellWidthPercentage(XWPFTable table, int row, int col, double width) {
// prevent out of bounds exception
if (row < 0 || row >= table.getRows().size()) return;
if (col < 0 || col >= table.getRow(row).getTableCells().size()) return;
// assign widths in units of 1/50 of a percentage
CTTblWidth tblW = table.getRow(row).getCell(col).getCTTc().addNewTcPr().addNewTcW();
tblW.setType(STTblWidth.PCT);
tblW.setW(BigInteger.valueOf(Math.round(width * 50)));
}
Thanks in advance!
Upvotes: 0
Views: 1518
Reputation: 61852
The problem you see is that Word
renders tables in respect of the column width settings of the row which has the most columns in it. If other rows contradict the column width settings of that row, then their column width setting will be ignored. And after merging cells you are not correcting the column width settings. For example after mergeCellHorizontally(table, 0, 0, 2);
column 0 in row 0 is up to column 2 now. So column 0 now need width of formerly columns 0 + 1 + 2. But since you are not correcting that, it stays width of formerly column 0 only and gets ignored while rendering if that contradicts the width settings of the row having the most columns.
So the main problem is that your code lacks correcting the column width settings in the rows after merging cells.
I have shown this already in how to set specific cell width in different row in apache poi table?.
But there are more issues.
First the method mergeCellHorizontally
should merge cells horizontally by setting grid span instead of using CTHMerge. This is much more compatible to all kinds of word processing applications which open *.docx
files than using CTHMerge
.
Second there always should be used the last apache poi
version. Current apache poi 4.1.2
provides XWPFTable.setWidth and XWPFTableCell.setWidth. So no own set-width-methods are necessary.
And third you should create a table grid for the table with widths of the columns. This is necessary for Libreoffice
/OpenOffice
to accept the column widths. Unfortunately this needs calculating the column widths in unit twentieths of a point (1/1440 of an inch) since TblGrid
- GridCol
does not accepts percent values.
The following complete example shows all this and creates the table you want.
import java.io.FileOutputStream;
import java.math.BigInteger;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
public class CreateWordTableMergedCells {
//merging horizontally by setting grid span instead of using CTHMerge
static void mergeCellHorizontally(XWPFTable table, int row, int fromCol, int toCol) {
XWPFTableCell cell = table.getRow(row).getCell(fromCol);
// Try getting the TcPr. Not simply setting an new one every time.
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr == null) tcPr = cell.getCTTc().addNewTcPr();
// The first merged cell has grid span property set
if (tcPr.isSetGridSpan()) {
tcPr.getGridSpan().setVal(BigInteger.valueOf(toCol-fromCol+1));
} else {
tcPr.addNewGridSpan().setVal(BigInteger.valueOf(toCol-fromCol+1));
}
// Cells which join (merge) the first one, must be removed
for(int colIndex = toCol; colIndex > fromCol; colIndex--) {
table.getRow(row).getCtRow().removeTc(colIndex);
table.getRow(row).removeCell(colIndex);
}
}
public static void main(String[] args) throws Exception {
XWPFDocument document= new XWPFDocument();
XWPFParagraph paragraph = document.createParagraph();
XWPFRun run=paragraph.createRun();
run.setText("The table:");
// determine the number of rows and columns required
int rows = 3;
int cols = 6;
//create table
XWPFTable table = document.createTable(rows, cols);
//set table width
table.setWidth("100%");
double[] columnWidths = new double[] { // columnWidths in percent
0.188, 0.125, 0.062, 0.25, 0.25, 0.125
};
//create CTTblGrid for this table with widths of the columns.
//necessary for Libreoffice/Openoffice to accept the column widths.
//values are in unit twentieths of a point (1/1440 of an inch)
int w100Percent = 6*1440; // twentieths of a point (1/1440 of an inch); 6 inches
//first column
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(
Math.round(w100Percent*columnWidths[0])));
//other columns
for (int c = 1; c < cols; c++) {
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(
Math.round(w100Percent*columnWidths[c])));
}
// set width of each column in each row
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
table.getRow(r).getCell(c).setWidth("" + (columnWidths[c]*100.0) + "%");
}
}
//using the merge method
mergeCellHorizontally(table, 0, 0, 2); // after that column 0 is up to column 2
//column 0 now need width of formerly columns 0 + 1 + 2
table.getRow(0).getCell(0).setWidth("" + ((columnWidths[0]+columnWidths[1]+columnWidths[2])*100.0) + "%");
mergeCellHorizontally(table, 1, 0, 1); // after that column 0 is up to column 1
//column 0 now need width of formerly columns 0 + 1
table.getRow(1).getCell(0).setWidth("" + ((columnWidths[0]+columnWidths[1])*100.0) + "%");
mergeCellHorizontally(table, 1, 1, 3); // formerly col 2 is now col 1 and after that formerly column 2 is up to column 4
//current column 1 now need width of formerly columns 2 + 3 + 4
table.getRow(1).getCell(1).setWidth("" + ((columnWidths[2]+columnWidths[3]+columnWidths[4])*100.0) + "%");
mergeCellHorizontally(table, 2, 1, 3); // after that column 1 is up to column 3
//column 1 now need width of formerly columns 1 + 2 + 3
table.getRow(2).getCell(1).setWidth("" + ((columnWidths[1]+columnWidths[2]+columnWidths[3])*100.0) + "%");
paragraph = document.createParagraph();
FileOutputStream out = new FileOutputStream("create_table.docx");
document.write(out);
out.close();
}
}
Upvotes: 1