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:54

If somebody needs also to keep the formatting of the text, this code works better.

private static Map<Integer, XWPFRun> getPosToRuns(XWPFParagraph paragraph) {
    int pos = 0;
    Map<Integer, XWPFRun> map = new HashMap<Integer, XWPFRun>(10);
    for (XWPFRun run : paragraph.getRuns()) {
        String runText = run.text();
        if (runText != null) {
            for (int i = 0; i < runText.length(); i++) {
                map.put(pos + i, run);
            }
            pos += runText.length();
        }
    }
    return (map);
}

public static <V> void replace(XWPFDocument document, Map<String, V> map) {
    List<XWPFParagraph> paragraphs = document.getParagraphs();
    for (XWPFParagraph paragraph : paragraphs) {
        replace(paragraph, map);
    }
}

public static <V> void replace(XWPFDocument document, String searchText, V replacement) {
    List<XWPFParagraph> paragraphs = document.getParagraphs();
    for (XWPFParagraph paragraph : paragraphs) {
        replace(paragraph, searchText, replacement);
    }
}

private static <V> void replace(XWPFParagraph paragraph, Map<String, V> map) {
    for (Map.Entry<String, V> entry : map.entrySet()) {
        replace(paragraph, entry.getKey(), entry.getValue());
    }
}

public static <V> void replace(XWPFParagraph paragraph, String searchText, V replacement) {
    boolean found = true;
    while (found) {
        found = false;
        int pos = paragraph.getText().indexOf(searchText);
        if (pos >= 0) {
            found = true;
            Map<Integer, XWPFRun> posToRuns = getPosToRuns(paragraph);
            XWPFRun run = posToRuns.get(pos);
            XWPFRun lastRun = posToRuns.get(pos + searchText.length() - 1);
            int runNum = paragraph.getRuns().indexOf(run);
            int lastRunNum = paragraph.getRuns().indexOf(lastRun);
            String texts[] = replacement.toString().split("\n");
            run.setText(texts[0], 0);
            XWPFRun newRun = run;
            for (int i = 1; i < texts.length; i++) {
                newRun.addCarriageReturn();
                newRun = paragraph.insertNewRun(runNum + i);
                /*
                    We should copy all style attributes
                    to the newRun from run
                    also from background color, ...
                    Here we duplicate only the simple attributes...
                 */
                newRun.setText(texts[i]);
                newRun.setBold(run.isBold());
                newRun.setCapitalized(run.isCapitalized());
                // newRun.setCharacterSpacing(run.getCharacterSpacing());
                newRun.setColor(run.getColor());
                newRun.setDoubleStrikethrough(run.isDoubleStrikeThrough());
                newRun.setEmbossed(run.isEmbossed());
                newRun.setFontFamily(run.getFontFamily());
                newRun.setFontSize(run.getFontSize());
                newRun.setImprinted(run.isImprinted());
                newRun.setItalic(run.isItalic());
                newRun.setKerning(run.getKerning());
                newRun.setShadow(run.isShadowed());
                newRun.setSmallCaps(run.isSmallCaps());
                newRun.setStrikeThrough(run.isStrikeThrough());
                newRun.setSubscript(run.getSubscript());
                newRun.setUnderline(run.getUnderline());
            }
            for (int i = lastRunNum + texts.length - 1; i > runNum + texts.length - 1; i--) {
                paragraph.removeRun(i);
            }
        }
    }
}
查看更多
相关推荐>>
3楼-- · 2019-01-08 07:55

As of the date of writing, none of the answers replace properly.

Gagravars answer does not include cases where words to replace are split in runs; Thierry Boduins solution sometimes left words to replace blank when they were after other words to replace, also it does not check tables.

Using Gagtavars answer as base I have also checked the run before current run if the text of both runs contain the word to replace, adding else block. My addition in kotlin:

if (text != null) {
        if (text.contains(findText)) {
            text = text.replace(findText, replaceText)
            r.setText(text, 0)
        } else if (i > 0 && p.runs[i - 1].getText(0).plus(text).contains(findText)) {
            val pos = p.runs[i - 1].getText(0).indexOf('$')
            text = textOfNotFullSecondRun(text, findText)
            r.setText(text, 0)
            val findTextLengthInFirstRun = findTextPartInFirstRun(p.runs[i - 1].getText(0), findText)
            val prevRunText = p.runs[i - 1].getText(0).replaceRange(pos, findTextLengthInFirstRun, replaceText)
            p.runs[i - 1].setText(prevRunText, 0)
        }
    }

private fun textOfNotFullSecondRun(text: String, findText: String): String {
    return if (!text.contains(findText)) {
        textOfNotFullSecondRun(text, findText.drop(1))
    } else {
        text.replace(findText, "")
    }
}

private fun findTextPartInFirstRun(text: String, findText: String): Int {
    return if (text.contains(findText)) {
        findText.length
    } else {
        findTextPartInFirstRun(text, findText.dropLast(1))
    }
}

it is the list of runs in a paragraph. Same with the search block in the table. With this solution I did not have any issues yet. All formatting is intact.

Edit: I made a java lib for replacing, check it out: https://github.com/deividasstr/docx-word-replacer

查看更多
迷人小祖宗
4楼-- · 2019-01-08 07:57

my task was to replace texts of the format ${key} with values of a map within a word docx document. The above solutions were a good starting point but did not take into account all cases: ${key} can be spread not only across multiple runs but also across multiple texts within a run. I therefore ended up with the following code:

    private void replace(String inFile, Map<String, String> data, OutputStream out) throws Exception, IOException {
    XWPFDocument doc = new XWPFDocument(OPCPackage.open(inFile));
    for (XWPFParagraph p : doc.getParagraphs()) {
        replace2(p, data);
    }
    for (XWPFTable tbl : doc.getTables()) {
        for (XWPFTableRow row : tbl.getRows()) {
            for (XWPFTableCell cell : row.getTableCells()) {
                for (XWPFParagraph p : cell.getParagraphs()) {
                    replace2(p, data);
                }
            }
        }
    }
    doc.write(out);
}

private void replace2(XWPFParagraph p, Map<String, String> data) {
    String pText = p.getText(); // complete paragraph as string
    if (pText.contains("${")) { // if paragraph does not include our pattern, ignore
        TreeMap<Integer, XWPFRun> posRuns = getPosToRuns(p);
        Pattern pat = Pattern.compile("\\$\\{(.+?)\\}");
        Matcher m = pat.matcher(pText);
        while (m.find()) { // for all patterns in the paragraph
            String g = m.group(1);  // extract key start and end pos
            int s = m.start(1);
            int e = m.end(1);
            String key = g;
            String x = data.get(key);
            if (x == null)
                x = "";
            SortedMap<Integer, XWPFRun> range = posRuns.subMap(s - 2, true, e + 1, true); // get runs which contain the pattern
            boolean found1 = false; // found $
            boolean found2 = false; // found {
            boolean found3 = false; // found }
            XWPFRun prevRun = null; // previous run handled in the loop
            XWPFRun found2Run = null; // run in which { was found
            int found2Pos = -1; // pos of { within above run
            for (XWPFRun r : range.values())
            {
                if (r == prevRun)
                    continue; // this run has already been handled
                if (found3)
                    break; // done working on current key pattern
                prevRun = r;
                for (int k = 0;; k++) { // iterate over texts of run r
                    if (found3)
                        break;
                    String txt = null;
                    try {
                        txt = r.getText(k); // note: should return null, but throws exception if the text does not exist
                    } catch (Exception ex) {

                    }
                    if (txt == null)
                        break; // no more texts in the run, exit loop
                    if (txt.contains("$") && !found1) {  // found $, replace it with value from data map
                        txt = txt.replaceFirst("\\$", x);
                        found1 = true;
                    }
                    if (txt.contains("{") && !found2 && found1) {
                        found2Run = r; // found { replace it with empty string and remember location
                        found2Pos = txt.indexOf('{');
                        txt = txt.replaceFirst("\\{", "");
                        found2 = true;
                    }
                    if (found1 && found2 && !found3) { // find } and set all chars between { and } to blank
                        if (txt.contains("}"))
                        {
                            if (r == found2Run)
                            { // complete pattern was within a single run
                                txt = txt.substring(0, found2Pos)+txt.substring(txt.indexOf('}'));
                            }
                            else // pattern spread across multiple runs
                                txt = txt.substring(txt.indexOf('}'));
                        }
                        else if (r == found2Run) // same run as { but no }, remove all text starting at {
                            txt = txt.substring(0,  found2Pos);
                        else
                            txt = ""; // run between { and }, set text to blank
                    }
                    if (txt.contains("}") && !found3) {
                        txt = txt.replaceFirst("\\}", "");
                        found3 = true;
                    }
                    r.setText(txt, k);
                }
            }
        }
        System.out.println(p.getText());

    }

}

private TreeMap<Integer, XWPFRun> getPosToRuns(XWPFParagraph paragraph) {
    int pos = 0;
    TreeMap<Integer, XWPFRun> map = new TreeMap<Integer, XWPFRun>();
    for (XWPFRun run : paragraph.getRuns()) {
        String runText = run.text();
        if (runText != null && runText.length() > 0) {
            for (int i = 0; i < runText.length(); i++) {
                map.put(pos + i, run);
            }
            pos += runText.length();
        }

    }
    return map;
}
查看更多
登录 后发表回答