PO2与POI3差异研究(基于POI3.8)用于解决JAVA环境下EXCEL2003.EXCEL2007.EXCEL2010的兼容性问题...

摘 要
我司系统是WEB项目,JAVA/WEBLOGIC/LINUX环境,会在前端客户机上会安装一个公司开发的EXCEL插件,用于与后台系统进行交互,近期在操作EXCEL时发现EXCEL2003和EXCEL2007之间的一系列兼容性问题,本文即对POI操作EXCEL的兼容性问题进行的探讨。
本文首先对现有问题的现象进行了描述,然后再对POI结构、POI版本差异作了概述,最后提供了一套解决方案。
阅读本文,默认读者已经了解了POI的具体使用,本文不具体介绍POI的API。

正文

1 现有问题:
目前以EXCEL为基础进行数据制作,在前端客户机上会安装一个公司开发的EXCEL插件,用于与后台系统进行交互,近期发现EXCEL版本之间的一系列兼容性问题:

1、 涉及到EXCEL2007文件的后台操作,会导致后台错误。
org.apache.poi7.poifs.filesystem.OfficeXmlFileException: The supplied data appears to be in the Office 2007+ XML. You are calling the part of POI that deals with OLE2 Office Documents. You need to call a different part of POI to process this data (eg XSSF instead of HSSF)
2、 涉及EXCEL导出的部分,若文件内容是EXCEL2007,却使用.XLS,为后缀名的文件。那么使用EXCEL2003打开会导致乱码现象。
3、 目前使用后台创建的EXCEL文件,无论客户安装的是什么版本的OFFICE,后台都是用EXCEL2003(POI2)文件格式创建的。

2 问题原因:
报表平台现有系统在后台处理Excel都是使用apache的POI2.x进行解析处理,但是poi2.x是和jdk1.4兼容的,且poi2.x只能处理excel2003及以前的老版本excel文件,客户机器上安装的往往是较新版的office2007,要处理excel2007就必须使用poi3.5以上的版本,poi3.5及以上的版本适用jdk1.5版及以上,并支持excel2007的处理。

3 POI2.x与POI3.x的区别:

3.1 什么是POI
Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程式对Microsoft Office格式档案读和写的功能。现在较新较稳定的POI版本是3.7,即常说的POI7,最新的POI3.8已经出了beta2版本,支持EXCEL2010的操作。
3.2 如何使用POI
完整的POI3.8 beta2包含如下类库:
poi-3.8-beta2-20110408.jar
poi-examples-3.8-beta2-20110408.jar
poi-excelant-3.8-beta2-20110408.jar
poi-ooxml-3.8-beta2-20110408.jar (Office Open XML)
poi-ooxml-schemas-3.8-beta2-20110408.jar
poi-scratchpad-3.8-beta2-20110408.jar
另需要一些配套包的支持(目前公司资产LIB库中已经存在的)
dom4j-1.6.1.jar
stax-api-1.0.1.jar
xmlbeans-2.3.0.jar (必须使用2.3或以上的版本)
commons-logging-1.1.jar
junit-3.8.1.jar
log4j-1.2.13.jar
POI3.8的具体API及实现方法不在本文详述。

3.3 POI结构及版本区别:
1、 首先,相对于POI2.X及以下版本,POI3.X支持较高版本的Office操作,在本文中关注的是能够对EXCEL2007进行操作,并可兼容操作EXCEL2003。

部分类图如下:


(图1)

如上图,POI2.x的结构只有类图中左半部分,POI3.x新增了以XSSF前缀的EXCEL文件操作对象,用于对OOM格式的EXCEL操作。

POI3完整具体结构和作用如下:
  结构:
  HSSF - 提供读写Microsoft Excel格式档案的功能。
  XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
  HWPF - 提供读写Microsoft Word格式档案的功能。
  HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
HDGF - 提供读写Microsoft Visio格式档案的功能。

2、其次,另一个重要区别就是,excel2007文件格式与之前版本不同,之前版本采用的是微软自己的存储格式。07版内容的存储采用XML格式(OOM)。
4 现有问题解决方案:
4.1 解决方案要求:
1、 解决目前EXCEL2003和EXCEL2007兼容性问题。
2、 提供后续EXCEL版本,如EXCEL2010的可扩展性。

4.2 算法(实现逻辑)
1、 兼容性:

如(图1)所示,我们可以看到,操作EXCEL2003的对象(HSSF为前缀)与操作EXCEL2007的对象(XSSF为前缀)共用一套接口,在遇到未知版本的EXCEL文件时,可以通过POI提供的方法判断文件头来获取版本信息,构造具体版本的Workbook实例。再返回统一的接口达到兼容性要求。
创建兼容性Workbook的工厂方法代码如下:
public static Workbook createCommonWorkbook(InputStream inp)
throws IOException, InvalidFormatException {
// 首先判断流是否支持mark和reset方法,最后两个if分支中的方法才能支持
if (!inp.markSupported()) {
// 还原流信息
inp = new PushbackInputStream(inp, 8);
}
// EXCEL2003使用的是微软的文件系统
if (POIFSFileSystem.hasPOIFSHeader(inp)) {
return new HSSFWorkbook(inp);
}
// EXCEL2007使用的是OOM文件格式
if (POIXMLDocument.hasOOXMLHeader(inp)) {
// 可以直接传流参数,但是推荐使用OPCPackage容器打开
return new XSSFWorkbook(OPCPackage.open(inp));
}
throw new IOException("不能解析的excel版本");
}
通过传入的文件流,获取到通用的Workbook接口,就可以进行一系列不同的业务操作了,实际上到这一步EXCEL2003与EXCEL2007的兼容性问题已经基本解决了,简单吧?具体实例请阅读本文最后的DEMO,此处不详述。

当然,HSSFWorkbook(EXCEL2003)会与XSSFWorkbook(EXCEL2007)有很多差异,通用接口只能满足这两者的交集部分。
如下图:

(图2)
若接口不能完全满足要求,可以通过判断Workbook类型,强制转换成其对应版本的对象,分别作两套或多套方法来处理。这是为满足兼容性值得牺牲,也是必须牺牲的部分。根据实际操作经验,接口能满足绝大部分EXCEL的基本操作,不能满足需要的情况非常少见。具体功能差异未作深究,感兴趣的读者可自行对比API。
伪码如下:
Workbook wb = createCommonWorkbook(不确定的版本的EXCEL文件流);

if (wb instanceof HSSFWorkbook) {
HSSFWorkbook hwb = (HSSFWorkbook)wb;
EXCEL2003的处理部分
} else if (wb instanceof XSSFWorkbook) {
XSSFWorkbook xwb = (XSSFWorkbook)wb;
EXCEL2007的处理部分
} else {
throw new IOException("不能解析的excel版本");
}

2、 扩展性:
我们在兼容性部分已经创建了获取兼容Workbook的工厂方法,若遇到其它版本,如EXCEL2010的需求,在工厂方法里添加一个创建EXCEL2010的Workbook条件就行了。
修改createCommonWorkbook方法,伪码如下:
public static Workbook createCommonWorkbook(InputStream inp)
throws IOException, InvalidFormatException {
...
if (输入文件的文件头为EXCEL2010格式) {
return new EXCEL2010Workbook(inp);
}
...
}
throw new IOException("不能解析的excel版本");
}
既然版本都是固定的,那么再提供一个版本枚举类,不就可以更方便管理不同的EXCEL版本了吗,如果将枚举的VALUE值定义为文件后缀名,也一并解决了不同版本的EXCEL文件后缀名不同的恼人问题,真是一举多得呢。后续即使有更高的Office版本,如EXCEL2010,也只需要添加一个工厂条件,再添加一个枚举项即可实现无缝升级了。
枚举类属性如下:
// KEY:版本号
// VALUE:文件后缀名

/** EXCEL2003版本 */
EXCEL_2003(2003, ".xls"),

/** EXCEL2007版本 */
EXCEL_2007(2007, ".xlsx");

/** 后续添加扩展添加的EXCEL2010版本 */
EXCEL_2010(2010, ".xlsx");

再创建一个根据Workbook取得版本信息的工具方法,如下:
public static ExcelVersionTypeEnum checkExcelVersion(Workbook wb)
throws IOException {

if (wb instanceof HSSFWorkbook) {
return ExcelVersionTypeEnum.EXCEL_2003;
} else if (wb instanceof XSSFWorkbook) {
return ExcelVersionTypeEnum.EXCEL_2007;
} else {
throw new IOException("不能解析的excel版本");
}
}
以后获取版本信息或后缀名就方便了:
ExcelVersionTypeEnum ev =CompatibleExcelUtil.checkExcelVersion(wb);
String suffix = ev.getValue();

3、 DEMO:
源码如下:

public class Test {

public static void main(String[] args) {
String path2007 = "d:\\2007.xlsx";
String path2003 = "d:\\2003.xls";
Test.Demo(path2003);
}

public static void Demo(String filePath){
InputStream is = null;
try {
is = new BufferedInputStream(new FileInputStream((new File(filePath))));
//调用工具类,获取兼容的Workbook接口(兼容性)
Workbook wb = CompatibleExcelUtil.createCommonWorkbook(is);
//获取首个单元格值(具体业务处理)
Sheet sheet = wb.getSheetAt(0);
sheet.getRow(0);
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
//调用工具类,返回版本枚举
ExcelVersionTypeEnum ev =CompatibleExcelUtil.checkExcelVersion(wb);
//控制台打印信息
System.out.println("EXCEL版本号:" + ev);
System.out.println("文件后缀名:" + ev.getValue() + "(即使此DEMO中文件后缀名不规范,此处也能正确识别)");
System.out.println("首个单元格值:" + cell.getNumericCellValue());
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
运行结果:
在D盘创建EXCEL2003格式文件《2003.xls》首个单元格值为2003。运行Test.Demo(path2003);
得到以下结果:

在D盘创建EXCEL2003格式文件《2007.xlsx》首个单元格值为111。运行Test.Demo(path2007);
得到以下结果:

把2007.xlsx改后缀名为2007.xls,运行Test.Demo("d:\\2007.xls");
得到以下结果:
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值