I've seen that the best way of handling large tables in itext 7 is explained in http://developers.itextpdf.com/examples/tables/clone-large-tables
I have a table inside another one. What if the large table is the inner one?
Table outTable = new Table(new float[]{1f},true);
Cell cellHeader1 = new Cell();
cellHeader1.add(new Paragraph("Header 1").addStyle(style));
outTable.addHeaderCell(cellHeader1);
document.add(outTable);
for (int i=0; i<smallArray.size();i++) {
Table innerTable = new Table(new float[]{0.5f,0.5f},true);
Cell cellHeader2 = new Cell(1,2);
cellHeader2.add(new Paragraph("Header 2").addStyle(style));
innerTable.addHeaderCell(cellHeader2);
Cell cellInnerTable = new Cell();
cellInnerTable.add(innerTable);
outTable.addCell(cellInnerTable);
for(int j=0;j<bigArray.size();j++){
//add cells to innerTable;
if (j%20==0){
innerTable.flush(); (1)
outTable.flush(); (2)
}
}
innerTable.complete(); (1)
}
outTable.complete();
(2) This flush doesn't solve the memory problem.
(1) These lines return a NullPointerException in the Table object, line 539 because document is null. The outTable's parent is the document so the 'flush' method flushes the data into the document, but the innerTable's parent is the outTable, not the document. Is there a way of having the innerTable flushed into the outTable and the outTable into the document?
If I set the document to the innerTable so I don't get the NullPointerException:
innerTable.setDocument(document);
it doesn't behave as it should, because now the innerTable is being flushed into the document, not into the outTable and it does weird things.
Thank you very much!
Unfortunately, large tables are only supported if you add them directly to the Document. Inner large tables are not supported and there are no plans to add support for inner large tables in the nearest future.
Try:
for(int j=0;j<bigArray.size();j++){
//add cells to innerTable;
if (j%20==0){
Cell cellContent = new Cell(1,2).add(innerTable);
outTable.addCell(cellContent);
innerTable.flushContent(); // API says is internal but is public and works OK
outTable.flush();
}
}
Related
I am parsing a XML that has HTML tags in it. After parsing the XML I am trying to create a table that is within the cell of another table and create a word document with these contents. I implemented the table within a table with the help of the answer of this Question. However, I am unable to set the width of the columns of the nested table. When the word document is generated the generated nested table is so ugly with super narrow columns that has to be manually adjusted to have a desired column width. I would like to set some default values as the desired column width. The generated word document with the nested table has been provided in the picture. I am using the following code to set the column width but no luck so far.
for (int i = 0; i < nestedtable.getNumberOfRows(); i++) {
XWPFTableRow row = nestedtable.getRow(i);
int numCells = row.getTableCells().size();
for (int j = 0; j < numCells; j++) {
XWPFTableCell cell = row.getCell(j);
CTTblWidth cellWidth = cell.getCTTc().addNewTcPr().addNewTcW();
CTTcPr pr = cell.getCTTc().addNewTcPr();
pr.addNewNoWrap();
cellWidth.setW(BigInteger.valueOf(400));
}
}
How should I get out of this problem? The generated word document is looking so ugly due to this nested table structure.
The question you linked was about how to insert a table into a table cell in a Word-table using Apache POI. Nothing about how to set column widths. And, since it is from 2017, it answers this using Apache POI versions of 2017. But Apache POI is highly in development. So current versions will have more straightforward methods to do things.
Especially to set widths of XWPFTable and/or XWPFTableCell, current Apache POI version 5.2.3 provides setWidth methods in XWPFTable as well as in XWPFTableCell. The most straightforward is public void setWidth(java.lang.String widthValue). There the String widthValue may be the width to the value "auto", an integer value (20ths of a point), or a percentage ("nn.nn%").
Also setting borders is much more straightforward now as there are XWPFTable.set*Border methods now.
So the code should look like so now using current Apache POI.
import java.io.*;
import org.apache.poi.xwpf.usermodel.*;
public class CreateWordTableInTable {
static void setAllBorders(XWPFTable table, XWPFTable.XWPFBorderType borderType, int size, int space, java.lang.String rgbColor) {
table.setTopBorder(borderType, size, space, rgbColor);
table.setRightBorder(borderType, size, space, rgbColor);
table.setBottomBorder(borderType, size, space, rgbColor);
table.setLeftBorder(borderType, size, space, rgbColor);
table.setInsideHBorder(borderType, size, space, rgbColor);
table.setInsideVBorder(borderType, size, space, rgbColor);
}
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument();
XWPFTable tableOne = document.createTable(2,2);
tableOne.setWidth("100%");
XWPFTableRow tablerow = tableOne.getRow(0);
tablerow.getCell(0).setWidth("40%");
tablerow.getCell(1).setWidth("60%");
tablerow.getCell(0).setText("Test");
tablerow.getCell(1).setText("Test");
tablerow = tableOne.getRow(1);
tablerow.getCell(0).setText("Test");
XWPFParagraph paragraph = tablerow.getCell(1).getParagraphArray(0);
XWPFTable tableTwo = tablerow.getCell(1).insertNewTbl(paragraph.getCTP().newCursor());
tableTwo.setWidth(0); // This is necessary because a XWPFTable created by insertNewTbl seems not to have full internally structure. It lacks the cell width field in this case.
tableTwo.setWidth("100%");
setAllBorders(tableTwo, XWPFTable.XWPFBorderType.SINGLE, 4, 0, "000000");
tablerow = tableTwo.createRow();
tablerow.createCell().setText("aaaaaaaaaa");
tablerow.createCell().setText("jjjjjjjj");
tablerow = tableTwo.createRow();
tablerow.getCell(0).setText("bbbbbbbbbb");
tablerow.getCell(1).setText("gggggggggg");
try (FileOutputStream out = new FileOutputStream("./CreateWordTableInTable.docx")) {
document.write(out);
}
document.close();
}
}
Note: A XWPFTable created by insertNewTbl seems not to have full internally structure. It has no default border setting for example and it lacks the internally cell width field. That's why explicit border setting is necessary. And calling setWidth(int width) is necessary before calling setWidth(java.lang.String widthValue) as the first sets the internally cell width field while the second relies on the presence of that field and fails if it is not present.
Result looks like so for me:
I'm getting unexpected results when I try to keep rows in an iText table together. Below is some standalone code that creates a PDF with the results I'm seeing.
FileOutputStream out = new FileOutputStream(new File("table.pdf"));
Document document = new Document(new Rectangle(612, 242));
PdfWriter.getInstance(document, out);
document.open();
PdfPTable table = new PdfPTable(1);
table.setWidthPercentage(100);
for (int i = 0; i < 10; i++) {
PdfPCell cell;
if (i == 9) {
cell = new PdfPCell(new Phrase("Two\nLines"));
}
else {
cell = new PdfPCell(new Phrase(Integer.toString(i)));
}
table.addCell(cell);
}
table.keepRowsTogether(new int[] { 8, 9 });
document.add(table);
document.close();
The example creates a table that is two pixels too small to fit on the first page, forcing the last row onto the next page. I would expect, however, since I added the array using keepRowsTogether, to see the first eight rows to stay on one page and the last two to stay together on the next page but that isn't the case as shown by the example images below. Instead the seventh row (counting from zero) is also carried over to the next page.
According to the API documentation found here, keepRowsTogether "Defines which rows should not allow a page break (if possible)." That indicates to me that row seven, which isn't included in the array, should allow a page break.
Does anyone have any ideas how to keep the seventh row from getting carried over to the next page when it definitely fits on the first?
Solution: After talking to Bruno, I realized I misunderstood how keepRowsTogether works. All I need to do for the example above is change table.keepRowsTogether(new int[] { 8, 9 }); to table.keepRowsTogether(9).
I think there's a misunderstanding about what keepRowsTogether() means. If you use:
table.keepRowsTogether(new int[] { 8, 9 });
You indicate that the table won't split at row 8 or 9. This is consistent with what I see.
Maybe you want something like this: split_at_row.pdf
This PDF is created using SplitRowAtSpecificRow. Instead of keepRowsTogether(), it uses:
table.setBreakPoints(8);
This means that iText will give preference to splitting the table after row 8 (start counting at 0). You can introduce more than one breakpoint. For instance:
table.setBreakPoints(4, 8, 12);
I'm generating a docx file with apache-poi. In the wordfile, I add tables, whose columns have a width I would like to see fixed.
Currently, I use the technique described here: http://apache-poi.1045710.n5.nabble.com/Is-there-a-way-to-set-the-width-of-a-column-in-XWPFTableCell-td5711491.html
Basically, this entails setting
cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(cols[j]));
on each cell of that column.
The problem is that while the file opens perfectly in MS Word, open office interprets the values I set to the columnwidth differently. Whereas MS Word apparantly assumes 20-th of a point as units, open office seems to use points instead and therefore all columns are 20 times wider when I open the generated document in OO.
Usually when I see something weird in the generated output, I unpack the docx file, see what the value should be and change my code. But open office does not seem to be able to save to docx, so I can't change the value in OO save it back and see if Word still interprets the document correctly in order to find a cross-application solution.
Any idea how I set the width of the table column so that both OO and MS Wordt interprets it the same?
Don't touch single cells.
Add a GRID:
XWPFDocument doc = new XWPFDocument();
XWPFTable table = doc.createTable(1,2);
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(6000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(2000));
table.getRow(0).getCell(0).setText("1A");
table.getRow(0).getCell(1).setText("1B");
XWPFTableRow newrow = table.createRow();
newrow.getCell(0).setText("2A");
newrow.getCell(1).setText("2B");
The grid sets widths for entire columns. You don't need to do any cycles to set a width for every cell. It works in LibreOffice and GoogleDocs.
To watch the seted width in MS Word too, you may set widths of cells in the first row:
widthCellsAcrossRow(table, 0, 0, 4000);
widthCellsAcrossRow(table, 0, 0, 5000);
private static void widthCellsAcrossRow (XWPFTable table, int rowNum, int colNum, int width) {
XWPFTableCell cell = table.getRow(rowNum).getCell(colNum);
if (cell.getCTTc().getTcPr() == null)
cell.getCTTc().addNewTcPr();
if (cell.getCTTc().getTcPr().getTcW()==null)
cell.getCTTc().getTcPr().addNewTcW();
cell.getCTTc().getTcPr().getTcW().setW(BigInteger.valueOf((long) width));
}
Answer extracted from question:
It was recently pointed out to me that LibreOffice is able to save to docx. By changing the generated file and saving it back and decompiling the result, I have been able to resolve the issue.
Key is to put an explicit width to the table itself first. Word doesn't seem to care about its presence, and OpenOffice/LibreOffice are able to render the table correctly.
So, after creation of the table, I did as follows:
CTTblWidth width = table.getCTTbl().addNewTblPr().addNewTblW();
width.setType(STTblWidth.DXA);
width.setW(BigInteger.valueOf(9072));
Upon creation of the table, the layout is set to "auto" by default hence the width of the cell will always increase to follow the length of the string. As per OpenXML markup, it look's like
w:tblPr
w:tblLayout w:type="auto"
the solution is to set the layout to fixed and set the individual column length
w:tblPr
w:tblLayout w:type="fixed"
Here's the poi code for setting table layout:
XWPFTable table = document.createTable();
CTTblLayoutType type = table.getCTTbl().getTblPr().addNewTblLayout();
type.setType(STTblLayoutType.FIXED);
Here's how to set the individual width:
int[] cols = {
4896,
1872,
4032,
1728,
1440
};
for (int i = 0; i < table.getNumberOfRows(); i++) {
XWPFTableRow row = table.getRow(i);
int numCells = row.getTableCells().size();
for (int j = 0; j < numCells; j++) {
XWPFTableCell cell = row.getCell(j);
CTTblWidth cellWidth = cell.getCTTc().addNewTcPr().addNewTcW();
CTTcPr pr = cell.getCTTc().addNewTcPr();
pr.addNewNoWrap();
cellWidth.setW(BigInteger.valueOf(cols[j]));
}
}
column lengths are in twentieths of a point (dxa) or 1/1440 inch.
This is a major and very tricky element. I solved it using this own generic method of setting the widths of a table cell.
private static void setTableColumnWidths(XWPFTable table) {
table.getCTTbl().addNewTblGrid().addNewGridCol().setW(BigInteger.valueOf(2000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(3200));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1000));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1105));
table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(1105));
}
Based on other answers...
public static void setTableColumnsWidth(XWPFTable table, long... widths) {
CTTblGrid grid = table.getCTTbl().addNewTblGrid();
for (long w : widths) {
grid.addNewGridCol().setW(BigInteger.valueOf(w));
}
}
Usage:
setTableColumnsWidth(table, 1440, 2700, 3000, 1440);
I've looked around at tutorials for TableLayouts and other such things, but all of it seems to be programmed as a static number of rows with textview's. I'm wondering if it would be possible to create a simple table of data, with 3 columns and a variable set of rows I can add/remove items from in the Java code.
Basically, have something like this:
DATA DATA DATA
row1 data data
row2 data data
and fill this table with data from an object array in the activity's Java class. Later, if I want to add another object, it will just create another row.
For instance, if I have this in java:
Class data{
public data(String d1, String d2, String d3){
data1=d1;
data2=d2;
data3=d3;
}
}
and this in the activity class:
Class activity{
data info[] = {
new data("row1", "row1", "row1"), new data("row2","row2","row2"),
new data("row3","row3","row3")};
}
}
And I will use a for loop to add this data into the table in the activity, regardless of how many rows I need for all of it to be fit. If I add a new object of row4, it will just add another row, and end with:
row1 row1 row1
row2 row2 row2
row3 row3 row3
I hope I haven't been too vague... Thanks in advance, fellas! :)
I feel very stupid, but I've figured this out on my own.
I simply created a <TableView> inside of my <SrollView>, and dynamically added rows to it via a for loop that goes from 0 to myArray.Length().
Bam:
TableLayout prices = (TableLayout)findViewById(R.id.prices);
prices.setStretchAllColumns(true);
prices.bringToFront();
for(int i = 0; i < drug.length; i++){
TableRow tr = new TableRow(this);
TextView c1 = new TextView(this);
c1.setText(drug[i].getName());
TextView c2 = new TextView(this);
c2.setText(String.valueOf(drug[i].getPrice()));
TextView c3 = new TextView(this);
c3.setText(String.valueOf(drug[i].getAmount()));
tr.addView(c1);
tr.addView(c2);
tr.addView(c3);
prices.addView(tr);
}
It's a drug-wars style game... Tryin' to start small in the game development field.
But... She works, and does exactly what I want it to do. Now I can wrap this into a seperate method and update it whenever I want. If I want to add a row, I just add an array entry.
Figured I'd answer my own question... lol!
If you want to have a completely dynamic table as you are used to by ListView or RecyclerView there is the table view on GitHub.
Your code will look like this:
String[][] myData = new String[][] { {"row1", "row1", "row1"},
{"row2", "row2", "row2"},
{"row3", "row3", "row3"} };
TableDataAdapter<String[]> myDataAdapter =
new SimpleTableDataAdapter(getContext(), myData);
TableHeaderAdapter myHeaderAdapter =
new SimpleTableHeaderAdapter(getContext(), "Header1", "Header2", "Header3");
TableView<String[]> table = findViewById(R.id.table);
table.setDataAdapter(myDataAdapter);
table.setHeaderAdapter(myHeaderAdapter);
Because the data is managed in a model you can update the table very easily.
myDataAdapter.getData().add(new String[]{"row4", "row4", "row4"});
myDataAdapter.getData().add(new String[]{"row5", "row5", "row5"});
myDataAdapter.notifyDatasetChanged();
As the table provides a lot of customization and styling possibilities result could look like this: (or completely different)
Best regards :)
You can also create it using a RecyclerView. I think it would be a lot quicker to render. All you have to do is to use GridLayoutManager. the number of gridcells on each row is the number of your columns. the Header row can be implemented as a different ViewHolder which uses a different ui and all you have to do with the rest of your data is to put it in a single dimension array.
You can create any view programatically from Java, by example: TextView tv = new TextView(context);
So you can do that for the the Table and for their rows and columns.
Once you have donde creating it you have to add it to your layout, you have to find the root xml element and then added to it:
Viewgroup root = findeViewById(R.id.root);
root.addViews(programaticView);
Context
I've been working on dynamic PDF generation for a while now, and I've come across my final requirement. Alas, it's a nightmare. I'm using the iText library, version 2.1.7.
I'm basically trying to get a formatting like the top row (which I just mocked up in paint). The other rows are how it looks at the moment, but I'm having some real trouble with getting them to line up properly!
Code
The code being used to generate each color coded block is here:
String currentPrice = p.getPrice();
String timeStr = p.getTime();
Chunk price = new Chunk(currentPrice);
Chunk time = (Chunk) generatePdfElement("Timestamp", timeStr);
if (priceDbl > lastPrice) {
// set color to blue.
price.setBackground(WebColors.getRGBColor("#7777FF"));
time.setBackground(WebColors.getRGBColor("#7777FF"));
} else if (priceDbl < lastPrice) {
// set to red.
price.setBackground(WebColors.getRGBColor("#FF0000"));
time.setBackground(WebColors.getRGBColor("#FF0000"));
}
Paragraph pricePara = new Paragraph();
pricePara.add(price);
pricePara.add(generateBreakLine());
pricePara.add(time);
pricePara.add(generateBreakLine());
// Add the new price data to the list of all the prices for this cell.
allPrices.add(pricePara);
allPrices is then added to a paragraph and put into the cell:
Paragraph pricesCellValue = new Paragraph();
pricesCellValue.addAll(allPrices);
PdfPCell pricesCell = new PdfPCell(pricesCellValue);
pricesCell.setBackgroundColor(WebColors.getRGBColor(getRowStr()));
selectionsTable.addCell(pricesCell);
// Add each cell to the table to create the row.
The approaches I've tried
I tried the obvious, which was removing the last breakline from each Chunk. This didn't work, and it just looked exactly the same, although each Chunk was closer together.
I also tried changing from Paragraph to Phrase, which means the code looked like this:
Phrase pricePara = new Phrase();
pricePara.add(price);
pricePara.add(generateBreakLine());
pricePara.add(time);
//pricePara.add(generateBreakLine());
// Add the new price data to the list of all the prices for this cell.
allPrices.add(pricePara);
And this was the result:
So now I'm fresh out of ideas! Does anyone else have any suggestions, or some experience with iText in this area?
Edit
Just for clarity, generateBreakLine() generates a new empty Paragraph object.
I used a nested PdfPTable in the last cell, to format the positioning of each Phrase. Works like a dream!