POI word合并 设置页眉页脚的处理方案

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


apache 操作 word(.docx)

业务场景

多个docx文档合并到一个文档中

遇到的坑

开始做的时候,是在网上找的资料,通过 XWPFDocument.getBdoy().xmlText() Xml字符串,把所有的xml合并在一起,再 CTBody.set(xml).

 /**
	 * 合并文档内容
	 *
	 * @param src    目标文档
	 * @param append 要合并的文档
	 * @param map 当时想处理页眉页脚,这里存放的是rId  (由于实现不了,可能写的有问题,就不做描述了)
	 * @throws Exception
	 */
	private static void appendBody(CTBody src, CTBody append,Map<String,String> map) throws Exception {
		XmlOptions optionsOuter = new XmlOptions();
		optionsOuter.setSaveOuter();
		String appendString = append.xmlText(optionsOuter);

		String srcString = src.xmlText();

		String prefix = srcString.substring(0, srcString.indexOf(">") + 1);
		String mainPart = srcString.substring(srcString.indexOf(">") + 1,
				srcString.lastIndexOf("<"));
		String sufix = srcString.substring(srcString.lastIndexOf("<"));


		// <w:sectPr>多了  匹配<w:sectPr的正则,由于只需删除最后一个<w:sectPr>标签,暂时用截取的方法处理
		//appendString = appendString.replaceAll("/<[\\s]*?w:sectPr[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?w:sectPr[\\s]*?>{1}/$", "");
		//if(isFz){
		//	appendString = appendString.replaceAll("/<[\\s]*?w:sectPr[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?w:sectPr[\\s]*?>{1}/$", "");
		//} else {
		for (String oldRId : map.keySet()) {
			appendString = appendString.replace(oldRId, map.get(oldRId));
		}

			int i = appendString.lastIndexOf("<w:sectPr");
			int i1 = appendString.lastIndexOf("</w:sectPr>") + 11;
			appendString = appendString.substring(0, i) + appendString.substring(i1);
		//}

		String addPart = appendString.substring(appendString.indexOf(">") + 1,
				appendString.lastIndexOf("<"));
		CTBody makeBody = CTBody.Factory.parse(prefix + mainPart + addPart
				+ sufix);
		src.set(makeBody);
	}

<w:headerReference w:type=“default” r:id=“rId9”/>
新合并的word中 没有rId9对应的xml文件,导致word打不开.所以当时就想了个办法,匹配<w:sectPr>标签并替换掉.
上面这个方法再不处理哦页眉页脚也是可以合并word文档的.

解决方案

直接上代码

	/**
	 * 测试方法
	 * 业务代码 MergeModel的type只是为了让我区分类型,在处理的时候更方便
	 * @throws IOException
	 */
	public static void testMergeWord() throws IOException {
		List<MergeModel> collect = new ArrayList<>();
		collect.add(new MergeModel("",  "L:\\文件1.docx"));
		collect.add(new MergeModel("zc","L:\\文件2.docx"));
		collect.add(new MergeModel("xj","L:\\文件3.docx"));
		collect.add(new MergeModel("lr","L:\\文件4.docx"));
		collect.add(new MergeModel("qy1","L\\文件5.docx"));
		collect.add(new MergeModel("fz","L:\\文件6.docx"));
		collect.add(new MergeModel("fz","L:\\文件7.docx"));
		collect.add(new MergeModel("fz","L:\\文件8.docx"));
		collect.add(new MergeModel("fz","L:\\文件9.docx"));
		collect.add(new MergeModel("fz","L:\\文件10.docx"));
		collect.add(new MergeModel("fz","L:\\文件11.docx"));
		collect.add(new MergeModel("fz","L:\\文件12.docx"));
		collect.add(new MergeModel("fz","L:\\文件13.docx"));
		collect.add(new MergeModel("fz","L:\\文件14.docx"));
		collect.add(new MergeModel("fz","L:\\文件15.docx"));
		collect.add(new MergeModel("fz","L:\\文件16.docx"));
		mergeDoc(collect, "L:\\结果.docx");
	}

	/**
	 * 合并docx文件
	 *
	 * @param srcDocxs 需要合并的目标docx文件
	 * @param destDocx 合并后的docx输出文件
	 */
	public static void mergeDoc(List<MergeModel> srcDocxs, String destDocx) {
		int fzStartIndex = 0;
		OutputStream dest = null;
		int opcpSize = srcDocxs.size();
		//获取的OPCPackage对象大于0时,执行合并操作
		if (opcpSize > 0) {
			try {
				dest = new FileOutputStream(destDocx);

				MergeModel mergeModel = srcDocxs.get(0);
				XWPFDocument src1Document = new XWPFDocument(mergeModel.getOpcPackage());

				// 空为报告封面,需要换页
				if(StringUtils.isBlank(mergeModel.getType())){
					dealCover(src1Document);
				}

				boolean firFz = false;
				//OPCPackage大于1的部分执行合并操作
				if (opcpSize > 1) {
					List<XWPFParagraph> headerParagraphs = new ArrayList<>();
					List<XWPFParagraph> fzFooterParagraphs = new ArrayList<>();
					for (int i = 1; i < opcpSize; i++) {
						MergeModel mergeModel2 = srcDocxs.get(i);
						XWPFDocument src2Document = new XWPFDocument(mergeModel2.getOpcPackage());

						clearSectPr(src2Document);
						if (mergeModel2.isReportModuleWord()) {
							BigInteger startNum = null;
							// 设置起始页码
							if("zc".equals(mergeModel2.getType())){
								startNum = new BigInteger("1");
							}
							copySectPr(src2Document, startNum);
							//src2Document.createParagraph().createRun().addBreak(BreakType.PAGE);
						}

						if(mergeModel2.getType().startsWith("fz")){
							// 只需要拿一次
							if(CollectionUtils.isEmpty(headerParagraphs)){
								List<XWPFHeader> headerList = src2Document.getHeaderList();
								for (XWPFHeader xwpfHeader : headerList) {
									String text = xwpfHeader.getText();
									if(StringUtils.isNotBlank(text.trim())){
										headerParagraphs = xwpfHeader.getParagraphs();
										break;
									}
								}
							}

							// 只需要拿一次
							if(CollectionUtils.isEmpty(fzFooterParagraphs)){
								List<XWPFFooter> footerList = src2Document.getFooterList();
								for (XWPFFooter xwpfFooter : footerList) {
									String text = xwpfFooter.getText();
									if(StringUtils.isNotBlank(text.trim())){
										fzFooterParagraphs = xwpfFooter.getParagraphs();
										break;
									}
								}
							}
						}

						List<IBodyElement> bodyElements = src2Document.getBodyElements();
						for (IBodyElement element : bodyElements) {
							if(element instanceof XWPFParagraph){
								XWPFParagraph paragraph = (XWPFParagraph) element;
								XWPFParagraph xwpfParagraph = src1Document.createParagraph();
								xwpfParagraph.getCTP().set(paragraph.getCTP().copy());

								if(mergeModel2.getType().startsWith("fz")){
									CTP ctp = xwpfParagraph.getCTP();
									CTPPr pPr = ctp.getPPr();
									if (pPr == null) {
										continue;
									}
									CTSectPr sectPr = pPr.getSectPr();
									if (sectPr == null) {
										continue;
									}

									createDefaultHeader(src1Document, sectPr, headerParagraphs);

									copyFooter(src1Document, sectPr, fzFooterParagraphs);

									if(!firFz){
										CTPageNumber ctPageNumber = sectPr.addNewPgNumType();
										ctPageNumber.setStart(new BigInteger("1"));
									}
									firFz = true;
								}

							} else if(element instanceof XWPFTable){
								XWPFTable xwpfTable = (XWPFTable) element;
								XWPFTable table = src1Document.createTable();
								table.getCTTbl().set(xwpfTable.getCTTbl().copy());
							}
						}
						if(mergeModel2.isReportModuleWord()){
							XWPFParagraph paragraph = src1Document.getLastParagraph();
							CTPPr pPr = paragraph.getCTP().getPPr();
							if (pPr == null) {
								pPr = paragraph.getCTP().addNewPPr();
							}
							CTSectPr sectPr = pPr.getSectPr();
							if (sectPr == null) {
								sectPr = pPr.addNewSectPr();
							}
							List<XWPFParagraph> xwpfParagraphList = new ArrayList<>();
							List<XWPFFooter> footerList = src2Document.getFooterList();
							for (XWPFFooter xwpfFooter : footerList) {
								if(StringUtils.isNotBlank(xwpfFooter.getText())){
									xwpfParagraphList = xwpfFooter.getParagraphs();
									break;
								}
							}
							copyFooter(src1Document, sectPr, xwpfParagraphList);
						}
					}
				}
				//将合并的文档写入目标文件中
				src1Document.write(dest);
			} catch (Exception e) {
				log.error(Consts.EMPTY, e);
				e.printStackTrace();
			} finally {
				//关闭流
				IoUtil.close(dest);
			}
		}

	}
	/**
	 * 处理封面的页脚页宽等信息
	 * @param docx
	 */
	public static void dealCover(XWPFDocument docx){
	   // 这里是复制原文档的页脚信息,我这只有一个,所以直接这么处理了
		List<XWPFParagraph> footerParagraph = new ArrayList<>();
		List<XWPFFooter> footerList = docx.getFooterList();
		for (XWPFFooter xwpfFooter : footerList) {
			// 找到需要的页脚
			if(StringUtils.isNotBlank(xwpfFooter.getText()) && xwpfFooter.getText().contains("页脚关键信息")){
				footerParagraph = xwpfFooter.getParagraphs();
				break;
			}
		}
		// 换页,业务需求
		docx.createParagraph().createRun().addBreak(BreakType.PAGE);
		List<XWPFParagraph> paragraphs = docx.getParagraphs();
		CTPageMar pgMar = null;
		CTPageSz pgSz = null;
		// 段落 从后往前找,复制宽度和边距等
		for (int p = paragraphs.size() - 1; p >= 0; p--) {
			XWPFParagraph lastParagraph = paragraphs.get(p);
			if(lastParagraph.getCTP().getPPr() == null || lastParagraph.getCTP().getPPr().getSectPr() == null){
				continue;
			}
			CTSectPr sectPr = lastParagraph.getCTP().getPPr().getSectPr();

			pgMar = sectPr.getPgMar();
			pgSz = sectPr.getPgSz();
			break;
		}
		XWPFParagraph lastParagraph = docx.createParagraph();
		CTPPr pPr = lastParagraph.getCTP().getPPr();
		if(pPr == null){
			pPr = lastParagraph.getCTP().addNewPPr();
		}
		//pPr.addNewWidowControl();
		CTSectPr sectPr = pPr.getSectPr();
		if(sectPr == null){
			sectPr = pPr.addNewSectPr();
		}
		if (pgMar != null) {
			sectPr.addNewPgMar().set(pgMar.copy());
		}
		if (pgSz != null) {
			sectPr.addNewPgSz().set(pgSz.copy());
		}

		// 清空首页所有的页眉页脚信息
		// 因为自己要重新设置页脚,所以我直接清空了页眉页脚属性.
		clearSectPr(docx);

		// 设置为起始页
		CTPageNumber ctPageNumber = sectPr.addNewPgNumType();
		ctPageNumber.setStart(new BigInteger("1"));
		// 复制首页的页脚
		copyFooter(docx, sectPr, footerParagraph);
	}
	/**
	 * 清空Sectpr的页眉页脚标签属性
	 * @param xwpfDocument
	 */
	public static void clearSectPr(XWPFDocument xwpfDocument){
		List<XWPFParagraph> paragraphs = xwpfDocument.getParagraphs();
		for (XWPFParagraph paragraph : paragraphs) {
			CTP ctp = paragraph.getCTP();
			CTPPr pPr = ctp.getPPr();
			if (pPr == null) {
				continue;
			}
			CTSectPr sectPr = pPr.getSectPr();
			if (sectPr == null) {
				continue;
			}
			CTPageSz pgSz = sectPr.getPgSz();

			CTSectPr ctSectPr = CTSectPr.Factory.newInstance();

			CTPageSz ctPageSz = ctSectPr.addNewPgSz();
			ctPageSz.set(pgSz.copy());
			//ctSectPr.addNewDocGrid().set(sectPr.getDocGrid().copy());
			ctSectPr.addNewPgMar().set(sectPr.getPgMar().copy());
			//ctSectPr.addNewCols().set(sectPr.getCols().copy());
			//ctSectPr.addNewPgNumType().set(src2SectPr.getPgNumType().copy());
			ctSectPr.setRsidR(sectPr.getRsidR());
			ctSectPr.setRsidRPr(sectPr.getRsidRPr());
			ctSectPr.setRsidSect(sectPr.getRsidSect());
			pPr.setSectPr(ctSectPr);
		}
		CTSectPr sectPr1 = xwpfDocument.getDocument().getBody().getSectPr();
		if (sectPr1 == null) {
			return;
		}
		CTPageSz pgSz = sectPr1.getPgSz();

		CTSectPr ctSectPr = CTSectPr.Factory.newInstance();

		CTPageSz ctPageSz = ctSectPr.addNewPgSz();
		ctPageSz.set(pgSz.copy());
		ctSectPr.addNewDocGrid().set(sectPr1.getDocGrid().copy());
		ctSectPr.addNewPgMar().set(sectPr1.getPgMar().copy());
		ctSectPr.addNewCols().set(sectPr1.getCols().copy());
		//ctSectPr.addNewPgNumType().set(src2SectPr.getPgNumType().copy());
		ctSectPr.setRsidR(sectPr1.getRsidR());
		ctSectPr.setRsidRPr(sectPr1.getRsidRPr());
		ctSectPr.setRsidSect(sectPr1.getRsidSect());
		xwpfDocument.getDocument().getBody().setSectPr(ctSectPr);
	}
	/**
	* 页眉页脚的主要处理
	**/
	public static void copyFooter(XWPFDocument docx, CTSectPr sectPr, List<XWPFParagraph> xwpfParagraphList) {
		XWPFRelation relation = XWPFRelation.FOOTER;
		int relationIndex = getRelationIndex(relation, docx);
		XWPFFooter footer = (XWPFFooter) docx.createRelationship(relation, XWPFFactory.getInstance(), relationIndex);
		for (XWPFParagraph xwpfParagraph : xwpfParagraphList) {
			CTP ctp = footer.createParagraph().getCTP();
			ctp.set(xwpfParagraph.getCTP().copy());
		}
		// 把上面创建的footer的关联id,设到需要的分页节上CTSectPr 
		String relationId = docx.getRelationId(footer);
		CTHdrFtrRef ctHdrFtrRef = sectPr.addNewFooterReference();
		ctHdrFtrRef.setId(relationId);
	}
	/**
	 * 获取关联 rId的最大值
	 * @param relation
	 * @param doc
	 * @return
	 */
	public static int getRelationIndex(XWPFRelation relation,XWPFDocument doc) {
		int i = 1;
		for (POIXMLDocumentPart.RelationPart rp : doc.getRelationParts()) {
			if (rp.getRelationship().getRelationshipType().equals(relation.getRelation())) {
				i++;
			}
		}
		return i;
	}
	/**
	 * 把最外层的sectPr标签的属性做了copy,去除了页眉页脚等标签,带有设置开始页码的处理
	 * @param xwpfDocument
	 */
	public static void copySectPr(XWPFDocument xwpfDocument,BigInteger startNum) {

		if(!xwpfDocument.getDocument().getBody().isSetSectPr()){
			return;
		}
		CTBody body = xwpfDocument.getDocument().getBody();
		CTSectPr src2SectPr = body.getSectPr();

		//src2SectPr.set

		CTPageSz pgSz = src2SectPr.getPgSz();
		XWPFParagraph xwpfParagraph = xwpfDocument.getLastParagraph();

		//CTP ctp = CTP.Factory.newInstance();
		//XWPFParagraph xwpfParagraph = new XWPFParagraph(ctp, xwpfDocument);
		CTPPr pPr = xwpfParagraph.getCTP().getPPr();
		if(pPr == null){
			pPr = xwpfParagraph.getCTP().addNewPPr();
		}
		if(pPr.getSectPr() != null){
			return;
		}
		CTSectPr ctSectPr = pPr.addNewSectPr();
		//ctSectPr.set(src2SectPr.copy());
		//ctSectPr.unsetFootnotePr();
		
		CTPageSz ctPageSz = ctSectPr.addNewPgSz();
		ctPageSz.set(pgSz.copy());
		ctSectPr.addNewDocGrid().set(src2SectPr.getDocGrid().copy());
		ctSectPr.addNewPgMar().set(src2SectPr.getPgMar().copy());
		ctSectPr.addNewCols().set(src2SectPr.getCols().copy());
		//ctSectPr.addNewPgNumType().set(src2SectPr.getPgNumType().copy());
		ctSectPr.setRsidR(src2SectPr.getRsidR());
		ctSectPr.setRsidRPr(src2SectPr.getRsidRPr());
		ctSectPr.setRsidSect(src2SectPr.getRsidSect());

		if (startNum != null) {
			CTPageNumber ctPageNumber = ctSectPr.addNewPgNumType();
			ctPageNumber.setStart(startNum);
		}

	}
	/**
	 * 复制已有页眉
	 * @param docx
	 * @param ctSectPr
	 * @param paragraphList
	 */
	public static void createDefaultHeader(XWPFDocument docx, CTSectPr ctSectPr, List<XWPFParagraph> paragraphList) {
		XWPFRelation relation = XWPFRelation.HEADER;
		int relationIndex = getRelationIndex(relation, docx);
		XWPFHeader header = (XWPFHeader) docx.createRelationship(relation, XWPFFactory.getInstance(), relationIndex);
		for (XWPFParagraph xwpfParagraph : paragraphList) {
			XWPFParagraph paragraph = header.createParagraph();
			paragraph.getCTP().set(xwpfParagraph.getCTP().copy());
		}

		String relationId = docx.getRelationId(header);
		CTHdrFtrRef ctHdrFtrRef = ctSectPr.addNewHeaderReference();
		ctHdrFtrRef.setId(relationId);
	}

总结

个人感觉上面代码主要是copyFooter和createDefaultHeader方法解决了我页眉页脚的问题,其他只是基于我业务做出的关联改动.

页眉页脚

	<w:pPr>
 			<w:pStyle w:val="1"/>
 			<w:rPr>
 				<w:rFonts w:eastAsiaTheme="minorEastAsia"/>
 			</w:rPr>
 			<w:sectPr w:rsidR="00AC18FB">
 				<w:headerReference w:type="default" r:id="rId9"/>
 				<w:footerReference w:type="default" r:id="rId10"/>
 				<w:pgSz w:w="12240" w:h="15840"/>
 				<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="720" w:footer="720" w:gutter="0"/>
 				<w:cols w:space="720"/>
 			</w:sectPr>
 		</w:pPr>

w:headerReference和w:footerReference控制页眉页脚,后面的type就是下图的信息
在这里插入图片描述

里面的rId,在 .\word_rels\document.xml.rels映射到 .\word_rels的各种xml中.例如:footer1.xml,header1.xml
注: 上面的路径是word.docx,把docx后缀修改成.zip,然后解压得到的文件
上面操作的原理就是创建页眉页脚的关联xml,然后拿到关联id设置给w:headerReference标签的 r:id属性.

合并word

开始采用获取body的xml进行合并,但是合并xml后,src1Document的getParagraphs方法获取到的还是合并前的段落,导致我后需找不到插入分页符的节点,最终选择了一个个IBodyElement 进行copy合并。如果只是简单的内容合并,拼接xml的方法完全可以实现。

POI-tl是一个基于Apache POIJava库,用于动态生成Word文档。它提供了一种简单而强大的方式来根据Word模板生成具有动态内容的文档。在使用POI-tl进行动态导出Word文档时,你需要在模板中定义需要替换的标记,并在代码中使用POI-tl的API来填充这些标记。 首先,你需要在项目中添加POI-tl的依赖。具体的依赖配置可以参考\[1\]中提供的文章。 然后,你需要准备一个Word模板,其中包含需要动态填充的内容。在模板中,你可以使用自定义的标记来标识需要替换的部分。这些标记可以是任意的字符串,但需要与代码中的标记保持一致。 接下来,在代码中,你可以使用POI-tl的API来加载模板并替换其中的标记。你可以使用POI-tl提供的方法来设置文本、图片、表格、页眉页脚等内容。具体的使用方法可以参考\[3\]中提供的教程。 最后,你可以将生成的Word文档导出到文件或直接在浏览器中下载。你可以使用POI-tl提供的方法来实现导出功能。具体的导出方法可以参考\[2\]中提供的代码示例。 总结起来,使用POI-tl动态导出Word文档的步骤包括添加依赖、准备模板、替换标记、导出文档。希望这些信息对你有帮助。 #### 引用[.reference_title] - *1* *2* [SpringBoot+Poi-tl根据Word模板动态生成word(含动态行表格、合并单元格)](https://blog.csdn.net/qq_26383975/article/details/112238802)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [poi-tl导出word](https://blog.csdn.net/weixin_43580824/article/details/129549483)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值