最近项目需要实现如题“所见即所得”的功能,之前每次生成Excel都需要重新从数据库查询一遍,降低效率不说,那些嵌套的表头实在是很难用Sql嵌套拼接实现。而且这样做还没有通用性,不同的表格需要写不同的Sql实现,非常繁琐。
在网上找了很多关于HTML解析为Excel的文章,有以下两种情况:
1、大部分用“偷懒”的办法,使用js直接将HTML代码输出为文本文件,然后将文件后缀改为.xls。这种方式的确简单易行,但是有它的缺点,之后讲到。
2、解析HTML然后生成Excel,网上的博客的确可以搜到一两篇这样的文章,但是内容太有局限性,适用于没有单元格合并的情况,并不适用于复杂单元格合并(eg:多级表头嵌套)的情况。而实际的报表,结构都很复杂。
终于找到一篇这样的文章《所见即所得的EXCEL报表生成(二)——从HTMLTABLE到EXCEL CELL》,文章介绍了使用.NET+HtmlAgilityPack解析工具实现HTML解析为Excel,无奈对.NET没接触过,看的过程中只借鉴了其中的推导思路,然后自己构思Java代码,很感谢文章的作者。我的原创点在于使用Java+Jsoup+POI的代码实现。
----------------------------------------------(づ ̄3 ̄)づ╭❤~华丽的分割线----------------------------------------------
为了实现“所见即所得”的效果,即将页面的报表直接导出为Excel,做了两种方法的尝试。
方法1:JS直接输HTML到文本文件,更改后缀为.xls
优点:
简单有效,代码量最少
缺点:
1、用户体验有欠缺。这种方式生成的文本文件是HTML代码,不是真正的Excel文件。但是可以被Excel解析,并且还附带有样式。但是每次打开时会弹出提示“文件安全性有问题”对话框,太烦人…
2、导入Excel解析是问题。导入Excel的功能一般只解析真正的Excel文件,不能解析HTML。这样还要区分不同的文件解析,有点麻烦
方法2:将HTML代码解析为Excel文件
优点:
1、生成的是真正的Excel文件,无需担心导入问题
2、良好的用户体验,没有任何弹窗提示
缺点:
对一些多余的HTML代码,或者不规范的HTLM,解析会不能正确识别。所以使用时需要注意传入HTML的规范。
第1种方法大家可以自己搜,本文重点介绍第2种方法,废话说完了_(:з」∠)_,终于可以进入正题了…
过程中用到的jar包:commons-lang-2.6.jar、jsoup-1.8.1.jar、poi-3.10.1-20140818.jar
下载地址:http://pan.baidu.com/s/1c02amQk
----------------------------------------------(づ ̄3 ̄)づ╭❤~华丽的分割线----------------------------------------------
我们目的是将HTML解析为Excel文件,需要分两步走:
1、使用Jsoup解析HTML
Jsoup是一个第三方HTML解析工具包,你可能没用过,但是你一看官方的介绍就明白了http://www.open-open.com/jsoup/,和DOM的操作方法特别相似。列举几个常用方法:
据标签名称获取元素:getElementsByTag(String tagName)
移除指定元素:removeAll(Elemente)
获取元素标签属性值:attr(StringattrName)
2、使用POI构造Excel文件
POI不用多说了,Excel导入导出经常用到的工具包。
第一步:使用Jsoup解析HTML
以下分析部分基于原文修改,并加入自己的详细解释:
直观的看,一个完整Excel的内容是由位于各个单元格(Cell)中的内容组合而成的。而每个单元格(Cell)都有相应X、Y坐标来标示其位置(最左上角的单元格,坐标(1,1))。也就是说,一个Excel文件实质上就是许多Cell构成的集合,每个Cell用坐标属性确定位置,用内容属性存储内容。
基于此,我们得到了最基本的Cell结构:
- X坐标
- Y坐标
- 合并列情况
- 合并行情况
- 内容
构成Excel的最基本的结构已经确定,下一步摆在我们面前的就是将html table转化为Excel Cell集合。
Html table中的每个td节点对应一个Excel单元格。单元格的内容可以通过解析table的td获取,行、列的合并情况也可由td的rowspan、colspan属性得出,转化的关键点就在于如何由table的tr、td结构确定Excel单元格位置,即如何确定X、Y坐标。
1、确定Y坐标
Y坐标容易确定,即td所在tr的行数。
例如:当前的td在table的第2行中,则当前td的Y坐标为2
2、确定X坐标
X坐标的确定没有那么简单,它要受到两方面因素的影响:
(1)与当前td处于同一tr,但位于其之前(反映在表格视觉上即其左侧td)td的占位情况。
也就是说,如果td在第2行,那么需要考虑同样在第2行的td的左边,可能存在有单元格有列的合并(占据当前行的若干个单元格),因为合并后的单元格只有一个坐标值,因此td左边的单元格合并的越多,td的X会越小。
(2)当前td所在tr的之前tr中某些td的跨行情况。
也就是说,如果td在第3行,那么需要考虑第1行和第2行可能存在单元格有行的合并(占据不同行的若干单元格)。比如在第1行中,一个td跨3行1列,那么当前第3行的td的X坐标会变小,因为第3行被合并的那个单元格并不属于第3行,属于第1行。
基于此种考虑,定位td的X坐标需经过两个过程的推导:用于处理左侧td占位影响的横向推导(HorizontalDeduction)和处理之前行跨行td影响的纵向推导(VerticalDeduction)。
以下图的table为例,展示两次推导过程,这两次的推导过程,就是这个问题的核心:
1 |
2 |
3 |
|
4 |
5 |
||
6 |
7 |
8 |
|
9 |
10 |
1、横向推导(HorizontalDeduction)
横向推导的目的就是发现与当前td处在同一行,且在td单元格左边,有单元格合并的情况。整个过程基于递归的原理,递归模型如下:
解释:n为当前行的单元格序列,从1开始。以table图为例,第1个单元格的X坐标X1=1,第二个单元格的X坐标X2=X1+colspan1=1+2=3。
也就是说,对于横向推导,td的X坐标=上个兄弟td的坐标+兄弟td的合并列数。
横向推导的java代码(文章里的原点为(1,1),代码里是(0,0)):
<span style="font-size:12px;"> /**
* @Title : HorizontalDeduction
* @Description : 使用递归,进行横坐标的第一步推导,横向推导,同时删除多余的非td元素
* @author : Qinchz
* @date : 2014年12月12日 下午8:51:39
* @param e
* @return
*/
private int HorizontalDeduction(Element e) {
Element preElement=e.previousElementSibling();
if(preElement!=null){
//表示td的上一个兄弟节点不是td,则删除这个多余的元素
if(!preElement.tagName().equals("td")){
preElement.remove();
}else{
int nColSpan=1;//默认为1
if(StringUtils.isNotBlank(preElement.attr("colspan"))){
//前一个元素的列合并情况
nColSpan=Integer.valueOf(preElement.attr("colspan").trim());
}
return HorizontalDeduction(preElement) + nColSpan;
}
}
return 0;
}</span><span style="font-size: 14px;">
</span>
经过横向推导,table的坐标情况:
1(1,1) |
2(3,1) |
3(4,1) |
|
4(1,2) |
5(2,2) |
||
6(1,3) |
7(2,3) |
8(3,3) |
|
9(1,4) |
10(2,4) |
2、纵向推导(VerticalDeduction)
纵向推导的目的是发现当前td所在行之前的行,存在跨行合并的情况。一次纵向推导的过程可以描述为(当前推导td用A表示):
找到A所在行之前的行tr中与A具有相同X坐标的td节点B
if(B.rowspan>(A.Y-B.Y))
{
X+=B.colspan,即A的X坐标向后推B.colspan的位置;
同时,与A同处一tr但在其后边的td节点均应向后推B.colspan个位移;
}
解释:
在横向推导之后,实际单元格的排列是有重合的(上图中不重合是因为忽略了单元格的高度,把高度视为相等),所以纵向推导就是要单元格合理的“避开”它之前行已经占用的位置。
例如:上图中,单元格4的坐标是(1,2),这并不是4最终的正确坐标,那么4为什么会跑到这里?因为横向推导只是让4在同一行中它的位置是对的,不能保证和4处在不同行的1对它产生的影响。
4为A节点,纵向推导先找到1为B节点。如果B跨越的行数>当前AB的Y坐标之差(即,判断A和B有重合),那么A的X坐标 =A.X+B跨越的列数(即,让A“避开”B已经占用的位置)。同时,A的移动影响了与A同行且在右边的节点,A右边的节点需要移动相同的长度。
就以节点4为例,按照上述的方法移动后的位置如图:
1(1,1) |
2(3,1) |
3(4,1) |
|
4(1,2) |
|||
|
5(2,2) |
||
|
|||
|
|||
6(1,3) |
7(2,3) |
8(3,3) |
|
9(1,4) |
10(2,4) |
纵向推导的java代码(文章里的原点为(1,1),代码里是(0,0)):
/**
* @Title : verticalDeduction
* @Description : 纵向推导
* @author : Qin
* @date : 2014年12月12日 下午9:12:25
* @param headerList
* @return
*/
private List<TD> verticalDeduction(List<TD> headerList) {
int headerSize = headerList.size();
for (int i = 0; i < headerSize; i++) {
TD tdA = headerList.get(i);
boolean flag = true;
while (flag) {// 不断排列当前节点的位置,直到它的位置绝对正确
flag = false;// 不需要移位
for (int j = i - 1; j >= 0; j--) {// 找到之前与td的横坐标相等的值
TD tdB = headerList.get(j);
if (tdA.getX() == tdB.getX()) {// A找到与其X坐标相等的B
// 如果B单元格“挡住”了A单元格,才进行移位操作。即:只有B占的行数
// 大于或等于A、B之间的距离,那么B才会挡住A
if (tdB.getRowspan() > tdA.getY() - tdB.getY()) {
// 如果存在移位单元格,则仍然需要重新判断当前的位置是否正确。需要移位
flag = true;
// A的X坐标向后推B.colspan的位置
tdA.setX(tdA.getX() + tdB.getColspan());
int YA = tdA.getY();
// 同时,与A同处一tr但在其后边的td节点均应向后推B.colspan位移
for (int m = i + 1; m < headerSize; m++) {
TD td = headerList.get(m);
if (td.getY() == YA) {
td.setX(td.getX() + tdB.getColspan());
}
}
}
}
}
}
}
return headerList;
}
单元格类:
/**
* @ClassName: TD
* @Description: 单元格类
* @author Qin
* @date 2014年12月12日 下午5:45:10
*/
class TD {
private int rowspan=1;
private int colspan=1;
private int x;
private int y;
private String content;
public int getRowspan() {
return rowspan;
}
public void setRowspan(int rowspan) {
this.rowspan = rowspan;
}
public int getColspan() {
return colspan;
}
public void setColspan(int colspan) {
this.colspan = colspan;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
}
以上是从HTML table构造Excel Cell的整个过程的核心代码(完成源码在文章附在文章末尾),经过整个构造过程,我们就得到了每个td的绝对坐标X、Y,再结合已知的td行列合并情况、td的内容,我们完全可以在Excel中构造出需要的表格。
第二步:使用POI构造Excel文件
烧脑的内容都在第一步,第二步很简单。
第一步中,我们得到了List< TD >这个含有表格信息的集合,其中含有每个表格的坐标、合并情况、内容。我们需要利用这些信息进行Excel中单元格的构造。
其实重点是进行单元格的合并,POI中HSSFSheet类有一个单元格合并的方法:
//参数:起始行,终止行,起始列,终止列。这四个参数,就要用到第一步得到的坐标
addMergedRegion(newCellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
下是合并单元格的部分核心代码:
//表头、单元格数据内容写入
for(int i=0;i<finalHeaderList.size();i++){
TD td=finalHeaderList.get(i);
sheet.getRow(td.getY()).getCell(td.getX()).setCellValue(td.getContent());
//单元格合并
sheet.addMergedRegion(
new CellRangeAddress(//起始行,终止行,起始列,终止列
td.getY(),
td.getY()+(td.getRowspan()-1),
td.getX(),
td.getX()+(td.getColspan()-1))
);
}
既然是web项目,得实现“下载Excel”功能吧,起初想通过Struts2的下载功能实现,结果不太好用,换了一种更好用的方式。
项目中所涉及的所有源码贴到下面:
1、Action
页面传递两个参数:需要解析的HTML字符串,下载的文件名称
import java.net.URLEncoder;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.struts2.ServletActionContext;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import com.opensymphony.xwork2.ActionContext;
import com.wondersgroup.qyws.common.utils.HtmlToExcel;
import com.wondersgroup.qyws.tjfx.common.BaseAction;
@Controller("htmlToExcelAction")
@Scope("prototype")
public class HtmlToExcelAction extends BaseAction{
privatestatic final long serialVersionUID = 5975906847955053344L;
/**前台传递的html字符串*/
privateString htmlStr;
/**文件名称*/
privateString fileName;
publicvoid htmlToExcel() throws Exception {
if(StringUtils.isNotBlank(htmlStr)){
try{
HtmlToExcelhtmlToExcel=new HtmlToExcel(2,fileName);
HSSFWorkbookwb= htmlToExcel.readHtmlStr(htmlStr);
ActionContextcontext = ActionContext.getContext();
HttpServletResponseresponse = (HttpServletResponse)context.get(ServletActionContext.HTTP_RESPONSE);
response.setHeader("Content-Disposition","attachment; filename="
+URLEncoder.encode(fileName+".xls", "utf-8"));
ServletOutputStreamfOut = response.getOutputStream();
wb.write(fOut);
fOut.flush();
fOut.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
publicString getHtmlStr() {
returnhtmlStr;
}
publicvoid setHtmlStr(String htmlStr) {
this.htmlStr= htmlStr;
}
publicString getFileName() {
returnfileName;
}
publicvoid setFileName(String fileName) {
this.fileName= fileName;
}
}
2、HTMLTOEXCEL工具类
构造器两个参数:生成Excel的标题所占行数,需要解析的HTML字符串
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.util.CellRangeAddress;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import com.wondersgroup.qyws.tjfx.common.BaseAction;
/**
* @ClassName:HtmlToExcel
* @Description: 传入html代码字符串,返回POI的工作簿对象HSSFWorkbook
* @author Qin
* @date 2014年12月13日 下午6:36:31
*/
public class HtmlToExcel extends BaseAction{
privatestatic final long serialVersionUID = 4175158575304402752L;
/**表格的列数*/
private int columnSize;
/**表格的行数*/
private int rowSize;
/**数据的行数,不含表头*/
private int rowSize_data;
/**标题所占行数*/
privateint rowSize_title=2;
/**表头所占行数*/
privateint rowSize_header;
/**工作表名称*/
privateString sheetName;
publicHtmlToExcel(int rowSize_title,String sheetName){
this.rowSize_title=rowSize_title;
this.sheetName=sheetName;
}
/**
* @Title : readHtmlStr
* @Description : 使用jsoup解析html,得到表头数据List,表体数据String[][]
* @author : Qin
* @date : 2014年12月13日 下午6:32:55
* @return
* @throws Exception
*/
public HSSFWorkbook readHtmlStr(String htmlStr)throws Exception{
Documentdoc = Jsoup.parseBodyFragment(htmlStr, "utf-8");
doc.select("input[type$=hidden]").remove();//删除所有input隐藏域
doc.select("tr[style*=none]").remove();//删除隐藏的tr
Elementscaptions=doc.getElementsByTag("caption");//查找表头
StringtableTitle="";// 保存表头标题
if(captions!=null&&captions.size()>0){
Elementcaption=captions.get(0);
tableTitle=caption.text();
}else{
rowSize_title=0;//表示,没有表头
}
Elementstrs_data = doc.getElementsByTag("tr");//获取所有tr
rowSize= trs_data.size()+rowSize_title;//获取表格的行数,外加标题行数
Elementstheads=doc.getElementsByTag("thead");//表头thead标签
List<TD>finalHeaderList=new ArrayList<TD>();//存放推导完毕的正确数据
List<TD>dataList = new ArrayList<TD>();//表头1单元格List
if(theads!=null&&theads.size()>0){//表示有表头
Elementsthead_trs=theads.get(0).getElementsByTag("tr");//表头中的tr
rowSize_header=thead_trs.size();
trs_data.removeAll(thead_trs);//移除表头中的的tr元素,trs中剩下数据行
List<TD>headerList = new ArrayList<TD>();//表头1单元格List
//构造表头
//将表头数据存到List中。x、y坐标从0开始
//确定x坐标之1:横向推导
intbasicY_thead=rowSize_title;
for(inti=0;i<thead_trs.size();i++){
Elementthead_tr=thead_trs.get(i);
Elementsthead_tr_ths=thead_tr.getElementsByTag("th");
for(intj=0;j<thead_tr_ths.size();j++){
Elemente=thead_tr_ths.get(j);
TDtd=new TD();
td.setContent(e.text());
if(StringUtils.isNotBlank(e.attr("colspan"))){
td.setColspan(Integer.valueOf(e.attr("colspan").trim()));
}
if(StringUtils.isNotBlank(e.attr("rowspan"))){
td.setRowspan(Integer.valueOf(e.attr("rowspan").trim()));
}
td.setX(HorizontalDeduction_th(e));//步骤1:横向推导,但这个坐标并不是最终坐标,需要进行纵向推导
td.setY(i+basicY_thead);//y坐标很简单,就是tr的值
headerList.add(td);
}
}
//确定x坐标之2:纵向推导
finalHeaderList=verticalDeduction(headerList);
if(trs_data.size()>0){//表示有表格内容数据
rowSize_data=trs_data.size();
}else{//表示只有表头数据,没有表格内容数据
rowSize_data=0;
}
}else{//表示没有表头
rowSize_header=0;
}
//循环每一个数据单元格
intbasicY_data=rowSize_title+rowSize_header;
for(int i = 0; i < trs_data.size(); i++) {
Elementtr = trs_data.get(i);
Elementstds = tr.getElementsByTag("td");
//循环每一行的所有列
for(int j = 0; j < tds.size(); j++) {
Elemente = tds.get(j);
Elementsinp=e.getElementsByTag("input");
TDtd=new TD();
if(StringUtils.isNotBlank(e.attr("colspan"))){
td.setColspan(Integer.valueOf(e.attr("colspan").trim()));
}
if(StringUtils.isNotBlank(e.attr("rowspan"))){
td.setRowspan(Integer.valueOf(e.attr("rowspan").trim()));
}
if(inp!=null&&inp.size()>0){//表示td中嵌套input
td.setContent(inp.get(0).val());
}else{//表示td中没有嵌套input
td.setContent(e.text().trim());
}
td.setX(HorizontalDeduction_td(e));//步骤1:横向推导,但这个坐标并不是最终坐标,需要进行纵向推导
td.setY(i+basicY_data);//y坐标很简单,就是tr的值
dataList.add(td);
}
}
//步骤2:纵向推导
dataList=verticalDeduction(dataList);
//表头和表内容合并为一个List
finalHeaderList.addAll(dataList);
//对列进行赋值,找到第一个单元格计算列宽
TDlastTd=finalHeaderList.get(finalHeaderList.size()-1);
columnSize=lastTd.getX()+lastTd.getColspan();
int[][]contextSizeArr=new int[rowSize][columnSize];//记录内容单元格内容长度,便于进行列宽自适应调整
String[][]dataArr=new String[rowSize_data][columnSize];
//将表格的长度按照该单元格的位置填入字符串长度
//不能使用普通下标方式赋值,因为如果有合并单元格的情况,数组的位置就会错位,使用坐标保证不会错位
for(int i = 0; i < finalHeaderList.size(); i++) {
TDtd = finalHeaderList.get(i);
contextSizeArr[td.getY()][td.getX()]= getStringLength(td.getContent())+1;
}
int[]maxLengthArr = getMaxLength(contextSizeArr);
//根据解析到的数据返回POI的Excel对象
returnbuildExcel(tableTitle,finalHeaderList,dataArr,maxLengthArr);
}
/**
* @Title : getStringLength
* @Description : 中文字符与非中文字符长度计算
* @author : Qin
* @date : 2014年12月14日 下午12:30:45
* @param s
* @return
*/
privateint getStringLength(String s) {
doublevalueLength = 0;
String chinese ="[\u4e00-\u9fa5]";
// 获取字段值的长度,如果含中文字符,则每个中文字符长度为2,否则为1
for (int i = 0; i < s.length(); i++){
// 获取一个字符
String temp = s.substring(i, i +1);
// 判断是否为中文字符
if (temp.matches(chinese)) {
// 中文字符长度为1
valueLength += 1;
} else {
// 其他字符长度为0.5
valueLength += 0.5;
}
}
//进位取整
return (int) Math.ceil(valueLength);
}
/**
* @Title : getMaxLength
* @Description : 竖向遍历二维数组,找到每一列的最大值
* @author : Qin
* @date : 2014年12月14日 上午1:18:02
* @param contextSizeArr
* @return
*/
privateint[] getMaxLength(int[][] contextSizeArr) {
int[]maxArr=new int[columnSize];
for(inti=0;i<columnSize;i++){
intbasic=0;
for(intj=0;j<rowSize;j++){
if(contextSizeArr[j][i]>basic){//注意下标的写法
basic=contextSizeArr[j][i];
}
}
maxArr[i]=basic;
}
returnmaxArr;
}
/**
* @Title : HorizontalDeduction
* @Description : 使用递归,进行横坐标的第一步推导,横向推导,同时删除多余的非td元素
* @author : Qin
* @date : 2014年12月12日 下午8:51:39
* @param e
* @return
*/
private int HorizontalDeduction_td(Element e) {
ElementpreElement=e.previousElementSibling();
if(preElement!=null){
if(!preElement.tagName().equals("td")){//表示td的上一个兄弟节点不是td,则删除这个多余的元素
preElement.remove();
}else{
intnColSpan=1;//默认为1
if(StringUtils.isNotBlank(preElement.attr("colspan"))){
nColSpan=Integer.valueOf(preElement.attr("colspan").trim());//前一个元素的列合并情况
}
returnHorizontalDeduction_td(preElement) + nColSpan;
}
}
return0;
}
/**
* @Title : HorizontalDeduction
* @Description : 使用递归,进行横坐标的第一步推导,横向推导,同时删除多余的非td元素
* @author : Qin
* @date : 2014年12月12日 下午8:51:39
* @param e
* @return
*/
private int HorizontalDeduction_th(Element e) {
ElementpreElement=e.previousElementSibling();
if(preElement!=null){
if(!preElement.tagName().equals("th")){//表示td的上一个兄弟节点不是td,则删除这个多余的元素
preElement.remove();
}else{
int nColSpan=1;//默认为1
if(StringUtils.isNotBlank(preElement.attr("colspan"))){
nColSpan=Integer.valueOf(preElement.attr("colspan").trim());//前一个元素的列合并情况
}
returnHorizontalDeduction_th(preElement) + nColSpan;
}
}
return0;
}
/**
* @Title : verticalDeduction
* @Description : 纵向推导
* @author : Qin
* @date : 2014年12月12日 下午9:12:25
* @param headerList
* @return
*/
privateList<TD> verticalDeduction(List<TD> headerList) {
intheaderSize = headerList.size();
for(int i = 0; i < headerSize; i++) {
TDtdA = headerList.get(i);
booleanflag = true;
while(flag) {// 不断排列当前节点的位置,直到它的位置绝对正确
flag= false;// 不需要移位
for(int j = i - 1; j >= 0; j--) {// 找到之前与td的横坐标相等的值
TDtdB = headerList.get(j);
if(tdA.getX() == tdB.getX()) {// A找到与其X坐标相等的B
if(tdB.getRowspan() > tdA.getY() - tdB.getY()) {// 如果B单元格“挡住”了A单元格,才进行移位操作。即:只有B占的行数大于或等于A、B之间的距离,那么B才会挡住A
flag= true;// 如果存在移位单元格,则仍然需要重新判断当前的位置是否正确。需要移位
tdA.setX(tdA.getX()+ tdB.getColspan());// A的X坐标向后推B.colspan的位置
intYA = tdA.getY();
for(int m = i + 1; m < headerSize; m++) {// 同时,与A同处一tr但在其后边的td节点均应向后推B.colspan个位移
TDtd = headerList.get(m);
if(td.getY() == YA) {
td.setX(td.getX()+ tdB.getColspan());
}
}
}
}
}
}
}
returnheaderList;
}
/**
* @Title : buildExcel
* @Description : 依据传入的数据生成Excel文件
* @author : Qin
* @date : 2014年12月13日 下午6:32:06
* @param title
* @param finalHeaderList 表格表头数据
* @param dataArr 表格内容数据
* @return
* @throws Exception
*/
private HSSFWorkbook buildExcel(Stringtitle,List<TD> finalHeaderList,String[][] dataArr,int[] maxLengthArr)
throwsException {
HSSFWorkbookwb=new HSSFWorkbook();
HSSFSheetsheet=null;
if(StringUtils.isNotBlank(sheetName)){
sheet=wb.createSheet(sheetName);
}elseif(StringUtils.isNotBlank(title)){
sheet=wb.createSheet("title");
}else{
sheet=wb.createSheet("Sheet1");
}
//表格样式
//1、基础样式
HSSFCellStylebasicStyle=wb.createCellStyle();
basicStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);//设置水平居中
basicStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); //设置垂直居中
basicStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);// 下边框
basicStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);//左边框
basicStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);//上边框
basicStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);//右边框
//2、标题样式
HSSFCellStyletitleStyle=wb.createCellStyle();
titleStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);//设置水平居中
titleStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); //设置垂直居中
HSSFFontheaderFont1 = (HSSFFont) wb.createFont();
headerFont1.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);//设置字体加粗
headerFont1.setFontHeightInPoints((short)14);//设置字体大小
titleStyle.setFont(headerFont1);
//3、偶数行样式
HSSFCellStyleevenStyle=wb.createCellStyle();
evenStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
evenStyle.setFillForegroundColor(HSSFColor.WHITE.index);
evenStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);
HSSFCellStyleoldStyle=wb.createCellStyle();
oldStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
oldStyle.setFillForegroundColor(HSSFColor.GREY_25_PERCENT.index);
oldStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER);
//构建基本空白表格
for(inti=0;i<rowSize;i++){
sheet.createRow(i);
for(intj=0;j<columnSize;j++){
sheet.getRow(i).createCell(j).setCellStyle(basicStyle);
}
}
//填充数据
if(rowSize_title!=0){
//1、标题
HSSFCellcell=sheet.getRow(0).getCell(0);
cell.setCellStyle(titleStyle);
cell.setCellValue(title);
//单元格合并
sheet.addMergedRegion(newCellRangeAddress(0,rowSize_title-1,0,columnSize-1));//起始行,终止行,起始列,终止列
}
//2、表头、单元格数据内容写入
for(inti=0;i<finalHeaderList.size();i++){
TDtd=finalHeaderList.get(i);
sheet.getRow(td.getY()).getCell(td.getX()).setCellValue(td.getContent());
//单元格合并
sheet.addMergedRegion(
newCellRangeAddress(//起始行,终止行,起始列,终止列
td.getY(),
td.getY()+(td.getRowspan()-1),
td.getX(),
td.getX()+(td.getColspan()-1))
);
}
//3、设置每一列宽度以该列的最长内容为准
for(inti=0;i<maxLengthArr.length;i++){
sheet.setColumnWidth(i,maxLengthArr[i]*2*235);
}
for(inti=0;i<rowSize;i++){
HSSFRowrow =sheet.getRow(i);
row.setHeightInPoints(row.getHeightInPoints()+3);
}
returnwb;
}
}
/**
* @ClassName: TD
* @Description: 单元格类
* @author Qin
* @date 2014年12月12日 下午5:45:10
*/
class TD {
privateint rowspan=1;
privateint colspan=1;
privateint x;
privateint y;
privateString content;
publicint getRowspan() {
returnrowspan;
}
publicvoid setRowspan(int rowspan) {
this.rowspan= rowspan;
}
publicint getColspan() {
returncolspan;
}
publicvoid setColspan(int colspan) {
this.colspan= colspan;
}
publicString getContent() {
returncontent;
}
publicvoid setContent(String content) {
this.content= content;
}
publicint getX() {
returnx;
}
publicvoid setX(int x) {
this.x= x;
}
publicint getY() {
returny;
}
publicvoid setY(int y) {
this.y= y;
}
}