Remplacement d’un texte dans Apache POI XWPF

Je viens de trouver la bibliothèque Apache POI très utile pour l’édition de fichiers Word à l’aide de Java. Plus précisément, je souhaite modifier un fichier DOCX à l’aide des classes XWPF d’Apache POI. Je n’ai trouvé aucune méthode / documentation appropriée à la suite de quoi je pouvais le faire. Quelqu’un peut-il s’il vous plaît expliquer en étapes, comment remplacer du texte dans un fichier DOCX.

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

Merci d’avance 🙂

    La méthode dont vous avez besoin est XWPFRun.setText (Ssortingng) . Parcourez simplement le fichier jusqu’à trouver le XWPFRun qui vous intéresse, déterminez ce que vous souhaitez que le nouveau texte apparaisse et remplacez-le. (Une exécution est une séquence de texte avec le même formatage)

    Vous devriez pouvoir faire quelque chose comme:

    XWPFDocument doc = new XWPFDocument(OPCPackage.open("input.docx")); for (XWPFParagraph p : doc.getParagraphs()) { List runs = p.getRuns(); if (runs != null) { for (XWPFRun r : runs) { Ssortingng 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()) { Ssortingng 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")); 

    Voici ce que nous avons fait pour le remplacement de texte avec Apache POI. Nous avons constaté que le remplacement du texte de tout un XWPFParagraph au lieu d’une parsing ne valait pas la peine. Une exécution peut être scindée de manière aléatoire au milieu d’un mot, car Microsoft Word est responsable de la création des exécutions dans le paragraphe d’un document. Par conséquent, le texte que vous recherchez peut être la moitié d’un passage et l’autre moitié. Utiliser le texte intégral d’un paragraphe, supprimer ses exécutions existantes et append une nouvelle exécution avec le texte ajusté semble résoudre le problème du remplacement du texte.

    Cependant, le remplacement au niveau des paragraphes a un coût. vous perdez le formatage des pistes dans ce paragraphe. Par exemple, si vous aviez mis en gras le mot “bits” au milieu de votre paragraphe, et que lors de l’parsing du fichier que vous avez remplacé par “octets”, le mot “octets” ne serait plus en gras. Parce que les caractères gras ont été stockés avec une exécution qui a été supprimée lors du remplacement de tout le corps du texte du paragraphe. Le code joint contient une section commentée qui fonctionnait pour le remplacement du texte au niveau de l’exécution, si vous en avez besoin.

    Il convient également de noter que ce qui suit fonctionne si le texte que vous insérez contient \ n caractères de retour. Nous n’avons pas pu trouver de moyen d’insérer des retours sans créer une exécution pour chaque section avant le retour et marquer l’exécution addCarriageReturn (). À votre santé

      package com.healthpartners.hcss.client.external.word.replacement; import java.util.List; import org.apache.commons.lang.SsortingngUtils; 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 Ssortingng searchValue; private Ssortingng replacement; public TextReplacer(Ssortingng searchValue, Ssortingng replacement) { this.searchValue = searchValue; this.replacement = replacement; } public void replace(XWPFDocument document) { List paragraphs = document.getParagraphs(); for (XWPFParagraph xwpfParagraph : paragraphs) { replace(xwpfParagraph); } } private void replace(XWPFParagraph paragraph) { if (hasReplaceableItem(paragraph.getText())) { Ssortingng replacedText = SsortingngUtils.replace(paragraph.getText(), searchValue, replacement); removeAllRuns(paragraph); insertReplacementRuns(paragraph, replacedText); } } private void insertReplacementRuns(XWPFParagraph paragraph, Ssortingng replacedText) { Ssortingng[] replacementTextSplitOnCarriageReturn = SsortingngUtils.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; // } 

    Si quelqu’un doit également conserver la mise en forme du texte, ce code fonctionne mieux.

     private static Map getPosToRuns(XWPFParagraph paragraph) { int pos = 0; Map map = new HashMap(10); for (XWPFRun run : paragraph.getRuns()) { Ssortingng 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  void replace(XWPFDocument document, Map map) { List paragraphs = document.getParagraphs(); for (XWPFParagraph paragraph : paragraphs) { replace(paragraph, map); } } public static  void replace(XWPFDocument document, Ssortingng searchText, V replacement) { List paragraphs = document.getParagraphs(); for (XWPFParagraph paragraph : paragraphs) { replace(paragraph, searchText, replacement); } } private static  void replace(XWPFParagraph paragraph, Map map) { for (Map.Entry entry : map.entrySet()) { replace(paragraph, entry.getKey(), entry.getValue()); } } public static  void replace(XWPFParagraph paragraph, Ssortingng searchText, V replacement) { boolean found = true; while (found) { found = false; int pos = paragraph.getText().indexOf(searchText); if (pos >= 0) { found = true; Map 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); Ssortingng texts[] = replacement.toSsortingng().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); } } } } 

    Ma tâche consistait à remplacer les textes du format $ {key} par les valeurs d’une carte dans un document Word docx. Les solutions ci-dessus constituaient un bon sharepoint départ, mais ne prenaient pas en compte tous les cas: $ {key} peut être réparti non seulement sur plusieurs exécutions, mais également sur plusieurs textes au cours d’une exécution. J’ai donc fini avec le code suivant:

      private void replace(Ssortingng inFile, Map 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 data) { Ssortingng pText = p.getText(); // complete paragraph as ssortingng if (pText.contains("${")) { // if paragraph does not include our pattern, ignore TreeMap posRuns = getPosToRuns(p); Pattern pat = Pattern.comstack("\\$\\{(.+?)\\}"); Matcher m = pat.matcher(pText); while (m.find()) { // for all patterns in the paragraph Ssortingng g = m.group(1); // extract key start and end pos int s = m.start(1); int e = m.end(1); Ssortingng key = g; Ssortingng x = data.get(key); if (x == null) x = ""; SortedMap 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; Ssortingng 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 ssortingng 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.subssortingng(0, found2Pos)+txt.subssortingng(txt.indexOf('}')); } else // pattern spread across multiple runs txt = txt.subssortingng(txt.indexOf('}')); } else if (r == found2Run) // same run as { but no }, remove all text starting at { txt = txt.subssortingng(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 getPosToRuns(XWPFParagraph paragraph) { int pos = 0; TreeMap map = new TreeMap(); for (XWPFRun run : paragraph.getRuns()) { Ssortingng 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; } 

    Le premier morceau de code me donne un NullPointerException, tout le monde sait ce qui ne va pas?

    run.getText (int position) – à partir de la documentation: Returns: le texte de ce texte est exécuté ou null s’il n’est pas défini

    Il suffit de vérifier si ce n’est pas null avant d’appeler contient () dessus

    Et d’ailleurs si vous voulez remplacer le texte, vous devez le définir dans la position d’où vous l’avez obtenu, dans ce cas, r.setText (text, 0) ;. Sinon le texte sera ajouté non remplacé

    La réponse acceptée ici nécessite une mise à jour supplémentaire avec la mise à jour de Justin Skiles. r.setText (text, 0); Raison: Si vous ne mettez pas à jour setText avec une variable pos, le résultat sera la combinaison de l’ancienne chaîne et de la chaîne de remplacement.

    Il existe replaceParagraph implémentation replaceParagraph qui remplace ${key} par value (le paramètre fieldsForReport ) et enregistre le format en fusionnant runs contenu du contenu ${key} .

     private void replaceParagraph(XWPFParagraph paragraph, Map fieldsForReport) throws POIXMLException { Ssortingng find, text, runsText; List runs; XWPFRun run, nextRun; for (Ssortingng 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); } } } } 

    Implémentation replaceParagraph

    Test de l'unité

    Je suggère ma solution pour remplacer le texte entre #, par exemple: Ce # bookmark # doit être remplacé. C’est remplacer dans:

    • paragraphes;
    • les tables;
    • pieds de page.

    En outre, il prend en compte les situations où le symbole # et le signet sont dans les exécutions séparées ( remplace la variable entre les exécutions différentes ).

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

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

    La réponse de Gagravars n’inclut pas les cas où les mots à remplacer sont divisés en séries; La solution de Thierry Boduins laissait parfois des mots pour remplacer les blancs alors qu’ils cherchaient d’autres mots, mais ne vérifie pas les tableaux.

    En utilisant Gagtavars comme base, j’ai également vérifié l’exécution avant l’exécution courante si le texte des deux exécutions contient le mot à remplacer, en ajoutant un autre bloc. Mon ajout à 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: Ssortingng, findText: Ssortingng): Ssortingng { return if (!text.contains(findText)) { textOfNotFullSecondRun(text, findText.drop(1)) } else { text.replace(findText, "") } } private fun findTextPartInFirstRun(text: Ssortingng, findText: Ssortingng): Int { return if (text.contains(findText)) { findText.length } else { findTextPartInFirstRun(text, findText.dropLast(1)) } } 

    c’est la liste des pistes dans un paragraphe. Idem avec le bloc de recherche dans la table. Avec cette solution, je n’ai pas encore eu de problèmes. Tout le formatage est intact.

    Edit: j’ai créé une librairie Java pour la remplacer, consultez-la: https://github.com/deividasstr/docx-word-replacer