Remplacer un texte dans Apache POI XWPF

je viens de trouver la bibliothèque poi D'Apache très utile pour éditer des fichiers Word en utilisant Java. Plus précisément, je veux éditer un DOCX Fichier utilisant les classes Xwpf D'Apache POI. Je n'ai trouvé aucune méthode / documentation appropriée à la suite de laquelle je pourrais faire ceci. Quelqu'un peut-il expliquer par étapes comment remplacer un texte dans un fichier DOCX?

** Le texte peut être en ligne / paragraphe ou dans une ligne de table/colonne

Merci d'Avance :)

30
demandé sur Gagan93 2014-03-08 15:46:25

9 réponses

la méthode dont vous avez besoin est XWPFRun.setText (String). Il vous suffit de parcourir le fichier jusqu'à ce que vous trouviez le XWPFRun qui vous intéresse, de trouver ce que vous voulez que le nouveau texte soit, et de le remplacer. (Une course est une séquence de texte avec la même mise en forme)

Vous devriez être en mesure de faire quelque chose comme:

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"));
49
répondu Gagravarr 2017-07-25 13:45:37

voici ce que nous avons fait pour le remplacement de texte en utilisant Apache POI. Nous avons trouvé qu'il ne valait pas la peine et plus simple de remplacer le texte d'un XWPFParagraph entier au lieu d'un run. Un run peut être divisé au hasard au milieu d'un mot car Microsoft Word est responsable de l'endroit où les Run sont créés dans le paragraphe d'un document. Par conséquent, le texte que vous pourriez être à la recherche pourrait être moitié dans une exécution et moitié dans une autre. En utilisant le texte intégral d'un paragraphe, en supprimant ses passages existants, et ajouter un nouveau passage avec le texte modifié semble résoudre le problème du remplacement du texte.

cependant il y a un coût de faire le remplacement au niveau du paragraphe; vous perdez le formatage des passages dans ce paragraphe. Par exemple, si, au milieu de votre paragraphe, vous aviez mis en caractères gras le mot "bits" et que, lors de l'analyse du fichier, vous aviez remplacé le mot "bits" par "bytes", le mot "bytes" ne serait plus en caractères gras. Parce que le boulonnage a été stocké avec un passage qui a été enlevé quand le texte intégral du paragraphe a été remplacé. Le code joint a commenté l'article, qui travaillait pour le remplacement de texte au niveau d'exécution si vous en avez besoin.

il est à noter que le texte ci-dessous fonctionne si le texte que vous insérez contient des caractères \n return. Nous n'avons pas pu trouver un moyen d'insérer des retours sans créer un run pour chaque section avant le retour et marquer le run addCarriageReturn(). Acclamations

    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;
//  }
9
répondu Kyle Willkomm 2014-08-07 15:40:53

si quelqu'un doit aussi garder le formatage du texte, ce code fonctionne mieux.

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);
            }
        }
    }
}
8
répondu Thierry Bodhuin 2016-10-25 14:33:10

ma tâche était de remplacer les textes du format $ {key} par les valeurs d'une carte dans un document word docx. Les solutions ci-dessus étaient un bon point de départ, mais n'ont pas pris en compte tous les cas: ${key} peut être réparti non seulement entre plusieurs passages, mais aussi entre plusieurs textes au sein d'un même passage. Je me suis donc retrouvé avec le code suivant:

    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;
}
6
répondu ron 2017-01-04 09:25:23

le premier morceau de code est de me donner une NullPointerException, quelqu'un sait ce qui ne va pas?

exécuter.getText ( position int) - à partir de la documentation: Retourner: le texte de ce texte s'exécute ou null s'il n'est pas défini

il suffit de vérifier si elle n'est pas null avant d'appeler contient ()

et btw si vous voulez remplacer le texte que vous avez besoin de mettre en position à partir de laquelle vous l'obtenez, dans ce cas R. setText (text, 0);. Sinon le texte sera ajouté pas remplacé

4
répondu birya 2014-07-18 10:09:55

la réponse acceptée ici nécessite une mise à jour de plus avec Justin Skiles mise à jour. R. setText (text, 0); Raison: si setText n'est pas mis à jour avec la variable pos, la sortie sera la combinaison de l'ancienne chaîne et de la chaîne replace.

3
répondu Sherin 2015-04-01 09:48:45

il y a le replaceParagraph implémentation qui remplace ${key}value (fieldsForReport paramètre) et sauve le format en fusionnant runs table des matières ${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);
            }
        }
    }
}

mise en Œuvre replaceParagraph

test unitaire

3
répondu Dmitry Stolbov 2018-09-13 05:14:14

je suggère ma solution pour remplacer le texte entre #, par exemple: ce# signet doit être remplacé. Il est remplacer dans:

  • paragraphes;
  • tables;
  • pieds de page.

aussi, il prend en compte les situations, quand le symbole # et le signet sont dans les passages séparés (remplacer variable entre les différentes pistes).

ici Lien vers le code: https://gist.github.com/aerobium/bf02e443c079c5caec7568e167849dda

2
répondu Optio 2017-10-03 11:15:55

A la date de rédaction, aucune des réponses ne remplace correctement.

Gagravars réponse ne comprend pas les cas où les mots à remplacer sont divisés en passages; Thierry Boduins solution laisse parfois les mots à remplacer vides quand ils étaient après d'autres mots à remplacer, aussi il ne vérifie pas les tableaux.

utilisation de Gagtavars answer comme base j'ai aussi coché la run before current run si le texte des deux run contient le mot à remplacer, ajoutant else block. Mon ajout dans 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))
    }
}

c'est la liste de courses dans un paragraphe. Idem pour le bloc de recherche dans la table. Avec cette solution, je n'avais pas encore de problèmes. Tout le formatage est intact.

Edit: j'ai fait une java lib pour le remplacement, check it out: https://github.com/deividasstr/docx-word-replacer

2
répondu Deividas Strioga 2018-04-16 07:12:43