Inserting XWPFTable in between contents - java

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.

Related

XWPFTable create new row (looping)

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.

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

The value "name" and "surname" aren't read apache poi

My purpose is to read a file docx and take this text "#name#" and "#surname#" and change the value with another casual text:
This is my docx file:
I do this:
XWPFDocument docx = new XWPFDocument(OPCPackage.open("..."));
for (XWPFParagraph p : docx.getParagraphs()) {
List<XWPFRun> runs = p.getRuns();
if (runs != null) {
for (XWPFRun r : runs) {
String text = r.getText(0);
if (text != null && text.startsWith("#") && text.endsWith("#")) {
text = text.replace("#", "new ");
r.setText(text, 0);
}
}
}
}
for (XWPFTable tbl : docx.getTables()) {
for (XWPFTableRow row : tbl.getRows()) {
for (XWPFTableCell cell : row.getTableCells()) {
for (XWPFParagraph p : cell.getParagraphs()) {
for (XWPFRun r : p.getRuns()) {
String text = r.getText(0);
if (text != null && text.startsWith("#") && text.endsWith("#")) {
text = text.replace("#", "new ");
r.setText(text,0);
}
}
}
}
}
the problem is that my code reads all label in docx file but it doesn't read the label "#surname#" and "#name". Anyone can help me?
From your screenshot it looks like the "#name#" and "#suremane#" are not in the document body directly but in a drawing (a text-box for example or a shape). Such elements are not covered by XWPFDocument.getParagraphs or .getTables or any other high level method in apache poi. So your main problem will be that the paragraphs which contain your text simply are not traversed by your code.
The only way to get really all paragraphs out of the documents body is using a XmlCursor which selects all w:p elements from the XML directly.
The code below shows that. It traverses really all XWPFParagraphs in documents body using a XmlCursor and replaces text if found.
For the replacement process I prefer the TextSegment replacement approach shown in Apache POI: ${my_placeholder} is treated as three different runs already. This is necessary because, even if the containing paragraph gets traversed, the text could be separated in different text runs because of formatting, spell checking or any other strange reasons. Microsoft Word knows nearly infinity reasons to strangely split text into different text runs.
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlCursor;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
public class WordReplaceTextSegment {
/**
* this methods parse the paragraph and search for the string searched.
* If it finds the string, it will return true and the position of the String
* will be saved in the parameter startPos.
*
* #param searched
* #param startPos
*/
static TextSegment searchText(XWPFParagraph paragraph, String searched, PositionInParagraph startPos) {
int startRun = startPos.getRun(),
startText = startPos.getText(),
startChar = startPos.getChar();
int beginRunPos = 0, candCharPos = 0;
boolean newList = false;
//CTR[] rArray = paragraph.getRArray(); //This does not contain all runs. It lacks hyperlink runs for ex.
java.util.List<XWPFRun> runs = paragraph.getRuns();
int beginTextPos = 0, beginCharPos = 0; //must be outside the for loop
//for (int runPos = startRun; runPos < rArray.length; runPos++) {
for (int runPos = startRun; runPos < runs.size(); runPos++) {
//int beginTextPos = 0, beginCharPos = 0, textPos = 0, charPos; //int beginTextPos = 0, beginCharPos = 0 must be outside the for loop
int textPos = 0, charPos;
//CTR ctRun = rArray[runPos];
CTR ctRun = runs.get(runPos).getCTR();
XmlCursor c = ctRun.newCursor();
c.selectPath("./*");
try {
while (c.toNextSelection()) {
XmlObject o = c.getObject();
if (o instanceof CTText) {
if (textPos >= startText) {
String candidate = ((CTText) o).getStringValue();
if (runPos == startRun) {
charPos = startChar;
} else {
charPos = 0;
}
for (; charPos < candidate.length(); charPos++) {
if ((candidate.charAt(charPos) == searched.charAt(0)) && (candCharPos == 0)) {
beginTextPos = textPos;
beginCharPos = charPos;
beginRunPos = runPos;
newList = true;
}
if (candidate.charAt(charPos) == searched.charAt(candCharPos)) {
if (candCharPos + 1 < searched.length()) {
candCharPos++;
} else if (newList) {
TextSegment segment = new TextSegment();
segment.setBeginRun(beginRunPos);
segment.setBeginText(beginTextPos);
segment.setBeginChar(beginCharPos);
segment.setEndRun(runPos);
segment.setEndText(textPos);
segment.setEndChar(charPos);
return segment;
}
} else {
candCharPos = 0;
}
}
}
textPos++;
} else if (o instanceof CTProofErr) {
c.removeXml();
} else if (o instanceof CTRPr) {
//do nothing
} else {
candCharPos = 0;
}
}
} finally {
c.dispose();
}
}
return null;
}
static void replaceTextSegment(XWPFParagraph paragraph, String textToFind, String replacement) {
TextSegment foundTextSegment = null;
PositionInParagraph startPos = new PositionInParagraph(0, 0, 0);
//while((foundTextSegment = paragraph.searchText(textToFind, startPos)) != null) { // search all text segments having text to find
while((foundTextSegment = searchText(paragraph, textToFind, startPos)) != null) { // search all text segments having text to find
System.out.println(foundTextSegment.getBeginRun()+":"+foundTextSegment.getBeginText()+":"+foundTextSegment.getBeginChar());
System.out.println(foundTextSegment.getEndRun()+":"+foundTextSegment.getEndText()+":"+foundTextSegment.getEndChar());
// maybe there is text before textToFind in begin run
XWPFRun beginRun = paragraph.getRuns().get(foundTextSegment.getBeginRun());
String textInBeginRun = beginRun.getText(foundTextSegment.getBeginText());
String textBefore = textInBeginRun.substring(0, foundTextSegment.getBeginChar()); // we only need the text before
// maybe there is text after textToFind in end run
XWPFRun endRun = paragraph.getRuns().get(foundTextSegment.getEndRun());
String textInEndRun = endRun.getText(foundTextSegment.getEndText());
String textAfter = textInEndRun.substring(foundTextSegment.getEndChar() + 1); // we only need the text after
if (foundTextSegment.getEndRun() == foundTextSegment.getBeginRun()) {
textInBeginRun = textBefore + replacement + textAfter; // if we have only one run, we need the text before, then the replacement, then the text after in that run
} else {
textInBeginRun = textBefore + replacement; // else we need the text before followed by the replacement in begin run
endRun.setText(textAfter, foundTextSegment.getEndText()); // and the text after in end run
}
beginRun.setText(textInBeginRun, foundTextSegment.getBeginText());
// runs between begin run and end run needs to be removed
for (int runBetween = foundTextSegment.getEndRun() - 1; runBetween > foundTextSegment.getBeginRun(); runBetween--) {
paragraph.removeRun(runBetween); // remove not needed runs
}
}
}
static List<XmlObject> getCTPObjects(XWPFDocument doc) {
List<XmlObject> result = new ArrayList<XmlObject>();
//create cursor selecting all paragraph elements
XmlCursor cursor = doc.getDocument().newCursor();
cursor.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' .//*/w:p");
while(cursor.hasNextSelection()) {
cursor.toNextSelection();
XmlObject obj = cursor.getObject();
// add only if the paragraph contains at least a run containing text
if (obj.selectPath("declare namespace w='http://schemas.openxmlformats.org/wordprocessingml/2006/main' ./w:r/w:t").length > 0) {
result.add(obj);
}
}
return result;
}
static void traverseAllParagraphsAndReplace(XWPFDocument doc, Map<String, String> replacements) throws Exception {
//This gets all XWPFParagraph out od the stored XML and replaces
//first get all CTP objects
List<XmlObject> allCTPObjects = getCTPObjects(doc);
//then traverse them and create XWPFParagraphs from them and do the replacing
for (XmlObject obj : allCTPObjects) {
XWPFParagraph paragraph = null;
if (obj instanceof CTP) {
CTP p = (CTP)obj;
paragraph = new XWPFParagraph(p, doc);
} else {
CTP p = CTP.Factory.parse(obj.xmlText());
paragraph = new XWPFParagraph(p, doc);
}
if (paragraph != null) {
for (String textToFind : replacements.keySet()) {
String replacement = replacements.get(textToFind);
if (paragraph.getText().contains(textToFind)) replaceTextSegment(paragraph, textToFind, replacement);
}
}
obj.set(paragraph.getCTP());
}
}
public static void main(String[] args) throws Exception {
XWPFDocument doc = new XWPFDocument(new FileInputStream("source.docx"));
Map<String, String> replacements;
replacements = new HashMap<String, String>();
replacements.put("#name#", "Axel");
replacements.put("#surename#", "Richter");
traverseAllParagraphsAndReplace(doc, replacements);
FileOutputStream out = new FileOutputStream("result.docx");
doc.write(out);
out.close();
doc.close();
}
}

Insert image in a table's cell using Apache POI's xwpfd

I am working on a program that requires writing results to a .docx in a table format. I need to write images from a folder in some of the table cells, which I have been unable to figure out how to do. The solutions I have seen are to insert the image in the document.
Student student = new Student ();
XWPFDocument Document = new XWPFDocument ();
XWPFTable table = document.createTable(20,2);
XWPFTableRow rows =null;
XWPFTableCell cell = null;
for (int i=0; i<20; i++) {
rows = table.getRow(i)
student = studentList.get(i);
//first column
cell = rows.getCell(0);
//add image to this cell. The path to the image can be gotten from student.getImagePath and the image itself in BufferedImage can be gotten from student.getImage
//second column
cell = rows.getCell(1);
cell.setText(student.getDetails);
}
I was able to sort it out. Below is the working code:
Student student = new Student ();
String [] details = {"Detail1: ", "Detail2: ", "Detail3: ", "Detail4: ", "Detail5: "};
FileInputStream fis;
String imageName;
int index;
File file;
XWPFDocument document = new XWPFDocument(); //create table
XWPFTable table;
XWPFParagraph paragraph;
XWPFRun run;
XWPFTableRow rows = null;
XWPFTableCell cell = null;
//Write Hall Name at the top of the word document
paragraph = document.createParagraph();
run = paragraph.createRun();
run.setUnderline(UnderlinePatterns.WORDS);
run.setText("Hall Name: " + hallName);
run.addBreak();
table = document.createTable(rw, cl);
table.getCTTbl().getTblPr().unsetTblBorders();
for (int i=0; i<rw; i++) {
rows = table.getRow(i);
student = studentList.get(i);
//First column
cell = rows.getCell(0);
paragraph = cell.addParagraph();
run = paragraph.createRun();
if (student.getImagePath() == null) {
run.setText("Image Unavailable");
}
else {
fis = new FileInputStream(student.getImagePath());
index = student.getImagePath().lastIndexOf('\\') + 1;
imageName = student.getImagePath().substring(index);
run.addPicture(fis, XWPFDocument.PICTURE_TYPE_JPEG, imageName, Units.toEMU(100), Units.toEMU(100));
}
//Second column
cell = rows.getCell(1);
paragraph = cell.addParagraph();
run = paragraph.createRun();
run.setText(details[0] + student.getRegNumber());
run.addBreak();
run.setText(details[1] + student.getName());
run.addBreak();
run.setText(details[2] + student.getExamNumber());
run.addBreak();
run.setText(details[3] + student.getTableNumber());
run.addBreak();
run.setText(details[4] + student.getLevel());
}

Replace table column value in Apache POI

I am using apache POI 3.7. I am trying to replace the value of a table column in a word document (docx). However, what I have done is it keeps appending the value of the current value in the document. But if a table column value is null, it places the value. Can you give me some thoughts how to resolve this. Below is the code I have done so far.
Thanks in advance.
package test.doc;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
public class POIDocXTableTest {
public static void main(String[] args)throws IOException {
String fileName = "C:\\Test.docx";
InputStream fis = new FileInputStream(fileName);
XWPFDocument document = new XWPFDocument(fis);
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (int x=0; x<paragraphs.size();x++)
{
XWPFParagraph paragraph = paragraphs.get(x);
System.out.println(paragraph.getParagraphText());
}
List<XWPFTable> tables = document.getTables();
for (int x=0; x<tables.size();x++)
{
XWPFTable table = tables.get(x);
List<XWPFTableRow> tableRows = table.getRows();
tableRows.remove(x);
for (int r=0; r<tableRows.size();r++)
{
System.out.println("Row "+ (r+1)+ ":");
XWPFTableRow tableRow = tableRows.get(r);
List<XWPFTableCell> tableCells = tableRow.getTableCells();
for (int c=0; c<tableCells.size();c++)
{
System.out.print("Column "+ (c+1)+ ": ");
XWPFTableCell tableCell = tableCells.get(c);
//tableCell.setText("TAE");
String tableCellVal = tableCell.getText();
if ((c+1)==2){
if (tableCellVal!=null){
if (tableCellVal.length()>0){
char c1 = tableCellVal.charAt(0);
String s2 = "-TEST";
char c2 = s2.charAt(0);
String test = tableCell.getText().replace(tableCellVal,s2);
tableCell.setText(test);
}else{
//tableCell.setText("NULL");
}
}
}
System.out.println("tableCell.getText(" + (c) + "):" + tableCellVal);
}
}
System.out.println("\n");
}
OutputStream out = new FileOutputStream(fileName);
document.write(out);
out.close();
}
}
The best solution to prevent styles in paragraphs and find search strings with different styles is this method:
private long replaceInParagraphs(Map<String, String> replacements, List<XWPFParagraph> xwpfParagraphs) {
long count = 0;
for (XWPFParagraph paragraph : xwpfParagraphs) {
List<XWPFRun> runs = paragraph.getRuns();
for (Map.Entry<String, String> replPair : replacements.entrySet()) {
String find = replPair.getKey();
String repl = replPair.getValue();
TextSegement found = paragraph.searchText(find, new PositionInParagraph());
if ( found != null ) {
count++;
if ( found.getBeginRun() == found.getEndRun() ) {
// whole search string is in one Run
XWPFRun run = runs.get(found.getBeginRun());
String runText = run.getText(run.getTextPosition());
String replaced = runText.replace(find, repl);
run.setText(replaced, 0);
} else {
// The search string spans over more than one Run
// Put the Strings together
StringBuilder b = new StringBuilder();
for (int runPos = found.getBeginRun(); runPos <= found.getEndRun(); runPos++) {
XWPFRun run = runs.get(runPos);
b.append(run.getText(run.getTextPosition()));
}
String connectedRuns = b.toString();
String replaced = connectedRuns.replace(find, repl);
// The first Run receives the replaced String of all connected Runs
XWPFRun partOne = runs.get(found.getBeginRun());
partOne.setText(replaced, 0);
// Removing the text in the other Runs.
for (int runPos = found.getBeginRun()+1; runPos <= found.getEndRun(); runPos++) {
XWPFRun partNext = runs.get(runPos);
partNext.setText("", 0);
}
}
}
}
}
return count;
}
This method works with search strings spanning over more than one Run. The replaced part gets the style from the first found Run.
well, I have done something like that, to replace marks in a word template by specified words...:
public DotxTemplateFiller() {
String filename = "/poi/ls_Template_modern_de.dotx";
String outputPath = "/poi/output/output" + new Date().getTime()
+ ".dotx";
OutputStream out = null;
try {
File file = new File(filename);
XWPFDocument template = new XWPFDocument(new FileInputStream(file));
List<XWPFParagraph> xwpfParagraphs = template.getParagraphs();
replaceInParagraphs(xwpfParagraphs);
List<XWPFTable> tables = template.getTables();
for (XWPFTable xwpfTable : tables) {
List<XWPFTableRow> tableRows = xwpfTable.getRows();
for (XWPFTableRow xwpfTableRow : tableRows) {
List<XWPFTableCell> tableCells = xwpfTableRow
.getTableCells();
for (XWPFTableCell xwpfTableCell : tableCells) {
xwpfParagraphs = xwpfTableCell.getParagraphs();
replaceInParagraphs(xwpfParagraphs);
}
}
}
out = new FileOutputStream(new File(outputPath));
template.write(out);
out.flush();
out.close();
//System.exit(0);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// nothing to do ....
}
}
}
}
/**
* #param xwpfParagraphs
*/
private void replaceInParagraphs(List<XWPFParagraph> xwpfParagraphs) {
for (XWPFParagraph xwpfParagraph : xwpfParagraphs) {
List<XWPFRun> xwpfRuns = xwpfParagraph.getRuns();
for (XWPFRun xwpfRun : xwpfRuns) {
String xwpfRunText = xwpfRun.getText(xwpfRun
.getTextPosition());
for (Map.Entry<String, String> entry : replacements
.entrySet()) {
if (xwpfRunText != null
&& xwpfRunText.contains(entry.getKey())) {
xwpfRunText = xwpfRunText.replaceAll(
entry.getKey(), entry.getValue());
}
}
xwpfRun.setText(xwpfRunText, 0);
}
}
}
public static void main(String[] args) {
new DotxTemplateFiller();
}
First I did it for regular paragraphs in the MS Word template and than for paragraphs inside table cells.
Hope it is helpful for you and I hope I understood your problem right... :-)
Best wishes.
Adding on to Josh's solution, the map I am building has ended up with over a thousand tags and continues to grow. To cut down on processing, I decided to build a small subset of the tags that I know appear in the paragraph, typically ending up with a map of only one or two tags that I then pass as the Map to the replaceInParagraphs method provided above. Also, using the Substitution object to store the substitution text, allows me to add methods into that object (such as formatting) that I can call once the substitution has been completed. Using the subset Map also allows me to know what replacements have been made in any paragraph.
private Map<String, Substitution> buildTagList(Map<String, Substitution> replacements, List<XWPFParagraph> xwpfParagraphs, String start, String end) {
Map<String, Substitution> returnMap = new HashMap<String, Substitution> ();
for (XWPFParagraph paragraph : xwpfParagraphs) {
List<XWPFRun> runs = paragraph.getRuns();
// Check is there is a tag in the paragraph
TextSegment found = paragraph.searchText(start, new PositionInParagraph());
String runText = "";
XWPFRun run = null;
if ( found != null ) {
StringBuilder b = new StringBuilder();
for (int runPos = found.getBeginRun(); runPos < runs.size(); runPos++) {
run = runs.get(runPos);
b.append(run.getText(run.getTextPosition()));
runText = b.toString();
}
// Now we need to find all tags in the run
boolean finished = false;
int tagStart = 0;
int tagEnd = 0;
while ( ! finished ) {
// get the first tag
tagStart = runText.indexOf(start,tagStart);
tagEnd = runText.indexOf(end, tagEnd);
if ( tagStart >= 0 ) {
String tag = runText.substring(tagStart, tagEnd + end.length());
Substitution s = replacements.get(tag);
if (s != null) {
returnMap.putIfAbsent(tag,s);
}
}
else
finished = true;
tagStart = tagEnd + end.length();
tagEnd = tagStart;
}
}
}
return returnMap;
}

Categories

Resources