既然使用的是csdn的博客,自然要给其来篇软文,多个广告啥的。
本项目已托管于https://code.csdn.net/代码托管平台,附git地址git://code.csdn.net/wangyuheng77/excel2word.git
本文除了功能完善与代码优化外,还会介绍如何将程序打包成jar包以及生成.exe文件。
模板的问题有两点:
第一点就是神奇的word格式。在另存为的过程中,一不小心(如:删除又增加一个字符),就会多余的格式标签。
另一个问题就是,模板的制作过程。居然需要写完这么复杂的标签后,还需要完成另存为.xml这么高难度的事情,简直是泯灭人性。
针对这两点,我一开始的思路是:
读取word文件,替换标签,io生成.xml文件,freemarker替换生成word文件。
这个尝试过程略过,在经历了痛苦的尝试之后,我终于醒悟(请原谅我的愚笨):为毛一定要用template.xml和freemarker啊?!
也许是网上大神们给的参考文章,让人变得懒得去思考。但现在静下心来一想,完全可以在读取word文件之后,替换固定标签,再通过io流生成新的word文件。
难点在于替换固定标签,不然这就是一个简单的io复制操作。
思路确定后,还是一步一步来,先从excel文件的数据读取开始,也就是传说中的XlsUtil.java。
之前是按照列读取,并且把第一列默认为文件名,实在是不够灵活。
这次给出的解决思路是:第一行作为标签的key,如
这里有两点需要注意的地方:
1、空格列,空格列,需要略过这列的内容。
2、非String格式,如1会被转换为1.0.解决方法:row.getCell(i).setCellType(Cell.CELL_TYPE_STRING);
读取到的map,key已经加上了我们指定的标签格式,如{“#{name}”:“zhangsan”,#{tel}:110}
关于生成的word文件命名的问题,我给出的方案是:如果用户未在excel文件中指定name标签(上图A1格),则默认第一列为name。
XlsUtil代码如下:
package excel2word.utils;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
public class XlsUtil {
public static Map<String, Object> extractRow(Row row, List<String> keys){
Map<String, Object> params = new HashMap<String, Object>();
for (int i=0; i<=row.getLastCellNum(); i++) {
if (StringUtils.isBlank(keys.get(i))) {
continue;
}
params.put("#{"+keys.get(i)+"}", row.getCell(i));
}
return params;
}
/**
* 第一行作为key,即mark标签名
*/
private static List<String> getKeyList(Row row) {
List<String> keys = new ArrayList<String>();
for (int i=0; i<row.getLastCellNum();i++) {
if (row.getCell(i) != null) {
row.getCell(i).setCellType(Cell.CELL_TYPE_STRING);
}
keys.add(row.getCell(i)==null?"":row.getCell(i).toString().toLowerCase());
}
//如果未指定name标签,则默认第一列为生成的word文件名
if (!keys.contains("name")) {
keys.set(0, "name");
}
return keys;
}
public static List<Map<String, Object>> extractXls(FileInputStream fis) throws IOException{
List<Map<String, Object>> results = new ArrayList<Map<String,Object>>();
try {
Workbook book = new HSSFWorkbook(new BufferedInputStream(fis));
Sheet sheet = book.getSheetAt(0);
if (sheet.getLastRowNum() == 0) {
return null;
}
List<String> keys = getKeyList(sheet.getRow(0));
for (int i=sheet.getFirstRowNum()+1; i <= sheet.getLastRowNum(); i++) {
Map<String, Object> param = extractRow(sheet.getRow(i), keys);
results.add(param);
}
} catch (IOException e) {
throw e;
}
return results;
}
}
然后是替换标签,生成word文件。
采用POI已经封装好的方法,HWPFDocument类,获取Range,然后采用range.replaceText()替换标签,最后再由HWPFDocument做write操作。
流程很简单,代码也简单,一看就明白了
DocUtil代码如下:
package excel2word.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.usermodel.Range;
import excel2word.config.constants.WorkConstant;
import excel2word.config.enumerations.FileType;
public class DocUtil {
private static File getRenameFile(String path, FileType type, String docName) {
//解决重名问题
int i = 1;
File doc = new File(path + docName+type.getLabel());
while (doc.exists()) {
doc = new File(path + docName+"("+i+")"+type.getLabel());
i++;
}
return doc;
}
public static void export(Map<String, Object> params, File template, FileType exportType, String exportName) {
FileOutputStream fos = null;
try {
HWPFDocument doc = new HWPFDocument(new FileInputStream(template));
Range range = doc.getRange();
for (String key : params.keySet()) {
range.replaceText(key, params.get(key).toString());
}
fos = new FileOutputStream(getRenameFile(WorkConstant.WORK_PATH, exportType, exportName));
doc.write(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
fos = null;
}
}
}
}
}
最后再看一下MainUtil怎么调用这两个工具类,增加了对template文件的检索,如果没有03的office,则搜索更高级的office文件。
FileType代码如下:
package excel2word.config.enumerations;
public enum FileType {
XML_TYPE(".xml"),
WORD_03_TYPE(".doc"),
WORD_07UP_TYPE(".doc"),
EXCEL_03_TYPE(".xls"),
EXCEL_07UP_TYPE(".xls")
;
private String label;
private FileType(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
MainUtil代码如下:
package excel2word;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import excel2word.config.constants.WorkConstant;
import excel2word.config.enumerations.FileType;
import excel2word.utils.DocUtil;
import excel2word.utils.XlsUtil;
public class MainUtil {
public static void main(String[] args) {
String xlsPath = WorkConstant.WORK_PATH+WorkConstant.TEMPLATE_NAME+FileType.EXCEL_03_TYPE.getLabel();
String docPath = WorkConstant.WORK_PATH+WorkConstant.TEMPLATE_NAME+FileType.WORD_03_TYPE.getLabel();
try {
List<Map<String, Object>> xlsList = XlsUtil.extractXls(new FileInputStream(new File(xlsPath)));
for (Map<String, Object> params : xlsList) {
DocUtil.export(params, new File(docPath), FileType.WORD_03_TYPE, params.get("#{name}").toString());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码看完了,有没有觉得之前的圈子绕的都太远了?
看来,思考确实比写代码更重要。
打JAR包:
在dajar包之前,最后做一些改变:将模板路径设置为当前执行目录,通过System.getProperty("user.dir")获取,并且要注意需要增加File.separator
WorkConstant代码如下:
package excel2word.config.constants;
import java.io.File;
public class WorkConstant {
public static final String WORK_PATH = System.getProperty("user.dir") +File.separator;
}
删除了TEMPLATE_NAME 常量,希望通过扫描当前目录,检索出word和excel作为模板文件
需要检索出一个word和excel文件,不能多也不能少,而且可能是03or07+的格式,逻辑比较绕,没有想到较好的方式。
修改后的MainUtil代码如下:
package excel2word;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import excel2word.config.constants.WorkConstant;
import excel2word.config.enumerations.FileType;
import excel2word.utils.DocUtil;
import excel2word.utils.XlsUtil;
public class MainUtil {
private static List<File> totalList = new ArrayList<File>();
public static void main(String[] args) {
Map<FileType, List<File>> param = new HashMap<FileType, List<File>>();
for (FileType type : FileType.values()) {
fillParam(totalList, param, type);
}
if (totalList.size() < 2) {
System.out.println("找不到模板文件");
} else if (2 == totalList.size() && 1 == (param.get(FileType.WORD_03_TYPE).size() + param.get(FileType.WORD_07UP_TYPE).size())) {
dealParam(param);
} else {
for (File file : totalList) {
System.out.println("无法鉴别模板文件");
System.out.println(file.getName());
}
}
}
private static void dealParam(Map<FileType, List<File>> param) {
FileType excelType = param.get(FileType.EXCEL_03_TYPE).size() > 0 ? FileType.EXCEL_03_TYPE : FileType.EXCEL_07UP_TYPE;
FileType wordType = param.get(FileType.WORD_03_TYPE).size() > 0 ? FileType.WORD_03_TYPE : FileType.WORD_07UP_TYPE;
File excelFile = param.get(excelType).get(0);
File wordFile = param.get(wordType).get(0);
try {
List<Map<String, Object>> xlsList = XlsUtil.extractXls(new FileInputStream(excelFile));
for (Map<String, Object> params : xlsList) {
DocUtil.export(params, wordFile, wordType, params.get("#{name}").toString());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
totalList.clear();
}
}
private static void fillParam(List<File> totalList, Map<FileType, List<File>> param, FileType type) {
param.put(type, scanFileBySuffix(totalList, type));
}
private static List<File> scanFileBySuffix(List<File> totalList, FileType suffix) {
List<File> files = new ArrayList<File>();
File dir = new File(WorkConstant.WORK_PATH);
//考虑过files.size()>0即中止,返回List,可以把信息反馈给用户。
if (dir.exists() && dir.isDirectory()){
for (File file : dir.listFiles()) {
if (!file.isDirectory() && file.getAbsolutePath().endsWith(suffix.getLabel())) {
files.add(file);
}
}
}
totalList.addAll(files);
return files;
}
}
这里先介绍通过eclipse生成jar包的方式
第一步:右键单击项目,选择Export;
第二步:因为是要让他人可以通过命令行使用,所以选择Runnable JAR file
第三步:选择打包项目的Main方法类,指定输出路径,并且指定第三方lib的处理方式
有人可能不太理解Library handling的三种方式有什么区别,我来解释一下
Extract required libraries into generated JAR:提取出引用到的第三方JAR包的class,并打入项目生成的jar包中
Package required libraries into generated JAR:将引用到的第三方JAR包仍以JAR包形式放入生成的jar包中
Copy required libraries into a sub-folder next to the generated JAR:将引用到的第三方JAR包复制到上级目录的一个文件中。
翻译的很抽象?看一眼图就明白了
至于ant脚本,在我们介绍到ant的时候在介绍。
第一种方式,更方便查看第三方lib的源码(好吧,是我自己的理解),第二种则所占空间较少,第三种,额,可以很直观的知道自己的项目有多少自己的代码。。。
打包完成,我们来执行一下,命令行到jar包路径,执行
java -jar excel2word.jar
运行成功。
怎么通过命令行执行打jar包操作:
略(因为不会)。。。。。
怎么将jar打包成.exe?
附录:http://blog.csdn.net/luoweifu/article/details/7628006 大神介绍的很详细
也许你觉得这就可以了? 永远不要低估一颗程序员玩命折腾的心。。。