知识站

 找回密码
 立即注册
知识站 首页 java 查看内容

Java生成复杂word文档 结合freemarker+docx4j+POI

2020-3-3 21:01| 发布者: admin| 查看: 27| 评论: 0

       简单说下java生成word文档的各个组件优缺点(详细网上有很多),POI、JXL等过于原生,如果制作简单的几页word文档还能接受,如果文档十几二十页。。。会头疼死,并且word一旦大了以后,样式很容易乱,很不美观;freemarker利用模板生成word文档,开发相对简单,但是freemarker是利用xml标签传入模板的,一旦在模板里加了部分标签(例如 list),模板就不可以用office打开了,再去调整模板样式,或者增加内容,又或者需要增加很多list,会无从下手,除非对xml标签很熟练。。。。

所以以下所有工作均是针对复杂的word文档

        首先说下报告制作的总体思路:使用freemarker进行模板式开发,以数据列表为界限(或者章节,具体以业务为准)将word模板拆分为多个模板,方便后期业务要求调整模板,docx4j进行多个文档合并,POI调整文档细节或者插入图片等。

注意事项:

报告模板划分时,以数据列表为界限(章节也可以),避免出现单个模板中存在复杂word内容。
Freemarker生成分word文档时,一定要生成docx格式,即2007版以上的word文档。
Freemarker生成docx与doc文档的模板获取方法不同。
文档合并方法支持生成doc和docx格式,建议生成docx,方便POI或freemarker再调整文档格式(实际上我是先生成各个word模板,然后分别插入图片或调整样式,最后合并文档)。
项目使用时,注意方法中使用的临时路径信息可用。
下面是封装的相关方法(都是我实际代码用的,拷贝时请稍加调整):

1、创建单个docx文档:

/**
* <p>描述: 生成docx文件</p>  
* @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<String,Object> 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<String, Object> 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文档:

/**
* <p>描述:合并多个docx文件 </p>  
* @author 范相如  
* @date 2018年8月13日  
* @param list  分文档路径地址
* @param path 合并后最终文档路径
* @return
*/
public static File mergeDocx(List<String> list,String path){
List<InputStream>  inList=new ArrayList<InputStream>();
for(int i=0;i<list.size();i++)
try {
inList.add(new FileInputStream(list.get(i)));
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
InputStream   inputStream=mergeDocx(inList);
saveTemplate(inputStream,path);
} catch (Docx4JException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new File(path);
}
private static InputStream mergeDocx(final List<InputStream> streams)  
throws Docx4JException, IOException {  
 
WordprocessingMLPackage target = null;  
final File generated = File.createTempFile("generated", ".docx");  
 
int chunkId = 0;  
Iterator<InputStream> 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):

/**
* <p>描述: 创建报告所需柱状图</p>  
* @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<String, Object> 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<XWPFParagraph> paragraphList = document.getParagraphs();  
processParagraphs(paragraphList, param, document);                    
//处理表格  
Iterator<XWPFTable> it = document.getTablesIterator();  
while (it.hasNext()) {  
XWPFTable table = it.next();  
List<XWPFTableRow> rows = table.getRows();  
for (XWPFTableRow row : rows) {  
List<XWPFTableCell> cells = row.getTableCells();  
for (XWPFTableCell cell : cells) {  
List<XWPFParagraph> 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<String, Object> 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<XWPFParagraph> paragraphList,Map<String, Object> 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<String, Object> 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<String> list=new ArrayList<String>();
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<String, Object> param = new HashMap<String, Object>();  
Map<String,Object> header = new HashMap<String, Object>();  
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<String, Object> param1 = new HashMap<String, Object>();  
Map<String,Object> header1 = new HashMap<String, Object>();  
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服务器
发表评论

最新评论

Archiver|知识站 ( 鲁ICP备20004068号-1 )

鲁公网安备 37132502371390号

GMT+8, 2020-6-23 22:50 , Processed in 0.019733 second(s), 17 queries .

返回顶部