POI, 模板读写和修改, 单元格内换行, 打包成zip



同上一篇TableTree4J的, 这是我实习一开始就遇到的一个问题.
据说还是已经离职的那位仁兄研究了好久都没有解决的疑难问题呢...
之前没有接触过POI, 研究了一个周末POI的API和源码, 算是曲线解决了= =
因为这个模块在公司的产品中可能以后还会用到, 所以写了一篇说明给公司
本文是在那个基础上修改的

先介绍一下问题的基本情况:

现在有一个模板doc, 里面存放着5个奇怪怪的表格,

我要生成一个新的doc, 一个doc里有好几份这5个奇怪怪的表格, 且表格里每个单元格都填上响应的内容


a)POI核心理念
 POI中, 对于每一张表, 表里的每一单元格, 单元格里的每一个段落(包括空段落), 都是一个对象, 其本质是一个xml.
 所以可以看到这样的import

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;	


 b)为什么都写在同一个格子里了?
 有一个模板文件后, 要生成多份表格, 很自然的一个想法是在这个文件里复制多份这个表格.
 但是这在POI中是行不通的, 你可以复制, 但小到段落大到表格, 复制出来的东西和原来的都储存在同一块堆内存上,
 当你试图写入的时候, 表面上是通过栈内存中的不同引用而选中了不同的表格, 但这个引用之后指向的堆内存却是一样的, 其本质上是同一个东西
 因此就出现了这样的现象: 复制了很多表, 但后面的表是空的, 所有的东西都在第一张表里.


c)如何解决?(生成临时模板, 保存在服务器上, 再次打开后才写入内容)
 解决时需要用到又一个神奇的东西

import org.apache.xmlbeans.XmlCursor;

 之前说过, POI的眼里看文件都是对象, 而游标则可以在这些按顺序的对象中穿梭提取
 不过首先你得获得一个对象的List:

doc是一个XWPFDocument对象

List<XWPFTable> tbls = doc.getTables();
List<XWPFParagraph> paras = doc.getParagraphs();

现在就按需要增加的表格套数来循环增加吧

for (int i = 0; i < pageCount-1; i++) {	//i是要增加的模板套数)
	for (int j = 0, k = 0; j < 5; j++) {//j是一套模板里的表格数, 倒序往word文档顶部插入一套表
		XmlCursor paraCursor = doc.getDocument().getBody().getPArray(1).newCursor();//文档顶部有两行回车, 即两个段落, getPArray(1)使得游标获得了第二个段落的位置
		doc.insertNewTbl(paraCursor);//在游标位置插入一个表格, 此时原来的第二段就下沉了, 成为此时第一第二张表之间的一个空格, 第二段的位置现在是一个表格对象, 不过现在什么都显示不出来
		doc.setTable(0,tbls.get(5+j-k));//将第0个表设置成和第5+j-k一样的表
	        XmlCursor tblCursor = doc.getDocument().getBody().getTblArray(0).newCursor(); //使得游标获得了第一个表格的位置
	        doc.insertNewParagraph(tblCursor);//在表格位置插入一个新段落, 文档顶部重新变成了两个空段落
	        doc.setParagraph(paras.get(0), 0);//将第1个段落设置成和第1个一样的段落
	        k+=1;
	}
}


需要注意的是, j循环是正着循环的, 但实际上是往文档顶部倒序插入表格, 这类似于一个向顶部压栈的过程, 压进去的东西轮流是段落和表格
 表格之间若是没有空段落就连在一起了, setTable之后就会变成一张表

 d)为什么又放弃了这种写法?
 两百多个导出条目的时候就不行了= =, 对象太多了, 堆内存爆掉啦~ 因此采用了打包成zip的方式, 写完一个文件, GC回收一段内存.

 e)如何打包成zip?
 也是一个文件读写的过程, Java本身也有以下的两个类, 但是中文会乱码, 所以使用了不乱码的:

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;

首先zip中若是有文件名相同的也会有出错, 先利用Set来去重, list储存着文件名:

HashSet<String> h = new HashSet<String>(list);
list.clear();
list.addAll(h);

先新建一个zip文件的普通文件流, 再放入zip文件流:

OutputStream os = new BufferedOutputStream(new FileOutputStream("呵呵呵.zip"));
ZipOutputStream zos = new ZipOutputStream(os);

 ZipEntry就是一个要加入的zip的条目, 把他加入到zip文件流中:

ZipEntry ze = new ZipEntry("完整的文件名.文件名后缀");
zos.putNextEntry(ze);

 还要注意文件流的关闭, 忘记关闭或者位置不对会导致zip的文件无法解压或打开


f)如何在一个单元格内实现换行格式化?

对于数据库中读取的文字, POI写入word时无法识别换行, 因此需要用正则表达式来匹配, 手动实现换行
换行是通过对单元格addParagraph()来实现的
这里需要注意一个特别的函数createRun(), 对一个段落设置文字需要先调用这个哦

private void splitAndWrite(String str, XWPFTableCell cell){
	if(null == str){
		
    	}else{
		String []s = str.split("[\n\r]");//按回车符分割字符
    		if (s.length==1) {
    			cell.setText(str);
    		}else{
				cell.setText(s[0]);
		        for (int i = 1; i < s.length; i++) {
		        	XWPFParagraph p = cell.addParagraph();//添加新段落
		        	p.createRun().setText(s[i]);
		    	}   
			}
	    }
}

g)模板中单元格的行数要变化怎么办?
了解了c)中的实现, 此处也比较简单, 获得一个表格, 增加一行即可
需要理清现在是第几个表格, 第几个行

tbls.get(1).addRow(tbls.get(1).getRow(1));



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值