写在前面
这篇是继我的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
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<#--ftl模板-->
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<#--一定要特别注意字体,很多字体部分中文不支持-->
<body screen_capture_injected="true" ryt11773="1">
<div id="header">
<div>![](${imgPath})</div>
<div id="text" sytle="font-size:30px;width:445px;height:45px;margin:0px auto;border:0px solid #ccc;"><p>STATEMENT OF ACCOUNT(Draft)</p></div>
</div>
<div>
<table style="width: 90%;margin:0px auto;font-size:11px;">
<tr>
<td><font style="font-weight:bold">Customer:</font><span style="font-family:宋体">${data.name}</span></td>
<td><font style="font-weight:bold">SOA#:</font>${data.number}</td>
<td style="width: 2%;"></td>
<td><font style="font-weight:bold">Currency:</font>${data.bill}</td>
</tr>
<tr>
<td><font style="font-weight:bold">Issud by:</font>UNI-TOP ALRLINES CO.,LTD</td>
<td><font style="font-weight:bold">Period:</font>${data.stDate} ~ ${data.edDate}</td>
</tr>
</table>
</div>
<div>
<table border="0" style="font-size:10px;text-align:center;border-collapse:collapse;">
<tr style="background-color:#FDCDCB">
<td style="border:1px solid #ccc;">NO</td>
<td style="border:1px solid #ccc;">Flight Date</td>
<td style="border:1px solid #ccc;">Flight No</td>
<td style="border:1px solid #ccc;">Prefi x No</td>
<td style="border:1px solid #ccc;">AWB NO</td>
<td style="border:1px solid #ccc;">Origin</td>
<td style="border:1px solid #ccc;">VIA</td>
<td style="border:1px solid #ccc;">Dest</td>
<td style="border:1px solid #ccc;">Gross Weight(KG)</td>
<td style="border:1px solid #ccc;">Chargeable Weight(KG)</td>
<td style="border:1px solid #ccc;">Unit Price</td>
<td style="border:1px solid #ccc;">Air Freight Charge</td>
<td style="border:1px solid #ccc;">Transfer Charge</td>
<td style="border:1px solid #ccc;">Other Charge</td>
<td style="border:1px solid #ccc;">Total</td>
<td style="border:1px solid #ccc;">Other Charge Remark</td>
</tr>
<tr style="background-color:#FDCDCB">
<td style="font-family:宋体;border:1px solid #ccc;">序号</td>
<td style="font-family:宋体;border:1px solid #ccc;">航班日期</td>
<td style="font-family:宋体;border:1px solid #ccc;">航班号</td>
<td style="font-family:宋体;border:1px solid #ccc;">货单前缀</td>
<td style="font-family:宋体;border:1px solid #ccc;">货单号</td>
<td style="font-family:宋体;border:1px solid #ccc;">货源地</td>
<td style="font-family:宋体;border:1px solid #ccc;">经停站</td>
<td style="font-family:宋体;border:1px solid #ccc;">目的地</td>
<td style="font-family:宋体;border:1px solid #ccc;">始发地(重)</td>
<td style="font-family:宋体;border:1px solid #ccc;">结算重量</td>
<td style="font-family:宋体;border:1px solid #ccc;">单价</td>
<td style="font-family:宋体;border:1px solid #ccc;">空运费</td>
<td style="font-family:宋体;border:1px solid #ccc;">国外转运费</td>
<td style="font-family:宋体;border:1px solid #ccc;">其他杂费</td>
<td style="font-family:宋体;border:1px solid #ccc;">共计</td>
<td style="font-family:宋体;border:1px solid #ccc;">备注</td>
</tr>
<#list dataList as bill>
<tr>
<td style="border:1px solid #ccc;">${bill.no}</td>
<td style="border:1px solid #ccc;">${bill.flightDate}</td>
<td style="border:1px solid #ccc;">${bill.flightNo}</td>
<td style="border:1px solid #ccc;">${bill.prefixNo}</td>
<td style="border:1px solid #ccc;">${bill.aWBNO}</td>
<td style="border:1px solid #ccc;">${bill.origin}</td>
<td style="border:1px solid #ccc;"></td>
<td style="border:1px solid #ccc;">${bill.dest}</td>
<td style="border:1px solid #ccc;">${bill.grossWeight}</td>
<td style="border:1px solid #ccc;">${bill.chargeableWeight}</td>
<td style="border:1px solid #ccc;">${bill.unit}</td>
<td style="border:1px solid #ccc;">${bill.airFreightCharge}</td>
<td style="border:1px solid #ccc;">${bill.transferCharge}</td>
<td style="border:1px solid #ccc;">${bill.otherCharge}</td>
<td style="border:1px solid #ccc;">${bill.total}</td>
<td style="font-family:宋体;border:1px solid #ccc;">${bill.remark!}</td>
</tr>
</#list>
<#if total??>
<tr>
<td style="border:1px solid #ccc;"></td>
<td style="border:1px solid #ccc;"></td>
<td style="border:1px solid #ccc;"></td>
<td style="border:1px solid #ccc;"></td>
<td style="border:1px solid #ccc;"></td>
<td style="border:1px solid #ccc;"></td>
<td style="border:1px solid #ccc;"></td>
<td style="font-family:宋体;color:red;border: 1px solid #ccc;">总计:</td>
<td style="color:red;border:1px solid #ccc;">${total.grossWeight}</td>
<td style="color:red;border:1px solid #ccc;">${total.chargeableWeight}</td>
<td style="color:red;border:1px solid #ccc;"></td>
<td style="color:red;border:1px solid #ccc;">${total.airFreightCharge}</td>
<td style="color:red;border:1px solid #ccc;">${total.transferCharge}</td>
<td style="color:red;border:1px solid #ccc;">${total.otherCharge}</td>
<td style="color:red;border:1px solid #ccc;">${total.total}</td>
<td style="color:red;border:1px solid #ccc;"></td>
</tr>
</#if>
</table>
</div>
</body>
</html>
以上代码在myeclipse中预览的效果如下:
注意:如果使用不存在的freemarker指令,FreeMarker不会使用模板输出,而是产生一个错误消息。其次,在写ftl模板的时候,因为xmlworker支持的CSS样式极少,所以模板内容要尽量简单。对于DOCTYPE和html标签的约束页比较严格。对于一个标签中含有中文、数字或英文的时候,很可能会出现问题。这是因为xmlworker在渲染PDF的时候是以html的标签为单位的。我发现有些字体下部分中文生成pdf不会显示。另外,对于freemarker模板语言不熟悉的童鞋,我会在文末贴出一些参考资料。
3.向ftl模板文件中填充数据,同时将其生成html
在业务处理层,将数据传递个ftl ,同时解析ftl模板生成html
//将需要在客户端浏览器中显示的业务数据放在一个map中,传递给FreeMarker
Map<String, Object> map = new HashMap<String, Object>();
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<String,Object> 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<DraftBill> bill = mgr.queryManuscriptPreview(topInfo);//查询表头
List<DraftBillResponse> 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<BillList> dataList = new ArrayList<BillList>();
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<String, Object> map = new HashMap<String, Object>();
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判断对象是否为空
阅读我的其他文章