Java解析word,獲取文檔中圖片位置

前言(背景介紹):
Apache POI是Apache基金會下一個開源的項目,用來處理office系列的文檔,可以建立和解析word、excel、ppt格式的文檔。
html

其中對word文檔的處理有兩個技術,分別是HWPF(.doc)和XWPF(.docx)。若是你對這兩個技術熟悉的話,就應該能明白使用java解析word文檔的痛楚所在。java

其中兩個最大的問題在於:數據庫

第一是這兩個類並無統一的父類和接口(隔壁的XSSF和HSSF投過來鄙視的眼光),因此無法進行同一格式的接口式編程;apache

第二是官方API中並無文檔中圖片相對位置的接口,這就致使了雖然你能得到文檔中的全部圖片,可是你並不能知道這些圖片是在哪裏,未來要展現圖片就無法插入到正確的位置。編程

對於第一點,我是沒什麼辦法,能夠研究下其餘相關技術,好比jacob,doc4j等看看有沒有其餘的解決方案,不過doc4j這貨貌似只能處理2007文檔(.docx)。ide

對於第二點,本文將給出筆者的解決方案,實際上,這也是我寫本文的目的所在。工具

 

注意:簡單求快的同窗看第二章和第三章就好了;性能

1、預備知識

1.word文檔的兩種格式對應兩種不一樣的存儲方式

衆所周知,word文檔有兩種存儲格式:doc和docx測試

doc:習慣上稱爲Word2003,使用二進制儲存數據;這個不是咱們今天討論的重點.ui

docx:word2007,使用xml來存儲數據和格式.

可能你會問了,明明是docx結尾的文檔,怎麼成了xml格式了?

很簡單:你隨便選擇一個docx文件,右鍵使用壓縮工具打開,就能獲得一個這樣的目錄結構:

 

 

因此你覺得docx是一個完整的文檔,其實它只是一個壓縮文件。(docx:?_?)

 

2.Word文檔中xml的定義格式:

從前面咱們知道了docx文檔使用壓縮文件也就是xml來描述數據,那麼word文檔中的數據具體是怎麼定義的呢?

出於篇幅的關係,這裏不會詳細地描述整個壓縮的文檔,這裏只簡單介紹下兩個文件/文件夾:

一是word目錄下的documen.xml文件,這個就是整個文檔內容的定義;

二是word目錄下的media文件夾,看名字也能猜出來這個文件夾裏面是文檔中的多媒體內容:

                                            圖3:word/document.xml(定義文檔內容)                                     圖4:word/media文件夾下的內容

 

如下是document.xml文檔的部分關鍵內容:

A:document總體結構定義:

<w:document mc:ignorable="w14 w15 wp14" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpscustomdata="http://www.wps.cn/officeDocument/2013/wpsCustomData">
    <w:body>
        <w:p>
            <w:ppr>
                <w:pstyle w:val="2">
                </w:pstyle>
                <w:keepnext w:val="0">
                </w:keepnext>
                <w:keeplines w:val="0">
                </w:keeplines>
                <w:widowcontrol>
                </w:widowcontrol>
                <w:suppresslinenumbers w:val="0">
                </w:suppresslinenumbers>
                <w:pbdr>
                    <w:top w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:top>
                    <w:left w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:left>
                    <w:bottom w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:bottom>
                    <w:right w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:right>
                </w:pbdr>
View Code

 

B:文檔段落內容:

<w:p>
            <w:ppr>
                <w:pstyle w:val="2">
                </w:pstyle>
                <w:keepnext w:val="0">
                </w:keepnext>
                <w:keeplines w:val="0">
                </w:keeplines>
                <w:widowcontrol>
                </w:widowcontrol>
                <w:suppresslinenumbers w:val="0">
                </w:suppresslinenumbers>
                <w:pbdr>
                    <w:top w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:top>
                    <w:left w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:left>
                    <w:bottom w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:bottom>
                    <w:right w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:right>
                </w:pbdr>
                <w:shd w:fill="FAFAFA" w:val="clear">
                </w:shd>
                <w:spacing w:after="150" w:afterautospacing="0" w:before="150" w:beforeautospacing="0" w:line="378" w:linerule="atLeast">
                </w:spacing>
                <w:ind w:firstline="0" w:left="0" w:right="0">
                </w:ind>
                <w:rpr>
                    <w:rfonts w:ascii="Verdana" w:cs="Verdana" w:hansi="Verdana" w:hint="default">
                    </w:rfonts>
                    <w:i w:val="0">
                    </w:i>
                    <w:caps w:val="0">
                    </w:caps>
                    <w:color w:val="404040">
                    </w:color>
                    <w:spacing w:val="0">
                    </w:spacing>
                    <w:sz w:val="21">
                    </w:sz>
                    <w:szcs w:val="21">
                    </w:szcs>
                </w:rpr>
            </w:ppr>
            <w:r>
                <w:rpr>
                    <w:rfonts w:ascii="Verdana" w:cs="Verdana" w:hansi="Verdana" w:hint="default">
                    </w:rfonts>
                    <w:i w:val="0">
                    </w:i>
                    <w:caps w:val="0">
                    </w:caps>
                    <w:color w:val="404040">
                    </w:color>
                    <w:spacing w:val="0">
                    </w:spacing>
                    <w:sz w:val="21">
                    </w:sz>
                    <w:szcs w:val="21">
                    </w:szcs>
                    <w:bdr w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:bdr>
                    <w:shd w:fill="FAFAFA" w:val="clear">
                    </w:shd>
                </w:rpr>
                <w:t>
                    做者: Brian Dear
                </w:t>
            </w:r>
        </w:p>
View Code

 

C:圖片內容定義:

<w:r>
                <w:rpr>
                    <w:rfonts w:ascii="Verdana" w:cs="Verdana" w:hansi="Verdana" w:hint="default">
                    </w:rfonts>
                    <w:i w:val="0">
                    </w:i>
                    <w:caps w:val="0">
                    </w:caps>
                    <w:color w:val="404040">
                    </w:color>
                    <w:spacing w:val="0">
                    </w:spacing>
                    <w:sz w:val="21">
                    </w:sz>
                    <w:szcs w:val="21">
                    </w:szcs>
                    <w:bdr w:color="auto" w:space="0" w:sz="0" w:val="none">
                    </w:bdr>
                    <w:shd w:fill="FAFAFA" w:val="clear">
                    </w:shd>
                </w:rpr>
                <w:drawing>
                    <wp:inline distb="0" distl="114300" distr="114300" distt="0">
                        <wp:extent cx="5543550" cy="5543550">
                        </wp:extent>
                        <wp:effectextent b="0" l="0" r="0" t="0">
                        </wp:effectextent>
                        <wp:docpr descr="IMG_256" id="1" name="Picture 1">
                        </wp:docpr>
                        <wp:cnvgraphicframepr>
                            <a:graphicframelocks nochangeaspect="1" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
                            </a:graphicframelocks>
                        </wp:cnvgraphicframepr>
                        <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
                            <a:graphicdata uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
                                <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
                                    <pic:nvpicpr>
                                        <pic:cnvpr descr="IMG_256" id="1" name="Picture 1">
                                        </pic:cnvpr>
                                        <pic:cnvpicpr>
                                            <a:piclocks nochangeaspect="1">
                                            </a:piclocks>
                                        </pic:cnvpicpr>
                                    </pic:nvpicpr>
                                    <pic:blipfill>
                                        <a:blip r:embed="rId4">
                                        </a:blip>
                                        <a:stretch>
                                            <a:fillrect>
                                            </a:fillrect>
                                        </a:stretch>
                                    </pic:blipfill>
                                    <pic:sppr>
                                        <a:xfrm>
                                            <a:off x="0" y="0">
                                            </a:off>
                                            <a:ext cx="5543550" cy="5543550">
                                            </a:ext>
                                        </a:xfrm>
                                        <a:prstgeom prst="rect">
                                            <a:avlst>
                                            </a:avlst>
                                        </a:prstgeom>
                                        <a:nofill>
                                        </a:nofill>
                                        <a:ln w="9525">
                                            <a:nofill>
                                            </a:nofill>
                                        </a:ln>
                                    </pic:sppr>
                                </pic:pic>
                            </a:graphicdata>
                        </a:graphic>
                    </wp:inline>
                </w:drawing>
            </w:r>
View Code

 

 

有興趣的童鞋能夠看一下上面三段xml代碼,我這裏直接給結論了:

  word文檔shema文件:xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"

       文檔根節點:<w:document> 定義了整個文檔的開始

         <w:body>是document的子節點,文檔的主體內容

          <w:p>body子節點,一個段落,就是word文檔中的段落

            <w:r>P元素的子節點,一個Run定義了段落中具備相同格式的一段內容

              <w:t>Run元素節點的子節點,就是文檔的內容.

              <w:drawing> run元素的子節點,定義了一張圖片:

                <w:inline> drawing子節點,具體應用也沒有深刻研究

                <a:graphic> 定義圖片內容

                  <pic:blipfill>這個是graphic文檔的子節點,定義了圖片內容的索引,具體來講,poi能根據這個名稱拿到圖片所對應的資源,而獲取文檔圖片位置的關鍵也就在這裏

 

整體看來:XWPF解析docx文檔就是作了xml文檔的解析,將全部的節點保存下來,而後轉換成更加好用的屬性,提供API出來供用戶使用.

因此咱們就能用POI提供給咱們的接口拿到文檔內容,本身去解析文檔中的數據,就能獲取到圖片是在哪個段落裏了,固然你也能夠得知圖片是位於哪個Run元素的後面.

 

2、實現

package com.szdfhx.reportStatistic.util;
import com.microsoft.schemas.vml.CTShape;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFPictureData;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject;
import org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class XWPFUtils {

    //獲取某一個段落中的全部圖片索引
    public static List<String> readImageInParagraph(XWPFParagraph paragraph) {
        //圖片索引List
        List<String> imageBundleList = new ArrayList<String>();

        //段落中全部XWPFRun
        List<XWPFRun> runList = paragraph.getRuns();
        for (XWPFRun run : runList) {
            //XWPFRun是POI對xml元素解析後生成的本身的屬性,沒法經過xml解析,須要先轉化成CTR
            CTR ctr = run.getCTR();

            //對子元素進行遍歷
            XmlCursor c = ctr.newCursor();
            //這個就是拿到全部的子元素:
            c.selectPath("./*");
            while (c.toNextSelection()) {
                XmlObject o = c.getObject();
                //若是子元素是<w:drawing>這樣的形式,使用CTDrawing保存圖片
                if (o instanceof CTDrawing) {
                    CTDrawing drawing = (CTDrawing) o;
                    CTInline[] ctInlines = drawing.getInlineArray();
                    for (CTInline ctInline : ctInlines) {
                        CTGraphicalObject graphic = ctInline.getGraphic();
                        //
                        XmlCursor cursor = graphic.getGraphicData().newCursor();
                        cursor.selectPath("./*");
                        while (cursor.toNextSelection()) {
                            XmlObject xmlObject = cursor.getObject();
                // 若是子元素是<pic:pic>這樣的形式
if (xmlObject instanceof CTPicture) { org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture picture = (org.openxmlformats.schemas.drawingml.x2006.picture.CTPicture) xmlObject; //拿到元素的屬性 imageBundleList.add(picture.getBlipFill().getBlip().getEmbed()); } } } } //使用CTObject保存圖片
          //<w:object>形式
if (o instanceof CTObject) { CTObject object = (CTObject) o; System.out.println(object); XmlCursor w = object.newCursor(); w.selectPath("./*"); while (w.toNextSelection()) { XmlObject xmlObject = w.getObject(); if (xmlObject instanceof CTShape) { CTShape shape = (CTShape) xmlObject; imageBundleList.add(shape.getImagedataArray()[0].getId2()); } } } } } return imageBundleList; } }

 

首先要提出來是XWPF對xml元素的封裝:

<w:document> 對應XWPFDocument類

<w:run>對應XWPFRun類

基本上只對應到Run這一層,由於run的子元素有不少,因此沒有再往下面的層次封裝和定義了,

因此咱們使用API只能拿到全部的XWPFRun對象轉成它的xml的定義:CTR對象。最後利用CTR去讀取和解析的Run元素中的內容,獲取圖片的索引。

 

其次要談的則是整個XML元素的定義:

咱們能夠看到POI使用的是Apache下的xmlbeans這個技術解析的XML,相關的技術不作深談,關鍵要明白兩點:

1:xml文檔中的全部元素通過xmlbean是封裝後都繼承了一個XMLObject的接口,因此能夠用這個類來接收穫取到的子元素;

2:元素遍歷是經過XmlCursor來作的,具體獲取子元素是根據XmlCursor對象的selectPath屬性來控制,當selectPath爲"./*"時就定義爲遍歷子元素;

因此寫成了以下的代碼:能遍歷當前元素的子元素,而且檢驗子元素的類型:

          
 CTR ctr = run.getCTR();

            //對子元素進行遍歷
            XmlCursor c = ctr.newCursor(); //這個就是拿到全部的子元素: c.selectPath("./*"); while (c.toNextSelection()) { XmlObject o = c.getObject(); //若是子元素是<w:drawing>這樣的形式,使用CTDrawing保存圖片 if (o instanceof CTDrawing) { CTDrawing drawing = (CTDrawing) o;
 

最後你可能會有疑問,不是說<w:drawing>這個元素定義了一張圖片嗎?

那麼

if (o instanceof CTObject) { CTObject object = (CTObject) o;
...
}

這個第二個判斷條件是用來幹嗎的?

聰明的你應該已經猜到了

沒錯!docx文檔中的xml定義圖片的方式除了<w:drawing>這一種以外,還能夠運用<w:object>元素去定義,

爲何只有這兩種?

由於我只使用第一種方式解析,發現有些圖片丟失了,因而發現了第二種方式.......也許不止兩種?我也不知道,反正對於目前的我來講已經沒有問題了.

或許聰明的你在實踐中還遇到了更多種狀況?

那麼運用上面提到的xml解析方式,相信你也能正確讀取,獲得本身想要的索引值.

再拓寬一點,若是POI還有其餘沒有提供的API,咱們是否是也能經過XML解析的技術本身實現呢?這個就須要咱們在實踐中去探索了,相信時間會給咱們答案

 

好了,如今咱們拿到了索引值,那麼如何去拿到圖片資源呢?

POI提供了現成的方法:

XWPFDocument類中有getPictureDataByID(String picture);方法能夠拿到XWPFPictrueDate對象,這個就是圖片的資源了.

具體的操做能夠參閱相關的博文和API,這裏就不詳細介紹了.

 

3、測試:

使用Junit4測試的代碼:

package com.szdfhx.reportStatistic.util;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFPictureData;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;

import static org.junit.Assert.*;

public class XWPFUtilsTest {

    @Test
    public void readImageInParagraph() throws IOException {
        InputStream in = new FileInputStream("D:\\Document\\個人博客\\Java解析word,獲取文檔中圖片位置\\示例.docx");
        XWPFDocument xwpfDocument = new XWPFDocument(in);
        List<XWPFParagraph> paragraphList = xwpfDocument.getParagraphs();
        System.out.println("圖片的索引\t|圖片名稱\t|圖片上一段文字的內容\t");
        System.out.pringln("------------------------------------------");
        for(int i = 0;i < paragraphList.size();i++){
            List<String> imageBundleList = XWPFUtils.readImageInParagraph(paragraphList.get(i));
            if(CollectionUtils.isNotEmpty(imageBundleList)){
                for(String pictureId:imageBundleList){
                    XWPFPictureData pictureData = xwpfDocument.getPictureDataByID(pictureId);
                    String imageName = pictureData.getFileName();
                    String lastParagraphText = paragraphList.get(i-1).getParagraphText();
                    System.out.println(pictureId +"\t|" + imageName + "\t|" + lastParagraphText);
                }
            }
        }
    }

}

 

 

展現結果:

這裏使用圖片名稱指表明明我拿到了對應的資源,實際上 若是你對前文的內容還熟悉的話,會發現圖片的名稱實際上就是word/media文件夾下的全部圖片的全名稱。

在對應的XWPFPictureData對象中,圖像的二進制數據能夠經過getData()屬性來拿到,這樣你就能夠保存到數據庫或者是你本地的文件夾中了!

 

4、其餘:

談到這裏,開頭提到的第二個問題這裏就已經解決了。

那麼,第一個問題怎麼辦呢?

若是你的系統對速度要求不高的話,那麼我給你的建議是,把doc文檔轉化成docx文檔來解析--POI就有成熟的API來作

若是要考慮性能的話,那就只好寫兩套方法去解析文檔。

那麼......doc類型的word文檔怎麼獲取圖片的相對位置呢?

我也不知道········或者,你來告訴我?

 


 

參考:

POI官網:https://poi.apache.org/

Apache POI Word - 快速指南 :https://www.w3cschool.cn/apache_poi_word/apache_poi_word_quick_guide.html

相關文章
相關標籤/搜索