使用POI讀寫word docx檔案

風靈使發表於2018-08-04

POI在讀寫word docx檔案時是通過xwpf模組來進行的,其核心是XWPFDocument。一個XWPFDocument代表一個docx文件,其可以用來讀docx文件,也可以用來寫docx文件。XWPFDocument中主要包含下面這幾種物件:

XWPFParagraph:代表一個段落。

XWPFRun:代表具有相同屬性的一段文字。

XWPFTable:代表一個表格。

XWPFTableRow:表格的一行。

XWPFTableCell:表格對應的一個單元格。

1.讀docx檔案

跟讀doc檔案一樣,POI在讀docx檔案的時候也有兩種方式,通過XWPFWordExtractor和通過XWPFDocument。在XWPFWordExtractor讀取資訊時其內部還是通過XWPFDocument來獲取的。

1.1通過XWPFWordExtractor

在使用XWPFWordExtractor讀取docx文件的內容時,我們只能獲取到其文字,而不能獲取到其文字對應的屬性值。下面是一段使用XWPFWordExtractor來讀取docx文件內容的示例程式碼:

    public class XwpfTest {

       /**
        * 通過XWPFWordExtractor訪問XWPFDocument的內容
        * @throws Exception
        */
       @Test
       public void testReadByExtractor() throws Exception {
          InputStream is = new FileInputStream("D:\\test.docx");
          XWPFDocument doc = new XWPFDocument(is);
          XWPFWordExtractor extractor = new XWPFWordExtractor(doc);
          String text = extractor.getText();
          System.out.println(text);
          CoreProperties coreProps = extractor.getCoreProperties();
          this.printCoreProperties(coreProps);
          this.close(is);
       }

       /**
        * 輸出CoreProperties資訊
        * @param coreProps
        */
       private void printCoreProperties(CoreProperties coreProps) {
          System.out.println(coreProps.getCategory());   //分類
          System.out.println(coreProps.getCreator()); //建立者
          System.out.println(coreProps.getCreated()); //建立時間
          System.out.println(coreProps.getTitle());   //標題
       }

       /**
        * 關閉輸入流
        * @param is
        */
       private void close(InputStream is) {
          if (is != null) {
             try {
                is.close();
             } catch (IOException e) {
                e.printStackTrace();
             }
          }
       }

    }

1.2 通過XWPFDocument

在通過XWPFDocument讀取docx文件時,我們就可以獲取到文字比較精確的屬性資訊了。比如我們可以獲取到某一個XWPFParagraphXWPFRun或者是某一個XWPFTable,包括它們對應的屬性資訊。下面是一個使用XWPFDocument讀取docx文件的示例:

    public class XwpfTest {

       /**
        * 通過XWPFDocument對內容進行訪問。對於XWPF文件而言,用這種方式進行讀操作更佳。
        * @throws Exception
        */
       @Test
       public void testReadByDoc() throws Exception {
          InputStream is = new FileInputStream("D:\\table.docx");
          XWPFDocument doc = new XWPFDocument(is);
          List<XWPFParagraph> paras = doc.getParagraphs();
          for (XWPFParagraph para : paras) {
             //當前段落的屬性
    //       CTPPr pr = para.getCTP().getPPr();
             System.out.println(para.getText());
          }
          //獲取文件中所有的表格
          List<XWPFTable> tables = doc.getTables();
          List<XWPFTableRow> rows;
          List<XWPFTableCell> cells;
          for (XWPFTable table : tables) {
             //表格屬性
    //       CTTblPr pr = table.getCTTbl().getTblPr();
             //獲取表格對應的行
             rows = table.getRows();
             for (XWPFTableRow row : rows) {
                //獲取行對應的單元格
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    System.out.println(cell.getText());;
                }
             }
          }
          this.close(is);
       }

       /**
        * 關閉輸入流
        * @param is
        */
       private void close(InputStream is) {
          if (is != null) {
             try {
                is.close();
             } catch (IOException e) {
                e.printStackTrace();
             }
          }
       }

    }

2.寫docx檔案

2.1直接通過XWPFDocument生成

在使用XWPFDocumentdocx檔案時不需要像使用HWPFDocumentdoc檔案那樣必須從一個doc檔案開始,我們可以直接new一個空的XWPFDocument,之後再往這個XWPFDocument裡面填充內容,然後再把它寫入到對應的輸出流中。下面是使用XWPFDocument生成docx檔案的示例程式碼:

    public class XwpfTest {

       /**
        * 基本的寫操作
        * @throws Exception
        */
       @Test
       public void testSimpleWrite() throws Exception {
          //新建一個文件
          XWPFDocument doc = new XWPFDocument();
          //建立一個段落
          XWPFParagraph para = doc.createParagraph();

          //一個XWPFRun代表具有相同屬性的一個區域。
          XWPFRun run = para.createRun();
          run.setBold(true); //加粗
          run.setText("加粗的內容");
          run = para.createRun();
          run.setColor("FF0000");
          run.setText("紅色的字。");
          OutputStream os = new FileOutputStream("D:\\simpleWrite.docx");
          //把doc輸出到輸出流
          doc.write(os);
          this.close(os);
       }

       /***
        * 寫一個表格
        * @throws Exception
        */
       @Test
       public void testWriteTable() throws Exception {
          XWPFDocument doc = new XWPFDocument();
          //建立一個5行5列的表格
          XWPFTable table = doc.createTable(5, 5);
          //這裡增加的列原本初始化建立的那5行在通過getTableCells()方法獲取時獲取不到,但通過row新增的就可以。
    //    table.addNewCol(); //給表格增加一列,變成6列
          table.createRow(); //給表格新增一行,變成6行
          List<XWPFTableRow> rows = table.getRows();
          //表格屬性
          CTTblPr tablePr = table.getCTTbl().addNewTblPr();
          //表格寬度
          CTTblWidth width = tablePr.addNewTblW();
          width.setW(BigInteger.valueOf(8000));
          XWPFTableRow row;
          List<XWPFTableCell> cells;
          XWPFTableCell cell;
          int rowSize = rows.size();
          int cellSize;
          for (int i=0; i<rowSize; i++) {
             row = rows.get(i);
             //新增單元格
             row.addNewTableCell();
             //設定行的高度
             row.setHeight(500);
             //行屬性
    //       CTTrPr rowPr = row.getCtRow().addNewTrPr();
             //這種方式是可以獲取到新增的cell的。
    //       List<CTTc> list = row.getCtRow().getTcList();
             cells = row.getTableCells();
             cellSize = cells.size();
             for (int j=0; j<cellSize; j++) {
                cell = cells.get(j);
                if ((i+j)%2==0) {
                    //設定單元格的顏色
                    cell.setColor("ff0000"); //紅色
                } else {
                    cell.setColor("0000ff"); //藍色
                }
                //單元格屬性
                CTTcPr cellPr = cell.getCTTc().addNewTcPr();
                cellPr.addNewVAlign().setVal(STVerticalJc.CENTER);
                if (j == 3) {
                    //設定寬度
                    cellPr.addNewTcW().setW(BigInteger.valueOf(3000));
                }
                cell.setText(i + ", " + j);
             }
          }
          //檔案不存在時會自動建立
          OutputStream os = new FileOutputStream("D:\\table.docx");
          //寫入檔案
          doc.write(os);
          this.close(os);
       }

       /**
        * 關閉輸出流
        * @param os
        */
       private void close(OutputStream os) {
          if (os != null) {
             try {
                os.close();
             } catch (IOException e) {
                e.printStackTrace();
             }
          }
       }

    }

2.2以docx檔案作為模板

當然,我們也可以像寫doc檔案那樣,先以一個docx檔案作為模板,然後建立基於該docx檔案的XWPFDocument物件,再把裡面一些變化的資訊在執行時進行替換,之後將XWPFDocument進行輸出就可以了。所不同的是XWPFDocument中沒有像HWPFDocument中那樣的Range可以用來直接替換內容。而且底層的XWPFParagraphXWPFRun也不支援直接將文字進行替換。倒是XWPFRun提供了一個設定文字的方法,不過新的文字不會替換舊的文字,而是會追加到原來的文字之後。現在的一個做法是先找出含有需要替換的變數的XWPFRun,然後將其移除,之後在原來的位置新增一個XWPFRun,其對應的文字是替換變數之後的文字。不過你設定的那個的變數的位置不一定就在一個XWPFRun裡面,它有可能會被拆分到兩個甚至更多的XWPFRun中,所以不是很有必要的話還是不推薦使用這種方式。

假設我們有一個docx檔案,其內容是這樣的:
這裡寫圖片描述

之後我們以該檔案作為模板,利用相關資料把裡面的變數進行替換,然後把替換後的文件輸出到另一個docx檔案中。具體做法如下:

    public class XwpfTest {

       /**
        * 用一個docx文件作為模板,然後替換其中的內容,再寫入目標文件中。
        * @throws Exception
        */
       @Test
       public void testTemplateWrite() throws Exception {
          Map<String, Object> params = new HashMap<String, Object>();
          params.put("reportDate", "2014-02-28");
          params.put("appleAmt", "100.00");
          params.put("bananaAmt", "200.00");
          params.put("totalAmt", "300.00");
          String filePath = "D:\\word\\template.docx";
          InputStream is = new FileInputStream(filePath);
          XWPFDocument doc = new XWPFDocument(is);
          //替換段落裡面的變數
          this.replaceInPara(doc, params);
          //替換表格裡面的變數
          this.replaceInTable(doc, params);
          OutputStream os = new FileOutputStream("D:\\word\\write.docx");
          doc.write(os);
          this.close(os);
          this.close(is);
       }

       /**
        * 替換段落裡面的變數
        * @param doc 要替換的文件
        * @param params 引數
        */
       private void replaceInPara(XWPFDocument doc, Map<String, Object> params) {
          Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
          XWPFParagraph para;
          while (iterator.hasNext()) {
             para = iterator.next();
             this.replaceInPara(para, params);
          }
       }

       /**
        * 替換段落裡面的變數
        * @param para 要替換的段落
        * @param params 引數
        */
       private void replaceInPara(XWPFParagraph para, Map<String, Object> params) {
          List<XWPFRun> runs;
          Matcher matcher;
          if (this.matcher(para.getParagraphText()).find()) {
             runs = para.getRuns();
             for (int i=0; i<runs.size(); i++) {
                XWPFRun run = runs.get(i);
                String runText = run.toString();
                matcher = this.matcher(runText);
                if (matcher.find()) {
                    while ((matcher = this.matcher(runText)).find()) {
                       runText = matcher.replaceFirst(String.valueOf(params.get(matcher.group(1))));
                    }
                    //直接呼叫XWPFRun的setText()方法設定文字時,在底層會重新建立一個XWPFRun,把文字附加在當前文字後面,
                    //所以我們不能直接設值,需要先刪除當前run,然後再自己手動插入一個新的run。
                    para.removeRun(i);
                    para.insertNewRun(i).setText(runText);
                }
             }
          }
       }

       /**
        * 替換表格裡面的變數
        * @param doc 要替換的文件
        * @param params 引數
        */
       private void replaceInTable(XWPFDocument doc, Map<String, Object> params) {
          Iterator<XWPFTable> iterator = doc.getTablesIterator();
          XWPFTable table;
          List<XWPFTableRow> rows;
          List<XWPFTableCell> cells;
          List<XWPFParagraph> paras;
          while (iterator.hasNext()) {
             table = iterator.next();
             rows = table.getRows();
             for (XWPFTableRow row : rows) {
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                       this.replaceInPara(para, params);
                    }
                }
             }
          }
       }

       /**
        * 正則匹配字串
        * @param str
        * @return
        */
       private Matcher matcher(String str) {
          Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
          Matcher matcher = pattern.matcher(str);
          return matcher;
       }

       /**
        * 關閉輸入流
        * @param is
        */
       private void close(InputStream is) {
          if (is != null) {
             try {
                is.close();
             } catch (IOException e) {
                e.printStackTrace();
             }
          }
       }

       /**
        * 關閉輸出流
        * @param os
        */
       private void close(OutputStream os) {
          if (os != null) {
             try {
                os.close();
             } catch (IOException e) {
                e.printStackTrace();
             }
          }
       }

    }

經過上面的程式碼所示的過程處理後,我們替換變數後新輸出來的docx檔案的內容是這樣的:
這裡寫圖片描述

相關文章