XWPFTable create new row (looping) - java

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.

Related

Apply Conditional Formatting to Entire Row Specific Only to That Row

I am trying to create Conditional Formatting that has multiple different rules based off different values, that will only apply to the row that the value matches in. Here is the code I have so far for some context.
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sheet = wb.createSheet("new sheet");
// Create a row and put some cells in it. Rows are 0 based.
XSSFRow row1 = sheet.createRow(0);
// Create a cell
row1.createCell(0).setCellValue("Lot Type");
row1.createCell(1).setCellValue("Lot Size");
row1.createCell(2).setCellValue("Square Footage");
row1.createCell(3).setCellValue("Heating/Cooling");
row1.createCell(4).setCellValue("Extras");
XSSFRow row2 = sheet.createRow(1);
row2.createCell(0).setCellValue("Residential");
row2.createCell(1).setCellValue("8000");
row2.createCell(2).setCellValue("1200");
row2.createCell(3).setCellValue("Yes");
row2.createCell(4).setCellValue("None");
XSSFRow row3 = sheet.createRow(1);
row3.createCell(0).setCellValue("Industrial");
row3.createCell(1).setCellValue("12000");
row3.createCell(2).setCellValue("8000");
row3.createCell(3).setCellValue("");
row3.createCell(4).setCellValue("");
// Set conditional rules
SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
ConditionalFormattingRule rule = sheetCF.createConditionalFormattingRule("$A2 = " + "\"" + "Residential" + "\"");
PatternFormatting fill = rule.createPatternFormatting();
fill.setFillBackgroundColor(IndexedColors.YELLOW.index);
fill.setFillPattern(PatternFormatting.SOLID_FOREGROUND);
CellRangeAddress[] regions = new CellRangeAddress[]{CellRangeAddress.valueOf("C2"), CellRangeAddress.valueOf("E2")};
ConditionalFormattingRule[] cfRules = new ConditionalFormattingRule[]{rule};
sheetCF.addConditionalFormatting(regions,cfRules);
// Write the output to a file
return wb;
So this works, but only if Row 2 A column has the value "Residential" entered. What I want to happen, is "Lot Type" will have a drop down list with different options ('Residential', 'Industrial', 'Agriculture', etc) and each "Lot Type" would have different "required" fields, and I don't know what the user will select per row for each row.
The possible options, both in terms of rows and possible drop downs choices, is large. At the moment, 10000 rows are generated each with 40+ drop down options. That isn't specifically necessary information, but what it does mean is that simply looping through i < 10000 and just incrementing the row index; "A#", "C#", etc, and then with each index looping through all my drop down option rules, is not a viable solution.
If that is the only option, so be it, but I was hoping there was a way I could do something with Excel equations or conditional formatting where I can connect the different "Lot Types" to their required columns, with the numbered index of the row dynamically used within the equation itself. Which means I would be able to just set the conditional formatting once on the possible rows, and not have to run through some massive double loop.
Something like the following, which I have tried and it did not work:
ConditionalFormattingRule rule = sheetCF.createConditionalFormattingRule("$A$ = " + "\"" + "Residential" + "\"");
PatternFormatting fill = rule.createPatternFormatting();
fill.setFillBackgroundColor(IndexedColors.YELLOW.index);
fill.setFillPattern(PatternFormatting.SOLID_FOREGROUND);
CellRangeAddress[] regions = new CellRangeAddress[]{CellRangeAddress.valueOf("C$"), CellRangeAddress.valueOf("E$")};
How I would envision this to work in words would be that what ever row index has the value "Residential" inputted into the A cell, it would cause that respective row's, and only that row's, C and E cells to be highlighted
Thanks in advance for any help!
Excel is not really good at pointing the user to the required cells to fill in. As long as always the same columns needs to be filled, either data validation or conditional formatting offer themselves. But if the mandatory columns are conditional by them self, then this leads very fast to thousands of different data validation or conditional formatting rules. And this is not practicable as you mentioned already.
But the question you asked was about a conditional formula rule which gets adjusted to different rows automatically. And this is possible.
Conditional formula rules, as well as default formulas, respect the $ mark in cell references to differentiate between fixed and variable cell references. So if a formula rule =($A2="Residential") gets applied to the range A2:E1000, then the row reference to row 2 gets adjusted in other rows because it is variable and not fixed by $. So it checks =($A2="Residential") in row 2, =($A3="Residential") in row 3, =($A4="Residential") in row 4 and so on.
But of course the conditional formatting gets applied to all the columns in the given range A2:E1000. So if the rule matches in a row, all columns A:E get formatted. And this is the problem. If only the required columns shall be highlighted, then there would must be multiple different cell ranges where the conditional formatting gets applied. And this leads to your mentioned thousands of possibilities in your case.
One solution, I can think of, would be having different formula rules per identifier (Lot Type in your case) which may have different rules for required columns per identifier. And as long one of those rules is violated, then the whole row gets highlighted. This would be solvable having as much conditional formatting rules as different identifiers are, all applied to the whole data range.
Complete example:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.io.FileOutputStream;
public class ConditionalFormattingRulesAndRanges {
public static void main(String[] args) throws Exception {
Object[][] data = new Object[][] {
new Object[] {"Lot Type", "Lot Size", "Square Footage", "Heating/Cooling", "Extras"},
new Object[] {"Residential", 8000, 1200, "Yes", "None"},
new Object[] {"Industrial", 12000, 8000, "", ""},
new Object[] {"Agriculture", 4000, 900, "No", "Much"},
new Object[] {"Industrial", 10000, "", "Yes", ""},
new Object[] {"Residential", 8500, 1000, "", "None"},
new Object[] {"Agriculture", 9000, "", "", ""}
};
int dataRowCount = data.length;
int columnCount = data[0].length;
String[] identifiers = new String[] {"Residential", "Industrial", "Agriculture"};
String[] requiredColumnsRules = new String[] {"OR($C2=\"\",$D2=\"\",$E2=\"\")", "OR($C2=\"\",$D2=\"\")", "$C2=\"\""};
int identifiersCount = identifiers.length;
Workbook workbook = new XSSFWorkbook(); String filePath ="./ConditionalFormattingRulesAndRanges.xlsx";
int lastRow = workbook.getSpreadsheetVersion().getLastRowIndex();
Sheet sheet = workbook.createSheet();
int r = 0;
for (Object[] rowData : data) {
Row row = sheet.createRow(r);
int c = 0;
for (Object cellValue : rowData) {
Cell cell = row.createCell(c);
if (cellValue instanceof Number) {
cell.setCellValue(((Number)cellValue).doubleValue());
} else {
cell.setCellValue(String.valueOf(cellValue));
}
c++;
}
r++;
}
for (int i = 0; i < columnCount; i++) {
sheet.autoSizeColumn(i);
}
sheet.createFreezePane(1,1,1,1);
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
DataValidationConstraint dvConstraint = dvHelper.createExplicitListConstraint(identifiers) ;
CellRangeAddressList addressList = new CellRangeAddressList(1, lastRow, 0, 0);
DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
validation.createPromptBox("Hint", "If the line is highlighted in yellow, mandatory fields are not filled out.");
if (workbook instanceof XSSFWorkbook) validation.setShowPromptBox(true);
sheet.addValidationData(validation);
SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
ConditionalFormattingRule[] cfRules = new ConditionalFormattingRule[identifiersCount];
for (int i = 0; i < identifiersCount; i++) {
String identifier = identifiers[i];
String requiredColumnsRule = requiredColumnsRules[i];
String formulaRule = "AND($A2 = " + "\"" + identifier + "\"," + requiredColumnsRule + ")"; // rule will be applied from row 2 on; Row reference is not fixed, so it will be adjusted
ConditionalFormattingRule rule = sheetCF.createConditionalFormattingRule(formulaRule);
PatternFormatting patternFmt = rule.createPatternFormatting();
patternFmt.setFillBackgroundColor(IndexedColors.YELLOW.index);
cfRules[i] = rule;
}
CellRangeAddress[] dataRange = new CellRangeAddress[]{CellRangeAddress.valueOf("A2:E" + lastRow)}; // rules applied to the whole possible data range
sheetCF.addConditionalFormatting(dataRange, cfRules);
FileOutputStream out = new FileOutputStream(filePath);
workbook.write(out);
out.close();
workbook.close();
}
}
Another solution would be having one conditional formatting rule per mandatory column. That rule would must define per formula for which identifiers this column is a mandatory column. This needs one complete conditional formatting for each mandatory column.
Complete example for this:
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import java.io.FileOutputStream;
public class ConditionalFormattingRulesAndRanges2 {
public static void main(String[] args) throws Exception {
Object[][] data = new Object[][] {
new Object[] {"Lot Type", "Lot Size", "Square Footage", "Heating/Cooling", "Extras"},
new Object[] {"Residential", 8000, 1200, "Yes", "None"},
new Object[] {"Industrial", 12000, 8000, "", ""},
new Object[] {"Agriculture", 4000, 900, "No", "Much"},
new Object[] {"Industrial", 10000, "", "Yes", ""},
new Object[] {"Residential", 8500, 1000, "", "None"},
new Object[] {"Agriculture", 9000, "", "", ""}
};
int dataRowCount = data.length;
int columnCount = data[0].length;
String[] identifiers = new String[] {"Residential", "Industrial", "Agriculture"};
String[] rules = new String[] { // rule will be applied from row 2 on; Row reference is not fixed, so it will be adjusted
"AND($C2=\"\",OR($A2=\"Residential\",$A2=\"Industrial\",$A2=\"Agriculture\"))", // column C needs to be filled for all identifiers
"AND($D2=\"\",OR($A2=\"Residential\",$A2=\"Industrial\"))", // column D needs to be filled for Residential and Industrial only
"AND($E2=\"\",OR($A2=\"Residential\"))" // column E needs to be filled for Residential only
};
int rulesCount = rules.length;
Workbook workbook = new XSSFWorkbook(); String filePath ="./ConditionalFormattingRulesAndRanges.xlsx";
int lastRow = workbook.getSpreadsheetVersion().getLastRowIndex();
CellRangeAddress[][] rulesAppliedTo = new CellRangeAddress[][] {
new CellRangeAddress[]{CellRangeAddress.valueOf("C2:C" + lastRow)},
new CellRangeAddress[]{CellRangeAddress.valueOf("D2:D" + lastRow)},
new CellRangeAddress[]{CellRangeAddress.valueOf("E2:E" + lastRow)}
};
Sheet sheet = workbook.createSheet();
int r = 0;
for (Object[] rowData : data) {
Row row = sheet.createRow(r);
int c = 0;
for (Object cellValue : rowData) {
Cell cell = row.createCell(c);
if (cellValue instanceof Number) {
cell.setCellValue(((Number)cellValue).doubleValue());
} else {
cell.setCellValue(String.valueOf(cellValue));
}
c++;
}
r++;
}
for (int i = 0; i < columnCount; i++) {
sheet.autoSizeColumn(i);
}
sheet.createFreezePane(1,1,1,1);
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
DataValidationConstraint dvConstraint = dvHelper.createExplicitListConstraint(identifiers) ;
CellRangeAddressList addressList = new CellRangeAddressList(1, lastRow, 0, 0);
DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
validation.createPromptBox("Hint", "If a cell is highlighted in yellow, then this is a mandatory.");
if (workbook instanceof XSSFWorkbook) validation.setShowPromptBox(true);
sheet.addValidationData(validation);
SheetConditionalFormatting sheetCF = sheet.getSheetConditionalFormatting();
for (int i = 0; i < rulesCount; i++) {
String formulaRule = rules[i];
ConditionalFormattingRule rule = sheetCF.createConditionalFormattingRule(formulaRule);
PatternFormatting patternFmt = rule.createPatternFormatting();
patternFmt.setFillBackgroundColor(IndexedColors.YELLOW.index);
ConditionalFormattingRule[] cfRules = new ConditionalFormattingRule[]{rule};
CellRangeAddress[] appliedTo = rulesAppliedTo[i];
sheetCF.addConditionalFormatting(appliedTo, cfRules);
}
FileOutputStream out = new FileOutputStream(filePath);
workbook.write(out);
out.close();
workbook.close();
}
}

POI Word Unable to merge newly created cell vertically

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

Inserting XWPFTable in between contents

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.

Blank cells in spreadsheet no longer blank when saved to desktop

I have a java application that exports some data to a spreadsheet using POI. The problem is that sometimes (not always) there are a couple of lines that have no information (they are "blank") but when I save the XLS document to my desktop and open it, the cells are filled with the correct information.
Im using POI 3.7.
Down here you have an example of what's happening.
[EDIT] - I add some code:
This is the method that insert the data into the sheet:
public void setTransactionDetailsData(HSSFSheet sheet, String[][] records){
HSSFRow rowContent = null;
HSSFCell cellContent = null;
String cell = "";
Integer recordNumber = records.length;
Integer columnNumber = records[0].length;
Integer nextDataIndex = 0;
String styleText = "";
boolean customStyles = styles.size()>0;
short alignment;
CellStyle cellStyle = null;
for(int i = 0; i<recordNumber;i++){
//New row
rowContent = sheet.createRow(lastRowCreated);
rowContent.setHeightInPoints((customDataCellHeight * sheet.getDefaultRowHeightInPoints()));
for(int j = 0; j<columnNumber;j++){
cellContent = null;
cellContent = rowContent.createCell((nextDataIndex));
cell = records[i][j];
if(customStyles){
try{
styleText = styles.get(nextDataIndex);
alignment = getAlignment(styleText);
}catch(IndexOutOfBoundsException e){
LOG.info("Error while obtaining style for position " +nextDataIndex+ ", max index is " +(styles.size()-1)+ ". Check the list of styles you send");
styleText = "";
alignment = 1;
}
}else{
alignment = 1;
}
cellStyle = getCellStyle(i,Integer.valueOf(alignment),styleText);
//Text
if("".equals(styleText) || "value_left".equals(styleText) || "value".equalsIgnoreCase(styleText)){
cellContent.setCellValue(cell);
}else{
if(isNumeric(cell)){
try{
cellContent.setCellValue(convertoToParseableNumber(cell));
cellContent.setCellType(0);
}catch(NumberFormatException e){
LOG.info("Error ocurred while parsing value " +cell+ " no number");
cellContent.setCellValue(cell);
}
}else{
cellContent.setCellValue(cell);
}
}
cellContent.setCellStyle(cellStyle);
nextDataIndex++;
}
//Back to first column
nextDataIndex = 0;
lastRowCreated++;
}
autosize(sheet);
}
Thank you in advance.

Reading superscript excel text with Apache POI in Java

I have an Excel worksheet with some Text cells that contains a superscript and an hyperlink. The hyperlink is simple to extract but i could't extract the superscript :/, the program detects it like plain text. "8^2 --> 82".
The code
excel = WorkbookFactory.create(new File("filename.xlsx"));
Sheet hoja = excel.getSheetAt(4);
List<String> datos = new ArrayList<String>();
List<String> links = new ArrayList<String>();
//recorrido
Iterator<Row> filas = hoja.rowIterator();
while (filas.hasNext()) {
Row fila = filas.next();
Iterator<Cell> celdas = fila.cellIterator();
while (celdas.hasNext()) {
Cell celda = celdas.next();
System.out.print(celda.toString() + " || ");
datos.add(String.valueOf(celda));
Hyperlink linkAddress = celda.getHyperlink();
if (linkAddress != null) {
links.add(linkAddress.getAddress());
}
}
System.out.println();
}
The code shows how i'm whatching the cell contain, just the code probe.
I'm using Apache POI 3.14.
I solved this problem using some RichTextString properties. I created two methods to extract the value and the superscript of the Cell's RichTextString.
To obtain the value we may need to iterate and to concatenate all the String content, except the last one. The last one is always the full superscript.
private static String getValue(XSSFRichTextString cellContent){
String value = "";
for (int i = 0; i < cellContent.numFormattingRuns() - 1; i++) {
int lenVal = cellContent.getLengthOfFormattingRun(i);
int iVal = cellContent.getIndexOfFormattingRun(i);
value += cellContent.toString().substring(iVal, lenVal + iVal);
}
return value;
}
private static String getSuperScript(XSSFRichTextString cellContent) {
int lenSuper = cellContent.getLengthOfFormattingRun(cellContent.numFormattingRuns() - 1);
int iSuper = cellContent.getIndexOfFormattingRun(cellContent.numFormattingRuns() - 1);
return cellContent.toString().substring(iSuper, lenSuper + iSuper);
}
It obtains from 8^1 --> value=8, superScript=1 for example.
Or from "superscript example ^ A,B" --> value="superscript example", superScript="A,B".

Categories

Resources