写在前面
这篇是继我的Web开发实战总结(一)的第二篇文章,在此篇里,我主要总结一下如何把Web页面上的报表或列表数据转换成pdf文件下载到本地。其中涉及到的知识我也会提出来供大家交流学习。ok,开始吧~
先来看看效果
上图就是Web页面上的列表数据,将其右侧生成pdf之后的效果如下:
实现思路
这里我提出两种实现思路:
1.利用Jacob将EXCEL转成PDF
2.利用iText将HTML 转为 PDF
1.利用Jacob将EXCEL转成PDF
一开始我用的这种思路,主要是因为有生成EXCEL的功能了,想着只要利用jacob再讲EXCEL转成PDF即可,但是后来放弃了。虽然jacob可以生成pdf,word,excel等,但经过本人的实操,问题多多,还要放dll文件到bin目录下。
首先,Dispatch.call(sheet,"Activate");指定活动sheet,这个是没有问题,设置哪一个就打哪一个,但是也只是当前的一个,其他的没有显示,对于有多个SHEET的EXCEL 怎么能这一次全部转到一个PDF上?实现是可以实现:遍历sheet保存多个pdf文件,通过itextpdf再将这多个PDF合成一个,不过效率偏低。
其次,jacob是对EXCEL中的每个单元格操作的,像上面的PDF中有图片读取很不方便,就算能打出图片也可能会很模糊,而且复杂的EXCEL更是无能为力。所以我建议大家使用第二种利用iText将HTML 转为 PDF,我也是用的第二种思路实现的。
2.利用iText将HTML 转为 PDF
这个思路就是我此篇要重点要讲的,将html转成PDF,首先html有图片,还有各种数据,那么怎么将图片和各种数据填充到html里面呢?这个问题非常好,有童鞋会说,将他们追加拼接到html里,我只想说:大兄弟,别呀,这样太蠢了。这里我们可以利用 freemarker,首先创建一个FreeMarker模板文件(.ftl),在这个文件中加入FreeMarker表达式,这些表达式就好比jsp中的jstl标签一样,我们在程序中将数据传递给此文件中即可,在客户端显示时会被真实的数据替换。*下面开始实现过程了。
利用iText将HTML 转为 PDF
1.准备好生成pdf所需的jar包
CORE 包:主要是itext相关的一些核心itext.jar
XML 包:xmlworker是一个基于iText的xml生成pdf工具
freemarker包:将模板转换成html的jar包(此jar包也能将模板转换成excel,word等)
这里我将它们打包免费分享出来,下载地址:itext生成pdf所需的jar包
2.创建ftl模板文件
创建一个FreeMarker模板文件(.ftl),在这个文件中加入FreeMarker表达式,这些表达式就好比jsp中的jstl标签一样,我们在程序中将数据传递给此文件中即可,在客户端显示时会被真实的数据替换。说白了,ftl模板文件就是在html里加入了FreeMarker表达式,所以里面的内容基本跟html一样,我们可以先创建html文件,修改完成后再将文件后缀改成.ftl即可。*本文.ftl模板如下:
arDraftBillPreview.ftl
<#--ftl模板-->
<#--一定要特别注意字体,很多字体部分中文不支持-->
![](${imgPath})
STATEMENT OF ACCOUNT(Draft)
Customer:${data.name}
SOA#:${data.number}
Currency:${data.bill}
Issud by:UNI-TOP ALRLINES CO.,LTD
Period:${data.stDate} ~ ${data.edDate}
NO
Flight Date
Flight No
Prefi x No
AWB NO
Origin
VIA
Dest
Gross Weight(KG)
Chargeable Weight(KG)
Unit Price
Air Freight Charge
Transfer Charge
Other Charge
Total
Other Charge Remark
序号
航班日期
航班号
货单前缀
货单号
货源地
经停站
目的地
始发地(重)
结算重量
单价
空运费
国外转运费
其他杂费
共计
备注
<#list dataList as bill>
${bill.no}
${bill.flightDate}
${bill.flightNo}
${bill.prefixNo}
${bill.aWBNO}
${bill.origin}
${bill.dest}
${bill.grossWeight}
${bill.chargeableWeight}
${bill.unit}
${bill.airFreightCharge}
${bill.transferCharge}
${bill.otherCharge}
${bill.total}
${bill.remark!}
#list>
<#if total??>
总计:
${total.grossWeight}
${total.chargeableWeight}
${total.airFreightCharge}
${total.transferCharge}
${total.otherCharge}
${total.total}
#if>
以上代码在myeclipse中预览的效果如下:
注意:如果使用不存在的freemarker指令,FreeMarker不会使用模板输出,而是产生一个错误消息。其次,在写ftl模板的时候,因为xmlworker支持的CSS样式极少,所以模板内容要尽量简单。对于DOCTYPE和html标签的约束页比较严格。对于一个标签中含有中文、数字或英文的时候,很可能会出现问题。这是因为xmlworker在渲染PDF的时候是以html的标签为单位的。我发现有些字体下部分中文生成pdf不会显示。另外,对于freemarker模板语言不熟悉的童鞋,我会在文末贴出一些参考资料。
3.向ftl模板文件中填充数据,同时将其生成html
在业务处理层,将数据传递个ftl ,同时解析ftl模板生成html
//将需要在客户端浏览器中显示的业务数据放在一个map中,传递给FreeMarker
Map map = new HashMap();
map.put("imgPath", imgPath);
map.put("data",data);
map.put("dataList", dataList);
map.put("total", total);
TemplateParseUtil.parse(templatePath, "arDraftBillPreview.ftl", htmlPath, map);
解析ftl模板生成html(此方法与生成Excel,xml等通用),这里我写了一个工具类
TemplateParseUtil.java
public class TemplateParseUtil {
/**
* 解析模板生成html(此方法与生成Excel,xml等通用)
* @param templateDir ftl模板目录
* @param templateName ftl模板名称
* @param htmlPath 生成的html文件路径
* @param data 数据参数
* @throws IOException
* @throws TemplateException
*/
public static void parse(String templateDir,String templateName,String htmlPath,Map data) throws IOException, TemplateException {
//初始化工作
Configuration cfg = new Configuration();
//设置默认编码格式为UTF-8
cfg.setDefaultEncoding("UTF-8");
//全局数字格式
cfg.setNumberFormat("0.00");
//设置模板文件位置
cfg.setDirectoryForTemplateLoading(new File(templateDir));
cfg.setObjectWrapper(new DefaultObjectWrapper());
//加载模板
Template template = cfg.getTemplate(templateName,"utf-8");
OutputStreamWriter writer = null;
try{
//填充数据至html
writer = new OutputStreamWriter(new FileOutputStream(htmlPath),"UTF-8");
template.process(data, writer);
writer.flush();
}finally{
writer.close();
}
}
}
4.利用itext将生成的html渲染生成PDF
步骤基本如下:
// 1.新建document对象
Document document = new Document();
// 2.建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。
// 创建 PdfWriter 对象 第一个参数是对文档对象的引用,第二个参数是文件的实际名称,在该名称中还会给出其输出路径。
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("D:/test.pdf"));
// 3.打开文档
document.open();
// 4.添加一个内容段落
document.add(new Paragraph("Hello World!"));
// 5.关闭文档
document.close();
本文中的利用itext生成PDF的代码如下:
Document document = new Document(PageSize.A4, 10, 50, 10, 50);
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(servletPath+DEST));
// step 3
document.open();
// step 4
XMLWorkerHelper.getInstance().parseXHtml(writer, document,
new FileInputStream(htmlPath), Charset.forName("UTF-8"));
// step 5
document.close();
5.完整处理逻辑
可能写到这里代码有点分散,这里将上面3、4步骤的代码完整逻辑贴出来,让大家看的清晰明白点:
/**
*
* 草稿账单pdf导出
*/
@RequestMapping("/exportPDFDraftPreview")
public @ResponseBody ControllerResult> exportPDFDraftPreview(HttpServletRequest request, HttpServletResponse response) throws Exception {
ExecuteController e = new ExecuteControllerHandle() {
@Override
public ControllerResult> dowith(HttpServletRequest request,HttpServletResponse response, Object... params) throws Exception {
/**
* 具体导出步骤:先查询出具体的表头信息,然后再查询出具体的列表数据。最后整合放在具体的类里
*/
// 请求参数绑定
DraftBillResponse info = ControllerUtils.bindParams(request,DraftBillResponse.class);//返回一个草稿账单号
info.setMoneytype(info.get币种());
DraftBill topInfo = new DraftBill();
topInfo.set币种(info.get币种());
topInfo.set草稿账单编号(info.get草稿账单编号());
// 业务层调用
IFinancialManagementMgr mgr = FinancialManagementFactory.createIFinancialManagementMgrMgrImpl();
List bill = mgr.queryManuscriptPreview(topInfo);//查询表头
List list = mgr.queryDraftPreview(info); //查询列表数据
String servletPath = request.getSession().getServletContext().getRealPath("/");//请求的服务器
String templatePath = servletPath+"template";//模板目录
String htmlPath = servletPath+"tempFile\\"+info.get草稿账单编号()+".html";//生成的html地址
String imgPath = servletPath+"images\\exlTop.png";//模板中的图片
String DEST = "tempFile\\"+info.get草稿账单编号()+".pdf";//将要生成的pdf
/*DraftBill data = new DraftBill();
data = bill.get(0);*/
//进行英文转换,防止无法识别
List dataList = new ArrayList();
BillList listData = null;
BillList total = new BillList();
BillExport data = new BillExport(bill.get(0).get代理人(),bill.get(0).get草稿账单编号(),bill.get(0).get币种(),bill.get(0).get账单周期起(),bill.get(0).get账单周期止());
double num1 = 0,num2 = 0,num3 = 0,num4 = 0,num5 = 0,num6 = 0; //地重,始发重量,空运费,转运费,杂费,共计
for(DraftBillResponse res : list){//集合
double 地重 = Double.valueOf(res.get始发地重量()); //设置统计数据
double 始发重 = Double.valueOf(res.get结算重量());
double 空运费 = Double.valueOf(res.get运费());
double 转运费 = Double.valueOf(res.get国外联运费());
double 杂费 = Double.valueOf(res.get杂费());
double 统计 = Double.valueOf(res.get运费())+ Double.valueOf(res.get国外联运费())+Double.valueOf(res.get杂费());
num1 = num1 + 地重;
num2 = num2 + 始发重;
num3 = num3 + 空运费;
num4 = num4 + 转运费;
num5 = num5 + 杂费;
num6 = num6 + 统计;
listData = new BillList(res.get序号(), res.get航班日期(), res.get航班号(), res.get货单前缀(), //设置列表数据
res.get货单号(), res.get货源地(), "", res.get目的站(), res.get始发地重量(),
res.get结算重量(), res.get空运费费率(), res.get运费(),
res.get国外联运费(),res.get杂费(), String.valueOf(CommonUtil.toDecimalFormat(统计)),
res.get备注());
dataList.add(listData);
}
//将统计的数据放进实体
total.setGrossWeight(String.valueOf(CommonUtil.toDecimalFormat(num1)));
total.setChargeableWeight(String.valueOf(CommonUtil.toDecimalFormat(num2)));
total.setAirFreightCharge(String.valueOf(CommonUtil.toDecimalFormat(num3)));
total.setTransferCharge(String.valueOf(CommonUtil.toDecimalFormat(num4)));
total.setOtherCharge(String.valueOf(CommonUtil.toDecimalFormat(num5)));
total.setTotal(String.valueOf(CommonUtil.toDecimalFormat(num6)));
//将需要在客户端浏览器中显示的业务数据放在一个map中,传递给FreeMarker
Map map = new HashMap();
map.put("imgPath", imgPath);
map.put("data",data);
map.put("dataList", dataList);
map.put("total", total);
TemplateParseUtil.parse(templatePath, "arDraftBillPreview.ftl", htmlPath, map);
Document document = new Document(PageSize.A4, 10, 50, 10, 50);
// step 2
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(servletPath+DEST));
// step 3
document.open();
// step 4
XMLWorkerHelper.getInstance().parseXHtml(writer, document,
new FileInputStream(htmlPath), Charset.forName("UTF-8"));
// step 5
document.close();
return ControllerUtils.buildSimpleResult(true, DEST);
}
};
在上面的程序中,包括PDF上的图片,表头及表身数据都传给ftl模板中了,在生成PDF之前,都会先生成一个.html的文件到tempFile的文件夹下,如下:
写在最后
其实整个过程都比较简单,难就难在一开始你不知道用那种方式去实现,这种时候我建议你都试试,毕竟一个东西你试过之后才知道好不好,适不适合。还有一点就是,对于你不知道的东西,网上一般都有很多参考资料,一定要善于利用搜索引擎学习。关于学习,就三点:坚持,坚持,坚持。
下面列出一些相关链接供大家参考:
iText入门
动态jsp页面转PDF输出到页面
最简单 iText 的 PDF 生成方案(含中文解决方案)HTML 转为 PDF
ftl 入门
Freemarker 最简单的例子程序
FreeMarker 例子
freemarker生成excel、word、html、xml实例教程
freemarker判断对象是否为空
阅读我的其他文章
- 1024程序员节,向改变世界的程序员致敬
- 程序员的你是否熟练掌握Chrome开发者工具?
- UML学习归纳整理