Populate docx table using List of Objects in java - java

I am trying to populate a table within a docx file with data from java objects. More precisely each row represents an Object and my pattern starts with one row. I want to find out how can I introduce a new row in case I have more than one objects in my list. See example below:
Docx table looks like this:
And I successfully realized the mapping with the fields but for ONLY one object. How can i introduce another row (from Java) to make room for another object ? For this implementation I am using org.apache.poi.xwpf.usermodel.XWPFDocument;
public class DocMagic {
public static XWPFDocument replaceTextFor(XWPFDocument doc, String findText, String replaceText) {
replaceTextFor(doc.getParagraphs(),findText,replaceText);
doc.getTables().forEach(p -> {
p.getRows().forEach(row -> {
row.getTableCells().forEach(cell -> {
replaceTextFor(cell.getParagraphs(), findText, replaceText);
});
});
});
return doc;
}
private static void replaceTextFor(List<XWPFParagraph> paragraphs, String findText, String replaceText) {
paragraphs.forEach(p -> {
p.getRuns().forEach(run -> {
String text = run.text();
if (text.contains(findText)) {
run.setText(text.replace(findText, replaceText), 0);
}
});
});
}
public static void saveWord(String filePath, XWPFDocument doc) throws FileNotFoundException, IOException {
FileOutputStream out = null;
try {
out = new FileOutputStream(filePath);
doc.write(out);
} catch (Exception e) {
e.printStackTrace();
} finally {
out.close();
}
}
}
EDIT: using addNewTableCell().setText() places the values on the right side of the table

Normally you use below steps to add row in a table,
XWPFTableRow row =tbl.createRow();
row.addNewTableCell().setText("whatever you want");
tbl.addRow(row, y);
But in your case seems you want to add rows on the fly while you are iterating the docx table together with your Java list of object,
In Java your are not safe or able to change the collection while looping it,
so you might need to do it in 2 steps,
you need to expand/add rows first to the docx table before you populate it,
by firstly calculate how many objects you have in your java list.
when the table rows are already added accordingly, you could iterate and populate them

Related

How do I ADD bullet points to a word document using Apache POI in Java

I have a word document which is used as a template. Inside this template I have some tables that contain predefined bullet points. Now I'm trying to replace the placeholder string with a set of strings.
I'm totally stuck on this. My simplified methods looks like this.
replaceKeyValue.put("[DescriptionOfItem]", new HashSet<>(Collections.singletonList("This is the description")));
replaceKeyValue.put("[AllowedEntities]", new HashSet<>(Arrays.asList("a", "b")));
replaceKeyValue.put("[OptionalEntities]", new HashSet<>(Arrays.asList("c", "d")));
replaceKeyValue.put("[NotAllowedEntities]", new HashSet<>(Arrays.asList("e", "f")));
try (XWPFDocument template = new XWPFDocument(OPCPackage.open(file))) {
template.getTables().forEach(
xwpfTable -> xwpfTable.getRows().forEach(
xwpfTableRow -> xwpfTableRow.getTableCells().forEach(
xwpfTableCell -> replaceInCell(replaceKeyValue, xwpfTableCell)
)
));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
template.write(baos);
return new ByteArrayResource(baos.toByteArray());
} finally {
if (file.exists()) {
file.delete();
}
}
private void replaceInCell(Map<String, Set<String>> replacementsKeyValuePairs, XWPFTableCell xwpfTableCell) {
for (XWPFParagraph xwpfParagraph : xwpfTableCell.getParagraphs()) {
for (Map.Entry<String, Set<String>> replPair : replacementsKeyValuePairs.entrySet()) {
String keyToFind = replPair.getKey();
Set<String> replacementStrings = replacementsKeyValuePairs.get(keyToFind);
if (xwpfParagraph.getText().contains(keyToFind)) {
replacementStrings.forEach(replacementString -> {
XWPFParagraph paragraph = xwpfTableCell.addParagraph();
XWPFRun run = paragraph.createRun();
run.setText(replacementString);
});
}
}
}
I was expecting that some more bullet points will be added to the current cell. Am I missing something? The paragraph is the one containing the placeholder string and format.
Thanks for any help!
UPDATE: This is how part of the template looks like. I would like to automatically search for the terms and replace them. Searching works so far. But trying to replace the bullet points ends in an unlocatable NullPointer.
Would it be easier to use fields? I need to keep the bullet point style though.
UPDATE 2: added download link and updated the code. Seems I can't alter the paragraphs if I'm iterating through them. I get a null-pointer.
Download link: WordTemplate
Since Microsoft Word is very, very "strange" in how it divides text in different runs in it's storage, such questions are not possible to answer without having a complete example including all code and the Word documents in question. Having a general usable code for adding content to Word documents seems not be possible, except all the adding or replacement is only in fields (form fields or content controls or mail merge fields).
So I downloaded your WordTemplate.docx which looks like so:
Then I runned the following code:
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.apache.xmlbeans.XmlCursor;
import java.util.*;
import java.math.BigInteger;
public class WordReadAndRewrite {
static void addItems(XWPFTableCell cell, XWPFParagraph paragraph, Set<String> items) {
XmlCursor cursor = null;
XWPFRun run = null;
CTR cTR = null; // for a deep copy of the run's low level object
BigInteger numID = paragraph.getNumID();
int indentationLeft = paragraph.getIndentationLeft();
int indentationHanging = paragraph.getIndentationHanging();
boolean first = true;
for (String item : items) {
if (first) {
for (int r = paragraph.getRuns().size()-1; r > 0; r--) {
paragraph.removeRun(r);
}
run = (paragraph.getRuns().size() > 0)?paragraph.getRuns().get(0):null;
if (run == null) run = paragraph.createRun();
run.setText(item, 0);
cTR = (CTR)run.getCTR().copy(); // take a deep copy of the run's low level object
first = false;
} else {
cursor = paragraph.getCTP().newCursor();
boolean thereWasParagraphAfter = cursor.toNextSibling(); // move cursor to next paragraph
// because the new paragraph shall be **after** that paragraph
// thereWasParagraphAfter is true if there is a next paragraph, else false
if (thereWasParagraphAfter) {
paragraph = cell.insertNewParagraph(cursor); // insert new paragraph if there are next paragraphs in cell
} else {
paragraph = cell.addParagraph(); // add new paragraph if there are no other paragraphs present in cell
}
paragraph.setNumID(numID); // set template paragraph's numbering Id
paragraph.setIndentationLeft(indentationLeft); // set template paragraph's indenting from left
if (indentationHanging != -1) paragraph.setIndentationHanging(indentationHanging); // set template paragraph's hanging indenting
run = paragraph.createRun();
if (cTR != null) run.getCTR().set(cTR); // set template paragraph's run formatting
run.setText(item, 0);
}
}
}
public static void main(String[] args) throws Exception {
Map<String, Set<String>> replaceKeyValue = new HashMap<String, Set<String>>();
replaceKeyValue.put("[AllowedEntities]", new HashSet<>(Arrays.asList("allowed 1", "allowed 2", "allowed 3")));
replaceKeyValue.put("[OptionalEntities]", new HashSet<>(Arrays.asList("optional 1", "optional 2", "optional 3")));
replaceKeyValue.put("[NotAllowedEntities]", new HashSet<>(Arrays.asList("not allowed 1", "not allowed 2", "not allowed 3")));
XWPFDocument document = new XWPFDocument(new FileInputStream("WordTemplate.docx"));
List<XWPFTable> tables = document.getTables();
for (XWPFTable table : tables) {
List<XWPFTableRow> rows = table.getRows();
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
int countParagraphs = cell.getParagraphs().size();
for (int p = 0; p < countParagraphs; p++) { // do not for each since new paragraphs were added
XWPFParagraph paragraph = cell.getParagraphArray(p);
String placeholder = paragraph.getText();
placeholder = placeholder.trim(); // this is the tricky part to get really the correct placeholder
Set<String> items = replaceKeyValue.get(placeholder);
if (items != null) {
addItems(cell, paragraph, items);
}
}
}
}
}
FileOutputStream out = new FileOutputStream("Result.docx");
document.write(out);
out.close();
document.close();
}
}
The Result.docx looks like so:
The code loops trough the table cells in the Word document and looks for a paragraph which contains exactly the placeholder. This even might be the tricky part since that placeholder might be splitted into differnt text runs by Word. If found it runs a method addItems which takes the found paragraph as a template for numbering and indention (might be incomplter though). Then it sets the first new item in first text run of found paragraph and removes all other text runs which possibly are there. Then it determines wheter new paragraphs must be inserted or added to the cell. For this a XmlCursor is used. In new inserted or added paragrahs the other items are placed and the numbering and indention settings are taken from the placeholder's paragraph.
As said, this is code for showing the principles of how to do. It would must be extended very much to be general usable. In my opinion those trials using text placeholders in Word documents for text replacements are not really good. Placeholders for variable text in Word documents should be fields. This could be form fields, content controls or mail merge fields. Advantage of fields in contrast of text placeholders is that Word knows the fields being entities for variable texts. It will not split them into multiple text runs for multiple strange reasons as it often does with normal text.

How to edit a Hyperlink in a Word Document using Apache POI?

So I've been browsing around the source code / documentation for POI (specifically XWPF) and I can't seem to find anything that relates to editing a hyperlink in a .docx. I only see functionality to get the information for the currently set hyperlink. My goal is to change the hyperlink in a .docx to link to "http://yahoo.com" from "http://google.com" as an example. Any help would be greatly appreciated. Thanks!
I found a way to edit the url of the link in a "indirect way" (copy the previous hyperlink, modify the url, delete the previous hyperlink and add the new one in the paragraph).
Code is shown below:
private void editLinksOfParagraph(XWPFParagraph paragraph, XWPFDocument document) {
for (int rIndex = 0; rIndex < paragraph.getRuns().size(); rIndex++) {
XWPFRun run = paragraph.getRuns().get(rIndex);
if (run instanceof XWPFHyperlinkRun) {
// get the url of the link to edit it
XWPFHyperlink link = ((XWPFHyperlinkRun) run).getHyperlink(document);
String linkURL = link.getURL();
//get the xml representation of the hyperlink that includes all the information
XmlObject xmlObject = run.getCTR().copy();
linkURL += "-edited-link"; //edited url of the link, f.e add a '-edited-link' suffix
//remove the previous link from the paragraph
paragraph.removeRun(rIndex);
//add the new hyperlinked with updated url in the paragraph, in place of the previous deleted
XWPFHyperlinkRun hyperlinkRun = paragraph.insertNewHyperlinkRun(rIndex, linkURL);
hyperlinkRun.getCTR().set(xmlObject);
}
}
}
This requirement needs knowledge about how hyperlinks referring to an external reference get stored in Microsoft Word documents and how this gets represented in XWPF of Apache POI.
The XWPFHyperlinkRun is the representation of a linked text run in a IRunBody. This text run, or even multiple text runs, is/are wrapped with a XML object of type CTHyperlink. This contains a relation ID which points to a relation in the package relations part. This package relation contains the URI which is the hyperlink's target.
Currently (apache poi 5.2.2) XWPFHyperlinkRun provides access to a XWPFHyperlink. But this is very rudimentary. It only has getters for the Id and the URI. It neither provides access to it's XWPFHyperlinkRun and it's IRunBody nor it provides a setter for the target URI in the package relations part. It not even has internally access to it's the package relations part.
So only using Apache POI classes the only possibility currently is to delete the old XWPFHyperlinkRun and create a new one pointing to the new URI. But as the text runs also contain the text formatting, deleting them will also delete the text formatting. It would must be copied from the old XWPFHyperlinkRun to the new before deleting the old one. That's uncomfortable.
So the rudimentary XWPFHyperlink should be extended to provide a setter for the target URI in the package relations part. A new class XWPFHyperlinkExtended could look like so:
import org.apache.poi.xwpf.usermodel.*;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
/**
* Extended XWPF hyperlink class
* Provides access to it's Id, URI, XWPFHyperlinkRun, IRunBody.
* Provides setting target URI in PackageRelationship.
*/
public class XWPFHyperlinkExtended {
private String id;
private String uri;
private XWPFHyperlinkRun hyperlinkRun;
private IRunBody runBody;
private PackageRelationship rel;
public XWPFHyperlinkExtended(XWPFHyperlinkRun hyperlinkRun, PackageRelationship rel) {
this.id = rel.getId();
this.uri = rel.getTargetURI().toString();
this.hyperlinkRun = hyperlinkRun;
this.runBody = hyperlinkRun.getParent();
this.rel = rel;
}
public String getId() {
return this.id;
}
public String getURI() {
return this.uri;
}
public IRunBody getIRunBody() {
return this.runBody;
}
public XWPFHyperlinkRun getHyperlinkRun() {
return this.hyperlinkRun;
}
/**
* Provides setting target URI in PackageRelationship.
* The old PackageRelationship gets removed.
* A new PackageRelationship gets added using the same Id.
*/
public void setTargetURI(String uri) {
this.runBody.getPart().getPackagePart().removeRelationship(this.getId());
this.uri = uri;
PackageRelationship rel = this.runBody.getPart().getPackagePart().addExternalRelationship(uri, XWPFRelation.HYPERLINK.getRelation(), this.getId());
this.rel = rel;
}
}
It does not extend XWPFHyperlink as this is so rudimentary it's not worth it. Furthermore after setTargetURI the String uri needs to be updated. But there is no setter in XWPFHyperlink and the field is only accessible from inside the package.
The new XWPFHyperlinkExtended can be got from XWPFHyperlinkRun like so:
/**
* If this HyperlinkRun refers to an external reference hyperlink,
* return the XWPFHyperlinkExtended object for it.
* May return null if no PackageRelationship found.
*/
/*modifiers*/ XWPFHyperlinkExtended getHyperlink(XWPFHyperlinkRun hyperlinkRun) {
try {
for (org.apache.poi.openxml4j.opc.PackageRelationship rel : hyperlinkRun.getParent().getPart().getPackagePart().getRelationshipsByType(XWPFRelation.HYPERLINK.getRelation())) {
if (rel.getId().equals(hyperlinkRun.getHyperlinkId())) {
return new XWPFHyperlinkExtended(hyperlinkRun, rel);
}
}
} catch (org.apache.poi.openxml4j.exceptions.InvalidFormatException ifex) {
// do nothing, simply do not return something
}
return null;
}
Once we have that XWPFHyperlinkExtended we can set an new target URI using it's method setTargetURI.
A further problem results from the fact, that the XML object of type CTHyperlink can wrap around multiple text runs, not only one. Then multiple XWPFHyperlinkRun are in one CTHyperlink and point to one target URI. For example this could look like:
... [this is a link to example.com]->https://example.com ...
This results in 6 XWPFHyperlinkRuns in one CTHyperlink linking to https://example.com.
This leads to problems when link text needs to be changed when the link target changes. The text of all the 6 text runs is the link text. So which text run shall be changed?
The best I have found is a method which sets the text of the first text run in the CTHyperlink.
/**
* Sets the text of the first text run in the CTHyperlink of this XWPFHyperlinkRun.
* Tries solving the problem when a CTHyperlink contains multiple text runs.
* Then the String value is set in first text run only. All other text runs are set empty.
*/
/*modifiers*/ void setTextInFirstRun(XWPFHyperlinkRun hyperlinkRun, String value) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHyperlink ctHyperlink = hyperlinkRun.getCTHyperlink();
for (int r = 0; r < ctHyperlink.getRList().size(); r++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR ctR = ctHyperlink.getRList().get(r);
for (int t = 0; t < ctR.getTList().size(); t++) {
org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText ctText = ctR.getTList().get(t);
if (r == 0 && t == 0) {
ctText.setStringValue(value);
} else {
ctText.setStringValue("");
}
}
}
}
There the String value is set in first text run only. All other text runs are set empty. The text formatting of the first text run remains.
This works, but need more some steps to get text formatting correctly:
try (var fis = new FileInputStream(fileName);
var doc = new XWPFDocument(fis)) {
var pList = doc.getParagraphs();
for (var p : pList) {
var runs = p.getRuns();
for (int i = 0; i < runs.size(); i++) {
var r = runs.get(i);
if (r instanceof XWPFHyperlinkRun) {
var run = (XWPFHyperlinkRun) r;
var link = run.getHyperlink(doc);
// To get text: link for checking
System.out.println(run.getText(0) + ": " + link.getURL());
// how i replace it
var run1 = p.insertNewHyperlinkRun(i, "http://google.com");
run1.setText(run.getText(0));
// remove the old link
p.removeRun(i + 1);
}
}
}
try (var fos = new FileOutputStream(outFileName)) {
doc.write(fos);
}
}
I'm using these libraries:
implementation 'org.apache.poi:poi:5.2.2'
implementation 'org.apache.poi:poi-ooxml:5.2.2'

Printer won't print the entire jTable [duplicate]

Question Now once the data is fetched from the database and shown in the JTable object "table" embedded in the scrollPane, how do we create a print job that makes it possible to print the displayed table as such in A3 sized paper ?
My code to fetch the data from the database is shown below:
try
{
Class.forName("com.mysql.jdbc.Driver");
Connection con=DriverManager.getConnection("jdbc:mysql://localhost/newb","root","pass");
Statement stat=con.createStatement();
ResultSet res=stat.executeQuery("select * from table where name = '"+name+"'");
ResultSetMetaData rsmd = res.getMetaData();
int colcount = rsmd.getColumnCount();
Vector columns = new Vector(colcount);
for(int i=3; i<=colcount; i++)
{
columns.add(rsmd.getColumnName(i));
}
Vector data = new Vector();
Vector row;
// Store row data
while(res.next())
{
row = new Vector(colcount);
for(int i=3; i<=colcount; i++)
{
row.add(res.getString(i));
}
data.add(row);
}
table = new JTable(data, columns);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
scrollPane.setViewportView(table);
}
catch(Exception ex)
{
System.out.println(ex);
}
I am using vector class to fetch the data from the table. How do we print the data shown in the displayed table to a paper?
just use JTable.print() method. here is an article about sending JTable into printer and another one with more parameters
You obviously didn't read the links provided in your previous question.
From the Printing section of How to use Tables
Printing
JTable provides a simple API for printing tables. The easiest way to
print out a table is to invoke JTable.print with no arguments:
try {
if (! table.print()) {
System.err.println("User cancelled printing");
}
} catch (java.awt.print.PrinterException e) {
System.err.format("Cannot print %s%n", e.getMessage());
}
Invoking print on a normal Swing application brings up a standard printing
dialog box. (On a headless application, the table is simply printed.)
The return value indicates whether the user went ahead with the print
job or cancelled it. JTable.print can throw
java.awt.print.PrinterException, which is a checked exception; that's
why the above example uses a try ... catch.
JTable provides several overloads of print with various options. The
following code from TablePrintDemo.java shows how to define a page
header:
MessageFormat header = new MessageFormat("Page {0,number,integer}");
try {
table.print(JTable.PrintMode.FIT_WIDTH, header, null);
} catch (java.awt.print.PrinterException e) {
System.err.format("Cannot print %s%n", e.getMessage());
}
For more sophisticated printing applications, use JTable.getPrintable to obtain
a Printable object for the table. For more on Printable, refer to the
Printing lesson in the 2D Graphics trail.
i hope help you with this code try it its for How to print JTable in Java netbeans
private void btn_printActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
MessageFormat header = new MessageFormat("Print Report");
MessageFormat footer = new MessageFormat("Page{0,number,integer}");
try {
table_employee.print(JTable.PrintMode.FIT_WIDTH, header, footer);
} catch (java.awt.print.PrinterAbortException e) {
} catch (PrinterException ex) {
Logger.getLogger(employee_info.class.getName()).log(Level.SEVERE, null, ex);
}
}

Using iText, Cannot Replicate PDF Bookmark Destinations Created from InDesign/FrameMaker

I am writing a utility to collapse the bookmarks an exisiting PDF, and save the file as a new PDF. The platform is Java using the iText API.
The following code retrieves the existing bookmarks and recursively calls a method to close them.
/* Retrieve the bookmarks: */
List<HashMap<String, Object>> bookmarks = SimpleBookmark.getBookmark(originalPdf);
/* Now we recursively close all the bookmarks.*/
if (bookmarks != null ) {
for (HashMap temp : bookmarks) {
closeBookmark(originalPdf, temp, "false");
}
}
private static void closeBookmark(PdfReader originalPdf, HashMap localBookmark, String bookmarkState) {
localBookmark.put("Open", bookmarkState);
/* If the current bookmark has kids (lower-level bookmarks), then recursively close them as well. */
if (localBookmark.containsKey("Kids")) {
ArrayList<HashMap> kidMap = (ArrayList) localBookmark.get("Kids");
for (HashMap temp : kidMap) {
closeBookmark(originalPdf, temp, bookmarkState);
}
}
}
This works on PDFs created by Word, OpenOffice, and XSL-FO, but not FrameMaker or InDesign. In the latter cases, I get the collapsed bookmarks, but clicking on the bookmarks does not scroll the PDF to the destination. It seems as if the destinations are not present in either the bookmarks or the PDF body. Any suggestions?

Can't add a row to a jTable even when using getModel

I've been searching through this website for numerous hours now on how to get my button to an a row to an already existing table, this table created by simply clicking the swing Controls, and adding a table and altering the fields through the properties.
The table's variable name is 'table'.
And when confronted with this line of code:
table.getModel().insertRow(table.getRowCount(),new Object[]{nome[i],data[i]});
The 'insertRow' part is redded and I can't seem to fix it.
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
String direcdate=direc1.getText();
File folder = new File(direcdate);
File[] listOfFiles=folder.listFiles();
String[] nome = new String[250];
String[] data = new String[250];
int i=0;
for (File listOfFile : listOfFiles) {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
if (listOfFile.isFile()) {
nome[i]= listOfFile.getName ();
data[i] =sdf.format(listOfFile.lastModified());
i++;
}
else if (listOfFile.isDirectory()) {
nome[i]= "Folder: " + listOfFile.getName ();
data[i] =sdf.format(listOfFile.lastModified());
i++;
}
}
for(int increm=0;increm<i;increm++)
{
table.getModel().insertRow(table.getRowCount(),new Object[]{nome[i],data[i]});
}
}
Any ideas or suggestions?
EDIT: where the table model is located:
public class GAPAC_TESTE extends javax.swing.JFrame {
public GAPAC_TESTE() {
initComponents();
ultimaalt.setText("0");
jTextPane2.setText("Após escolher a diretoria, escolha uma das opções.");
DefaultTableModel model = new javax.swing.table.DefaultTableModel();
table = new javax.swing.JTable(model);
}
table.getModel().
That method return a TableModel. Did you look at the API for the TableModel interface? It does not contain an insertRow(...) method.
The DefaultTableModel has the insertRow(...) method. So assuming your table is using a DefaultTableModel the code would be:
DefaultTableModel model = (DefaultTableMode)table.getModel();
model.insertRow(...);
Don't always write you code in a single statmentment. Break the statement up into multiple statements so you understand exactly which part of the statement causes the problem and it makes sure you assign the variable to the proper class.
If you implement a TableModel, you will be able to exactly determine how data is added and which data types are displayed in your table.

Categories