使用Java poi編輯word.docx文件

yao1996發表於2018-02-16

前置瞭解

  這裡僅說明以docx字尾的word檔案,doc檔案似乎用的是另一個api,不是很瞭解。

  首先在使用poi之前,需要了解word.docx的儲存方式。右鍵word文件,我們可以發現選項中是有開啟壓縮包等選項的。解壓縮後資料夾格式如下:

    -word
      -_rels
        - .rels
      -customXml
        ...
      -docProps
        ...
      -media
        - 1.png
        - 2.png
        - ...
      -word
        ...
        - document.xml
        ...
      [Content_Types].xml
複製程式碼

  其中我們需要關注的主要是media資料夾,它下面是該word文件中所有的圖片;以及word資料夾下的document.xml,該xml檔案統領全域性,包括了word文件中的文字、格式、圖片的引用等等。

使用poi

  如果使用maven的話,可以新增如下引用:

  <dependency>
		  	<groupId>org.apache.poi</groupId>
		  	<artifactId>poi-scratchpad</artifactId>
		  	<version>3.17</version>
  </dependency>
複製程式碼

  如果需要處理word中的數學公式,那麼還需要新增:

  <dependency>
		    <groupId>org.apache.poi</groupId>
		    <artifactId>ooxml-schemas</artifactId>
		    <version>1.0</version>
  </dependency>
複製程式碼

document

  首先是獲取文件的內容,XWPFDocument document = new XWPFDocument(new FileInputStream(filePath));

  接下來說下document的整體格式。

  此時我們獲取的document,很大程度上就是對應了之前word資料夾下的document.xml檔案,我們之後也會很多的使用xml的方式來處理word文件。用文字編輯器開啟document.xml,這個檔案是經過混淆的,我這裡使用sublime的外掛來進行重新排版。

  從document.xml檔案可以看到,根節點w:document下緊跟的是w:body,body節點下幾乎包括了所有的檔案資訊。body下是三種節點:

1.<w:sectPr>:該節點下定義了該word文件的整體資訊,包括頁首頁尾的引用,你可以在解壓後的word資料夾下找到頁首頁尾的定義。另外也包括了頁面的大小。這一部分我在使用過程中不需要獲取,具體內容也不是很瞭解。可以在複製新的document時使用document方法直接整體set進去(如果不需要獲取格式內容的話,接下來的段落、表格及它們內部的節點同理,格式部分直接用原來的設定);

2.<w:p>:段落,對應XWPFParagraph。段落下主要也有三個節點

  1.<w:pPr>與sectPr同理,格式部分內容,接下來的各部分的格式部分就不在重複了,基本上每一個節點都擁有一個屬性,對應poi中的一個類

  2.<w:run>run標籤,對應XWPFRun,是段落中格式相同的一部分內容,主要包括了屬性部分,圖片部分,文字部分。也就是說,一個段落中如果有多個格式的文字,或是圖片,那麼該段落節點下相應的就會有多個run標籤。

--------1.文字部分:在text標籤下,設定和獲取都很簡單,相應物件中有直接的方法

--------2.圖片部分:<w:drawing>標籤,對應XWPFPictureData。需要注意的是,word中每一張圖片都對應一個Id:

<pic:blipFill>
	<a:blip cstate="print" r:embed="rId15"/>
	<a:stretch>
		<a:fillRect/>
	</a:stretch>
</pic:blipFill>
複製程式碼

    找到其中的該部分內容,其中我們可以得到該圖片的id為rId15,我們可以直接通過XWPFPictureData picture = document.getPictureDataByID(id);來獲取指定的圖片,獲取id的方法放到文章末尾。這個picture擁有getBytes方法,之後想怎麼做就看你了=。=另外網上有同學說可能有另一種格式來儲存圖片,不過我在使用過程中未遇到該種情況,所以不細述。

  3.<m:oMath>公式部分,需要新增文章開頭的依賴,對應CTOMath。如果不需要獲取公式的話可以跳過。如果大家需要用到公式的話,很可能就需要將他用pandoc將其轉成latex格式。我在使用過程中遇到的問題是部門老大的mac無法讀取word中的公式部分,以及在使用pandoc轉檔案格式時會忽略其中的公式,而在windows下是能夠正常通過word檢視的。最後發現是該部分標籤放在的run標籤下,這導致了這兩個問題。所以我當時需要做的很簡單,遍歷run標籤內元素,遇到公式,將run標籤內的公式部分刪去,獲取父段落,新增到與run標籤同級。遍歷部分與獲取圖片的id方法一樣,在最後說。

3.<w:tab>表格,對應XWPFTable。表格內除了格式屬性外,就是每一行,下面是列,單元格,我們在閱讀word文件時可以發現,每一個單元格的內容後都有一個回車符號,這是因為單元格下就是使用<w:p>段落來儲存的,所以知道了上面知識,表格也就簡單了。

遍歷

接下來說下遍歷的方法。如果僅僅需要段落、圖片或是表格的一種,那麼XWPFDocument下有直接的方法來獲取;如果需求相對複雜的話,使用XWPFDocument:getBodyElements獲取所有元素。通過instanceof判斷格式。對於具體的,可以通過:

target.getCTR().setRPr(source.getCTR().getRPr());
XmlCursor cursor = source.getCTR().newCursor();
cursor.selectPath("./*");

while (cursor.toNextSelection()) {
    XmlObject xmlObject = cursor.getObject();
    if (xmlObject instanceof CTText) {
        CTText ctText = (CTText) xmlObject;
        target.setText(ctText.getStringValue());
    } else if (xmlObject instanceof XmlAnyTypeImpl) {
        CTOMath ctoMath = paragraph.getCTP().addNewOMath();
        ctoMath.set(xmlObject);
    }
}
複製程式碼

來進行,上述是我複製run標籤並刪除run內部的段落的部分程式碼,可以簡單參考下。其中公式在run內部時的class型別是XmlAnyTypeImpl。cursor.selectPath("./*");的意思是獲取所有子元素。

End

  那麼文件部分就簡單講到這了,poi中具體的方法還是需要大家自己探索了。祝大家新年快樂!

相關文章