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,

// 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"); 
得到以下结果: 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值