简单说下java生成word文档的各个组件优缺点(详细网上有很多),POI、JXL等过于原生,如果制作简单的几页word文档还能接受,如果文档十几二十页。。。会头疼死,并且word一旦大了以后,样式很容易乱,很不美观;freemarker利用模板生成word文档,开发相对简单,但是freemarker是利用xml标签传入模板的,一旦在模板里加了部分标签(例如 list),模板就不可以用office打开了,再去调整模板样式,或者增加内容,又或者需要增加很多list,会无从下手,除非对xml标签很熟练。。。。
所以以下所有工作均是针对复杂的word文档
首先说下报告制作的总体思路:使用freemarker进行模板式开发,以数据列表为界限(或者章节,具体以业务为准)将word模板拆分为多个模板,方便后期业务要求调整模板,docx4j进行多个文档合并,POI调整文档细节或者插入图片等。
注意事项:
下面是封装的相关方法(都是我实际代码用的,拷贝时请稍加调整):
1、创建单个docx文档:
/**
*
* 描述: 生成docx文件
* @author XXX
* @date 2018年8月13日
* @param templateName 模板名称 (请勿带后缀)
* @param templatePath 模板路径:请从templates下开始填写
* @param userCode 用户名称:用以区分生成的文件
* @param sign 标记创建报告的类型 (gjfs-国检分省,)
* @param date 报告创建日期:请精确至秒
* @param data 报告填充的数据
* @return
* @throws FileNotFoundException
*/
public static String creatDocx(String templateName,String templatePath,String userCode,String sign,String date,Map data) throws FileNotFoundException {
//获取跟目录
File path = new File(ResourceUtils.getURL("classpath:").getPath());
//如果上传目录为/static/images/upload/,则可以如下获取:
File upload = new File(path.getAbsolutePath(),"templates/"+templatePath);
if(!upload.exists()) upload.mkdirs();
System.out.println("upload url:"+upload.getAbsolutePath());
// 路径
String templatepath = upload.getAbsolutePath();
String docxname = "test.docx";//空白docx文件即可
//String templateName = "test.xml";模板名称
//结果文件
String resxmlpath = "d:/report/"+userCode+"/sign/"+templateName+"(date).xml";
String reswordpath = "d:/report/"+userCode+"/sign/"+templateName+"(date).docx";
//如果文件不存在则自动创建
File xmlFile = new File("d:/report/"+userCode+"/sign/");
File docxFile = new File("d:/report/"+userCode+"/sign/");
if(!xmlFile.exists()) xmlFile.mkdirs();
if(!docxFile.exists()) docxFile.mkdirs();
// 生成文档
try {
File file = generate(templatepath, docxname, templateName+".xml", resxmlpath, reswordpath, data);
return reswordpath;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static File generate(String templatepath, String docxname, String xmlname,
String resxmlpath, String reswordpath, Map param) throws Exception {
Configuration cfg = new Configuration();
cfg.setDirectoryForTemplateLoading(new File(templatepath));
Template template = cfg.getTemplate(xmlname);
template.setOutputEncoding("UTF-8");
Writer out = new FileWriter(new File(resxmlpath));
// 数据放到模板xml里面,生成带数据的xml
template.process(param, out);
if (out != null) {
out.close();
}
// 带数据的xml生成docx
File file = new File(resxmlpath);
File docxFile = new File(templatepath + "/" + docxname);
ZipFile zipFile = new ZipFile(docxFile);
Enumeration extends ZipEntry> zipEntrys = zipFile.entries();
ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(reswordpath));
int len = -1;
byte[] buffer = new byte[1024];
while (zipEntrys.hasMoreElements()) {
ZipEntry next = zipEntrys.nextElement();
InputStream is = zipFile.getInputStream(next);
// 把输入流的文件传到输出流中 如果是word/document.xml由我们输入
zipout.putNextEntry(new ZipEntry(next.toString()));
if ("word/document.xml".equals(next.toString())) {
InputStream in = new FileInputStream(file);
while ((len = in.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
in.close();
} else {
while ((len = is.read(buffer)) != -1) {
zipout.write(buffer, 0, len);
}
is.close();
}
}
zipout.close();
return new File(reswordpath);
}
2、合并多个docx文档:
/**
*
* 描述:合并多个docx文件
* @author 范相如
* @date 2018年8月13日
* @param list 分文档路径地址
* @param path 合并后最终文档路径
* @return
*/
public static File mergeDocx(List list,String path){
List inList=new ArrayList();
for(int i=0;i streams)
throws Docx4JException, IOException {
WordprocessingMLPackage target = null;
final File generated = File.createTempFile("generated", ".docx");
int chunkId = 0;
Iterator it = streams.iterator();
while (it.hasNext()) {
InputStream is = it.next();
if (is != null) {
if (target == null) {
// Copy first (master) document
OutputStream os = new FileOutputStream(generated);
os.write(IOUtils.toByteArray(is));
os.close();
target = WordprocessingMLPackage.load(generated);
} else {
// Attach the others (Alternative input parts)
insertDocx(target.getMainDocumentPart(),
IOUtils.toByteArray(is), chunkId++);
}
}
}
if (target != null) {
target.save(generated);
return new FileInputStream(generated);
} else {
return null;
}
}
private static void saveTemplate(InputStream fis,String toDocPath){
FileOutputStream fos;
int bytesum = 0;
int byteread = 0;
try {
fos = new FileOutputStream(toDocPath);
byte[] buffer = new byte[1444];
while ( (byteread = fis.read(buffer)) != -1) {
bytesum += byteread; //字节数 文件大小
fos.write(buffer, 0, byteread);
}
fis.close();
fos.close();
} catch (FileNotFoundException e1) {
e1.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
// 插入文档
private static void insertDocx(MainDocumentPart main, byte[] bytes, int chunkId) {
try {
AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(
new PartName("/part" + chunkId + ".docx"));
// afiPart.setContentType(new ContentType(CONTENT_TYPE));
afiPart.setBinaryData(bytes);
Relationship altChunkRel = main.addTargetPart(afiPart);
CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk();
chunk.setId(altChunkRel.getId());
main.addObject(chunk);
} catch (Exception e) {
e.printStackTrace();
}
}
3、创建图片(使用的jfree):
/**
*
* 描述: 创建报告所需柱状图
* @author XXX
* @date 2018年8月21日
* @param pqi
* @param pci
* @param rqi
* @param rdi
* @param userCode
*/
public static String createImg(String pqi,String pci,String rqi,String rdi,String userCode) {
//数据集
DefaultCategoryDataset dataSet = new DefaultCategoryDataset();
dataSet.addValue(Double.valueOf(pqi), "", "PQI");
dataSet.addValue(Double.valueOf(pci), "", "PCI");
dataSet.addValue(Double.valueOf(rqi), "", "RQI");
if(null!=null || !rdi.equals("")) {
dataSet.addValue(Double.valueOf(rdi), "", "RDI");
}
// 柱状图
JFreeChart jfreeChart = ChartFactory.createBarChart
("", "", "", dataSet, PlotOrientation.VERTICAL, false,false, false);
CategoryPlot plot = jfreeChart.getCategoryPlot();
// 初始化柱子颜色
String[] colorValues = getColors( pqi,pci,rqi,rdi);
CustomRender renderer = new CustomRender(colorValues);
renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setBaseItemLabelsVisible(true);
renderer.setBaseItemLabelPaint(Color.BLACK);//设置数值颜色,默认黑色
// 设置柱子宽度
renderer.setMaximumBarWidth(0.3);
plot.setRenderer(renderer);//将修改后的属性值保存到图中
// 设置总的背景颜色
jfreeChart.setBackgroundPaint(ChartColor.WHITE);
// 获得图表对象
CategoryPlot p = jfreeChart.getCategoryPlot();
// 设置图的背景颜色
p.setBackgroundPaint(ChartColor.WHITE);
// 设置表格线颜色
p.setRangeGridlinePaint(ChartColor.BLACK);
try{
// 保存图片到指定文件夹"d:/report/"+userCode+"/sign/"
ChartUtilities.saveChartAsPNG(new File("d:/report/"+userCode+"/sign/BarChart.png"), jfreeChart, 500, 300);
return "d:/report/"+userCode+"/sign/BarChart.png";
} catch (Exception e){
System.err.println("Problem occurred creating chart.");
}
return "";
}
4、根据特殊字符替换为图片:
/**
* 替换word中的自定义字符串以及图片(适用于word2003+ 版本)
*
* 注:2003版本word不支持替换图片,2007版本以上可以替换图片
*
* @param filePath
* @param param
* @param fileName
* @param request
* @param response
* @return
*/
public static String replaceAndGenerateWord(String filePath, Map param, String fileName,
HttpServletRequest request, HttpServletResponse response){
String[] sp = filePath.split("\\.");
//判断文件是否有后缀名
if(sp.length > 0){
try{
//处理docx文档 2007-2013
if(sp[sp.length - 1].equalsIgnoreCase("docx")){
CustomXWPFDocument document = null;
OPCPackage pack = POIXMLDocument.openPackage(filePath);
document = new CustomXWPFDocument(pack);
if (param != null && param.size() > 0) {
//处理段落
List paragraphList = document.getParagraphs();
processParagraphs(paragraphList, param, document);
//处理表格
Iterator it = document.getTablesIterator();
while (it.hasNext()) {
XWPFTable table = it.next();
List rows = table.getRows();
for (XWPFTableRow row : rows) {
List cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
List paragraphListTable = cell.getParagraphs();
processParagraphs(paragraphListTable, param, document);
}
}
}
createDir(tempFilePath);
FileOutputStream fos = new FileOutputStream(new File(tempFilePath.concat(fileName)));
document.write(fos);
fos.flush();
fos.close();
doExport(fileName, tempFilePath.concat(fileName), request, response);
return tempFilePath.concat(fileName);
}
//处理doc文档 97-2003
}else if(sp[sp.length - 1].equalsIgnoreCase("doc")){
HWPFDocument document = null;
document = new HWPFDocument(new FileInputStream(filePath));
Range range = document.getRange();
for (Map.Entry entry : param.entrySet()) {
Object value = entry.getValue();
if(value instanceof String){
range.replaceText(entry.getKey(), entry.getValue().toString());
}else if(value instanceof Map){
//TODO word2003暂时不能处理图片
}
}
createDir(tempFilePath);
FileOutputStream fos = new FileOutputStream(new File(tempFilePath.concat(fileName)));
document.write(fos);
fos.flush();
fos.close();
doExport(fileName, tempFilePath.concat(fileName), request, response);
return tempFilePath.concat(fileName);
}
}catch(Exception e){
return "fail";
}
}else{
return "fail";
}
return "success";
}
/**
* 处理段落
* @param paragraphList
* @throws FileNotFoundException
* @throws InvalidFormatException
*/
public static void processParagraphs(List paragraphList,Map param,CustomXWPFDocument doc)
throws InvalidFormatException, FileNotFoundException{
if(paragraphList != null && paragraphList.size() > 0){
//首选循环段落
for(XWPFParagraph paragraph:paragraphList){
//获取段落的text
boolean needDel = false;
String text = paragraph.getText();
if(text != null){
for (Entry entry : param.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
//替换
if(value instanceof String){
String text2 = text.replace(key, value.toString());
if(!text2.equals(text)){
needDel = true;
}
text = text2;
}else if(value instanceof Map){
if(text.indexOf(key) != -1){
//特殊处理图片
int length = paragraph.getRuns().size();
//将原有的Run去掉
if (length > 0) {
for (int i = (length - 1); i >= 0; i--) {
paragraph.removeRun(i);
}
}
String imgurl = (String)((Map, ?>) value).get("content");
String type = (String)((Map, ?>) value).get("type");
int width = (Integer) ((Map, ?>) value).get("width");
int height = (Integer) ((Map, ?>) value).get("height");
String blipId = doc.addPictureData(new FileInputStream(new File(imgurl)), getPictureType(type));
doc.createPicture(blipId,doc.getNextPicNameNumber(getPictureType(type)), width, height,paragraph);
}
}
}
}
int length = paragraph.getRuns().size();
//将原有的Run去掉(原因是paragraph将XWPFRun分割成了一个乱七八糟的数组,例:${1}$,这个获取出来的是[$,{,1,},$],不能满足我们替换的要求,这里进行特殊处理)
if(needDel){
if (length > 0) {
for (int i = (length - 1); i >= 0; i--) {
paragraph.removeRun(i);
}
//在段落里面插入我们替换过后的文本
XWPFRun newRun = paragraph.insertNewRun(0);
newRun.setText(text, 0);
paragraph.addRun(newRun);
}
}
}
}
}
/**
* 根据图片类型,取得对应的图片类型代码
* @param picType
* @return int
*/
private static int getPictureType(String picType){
int res = CustomXWPFDocument.PICTURE_TYPE_PICT;
if(picType != null){
if(picType.equalsIgnoreCase("png")){
res = CustomXWPFDocument.PICTURE_TYPE_PNG;
}else if(picType.equalsIgnoreCase("dib")){
res = CustomXWPFDocument.PICTURE_TYPE_DIB;
}else if(picType.equalsIgnoreCase("emf")){
res = CustomXWPFDocument.PICTURE_TYPE_EMF;
}else if(picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")){
res = CustomXWPFDocument.PICTURE_TYPE_JPEG;
}else if(picType.equalsIgnoreCase("wmf")){
res = CustomXWPFDocument.PICTURE_TYPE_WMF;
}
}
return res;
}
/**
* 导出
*
* @param fileName
* @param filePath
* @param request
* @param response
*/
public static void doExport(String fileName, String filePath, HttpServletRequest request, HttpServletResponse response){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
File file = null;
// HttpServletResponse response = (HttpServletResponse)RpcContext.getContext().getResponse(HttpServletResponse.class);
try {
file = new File(filePath);
// HttpServletRequest request = (HttpServletRequest)RpcContext.getContext().getRequest(HttpServletRequest.class);
request.setCharacterEncoding("UTF-8");
String agent = request.getHeader("User-Agent").toUpperCase();
if ((agent.indexOf("MSIE") > 0) || ((agent.indexOf("RV") != -1) && (agent.indexOf("FIREFOX") == -1)))
fileName = URLEncoder.encode(fileName, "UTF-8");
else {
fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
}
response.setContentType("application/x-msdownload;");
response.setHeader("Content-disposition", "attachment; filename=" + fileName);
response.setHeader("Content-Length", String.valueOf(file.length()));
bis = new BufferedInputStream(new FileInputStream(file));
bos = new BufferedOutputStream(response.getOutputStream());
byte[] buff = new byte[2048];
int bytesRead;
while (-1 != (bytesRead = bis.read(buff, 0, buff.length)))
bos.write(buff, 0, bytesRead);
}
catch (Exception e) {
// System.out.println("导出文件失败!");
} finally {
try {
if (bis != null) {
bis.close();
}
if (bos != null) {
bos.close();
}
//file.delete();
} catch (Exception e) {
// LOGGER.error("导出文件关闭流出错!", e);
}
}
}
/**
* 创建目录
* @param basePath
*/
public static void createDir(String basePath)
{
File file = new File(basePath);
if (!file.exists())
file.mkdirs();
}
5、示例代码:
String file1 = DocxUtils.creatDocx("0-首页", "report/gjfs/", userCode, "", "", dataMap1);
String file2 = DocxUtils.creatDocx("1-1.概况", "report/gjfs/", userCode, "", "", dataMap2);
String file3 = DocxUtils.creatDocx("2-2.11概述图1表2", "report/gjfs/", userCode, "", "", dataMap3);
String file4 = DocxUtils.creatDocx("2-2.12表3", "report/gjfs/", userCode, "", "", dataMap4);
String file5 = DocxUtils.creatDocx("2-2.13表4", "report/gjfs/", userCode, "", "", dataMap5);
String file6 = DocxUtils.creatDocx("2-2.14表5", "report/gjfs/", userCode, "", "", dataMap6);
String file7 = DocxUtils.creatDocx("2-2.21图2表6", "report/gjfs/", userCode, "", "", dataMap7);
String file8 = DocxUtils.creatDocx("2-2.22表7", "report/gjfs/", userCode, "", "", dataMap8);
String file9 = DocxUtils.creatDocx("2-2.22表8", "report/gjfs/", userCode, "", "", dataMap9);
String file11 = DocxUtils.creatDocx("3-附件1、路况评定PQI计算方法说明", "report/gjfs/", userCode, "", "", dataMap11);
session.setAttribute(uid, "60");
//生成docx文档,方便后期调整样式或插入图片
String reswordpath = "d:/report/"+userCode+"/sign/result.docx";
List list=new ArrayList();
list.add(file1);
list.add(file2);
//file3创建图片,并替换图1
String imgPath = DocxUtils.createImg(dataMap3.get("pqi")+"", dataMap3.get("pci")+"",
dataMap3.get("rqi")+"", dataMap3.get("rdi")+"", userCode);
Map param = new HashMap();
Map header = new HashMap();
header.put("width", 500);
header.put("height", 350);
header.put("type", "png");
header.put("content", imgPath);
param.put("imgoo",header);
file3 = DocxUtils.replaceAndGenerateWord(file3, param, "result1.docx", null, null);
list.add(file3);
list.add(file4);
list.add(file5);
list.add(file6);
//file7创建图片,并替换图1
String imgPath1 = DocxUtils.createImg(dataMap7.get("pqi")+"", dataMap7.get("pci")+"",
dataMap7.get("rqi")+"", "", userCode);
Map param1 = new HashMap();
Map header1 = new HashMap();
header1.put("width", 500);
header1.put("height", 350);
header1.put("type", "png");
header1.put("content", imgPath1);
param1.put("imgoo",header1);
file7 = DocxUtils.replaceAndGenerateWord(file7, param1, "result2.docx", null, null);
list.add(file7);
list.add(file8);
list.add(file9);
list.add(file11);
File file = DocxUtils.mergeDocx(list, reswordpath);
return file;
以上代码研究了将近两个星期,发现java开源组件在操作word上还是挺鸡肋的,另外jacob等组件没有使用,是因为我们是linux服务器。。。。。。。。。