docx格式之指定位置插入图片(图片复制)

本文档介绍了如何使用Java直接操作docx文件,避免使用poi或docx4j库。通过解析docx的文件结构,理解rels、media、document.xml等文件的作用,实现了在docx文档的指定位置插入图片。详细讲解了图片的引入、位置调整以及相关xml文件的修改方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先,针对的是docx格式,毕竟与时俱进嘛,07版以后的都采用xml格式了,这就方便对word进行操纵了。
docx格式实际上是一个压缩包文件来的,里面包含了该docx的许多信息,基本都是xml文件格式来的。对xml文件来说,操纵是挺容易的,dom操作在有许多成熟的api。

首先,本实例是采用java语言的,需要进行xml操作,至于poi,docx4j这些,因为感觉不是文档不太好,要嘛就是操作不方便,而且英文就算了,主要是注释很少,让人云里雾里的。所以,嘿嘿,直接用java写,这些操作包就不用了
首先,得明白docx解压后的文件夹是什么,解压后的东西如下
这里写图片描述
“----------------------------------------------------------------------------”
其中,_rels文件夹内容如下:
这里写图片描述
就一个文件,里面的内容如下:
这里写图片描述
里面标识了三个xml文件的位置,主要是指示链接了哪些文件。
“------------------------------------------------------------------------------”
docProps文件夹里面的东西如下:
这里写图片描述
app.xml指明了这个文档的总体信息,比如多少行,多少空格,单词,页数之类的一些基础信息。
core.xml也是如此,里面弄了创建人啊,时间啊,最后修改人之类的信息。

doc是最主要的文件夹,里面是我们的word文档内容,挺多东西的
这里写图片描述
(1)其中这里的_rels文件夹下有个
这里写图片描述
这里面包含了你需要用到的下面的内容,比如说,下面的什么styles.xml啊等等等文件都需要在这里引入,包括需要用到的文件,比如说一个png图片。

(2)media里面放的就是图片,等等其它一些word支持的媒体文件。要使用这些文件,记得在document.xml.rels里引入

(3)theme文件夹下放的是word文档的主题xml文件(包含了各种什么字体啊,颜色啊,等等一堆乱七八糟的东西)

(4)document.xml里放的就是我们的word文档内容了,如果把docx文档比喻成网页,这里面放的就是html文件了。(等等主要详细介绍)

(5)剩下的几个xml文件就是注入脚注,样式之类的xml文件,由于只是介绍盖章,就不介绍了。一般docx批处理不会特意单独去改单独某个docx的样式的。


这里写图片描述
最后,顾名思义,[Content Types]文件里放的是文件内容类型的信息,截图如下,
这里写图片描述
Extension是扩展类型,如果在word文档里用到了jpeg图片,这里就要指明使用了image/jpeg的内容类型(其它格式模仿上面)。这里的jpeg可不是指修改了文件后缀就好了,是真正的jpeg格式,如果格式和后缀对不上的话(文件损坏),word要嘛会把格式转换,要嘛就改后缀成格式后缀了。当然了,正常情况下,格式和后缀不会变的。

好了,现在回头讲讲document.xml这个文件。
现在,弄了个word文档,内容如下(随便编的内容和章)
这里写图片描述
document.xml里面的内容摘要图如下:
这里写图片描述

这里写图片描述

这里写图片描述

上面的内容,随便几个摘要图随便浏览一下就行了,下面我们分析一下。
这个 xml有<w:body>表示主体,w:p 表示一个段落
如:
这里写图片描述

画线的部分都是一个段落,其中,图片的左上角由于是和申请人在同一行上,所以算是同一段落的。
因此,这份document.xml(用谷歌浏览器打开xml,能合并标签)有段落数量如下:

这里写图片描述
额,感觉还不错,
现在,按顺序点下来,图片应该是在第四段落。也就是第四个w:p标签里面(当然了,凭肉眼是这么看的,写程序当然不是用看的了)。
该段落如下:

这里写图片描述
这里面有个w:drawing标签,存放了章图片的信息。
这里写图片描述
我们只需要,把这个dom弄出来。添加到到没盖章的word文档自己需要的段落。注意,rels里一定要添加对该图片的引用,且里面声明的Id要与上图的rId保持一致 ,且里面的rId是唯一的。
上图的rId7在document.xml.rels里已经声明引用了
这里写图片描述
还有,[Content_Types].xml也要声明该图片类型。

如果想要移动该图片的位置,还是在<w:drawing>包裹的标签里面,找到
这里写图片描述
里面的H是水平移动,也就是X轴的意思,V是垂直也就是y轴,改变这两个数的数值就改变图片的位置了。这里是是相对位置(其实也可以弄成绝对于页面的位置的),也就是相对于这一一段开头行的位置。包括上padding间隔。假如两个都设置为0,图片位置是变成如下:
这里写图片描述
当然,也可以设置成负数,现在将垂直值设置成-1000000,效果如下:
这里写图片描述
这样就实现了图片指定位置插入(图片是衬于文字下方的,浮于上方也行,不然漂移图片不了,不然得设置边距,但是这样就不妥了,盖章需要在文字下方)

下面开始例子
首先,先写解压缩和压缩的功能,毕竟word是个zip包。

package com.gmr.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;

import com.gmr.execption.DocxException;
import com.gmr.execption.GmrZipException;

public class DocxFile {
    private ZipFile docxFile;
    private String docxName;
    private File file;
    private Map<String, byte[]> updateEntryMap = new HashMap<String, byte[]>();
    boolean tag = true;

    /**
     * 初始化(docx)
     * 
     * @param docxName
     *            docx文件名及路径 如 web-inf/a.docx
     * 
     */
    public DocxFile(String docxName) {
        this.docxName = docxName;
        try {
            file = new File(docxName);
            docxFile = new ZipFile(file);
        } catch (ZipException e) {
            throw new GmrZipException(e);
        } catch (IOException e) {
            throw new DocxException(e);
        }
    }


    /**
     * 获取docx压缩体内容
     * 
     * @param entryName
     *            压缩体名
     * 
     * @return
     */
    public InputStream getEntryInputStream(String entryName) {
        ZipEntry entry = docxFile.getEntry(entryName);

        try {
            return entry == null ? null : docxFile.getInputStream(entry);
        } catch (IOException e) {
            throw new DocxException(e);
        }
    }

    /**
     * 获取docx压缩体内容
     * 
     * @param entry
     *            压缩体
     * 
     * @return
     */
    public InputStream getEntryInputStream(ZipEntry entry) {
        try {
            return docxFile.getInputStream(entry);
        } catch (IOException e) {
            throw new DocxException(e);
        }
    }

    /**
     * 获取指定文件夹下的ZipEntry
     * 
     * @param directoryName
     * @return
     */
    public List<ZipEntry> getEntries(String directoryName) {
        List<ZipEntry> list = new ArrayList<ZipEntry>();
        Enumeration<? extends ZipEntry> entries = docxFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (name.contains(directoryName)
                    || name.contains(directoryName + "/")
                    || name.contains(directoryName + "\\")) {
                list.add(entry);
            }
        }
        return list;
    }

    /**
     * 放置修改后的Entry
     * 
     * @param entryName
     * @param bs
     */
    public void putUpdateEntry(String entryName, byte[] bs) {
        updateEntryMap.put(entryName, bs);
    }

    /**
     * 修改当前的docx文件(这是文件名非空,也就是初始化时流的时候)
     * 
     * @throws Exception
     */
    public void updateZip() throws Exception {
        String suffix = "" + System.currentTimeMillis() + docxFile.hashCode()
                + updateEntryMap.hashCode();
        File tFile = new File(docxName + suffix);
        OutputStream out;
        try {
            out = new FileOutputStream(tFile);
        } catch (FileNotFoundException e) {
            throw new DocxException(e);
        }
        ZipOutputStream docxOut = new ZipOutputStream(out);
        Enumeration<? extends ZipEntry> zipEntrys = docxFile.entries();
        try {
            // 原有的部分,包括修改后的覆盖原有的
            while (zipEntrys.hasMoreElements()) {
                ZipEntry zipEntry = zipEntrys.nextElement();
                docxOut.putNextEntry(new ZipEntry(zipEntry.getName()));
                if (updateEntryMap.containsKey(zipEntry.getName())) {
                    byte[] b = updateEntryMap.get(zipEntry.getName());
                    if (b != null && b.length > 0) {
                        docxOut.write(b);
                    }
                    updateEntryMap.remove(zipEntry.getName());
                } else {
                    InputStream in = docxFile.getInputStream(zipEntry);
                    IOUtils.copy(in, docxOut);
                }
            }
            // 表示新增的修改部分
            for (Entry<String, byte[]> entry : updateEntryMap.entrySet()) {
                docxOut.putNextEntry(new ZipEntry(entry.getKey()));
                docxOut.write(entry.getValue());
            }
            docxOut.flush();
        } finally {
            docxOut.close();
            tag = false;
            docxFile.close();
        }
        this.file.delete();
        tFile.renameTo(new File(docxName));
    }

    /**
     * 关闭文件
     */
    public void close() {
        try {
            if (tag) {
                docxFile.close();
            }
        } catch (IOException e) {
            throw new DocxException(e);
        }
    }

}

/**
 * docx图片拷贝操作
 * 
 * @author gmr
 *
 */
public class DocxImageOperator {
	private DocxFile docxTemplate;

	/**
	 * 初始化图片拷贝模板
	 * 
	 * @param templateDocxName
	 */
	public DocxImageOperator(String templateDocxName) {
		docxTemplate = new DocxFile(templateDocxName);
	}

	public static void main(String[] args) {
		DocxImageOperator docxImageOperator = new DocxImageOperator(
				"C:/Users/gmr/Desktop/xx/a.docx");
		try {
			//a.docx作为模板,将图片拷贝入b.docx
			docxImageOperator
					.copyImageToDocxFromTemplate("C:/Users/gmr/Desktop/xx/b.docx");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 图片复样
	 * 
	 * @param docxNames
	 */
	public void copyImageToDocxFromTemplate(String... docxNames) {
		try {
			for (String docxName : docxNames) {
				copyImageToDocxFromTemplate(docxName);
			}
		} catch (Exception e) {
			if (e instanceof RuntimeException) {
				throw (RuntimeException) e;
			} else {
				throw new DocxException(e);
			}
		}
	}

	/**
	 * 将模板里的图片拷贝到docx中
	 * 
	 * @param docxName
	 */
	private void copyImageToDocxFromTemplate(String docxName) throws Exception {
		DocxFile docx = new DocxFile(docxName);
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder documentBuilder = factory.newDocumentBuilder();
			TransformerFactory transformerFactory = TransformerFactory
					.newInstance();
			Transformer transformer = transformerFactory.newTransformer();

			// 图片数据切入(图片media进入压缩包)
			Set<String> imageNames = new HashSet<String>();// 其实就只支持一张,当然了,可以自行改动代码支持多张
			addImage(imageNames, docx);
			// 图片引用导入(document.xml.rels添加对图片的引用)
			// 虽然写着imageNames,但是为了偷懒,我只让放一张,否则就乱序(因为打算直接replace,不管顺序了)
			String idName = documentRelsXmlCheck(transformer, imageNames,
					documentBuilder, docx);
			// 下面是contentType.xml的修改
			// 修改[Content_Types].xml
			contentTypesXmlCheck(transformer, imageNames, documentBuilder, docx);
			/*
			 * 修改最后的内容,添加正文图片修改document.xml
			 */
			documentXmlCheck(transformer, documentBuilder, docx, idName);
			// 保存该word文档
			docx.updateZip();
		} finally {
			docx.close();
		}
	}

	/**
	 * 拷贝模板的图片正文内容到word文档
	 * 
	 * @param transformer
	 * @param documentBuilder
	 * @param docx
	 * @param idName
	 * @throws Exception
	 */
	public void documentXmlCheck(Transformer transformer,
			DocumentBuilder documentBuilder, DocxFile docx, String idName)
			throws Exception {
		Document documentXml = documentBuilder.parse(docxTemplate
				.getEntryInputStream("word/document.xml"));
		NodeList blips = documentXml.getElementsByTagName("a:blip");
		if (blips.getLength() > 0) {
			Node node = blips.item(0);
			node.getAttributes().getNamedItem("r:embed").setNodeValue(idName);
		}
		NodeList imageContents = documentXml.getElementsByTagName("w:drawing");
		if (imageContents.getLength() > 0) {
			Node node = null;
			Node rNode = null;
			Node pNode = null;
			int imgLength = imageContents.getLength();
			for (int i = 0; i < imgLength; i++) {
				node = imageContents.item(i);
				rNode = node.getParentNode();
				pNode = rNode.getParentNode();
				if (node.getParentNode().getNodeName().equals("w:r")
						&& pNode.getNodeName().equals("w:p")) {
					break;
				}
			}
			NodeList pList = documentXml.getElementsByTagName("w:p");
			int pLength = pList.getLength();
			int position = -1;
			for (int i = 0; i < pLength; i++) {
				if (pList.item(i) == pNode) {
					position = i;
				}
			}

			Document toDocumentXml = documentBuilder.parse(docx
					.getEntryInputStream("word/document.xml"));
			Node n = toDocumentXml.importNode(rNode, true);
			NodeList toNodeList = toDocumentXml.getElementsByTagName("w:p");
			if (position != -1) {
				if (position >= toNodeList.getLength()) {
					position = toNodeList.getLength() - 1;
				}
				toDocumentXml.getElementsByTagName("w:p").item(position)
						.appendChild(n);
			}
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			try {
				transformer.transform(new DOMSource(toDocumentXml),
						new StreamResult(out));
				docx.putUpdateEntry("word/document.xml", out.toByteArray());
			} finally {
				if (out != null) {
					out.close();
				}
			}
		}
	}

	/**
	 * 检查word文档对图片类型的拓展(如果没有,则添加该拓展)
	 * 
	 * @param transformer
	 * @param imageNames
	 * @param documentBuilder
	 * @param docx
	 * @throws Exception
	 */
	private void contentTypesXmlCheck(Transformer transformer,
			Set<String> imageNames, DocumentBuilder documentBuilder,
			DocxFile docx) throws Exception {
		Document contentTypesXml = documentBuilder.parse(docx
				.getEntryInputStream("[Content_Types].xml"));
		NodeList nodeList = contentTypesXml.getElementsByTagName("Default");
		int length = nodeList.getLength();
		boolean tag = false;
		String imgName = "";
		for (String name : imageNames) {
			imgName = name;
			break;
		}
		String type = imgName.substring(imgName.lastIndexOf('.') + 1);
		for (int i = 0; i < length; i++) {
			Node node = nodeList.item(i);
			String value = node.getAttributes().getNamedItem("Extension")
					.getNodeValue();
			if (value.equals(type)) {
				tag = true;
				break;
			}
		}
		if (!tag) {
			// 没有该类型拓展则添加
			Element defaultNode = contentTypesXml.createElement("Default");
			defaultNode.setAttribute("Extension", type);
			defaultNode.setAttribute("ContentType", "image/" + type);// 就不辨别其它非图片类型了,直接加(不然很长很长的代码)
			contentTypesXml.getElementsByTagName("Types").item(0)
					.appendChild(defaultNode);

			ByteArrayOutputStream out = new ByteArrayOutputStream();
			try {
				transformer.transform(new DOMSource(contentTypesXml),
						new StreamResult(out));
				docx.putUpdateEntry("[Content_Types].xml", out.toByteArray());
			} finally {
				if (out != null) {
					out.close();
				}
			}
		}
	}

	/**
	 * 添加对图片的引用
	 * 
	 * @param transformer
	 * @param imageNames
	 * @param documentBuilder
	 * @param docx
	 * @return
	 * @throws Exception
	 */
	private String documentRelsXmlCheck(Transformer transformer,
			Set<String> imageNames, DocumentBuilder documentBuilder,
			DocxFile docx) throws Exception {
		Document documentRelsXml = documentBuilder.parse(docx
				.getEntryInputStream("word/_rels/document.xml.rels"));
		String idName = "";
		for (String imageName : imageNames) {
			// 添加对图片(暂时只支持图片,其它什么乱七八糟非图片格式也暂时不判断过滤了)的引用
			Element relTextNode = documentRelsXml.createElement("Relationship");
			idName = "gmr" + imageName.hashCode();
			relTextNode.setAttribute("Id", idName);
			relTextNode
					.setAttribute("Type",
							"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image");
			relTextNode.setAttribute("Target", imageName);
			documentRelsXml.getElementsByTagName("Relationships").item(0)
					.appendChild(relTextNode);
		}
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		try {
			transformer.transform(new DOMSource(documentRelsXml),
					new StreamResult(out));
			docx.putUpdateEntry("word/_rels/document.xml.rels",
					out.toByteArray());
		} finally {
			if (out != null) {
				out.close();
			}
		}
		return idName;
	}

	/**
	 * 添加图片文件进word
	 * 
	 * @param imageNames
	 * @param docx
	 * @throws Exception
	 */
	private void addImage(Set<String> imageNames, DocxFile docx)
			throws Exception {
		List<ZipEntry> list = docxTemplate.getEntries("word/media");
		for (ZipEntry zipEntry : list) {
			if (!zipEntry.isDirectory()) {
				imageNames.add(zipEntry.getName().replace("word/", "")
						.replace("word\\", ""));// 图片名集合
				docx.putUpdateEntry(zipEntry.getName(),
						IOUtils.toByteArray(docxTemplate
								.getEntryInputStream(zipEntry)));
			}
		}
	}

	public void close() {
		docxTemplate.close();
	}
}
public class DocxFile {
	private ZipFile docxFile;
	private String docxName;
	private File file;
	private Map<String, byte[]> updateEntryMap = new HashMap<String, byte[]>();
	boolean tag = true;

	/**
	 * 初始化(docx)
	 * 
	 * @param docxName
	 *            docx文件名及路径 如 web-inf/a.docx
	 * 
	 */
	public DocxFile(String docxName) {
		this.docxName = docxName;
		try {
			file = new File(docxName);
			docxFile = new ZipFile(file);
		} catch (ZipException e) {
			throw new GmrZipException(e);
		} catch (IOException e) {
			throw new DocxException(e);
		}
	}
	

	/**
	 * 获取docx压缩体内容
	 * 
	 * @param entryName
	 *            压缩体名
	 * 
	 * @return
	 */
	public InputStream getEntryInputStream(String entryName) {
		ZipEntry entry = docxFile.getEntry(entryName);

		try {
			return entry == null ? null : docxFile.getInputStream(entry);
		} catch (IOException e) {
			throw new DocxException(e);
		}
	}

	/**
	 * 获取docx压缩体内容
	 * 
	 * @param entry
	 *            压缩体
	 * 
	 * @return
	 */
	public InputStream getEntryInputStream(ZipEntry entry) {
		try {
			return docxFile.getInputStream(entry);
		} catch (IOException e) {
			throw new DocxException(e);
		}
	}

	/**
	 * 获取指定文件夹下的ZipEntry
	 * 
	 * @param directoryName
	 * @return
	 */
	public List<ZipEntry> getEntries(String directoryName) {
		List<ZipEntry> list = new ArrayList<ZipEntry>();
		Enumeration<? extends ZipEntry> entries = docxFile.entries();
		while (entries.hasMoreElements()) {
			ZipEntry entry = entries.nextElement();
			String name = entry.getName();
			if (name.contains(directoryName)
					|| name.contains(directoryName + "/")
					|| name.contains(directoryName + "\\")) {
				list.add(entry);
			}
		}
		return list;
	}

	/**
	 * 放置修改后的Entry
	 * 
	 * @param entryName
	 * @param bs
	 */
	public void putUpdateEntry(String entryName, byte[] bs) {
		updateEntryMap.put(entryName, bs);
	}
	
	/**
	 * 修改当前的docx文件(这是文件名非空,也就是初始化时流的时候)
	 * 
	 * @throws Exception
	 */
	public void updateZip() throws Exception {
		String suffix = "" + System.currentTimeMillis() + docxFile.hashCode()
				+ updateEntryMap.hashCode();
		File tFile = new File(docxName + suffix);
		OutputStream out;
		try {
			out = new FileOutputStream(tFile);
		} catch (FileNotFoundException e) {
			throw new DocxException(e);
		}
		ZipOutputStream docxOut = new ZipOutputStream(out);
		Enumeration<? extends ZipEntry> zipEntrys = docxFile.entries();
		try {
			// 原有的部分,包括修改后的覆盖原有的
			while (zipEntrys.hasMoreElements()) {
				ZipEntry zipEntry = zipEntrys.nextElement();
				docxOut.putNextEntry(new ZipEntry(zipEntry.getName()));
				if (updateEntryMap.containsKey(zipEntry.getName())) {
					byte[] b = updateEntryMap.get(zipEntry.getName());
					if (b != null && b.length > 0) {
						docxOut.write(b);
					}
					updateEntryMap.remove(zipEntry.getName());
				} else {
					InputStream in = docxFile.getInputStream(zipEntry);
					IOUtils.copy(in, docxOut);
				}
			}
			// 表示新增的修改部分
			for (Entry<String, byte[]> entry : updateEntryMap.entrySet()) {
				docxOut.putNextEntry(new ZipEntry(entry.getKey()));
				docxOut.write(entry.getValue());
			}
			docxOut.flush();
		} finally {
			docxOut.close();
			tag = false;
			docxFile.close();
		}
		this.file.delete();
		tFile.renameTo(new File(docxName));
	}

	/**
	 * 关闭文件
	 */
	public void close() {
		try {
			if (tag) {
				docxFile.close();
			}
		} catch (IOException e) {
			throw new DocxException(e);
		}
	}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值