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

Cliff Pereira :

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.

the word template


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

Axel Richter :

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:

enter image description here

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:

enter image description here

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=140879&siteId=1