I know how to merge cells vertically with Apache POI word. But it seems if a new row is created, the merge won't take effect.
Here is the input table:
I wish to add a new row between old row 2 and old row 3, and have the new row's cell at first column merged into C2, like this:
So I created a new row and added it to the table below old row 2, and attempt to merge the cells
github source code link is here, it can reproduce the problem.
public class POIWordAddSubRowQuestionDemo{
public static void main(String[] args) throws IOException, XmlException{
ClassLoader classLoader = POIWordAddSubRowQuestionDemo.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("input.docx");
String outputDocxPath = "F:/TEMP/output.docx";
assert inputStream != null;
XWPFDocument doc = new XWPFDocument(inputStream);
XWPFTable table = doc.getTables().get(0);
//this is 'old row 2'
XWPFTableRow secondRow = table.getRows().get(1);
//create a new row that is based on 'old row 2'
CTRow ctrow = CTRow.Factory.parse(secondRow.getCtRow().newInputStream());
XWPFTableRow newRow = new XWPFTableRow(ctrow, table);
XWPFRun xwpfRun = newRow.getCell(1).getParagraphs().get(0).getRuns().get(0);
//set row text
xwpfRun.setText("new row", 0);
// add new row below 'old row 2'
table.addRow(newRow, 2);
//merge cells at first column of 'old row 2', 'new row', and 'old row 3'
mergeCellVertically(doc.getTables().get(0), 0, 1, 3);
FileOutputStream fos = new FileOutputStream(outputDocxPath);
doc.write(fos);
fos.close();
}
static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
CTVMerge vmerge = CTVMerge.Factory.newInstance();
if(rowIndex == fromRow){
// The first merged cell is set with RESTART merge value
vmerge.setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
vmerge.setVal(STMerge.CONTINUE);
// and the content should be removed
for (int i = cell.getParagraphs().size(); i > 0; i--) {
cell.removeParagraph(0);
}
cell.addParagraph();
}
// Try getting the TcPr. Not simply setting an new one every time.
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr == null) tcPr = cell.getCTTc().addNewTcPr();
tcPr.setVMerge(vmerge);
}
}
}
But the merge did not work and I got:
In another attempt, I tried to merge based on the table in picture 3 to get the table in picture 2, and it was a success. The only difference between the 2 attempts is that new row was not newly created, but rather read from the docx document, so I believe creating a new row was the reason why merge failed.
So is there a solution for merging newly created rows? I really don't want to split this operation like this: adding rows > saving docx to disk> read docx from disk> merge rows.
The problem you have is not with mergeCellVertically method but with your approach to copy table row. When copying the underlying CTRow and inserting it in CTTbl.TrArray using XWPFTable.addRow it must be fully complete. Later changings are not written in XML. I told that in my answer java Apache POI Word existing table insert row with cell style and formatting already. And I provided a method commitTableRows in my answer Can't change row text in .docx file once row is added to table. This method needs to be called before writing out the document, so the later changes get written in XML.
So because you are copying second row, which is the start of merging, that setting also gets copied. And the later called mergeCellVertically does not take effect. So your newRow remains new start of merging. This is what you get.
So after all changes and before writing out, call commitTableRows.
Complete example:
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
public class WordInsertTableRowAndMerge {
static XWPFTableRow insertNewTableRow(XWPFTableRow sourceTableRow, int pos) throws Exception {
XWPFTable table = sourceTableRow.getTable();
CTRow newCTRrow = CTRow.Factory.parse(sourceTableRow.getCtRow().newInputStream());
XWPFTableRow tableRow = new XWPFTableRow(newCTRrow, table);
table.addRow(tableRow, pos);
return tableRow;
}
static void commitTableRows(XWPFTable table) {
int rowNr = 0;
for (XWPFTableRow tableRow : table.getRows()) {
table.getCTTbl().setTrArray(rowNr++, tableRow.getCtRow());
}
}
static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
for(int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
System.out.println("rowIndex: " + rowIndex);
XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
CTVMerge vmerge = CTVMerge.Factory.newInstance();
if(rowIndex == fromRow){
// The first merged cell is set with RESTART merge value
vmerge.setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
vmerge.setVal(STMerge.CONTINUE);
// and the content should be removed
for (int i = cell.getParagraphs().size(); i > 0; i--) {
cell.removeParagraph(0);
}
cell.addParagraph();
}
// Try getting the TcPr. Not simply setting an new one every time.
CTTcPr tcPr = cell.getCTTc().getTcPr();
if (tcPr == null) tcPr = cell.getCTTc().addNewTcPr();
tcPr.setVMerge(vmerge);
}
}
public static void main(String[] args) throws Exception {
XWPFDocument doc = new XWPFDocument(new FileInputStream("./source.docx"));
XWPFTable table = doc.getTables().get(0);
XWPFTableRow row = table.getRow(1);
XWPFTableRow newRow = insertNewTableRow(row, 2);
XWPFTableCell cell = newRow.getCell(0); if (cell == null) cell = newRow.addNewTableCell();
// not needed because merged to cell above
cell = newRow.getCell(1); if (cell == null) cell = newRow.addNewTableCell();
for (XWPFParagraph paragraph : cell.getParagraphs()) { // only use first text runs in paragraphs
for (int r = paragraph.getRuns().size()-1; r >= 0; r--) {
XWPFRun run = paragraph.getRuns().get(r);
if (r == 0) {
run.setText("new row 1", 0);
} else {
paragraph.removeRun(r);
}
}
}
mergeCellVertically(table, 0, 1, 3);
commitTableRows(table);
FileOutputStream out = new FileOutputStream("./result.docx");
doc.write(out);
out.close();
doc.close();
}
}
Here's a VBA approach you might like to adapt. It inserts a new row between rows 3 & 4:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Tables(1)
.Cell(4, 2).Range.InsertBreak (wdColumnBreak)
.Rows.Add
.Cell(2, 1).Merge MergeTo:=.Cell(4, 1)
.Range.Characters.Last.Next.Delete
.Cell(2, 1).Merge MergeTo:=.Cell(5, 1)
End With
Application.ScreenUpdating = True
End Sub
Related
I have a problem about how to loop data using XWPFTable?
i have some problems;
I am confused why XWPFRun examRun = examInfoRowP.createRun();
(automatically reads the 2nd column, not the 1st column first)
My coding structure, I think it is less efficient in using XWPFTable, how to make it cleaner code
note:
List listLHA is the data
I'm using apache-poi 3.8
XWPFTable Table = document.getTableArray(16);
XWPFTableRow getRow1 = Table.getRow(1);
XWPFTableRow getRow0 = Table.getRow(0);
//baris 1
for(int i = 0; i < listLHA.size(); i++) {
getRow0.getCell(0).setText(listLHA.get(0).getKeyProsses()+ " KEY PROSES");
break;
}
//baris 2
for(int i = 0; i < listLHA.size(); i++) {
getRow1.getCell(0).setText(listLHA.get(0).getRiskRating());
getRow1.getCell(1).setText(listLHA.get(0).getAuditObservationTitle()+ " AO TITLE");
break;
}
XWPFTableRow examInfoRow = Table.createRow();
XWPFTableCell cellRowInfo = examInfoRow.addNewTableCell();
XWPFParagraph examInfoRowP = cellRowInfo.getParagraphs().get(0);
XWPFRun examRun = examInfoRowP.createRun(); //problem 1
examInfoRowP.setAlignment(ParagraphAlignment.LEFT);
//list Action plan
examRun.setText("Action Plan:");
examRun.addBreak();
for (AuditEngagementLHA lha : listLHA) {
int i = listLHA.indexOf(lha);
examRun.setText(i+1 +"."+lha.getDescAP().replaceAll("\\<[^>]*>",""));
examRun.addBreak();
}
for(int i = 0; i < listLHA.size(); i++) {
examRun.setText("Target Date: ");
examRun.setText(listLHA.get(0).getTargetDateAP());
examRun.addBreak();
break;
}
examRun.addBreak();
for(int i = 0; i < listLHA.size(); i++) {
examInfoRow.getCell(0).setText(listLHA.get(0).getDescAO()+" Desc AO");
examRun.addBreak();
break;
}
//List penanggung jawab
examRun.setText("Penanggung Jawab:");
examRun.addBreak();
for (AuditEngagementLHA lha : listLHA) {
int i = listLHA.indexOf(lha);
examRun.setText(i+1 +"."+lha.getPicAP()+" - ");
examRun.setText(lha.getJabatanPicAP());
examRun.addBreak();
}
*my word template is like this
*the result from now is like this
*and the result should be like this and neat
Apache POI is highly in development. Therefore, versions become obsolete very quickly. The version apache poi 3.8 was released in 2012. It is much too old to be used ten years later in 2022.
To your first question:
According to the documentation XWPFTable.createRow creates a new XWPFTableRow object with as many cells as the number of columns defined in that moment.
So in your case XWPFTableRow examInfoRow = Table.createRow(); creates a examInfoRow which has at least two columns already, since the table contains at least two columns already from rows above.
Then XWPFTableCell cellRowInfo = examInfoRow.addNewTableCell(); adds an additional column by adding a new table cell to that row. That's why cellRowInfo will not be in 1st column.
Your second question is too broad to be completely answered here.
What I can tell you is that Word tables are beastly things. They have a underlying table grid defining the columns. And if rows need different column sizes, then setting column spans is necessary, just like in HTML tables too. The table, you show as the wanted result, contains three columns. In first row the first column spans the others. In second row the second column spans the third. In third row the first column spans the second. That's the only way to produce those different columns for each row.
Knowing this one should avoid to mess with inserting and/or adding or creating table cells. If a template is possible, then that template should contain all possible row templates already. Then only copying the rows is necessary instead of messing around with inserting cells.
Complete example:
Template:
Code:
import java.io.FileOutputStream;
import java.io.FileInputStream;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRow;
import java.util.List;
import java.util.ArrayList;
public class WordInsertContentInTable {
static void setText(XWPFTableCell cell, String text) {
String[] lines = text.split("\n");
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
XWPFParagraph paragraph = null;
if (paragraphs.size() > i) paragraph = paragraphs.get(i);
if (paragraph == null) paragraph = cell.addParagraph();
XWPFRun run = null;
if (paragraph.getRuns().size() > 0) run = paragraph.getRuns().get(0);
if (run == null) run = paragraph.createRun();
run.setText(line, 0);
}
for (int i = paragraphs.size()-1; i >= lines.length; i--) {
cell.removeParagraph(i);
}
}
static void insertContentInTable(XWPFTable table, List<POJO> listOfPOJOs) throws Exception {
XWPFTableRow titleRowTemplate = table.getRow(0);
if (titleRowTemplate == null) throw new Exception("Table template does not match: No title row.");
if (titleRowTemplate.getTableCells().size() != 1) throw new Exception("Table template does not match: Wrong title row column count.");
XWPFTableRow subTitleRowTemplate = table.getRow(1);
if (subTitleRowTemplate == null) throw new Exception("Table template does not match: No sub title row.");
if (subTitleRowTemplate.getTableCells().size() != 2) throw new Exception("Table template does not match: Wrong sub title row column count.");
XWPFTableRow contentRowTemplate = table.getRow(2);
if (contentRowTemplate == null) throw new Exception("Table template does not match: No content row.");
if (contentRowTemplate.getTableCells().size() != 2) throw new Exception("Table template does not match: Wrong content row column count.");
XWPFTableRow titleRow = titleRowTemplate;
XWPFTableRow subTitleRow = subTitleRowTemplate;
XWPFTableRow contentRow = contentRowTemplate;
XWPFTableCell cell;
for (int i = 0; i < listOfPOJOs.size(); i++) {
POJO pojo = listOfPOJOs.get(i);
if (i > 0) {
titleRow = new XWPFTableRow((CTRow)titleRowTemplate.getCtRow().copy(), table);
subTitleRow = new XWPFTableRow((CTRow)subTitleRowTemplate.getCtRow().copy(), table);
contentRow = new XWPFTableRow((CTRow)contentRowTemplate.getCtRow().copy(), table);
}
String titleRowText = pojo.getTitleRowText();
cell = titleRow.getCell(0);
setText(cell, titleRowText);
String subTitleRowLeftText = pojo.getSubTitleRowLeftText();
String subTitleRowLeftColor = pojo.getSubTitleRowLeftColor();
String subTitleRowRightText = pojo.getSubTitleRowRightText();
cell = subTitleRow.getCell(0);
setText(cell,subTitleRowLeftText);
cell.setColor(subTitleRowLeftColor);
cell = subTitleRow.getCell(1);
setText(cell,subTitleRowRightText);
String contentRowLeftText = pojo.getContentRowLeftText();
String contentRowRightText = pojo.getContentRowRightText();
cell = contentRow.getCell(0);
setText(cell, contentRowLeftText);
cell = contentRow.getCell(1);
setText(cell, contentRowRightText);
if (i > 0) {
table.addRow(titleRow);
table.addRow(subTitleRow);
table.addRow(contentRow);
}
}
}
public static void main(String[] args) throws Exception {
List<POJO> listOfPOJOs = new ArrayList<POJO>();
listOfPOJOs.add(new POJO("Title row text 1",
"Sub title row left text 1", "FF0000", "Sub title row right text 1\nSub title row right text 1\nSub title row right text 1",
"Content row left text 1\nContent row left text 1\nContent row left text 1",
"Content row right text 1\nContent row right text 1\nContent row right text 1"));
listOfPOJOs.add(new POJO("Title row text 2",
"Sub title row left text 2", "00FF00", "Sub title row right text 2\nSub title row right text 2",
"Content row left text 2\nContent row left text 2",
"Content row right text 2\nContent row right text 2"));
listOfPOJOs.add(new POJO("Title row text 3",
"Sub title row left text 3", "0000FF", "Sub title row right text 3",
"Content row left text 3",
"Content row right text 3"));
XWPFDocument document = new XWPFDocument(new FileInputStream("./WordTenplate.docx"));
XWPFTable table = document.getTableArray(0);
insertContentInTable(table, listOfPOJOs);
FileOutputStream out = new FileOutputStream("./WordResult.docx");
document.write(out);
out.close();
document.close();
}
static class POJO {
private String titleRowText;
private String subTitleRowLeftText;
private String subTitleRowLeftColor;
private String subTitleRowRightText;
private String contentRowLeftText;
private String contentRowRightText;
public POJO ( String titleRowText,
String subTitleRowLeftText,
String subTitleRowLeftColor,
String subTitleRowRightText,
String contentRowLeftText,
String contentRowRightText ) {
this.titleRowText = titleRowText;
this.subTitleRowLeftText = subTitleRowLeftText;
this.subTitleRowLeftColor = subTitleRowLeftColor;
this.subTitleRowRightText = subTitleRowRightText;
this.contentRowLeftText = contentRowLeftText;
this.contentRowRightText = contentRowRightText;
}
public String getTitleRowText() {
return this.titleRowText;
}
public String getSubTitleRowLeftText() {
return this.subTitleRowLeftText;
}
public String getSubTitleRowLeftColor() {
return this.subTitleRowLeftColor;
}
public String getSubTitleRowRightText() {
return this.subTitleRowRightText;
}
public String getContentRowLeftText() {
return this.contentRowLeftText;
}
public String getContentRowRightText() {
return this.contentRowRightText;
}
}
}
Result:
This code is minimized to show the principle. If text formatting is necessary, then the method setText(XWPFTableCell cell, String text) needs to be extended. Text formatting in Word needs XWPFRuns for each text which shall be formatted different. Of course also the POJO needs fields to determine the needed formatting then.
I am trying to create a pivot table using apache poi for normal values it is working fine but if there is null or blank values xlsx file gets repaired and pivot gets removed on opening it.
Here is my code:
static void addRowLabel(XSSFPivotTable pivotTable, XSSFSheet dataSheet, AreaReference areaReference, int column) {
DataFormatter formatter = new DataFormatter(java.util.Locale.US);
//apache poi creates as much fields for each as rows are in the pivot table data range
pivotTable.addRowLabel(column);
java.util.TreeSet<String> uniqueItems = new java.util.TreeSet<String>();
for (int r = areaReference.getFirstCell().getRow()+1; r < areaReference.getLastCell().getRow()+1; r++) {
if (dataSheet.getRow(r).getCell(column) != null && dataSheet.getRow(r).getCell(column).getCellType() != CellType.BLANK) {
uniqueItems.add(formatter.formatCellValue(dataSheet.getRow(r).getCell(column)));
} else {
uniqueItems.add("");
}
}
CTPivotField ctPivotField = pivotTable.getCTPivotTableDefinition().getPivotFields().getPivotFieldArray(column);
int i = 0;
for (String item : uniqueItems) {
//take the items as numbered items
ctPivotField.getItems().getItemArray(i).unsetT();
ctPivotField.getItems().getItemArray(i).setX((long)i);
//build a cache definition which has shared elements for those items
pivotTable.getPivotCacheDefinition().getCTPivotCacheDefinition().getCacheFields().getCacheFieldArray(column).getSharedItems().addNewS().setV(item);
i++;
}
//set pivot field settings
ctPivotField.setOutline(false); // no outline format
ctPivotField.setDefaultSubtotal(false); // no subtotals for this field
if (ctPivotField.getDefaultSubtotal()) i++;
for (int k = ctPivotField.getItems().getItemList().size()-1; k >= i; k--) {
ctPivotField.getItems().removeItem(k);
}
ctPivotField.getItems().setCount(i);
}
This method is used for adding rows and below code is to start execution:
public static void secondway( ) throws IOException {
try (XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream("/opt/source.xlsx"));
FileOutputStream fileout = new FileOutputStream("/opt/ExcelResult.xlsx") ) {
XSSFSheet dataSheet = workbook.getSheetAt(0);
XSSFSheet pivotSheet = workbook.createSheet("Pivot");
int firstRow = dataSheet.getFirstRowNum();
int lastRow = dataSheet.getLastRowNum();
int firstCol = dataSheet.getRow(0).getFirstCellNum();
int lastCol = dataSheet.getRow(0).getLastCellNum();
CellReference topLeft = new CellReference(firstRow, firstCol);
CellReference botRight = new CellReference(lastRow, lastCol - 1);
AreaReference areaReference = new AreaReference(topLeft,botRight, SpreadsheetVersion.EXCEL2007);
XSSFPivotTable pivotTable = pivotSheet.createPivotTable(areaReference, new CellReference("A1"), dataSheet);
addRowLabel(pivotTable, dataSheet, areaReference, 0);
addRowLabel(pivotTable, dataSheet, areaReference, 2);
pivotTable.addColumnLabel(DataConsolidateFunction.SUM, 1, "test");
workbook.write(fileout);
}
}
I am not sure what is wrong am I doing or how to support blank values. Please help.
Input that I am using:
While opening shows below error and removes piivot:
[![enter image description here][3]][3]
The aim of the addRowLabel method, which seems to be from my answer java: How to create a pivot with apache poi?, is to correct apache poi, which creates as much items for each pivot field as rows are in the pivot table data range. But it should be as much items as unique items are in pivot field data column.
To get the unique items per column a java.util.TreeSet is used as this cannot contain duplicate elements.
But Excel pivot table takes the values case insensitive. So 11 KD and 11 kd are the same value for Excel pivot tables. Thats why String.CASE_INSENSITIVE_ORDER needs to be used as Comparator while creating the java.util.TreeSet.
Do changing:
...
java.util.TreeSet<String> uniqueItems = new java.util.TreeSet<String>();
...
into
...
java.util.TreeSet<String> uniqueItems = new java.util.TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
...
and it should work.
I am trying to write out to an existing excel file. I don't want to create new rows or cells, I just want to write out the value from my array into the value at row x column y. Every time I have tried this so far I can only get it to work if I create a new row. Please help!!!
Integer columns = DataImport.columns_in_sheet[0];
Integer rowNum = learnerRow + 2;
try {
FileInputStream inp = new FileInputStream("D:/location/update.xlsx");
XSSFWorkbook wb = null;
wb = (XSSFWorkbook) WorkbookFactory.create(inp);
XSSFSheet sheet = wb.getSheetAt(0);
XSSFRow row = sheet.getRow(18);//places the start row
XSSFCell cell = null;//places the start column
cell = row.getCell(0);
//#########################################################################################
//#########################################################################################
for (int j = 0; j < exportData.length; j++) {
//sheet.createRow(rowNum+j);
//row = sheet.getRow(rowNum+j);
//row = sheet.getRow(rowNum+j);
for (int i=0; i < columns;i++){
cell.setCellType(CellType.STRING);
cell.setCellValue(exportData[j][i]);
}
}
// Write the output to a file
FileOutputStream fileOut = new FileOutputStream("D:/location/update.xlsx");
wb.write(fileOut);
fileOut.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
this code throws a null pointer because of row being null, I can only seem to get rid of the error by creating new rows. I am using XSSF formatting.
The logic of your code snippet is not clear. It looks not logically to me.
But to avoid NPE while using rows and cells from present sheets, one always needs check whether the row or cell was present already or needs to be new created. This is necessary because for not present rows Sheet.getRow will return null. Also Row.getCell will return null for not present cells.
So we can do:
Sheet sheet = ...
Row row = sheet.getRow(rowIdx); if (row == null) row = sheet.createRow(rowIdx);
Cell cell = row.getCell(cellIdx); if (cell == null) cell = row.createCell(cellIdx);
Now row either is a row which was already present or it is a new created row. And cell either is a cell which was already present or it is a new created cell. Neither row nor cell will be null. And at first present rows/cells will be got before they were new created if not present. So present rows and cells will not be destroyed.
The same is needed in loops:
Sheet sheet = ...
Row row;
Cell cell;
for (int rowIdx = 0; rowIdx < 10; rowIdx++) {
row = sheet.getRow(rowIdx); if (row == null) row = sheet.createRow(rowIdx);
for (int cellIdx = 0; cellIdx < 10; cellIdx++) {
cell = row.getCell(cellIdx); if (cell == null) cell = row.createCell(cellIdx);
// do something with cell
}
}
HI I would like to insert a XWPFTable in between some contents. The file is content is fixed and file is taken as input. I need the table to be inserted in the specific field.
like this:
Stack Overflow is a privately held website, the flagship site of the Stack Exchange Network, created in 2008 by Jeff Atwood and Joel Spolsky. Here is the table.
The contents continue.It was created to be a more open alternative to earlier question and answer sites such as Experts-Exchange.
Thanks
The code i have written
public static void main(String[] args) throws IOException {
XWPFDocument document = new XWPFDocument(new FileInputStream(new File("input.docx")));
FileOutputStream out = new FileOutputStream(new File("output.docx"));
XmlCursor cursor = null;
List<IBodyElement> elements = document.getBodyElements();
for (int n = 0; n < elements.size(); n++) {
IBodyElement element = elements.get(n);
if (element instanceof XWPFParagraph) {
XWPFParagraph p1 = (XWPFParagraph) element;
List<XWPFRun> runList = p1.getRuns();
StringBuilder sb = new StringBuilder();
for (XWPFRun run : runList)
sb.append(run.getText(0));
if (sb.toString().contains("Text after which table should be created")) {
cursor= p1.getCTP().newCursor();
break;
}
}
}
XWPFParagraph p = document.insertNewParagraph(cursor);
XWPFTable table = p.getBody().insertNewTbl(cursor);
XWPFTableRow tableRowOne = table.createRow();
//other codes for generating the table
I am getting null pointer exception on creating the row.
Have tested now. My suspicion was right. The cursor was on the wrong place in your code after XWPFParagraph p = document.insertNewParagraph(cursor);. So the XWPFTable table = p.getBody().insertNewTbl(cursor); could not be inserted and was null then.
But there are further problems. If the text was found, we are in the paragraph after which the table shall be placed. So we need moving the cursor to the next paragraph. But what if there is not a next paragraph? Then a new paragraph needs to be created. Fortunately the XmlCursor.toNextSibling flags if it was successful.
Example:
Template:
Code:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSectPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.apache.xmlbeans.XmlCursor;
import java.math.BigInteger;
public class WordInsertTableInBody {
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument(new FileInputStream("WordTableExample.docx"));
XmlCursor cursor = null;
XWPFParagraph paragraph = null;
XWPFRun run = null;
boolean foundTablePosition = false;
boolean thereWasParagraphAfter = false;
for (IBodyElement element : document.getBodyElements()) {
if (element instanceof XWPFParagraph) {
paragraph = (XWPFParagraph) element;
StringBuilder sb = new StringBuilder();
for (XWPFRun irun : paragraph.getRuns()) {
sb.append(irun.getText(0));
System.out.println(sb);
if (sb.toString().contains("Text after which table should be created")) {
cursor= paragraph.getCTP().newCursor();
thereWasParagraphAfter = cursor.toNextSibling(); // move cursor to next paragraph
//because the table shall be **after** that paragraph
//thereWasParagraphAfter is true if there is a next paragraph, else false
foundTablePosition = true;
}
}
}
if (foundTablePosition) break;
}
if (cursor != null) {
if (thereWasParagraphAfter) {
paragraph = document.insertNewParagraph(cursor);
} else {
paragraph = document.createParagraph();
}
cursor = paragraph.getCTP().newCursor();
XWPFTable table = document.insertNewTbl(cursor);
XWPFTableRow row = table.getRow(0); if (row == null) row = table.createRow();
int twipsPerInch = 1440;
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(1*1440));
for (int col = 1 ; col < 4; col++) {
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1*1440));
}
for (int i = 0; i < 4; i++) {
XWPFTableCell cell = row.getCell(i); if (cell == null) cell = row.createCell();
CTTblWidth tblWidth = cell.getCTTc().addNewTcPr().addNewTcW();
tblWidth.setW(BigInteger.valueOf(1 * twipsPerInch));
tblWidth.setType(STTblWidth.DXA);
if (cell.getParagraphs().size() > 0 ) paragraph = cell.getParagraphs().get(0); else paragraph = cell.addParagraph();
run = paragraph.createRun();
run.setText("Table Cell " + i);
}
}
FileOutputStream out = new FileOutputStream("WordTableExampleNew.docx");
document.write(out);
out.close();
document.close();
}
}
Result:
I am not certain that either of the XWPFDocument.insertTable() methods work correctly. One thing I noticed is that you have:
XWPFParagraph p = document.insertNewParagraph(cursor);
XWPFTable table = p.getBody().insertNewTbl(cursor);
which is equivalent to:
XWPFParagraph p = document.insertNewParagraph(cursor);
XWPFTable table = document.insertNewTbl(cursor);
This leaves me wondering if you have some confusion concerning where a table fits inside a document. Paragraphs and Tables are proper siblings. Paragraphs do not contain tables, though Tables can contain Paragraphs (and other tables) in table cells.
The paragraph body is actually the part that contains the paragraph. This can be a document part, or a header/footer part, or a comment part, etc. But, XWPFParagraph.getBody() does not refer to the contents of the paragraph. Rather, it refers to the paragraph's parent part.
I'm using the Apache POi HSSF library to import info into my application. The problem is that the files have some extra/empty rows that need to be removed first before parsing.
There's not a HSSFSheet.removeRow( int rowNum ) method. Only removeRow( HSSFRow row ). The problem with this it that empty rows can't be removed. For example:
sheet.removeRow( sheet.getRow(rowNum) );
gives a NullPointerException on empty rows because getRow() returns null.
Also, as I read on forums, removeRow() only erases the cell contents but the row is still there as an empty row.
Is there a way of removing rows (empty or not) without creating a whole new sheet without the rows that I want to remove?
/**
* Remove a row by its index
* #param sheet a Excel sheet
* #param rowIndex a 0 based index of removing row
*/
public static void removeRow(HSSFSheet sheet, int rowIndex) {
int lastRowNum=sheet.getLastRowNum();
if(rowIndex>=0&&rowIndex<lastRowNum){
sheet.shiftRows(rowIndex+1,lastRowNum, -1);
}
if(rowIndex==lastRowNum){
HSSFRow removingRow=sheet.getRow(rowIndex);
if(removingRow!=null){
sheet.removeRow(removingRow);
}
}
}
I know, this is a 3 year old question, but I had to solve the same problem recently, and I had to do it in C#. And here is the function I'm using with NPOI, .Net 4.0
public static void DeleteRow(this ISheet sheet, IRow row)
{
sheet.RemoveRow(row); // this only deletes all the cell values
int rowIndex = row.RowNum;
int lastRowNum = sheet.LastRowNum;
if (rowIndex >= 0 && rowIndex < lastRowNum)
{
sheet.ShiftRows(rowIndex + 1, lastRowNum, -1);
}
}
Something along the lines of
int newrownum=0;
for (int i=0; i<=sheet.getLastRowNum(); i++) {
HSSFRow row=sheet.getRow(i);
if (row) row.setRowNum(newrownum++);
}
should do the trick.
The HSSFRow has a method called setRowNum(int rowIndex).
When you have to "delete" a row, you put that index in a List. Then, when you get to the next row non-empty, you take an index from that list and set it calling setRowNum(), and remove the index from that list. (Or you can use a queue)
My special case (it worked for me):
//Various times to delete all the rows without units
for (int j=0;j<7;j++) {
//Follow all the rows to delete lines without units (and look for the TOTAL row)
for (int i=1;i<sheet.getLastRowNum();i++) {
//Starting on the 2nd row, ignoring first one
row = sheet.getRow(i);
cell = row.getCell(garMACode);
if (cell != null)
{
//Ignore empty rows (they have a "." on first column)
if (cell.getStringCellValue().compareTo(".") != 0) {
if (cell.getStringCellValue().compareTo("TOTAL") == 0) {
cell = row.getCell(garMAUnits+1);
cell.setCellType(HSSFCell.CELL_TYPE_FORMULA);
cell.setCellFormula("SUM(BB1" + ":BB" + (i - 1) + ")");
} else {
cell = row.getCell(garMAUnits);
if (cell != null) {
int valor = (int)(cell.getNumericCellValue());
if (valor == 0 ) {
//sheet.removeRow(row);
removeRow(sheet,i);
}
}
}
}
}
}
}
This answer is an extension over AndreAY's answer, Giving you complete function on deleting a row.
public boolean deleteRow(String sheetName, String excelPath, int rowNo) throws IOException {
XSSFWorkbook workbook = null;
XSSFSheet sheet = null;
try {
FileInputStream file = new FileInputStream(new File(excelPath));
workbook = new XSSFWorkbook(file);
sheet = workbook.getSheet(sheetName);
if (sheet == null) {
return false;
}
int lastRowNum = sheet.getLastRowNum();
if (rowNo >= 0 && rowNo < lastRowNum) {
sheet.shiftRows(rowNo + 1, lastRowNum, -1);
}
if (rowNo == lastRowNum) {
XSSFRow removingRow=sheet.getRow(rowNo);
if(removingRow != null) {
sheet.removeRow(removingRow);
}
}
file.close();
FileOutputStream outFile = new FileOutputStream(new File(excelPath));
workbook.write(outFile);
outFile.close();
} catch(Exception e) {
throw e;
} finally {
if(workbook != null)
workbook.close();
}
return false;
}
I'm trying to reach back into the depths of my brain for my POI-related experience from a year or two ago, but my first question would be: why do the rows need to be removed before parsing? Why don't you just catch the null result from the sheet.getRow(rowNum) call and move on?