Replacing a text in Apache POI XWPF

2019-01-08 07:07发布

I just found Apache POI library very useful for editing Word files using Java. Specifically, I want to edit a DOCX file using Apache POI's XWPF classes. I found no proper method / documentation following which I could do this. Can somebody please explain in steps, how to replace some text in a DOCX file.

** The text may be in a line / paragraph or in a table row/column

Thanks in Advance :)

9条回答
男人必须洒脱
2楼-- · 2019-01-08 07:35

The answer accepted here needs one more update along with Justin Skiles update. r.setText(text, 0); Reason: If not updating setText with pos variable, the output will be the combination of old string and replace string.

查看更多
别忘想泡老子
3楼-- · 2019-01-08 07:42

I suggest my solution for replacing text between #, for example: This #bookmark# should be replaced. It is replace in:

  • paragraphs;
  • tables;
  • footers.

Also, it takes into account situations, when symbol # and bookmark are in the separated runs (replace variable between different runs).

Here link to the code: https://gist.github.com/aerobium/bf02e443c079c5caec7568e167849dda

查看更多
一夜七次
4楼-- · 2019-01-08 07:47

The method you need is XWPFRun.setText(String). Simply work your way through the file until you find the XWPFRun of interest, work out what you want the new text to be, and replace it. (A run is a sequence of text with the same formatting)

You should be able to do something like:

XWPFDocument doc = new XWPFDocument(OPCPackage.open("input.docx"));
for (XWPFParagraph p : doc.getParagraphs()) {
    List<XWPFRun> runs = p.getRuns();
    if (runs != null) {
        for (XWPFRun r : runs) {
            String text = r.getText(0);
            if (text != null && text.contains("needle")) {
                text = text.replace("needle", "haystack");
                r.setText(text, 0);
            }
        }
    }
}
for (XWPFTable tbl : doc.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.contains("needle")) {
                text = text.replace("needle", "haystack");
                r.setText(text,0);
              }
            }
         }
      }
   }
}
doc.write(new FileOutputStream("output.docx"));
查看更多
走好不送
5楼-- · 2019-01-08 07:48

There is the replaceParagraph implementation that replaces ${key} with value (the fieldsForReport parameter) and saves format by merging runs contents ${key}.

private void replaceParagraph(XWPFParagraph paragraph, Map<String, String> fieldsForReport) throws POIXMLException {
    String find, text, runsText;
    List<XWPFRun> runs;
    XWPFRun run, nextRun;
    for (String key : fieldsForReport.keySet()) {
        text = paragraph.getText();
        if (!text.contains("${"))
            return;
        find = "${" + key + "}";
        if (!text.contains(find))
            continue;
        runs = paragraph.getRuns();
        for (int i = 0; i < runs.size(); i++) {
            run = runs.get(i);
            runsText = run.getText(0);
            if (runsText.contains("${") || (runsText.contains("$") && runs.get(i + 1).getText(0).substring(0, 1).equals("{"))) {
                while (!runsText.contains("}")) {
                    nextRun = runs.get(i + 1);
                    runsText = runsText + nextRun.getText(0);
                    paragraph.removeRun(i + 1);
                }
                run.setText(runsText.contains(find) ?
                        runsText.replace(find, fieldsForReport.get(key)) :
                        runsText, 0);
            }
        }
    }
}

Implementation replaceParagraph

Unit test

查看更多
姐就是有狂的资本
6楼-- · 2019-01-08 07:52

The first chunk of code is giing me a NullPointerException, anyone know what is wrong?

run.getText(int position) - from documentation: Returns: the text of this text run or null if not set

Just check if it is not null before calling contains() on it

And btw if you want to replace the text you need to set it in position from which you get it, in this case r.setText(text, 0);. Otherwise text will be added not replaced

查看更多
霸刀☆藐视天下
7楼-- · 2019-01-08 07:53

Here is what we did for text replacement using Apache POI. We found that it was not worth the hassle and simpler to replace the text of an entire XWPFParagraph instead of a run. A run can be randomly split in the middle of a word as Microsoft Word is in charge of where runs are created within the paragraph of a document. Therefore the text you might be searching for could be half in one run and half in another. Using the full text of a paragraph, removing its existing runs, and adding a new run with the adjusted text seems to solve the problem of text replacement.

However there is a cost of doing the replacement at the paragraph level; you lose the formatting of the runs in that paragraph. For example if in the middle of your paragraph you had bolded the word "bits", and then when parsing the file you replaced the word "bits" with "bytes", the word "bytes" would no longer be bolded. Because the bolding was stored with a run that was removed when the paragraph's entire body of text was replaced. The attached code has a commented out section that was working for replacement of text at the run level if you need it.

It should also be noted that the below works if the text you are inserting contains \n return characters. We could not find a way to insert returns without creating a run for each section prior to the return and marking the run addCarriageReturn(). Cheers

    package com.healthpartners.hcss.client.external.word.replacement;

import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;

public class TextReplacer {
    private String searchValue;
    private String replacement;

    public TextReplacer(String searchValue, String replacement) {
        this.searchValue = searchValue;
        this.replacement = replacement;
    }

    public void replace(XWPFDocument document) {
        List<XWPFParagraph> paragraphs = document.getParagraphs();

    for (XWPFParagraph xwpfParagraph : paragraphs) {
        replace(xwpfParagraph);
    }
}

private void replace(XWPFParagraph paragraph) {
    if (hasReplaceableItem(paragraph.getText())) {
        String replacedText = StringUtils.replace(paragraph.getText(), searchValue, replacement);

        removeAllRuns(paragraph);

        insertReplacementRuns(paragraph, replacedText);
    }
}

private void insertReplacementRuns(XWPFParagraph paragraph, String replacedText) {
    String[] replacementTextSplitOnCarriageReturn = StringUtils.split(replacedText, "\n");

    for (int j = 0; j < replacementTextSplitOnCarriageReturn.length; j++) {
        String part = replacementTextSplitOnCarriageReturn[j];

        XWPFRun newRun = paragraph.insertNewRun(j);
        newRun.setText(part);

        if (j+1 < replacementTextSplitOnCarriageReturn.length) {
            newRun.addCarriageReturn();
        }
    }       
}

private void removeAllRuns(XWPFParagraph paragraph) {
    int size = paragraph.getRuns().size();
    for (int i = 0; i < size; i++) {
        paragraph.removeRun(0);
    }
}

private boolean hasReplaceableItem(String runText) {
    return StringUtils.contains(runText, searchValue);
}

//REVISIT The below can be removed if Michele tests and approved the above less versatile replacement version

//  private void replace(XWPFParagraph paragraph) {
//      for (int i = 0; i < paragraph.getRuns().size()  ; i++) {
//          i = replace(paragraph, i);
//      }
//  }

//  private int replace(XWPFParagraph paragraph, int i) {
//      XWPFRun run = paragraph.getRuns().get(i);
//      
//      String runText = run.getText(0);
//      
//      if (hasReplaceableItem(runText)) {
//          return replace(paragraph, i, run);
//      }
//      
//      return i;
//  }

//  private int replace(XWPFParagraph paragraph, int i, XWPFRun run) {
//      String runText = run.getCTR().getTArray(0).getStringValue();
//      
//      String beforeSuperLong = StringUtils.substring(runText, 0, runText.indexOf(searchValue));
//      
//      String[] replacementTextSplitOnCarriageReturn = StringUtils.split(replacement, "\n");
//      
//      String afterSuperLong = StringUtils.substring(runText, runText.indexOf(searchValue) + searchValue.length());
//      
//      Counter counter = new Counter(i);
//      
//      insertNewRun(paragraph, run, counter, beforeSuperLong);
//      
//      for (int j = 0; j < replacementTextSplitOnCarriageReturn.length; j++) {
//          String part = replacementTextSplitOnCarriageReturn[j];
//
//          XWPFRun newRun = insertNewRun(paragraph, run, counter, part);
//          
//          if (j+1 < replacementTextSplitOnCarriageReturn.length) {
//              newRun.addCarriageReturn();
//          }
//      }
//      
//      insertNewRun(paragraph, run, counter, afterSuperLong);
//      
//      paragraph.removeRun(counter.getCount());
//      
//      return counter.getCount();
//  }

//  private class Counter {
//      private int i;
//      
//      public Counter(int i) {
//          this.i = i;
//      }
//      
//      public void increment() {
//          i++;
//      }
//      
//      public int getCount() {
//          return i;
//      }
//  }

//  private XWPFRun insertNewRun(XWPFParagraph xwpfParagraph, XWPFRun run, Counter counter, String newText) {
//      XWPFRun newRun = xwpfParagraph.insertNewRun(counter.i);
//      newRun.getCTR().set(run.getCTR());
//      newRun.getCTR().getTArray(0).setStringValue(newText);
//      
//      counter.increment();
//      
//      return newRun;
//  }
查看更多
登录 后发表回答