javafindapache-poihighlightfind-replace

How to Highlight Replaced Word Using Apache POI


How to highlight Replaced Word: I am using this code for find and replace words in docx file with Apache POI, but I want the word that is replaced to be highlighted or its color changed.

       XWPFDocument doc = new XWPFDocument(
         OPCPackage.open("d:\\1\\rpt.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("$$key$$")) {
           text = text.replace("$$key$$", "ABCD");//your content
           r.setText(text, 0);
      }
     }
    }
   }

Solution

  • Your code not will be able to replace the text "$$key$$" in all cases. It only works if the text is fully contained in one text run in Word. But there are circumstances where the text gets split up into multiple runs. Then your code will not work.

    I have provided replacement code using TextSegment already. See Apache POI: ${my_placeholder} is treated as three different runs, The value "name" and "surname" aren't read apache poi and Update content of references to text mark in DOCX.

    But as the additional requirement is to format the replaced text, this only can be resolved when the text gets placed into its own text run. Only text runs provide formatting in Word.

    The following code provides a method List<XWPFRun> getTextSegmentsInOwnRuns(XWPFParagraph paragraph, String textToFind) which is able to create own text runs for each occurence of textToFind in given paragraph. It returns those text runs as a List<XWPFRun> . This list then can be used to replace the text and to format.

    Complete example:

    import java.io.*;
    import org.apache.poi.xwpf.usermodel.*;
    import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
    
    import java.util.List;
    import java.util.ArrayList;
    
    public class WordReplaceTextSegmentAndFormat {
        
     static void cloneRunProperties(XWPFRun source, XWPFRun dest) { // clones the underlying w:rPr element
      CTR tRSource = source.getCTR();
      CTRPr rPrSource = tRSource.getRPr();
      if (rPrSource != null) {
       CTRPr rPrDest = (CTRPr)rPrSource.copy();
       CTR tRDest = dest.getCTR();
       tRDest.setRPr(rPrDest);
      }
     }
         
     static List<XWPFRun> getTextSegmentsInOwnRuns(XWPFParagraph paragraph, String textToFind) {
      TextSegment foundTextSegment = null;
      PositionInParagraph startPos = new PositionInParagraph(0, 0, 0); // start at position 0
      List<XWPFRun> resultRuns = new ArrayList<XWPFRun>();
      while((foundTextSegment = paragraph.searchText(textToFind, startPos)) != null) { // search all text segments having text to find
    
    //System.out.println(foundTextSegment.getBeginRun()+":"+foundTextSegment.getBeginText()+":"+foundTextSegment.getBeginChar());
    //System.out.println(foundTextSegment.getEndRun()+":"+foundTextSegment.getEndText()+":"+foundTextSegment.getEndChar());
    
       int posOfBeginRun = foundTextSegment.getBeginRun();
       // maybe there is text before textToFind in begin run
       XWPFRun beginRun = paragraph.getRuns().get(posOfBeginRun);
       String textInBeginRun = beginRun.getText(foundTextSegment.getBeginText());
       String textBefore = textInBeginRun.substring(0, foundTextSegment.getBeginChar()); // we only need the text before
    
       int posOfEndRun = foundTextSegment.getEndRun();
       // maybe there is text after textToFind in end run
       XWPFRun endRun = paragraph.getRuns().get(posOfEndRun);
       String textInEndRun = endRun.getText(foundTextSegment.getEndText());
       String textAfter = textInEndRun.substring(foundTextSegment.getEndChar() + 1); // we only need the text after
       
       XWPFRun resultRun = null;
       if (posOfEndRun == posOfBeginRun) { // there is only one run, split it up
        beginRun.setText(textBefore, foundTextSegment.getBeginText()); // begin run only contains text before textToFind
        resultRun = paragraph.insertNewRun(++posOfBeginRun); posOfEndRun++; // a new run added containing textToFind
        cloneRunProperties(beginRun, resultRun);
        resultRun.setText(textToFind, 0);
        resultRuns.add(resultRun);
        XWPFRun newEndRun = paragraph.insertNewRun(++posOfBeginRun); posOfEndRun++; // a new run added containing text after textToFind
        cloneRunProperties(beginRun, newEndRun);
        newEndRun.setText(textAfter, 0);
       } else { // there are multiple runs already, use beginRund and endRun, insert new run for textToFind
        beginRun.setText(textBefore, foundTextSegment.getBeginText()); // begin run only contains text before textToFind
        resultRun = paragraph.insertNewRun(++posOfBeginRun); posOfEndRun++; // a new run added containing textToFind
        cloneRunProperties(beginRun, resultRun);
        resultRun.setText(textToFind, 0);
        resultRuns.add(resultRun);
        endRun.setText(textAfter, foundTextSegment.getEndText()); // end run only contains text after textToFind
       }
    
       // runs between begin run and end run needs to be removed
       for (int runBetween = posOfEndRun - 1; runBetween > posOfBeginRun; runBetween--) {
        paragraph.removeRun(runBetween); // remove not needed runs
       }
       
       // start searchText from new position
       startPos = new PositionInParagraph(posOfEndRun, 0, 0);
    
      }
      return resultRuns;
     }
     
     public static void main(String[] args) throws Exception {
    
      String sourcePath = "./source.docx";
      String resultPath = "./result.docx";
      String textToFind = "$$key$$";
    
      try ( XWPFDocument document = new XWPFDocument(new FileInputStream(sourcePath));
            FileOutputStream out = new FileOutputStream(resultPath);
          ) {
    
       for (XWPFParagraph paragraph : document.getParagraphs()) {
        if (paragraph.getText().contains(textToFind)) {
         List<XWPFRun> runsWithTextToFind = getTextSegmentsInOwnRuns(paragraph, textToFind);
         System.out.println(runsWithTextToFind);
         for (XWPFRun run : runsWithTextToFind) {
          run.setText("Replaced", 0);
          //run.setUnderline(UnderlinePatterns.DOUBLE);
          run.getCTR().addNewRPr().addNewHighlight().setVal(STHighlightColor.YELLOW);
         }     
        }
       }  
       document.write(out); 
      }
     }
    }