javaapache-poidocxxwpf

Change font style in a specific word from docx file using Java Apache POI


I'm using Apache POI XWPF to manipulate a docx file, I need to update some words of paragraph and change the font style of it. For updating a single word, it's ok, let's assume that my docx content has the paragraphs bellow:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer pretium sodales nisl, ut ornare ligula vehicula vitae. Fusce non magna feugiat, sagittis massa at, fermentum nibh. Curabitur auctor leo vitae sem tempus, facilisis feugiat orci vestibulum. Mauris molestie sem sem, id venenatis arcu congue id. Duis nulla quam, commodo vel dolor eget, tempor varius sem. Pellentesque gravida, lectus eu mollis pretium, sapien nibh consectetur lacus, non pellentesque lectus ipsum ornare eros. Maecenas at magna nunc.

Nulla sagittis aliquam maximus. Cras faucibus id neque sed faucibus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Phasellus fermentum nulla sed nibh varius maximus. Pellentesque dui lorem, luctus non lorem a, blandit lacinia arcu. Nunc porttitor erat ut elit hendrerit malesuada. Sed ut ex ultricies, rutrum est ut, vulputate orci. Suspendisse vitae diam ullamcorper, pulvinar tellus vitae, feugiat ex. In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin massa lectus, venenatis eget massa a, fringilla molestie nisl.

I just do something like it, in this case, the code works, I just want to update the word "ipsum" to "hello world":

   File file = new File("document.docx");
   FileInputStream fis = new FileInputStream(file.getAbsolutePath());

   XWPFDocument document = new XWPFDocument(fis);

   List<XWPFParagraph> paragraphs = document.getParagraphs();

    for (XWPFParagraph p: paragraphs) {
        List<XWPFRun> runs = p.getRuns();
        for (XWPFRun r: runs) {
           String endText = r.getText(0).replaceFirst("ipsum" , "hello world");
           r.setText(endText,0);
        }
    }

        document.write(new FileOutputStream(uuid + ".docx"));
        fis.close();
        document.close();

But if I try to change the text style, like bold, or even change the font color, all of "run" will change, not only the world that I've searched. What I have to do to change the font style only of the word "ipsum" in the example text?


Solution

  • You have concluded already that the text run is the lowest text entity which may have text formatting. So the need is taking a word or words which shall be formatted differently into their own text runs. Then one can format those text runs.

    I have answered this already in Split a XWPFRun into multiple runs. But I will show it again for your special case.

    Having the souce.docx like this:

    enter image description here

    Code:

    import java.io.*;
    import org.apache.poi.xwpf.usermodel.*;
    import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
    
    import java.util.*;
    import java.awt.Desktop;
    
    public class WordFormatWords {
    
     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 void formatWord(XWPFParagraph paragraph, String keyword, Map<String, String> formats) {
      int runNumber = 0;
      while (runNumber < paragraph.getRuns().size()) { //go through all runs, we cannot use for each since we will possibly insert new runs
       XWPFRun run = paragraph.getRuns().get(runNumber);
       XWPFRun run2 = run;
       String runText = run.getText(0);
       if (runText != null && runText.contains(keyword)) { //if we have a run with keyword in it, then
    
        char[] runChars = runText.toCharArray(); //split run text into characters
        StringBuffer sb = new StringBuffer();
        for (int charNumber = 0; charNumber < runChars.length; charNumber++) { //go through all characters in that run
         sb.append(runChars[charNumber]); //buffer all characters
         runText = sb.toString();
         if (runText.endsWith(keyword)) { //if the bufferend character stream ends with the keyword  
          //set all chars, which are current buffered, except the keyword, as the text of the actual run
          run.setText(runText.substring(0, runText.length() - keyword.length()), 0); 
          run2 = paragraph.insertNewRun(++runNumber); //insert new run for the formatted keyword
          cloneRunProperties(run, run2); // clone the run properties from original run
          run2.setText(keyword, 0); // set the keyword in run
          for (String toSet : formats.keySet()) { // do the additional formatting
           if ("color".equals(toSet)) {
            run2.setColor(formats.get(toSet));
           } else if ("bold".equals(toSet)) {
            run2.setBold(Boolean.valueOf(formats.get(toSet)));
           }
          }
          run2 = paragraph.insertNewRun(++runNumber); //insert a new run for the next characters
          cloneRunProperties(run, run2); // clone the run properties from original run
          run = run2;
          sb = new StringBuffer(); //empty the buffer
         } 
        }
        run.setText(sb.toString(), 0); //set all characters, which are currently buffered, as the text of the actual run
    
       }
       runNumber++;
      }
     }
    
    
     public static void main(String[] args) throws Exception {
    
      XWPFDocument doc = new XWPFDocument(new FileInputStream("source.docx"));
    
      String[] keywords = new String[]{"ipsum"};
      Map<String, String> formats = new HashMap<String, String>();
      formats.put("bold", "true");
      formats.put("color", "DC143C");
    
      for (XWPFParagraph paragraph : doc.getParagraphs()) { //go through all paragraphs
       for (String keyword : keywords) {
        formatWord(paragraph, keyword, formats);
       }
      }
    
      FileOutputStream out = new FileOutputStream("result.docx");
      doc.write(out);
      out.close();
      doc.close();
    
      System.out.println("Done");
      Desktop.getDesktop().open(new File("result.docx"));
    
     }
    }
    

    This code leads to a result.docx like this:

    enter image description here