Word - Images Replacement

需求很單純,在我們有了自動抓圖程式後,接著就是要自動取代文件圖片了。經過研究一番,目前我只有辦法去取代Word 2007的圖片,2003並沒找到相應的操作API。

在Word2007中,本文是使用XWPFDocument物件去操作,而XWPFDocument(本文)下包含了多個XWPFParagraph(段落)。XWPFParagraph包含多個XWPFRun(文字屬性描述區塊),XWPFRun則包含CTR(Run的描述屬性)。圖片相關物件可從CTR中取出來去操作。

要達到需求,我們必須去loop本文內容,插入新圖片並刪除原本的圖片。我的範例僅僅將原本圖片大小讀出來,接著再把圖片依據這個大小插入到Word中。做法很直覺,廢話不多說,看code:

TestWord2007.java
	public static void main(String[] args) throws Exception {
		String testFile = "testdata/test.docx";
		String targetFile = "testdata/test_c.docx";
		String image = "./testdata/Desert.jpg";
 
		InputStream is = null;
		OutputStream os = null;
 
		try {
			is = new FileInputStream(testFile);
			os = new FileOutputStream(targetFile);
 
			CustomXWPFDocument doc = new CustomXWPFDocument(is);
			int count = 0;
			for( XWPFParagraph para : doc.getParagraphs()){
				List<XWPFRun> runs = para.getRuns();
				for( XWPFRun run : runs ){
					List<CTDrawing> drawings = run.getCTR().getDrawingList();
 
					int size = drawings.size();
					for( int i = 0 ; i < size ; i++ ){
						CTPositiveSize2D ps2d = drawings.get(0).getInlineList().get(0).getExtent();
						String blipId = addPictureData(image, doc);
						doc.createPicture(run.getCTR(), 
								blipId, doc.getNextPicNameNumber(XWPFDocument.PICTURE_TYPE_PNG), 
								ps2d.getCx(), ps2d.getCy());
 
						run.getCTR().removeDrawing(0);
 
						count++;
					}
				}
			}
			doc.write(os);
 
			System.out.println("Replace " + count + " image(s).");
		} finally {
			Cleaner.close(is);
			Cleaner.close(os);
		}
	}
 
	private static String addPictureData(String image, CustomXWPFDocument doc)
			throws FileNotFoundException, InvalidFormatException {
		InputStream images = null;
		try {
			images = new FileInputStream(image);
			return doc.addPictureData(images, XWPFDocument.PICTURE_TYPE_JPEG);
		} finally {
			Cleaner.close(images);
		}
	}

這裡是透過新增一筆在尾巴,接著刪除index 0那筆去做到類似替換的效果。但如果是文繞圖或特別調整的圖片,不僅僅只是讀取圖片大小,可能還要去取得設定的樣式做處理。如果之後有類似的需求我再研究並分享。

另外我繼承XWPFDocument類別去定義了CustomXWPFDocument,是由於XWPFDocument本身的createPicture存在bug,會造成輸出的Word無法正常開啟。

CustomXWPFDocument.java
import java.io.IOException;
import java.io.InputStream;
 
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
 
public class CustomXWPFDocument extends XWPFDocument {
	public CustomXWPFDocument() {
		super();
	}
 
	public CustomXWPFDocument(OPCPackage opcPackage) throws IOException {
		super(opcPackage);
	}
 
    public CustomXWPFDocument(InputStream in) throws IOException {
        super(in);
    }
 
    public void createPicture(CTR ctr, String blipId,int id, long width, long height) {
        //inal int EMU = 9525;
        //width *= EMU;
        //height *= EMU;
        //String blipId = getAllPictures().get(id).getPackageRelationship().getId();
 
 
        CTInline inline = ctr.addNewDrawing().addNewInline();
 
        String picXml = "" +
                "<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 id=\"" + id + "\" name=\"Generated\"/>" +
                "            <pic:cNvPicPr/>" +
                "         </pic:nvPicPr>" +
                "         <pic:blipFill>" +
                "            <a:blip r:embed=\"" + blipId + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>" +
                "            <a:stretch>" +
                "               <a:fillRect/>" +
                "            </a:stretch>" +
                "         </pic:blipFill>" +
                "         <pic:spPr>" +
                "            <a:xfrm>" +
                "               <a:off x=\"0\" y=\"0\"/>" +
                "               <a:ext cx=\"" + width + "\" cy=\"" + height + "\"/>" +
                "            </a:xfrm>" +
                "            <a:prstGeom prst=\"rect\">" +
                "               <a:avLst/>" +
                "            </a:prstGeom>" +
                "         </pic:spPr>" +
                "      </pic:pic>" +
                "   </a:graphicData>" +
                "</a:graphic>";
 
        //CTGraphicalObjectData graphicData = inline.addNewGraphic().addNewGraphicData();
        XmlToken xmlToken = null;
        try {
            xmlToken = XmlToken.Factory.parse(picXml);
        } catch(XmlException xe) {
            xe.printStackTrace();
        }
        inline.set(xmlToken);
        //graphicData.set(xmlToken);
 
        inline.setDistT(0);
        inline.setDistB(0);
        inline.setDistL(0);
        inline.setDistR(0);
 
        CTPositiveSize2D extent = inline.addNewExtent();
        extent.setCx(width);
        extent.setCy(height);
 
        CTNonVisualDrawingProps docPr = inline.addNewDocPr();
        docPr.setId(id);
        docPr.setName("Picture " + id);
        docPr.setDescr("Generated");
    }
 
    public void createPicture(String blipId,int id, int width, int height) {
        createPicture(createParagraph().createRun().getCTR(), blipId, id, width, height);
    }
}

友藏內心獨白: 熬夜好幾天非常累,偷懶減少對code的說明。