shtanddwn
shtanddwn

Reputation: 23

Insert varying amount of headings and tables with apache poi

I want to insert a varying amount of text into a word document. E.g.

Heading 1
  Subheading 1
    Table 1
  Subheading 2
    Table 2

I used a cursor after finding the right spot in the word document, but don't know how I can create multiple stuff at that position. I got it to work with a single paragraph with text. I also tried using a for-loop after the while-statement but the createRun method referenced a paragraph that is null and therefore threw an error.

XmlCursor cursor = table.getCTTbl().newCursor();
cursor.toEndToken();
while (cursor.toNextToken() != org.apache.xmlbeans.XmlCursor.TokenType.START);
    XWPFParagraph newParagraph = document.insertNewParagraph(cursor);
    XWPFRun run = newParagraph.createRun();
    run.setText("inserted new text " + "\n");

Upvotes: 1

Views: 575

Answers (1)

Axel Richter
Axel Richter

Reputation: 61945

The XmlCursor needs to be created on the correct position in document. That must not be a position, which is not before a start token. And that must not be a position inside any other body element which cannot contain a paragraph or a table.

To adjust the cursor properly before a start token, following method cn be used:

 /*modifiers*/ XmlCursor setCursorToNextStartToken(XmlObject object) {
  XmlCursor cursor = object.newCursor();
  cursor.toEndToken(); //Now we are at end of the XmlObject.
  //There always must be a next start token.
  while(cursor.hasNextToken() && cursor.toNextToken() != org.apache.xmlbeans.XmlCursor.TokenType.START);
  //Now we are at the next start token and can insert new things here.
  return cursor;
 }

But the XmlObject object needs to be an body element which is at a position of the text body which allows to add a paragraph or a table.

Let`s have a complete example again:

If WordTemplate.docx looks like...

enter image description here

...then following code

import java.io.FileOutputStream;
import java.io.FileInputStream;

import org.apache.poi.xwpf.usermodel.*;

import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;

public class WordCopyTableAfterTable {

 static XmlCursor setCursorToNextStartToken(XmlObject object) {
  XmlCursor cursor = object.newCursor();
  cursor.toEndToken(); //Now we are at end of the XmlObject.
  //There always must be a next start token.
  while(cursor.hasNextToken() && cursor.toNextToken() != org.apache.xmlbeans.XmlCursor.TokenType.START);
  //Now we are at the next start token and can insert new things here.
  return cursor;
 }

 static void removeCellValues(XWPFTableCell cell) {
  for (XWPFParagraph paragraph : cell.getParagraphs()) {
   for (int i = paragraph.getRuns().size()-1; i >= 0; i--) {
    paragraph.removeRun(i);
   }  
  }
 }

 public static void main(String[] args) throws Exception {

  //The data. Each row a new table.
  String[][] data= new String[][] {
   new String[] {"John Doe", "5/23/2019", "1234.56"},
   new String[] {"Jane Doe", "12/2/2019", "34.56"},
   new String[] {"Marie Template", "9/20/2019", "4.56"},
   new String[] {"Hans Template", "10/2/2019", "4567.89"}
  };

  String value;
  XWPFDocument document = new XWPFDocument(new FileInputStream("WordTemplate.docx"));
  XWPFTable tableTemplate;
  CTTbl cTTblTemplate;
  XWPFTable tableCopy;
  XWPFTable table;
  XWPFTableRow row;
  XWPFTableCell cell;
  XmlCursor cursor;
  XWPFParagraph paragraph;
  XWPFRun run;
  
  // get Heading 2 style
  XWPFStyles styles = document.getStyles();
  XWPFStyle style = styles.getStyleWithName("Heading 2"); if (style == null) style = styles.getStyleWithName("heading 2");
  String heading2StyleId = (style != null)?style.getStyleId():"";
  
  //get first table (the template)
  tableTemplate = document.getTableArray(0);
  cTTblTemplate = tableTemplate.getCTTbl();
  cursor = setCursorToNextStartToken(cTTblTemplate);

  //fill in first data in first table (the template)
  for (int c = 0; c < data[0].length; c++) {
   value = data[0][c];
   row = tableTemplate.getRow(1);
   cell = row.getCell(c);
   removeCellValues(cell);
   cell.setText(value);
  }

  paragraph = document.insertNewParagraph(cursor); //insert new empty paragraph
  cursor = setCursorToNextStartToken(paragraph.getCTP());

  //fill in next data, each data row in one table
  for (int t = 1; t < data.length; t++) {
   paragraph = document.insertNewParagraph(cursor); //insert new empty paragraph
   paragraph.setStyle(heading2StyleId); //style it Heading 2
   run = paragraph.createRun(); 
   run.setText("Subheading " + (t+1));
   cursor = setCursorToNextStartToken(paragraph.getCTP());
     
   table = document.insertNewTbl(cursor); //insert new empty table at position t
   cursor = setCursorToNextStartToken(table.getCTTbl());

   tableCopy = new XWPFTable((CTTbl)cTTblTemplate.copy(), document); //copy the template table

   //fill in data in tableCopy
   for (int c = 0; c < data[t].length; c++) {
    value = data[t][c];
    row = tableCopy.getRow(1);
    cell = row.getCell(c);
    removeCellValues(cell);
    cell.setText(value);
   }
   document.setTable(t, tableCopy); //set tableCopy at position t instead of table

   paragraph = document.insertNewParagraph(cursor); //insert new empty paragraph
   cursor = setCursorToNextStartToken(paragraph.getCTP());
  }

  FileOutputStream out = new FileOutputStream("WordResult.docx");
  document.write(out);
  out.close();
  document.close();
 }
}

...produces following WordResult.docx.

enter image description here

Upvotes: 2

Related Questions