这篇博文在我的草稿箱里已经放够久了,又到一年樱花时,想起母校到处灿烂的樱花了。不知道老班这个感性的女人今年有没有再去老校区拍些照片传到空间里,也好解下思念母校之苦啊!当然了,今天去蠡湖公园欣赏樱花的来着。呵呵呵~今天结束jxl读写Excel表的应用吧。
JExcelAPI简称JXL,一套纯Java实现读写Excel功能的API;非常的简洁,只需要一个jxl.jar (该博文对应的jxl版本为2.6.12)就可以。有多个传说中的官网,也不知道怎么搞的,整得乱七八糟的,最后找到这个http://www.andykhan.com/jexcelapi,绝对有你所有想要的资料。
我们也知道Apache POI项目相对于JXL,则提供了更为功能强大的Excel读取功能,还包括了Microsoft其它文档的读写API,包括word、powerpoint等等,拥有着强大的社区支持,在2011-03-07已经出了3.8的测试版本,相对于JXL,它的功能确实够强大。不过,要读写不同版本的Excel还得切换不同的API,也不得不说,这是件幸福而烦恼的事情。
今天暂且介绍JXL读写Excel,有需要的朋友,学习POI项目还是有必要的,我这都是图省事儿。
先说说这个JXL的实用类的由来吧,主要是面向加拿大和美国的项目产品,所以,国际化就成了必不可少的功能,英文能猜个八九不离十,法文就糟糕了。所以,我们这边必须得整理出项目中的单词发给加拿大那边的同事给我们进行法文和英文的翻译,最后我们共同协商决定用Excel统一发过去,N多的国际化资源文件,让你一个个的往Excel里面拷贝,搞这个玩意儿,我相信你搞会儿有够你郁闷的。
所以,急需要一个工具类,将*.properties和*.js文件读取到Excel表中,然后再把翻译完的Excel表的内容读出再写到properties和js文件中,所以ExcelOperator.java应运而生。
不管怎么样,先得把properties以及js文件写入Excel表中,properties文件我们肯定是很清楚了,也是存储key-value值的一种方式,当然,在我们的项目中,js也采用了这种key-value的方式,便于国际化,现列出properties和js文件的格式:
Properties:
res_properties_i18n_test1 =国际化1 res_properties_i18n_test2 =国际化2 res_properties_i18n_test3 =国际化3 res_properties_i18n_test4 =国际化4
Js:
res_js_i18n_test1 = "国际化1"; res_js_i18n_test2 = "国际化2"; res_js_i18n_test3 = "国际化3"; res_js_i18n_test4 = "国际化4";
一、 先看将这两种类型的文件写入Excel的方法writeExcel(String[] fileNames,String specName):
现对此代码的分析如下:
/** * 将Js/Properties文件读取存储到xls文件中 * @param fileNames 待处理的文件集合 * @param specName 指定的文件名 * @throws WriteException * @throws RowsExceededException * */ public String writeExcel(String[] fileNames,String specName) throws BiffException, IOException, RowsExceededException, WriteException { if(null!=fileNames&&fileNames.length>0){ //如未指定Excel文件名则根据当前时间获取 if(null==specName||specName.length()<=0){ specName = new Date().getTime()+".xls"; } //1)创建Excel OutputStream os = new FileOutputStream(specName); WritableWorkbook workbook = Workbook.createWorkbook(os); for(int i=0;i<fileNames.length;i++){ String fileName = fileNames[i];//文件名 InputStream is = this.getClass().getResourceAsStream(fileName); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String prefixName = fileName.substring(0,fileName.indexOf(".")); //获取i18n类型,并格式化成诸如:的ZH-CN形式 String i18nType = ((prefixName.substring(prefixName.length()-5)).replace("_","-")).toUpperCase(); //文件的扩展名 String suffixName = fileName.substring(fileName.indexOf(".")+1); //2)创建sheet String sheetName = prefixName.substring(0,prefixName.indexOf("_"))+"."+suffixName; WritableSheet wSheet = null; if(i==0){ wSheet = workbook.createSheet(sheetName,i); }else{ //获取已建立的sheet名称 String[] sheetNames = workbook.getSheetNames(); //与之前的创建的sheet进行比较,看是否已存在同名, //如果存在,则把此sheet赋给当前sheet,否则新建一个sheet。 for(int k=0;k<sheetNames.length;k++){ WritableSheet frontSheet = workbook.getSheet(k);//获取之前创建的sheet if(sheetName.equals(frontSheet.getName())){ wSheet = frontSheet; break; } } if(wSheet==null){//不存在同名的sheet,则新建一个sheet wSheet = workbook.createSheet(sheetName,sheetNames.length); } } //3)需要每次初始化,否则抛异常 //Exception in thread "main" jxl.write.biff.JxlWriteException:Cell has already been added to a worksheet WritableCell[] wcells = newInstanceHeaderCell(); for(int j=0;j<wcells.length;j++){//添加表头 wSheet.addCell(wcells[j]); } //4) if(suffixName.equals(FILE_SUFFIX_PROPERTIES)){ readProperties(wSheet,reader,i18nType);//读取Properties }else if(suffixName.equals(FILE_SUFFIX_JS)){ readJs(wSheet, reader, i18nType);//读取js } } workbook.write(); workbook.close(); } return null; }
1)通过指定的文件名(或者自动生成),得到一个Excel的文件输出流,接着通过WorkBook这个抽象类的静态方法createWorkbook(OutputStream os)创建一个可读写的工作薄实例WritableWorkbook,这里有必要解释一下WorkBook,此抽象类就是代表一个工作簿,即一个具体的Excel,该类包含了多种的工厂方法以及提供了多种访问工作表的访问器。除了通过指定输出流来生成一个可读写的WritableWorkbook实例外,你还可以通过指定File对象,具体的可参见源代码,在此不详述!
2)创建一个工作簿的Sheet对象,如何获取Sheet的文件名,现在大概说一下,我之前有说过,Excel表格的格式,而在我的项目中,它是分别放在中、英、法对应的国际化目录中的,所以,我会将每个国家对应的国家化文件读取到同一个Sheet中。所以,通过指定的SheetName和位置生成一个可读写的WritableSheet对象,如果此Sheet已经存在,则获取,否则新建一个。
3)有了工作表之后,之后的工作当然要往里面写入表格内容,每一个元件即对应JXL中的Cell对象,创建当前工作表的表头元件集合,即newInstanceHeaderCell方法:
/** * 返回新cell实例集合 * */ private WritableCell[] newInstanceHeaderCell(){ WritableCell[] wcells = new Label[4]; wcells[0] = new Label(0,0,RESOURCES_KEY); wcells[1] = new Label(1,0,RESOURCES_EN_US); wcells[2] = new Label(2,0,RESOURCES_FR_BE); wcells[3] = new Label(3,0,RESOURCES_ZH_CN); return wcells; }
该方法返回一个WritableCell的数据,数组里每个元素的实例为Label,实际上,顾名思议就是指单元格的文本了,呵呵,不再多说,咱继续看。
4)根据不同文件的后缀名,来读取对应文件里的内容,着重说一下readProperties这个方法吧(readJs原理相同,具体可参见附件源码):
// 读取properties的全部信息 protected static void readProperties(WritableSheet wSheet,BufferedReader reader,String i18nType){ Properties props = new Properties(); try { props.load(reader); //1)读入Properties Enumeration en = props.propertyNames(); int row=1; while (en.hasMoreElements()){ String key = (String) en.nextElement(); //2)获取当前的Key值,以便与其进行匹配 String nowKey = wSheet.getCell(0,row).getContents(); boolean addFlag = false; if(null==nowKey||""==nowKey){ wSheet.addCell(new Label(0,row,key)); addFlag = true; }else{ if(key.equals(nowKey)){ addFlag = true; } } //3) if(addFlag){ //获取当前Properties主键对应的值 String value = props.getProperty(key); if(i18nType.equalsIgnoreCase(RESOURCES_EN_US)){ wSheet.addCell(new Label(1,row,value)); }else if(i18nType.equalsIgnoreCase(RESOURCES_FR_BE)){ wSheet.addCell(new Label(2,row,value)); }else if(i18nType.equalsIgnoreCase(RESOURCES_ZH_CN)){ wSheet.addCell(new Label(3,row,value)); } } row++; } } catch (Exception e) { e.printStackTrace(); } }
1)通过Properties的load方法载入包含数据的文件,然后再通过propertyNames方法返回此property文件的key值集合并保存到Enumeration类型的en变量中。
2)遍历Properties文件,通过wSheet.getCell(0,row).getContents(),获取当第一列每一行的表格主键值的内容,如果不存在,则通过wSheet.addCell(new Label(0,row,key))这个方法来创建。row是个变量,记录每一行。
3)如果一切顺利的话,则比较后缀名,将获取到properties的值存储到对应的国际化列下。
OK,将Properties和Js的文件写入到Excel算是搞定了,以下测试代码:
ExcelOperator eo = ExcelOperator.getInstance(); String[] fileNames = {"Jsi18nTestResource_ZH_CN.js","Jsi18nTestResource_EN_US.js","Proi18nTestResource_ZH-CN.properties"}; eo.writeExcel(fileNames, "i18n_2011_04_04.xls");
如果一切运行正常的话,则会在项目目录下生成一个名为i18n_2011_04_04.xls的文件,内容截图如下:
从结果来看,传入三个文件名,生成两个Sheet,Sheet名为Jsi18nTestResource.js的内容,除了FR-BE法文没有内容外,另外两个分别从Jsi18nTestResource_ZH_CN.js和Jsi18nTestResource_EN_US.js中读取并写入。
二、将对应的文件写入到Excel表之后,经过国际化的翻译,我还得把它转过来不是嘛~接下来,做这样的事情之前呢,为了能够生成法文文件,所以,将Sheet名为Jsi18nTestResource.js的FR-BE列分别加入testFr1~testFr4
/** * @param path 文件路径 * js/properties 最终生成的文件是.js/.properties */ public String readExcel(String path) throws BiffException, IOException { InputStream in = getClass().getResourceAsStream(path); if (in == null) { throw new IllegalArgumentException(path + " not found."); } //1) WorkbookSettings wbs = new WorkbookSettings(); wbs.setEncoding("ISO-8859-1");// 解决乱码 Workbook wb = Workbook.getWorkbook(in, wbs); //2)获取sheet工作表 Sheet[] sheets = wb.getSheets(); for(Sheet sheet:sheets){ String sheetName = sheet.getName();//获取工作表名 String fileName = sheetName.substring(0,sheetName.indexOf(".")); String suffix = sheetName.substring(sheetName.indexOf(".")+1); OutputStream zh_cn_os = new FileOutputStream(fileName + "_" + RESOURCES_ZH_CN + "." + suffix); OutputStream en_us_os = new FileOutputStream(fileName + "_" + RESOURCES_EN_US + "." + suffix); OutputStream fr_be_os = new FileOutputStream(fileName + "_" + RESOURCES_FR_BE + "." + suffix); //3) 得到行数 int rows = sheet.getRows(); for (int i = 1; i < rows; i++) { Cell[] rowCells = sheet.getRow(i);// 针对每行进行数据的加工处理 String key = ""; for (int j = 0; j < rowCells.length; j++) {// 读取值 if (j == 0) { key = rowCells[j].getContents(); } else { String value = (key + "="); String content = rowCells[j].getContents(); content = content.replace("\"", "").replace(";", ""); if ("js".equals(suffix)) { content = ("\"" + content + "\";");// 加上双引号以及分号 } value += (content+"\n"); byte[] bytes = value.getBytes("UTF-8"); if (j == 1) { en_us_os.write(bytes); }else if (j == 2) { fr_be_os.write(bytes); }else{ zh_cn_os.write(bytes); } } zh_cn_os.flush(); en_us_os.flush(); fr_be_os.flush(); } } zh_cn_os.close(); en_us_os.close(); fr_be_os.close(); } return null; }
1)通过WorkbookSettings对象的setEncoding("ISO-8859-1")来解决乱码的问题,再通过Workbook wb = Workbook.getWorkbook(in, wbs)来获取Workbook对象。记得写Excel表是什么对象来着?!对了:WritableWorkbook,这两个都代表当前的工作薄,唯一的区别就是前者是只读的,后者是可写的。
2)获取当前工作薄里所有内容不为空的Sheet集合,然后循环所有的Sheet对象,通过Sheet的名字及后缀名,生成对象的国际化文件输出流,并将Sheet内容读出写入到对应的国际化文件中。
3)获取当前工作表的所有行,然后从第二行开始遍历(第一行为对应的表头,第二行才是真正的数据)。
Cell[] rowCells = sheet.getRow(i);该方法返回当前行所有的元件集合,然后遍历该行的表格内容,每一行的第一列为国际化文件的主键值,所以通过key = rowCells[j].getContents()返回其内容,否则,则获取数值内容并通过转义保存到对应的国际化文件中。
看如下的测试代码:
eo.readExcel("i18n_2011_04_04.xls");
如果一切正常的话,项目中会成功生成如下的文件:
该博文的内容看起来虽然不少,但是真正涉及到Excel读写的代码,也就几行,倒是大量的篇幅花在了业务逻辑上 ,可以看到JXL读写Excel是多么的简单易用,可惜的是官网说明只支持Excel 95, 97, 2000,即不支持*.xlsx格式,所以,这是一个很遗憾的事情,要想读取*xlsx,还是得选择 POI,事实再一次的证明了研究POI的重要性,所以,有时间,POI再见!